fix(form): уникальность имён во всех коллекциях форм + префикс колонок субконто ЧПС

Проверка уникальности имён элементов форм (основа — PR #21 от brake71),
портированная на актуальную ветку и расширенная на все именованные коллекции.

Корень проблемы: генератор формы счёта ПланаСчетов строил колонки таблицы
субконто с «голыми» именами (Валютный, ТолькоОбороты, ВидСубконто), из-за чего
флаг субконто сталкивался с одноимённым признаком учёта счёта → невалидный для
1С XML (форма не открывалась). Теперь имена колонок префиксуются именем таблицы
(ВидыСубконтоВалютный) — как делает generic-путь табчастей и типовая 1С.

- form-compile: fail-fast проверка уникальности в едином emit_element + по
  реквизитам, колонкам (в пределах реквизита), параметрам и командам. Хелпер
  вместо копипаста; проверка после нормализации синонимов.
- form-validate: проверка имён симметрично существующим id-пулам (элементы,
  реквизиты, колонки, команды) + новый блок параметров.
- form-edit: дедуп внутри JSON-определения и против существующих в форме —
  для элементов (рекурсивно), реквизитов (+колонки) и команд; WARN→ERROR.

Каждая коллекция — свой неймспейс (имя реквизита и имя элемента могут совпадать
легально). PS1 и PY — зеркальны. Версии: form-compile 1.74, form-validate 1.7,
form-edit 1.1. Все тест-сеты зелёные на обоих рантаймах.

Co-authored-by: brake71 <8448482+brake71@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-08 13:16:12 +03:00
co-authored by brake71 Claude Opus 4.8
parent ad89929efd
commit 41e4714773
7 changed files with 260 additions and 35 deletions
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-validate v1.6 — Validate 1C managed form
# form-validate v1.7 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -182,7 +182,8 @@ def main():
report_error("AutoCommandBar element missing")
# --- Collect all elements with IDs ---
element_ids = {} # id -> name
element_ids = {} # id -> name
element_names = {} # name -> id (имена элементов уникальны в пределах формы)
all_elements = [] # list of dicts {Name, Tag, Id, ParentName, Node}
def collect_elements(node, parent_name):
@@ -211,6 +212,12 @@ def main():
else:
element_ids[eid] = name
# Имена элементов уникальны (требование 1С)
if name in element_names:
report_error(f"Duplicate element name '{name}': id={eid} and id={element_names[name]}")
else:
element_names[name] = eid
child_items = child.find(f"{{{F_NS}}}ChildItems")
if child_items is not None:
collect_elements(child_items, name)
@@ -252,6 +259,9 @@ def main():
attr_name = attr.get("name", "")
attr_id = attr.get("id", "")
if attr_name:
# Имена реквизитов уникальны среди реквизитов (отдельный неймспейс от элементов)
if attr_name in attr_map:
report_error(f"Duplicate attribute name '{attr_name}': id={attr_id} and id={attr_map[attr_name].get('id', '')}")
attr_map[attr_name] = attr
if attr_id:
if attr_id in attr_ids:
@@ -261,6 +271,7 @@ def main():
# Column IDs uniqueness within parent
col_ids = {}
col_names = {} # имена колонок уникальны в пределах своего реквизита
columns = attr.find(f"{{{F_NS}}}Columns")
if columns is not None:
for col in columns.findall(f"{{{F_NS}}}Column"):
@@ -271,6 +282,11 @@ def main():
report_error(f"Duplicate column id={col_id} in '{attr_name}': '{col_name}' and '{col_ids[col_id]}'")
else:
col_ids[col_id] = col_name
if col_name:
if col_name in col_names:
report_error(f"Duplicate column name '{col_name}' in '{attr_name}': id={col_id} and id={col_names[col_name]}")
else:
col_names[col_name] = col_id
if not stopped:
if attr_ids:
@@ -289,6 +305,9 @@ def main():
cmd_name = cmd.get("name", "")
cmd_id = cmd.get("id", "")
if cmd_name:
# Имена команд уникальны среди команд (отдельный неймспейс)
if cmd_name in cmd_map:
report_error(f"Duplicate command name '{cmd_name}': id={cmd_id} and id={cmd_map[cmd_name].get('id', '')}")
cmd_map[cmd_name] = cmd
if cmd_id:
if cmd_id in cmd_ids:
@@ -300,6 +319,18 @@ def main():
if cmd_ids:
report_ok(f"Unique command IDs: {len(cmd_ids)} entries")
# --- Collect parameters (separate name pool, без id) ---
param_names = {} # name -> True (имена параметров уникальны среди параметров)
params_parent = root.find(f"{{{F_NS}}}Parameters")
if params_parent is not None:
for param in params_parent.findall(f"{{{F_NS}}}Parameter"):
param_name = param.get("name", "")
if param_name:
if param_name in param_names:
report_error(f"Duplicate parameter name '{param_name}'")
else:
param_names[param_name] = True
# --- Check 4: Companion elements ---
companion_rules = {
"InputField": ["ContextMenu", "ExtendedTooltip"],