feat(form-decompile,form-compile): суппресс авто-вывода (MainAttribute/SavedData/AutoTitle) + AutoFillAvailableFields + SaveWindowSettings

Раундтрип TOTAL 40→25, match 138→146. Три класса «компилятор додумывает на
main/titled формах» (декомпилятор не давал суппресс-маркера) + два непокрытых свойства.

- MainAttribute: эвристика 11b.3 (нет явного main + ровно 1 объектный реквизит
  → помечает main). Декомпилятор зеркалит условие → ставит main:false (компилятор
  уже исключает такие кандидаты). Объектные реквизиты часто НЕ main (DynamicList
  1207 без, RecordSet 226 без, и т.д.). Decompiler-only.
- SavedData: эвристика $mainSaved (main + Catalog/Document/ChartOf*/ExchangePlan/
  BusinessProcess/Task Object + RecordManager → SavedData=true). Часто отсутствует
  (DocumentObject 332 без = 23%). Компилятор: явный savedData:false побеждает;
  декомпилятор ставит savedData:false для main-реквизита saved-типа без <SavedData>.
- AutoTitle: компилятор инъектит false при наличии title (~95% форм). Редкие 5%
  (Title есть, AutoTitle нет) → декомпилятор ставит autoTitle:"", компилятор
  пропускает пустую строку в Emit-Properties (общий ""-суппресс).
- AutoFillAvailableFields: свойство <Settings> дин-списка (дефолт true, эмит только
  отклонение false; ключ settings.autoFillAvailableFields).
- SaveWindowSettings: форменный bool (KNOWN_FORM_PROPS + auto-PascalCase).

Зеркало py (компилятор). Кейс dynamic-list-form +saveWindowSettings (сертифицирован).
Формы ЗадачаИсполнителя/Дополнительно, БизнесСеть/*, АнализПравДоступа → чисто.
Регресс 39/39 в обоих рантаймах.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-09 13:07:38 +03:00
co-authored by Claude Opus 4.8
parent 1a601a4d0c
commit bc81faf892
6 changed files with 62 additions and 14 deletions
@@ -1,4 +1,4 @@
# form-compile v1.90 — Compile 1C managed form from JSON or object metadata
# form-compile v1.91 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$JsonPath,
@@ -4555,7 +4555,9 @@ function Emit-Attributes {
if ($attr.main -eq $true -and $attr.type) {
$mainSaved = ("$($attr.type)") -match '^(CatalogObject|DocumentObject|ChartOfAccountsObject|ChartOfCalculationTypesObject|ChartOfCharacteristicTypesObject|ExchangePlanObject|BusinessProcessObject|TaskObject)\.' -or ("$($attr.type)") -match 'RecordManager\.'
}
if ($attr.savedData -eq $true -or $mainSaved) {
# Явный ключ savedData побеждает (в т.ч. false → суппресс авто-вывода $mainSaved); нет ключа → авто.
$emitSaved = if ($null -ne $attr.PSObject.Properties['savedData']) { $attr.savedData -eq $true } else { $mainSaved }
if ($emitSaved) {
X "$inner<SavedData>true</SavedData>"
}
# Save: сохранение значения реквизита в пользовательских настройках. true → <Field>имя</Field>;
@@ -4655,7 +4657,9 @@ function Emit-Attributes {
$st = $attr.settings
X "$inner<Settings xsi:type=`"DynamicList`">"
$si = "$inner`t"
# Порядок платформы: ManualQuery, DynamicDataRead, QueryText, Field*, MainTable, ListSettings
# Порядок платформы: AutoFillAvailableFields, ManualQuery, DynamicDataRead, QueryText, Field*, MainTable, ListSettings
# AutoFillAvailableFields — дефолт true; эмитим только при заданном ключе (отклонение).
if ($null -ne $st.autoFillAvailableFields) { X "$si<AutoFillAvailableFields>$(if ($st.autoFillAvailableFields){'true'}else{'false'})</AutoFillAvailableFields>" }
$hasQuery = $st.query -and "$($st.query)".Trim()
$mq = if ($hasQuery -or $st.manualQuery -eq $true) { "true" } else { "false" }
X "$si<ManualQuery>$mq</ManualQuery>"
@@ -4830,6 +4834,8 @@ function Emit-Properties {
}
# Convert boolean to lowercase string (PS renders as True/False)
$val = $p.Value
# Пустая строка = суппресс-маркер (напр. autoTitle:"" — не эмитить и не додумывать)
if ($val -is [string] -and $val -eq '') { continue }
if ($val -is [bool]) {
$val = if ($val) { "true" } else { "false" }
}
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-compile v1.90 — Compile 1C managed form from JSON or object metadata
# form-compile v1.91 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import copy
@@ -4273,7 +4273,9 @@ def emit_attributes(lines, attrs, indent):
if attr.get('main') is True and attr.get('type'):
t = str(attr['type'])
main_saved = bool(re.match(r'^(CatalogObject|DocumentObject|ChartOfAccountsObject|ChartOfCalculationTypesObject|ChartOfCharacteristicTypesObject|ExchangePlanObject|BusinessProcessObject|TaskObject)\.', t)) or ('RecordManager.' in t)
if attr.get('savedData') is True or main_saved:
# Явный ключ savedData побеждает (в т.ч. False → суппресс авто-вывода main_saved); нет ключа → авто.
emit_saved = (attr['savedData'] is True) if 'savedData' in attr else main_saved
if emit_saved:
lines.append(f'{inner}<SavedData>true</SavedData>')
# Save: сохранение значения реквизита в пользовательских настройках. true → <Field>имя</Field>;
# строка/массив → под-поля с авто-префиксом "имя." (путь с точкой / UUID / =имя — как есть).
@@ -4361,6 +4363,10 @@ def emit_attributes(lines, attrs, indent):
s = attr['settings']
lines.append(f'{inner}<Settings xsi:type="DynamicList">')
si = f'{inner}\t'
# Порядок платформы: AutoFillAvailableFields, ManualQuery, DynamicDataRead, QueryText, Field*, MainTable, ListSettings
# AutoFillAvailableFields — дефолт true; эмитим только при заданном ключе (отклонение).
if s.get('autoFillAvailableFields') is not None:
lines.append(f'{si}<AutoFillAvailableFields>{"true" if s["autoFillAvailableFields"] else "false"}</AutoFillAvailableFields>')
# Порядок платформы: ManualQuery, DynamicDataRead, QueryText, Field*, MainTable, ListSettings
has_query = bool(s.get('query') and str(s['query']).strip())
mq = 'true' if (has_query or s.get('manualQuery')) else 'false'
@@ -4519,6 +4525,9 @@ def emit_properties(lines, props, indent):
# Auto PascalCase
xml_name = p_name[0].upper() + p_name[1:]
# Пустая строка = суппресс-маркер (напр. autoTitle:"" — не эмитить и не додумывать)
if isinstance(p_value, str) and p_value == '':
continue
# Convert boolean to lowercase
if isinstance(p_value, bool):
val = 'true' if p_value else 'false'