feat(form-validate): резолв Items.<Table>.CurrentData.* и ~<Attr>.* в DataPath

В Check 5 раньше брался первый сегмент DataPath и искался в attrMap,
из-за чего ложно ругались реальные формы ERP/БП с путями вида
Items.<TableName>.CurrentData.<Field> (подвалы, инфо-панели) и
~<DynamicListAttr>.<Field> (текущая строка списка).

Теперь:
- ведущий ~ стрипается перед разбором сегментов;
- для Items.<Table>.CurrentData.* находим элемент-таблицу по name,
  берём её <DataPath> (атрибут DynamicList/TableSection) и проверяем
  его в attrMap. Если таблицы нет — Error; если третий сегмент не
  CurrentData — Warn.

Добавлен тест-кейс datapath-currentdata, версия скриптов v1.4 → v1.5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-05-15 11:38:56 +03:00
parent ac3047cf55
commit 54cbc69a59
4 changed files with 109 additions and 2 deletions
@@ -1,4 +1,4 @@
# form-validate v1.4 — Validate 1C managed form
# form-validate v1.5 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -370,9 +370,40 @@ if (-not $stopped) {
# Extract root segment of path, strip array indices like [0]
$cleanPath = $dataPath -replace '\[\d+\]', ''
# Strip leading '~' (current row of DynamicList: ~Список.Поле)
if ($cleanPath.StartsWith('~')) { $cleanPath = $cleanPath.Substring(1) }
$segments = $cleanPath -split '\.'
$rootAttr = $segments[0]
# Resolve Items.<TableName>.CurrentData.<Field>... — table element, not attribute
if ($rootAttr -eq 'Items') {
if ($segments.Count -lt 3 -or $segments[2] -ne 'CurrentData') {
Report-Warn "[$tag] '$elName': DataPath='$dataPath' — unknown Items.* shape, expected Items.<Table>.CurrentData.*"
continue
}
$tableName = $segments[1]
$tableEl = $null
foreach ($candidate in $allElements) {
if ($candidate.Tag -eq 'Table' -and $candidate.Name -eq $tableName) {
$tableEl = $candidate
break
}
}
if (-not $tableEl) {
Report-Error "[$tag] '$elName': DataPath='$dataPath' — table element '$tableName' not found"
$pathErrors++
continue
}
$tableDpNode = $tableEl.Node.SelectSingleNode("f:DataPath", $nsMgr)
if (-not $tableDpNode -or -not $tableDpNode.InnerText.Trim()) {
# Table without DataPath — can't resolve further, accept silently
continue
}
$tableDp = $tableDpNode.InnerText.Trim() -replace '\[\d+\]', ''
if ($tableDp.StartsWith('~')) { $tableDp = $tableDp.Substring(1) }
$rootAttr = ($tableDp -split '\.')[0]
}
if (-not $attrMap.ContainsKey($rootAttr)) {
Report-Error "[$tag] '$elName': DataPath='$dataPath' — attribute '$rootAttr' not found"
$pathErrors++
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-validate v1.4 — Validate 1C managed form
# form-validate v1.5 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -379,9 +379,35 @@ def main():
path_checked += 1
clean_path = re.sub(r'\[\d+\]', '', data_path)
# Strip leading '~' (current row of DynamicList: ~\u0421\u043f\u0438\u0441\u043e\u043a.\u041f\u043e\u043b\u0435)
if clean_path.startswith('~'):
clean_path = clean_path[1:]
segments = clean_path.split(".")
root_attr = segments[0]
# Resolve Items.<TableName>.CurrentData.<Field>... \u2014 table element, not attribute
if root_attr == 'Items':
if len(segments) < 3 or segments[2] != 'CurrentData':
report_warn(f"[{tag}] '{el_name}': DataPath='{data_path}' \u2014 unknown Items.* shape, expected Items.<Table>.CurrentData.*")
continue
table_name = segments[1]
table_el = None
for candidate in all_elements:
if candidate["Tag"] == 'Table' and candidate["Name"] == table_name:
table_el = candidate
break
if table_el is None:
report_error(f"[{tag}] '{el_name}': DataPath='{data_path}' \u2014 table element '{table_name}' not found")
path_errors += 1
continue
table_dp_node = table_el["Node"].find(f"{{{F_NS}}}DataPath")
if table_dp_node is None or not (table_dp_node.text or "").strip():
continue
table_dp = re.sub(r'\[\d+\]', '', (table_dp_node.text or "").strip())
if table_dp.startswith('~'):
table_dp = table_dp[1:]
root_attr = table_dp.split(".")[0]
if root_attr not in attr_map:
report_error(f"[{tag}] '{el_name}': DataPath='{data_path}' \u2014 attribute '{root_attr}' not found")
path_errors += 1
@@ -0,0 +1,5 @@
{
"name": "DataPath с Items.<Table>.CurrentData и ~Атрибут не вызывают ложных ошибок",
"setup": "fixture:datapath-currentdata",
"params": { "formPath": "DataProcessors/Spec/Forms/Форма" }
}
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Тест</v8:content>
</v8:item>
</Title>
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1"/>
<ChildItems>
<Table name="Список" id="1">
<DataPath>Список</DataPath>
<ContextMenu name="СписокКонтекстноеМеню" id="2"/>
<AutoCommandBar name="СписокКоманднаяПанель" id="3"/>
<SearchStringAddition name="СписокСтрокаПоиска" id="4"/>
<ViewStatusAddition name="СписокСостояниеПросмотра" id="5"/>
<SearchControlAddition name="СписокУправлениеПоиском" id="6"/>
<ChildItems>
<InputField name="Ссылка" id="7">
<DataPath>Список.Ссылка</DataPath>
<ContextMenu name="СсылкаКонтекстноеМеню" id="8"/>
<ExtendedTooltip name="СсылкаРасширеннаяПодсказка" id="9"/>
</InputField>
</ChildItems>
</Table>
<InputField name="ТекущаяСсылка" id="10">
<DataPath>Items.Список.CurrentData.Ссылка</DataPath>
<ContextMenu name="ТекущаяСсылкаКонтекстноеМеню" id="11"/>
<ExtendedTooltip name="ТекущаяСсылкаРасширеннаяПодсказка" id="12"/>
</InputField>
<InputField name="ВыбраннаяСсылка" id="13">
<DataPath>~Список.Ссылка</DataPath>
<ContextMenu name="ВыбраннаяСсылкаКонтекстноеМеню" id="14"/>
<ExtendedTooltip name="ВыбраннаяСсылкаРасширеннаяПодсказка" id="15"/>
</InputField>
</ChildItems>
<Attributes>
<Attribute name="Список" id="16">
<Type>
<v8:Type>cfg:DynamicList</v8:Type>
</Type>
<MainAttribute>true</MainAttribute>
</Attribute>
</Attributes>
</Form>