feat(meta-edit): create-if-missing свойств + типо-гард (Фаза 2 Шаг 1, v1.12)

modify-property и default-ветка modify-attribute/-dimension/-resource раньше при
отсутствии элемента свойства делали тихий Warn+no-op — правка молча терялась
(модель думала, что применилось). Теперь: известное отсутствующее свойство
СОЗДАЁТСЯ (в канонической позиции по attrPropOrder для реквизитов, append для
объектных — порядок 1С терпит, cert Шаг 0); неизвестное имя (опечатка) → внятная
ошибка exit≠0. Наборы известных свойств — union по корпусу acc+erp 8.3.24
(knownObjectProps 133, knownChildProps 40); attrPropOrder — из Build-AttributeFragment.
Новый хелпер Insert-PropertyInOrder. ps1+py зеркально.

Cert: Шаг 0 (навыками db-*) — 1С грузит и применяет объект со свойством вне
канонической позиции → append безопасен. Load-cert create-if-missing выхода —
LoadConfigFromFiles+UpdateDBCfg успешны. verify-snapshots --skill meta-edit 9/9.

Тесты: error-modify-property-typo + error-modify-attribute-typo (expectError) +
integration meta-edit-create-if-missing (compile→editFile удалить→правка→assert).
meta-edit 13/13 ps1+py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-07-11 22:23:07 +03:00
parent 9b5908c3b2
commit 0e68421f50
5 changed files with 252 additions and 8 deletions
+98 -4
View File
@@ -1,4 +1,4 @@
# meta-edit v1.11 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts)
# meta-edit v1.12 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts + create-if-missing свойств)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$DefinitionFile,
@@ -1974,8 +1974,19 @@ function Modify-Properties($propsDef) {
}
if (-not $propEl) {
Warn "Property '$propName' not found in Properties"
return
# create-if-missing: известное свойство создаём (порядок 1С терпит — append); неизвестное → ошибка (опечатка)
if ($script:knownObjectProps -notcontains $propName) {
Write-Error "modify-property: неизвестное свойство '$propName' — нет такого свойства объекта (опечатка?)"
exit 1
}
$newNodes = Import-Fragment "<$propName/>"
if ($newNodes.Count -gt 0) {
Insert-PropertyInOrder $script:propertiesEl $newNodes[0] $null $propName
$propEl = $newNodes[0]
} else {
Warn "Property '$propName': could not create element"
return
}
}
# Complex property: Owners, RegisterRecords, BasedOn, InputByString
@@ -2248,7 +2259,23 @@ function Modify-ChildElements($modifyDef, [string]$childType) {
Info "Modified $xmlTag '$elemName'.$changeProp = $valueStr"
$script:modifyCount++
} else {
Warn "$xmlTag '$elemName': property '$changeProp' not found"
# create-if-missing: известное свойство создаём в позиции; неизвестное → ошибка (опечатка)
if ($script:knownChildProps -notcontains $changeProp) {
Write-Error "modify: неизвестное свойство '$changeProp' у $xmlTag '$elemName' (опечатка?)"
exit 1
}
$valueStr = "$changeValue"
if ($changeValue -is [bool]) {
$valueStr = if ($changeValue) { "true" } else { "false" }
} else {
$valueStr = Normalize-EnumValue $changeProp $valueStr
}
$newNodes = Import-Fragment "<$changeProp>$(Esc-Xml $valueStr)</$changeProp>"
if ($newNodes.Count -gt 0) {
Insert-PropertyInOrder $propsEl $newNodes[0] $script:attrPropOrder $changeProp
Info "Created $xmlTag '$elemName'.$changeProp = $valueStr"
$script:modifyCount++
}
}
}
}
@@ -2286,6 +2313,73 @@ $script:complexPropertyMap = @{
"InputByString" = @{ tag = "xr:Field"; attr = $null }
}
# Известные свойства объекта (union по корпусу acc+erp 8.3.24) — allowlist для modify-property.
# Известное отсутствующее свойство create-if-missing создаётся; неизвестное (опечатка) → ошибка.
$script:knownObjectProps = @(
'ActionPeriod','ActionPeriodUse','Addressing','AutoOrderByCode','Autonumbering','AuxiliaryChoiceForm',
'AuxiliaryFolderChoiceForm','AuxiliaryFolderForm','AuxiliaryForm','AuxiliaryListForm','AuxiliaryObjectForm',
'AuxiliaryRecordForm','AuxiliarySettingsForm','BaseCalculationTypes','BasePeriod','BasedOn',
'CharacteristicExtValues','Characteristics','ChartOfAccounts','ChartOfCalculationTypes','CheckUnique',
'ChoiceDataGetModeOnInputByString','ChoiceFoldersAndItems','ChoiceForm','ChoiceHistoryOnInput','ChoiceMode',
'ChoiceParameterLinks','ChoiceParameters','CodeAllowedLength','CodeLength','CodeMask','CodeSeries','CodeType',
'Comment','Correspondence','CreateOnInput','CreateTaskInPrivilegedMode','CurrentPerformer','DataHistory',
'DataLockControlMode','DataLockFields','DefaultChoiceForm','DefaultFolderChoiceForm','DefaultFolderForm',
'DefaultForm','DefaultListForm','DefaultObjectForm','DefaultPresentation','DefaultRecordForm','DefaultSettingsForm',
'DefaultVariantForm','DependenceOnCalculationTypes','DescriptionLength','DistributedInfoBase','EditFormat',
'EditType','EnableTotalsSliceFirst','EnableTotalsSliceLast','EnableTotalsSplitting',
'ExecuteAfterWriteDataHistoryVersionProcessing','Explanation','ExtDimensionTypes','ExtendedEdit',
'ExtendedListPresentation','ExtendedObjectPresentation','ExtendedPresentation','ExtendedRecordPresentation',
'FillChecking','FoldersOnTop','Format','FullTextSearch','FullTextSearchOnInputByString','Hierarchical',
'HierarchyType','IncludeConfigurationExtensions','IncludeHelpInContents','InformationRegisterPeriodicity',
'InputByString','LevelCount','LimitLevelCount','LinkByType','ListPresentation','MainAddressingAttribute',
'MainDataCompositionSchema','MainFilterOnPeriod','MarkNegatives','Mask','MaxExtDimensionCount','MaxValue',
'MinValue','MultiLine','Name','NumberAllowedLength','NumberLength','NumberPeriodicity','NumberType','Numerator',
'ObjectPresentation','OrderLength','Owners','PasswordMode','PeriodAdjustmentLength','Periodicity',
'PostInPrivilegedMode','Posting','PredefinedDataUpdate','QuickChoice','RealTimePosting','RecordPresentation',
'RegisterRecords','RegisterRecordsDeletion','RegisterRecordsWritingOnPost','RegisterType','RegisteredDocuments',
'Schedule','ScheduleDate','ScheduleValue','SearchStringModeOnInputByString','SequenceFilling','SettingsStorage',
'StandardAttributes','StandardTabularSections','SubordinationUse','Synonym','Task','TaskNumberAutoPrefix',
'ToolTip','Type','UnpostInPrivilegedMode','UpdateDataHistoryImmediatelyAfterWrite','UseStandardCommands',
'VariantsStorage','WriteMode'
)
# Известные свойства дочерних элементов (union Attribute/Dimension/Resource по корпусу) — allowlist default-ветки
# modify-attribute/-dimension/-resource.
$script:knownChildProps = @(
'AccountingFlag','Balance','BaseDimension','ChoiceFoldersAndItems','ChoiceForm','ChoiceHistoryOnInput',
'ChoiceParameterLinks','ChoiceParameters','Comment','CreateOnInput','DataHistory','DenyIncompleteValues',
'DocumentMap','EditFormat','ExtDimensionAccountingFlag','ExtendedEdit','FillChecking','FillFromFillingValue',
'FillValue','Format','FullTextSearch','Indexing','LinkByType','MainFilter','MarkNegatives','Mask','Master',
'MaxValue','MinValue','MultiLine','Name','PasswordMode','QuickChoice','RegisterRecordsMap','ScheduleLink',
'Synonym','ToolTip','Type','Use','UseInTotals'
)
# Канонический порядок свойств реквизита (последовательность Build-AttributeFragment) — для вставки в позицию.
# Порядок 1С терпит (cert), но держим канонический для консистентности с meta-compile.
$script:attrPropOrder = @(
'Name','Synonym','Comment','Type','PasswordMode','Format','EditFormat','ToolTip','MarkNegatives','Mask',
'MultiLine','ExtendedEdit','MinValue','MaxValue','FillFromFillingValue','FillValue','FillChecking',
'ChoiceFoldersAndItems','ChoiceParameterLinks','ChoiceParameters','QuickChoice','CreateOnInput','ChoiceForm',
'LinkByType','ChoiceHistoryOnInput','Use','Indexing','FullTextSearch','DataHistory'
)
# Вставить новый элемент свойства в Properties в канонической позиции (по orderArray); если свойства нет в
# orderArray (или orderArray пуст) — append. Порядок 1С терпит, канонический — для консистентности/снапшотов.
function Insert-PropertyInOrder($propsEl, $newNode, $orderArray, $propName) {
$childIndent = "$(Get-ChildIndent $propsEl)"
$refNode = $null
$idx = if ($orderArray) { [array]::IndexOf($orderArray, $propName) } else { -1 }
if ($idx -ge 0) {
foreach ($ch in $propsEl.ChildNodes) {
if ($ch.NodeType -eq 'Element') {
$ci = [array]::IndexOf($orderArray, $ch.LocalName)
if ($ci -gt $idx) { $refNode = $ch; break }
}
}
}
Insert-BeforeElement $propsEl $newNode $refNode $childIndent
}
function Find-PropertyElement([string]$propName) {
foreach ($child in $script:propertiesEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $propName) {
+88 -4
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-edit v1.11 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts)
# meta-edit v1.12 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts + create-if-missing свойств)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1858,8 +1858,17 @@ def modify_properties(props_def):
break
if prop_el is None:
warn(f"Property '{prop_name}' not found in Properties")
continue
# create-if-missing: известное свойство создаём (порядок 1С терпит); неизвестное → ошибка (опечатка)
if prop_name not in known_object_props:
print(f"meta-edit: modify-property: неизвестное свойство '{prop_name}' — нет такого свойства объекта (опечатка?)", file=sys.stderr)
sys.exit(1)
new_nodes = import_fragment(f"<{prop_name}/>")
if new_nodes:
insert_property_in_order(properties_el, new_nodes[0], None, prop_name)
prop_el = new_nodes[0]
else:
warn(f"Property '{prop_name}': could not create element")
continue
# Complex property: Owners, RegisterRecords, BasedOn, InputByString
if prop_name in complex_property_map:
@@ -2100,7 +2109,20 @@ def modify_child_elements(modify_def, child_type):
info(f"Modified {xml_tag} '{elem_name}'.{change_prop} = {value_str}")
modify_count += 1
else:
warn(f"{xml_tag} '{elem_name}': property '{change_prop}' not found")
# create-if-missing: известное свойство создаём в позиции; неизвестное → ошибка (опечатка)
if change_prop not in known_child_props:
print(f"meta-edit: modify: неизвестное свойство '{change_prop}' у {xml_tag} '{elem_name}' (опечатка?)", file=sys.stderr)
sys.exit(1)
value_str = str(change_value)
if isinstance(change_value, bool):
value_str = "true" if change_value else "false"
else:
value_str = normalize_enum_value(change_prop, value_str)
new_nodes = import_fragment(f"<{change_prop}>{esc_xml(value_str)}</{change_prop}>")
if new_nodes:
insert_property_in_order(props_el, new_nodes[0], attr_prop_order, change_prop)
info(f"Created {xml_tag} '{elem_name}'.{change_prop} = {value_str}")
modify_count += 1
def process_modify(modify_def):
@@ -2128,6 +2150,68 @@ complex_property_map = {
"InputByString": {"tag": "xr:Field", "attr": None},
}
# Известные свойства объекта (union по корпусу acc+erp 8.3.24) — allowlist для modify-property.
# Известное отсутствующее свойство create-if-missing создаётся; неизвестное (опечатка) → ошибка.
known_object_props = {
'ActionPeriod', 'ActionPeriodUse', 'Addressing', 'AutoOrderByCode', 'Autonumbering', 'AuxiliaryChoiceForm',
'AuxiliaryFolderChoiceForm', 'AuxiliaryFolderForm', 'AuxiliaryForm', 'AuxiliaryListForm', 'AuxiliaryObjectForm',
'AuxiliaryRecordForm', 'AuxiliarySettingsForm', 'BaseCalculationTypes', 'BasePeriod', 'BasedOn',
'CharacteristicExtValues', 'Characteristics', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'CheckUnique',
'ChoiceDataGetModeOnInputByString', 'ChoiceFoldersAndItems', 'ChoiceForm', 'ChoiceHistoryOnInput', 'ChoiceMode',
'ChoiceParameterLinks', 'ChoiceParameters', 'CodeAllowedLength', 'CodeLength', 'CodeMask', 'CodeSeries', 'CodeType',
'Comment', 'Correspondence', 'CreateOnInput', 'CreateTaskInPrivilegedMode', 'CurrentPerformer', 'DataHistory',
'DataLockControlMode', 'DataLockFields', 'DefaultChoiceForm', 'DefaultFolderChoiceForm', 'DefaultFolderForm',
'DefaultForm', 'DefaultListForm', 'DefaultObjectForm', 'DefaultPresentation', 'DefaultRecordForm', 'DefaultSettingsForm',
'DefaultVariantForm', 'DependenceOnCalculationTypes', 'DescriptionLength', 'DistributedInfoBase', 'EditFormat',
'EditType', 'EnableTotalsSliceFirst', 'EnableTotalsSliceLast', 'EnableTotalsSplitting',
'ExecuteAfterWriteDataHistoryVersionProcessing', 'Explanation', 'ExtDimensionTypes', 'ExtendedEdit',
'ExtendedListPresentation', 'ExtendedObjectPresentation', 'ExtendedPresentation', 'ExtendedRecordPresentation',
'FillChecking', 'FoldersOnTop', 'Format', 'FullTextSearch', 'FullTextSearchOnInputByString', 'Hierarchical',
'HierarchyType', 'IncludeConfigurationExtensions', 'IncludeHelpInContents', 'InformationRegisterPeriodicity',
'InputByString', 'LevelCount', 'LimitLevelCount', 'LinkByType', 'ListPresentation', 'MainAddressingAttribute',
'MainDataCompositionSchema', 'MainFilterOnPeriod', 'MarkNegatives', 'Mask', 'MaxExtDimensionCount', 'MaxValue',
'MinValue', 'MultiLine', 'Name', 'NumberAllowedLength', 'NumberLength', 'NumberPeriodicity', 'NumberType', 'Numerator',
'ObjectPresentation', 'OrderLength', 'Owners', 'PasswordMode', 'PeriodAdjustmentLength', 'Periodicity',
'PostInPrivilegedMode', 'Posting', 'PredefinedDataUpdate', 'QuickChoice', 'RealTimePosting', 'RecordPresentation',
'RegisterRecords', 'RegisterRecordsDeletion', 'RegisterRecordsWritingOnPost', 'RegisterType', 'RegisteredDocuments',
'Schedule', 'ScheduleDate', 'ScheduleValue', 'SearchStringModeOnInputByString', 'SequenceFilling', 'SettingsStorage',
'StandardAttributes', 'StandardTabularSections', 'SubordinationUse', 'Synonym', 'Task', 'TaskNumberAutoPrefix',
'ToolTip', 'Type', 'UnpostInPrivilegedMode', 'UpdateDataHistoryImmediatelyAfterWrite', 'UseStandardCommands',
'VariantsStorage', 'WriteMode',
}
# Известные свойства дочерних элементов (union Attribute/Dimension/Resource) — allowlist default-ветки modify-child.
known_child_props = {
'AccountingFlag', 'Balance', 'BaseDimension', 'ChoiceFoldersAndItems', 'ChoiceForm', 'ChoiceHistoryOnInput',
'ChoiceParameterLinks', 'ChoiceParameters', 'Comment', 'CreateOnInput', 'DataHistory', 'DenyIncompleteValues',
'DocumentMap', 'EditFormat', 'ExtDimensionAccountingFlag', 'ExtendedEdit', 'FillChecking', 'FillFromFillingValue',
'FillValue', 'Format', 'FullTextSearch', 'Indexing', 'LinkByType', 'MainFilter', 'MarkNegatives', 'Mask', 'Master',
'MaxValue', 'MinValue', 'MultiLine', 'Name', 'PasswordMode', 'QuickChoice', 'RegisterRecordsMap', 'ScheduleLink',
'Synonym', 'ToolTip', 'Type', 'Use', 'UseInTotals',
}
# Канонический порядок свойств реквизита (последовательность build_attribute_fragment) — для вставки в позицию.
attr_prop_order = [
'Name', 'Synonym', 'Comment', 'Type', 'PasswordMode', 'Format', 'EditFormat', 'ToolTip', 'MarkNegatives', 'Mask',
'MultiLine', 'ExtendedEdit', 'MinValue', 'MaxValue', 'FillFromFillingValue', 'FillValue', 'FillChecking',
'ChoiceFoldersAndItems', 'ChoiceParameterLinks', 'ChoiceParameters', 'QuickChoice', 'CreateOnInput', 'ChoiceForm',
'LinkByType', 'ChoiceHistoryOnInput', 'Use', 'Indexing', 'FullTextSearch', 'DataHistory',
]
def insert_property_in_order(props_el, new_node, order_array, prop_name):
"""Вставить новое свойство в Properties в канонической позиции (по order_array); иначе append."""
child_indent = get_child_indent(props_el)
ref_node = None
idx = order_array.index(prop_name) if (order_array and prop_name in order_array) else -1
if idx >= 0:
for ch in props_el:
cl = localname(ch)
if cl in order_array and order_array.index(cl) > idx:
ref_node = ch
break
insert_before_element(props_el, new_node, ref_node, child_indent)
def find_property_element(prop_name):
for child in properties_el:
@@ -0,0 +1,14 @@
{
"name": "modify-attribute с опечаткой в имени свойства реквизита → ошибка (не тихий no-op)",
"setup": "empty-config",
"preRun": [
{
"script": "meta-compile/scripts/meta-compile",
"input": { "type": "Catalog", "name": "Спр", "attributes": ["Комм: String(50)"] },
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
}
],
"params": { "objectPath": "Catalogs/Спр" },
"input": { "modify": { "attributes": { "Комм": { "ЧёТоНеТо": "1" } } } },
"expectError": true
}
@@ -0,0 +1,14 @@
{
"name": "modify-property с опечаткой в имени свойства → ошибка (не тихий no-op)",
"setup": "empty-config",
"preRun": [
{
"script": "meta-compile/scripts/meta-compile",
"input": { "type": "Catalog", "name": "Спр" },
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
}
],
"params": { "objectPath": "Catalogs/Спр" },
"input": { "modify": { "properties": { "CodeLenght": "11" } } },
"expectError": true
}
@@ -0,0 +1,38 @@
// meta-edit-create-if-missing.test.mjs — Integration: create-if-missing свойства объекта
// Steps: cf-init → meta-compile Catalog → editFile (удалить DataHistory) → meta-edit modify-property
// DataHistory=Use (свойство отсутствует → должно СОЗДАТЬСЯ, а не тихий no-op) → assertContains
export const name = 'meta-edit create-if-missing: пересоздание отсутствующего свойства объекта';
export const setup = 'none';
export const steps = [
{
name: 'cf-init: пустая конфигурация',
script: 'cf-init/scripts/cf-init',
args: { '-Name': 'ТестПравки', '-OutputDir': '{workDir}/config' },
},
{
name: 'meta-compile: Catalog Спр',
script: 'meta-compile/scripts/meta-compile',
input: { type: 'Catalog', name: 'Спр' },
args: { '-JsonPath': '{inputFile}', '-OutputDir': '{workDir}/config' },
},
{
name: 'editFile: удалить свойство DataHistory (симуляция отсутствия)',
editFile: '{workDir}/config/Catalogs/Спр.xml',
replace: '<DataHistory>DontUse</DataHistory>',
with: '',
},
{
name: 'meta-edit: modify-property DataHistory=Use (create-if-missing)',
script: 'meta-edit/scripts/meta-edit',
input: { modify: { properties: { DataHistory: 'Use' } } },
args: { '-DefinitionFile': '{inputFile}', '-ObjectPath': '{workDir}/config/Catalogs/Спр.xml' },
validate: { script: 'meta-validate/scripts/meta-validate', flag: '-ObjectPath', path: 'config/Catalogs/Спр.xml' },
},
{
name: 'assertContains: DataHistory создан',
assertContains: '{workDir}/config/Catalogs/Спр.xml',
expect: '<DataHistory>Use</DataHistory>',
},
];