mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-31 23:40:51 +03:00
fix(skills): пишущие навыки — запрет позиционного связывания параметров
Значение с пробелом без кавычек разрывалось на токены, и лишние молча растекались по свободным позиционным параметрам. На документированной форме вызова (powershell -NoProfile -File): form-compile.ps1 -JsonPath a.json -OutputPath b.xml -Purpose Форма списка → Purpose=[Форма] ObjectPath=[списка], ошибки НЕТ Навык отрабатывал успешно с усечённым значением; реальный триггер — путь с пробелом в -EmitDsl или -ObjectPath, результат уезжал не туда молча. У навыков с парой -DefinitionFile/-Operation осколок уезжал в -DefinitionFile, и навык жаловался на параметр, которого в команде не было. Расхождение портов: py на том же вызове отвечает «unrecognized arguments: списка» — там все аргументы объявлены опциями. Правка выравнивает PS по py, python не менялся. 41 навык получает [CmdletBinding(PositionalBinding=$false)] и ни одного позиционного параметра. Без Position=0 (в отличие от read-only навыков): «главный» параметр механически не выводится — у meta-edit первым объявлен -DefinitionFile, а путь к объекту вторым, — а ошибка выбора тестами не ловится, раннер передаёт только именованные флаги. Позиционной формы вызова нет ни в одной из 140 строк SKILL.md, внутренние вызовы навыков друг из друга тоже именованные. check-positional-binding.mjs расширен на пишущие навыки статической проверкой. Поведенческая (канареечный файл) остаётся только у read-only: у web-stop и db-run все параметры Mandatory=$false, связывание прошло бы и тело выполнилось — гард остановил бы Apache и запустил 1С. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
77ee8f1047
commit
0a0322faab
@@ -10,11 +10,20 @@
|
||||
// то есть любая перестановка параметров в param() открывала дыру заново.
|
||||
// Снапшот-тесты этого не видят: они сверяют вывод, а не связывание аргументов.
|
||||
//
|
||||
// Две проверки. Статическая: [CmdletBinding(PositionalBinding=$false)] объявлен, позиционный
|
||||
// параметр не более одного и это Position=0; в py-порте все add_argument именованные.
|
||||
// Две области. Read-only навыки: позиционным остаётся путь ко входу (Position=0), не более
|
||||
// одного. Пишущие навыки: позиционных параметров нет вовсе — «главный» параметр там не
|
||||
// выводится механически (у meta-edit первым объявлен -DefinitionFile, а путь к объекту вторым),
|
||||
// и ошибка выбора не ловится тестами: раннер передаёт только именованные флаги. Молчаливое
|
||||
// растекание там уже стреляло: `-Purpose Форма списка` у form-compile отдавало
|
||||
// Purpose=[Форма] ObjectPath=[списка] БЕЗ ошибки, тогда как py-порт тот же вызов отвергал.
|
||||
//
|
||||
// Статическая проверка: [CmdletBinding(PositionalBinding=$false)] объявлен, число Position
|
||||
// не выше нормы области; в py-порте все add_argument именованные.
|
||||
// Поведенческая: лишний позиционный аргумент, указывающий на канареечный файл, роняет вызов
|
||||
// и файл остаётся байт-в-байт. Валидные фикстуры не нужны — связывание параметров происходит
|
||||
// до тела скрипта. Выход 1 при нарушении. Запуск: node tests/skills/check-positional-binding.mjs [--runtime python]
|
||||
// до тела скрипта. Только для read-only навыков: у web-stop и db-run все параметры
|
||||
// Mandatory=$false, связывание пройдёт и ТЕЛО ВЫПОЛНИТСЯ — гард остановил бы Apache и запустил
|
||||
// 1С. Выход 1 при нарушении. Запуск: node tests/skills/check-positional-binding.mjs [--runtime python]
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { readFileSync, writeFileSync, readdirSync, existsSync, mkdtempSync, rmSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
@@ -55,6 +64,18 @@ if (skills.length === 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Пишущие навыки — всё остальное со скриптом. Список тоже по имени, чтобы новый навык
|
||||
// попадал под гард сам.
|
||||
const writeSkills = readdirSync(SKILLS_DIR)
|
||||
.filter(name => !isReadOnly(name))
|
||||
.filter(name => existsSync(join(SKILLS_DIR, name, 'scripts', `${name}.ps1`)))
|
||||
.sort();
|
||||
|
||||
if (writeSkills.length === 0) {
|
||||
console.error('Не найдено ни одного пишущего навыка со скриптом — гард потерял цель.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const violations = [];
|
||||
const fail = (skill, msg) => violations.push(`${skill}: ${msg}`);
|
||||
|
||||
@@ -67,7 +88,7 @@ function paramBlock(text) {
|
||||
return end < 0 ? null : text.slice(start, end);
|
||||
}
|
||||
|
||||
for (const skill of skills) {
|
||||
function checkStatic(skill, allowPositional) {
|
||||
const ps1Path = join(SKILLS_DIR, skill, 'scripts', `${skill}.ps1`);
|
||||
const ps1 = readFileSync(ps1Path, 'utf8');
|
||||
|
||||
@@ -80,9 +101,11 @@ for (const skill of skills) {
|
||||
fail(skill, '.ps1: не разобран блок param()');
|
||||
} else {
|
||||
const positions = [...block.matchAll(/Position\s*=\s*(\d+)/g)].map(m => m[1]);
|
||||
if (positions.length > 1) {
|
||||
if (!allowPositional && positions.length > 0) {
|
||||
fail(skill, `.ps1: у пишущего навыка не должно быть позиционных параметров (Position=${positions.join(', ')})`);
|
||||
} else if (allowPositional && positions.length > 1) {
|
||||
fail(skill, `.ps1: позиционных параметров больше одного (Position=${positions.join(', ')})`);
|
||||
} else if (positions.length === 1 && positions[0] !== '0') {
|
||||
} else if (allowPositional && positions.length === 1 && positions[0] !== '0') {
|
||||
fail(skill, `.ps1: единственный позиционный параметр должен быть Position=0, а не Position=${positions[0]}`);
|
||||
}
|
||||
}
|
||||
@@ -98,7 +121,10 @@ for (const skill of skills) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- 2. Поведенческая проверка: лишний позиционный аргумент не трогает файл ---
|
||||
for (const skill of skills) checkStatic(skill, true);
|
||||
for (const skill of writeSkills) checkStatic(skill, false);
|
||||
|
||||
// --- 2. Поведенческая проверка (только read-only, см. шапку): лишний позиционный аргумент не трогает файл ---
|
||||
|
||||
const CANARY = 'канарейка: этот файл не должен быть перезаписан отчётом навыка\n';
|
||||
const work = mkdtempSync(join(tmpdir(), 'posbind-'));
|
||||
@@ -148,5 +174,6 @@ if (violations.length) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`OK — ${skills.length} read-only навыков, рантаймы: ${runtimes.join(', ')}.`);
|
||||
console.log('Позиционным остаётся только путь ко входу; лишний позиционный аргумент падает и файл цел.');
|
||||
console.log(`OK — read-only: ${skills.length}, пишущих: ${writeSkills.length}, рантаймы: ${runtimes.join(', ')}.`);
|
||||
console.log('У read-only позиционным остаётся только путь ко входу (и лишний аргумент падает, файл цел);');
|
||||
console.log('у пишущих позиционных параметров нет вовсе.');
|
||||
|
||||
Reference in New Issue
Block a user