fix(meta-edit): копия эмиттера звала функции, которых в навыке нет

Скопированные из meta-compile Emit-FormRef и Emit-Characteristics тянули за собой хвост
хелперов (Normalize-FormRef, Normalize-CharFrom, Expand-CharField, Get-CharIntField), которых
в meta-edit нет. Добавление таблицы с ключом defaultListForm или characteristics давало в PS
CommandNotFoundException при коде возврата 0: навык отчитывался успехом, а файл таблицы
не создавался и в ChildObjects ничего не появлялось. В python то же место падало трейсбеком —
порты расходились ещё и потоком ошибки.

Причина не в копировании как таковом: копия обязана быть не только идентичной, но и
самодостаточной. Форма функции для этого не годилась, поэтому изменена в эталоне и
перекопирована — тем же приёмом, что уже применён в этой семье дважды: Emit-EdsTableProperties
принимает готовые блоки <Characteristics> и четырёх слотов <Default*Form>, рендерит их
вызывающий навык своим эмиттером. Зависимостей у тела не осталось.

meta-edit передаёт пустые блоки и отвергает ключи characteristics и default*Form с объяснением,
куда идти: форму назначает form-add, характеристики — meta-compile.

Попутно исправлено то, что вскрылось при разборе: короткое имя формы в defaultListForm
эмитилось как есть, а платформа отвечает «Неизвестный объект метаданных». Теперь оно
разворачивается в полный путь ExternalDataSource.И.Table.Т.Form.Ф — как и ссылки на поля.

