fix(meta-compile,meta-validate): «только единственным» касается и характеристики

Правило было уже реальности: Конфигуратор не даёт включить характеристику в
составной тип вместе ни с каким другим типом — в том числе с другой
характеристикой. Предупреждение теперь ловит и DefinedType.X, и
Characteristic.X.

Корпус подтверждает даже чище, чем для определяемого типа: 1741 характеристика
единственным типом и 0 в составных (у определяемого было 6500 против 1).
Уровень прежний — предупреждение: загрузчик такое принимает, проверено
загрузкой в базу.

Голых метатипов это не касается: DocumentRef + CatalogRef — обычный составной
тип, 525 вхождений в поставляемых ERP и БП.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-09-20 19:54:29 +03:00
co-authored by Claude Opus 5
parent 0db3656c9c
commit dd0ad18076
14 changed files with 466 additions and 29 deletions
@@ -988,11 +988,12 @@ function Emit-ValueType {
X "$indent</Type>"
}
# Определяемый тип Конфигуратор даёт выбрать только ЕДИНСТВЕННЫМ, не одним из составного.
# Загрузчик такое принимает, и в типовой ERP один такой реквизит есть
# (Документ.НачислениеИСписаниеБонусныхБаллов.Баллы — два определяемых типа подряд), поэтому
# предупреждение, а не отказ: иначе навык не собрал бы того, что поставляет 1С. Соотношение в
# корпусе erp+acc — 6500 единственных против 1 составного.
# Определяемый тип и характеристику Конфигуратор даёт выбрать только ЕДИНСТВЕННЫМ, не одним из
# составного (характеристику — даже вместе с другой характеристикой). Загрузчик такое принимает,
# и в типовой ERP один такой реквизит есть (Документ.НачислениеИСписаниеБонусныхБаллов.Баллы —
# два определяемых типа подряд), поэтому предупреждение, а не отказ: иначе навык не собрал бы
# того, что поставляет 1С. Соотношение в корпусе erp+acc — 6500 определяемых единственным против
# 1 составного и 1741 характеристика единственным против 0 составных.
function Warn-DefinedTypeInComposite([int]$fromLength) {
$frag = $script:xml.ToString($fromLength, $script:xml.Length - $fromLength)
# После имени тега — либо '>', либо пробел: тип из чужого пространства имён несёт локальную
@@ -1000,12 +1001,12 @@ function Warn-DefinedTypeInComposite([int]$fromLength) {
$members = [regex]::Matches($frag, '<v8:(Type|TypeSet)[ >]').Count
if ($members -lt 2) { return }
$dts = @()
foreach ($m in [regex]::Matches($frag, '<v8:TypeSet>cfg:(DefinedType\.[^<]+)</v8:TypeSet>')) { $dts += $m.Groups[1].Value }
foreach ($m in [regex]::Matches($frag, '<v8:TypeSet>cfg:((?:DefinedType|Characteristic)\.[^<]+)</v8:TypeSet>')) { $dts += $m.Groups[1].Value }
if ($dts.Count -gt 0) {
# Прямо в stderr, а не Write-Warning: в PS 5.1 предупреждение уходит не в тот поток
# (см. cf-init) и до expect.stderrContains не доезжает.
[Console]::Error.WriteLine("WARNING: Составной тип содержит определяемый тип (" + ($dts -join ', ') +
"). Конфигуратор даёт выбрать определяемый тип только единственным — собрать такое руками не получится. Платформа загрузит.")
[Console]::Error.WriteLine("WARNING: Составной тип содержит (" + ($dts -join ', ') +
"). Конфигуратор даёт выбрать определяемый тип и характеристику только единственным — собрать такое руками не получится. Платформа загрузит.")
}
}
@@ -1096,22 +1096,23 @@ def emit_value_type(indent, type_str):
warn_defined_type_in_composite(mark)
X(f'{indent}</Type>')
# Определяемый тип Конфигуратор даёт выбрать только ЕДИНСТВЕННЫМ, не одним из составного.
# Загрузчик такое принимает, и в типовой ERP один такой реквизит есть
# (Документ.НачислениеИСписаниеБонусныхБаллов.Баллы — два определяемых типа подряд), поэтому
# предупреждение, а не отказ: иначе навык не собрал бы того, что поставляет 1С. Соотношение в
# корпусе erp+acc — 6500 единственных против 1 составного.
# Определяемый тип и характеристику Конфигуратор даёт выбрать только ЕДИНСТВЕННЫМ, не одним из
# составного (характеристику — даже вместе с другой характеристикой). Загрузчик такое принимает,
# и в типовой ERP один такой реквизит есть (Документ.НачислениеИСписаниеБонусныхБаллов.Баллы —
# два определяемых типа подряд), поэтому предупреждение, а не отказ: иначе навык не собрал бы
# того, что поставляет 1С. Соотношение в корпусе erp+acc — 6500 определяемых единственным против
# 1 составного и 1741 характеристика единственным против 0 составных.
def warn_defined_type_in_composite(from_index):
frag = chr(10).join(lines[from_index:])
# После имени тега — либо '>', либо пробел: тип из чужого пространства имён несёт локальную
# xmlns прямо в теге (<v8:Type xmlns:mxl="…">), и без пробела в классе он не считался членом.
if len(re.findall(r'<v8:(?:Type|TypeSet)[ >]', frag)) < 2:
return
dts = re.findall(r'<v8:TypeSet>cfg:(DefinedType[.][^<]+)</v8:TypeSet>', frag)
dts = re.findall(r'<v8:TypeSet>cfg:((?:DefinedType|Characteristic)[.][^<]+)</v8:TypeSet>', frag)
if dts:
print("WARNING: Составной тип содержит определяемый тип (" + ', '.join(dts) +
"). Конфигуратор даёт выбрать определяемый тип только единственным — собрать такое "
"руками не получится. Платформа загрузит.", file=sys.stderr)
print("WARNING: Составной тип содержит (" + ', '.join(dts) +
"). Конфигуратор даёт выбрать определяемый тип и характеристику только единственным — "
"собрать такое руками не получится. Платформа загрузит.", file=sys.stderr)
# Что из только что записанного фрагмента ушло МНОЖЕСТВОМ. Спрашиваем сам эмиттер, а не повторяем
# его регулярки: список видов, дающих v8:TypeSet, живёт в emit_type_content, и вторая копия
@@ -1733,8 +1733,8 @@ if ($badGrammar.Count -eq 0 -and $notStorable.Count -eq 0 -and $unknownVocab.Cou
# WARN — голый метатип в типе значения ПВХ: загрузка проходит, но Конфигуратор такой тип не
# предлагает (в дереве выбора это папка без флажка), а ЛюбаяСсылка на выгрузке
# возвращается как ЛюбаяСсылкаИБ;
# WARN — определяемый тип одним из составного: Конфигуратор даёт выбрать его только
# единственным. В корпусе erp+acc 6500 единственных против 1 составного — и этот один
# WARN — определяемый тип или характеристика одним из составного: Конфигуратор даёт
# выбрать их только единственными (характеристику — даже с другой характеристикой). В корпусе erp+acc 6500 единственных против 1 составного — и этот один
# лежит в типовой ERP (Документ.НачислениеИСписаниеБонусныхБаллов.Баллы), поэтому не ошибка.
$typeBlocks23 = @($xmlDoc.SelectNodes("//md:Type | //md:ValueType", $ns))
$dtSetsSeen = 0
@@ -1756,9 +1756,9 @@ foreach ($tb in $typeBlocks23) {
$dtSetsBad = $true
Report-Warn "23. План видов характеристик '$objName': тип значения '$raw' Конфигуратор не предлагает (в дереве выбора это папка без флажка); ЛюбаяСсылка на выгрузке вернётся как ЛюбаяСсылкаИБ"
}
if ($members -gt 1 -and $raw -match '^DefinedType\.') {
if ($members -gt 1 -and $raw -match '^(DefinedType|Characteristic)\.') {
$dtSetsBad = $true
Report-Warn "23. Составной тип содержит определяемый тип '$raw' — Конфигуратор даёт выбрать его только единственным; платформа загрузит"
Report-Warn "23. Составной тип содержит '$raw' — Конфигуратор даёт выбрать определяемый тип и характеристику только единственным; платформа загрузит"
}
}
}
@@ -1674,8 +1674,8 @@ if not bad_grammar and not not_storable and not unknown_vocab and types_seen:
# WARN — голый метатип в типе значения ПВХ: загрузка проходит, но Конфигуратор такой тип не
# предлагает (в дереве выбора это папка без флажка), а ЛюбаяСсылка на выгрузке
# возвращается как ЛюбаяСсылкаИБ;
# WARN — определяемый тип одним из составного: Конфигуратор даёт выбрать его только
# единственным. В корпусе erp+acc 6500 единственных против 1 составного — и этот один
# WARN — определяемый тип или характеристика одним из составного: Конфигуратор даёт
# выбрать их только единственными (характеристику — даже с другой характеристикой). В корпусе erp+acc 6500 единственных против 1 составного — и этот один
# лежит в типовой ERP (Документ.НачислениеИСписаниеБонусныхБаллов.Баллы), поэтому не ошибка.
dt_sets_seen = 0
dt_sets_bad = False
@@ -1696,9 +1696,9 @@ for tb in root.xpath("//md:Type | //md:ValueType", namespaces=NS):
if owner_kind == "ChartOfCharacteristicTypes" and not re.match(r'^DefinedType[.]', raw):
dt_sets_bad = True
report_warn(f"23. План видов характеристик '{obj_name}': тип значения '{raw}' Конфигуратор не предлагает (в дереве выбора это папка без флажка); ЛюбаяСсылка на выгрузке вернётся как ЛюбаяСсылкаИБ")
if members > 1 and re.match(r'^DefinedType[.]', raw):
if members > 1 and re.match(r'^(DefinedType|Characteristic)[.]', raw):
dt_sets_bad = True
report_warn(f"23. Составной тип содержит определяемый тип '{raw}' — Конфигуратор даёт выбрать его только единственным; платформа загрузит")
report_warn(f"23. Составной тип содержит '{raw}' — Конфигуратор даёт выбрать определяемый тип и характеристику только единственным; платформа загрузит")
if dt_sets_seen and not dt_sets_bad:
report_ok(f"23. Type sets: {dt_sets_seen} block(s) checked")