From 6056a4a5af292ba404385c8d39f804283157ee98 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 7 Jun 2026 13:29:39 +0300 Subject: [PATCH] =?UTF-8?q?feat(form-decompile,form-compile):=20UseAlways?= =?UTF-8?q?=20=E2=80=94=20=D0=BF=D0=BE=D0=BB=D1=8F=20=D1=80=D0=B5=D0=BA?= =?UTF-8?q?=D0=B2=D0=B8=D0=B7=D0=B8=D1=82=D0=B0,=20=D0=B2=D1=81=D0=B5?= =?UTF-8?q?=D0=B3=D0=B4=D0=B0=20=D1=87=D0=B8=D1=82=D0=B0=D0=B5=D0=BC=D1=8B?= =?UTF-8?q?=D0=B5=20(=D0=B4=D0=B2=D0=B5=20=D1=84=D0=BE=D1=80=D0=BC=D1=8B?= =?UTF-8?q?=20DSL)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ИмяРеквизита.Поле…> у Attribute (5189: дин-список 3575 + ValueTable ~788 + прочие) — не захватывался. Свойство «поля, всегда читаемые из БД». DSL — две формы (сливаются компилятором): - на реквизите: useAlways: ["Поле1","Поле2"] (короткие имена; forgiving с/без префикса); - на колонке ValueTable: useAlways: true (columns[*]). Компилятор собирает ИмяРеквизита.X из обоих источников (dedupe), порядок схемы: после FillChecking, до FunctionalOptions/Columns/Settings. Дин-список: колонки в XML не эмитятся, но если заданы в DSL с useAlways — формируют UseAlways-массив (Columns подавляются при наличии settings). Декомпилятор по контексту: ValueTable (есть columns) → useAlways:true на совпавшей колонке; дин-список/прочие → массив useAlways на реквизите. Префикс «Имя.» снимается. TOTAL diff lines выборки 2.17: 3869 → 3695 (-174), match 14 → 17 (+3 чистых формы). Attribute>UseAlways residual → 0. Снапшот table (обе формы + merge) сертифицирован в 1С (8.3.24). Регресс form-compile 33/33 зелёный на ps + python. decompile v0.36, compile v1.54. Co-Authored-By: Claude Opus 4.8 --- .../form-compile/scripts/form-compile.ps1 | 32 +++++++++++++++++-- .../form-compile/scripts/form-compile.py | 28 ++++++++++++++-- .../form-decompile/scripts/form-decompile.ps1 | 25 ++++++++++++++- docs/form-dsl-spec.md | 1 + .../Таблица/Forms/Форма/Ext/Form.xml | 4 +++ tests/skills/cases/form-compile/table.json | 4 +-- 6 files changed, 85 insertions(+), 9 deletions(-) diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index 757c3a43..0d23f188 100644 --- a/.claude/skills/form-compile/scripts/form-compile.ps1 +++ b/.claude/skills/form-compile/scripts/form-compile.ps1 @@ -1,4 +1,4 @@ -# form-compile v1.53 — Compile 1C managed form from JSON or object metadata +# form-compile v1.54 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -3657,10 +3657,36 @@ function Emit-Attributes { if ($attr.fillChecking) { X "$inner$($attr.fillChecking)" } + + # UseAlways: поля, всегда читаемые (дин-список/таблица). Две формы DSL сливаются: + # attr.useAlways[] (короткие имена) + columns с useAlways:true → ИмяРеквизита.Поле. + $uaFields = New-Object System.Collections.ArrayList + if ($attr.useAlways) { + foreach ($e in @($attr.useAlways)) { + $fld = "$e" + if ($fld -notmatch "^$([regex]::Escape($attrName))\.") { $fld = "$attrName.$fld" } + if (-not $uaFields.Contains($fld)) { [void]$uaFields.Add($fld) } + } + } + if ($attr.columns) { + foreach ($col in $attr.columns) { + if ($col.useAlways -eq $true) { + $fld = "$attrName.$($col.name)" + if (-not $uaFields.Contains($fld)) { [void]$uaFields.Add($fld) } + } + } + } + if ($uaFields.Count -gt 0) { + X "$inner" + foreach ($f in $uaFields) { X "$inner`t$f" } + X "$inner" + } + Emit-FunctionalOptions -fo $attr.functionalOptions -indent $inner - # Columns (for ValueTable/ValueTree) - if ($attr.columns -and $attr.columns.Count -gt 0) { + # Columns (for ValueTable/ValueTree). Для дин-списка (есть settings) колонки НЕ эмитим — + # они служат лишь для формирования UseAlways (поля выше). + if ($attr.columns -and $attr.columns.Count -gt 0 -and -not $attr.settings) { X "$inner" foreach ($col in $attr.columns) { $colId = New-Id diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 7abda8a1..7e00c709 100644 --- a/.claude/skills/form-compile/scripts/form-compile.py +++ b/.claude/skills/form-compile/scripts/form-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-compile v1.53 — Compile 1C managed form from JSON or object metadata +# form-compile v1.54 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -3331,10 +3331,32 @@ def emit_attributes(lines, attrs, indent): lines.append(f'{inner}true') if attr.get('fillChecking'): lines.append(f'{inner}{attr["fillChecking"]}') + + # UseAlways: поля, всегда читаемые. Две формы DSL сливаются: + # attr.useAlways[] (короткие имена) + columns с useAlways:true → ИмяРеквизита.Поле. + ua_fields = [] + for e in (attr.get('useAlways') or []): + fld = str(e) + if not re.match(r'^' + re.escape(attr_name) + r'\.', fld): + fld = f'{attr_name}.{fld}' + if fld not in ua_fields: + ua_fields.append(fld) + for col in (attr.get('columns') or []): + if col.get('useAlways') is True: + fld = f'{attr_name}.{col["name"]}' + if fld not in ua_fields: + ua_fields.append(fld) + if ua_fields: + lines.append(f'{inner}') + for f in ua_fields: + lines.append(f'{inner}\t{f}') + lines.append(f'{inner}') + emit_functional_options(lines, attr.get('functionalOptions'), inner) - # Columns (for ValueTable/ValueTree) - if attr.get('columns') and len(attr['columns']) > 0: + # Columns (for ValueTable/ValueTree). Для дин-списка (есть settings) колонки НЕ эмитим — + # они служат лишь для формирования UseAlways. + if attr.get('columns') and len(attr['columns']) > 0 and not attr.get('settings'): lines.append(f'{inner}') for col in attr['columns']: col_id = new_id() diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index e567a259..e7cafc48 100644 --- a/.claude/skills/form-decompile/scripts/form-decompile.ps1 +++ b/.claude/skills/form-decompile/scripts/form-decompile.ps1 @@ -1,4 +1,4 @@ -# form-decompile v0.35 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.36 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -1329,6 +1329,29 @@ if ($attrsNode) { } if ($cols.Count -gt 0) { $ao['columns'] = @($cols) } } + # UseAlways: поля, всегда читаемые. Префикс "ИмяРеквизита." снимаем. + # ValueTable (есть columns): useAlways:true на совпавшей колонке; остальные → массив атрибута. + # Дин-список/прочие (нет columns): массив useAlways на атрибуте. + $uaNode = $a.SelectSingleNode("lf:UseAlways", $ns) + if ($uaNode) { + $prefix = "$($ao['name'])." + $shorts = New-Object System.Collections.ArrayList + foreach ($fn in @($uaNode.SelectNodes("lf:Field", $ns))) { + $t = $fn.InnerText.Trim() + if ($t.StartsWith($prefix)) { $t = $t.Substring($prefix.Length) } + [void]$shorts.Add($t) + } + if ($ao.Contains('columns')) { + $rest = New-Object System.Collections.ArrayList + foreach ($s in $shorts) { + $col = $ao['columns'] | Where-Object { $_['name'] -eq $s } | Select-Object -First 1 + if ($col) { $col['useAlways'] = $true } else { [void]$rest.Add($s) } + } + if ($rest.Count -gt 0) { $ao['useAlways'] = @($rest) } + } elseif ($shorts.Count -gt 0) { + $ao['useAlways'] = @($shorts) + } + } # Settings динамического списка $setNode = $a.SelectSingleNode("lf:Settings", $ns) if ($setNode) { diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index 3bcda4cf..51328bfe 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -612,6 +612,7 @@ Pages поддерживает `pagesRepresentation`: `None`, `TabsOnTop`, `Tabs | `view` | bool/object | Просмотр по ролям (``). См. §4.1c | | `edit` | bool/object | Редактирование по ролям (``). См. §4.1c | | `functionalOptions` | array | Функциональные опции (`FunctionalOption.X…`). Массив имён; forgiving: `"X"`/`"FunctionalOption.X"`. Также у колонок (`columns[*]`) и команд (§7) | +| `useAlways` | array | Поля, всегда читаемые (`Имя.Поле…`). Массив коротких имён полей (forgiving: с/без префикса `Имя.`). **Две формы**: этот массив на реквизите ИЛИ `useAlways: true` на колонке (`columns[*]`) — компилятор сливает. Для дин-списка — только массив (колонки не эмитятся, но формируют ``) | | `savedData` | bool | Сохраняемые данные | | `fillChecking` | string | `Show`, `DontShow` | | `columns` | array | Колонки для ValueTable/ValueTree | diff --git a/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml index 0971f54f..c7fd5f9e 100644 --- a/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml @@ -106,6 +106,10 @@ v8:ValueTable + + Данные.Сумма + Данные.Дата + diff --git a/tests/skills/cases/form-compile/table.json b/tests/skills/cases/form-compile/table.json index d4931af8..8f007f3e 100644 --- a/tests/skills/cases/form-compile/table.json +++ b/tests/skills/cases/form-compile/table.json @@ -35,8 +35,8 @@ ], "attributes": [ { "name": "Объект", "type": "DataProcessorObject.Таблица", "main": true }, - { "name": "Данные", "type": "ValueTable", "columns": [ - { "name": "Дата", "type": "date" }, + { "name": "Данные", "type": "ValueTable", "useAlways": ["Сумма"], "columns": [ + { "name": "Дата", "type": "date", "useAlways": true }, { "name": "Сумма", "type": "decimal(15,2)" }, { "name": "Комментарий", "type": "string(200)" }, { "name": "Объект", "type": "AnyRef" },