check-inline-drift получил проверку РАЗРЕШИМОСТИ: всё, что зовёт скопированное тело, обязано
быть определено в том же навыке. Критерий «своей» функции — имя, определённое в навыке-эталоне,
поэтому список командлетов-исключений не нужен. Проверено обратным экспериментом: гард ловит
исходный дефект с прямой формулировкой «копия неразрешима».

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBsZA5cr2WFThtgp7i5WVi
This commit is contained in:
Nick Shirokov
2026-09-06 17:10:43 +03:00
co-authored by Claude Opus 5
parent 938db35c85
commit 8d1670468a
11 changed files with 642 additions and 158 deletions
@@ -1,4 +1,4 @@
# meta-compile v1.107 — Compile 1C metadata object from JSON
# meta-compile v1.108 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -4466,7 +4466,10 @@ function Emit-EdsFunction {
# Свойства таблицы: 38 узлов в порядке выгрузки платформы.
function Emit-EdsTableProperties {
param([string]$indent, [string]$srcName, [string]$tableName, $t)
# $charXml и $defaultFormsXml — уже собранные блоки <Characteristics> и четыре слота
# <Default*Form>: их рендерит вызывающий навык своим эмиттером. Так тело не зависит
# от хелперов конкретного навыка и годится для копирования (check-inline-drift).
param([string]$indent, [string]$srcName, [string]$tableName, $t, [string]$charXml, [string]$defaultFormsXml)
$i = $indent
$tblSynonym = if ($t -and $null -ne $t.synonym) { $t.synonym } else { Split-CamelCase $tableName }
X "$i<Name>$(Esc-XmlText $tableName)</Name>"
@@ -4493,7 +4496,7 @@ function Emit-EdsTableProperties {
# с полем родителя — пустая строка (как после загрузки), а без него — nil.
if ($t -and $t.parentField) { X "$i<UnfilledParentValue xsi:type=`"xs:string`"/>" }
else { X "$i<UnfilledParentValue xsi:nil=`"true`"/>" }
Emit-Characteristics $i $(if ($t) { $t.characteristics } else { $null })
if ($charXml) { X $charXml.TrimEnd("`r", "`n") } else { X "$i<Characteristics/>" }
X "$i<UseStandardCommands>$(if ($t -and $t.useStandardCommands -eq $false) { 'false' } else { 'true' })</UseStandardCommands>"
X "$i<QuickChoice>$(if ($t -and $t.quickChoice -eq $true) { 'true' } else { 'false' })</QuickChoice>"
@@ -4507,10 +4510,9 @@ function Emit-EdsTableProperties {
X "$i<ChoiceDataGetModeOnInputByString>$(if ($t -and $t.choiceDataGetModeOnInputByString) { "$($t.choiceDataGetModeOnInputByString)" } else { 'Directly' })</ChoiceDataGetModeOnInputByString>"
X "$i<ChoiceHistoryOnInput>$(if ($t -and $t.choiceHistoryOnInput) { "$($t.choiceHistoryOnInput)" } else { 'Auto' })</ChoiceHistoryOnInput>"
foreach ($formTag in @("DefaultObjectForm","DefaultRecordForm","DefaultListForm","DefaultChoiceForm")) {
$key = $formTag.Substring(0,1).ToLower() + $formTag.Substring(1)
Emit-FormRef $i $formTag $(if ($t) { $t.$key } else { $null })
}
# Пустая строка — четыре слота всё равно обязаны быть: в свойствах таблицы их ровно 38.
if ($defaultFormsXml) { X $defaultFormsXml.TrimEnd("`r", "`n") }
else { foreach ($formTag in @("DefaultObjectForm","DefaultRecordForm","DefaultListForm","DefaultChoiceForm")) { X "$i<$formTag/>" } }
foreach ($presTag in @("ObjectPresentation","ExtendedObjectPresentation","RecordPresentation",
"ExtendedRecordPresentation","ListPresentation","ExtendedListPresentation","Explanation")) {
$key = $presTag.Substring(0,1).ToLower() + $presTag.Substring(1)
@@ -4530,9 +4532,9 @@ function Emit-EdsTableProperties {
# поэтому «перехват» — запомнить длину, отдать эмиттерам, вырезать добавленное
# (тот же приём, что у составного типа).
function Build-EdsTableXml {
# $fieldsXml — уже собранные узлы <Field>: их рендерит вызывающий навык своим эмиттером
# реквизита. Так тело функции не зависит от того, какой это навык.
param([string]$srcName, [string]$tableName, $entry, [string]$fieldsXml)
# $fieldsXml, $charXml, $defaultFormsXml — уже собранные узлы: их рендерит вызывающий навык
# своими эмиттерами. Так тело функции не зависит от того, какой это навык.
param([string]$srcName, [string]$tableName, $entry, [string]$fieldsXml, [string]$charXml, [string]$defaultFormsXml)
$before = $script:xml.Length
$tableUuid = New-Guid-String
@@ -4559,7 +4561,7 @@ function Build-EdsTableXml {
X "`t`t</InternalInfo>"
X "`t`t<Properties>"
Emit-EdsTableProperties "`t`t`t" $srcName $tableName $entry.props
Emit-EdsTableProperties "`t`t`t" $srcName $tableName $entry.props $charXml $defaultFormsXml
X "`t`t</Properties>"
if ($fieldsXml) {
@@ -5357,7 +5359,25 @@ if ($objType -eq "ExternalDataSource" -and $script:edsTables.Count -gt 0) {
}
$fieldsXml = $script:xml.ToString($fieldsBefore, $script:xml.Length - $fieldsBefore)
[void]$script:xml.Remove($fieldsBefore, $script:xml.Length - $fieldsBefore)
$tableXml = Build-EdsTableXml $objName $tblName $entry $fieldsXml
$tp = $entry.props
$charBefore = $script:xml.Length
Emit-Characteristics "`t`t`t" $(if ($tp) { $tp.characteristics } else { $null })
$charXml = $script:xml.ToString($charBefore, $script:xml.Length - $charBefore)
[void]$script:xml.Remove($charBefore, $script:xml.Length - $charBefore)
# Слот формы: короткое имя разворачивается в полный путь таблицы внешнего источника —
# голое имя платформа отвергает («Неизвестный объект метаданных»).
$formsBefore = $script:xml.Length
foreach ($formTag in @("DefaultObjectForm","DefaultRecordForm","DefaultListForm","DefaultChoiceForm")) {
$key = $formTag.Substring(0,1).ToLower() + $formTag.Substring(1)
$formVal = if ($tp) { $tp.$key } else { $null }
if ($formVal -and "$formVal" -notmatch '\.') { $formVal = "ExternalDataSource.$objName.Table.$tblName.Form.$formVal" }
Emit-FormRef "`t`t`t" $formTag $formVal
}
$defaultFormsXml = $script:xml.ToString($formsBefore, $script:xml.Length - $formsBefore)
[void]$script:xml.Remove($formsBefore, $script:xml.Length - $formsBefore)
$tableXml = Build-EdsTableXml $objName $tblName $entry $fieldsXml $charXml $defaultFormsXml
$tablePath = Join-Path $tablesDir "$tblName.xml"
Write-XmlFileKeepEol $tablePath $tableXml $enc
$edsTablesCreated += $tablePath
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-compile v1.107 — Compile 1C metadata object from JSON
# meta-compile v1.108 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -4459,8 +4459,12 @@ def emit_eds_function(indent, fn_name, val, type_xml):
X(f'{indent}</Function>')
def emit_eds_table_properties(indent, src_name, table_name, t):
"""Свойства таблицы: 38 узлов в порядке выгрузки платформы."""
def emit_eds_table_properties(indent, src_name, table_name, t, char_xml, default_forms_xml):
"""Свойства таблицы: 38 узлов в порядке выгрузки платформы.
char_xml и default_forms_xml уже собранные блоки <Characteristics> и четыре слота
<Default*Form>: их рендерит вызывающий навык своим эмиттером. Так тело не зависит
от хелперов конкретного навыка и годится для копирования (check-inline-drift)."""
i = indent
t = t or {}
tbl_synonym = t['synonym'] if t.get('synonym') is not None else split_camel_case(table_name)
@@ -4497,7 +4501,10 @@ def emit_eds_table_properties(indent, src_name, table_name, t):
X(f'{i}<UnfilledParentValue xsi:type="xs:string"/>')
else:
X(f'{i}<UnfilledParentValue xsi:nil="true"/>')
emit_characteristics(i, t.get('characteristics'))
if char_xml:
X(char_xml.rstrip('\r\n'))
else:
X(f'{i}<Characteristics/>')
X(f'{i}<UseStandardCommands>{"false" if t.get("useStandardCommands") is False else "true"}</UseStandardCommands>')
X(f'{i}<QuickChoice>{"true" if t.get("quickChoice") is True else "false"}</QuickChoice>')
@@ -4515,9 +4522,12 @@ def emit_eds_table_properties(indent, src_name, table_name, t):
X(f'{i}<ChoiceDataGetModeOnInputByString>{t.get("choiceDataGetModeOnInputByString") or "Directly"}</ChoiceDataGetModeOnInputByString>')
X(f'{i}<ChoiceHistoryOnInput>{t.get("choiceHistoryOnInput") or "Auto"}</ChoiceHistoryOnInput>')
for form_tag in ('DefaultObjectForm', 'DefaultRecordForm', 'DefaultListForm', 'DefaultChoiceForm'):
key = form_tag[0].lower() + form_tag[1:]
emit_form_ref(i, form_tag, t.get(key))
# Пустая строка — четыре слота всё равно обязаны быть: в свойствах таблицы их ровно 38.
if default_forms_xml:
X(default_forms_xml.rstrip('\r\n'))
else:
for form_tag in ('DefaultObjectForm', 'DefaultRecordForm', 'DefaultListForm', 'DefaultChoiceForm'):
X(f'{i}<{form_tag}/>')
for pres_tag in ('ObjectPresentation', 'ExtendedObjectPresentation', 'RecordPresentation',
'ExtendedRecordPresentation', 'ListPresentation', 'ExtendedListPresentation', 'Explanation'):
key = pres_tag[0].lower() + pres_tag[1:]
@@ -4544,9 +4554,10 @@ EDS_TABLE_GENERATED_TYPES = (
)
def build_eds_table_xml(src_name, table_name, entry, fields_xml):
"""Отдельный XML-документ таблицы. fields_xml — уже собранные узлы <Field>: их рендерит
вызывающий навык своим эмиттером реквизита, поэтому тело не зависит от того, какой это навык.
def build_eds_table_xml(src_name, table_name, entry, fields_xml, char_xml, default_forms_xml):
"""Отдельный XML-документ таблицы. fields_xml, char_xml, default_forms_xml — уже собранные
узлы: их рендерит вызывающий навык своими эмиттерами, поэтому тело не зависит от того,
какой это навык.
Возвращает строку: X пишет в общий список строк,
поэтому «перехват» запомнить длину, отдать эмиттерам, срезать добавленное
(в ps1-порте тот же приём выражен через StringBuilder различие рантаймов, не логики)."""
@@ -4566,7 +4577,7 @@ def build_eds_table_xml(src_name, table_name, entry, fields_xml):
X('\t\t</InternalInfo>')
X('\t\t<Properties>')
emit_eds_table_properties('\t\t\t', src_name, table_name, entry['props'])
emit_eds_table_properties('\t\t\t', src_name, table_name, entry['props'], char_xml, default_forms_xml)
X('\t\t</Properties>')
if fields_xml:
@@ -5116,7 +5127,25 @@ if obj_type == 'ExternalDataSource' and eds_tables:
emit_attribute('\t\t\t', parse_attribute_shorthand(f), 'eds-field', 'Field')
fields_xml = '\r\n'.join(lines[fields_before:])
del lines[fields_before:]
table_xml = build_eds_table_xml(obj_name, tbl_name, entry, fields_xml)
tp = entry['props'] or {}
char_before = len(lines)
emit_characteristics('\t\t\t', tp.get('characteristics'))
char_xml = '\r\n'.join(lines[char_before:])
del lines[char_before:]
# Слот формы: короткое имя разворачивается в полный путь таблицы внешнего источника —
# голое имя платформа отвергает («Неизвестный объект метаданных»).
forms_before = len(lines)
for form_tag in ('DefaultObjectForm', 'DefaultRecordForm', 'DefaultListForm', 'DefaultChoiceForm'):
key = form_tag[0].lower() + form_tag[1:]
form_val = tp.get(key)
if form_val and '.' not in str(form_val):
form_val = f'ExternalDataSource.{obj_name}.Table.{tbl_name}.Form.{form_val}'
emit_form_ref('\t\t\t', form_tag, form_val)
default_forms_xml = '\r\n'.join(lines[forms_before:])
del lines[forms_before:]
table_xml = build_eds_table_xml(obj_name, tbl_name, entry, fields_xml, char_xml, default_forms_xml)
table_path = os.path.join(tables_dir, f'{tbl_name}.xml')
write_xml_file_keep_eol(table_path, table_xml)
eds_tables_created.append(table_path)