From 4916f5bf7cb108bd0e8063a33f262b8ba4ec620e Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Mon, 8 Jun 2026 22:44:36 +0300 Subject: [PATCH] =?UTF-8?q?fix(form-compile,form-decompile):=20UseAlways?= =?UTF-8?q?=20=D0=BC=D0=B0=D1=80=D0=BA=D0=B5=D1=80=20"~"=20(query-=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=8F=20=D0=B4=D0=B8=D0=BD-=D1=81=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=BA=D0=B0)=20=E2=80=94=20=D0=B4=D0=B2=D0=BE=D0=B9?= =?UTF-8?q?=D0=BD=D0=BE=D0=B9=20=D0=BF=D1=80=D0=B5=D1=84=D0=B8=D0=BA=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Компилятор-баг: поле "~Список.Остановлен" (декомпилятор хранил verbatim) не матчило проверку префикса ^Список\. → добавлялся ещё префикс → "Список.~Список.Остановлен" (8+ форм выборки: ЗаявкаСотрудника*, Банки, ОбеспечениеПроизводственныхПроцессов…). "~" — легитимный маркер query-полей динамического списка (2234/17266 = 13% корпуса). - Компилятор: префикс ИмяРеквизита. ставится ПОСЛЕ "~" (~Остановлен → ~Список.Остановлен); полная форма ~Список.X — verbatim (forgiving ввод). - Декомпилятор: компактит ~Список.X → ~X (единообразно с короткими именами; компилятор разворачивает обратно). Зеркало py. Кейс dynamic-list-form расширен (~Артикул + Список.Code + Description), сертифицирован загрузкой в 1С. Регресс 39/39 в обоих рантаймах. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../skills/form-compile/scripts/form-compile.ps1 | 13 +++++++++++-- .claude/skills/form-compile/scripts/form-compile.py | 12 ++++++++++-- .../form-decompile/scripts/form-decompile.ps1 | 12 ++++++++++-- docs/form-dsl-spec.md | 2 +- .../cases/form-compile/dynamic-list-form.json | 2 +- .../Catalogs/Товары/Forms/ФормаСписка/Ext/Form.xml | 5 +++++ 6 files changed, 38 insertions(+), 8 deletions(-) diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index fc8df131..ad1c0b84 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.86 — Compile 1C managed form from JSON or object metadata +# form-compile v1.87 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -4583,7 +4583,16 @@ function Emit-Attributes { if ($attr.useAlways) { foreach ($e in @($attr.useAlways)) { $fld = "$e" - if ($fld -notmatch "^$([regex]::Escape($attrName))\.") { $fld = "$attrName.$fld" } + # Префикс "ИмяРеквизита." добавляем к коротким именам. Поля дин-списка с маркером "~" + # (query-поля, ~13% корпуса) — префикс ставится ПОСЛЕ "~": ~Остановлен → ~Список.Остановлен. + # Полная форма (~Список.Остановлен / Список.Остановлен) — verbatim (forgiving ввод). + if ($fld.StartsWith('~')) { + $bare = $fld.Substring(1) + if ($bare -notmatch "^$([regex]::Escape($attrName))\.") { $bare = "$attrName.$bare" } + $fld = "~$bare" + } elseif ($fld -notmatch "^$([regex]::Escape($attrName))\.") { + $fld = "$attrName.$fld" + } if (-not $uaFields.Contains($fld)) { [void]$uaFields.Add($fld) } } } diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index d25d01b7..0cb0b4c4 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.86 — Compile 1C managed form from JSON or object metadata +# form-compile v1.87 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -4294,7 +4294,15 @@ def emit_attributes(lines, attrs, indent): ua_fields = [] for e in (attr.get('useAlways') or []): fld = str(e) - if not re.match(r'^' + re.escape(attr_name) + r'\.', fld): + # Префикс "ИмяРеквизита." добавляем к коротким именам. Поля дин-списка с маркером "~" + # (query-поля, ~13% корпуса) — префикс ставится ПОСЛЕ "~": ~Остановлен → ~Список.Остановлен. + # Полная форма (~Список.Остановлен / Список.Остановлен) — verbatim (forgiving ввод). + if fld.startswith('~'): + bare = fld[1:] + if not re.match(r'^' + re.escape(attr_name) + r'\.', bare): + bare = f'{attr_name}.{bare}' + fld = f'~{bare}' + elif 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) diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index cfda5071..2ad7282b 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.62 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.63 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -1814,7 +1814,15 @@ if ($attrsNode) { $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) } + # Снимаем префикс "ИмяРеквизита.". Маркер "~" (query-поле дин-списка) сохраняем, + # префикс снимаем ПОСЛЕ него: ~Список.Остановлен → ~Остановлен (компилятор развернёт обратно). + if ($t.StartsWith('~')) { + $rest = $t.Substring(1) + if ($rest.StartsWith($prefix)) { $rest = $rest.Substring($prefix.Length) } + $t = "~$rest" + } elseif ($t.StartsWith($prefix)) { + $t = $t.Substring($prefix.Length) + } [void]$shorts.Add($t) } if ($ao.Contains('columns')) { diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index 6b49e2a6..dfb29a65 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -766,7 +766,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[*]`) — компилятор сливает. Для дин-списка — только массив (колонки не эмитятся, но формируют ``) | +| `useAlways` | array | Поля, всегда читаемые (`Имя.Поле…`). Массив коротких имён полей (forgiving: с/без префикса `Имя.`). **Маркер `~`** (query-поля дин-списка): `~Остановлен` → `~Список.Остановлен` (префикс ставится ПОСЛЕ `~`; полная форма `~Список.Остановлен` тоже принимается verbatim). **Две формы**: этот массив на реквизите ИЛИ `useAlways: true` на колонке (`columns[*]`) — компилятор сливает. Для дин-списка — только массив (колонки не эмитятся, но формируют ``) | | `valueType` | string | Тип значений у реквизита типа `ValueList` (``). Грамматика — как у `type`, включая составной `A \| B`. **Три состояния**: нет ключа → нет ``; `""` → пустой `` (список без ограничения типа); тип → с типом. Forgiving-синонимы: `typeDescription` (≈1С «ОписаниеТипов» / XML), `описаниеТипов`, `типЗначений`. Пример: `"valueType": "CatalogRef.Контрагенты"` | | `savedData` | bool | Сохраняемые данные (``) | | `save` | bool/string/array | Сохранение значения в пользовательских настройках (`…`). `true` → `имя`; строка/массив строк → под-поля с авто-префиксом `имя.` (путь с точкой / UUID `1/0:…` / совпадающее с именем — берётся как есть). Нет ключа или `false` → не эмитится. Пример периода: `["Период","EndDate","StartDate","Variant"]` | diff --git a/tests/skills/cases/form-compile/dynamic-list-form.json b/tests/skills/cases/form-compile/dynamic-list-form.json index f3e58d45..14288aa7 100644 --- a/tests/skills/cases/form-compile/dynamic-list-form.json +++ b/tests/skills/cases/form-compile/dynamic-list-form.json @@ -16,7 +16,7 @@ "input": { "title": "Товары", "attributes": [ - { "name": "Список", "type": "DynamicList", "settings": { + { "name": "Список", "type": "DynamicList", "useAlways": ["~Артикул", "Список.Code", "Description"], "settings": { "mainTable": "Catalog.Товары", "dynamicDataRead": true, "order": [ "Description", "Code desc" ], "filter": [ "Артикул = _ @off @user" ], diff --git a/tests/skills/cases/form-compile/snapshots/dynamic-list-form/Catalogs/Товары/Forms/ФормаСписка/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/dynamic-list-form/Catalogs/Товары/Forms/ФормаСписка/Ext/Form.xml index 333ca92a..d90d5dcd 100644 --- a/tests/skills/cases/form-compile/snapshots/dynamic-list-form/Catalogs/Товары/Forms/ФормаСписка/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/dynamic-list-form/Catalogs/Товары/Forms/ФормаСписка/Ext/Form.xml @@ -88,6 +88,11 @@ cfg:DynamicList true + + ~Список.Артикул + Список.Code + Список.Description + false true