mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-08 19:20:52 +03:00
feat(meta-validate,meta-compile): гард на нераспознанное имя типа, два уровня
Неизвестное имя типа проходило насквозь молча: meta-compile писал его в XML дословно (<v8:Type>varchar(150)</v8:Type>), meta-validate не смотрел на скалярные типы вовсе — проверка 16 разбирает только ссылочные с известным префиксом. Отказ приходил лишь от платформы при загрузке всей конфигурации, без указания объекта и реквизита. Так вело себя и имя типа СУБД, и опечатка. meta-validate, проверка 22 — два уровня, потому что «невалидно» и «нам незнакомо» разные вещи: Уровень 1, грамматика (ERROR). Содержимое <v8:Type> всегда несёт префикс пространства имён; голое имя платформа не примет никогда, независимо от её версии и состава конфигурации. Сюда же неверная форма ссылочного типа. Уровень 2, словарь по контексту владельца (WARN). У хранимого объекта набор типов у́же, чем у обработки или отчёта, где доступны ТаблицаЗначений, ОписаниеТипов, Картинка. Замер по корпусу подтверждает разделение: v8:ValueTable/ValueTree/StandardPeriod в объектных файлах acc и erp встречаются только у обработок и отчётов, у хранимых — ноль. Оставлено предупреждением намеренно: словарь — та часть, которая расширяется с версиями платформы, и цена ложной ошибки выше пользы. Жёстко только там, где решает грамматика. meta-compile отвергает голое неизвестное имя с перечнем допустимых форм. Имя с готовым префиксом (v8:ValueTable, ent:AccountType, v8ui:Color) по-прежнему проходит как есть — это законный ввод, и правильность имени судит валидатор, которому виден весь файл. check-type-synonyms.mjs — новый гард: по общему ключу meta-edit обязан понимать то же, что meta-compile (авторитет). Отсутствие ключа допустимо, разное значение — нет. Проверено: - корпус, 23430 объектов четырёх выгрузок: ноль ошибок и ноль предупреждений проверки 22; на реальном справочнике разбирается 88 имён типов, то есть проверка работает, а не молчит; - раундтрип A, 13788 объектов: побайтово как эталон, compile-fail 0 — отказ компилятора не задел ни одного законного типа; - meta-compile 94/94 и meta-validate 48/48 в обоих рантаймах, 11 гардов; - снэпшот type-with-prefix-passthrough принят платформой 8.3.24.1691. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBsZA5cr2WFThtgp7i5WVi
This commit is contained in:
co-authored by
Claude Opus 5
parent
eccb11b0ed
commit
ed160c65b5
@@ -1,4 +1,4 @@
|
||||
# meta-compile v1.110 — Compile 1C metadata object from JSON
|
||||
# meta-compile v1.111 — Compile 1C metadata object from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
[CmdletBinding(PositionalBinding=$false)]
|
||||
param(
|
||||
@@ -911,8 +911,21 @@ function Emit-TypeContent {
|
||||
return
|
||||
}
|
||||
|
||||
# Fallback — emit as-is
|
||||
X "$indent<v8:Type>$typeStr</v8:Type>"
|
||||
# Имя с готовым префиксом пропускаем как есть: это законный ввод (v8:ValueTable, ent:AccountType,
|
||||
# v8ui:Color …), и своё пространство имён выбрал вызывающий. Правильность имени судит
|
||||
# meta-validate (проверка 22) — там виден весь файл и вид владельца.
|
||||
if ($typeStr.Contains(':')) {
|
||||
X "$indent<v8:Type>$typeStr</v8:Type>"
|
||||
return
|
||||
}
|
||||
|
||||
# Голое имя без префикса и без совпадения с известной формой — не тип платформы. Раньше
|
||||
# такое имя уходило в XML дословно, и отказ приходил только от платформы при загрузке
|
||||
# всей конфигурации — «Неизвестное имя типа», без указания объекта и реквизита.
|
||||
Write-Error ("Неизвестный тип '$typeStr'. Допустимые формы: String(N), Number(D,F), Boolean, " +
|
||||
"Date/DateTime/Time, ValueStorage, UUID, BinaryData; ссылочные <Вид>Ref.<Имя>; " +
|
||||
"DefinedType.<Имя>, Characteristic.<Имя>; типы платформы с префиксом (v8:ValueTable, ent:AccountType).")
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Emit-ValueType {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# meta-compile v1.110 — Compile 1C metadata object from JSON
|
||||
# meta-compile v1.111 — Compile 1C metadata object from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -1022,8 +1022,21 @@ def emit_type_content(indent, type_str):
|
||||
else:
|
||||
X(f'{indent}<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:{type_str}</v8:Type>')
|
||||
return
|
||||
# Fallback
|
||||
X(f'{indent}<v8:Type>{type_str}</v8:Type>')
|
||||
# Имя с готовым префиксом пропускаем как есть: это законный ввод (v8:ValueTable, ent:AccountType,
|
||||
# v8ui:Color …), и своё пространство имён выбрал вызывающий. Правильность имени судит
|
||||
# meta-validate (проверка 22) — там виден весь файл и вид владельца.
|
||||
if ':' in type_str:
|
||||
X(f'{indent}<v8:Type>{type_str}</v8:Type>')
|
||||
return
|
||||
|
||||
# Голое имя без префикса и без совпадения с известной формой — не тип платформы. Раньше
|
||||
# такое имя уходило в XML дословно, и отказ приходил только от платформы при загрузке
|
||||
# всей конфигурации — «Неизвестное имя типа», без указания объекта и реквизита.
|
||||
print(f"Неизвестный тип '{type_str}'. Допустимые формы: String(N), Number(D,F), Boolean, "
|
||||
"Date/DateTime/Time, ValueStorage, UUID, BinaryData; ссылочные <Вид>Ref.<Имя>; "
|
||||
"DefinedType.<Имя>, Characteristic.<Имя>; типы платформы с префиксом (v8:ValueTable, ent:AccountType).",
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def emit_value_type(indent, type_str):
|
||||
X(f'{indent}<Type>')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# meta-validate v1.27 — Validate 1C metadata object structure
|
||||
# meta-validate v1.28 — Validate 1C metadata object structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
[CmdletBinding(PositionalBinding=$false)]
|
||||
param(
|
||||
@@ -1625,6 +1625,106 @@ if ($script:configDir) {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check 22: имя типа — грамматика (уровень 1) и словарь по контексту владельца (уровень 2) ---
|
||||
# УРОВЕНЬ 1 не зависит ни от версии платформы, ни от состава конфигурации: содержимое <v8:Type>
|
||||
# всегда несёт префикс пространства имён (xs:/v8:/cfg:/dNpM:/ent:/…). Голое имя платформа не примет
|
||||
# никогда — так выглядит и тип СУБД («varchar(150)»), и опечатка («Srting(20)»).
|
||||
# УРОВЕНЬ 2 — словарь: у хранимого объекта набор типов у́же, чем у обработки или отчёта, где
|
||||
# доступны ТаблицаЗначений, ОписаниеТипов, Картинка и прочие рантайм-типы. Здесь только
|
||||
# предупреждение: список конечен, но пополняется с версиями платформы.
|
||||
$knownXsTypes = @("xs:string", "xs:decimal", "xs:boolean", "xs:dateTime", "xs:base64Binary")
|
||||
$knownV8Types = @("ValueStorage", "UUID", "Null", "Type", "ValueTable", "ValueTree", "ValueList",
|
||||
"ValueListType", "StandardPeriod", "StandardBeginningDate", "PointInTime", "TypeDescription",
|
||||
"FixedArray", "FixedMap", "FixedStructure", "FillChecking", "Universal")
|
||||
# Ссылочные метатипы: с именем объекта (<Метатип>.<Имя>) — конкретный тип, без имени — множество.
|
||||
$refMetaTypes = @("CatalogRef", "DocumentRef", "EnumRef", "ChartOfAccountsRef",
|
||||
"ChartOfCharacteristicTypesRef", "ChartOfCalculationTypesRef", "ExchangePlanRef",
|
||||
"BusinessProcessRef", "BusinessProcessRoutePointRef", "TaskRef", "AnyRef", "AnyIBRef")
|
||||
# Прочие имена пространства current-config без точки — платформенные, состав конфигурации их не меняет.
|
||||
$cfgBareNames = @("ConstantsSet", "ReportBuilder", "FilterCriterion", "DynamicList")
|
||||
# Виды, чьи реквизиты ХРАНЯТСЯ в базе: там рантайм-типы недопустимы. У обработки и отчёта — наоборот.
|
||||
$storedOwnerTypes = @("Catalog", "Document", "DocumentJournal", "InformationRegister",
|
||||
"AccumulationRegister", "AccountingRegister", "CalculationRegister", "ChartOfAccounts",
|
||||
"ChartOfCharacteristicTypes", "ChartOfCalculationTypes", "ExchangePlan", "BusinessProcess",
|
||||
"Task", "Constant", "Table")
|
||||
|
||||
function Test-StorableType([string]$t) {
|
||||
if ($knownXsTypes -contains $t) { return $true }
|
||||
if ($t -eq "v8:ValueStorage" -or $t -eq "v8:UUID" -or $t -eq "v8:Null") { return $true }
|
||||
$m = [regex]::Match($t, '^(?:cfg|d\d+p\d+):(.+)$')
|
||||
if (-not $m.Success) { return $false }
|
||||
$name = $m.Groups[1].Value
|
||||
$base = if ($name.Contains('.')) { $name.Substring(0, $name.IndexOf('.')) } else { $name }
|
||||
if ($refMetaTypes -contains $base) { return $true }
|
||||
if ($base -eq "DefinedType" -or $base -eq "Characteristic" -or $base -eq "ExternalDataSourceTableRef") { return $true }
|
||||
return $false
|
||||
}
|
||||
|
||||
$typeNodes22 = @($xmlDoc.SelectNodes("//v8:Type", $ns)) + @($xmlDoc.SelectNodes("//v8:TypeSet", $ns))
|
||||
# Уровень 2 смотрит только на типы САМИХ реквизитов: параметры команд, характеристики и стандартные
|
||||
# реквизиты живут по другим правилам, и мешать их в один котёл нельзя.
|
||||
$attrTypePaths = @("Attribute", "Dimension", "Resource", "Column", "Field", "AddressingAttribute",
|
||||
"AccountingFlag", "ExtDimensionAccountingFlag")
|
||||
$badGrammar = @{}
|
||||
$unknownVocab = @{}
|
||||
$notStorable = @{}
|
||||
$typesSeen = 0
|
||||
foreach ($tn in $typeNodes22) {
|
||||
$t = "$($tn.InnerText)".Trim()
|
||||
if (-not $t) { continue }
|
||||
$typesSeen++
|
||||
if (-not $t.Contains(':')) {
|
||||
$badGrammar[$t] = $true
|
||||
continue
|
||||
}
|
||||
$prefix = $t.Substring(0, $t.IndexOf(':'))
|
||||
$local = $t.Substring($t.IndexOf(':') + 1)
|
||||
if ($prefix -eq "xs") {
|
||||
if ($knownXsTypes -notcontains $t) { $unknownVocab[$t] = $true }
|
||||
} elseif ($prefix -eq "v8") {
|
||||
if ($knownV8Types -notcontains $local) { $unknownVocab[$t] = $true }
|
||||
} elseif ($prefix -eq "cfg" -or $prefix -match '^d\d+p\d+$') {
|
||||
# Имя объекта конфигурации проверяет Check 16; здесь — только форма и платформенная часть.
|
||||
if (-not $local.Contains('.')) {
|
||||
if (($refMetaTypes -notcontains $local) -and ($cfgBareNames -notcontains $local) -and
|
||||
($local -notmatch '^[A-Za-z][A-Za-z0-9]*(Object|Manager|List|Selection|RecordSet|RecordKey|RecordManager)$')) {
|
||||
$unknownVocab[$t] = $true
|
||||
}
|
||||
} elseif ($local -notmatch '^[A-Za-z][A-Za-z0-9]*\.[^.]+(\.[^.]+)?$') {
|
||||
$badGrammar[$t] = $true
|
||||
}
|
||||
}
|
||||
# Прочие пространства (ent:, v8ui:, dcs*:, mxl: …) — форму имени не навязываем: там свои словари.
|
||||
|
||||
if ($storedOwnerTypes -contains $mdType) {
|
||||
$owner = $tn.ParentNode # <Type>
|
||||
$props = if ($owner) { $owner.ParentNode } else { $null } # <Properties>
|
||||
$child = if ($props) { $props.ParentNode } else { $null } # <Attribute>/<Field>/…
|
||||
if ($child -and ($attrTypePaths -contains $child.LocalName) -and -not (Test-StorableType $t)) {
|
||||
$notStorable[$t] = $child.LocalName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($badGrammar.Count -gt 0) {
|
||||
foreach ($bk in ($badGrammar.Keys | Sort-Object)) {
|
||||
Report-Error "22. Тип '$bk' — не имя типа платформы: нет префикса пространства имён либо неверна форма ссылочного типа. При загрузке — «Неизвестное имя типа»"
|
||||
}
|
||||
}
|
||||
if ($notStorable.Count -gt 0) {
|
||||
foreach ($nk in ($notStorable.Keys | Sort-Object)) {
|
||||
Report-Warn "22. Тип '$nk' у элемента $($notStorable[$nk]) объекта ${mdType}: такие типы бывают у реквизитов обработок и отчётов, но не у хранимых в базе"
|
||||
}
|
||||
}
|
||||
if ($unknownVocab.Count -gt 0) {
|
||||
foreach ($uk in ($unknownVocab.Keys | Sort-Object)) {
|
||||
Report-Warn "22. Тип '$uk' не в списке известных платформенных типов — проверьте написание (список пополняется с версиями платформы)"
|
||||
}
|
||||
}
|
||||
if ($badGrammar.Count -eq 0 -and $notStorable.Count -eq 0 -and $unknownVocab.Count -eq 0 -and $typesSeen -gt 0) {
|
||||
Report-OK "22. Type names: $typesSeen checked"
|
||||
}
|
||||
|
||||
# --- Check 18: свойства, появившиеся в новых версиях формата ---
|
||||
# Реестр «тег → минимальная версия формата». Служит двум целям: (1) поймать свойство в файле со
|
||||
# слишком старым штампом — при сборке на старой платформе оно будет молча отброшено (платформа
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# meta-validate v1.27 — Validate 1C metadata object structure (Python port)
|
||||
# meta-validate v1.28 — Validate 1C metadata object structure (Python port)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import os
|
||||
@@ -1569,6 +1569,103 @@ if config_dir:
|
||||
if not absent_refs and not missing_refs and not unknown_refs and checked_refs:
|
||||
report_ok(f"16. Reference types: {len(checked_refs)} resolved")
|
||||
|
||||
# ── Check 22: имя типа — грамматика (уровень 1) и словарь по контексту владельца (уровень 2) ──
|
||||
# УРОВЕНЬ 1 не зависит ни от версии платформы, ни от состава конфигурации: содержимое <v8:Type>
|
||||
# всегда несёт префикс пространства имён (xs:/v8:/cfg:/dNpM:/ent:/…). Голое имя платформа не примет
|
||||
# никогда — так выглядит и тип СУБД («varchar(150)»), и опечатка («Srting(20)»).
|
||||
# УРОВЕНЬ 2 — словарь: у хранимого объекта набор типов у́же, чем у обработки или отчёта, где
|
||||
# доступны ТаблицаЗначений, ОписаниеТипов, Картинка и прочие рантайм-типы. Здесь только
|
||||
# предупреждение: список конечен, но пополняется с версиями платформы.
|
||||
KNOWN_XS_TYPES = {"xs:string", "xs:decimal", "xs:boolean", "xs:dateTime", "xs:base64Binary"}
|
||||
KNOWN_V8_TYPES = {
|
||||
"ValueStorage", "UUID", "Null", "Type", "ValueTable", "ValueTree", "ValueList",
|
||||
"ValueListType", "StandardPeriod", "StandardBeginningDate", "PointInTime", "TypeDescription",
|
||||
"FixedArray", "FixedMap", "FixedStructure", "FillChecking", "Universal",
|
||||
}
|
||||
# Ссылочные метатипы: с именем объекта (<Метатип>.<Имя>) — конкретный тип, без имени — множество.
|
||||
REF_META_TYPES = {
|
||||
"CatalogRef", "DocumentRef", "EnumRef", "ChartOfAccountsRef",
|
||||
"ChartOfCharacteristicTypesRef", "ChartOfCalculationTypesRef", "ExchangePlanRef",
|
||||
"BusinessProcessRef", "BusinessProcessRoutePointRef", "TaskRef", "AnyRef", "AnyIBRef",
|
||||
}
|
||||
# Прочие имена пространства current-config без точки — платформенные, состав конфигурации их не меняет.
|
||||
CFG_BARE_NAMES = {"ConstantsSet", "ReportBuilder", "FilterCriterion", "DynamicList"}
|
||||
# Виды, чьи реквизиты ХРАНЯТСЯ в базе: там рантайм-типы недопустимы. У обработки и отчёта — наоборот.
|
||||
STORED_OWNER_TYPES = {
|
||||
"Catalog", "Document", "DocumentJournal", "InformationRegister", "AccumulationRegister",
|
||||
"AccountingRegister", "CalculationRegister", "ChartOfAccounts", "ChartOfCharacteristicTypes",
|
||||
"ChartOfCalculationTypes", "ExchangePlan", "BusinessProcess", "Task", "Constant", "Table",
|
||||
}
|
||||
ATTR_TYPE_HOLDERS = {
|
||||
"Attribute", "Dimension", "Resource", "Column", "Field", "AddressingAttribute",
|
||||
"AccountingFlag", "ExtDimensionAccountingFlag",
|
||||
}
|
||||
|
||||
|
||||
def _is_storable_type(t):
|
||||
if t in KNOWN_XS_TYPES:
|
||||
return True
|
||||
if t in ("v8:ValueStorage", "v8:UUID", "v8:Null"):
|
||||
return True
|
||||
m = re.match(r"^(?:cfg|d\d+p\d+):(.+)$", t)
|
||||
if not m:
|
||||
return False
|
||||
name = m.group(1)
|
||||
base = name.split(".", 1)[0]
|
||||
if base in REF_META_TYPES:
|
||||
return True
|
||||
return base in ("DefinedType", "Characteristic", "ExternalDataSourceTableRef")
|
||||
|
||||
|
||||
type_nodes_22 = root.findall(".//v8:Type", NS) + root.findall(".//v8:TypeSet", NS)
|
||||
bad_grammar = {}
|
||||
unknown_vocab = {}
|
||||
not_storable = {}
|
||||
types_seen = 0
|
||||
for tn in type_nodes_22:
|
||||
t = (tn.text or "").strip()
|
||||
if not t:
|
||||
continue
|
||||
types_seen += 1
|
||||
if ":" not in t:
|
||||
bad_grammar[t] = True
|
||||
continue
|
||||
prefix, local = t.split(":", 1)
|
||||
if prefix == "xs":
|
||||
if t not in KNOWN_XS_TYPES:
|
||||
unknown_vocab[t] = True
|
||||
elif prefix == "v8":
|
||||
if local not in KNOWN_V8_TYPES:
|
||||
unknown_vocab[t] = True
|
||||
elif prefix == "cfg" or re.match(r"^d\d+p\d+$", prefix):
|
||||
# Имя объекта конфигурации проверяет Check 16; здесь — только форма и платформенная часть.
|
||||
if "." not in local:
|
||||
if (local not in REF_META_TYPES and local not in CFG_BARE_NAMES
|
||||
and not re.match(r"^[A-Za-z][A-Za-z0-9]*(Object|Manager|List|Selection|RecordSet|RecordKey|RecordManager)$", local)):
|
||||
unknown_vocab[t] = True
|
||||
elif not re.match(r"^[A-Za-z][A-Za-z0-9]*\.[^.]+(\.[^.]+)?$", local):
|
||||
bad_grammar[t] = True
|
||||
# Прочие пространства (ent:, v8ui:, dcs*:, mxl: …) — форму имени не навязываем: там свои словари.
|
||||
|
||||
if md_type in STORED_OWNER_TYPES:
|
||||
owner = tn.getparent() # <Type>
|
||||
props = owner.getparent() if owner is not None else None # <Properties>
|
||||
child = props.getparent() if props is not None else None # <Attribute>/<Field>/…
|
||||
if child is not None and etree.QName(child).localname in ATTR_TYPE_HOLDERS and not _is_storable_type(t):
|
||||
not_storable[t] = etree.QName(child).localname
|
||||
|
||||
for bk in sorted(bad_grammar):
|
||||
report_error(f"22. Тип '{bk}' — не имя типа платформы: нет префикса пространства имён либо "
|
||||
"неверна форма ссылочного типа. При загрузке — «Неизвестное имя типа»")
|
||||
for nk in sorted(not_storable):
|
||||
report_warn(f"22. Тип '{nk}' у элемента {not_storable[nk]} объекта {md_type}: такие типы "
|
||||
"бывают у реквизитов обработок и отчётов, но не у хранимых в базе")
|
||||
for uk in sorted(unknown_vocab):
|
||||
report_warn(f"22. Тип '{uk}' не в списке известных платформенных типов — проверьте написание "
|
||||
"(список пополняется с версиями платформы)")
|
||||
if not bad_grammar and not not_storable and not unknown_vocab and types_seen:
|
||||
report_ok(f"22. Type names: {types_seen} checked")
|
||||
|
||||
# ── Check 18: свойства, появившиеся в новых версиях формата ──
|
||||
# Реестр «тег → минимальная версия формата». Служит двум целям: (1) поймать свойство в файле со
|
||||
# слишком старым штампом — при сборке на старой платформе оно будет молча отброшено (платформа
|
||||
|
||||
Reference in New Issue
Block a user