mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-13 15:03:20 +03:00
feat(form-validate): путь на необъявленный основной реквизит заимствованной формы
Форма, которую платформа отвергала с «Неверный путь к полю - Объект.Партнер», проходила валидацию с вердиктом OK. Check 5 такое не видит по двум причинам: у формы с BaseForm он пропускает базовые элементы (id < 1000000), а привязка внутри <ChoiceParameterLinks> лежит в <xr:DataPath> и в его список тегов не входит вовсе. Проверка 11d: если форма не объявляет основной реквизит, любой путь с корнем «Объект» не разрешится — ошибка. Непрозрачные формы пути (1/0:uuid), которыми как раз и заменяет такие ссылки Конфигуратор, ошибкой не считаются. Две фикстуры: висячая ссылка ловится, та же форма в uuid-форме проходит. Регресс 13/13 на обоих рантаймах.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# form-validate v1.10 — Validate 1C managed form
|
||||
# form-validate v1.11 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -742,6 +742,36 @@ if (-not $stopped -and $isExtension) {
|
||||
Report-OK "Extension ID ranges: $extAttrCount attr(s), $extCmdCount cmd(s) — all >= 1000000"
|
||||
}
|
||||
}
|
||||
|
||||
# 11d. Пути на основной реквизит, которого форма не объявляет.
|
||||
# Check 5 такое пропускает: у заимствованной формы он не проверяет базовые элементы (id < 1000000),
|
||||
# а привязки в <xr:Link> вообще вне его списка тегов. Между тем это ровно тот случай, на котором
|
||||
# платформа отвергает загрузку: «Неверный путь к полю - Объект.X». Правило: если основной реквизит
|
||||
# не объявлен в <Attributes> формы, любой путь с корнем «Объект» не разрешится.
|
||||
$mainAttrDeclared = $false
|
||||
foreach ($attr in $attrNodes) {
|
||||
$maNode = $attr.SelectSingleNode("f:MainAttribute", $nsMgr)
|
||||
if ($maNode -and $maNode.InnerText.Trim() -eq "true") { $mainAttrDeclared = $true; break }
|
||||
}
|
||||
|
||||
if (-not $mainAttrDeclared) {
|
||||
# Значения привязок ищем текстом: интересуют и обычные теги, и <xr:DataPath> внутри
|
||||
# <ChoiceParameterLinks>, а те живут в чужом пространстве имён.
|
||||
$rawForm = [System.IO.File]::ReadAllText($FormPath, [System.Text.Encoding]::UTF8)
|
||||
$mainBase = $baseFormNode.SelectSingleNode("f:Attributes/f:Attribute[f:MainAttribute='true']", $bfNs)
|
||||
$danglingPaths = @{}
|
||||
foreach ($m in [regex]::Matches($rawForm, '<(?:\w+:)?\w*DataPath[^>]*>(Объект\.[^<]+)</(?:\w+:)?\w*DataPath>')) {
|
||||
$danglingPaths[$m.Groups[1].Value] = $true
|
||||
}
|
||||
if ($danglingPaths.Count -gt 0) {
|
||||
$shown = @($danglingPaths.Keys | Sort-Object)
|
||||
$sample = ($shown | Select-Object -First 3) -join ", "
|
||||
$suffix = if ($shown.Count -gt 3) { " (и ещё $($shown.Count - 3))" } else { "" }
|
||||
Report-Error "Path(s) rooted at 'Объект' but the form declares no MainAttribute: $sample$suffix"
|
||||
} elseif ($mainBase) {
|
||||
Report-OK "Object paths: none dangling (MainAttribute not declared)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check callType without BaseForm (structural warning)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-validate v1.10 — Validate 1C managed form
|
||||
# form-validate v1.11 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -708,6 +708,33 @@ def main():
|
||||
if (ext_attr_count + ext_cmd_count) > 0:
|
||||
report_ok(f"Extension ID ranges: {ext_attr_count} attr(s), {ext_cmd_count} cmd(s) \u2014 all >= 1000000")
|
||||
|
||||
# 11d. \u041f\u0443\u0442\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442, \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0444\u043e\u0440\u043c\u0430 \u043d\u0435 \u043e\u0431\u044a\u044f\u0432\u043b\u044f\u0435\u0442.
|
||||
# Check 5 \u0442\u0430\u043a\u043e\u0435 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442: \u0443 \u0437\u0430\u0438\u043c\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0444\u043e\u0440\u043c\u044b \u043e\u043d \u043d\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b (id < 1000000),
|
||||
# \u0430 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u0432 <xr:Link> \u0432\u043e\u043e\u0431\u0449\u0435 \u0432\u043d\u0435 \u0435\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0430 \u0442\u0435\u0433\u043e\u0432. \u041c\u0435\u0436\u0434\u0443 \u0442\u0435\u043c \u044d\u0442\u043e \u0440\u043e\u0432\u043d\u043e \u0442\u043e\u0442 \u0441\u043b\u0443\u0447\u0430\u0439, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c
|
||||
# \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u043e\u0442\u0432\u0435\u0440\u0433\u0430\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443: \u00ab\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u043a \u043f\u043e\u043b\u044e - \u041e\u0431\u044a\u0435\u043a\u0442.X\u00bb. \u041f\u0440\u0430\u0432\u0438\u043b\u043e: \u0435\u0441\u043b\u0438 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442
|
||||
# \u043d\u0435 \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d \u0432 <Attributes> \u0444\u043e\u0440\u043c\u044b, \u043b\u044e\u0431\u043e\u0439 \u043f\u0443\u0442\u044c \u0441 \u043a\u043e\u0440\u043d\u0435\u043c \u00ab\u041e\u0431\u044a\u0435\u043a\u0442\u00bb \u043d\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0441\u044f.
|
||||
main_attr_declared = False
|
||||
for attr in attr_nodes:
|
||||
ma_node = attr.find(f"{{{F_NS}}}MainAttribute")
|
||||
if ma_node is not None and (ma_node.text or "").strip() == "true":
|
||||
main_attr_declared = True
|
||||
break
|
||||
|
||||
if not main_attr_declared:
|
||||
# \u0417\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u043e\u043a \u0438\u0449\u0435\u043c \u0442\u0435\u043a\u0441\u0442\u043e\u043c: \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0442 \u0438 \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u0442\u0435\u0433\u0438, \u0438 <xr:DataPath> \u0432\u043d\u0443\u0442\u0440\u0438
|
||||
# <ChoiceParameterLinks>, \u0430 \u0442\u0435 \u0436\u0438\u0432\u0443\u0442 \u0432 \u0447\u0443\u0436\u043e\u043c \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0435 \u0438\u043c\u0451\u043d.
|
||||
with open(form_path, "r", encoding="utf-8-sig") as fh:
|
||||
raw_form = fh.read()
|
||||
main_base = base_form_node.find(f"{{{F_NS}}}Attributes/{{{F_NS}}}Attribute[{{{F_NS}}}MainAttribute='true']")
|
||||
dangling_paths = set(re.findall(r'<(?:\w+:)?\w*DataPath[^>]*>(\u041e\u0431\u044a\u0435\u043a\u0442\.[^<]+)</(?:\w+:)?\w*DataPath>', raw_form))
|
||||
if dangling_paths:
|
||||
shown = sorted(dangling_paths)
|
||||
sample = ", ".join(shown[:3])
|
||||
suffix = f" (\u0438 \u0435\u0449\u0451 {len(shown) - 3})" if len(shown) > 3 else ""
|
||||
report_error(f"Path(s) rooted at '\u041e\u0431\u044a\u0435\u043a\u0442' but the form declares no MainAttribute: {sample}{suffix}")
|
||||
elif main_base is not None:
|
||||
report_ok("Object paths: none dangling (MainAttribute not declared)")
|
||||
|
||||
# Check callType without BaseForm
|
||||
if not stopped and not is_extension:
|
||||
call_type_without_base = False
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Ошибка: путь Объект.* в заимствованной форме без объявленного основного реквизита",
|
||||
"setup": "fixture:borrowed-object-path-no-main",
|
||||
"params": { "formPath": "Documents/Заказ/Forms/ФормаДокумента" },
|
||||
"expectError": true,
|
||||
"expect": { "stdoutContains": "Объект.Партнер" }
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Заимствованная форма без основного реквизита: непрозрачный путь 1/0:uuid ошибкой не считается",
|
||||
"setup": "fixture:borrowed-object-path-opaque",
|
||||
"params": { "formPath": "Documents/Заказ/Forms/ФормаДокумента" }
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?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:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>false</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems>
|
||||
<InputField name="КонтактноеЛицо" id="4">
|
||||
<ChoiceParameterLinks>
|
||||
<xr:Link>
|
||||
<xr:Name>Отбор.Владелец</xr:Name>
|
||||
<xr:DataPath xsi:type="xs:string">Объект.Партнер</xr:DataPath>
|
||||
<xr:ValueChange>Clear</xr:ValueChange>
|
||||
</xr:Link>
|
||||
</ChoiceParameterLinks>
|
||||
<ContextMenu name="КонтактноеЛицоКонтекстноеМеню" id="5"/>
|
||||
<ExtendedTooltip name="КонтактноеЛицоРасширеннаяПодсказка" id="6"/>
|
||||
</InputField>
|
||||
</ChildItems>
|
||||
<Attributes/>
|
||||
<BaseForm version="2.20">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>false</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems>
|
||||
<InputField name="КонтактноеЛицо" id="4">
|
||||
<ChoiceParameterLinks>
|
||||
<xr:Link>
|
||||
<xr:Name>Отбор.Владелец</xr:Name>
|
||||
<xr:DataPath xsi:type="xs:string">Объект.Партнер</xr:DataPath>
|
||||
<xr:ValueChange>Clear</xr:ValueChange>
|
||||
</xr:Link>
|
||||
</ChoiceParameterLinks>
|
||||
<ContextMenu name="КонтактноеЛицоКонтекстноеМеню" id="5"/>
|
||||
<ExtendedTooltip name="КонтактноеЛицоРасширеннаяПодсказка" id="6"/>
|
||||
</InputField>
|
||||
</ChildItems>
|
||||
<Attributes/>
|
||||
</BaseForm>
|
||||
</Form>
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?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:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>false</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems>
|
||||
<InputField name="КонтактноеЛицо" id="4">
|
||||
<ChoiceParameterLinks>
|
||||
<xr:Link>
|
||||
<xr:Name>Отбор.Владелец</xr:Name>
|
||||
<xr:DataPath xsi:type="xs:string">1/0:8d121ce6-6d64-46b8-994d-69e60ed91a43</xr:DataPath>
|
||||
<xr:ValueChange>Clear</xr:ValueChange>
|
||||
</xr:Link>
|
||||
</ChoiceParameterLinks>
|
||||
<ContextMenu name="КонтактноеЛицоКонтекстноеМеню" id="5"/>
|
||||
<ExtendedTooltip name="КонтактноеЛицоРасширеннаяПодсказка" id="6"/>
|
||||
</InputField>
|
||||
</ChildItems>
|
||||
<Attributes/>
|
||||
<BaseForm version="2.20">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>false</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems>
|
||||
<InputField name="КонтактноеЛицо" id="4">
|
||||
<ChoiceParameterLinks>
|
||||
<xr:Link>
|
||||
<xr:Name>Отбор.Владелец</xr:Name>
|
||||
<xr:DataPath xsi:type="xs:string">1/0:8d121ce6-6d64-46b8-994d-69e60ed91a43</xr:DataPath>
|
||||
<xr:ValueChange>Clear</xr:ValueChange>
|
||||
</xr:Link>
|
||||
</ChoiceParameterLinks>
|
||||
<ContextMenu name="КонтактноеЛицоКонтекстноеМеню" id="5"/>
|
||||
<ExtendedTooltip name="КонтактноеЛицоРасширеннаяПодсказка" id="6"/>
|
||||
</InputField>
|
||||
</ChildItems>
|
||||
<Attributes/>
|
||||
</BaseForm>
|
||||
</Form>
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?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:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>false</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems>
|
||||
<InputField name="КонтактноеЛицо" id="4">
|
||||
<ChoiceParameterLinks>
|
||||
<xr:Link>
|
||||
<xr:Name>Отбор.Владелец</xr:Name>
|
||||
<xr:DataPath xsi:type="xs:string">1/0:UUID-001</xr:DataPath>
|
||||
<xr:ValueChange>Clear</xr:ValueChange>
|
||||
</xr:Link>
|
||||
</ChoiceParameterLinks>
|
||||
<ContextMenu name="КонтактноеЛицоКонтекстноеМеню" id="5"/>
|
||||
<ExtendedTooltip name="КонтактноеЛицоРасширеннаяПодсказка" id="6"/>
|
||||
</InputField>
|
||||
</ChildItems>
|
||||
<Attributes/>
|
||||
<BaseForm version="2.20">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>false</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems>
|
||||
<InputField name="КонтактноеЛицо" id="4">
|
||||
<ChoiceParameterLinks>
|
||||
<xr:Link>
|
||||
<xr:Name>Отбор.Владелец</xr:Name>
|
||||
<xr:DataPath xsi:type="xs:string">1/0:UUID-001</xr:DataPath>
|
||||
<xr:ValueChange>Clear</xr:ValueChange>
|
||||
</xr:Link>
|
||||
</ChoiceParameterLinks>
|
||||
<ContextMenu name="КонтактноеЛицоКонтекстноеМеню" id="5"/>
|
||||
<ExtendedTooltip name="КонтактноеЛицоРасширеннаяПодсказка" id="6"/>
|
||||
</InputField>
|
||||
</ChildItems>
|
||||
<Attributes/>
|
||||
</BaseForm>
|
||||
</Form>
|
||||
Reference in New Issue
Block a user