feat(plan): the Test first File line follows the 1.9 grammar [skip-docs]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-12 16:18:53 +02:00
commit fc157c3ad5
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
3 changed files with 132 additions and 9 deletions

View file

@ -42,7 +42,9 @@
// anything else, `none`, and an empty or absent `Files:` are
// PLAN_STEP_PATH_NOT_FIRST; a leading span that is no path (`parse()`, an
// option, a URL) is PLAN_STEP_PATH_NOT_A_PATH. Errors under strict,
// warnings under --soft (checkPathGrammar).
// warnings under --soft (checkPathGrammar). Each `Test first:` `File:`
// line (a line that opens with `File:`, backticked or not) is one entry
// under the same grammar and raises the same two codes.
// - below 1.9, a step whose `Files:` reader (plan-schema `fieldPaths`) finds a
// path outside backticks gets the warning PLAN_STEP_PATH_UNTICKED, under
// --strict too; the plan is read and gated as before.
@ -53,6 +55,7 @@ import {
parseStepFields, findSection, splitSections, findModuleCycle, parseMermaid, fencedBlocks,
versionAtLeast, PLAN_VERSION_CURRENT, PLAN_VERSIONS_SUPPORTED, CEREMONY_VALUES,
ON_FAILURE_ACTIONS, onFailureAction, stepTestsOnlyFromFields, entryPaths, untickedPaths,
testFirstFileEntries,
} from '../parsers/plan-schema.mjs';
import { validateAllManifests } from '../parsers/manifest-yaml.mjs';
import { parseDocument } from '../util/frontmatter.mjs';
@ -200,7 +203,8 @@ function checkRecovery(sections, opts, planVersion) {
/**
* plan_version 1.9: every `Files:` entry opens with its backticked path(s)
* (plan-schema `entryPaths`). An absent `Files:` is read as empty text, so it
* names no file.
* names no file. Each `Test first:` `File:` line (plan-schema
* `testFirstFileEntries`) is one entry under the same grammar.
*/
function checkPathGrammar(sections) {
const out = [];
@ -222,6 +226,23 @@ function checkPathGrammar(sections) {
'the description follows (`` `src/a.mjs` - the parser ``). A step that changes no file names none.'));
}
}
for (const entry of testFirstFileEntries(f)) {
for (const i of entryPaths(entry).issues) {
if (i.code === 'PLAN_STEP_PATH_NOT_A_PATH') {
out.push(issue('PLAN_STEP_PATH_NOT_A_PATH',
`Step ${s.n}: a \`Test first:\` \`File:\` line opens with a backticked span that is no path: ${entry}`,
'plan_version 1.9: the leading backticked spans of the `File:` line are the test files; a call ' +
'(`parse()`), an option or a URL is description and goes after the path.'));
} else {
out.push(issue('PLAN_STEP_PATH_NOT_FIRST',
entry === ''
? `Step ${s.n}: a \`Test first:\` \`File:\` line names no file`
: `Step ${s.n}: a \`Test first:\` \`File:\` line does not open with a backticked path: ${entry}`,
'plan_version 1.9: the `File:` line opens with its backticked test file(s), separated by commas or ' +
'spaces; the description follows (`` File: `tests/a.test.mjs` (new) ``).'));
}
}
}
}
return out;
}