refactor(plan): the plan validator parses each step once
Review finding 93382464. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1621352e67
commit
045191f51d
2 changed files with 125 additions and 7 deletions
|
|
@ -70,6 +70,12 @@ export const DESIGN_SUBSECTIONS = Object.freeze([
|
|||
]);
|
||||
const BACKTICKED = /`[^`\n]+`/;
|
||||
|
||||
// Each step's fields are parsed once, here; every check reads `s.fields`
|
||||
// (review finding 93382464).
|
||||
function withFields(sections) {
|
||||
return sections.map((s) => ({ ...s, fields: parseStepFields(s.body) }));
|
||||
}
|
||||
|
||||
// From 1.9 the step names its test the way the executor reads it
|
||||
// (testFirstFiles): only through a line that opens with `File:` and its
|
||||
// backticked path, so a `File:` mid-line names no test. Below 1.9 the
|
||||
|
|
@ -79,7 +85,7 @@ function checkSteps(sections, planVersion) {
|
|||
let endToEnd = 0;
|
||||
const grammar = usesFilesGrammar(planVersion);
|
||||
for (const s of sections) {
|
||||
const f = parseStepFields(s.body);
|
||||
const f = s.fields;
|
||||
const tf = f.get('Test first');
|
||||
const file = grammar
|
||||
? testFirstFiles(f, planVersion).length > 0
|
||||
|
|
@ -138,7 +144,7 @@ function checkRunnable(text, sections, opts) {
|
|||
}
|
||||
const verified = new Set(criteria.filter(c => !refusal(c)).map(c => c.command.trim().replace(/\s+/g, ' ')));
|
||||
for (const s of sections) {
|
||||
const f = parseStepFields(s.body);
|
||||
const f = s.fields;
|
||||
if (f.has('Verify')) {
|
||||
const v = firstCommand(f.get('Verify').text);
|
||||
const r = runnableCheck(v, opts);
|
||||
|
|
@ -177,7 +183,7 @@ function checkRecovery(sections, opts, planVersion) {
|
|||
const errors = [];
|
||||
const warnings = [];
|
||||
for (const s of sections) {
|
||||
const f = parseStepFields(s.body);
|
||||
const f = s.fields;
|
||||
if (f.has('Regression')) {
|
||||
const r = runnableCheck(firstCommand(f.get('Regression').text), opts);
|
||||
if (!r.runnable) {
|
||||
|
|
@ -244,7 +250,7 @@ function pathGrammarIssue(n, code, shown, w) {
|
|||
function checkPathGrammar(sections) {
|
||||
const out = [];
|
||||
for (const s of sections) {
|
||||
const f = parseStepFields(s.body);
|
||||
const f = s.fields;
|
||||
for (const i of entryPaths(f.has('Files') ? f.get('Files').text : '').issues) {
|
||||
out.push(pathGrammarIssue(s.n, i.code, i.entry, FILES_WORDING));
|
||||
}
|
||||
|
|
@ -341,7 +347,7 @@ export function validatePlanContent(rawText, opts = {}) {
|
|||
const warnings = [...headRes.warnings];
|
||||
|
||||
const steps = headRes.parsed?.steps || [];
|
||||
const sections = sliceSteps(text);
|
||||
const sections = withFields(sliceSteps(text));
|
||||
const manRes = validateAllManifests(sections);
|
||||
errors.push(...manRes.errors);
|
||||
warnings.push(...manRes.warnings);
|
||||
|
|
@ -372,7 +378,7 @@ export function validatePlanContent(rawText, opts = {}) {
|
|||
else warnings.push(...grammar);
|
||||
} else {
|
||||
for (const s of sections) {
|
||||
const f = parseStepFields(s.body);
|
||||
const f = s.fields;
|
||||
const unticked = f.has('Files') ? [...new Set(untickedPaths(f.get('Files').text))] : [];
|
||||
if (unticked.length > 0) {
|
||||
warnings.push(issue('PLAN_STEP_PATH_UNTICKED',
|
||||
|
|
@ -398,7 +404,7 @@ export function validatePlanContent(rawText, opts = {}) {
|
|||
...checkRunnable(text, appended, { cwd: opts.cwd, stepsOnly: true }),
|
||||
];
|
||||
for (const s of appended) {
|
||||
if (!parseStepFields(s.body).has('Verify')) {
|
||||
if (!s.fields.has('Verify')) {
|
||||
stepIssues.push(issue('PLAN_STEP_VERIFY_NOT_RUNNABLE', `Step ${s.n}: no \`Verify:\` field`,
|
||||
'An appended step needs one test command: proof of red and the done line run it.'));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue