fix(form-validate): команда без Action — предупреждение, а не ошибка

Проверка объявляла ошибкой любую команду формы без <Action>. Корпусный
прогон (УТ 8.3.27, БП 8.3.27, ERP 8.3.24 — 21 097 форм) показал 406 таких
команд на 275 формах, и все они произведены самой платформой: формат это
допускает, конфигурации грузятся.

Приём типовых: действие назначается в рантайме, в ПриСозданииНаСервере —
`Команда.Действие = "Подключаемый_" + Имя + "Локализация"`. Назначать может
и чужой модуль (переопределяемый слой, подключаемые команды), поэтому по
одному Form.xml вердикт не вынести — отсюда предупреждение, а не ошибка, и
никакого подглядывания в соседний модуль.

Корпус после правки: форм с ошибками 283 → 8, новых срабатываний нет.
Оставшиеся 8 — четыре других класса, разбираются отдельно.

Кейс с фикстурой: команда без Action на кнопке → предупреждение, exit 0.
This commit is contained in:
Nick Shirokov
2026-08-12 17:24:34 +03:00
parent c9b64f3a0d
commit e1d5d1f903
5 changed files with 96 additions and 4 deletions
@@ -1,4 +1,4 @@
# form-validate v1.12 — Validate 1C managed form
# form-validate v1.13 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -569,13 +569,18 @@ if (-not $stopped) {
$actionErrors = 0
$actionChecked = 0
# Предупреждение, а не ошибка: <Action> может назначаться в рантайме
# (`Команда.Действие = "Подключаемый_…"` в ПриСозданииНаСервере) — приём типовых конфигураций
# там, где обработчик существует не во всякой сборке. Назначать может и чужой модуль
# (переопределяемый слой, подключаемые команды), так что по одному Form.xml не решить.
# Корпус УТ/БП/ERP: 406 таких команд на 275 формах, произведённых платформой.
foreach ($cmd in $cmdNodes) {
if ($stopped) { break }
$cmdName = $cmd.GetAttribute("name")
$actionNode = $cmd.SelectSingleNode("f:Action", $nsMgr)
$actionChecked++
if (-not $actionNode -or -not $actionNode.InnerText.Trim()) {
Report-Error "Command '$cmdName': missing or empty Action"
Report-Warn "Command '$cmdName': no Action — handler must be assigned at runtime, otherwise the command does nothing"
$actionErrors++
}
}
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-validate v1.12 — Validate 1C managed form
# form-validate v1.13 — Validate 1C managed form
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -570,6 +570,11 @@ def main():
action_errors = 0
action_checked = 0
# Предупреждение, а не ошибка: <Action> может назначаться в рантайме
# (`Команда.Действие = "Подключаемый_…"` в ПриСозданииНаСервере) — приём типовых конфигураций
# там, где обработчик существует не во всякой сборке. Назначать может и чужой модуль
# (переопределяемый слой, подключаемые команды), так что по одному Form.xml не решить.
# Корпус УТ/БП/ERP: 406 таких команд на 275 формах, произведённых платформой.
for cmd in cmd_nodes:
if stopped:
break
@@ -577,7 +582,7 @@ def main():
action_node = cmd.find(f"{{{F_NS}}}Action")
action_checked += 1
if action_node is None or not (action_node.text or "").strip():
report_error(f"Command '{cmd_name}': missing or empty Action")
report_warn(f"Command '{cmd_name}': no Action — handler must be assigned at runtime, otherwise the command does nothing")
action_errors += 1
if action_errors == 0 and action_checked > 0:
@@ -0,0 +1,6 @@
{
"name": "Команда без Action — предупреждение, а не ошибка (действие назначается в рантайме)",
"setup": "fixture:command-without-action",
"params": { "formPath": "DataProcessors/БезДействия/Forms/Форма" },
"expect": { "stdoutContains": "no Action" }
}
@@ -0,0 +1,38 @@
<?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.20">
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
<Autofill>true</Autofill>
<ChildItems>
<Button name="КнопкаЛокализация" id="5">
<CommandName>Form.Command.ЛокализованнаяКоманда</CommandName>
<ExtendedTooltip name="КнопкаЛокализацияРасширеннаяПодсказка" id="6"/>
</Button>
</ChildItems>
</AutoCommandBar>
<ChildItems>
<InputField name="Поле" id="2">
<DataPath>Объект.Наименование</DataPath>
<ContextMenu name="ПолеКонтекстноеМеню" id="3"/>
<ExtendedTooltip name="ПолеРасширеннаяПодсказка" id="4"/>
</InputField>
</ChildItems>
<Attributes>
<Attribute name="Объект" id="1">
<Type>
<v8:Type>cfg:DataProcessorObject.БезДействия</v8:Type>
</Type>
<MainAttribute>true</MainAttribute>
<SavedData>true</SavedData>
</Attribute>
</Attributes>
<Commands>
<Command name="ЛокализованнаяКоманда" id="1">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Локализованная команда</v8:content>
</v8:item>
</Title>
</Command>
</Commands>
</Form>
@@ -0,0 +1,38 @@
<?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.20">
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
<Autofill>true</Autofill>
<ChildItems>
<Button name="КнопкаЛокализация" id="5">
<CommandName>Form.Command.ЛокализованнаяКоманда</CommandName>
<ExtendedTooltip name="КнопкаЛокализацияРасширеннаяПодсказка" id="6"/>
</Button>
</ChildItems>
</AutoCommandBar>
<ChildItems>
<InputField name="Поле" id="2">
<DataPath>Объект.Наименование</DataPath>
<ContextMenu name="ПолеКонтекстноеМеню" id="3"/>
<ExtendedTooltip name="ПолеРасширеннаяПодсказка" id="4"/>
</InputField>
</ChildItems>
<Attributes>
<Attribute name="Объект" id="1">
<Type>
<v8:Type>cfg:DataProcessorObject.БезДействия</v8:Type>
</Type>
<MainAttribute>true</MainAttribute>
<SavedData>true</SavedData>
</Attribute>
</Attributes>
<Commands>
<Command name="ЛокализованнаяКоманда" id="1">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Локализованная команда</v8:content>
</v8:item>
</Title>
</Command>
</Commands>
</Form>