fix(plan): below 1.9 the step's test files are the paths right after File:
Operator decision 20 (12.10); review finding 6c31212c. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
6b8b0e3f2a
commit
1320e3a4d5
2 changed files with 92 additions and 27 deletions
|
|
@ -499,6 +499,27 @@ export function isPathShape(s) {
|
|||
return tickedPath(span) || BUILD_FILE.test(span) || BARE_NAME.test(span);
|
||||
}
|
||||
|
||||
/**
|
||||
* The trimmed contents of the backticked spans `text` opens with, each
|
||||
* separated from the next only by commas or whitespace; empty when `text` does
|
||||
* not open with a backticked span. The caller judges each span.
|
||||
* @param {string} text
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function leadingSpans(text) {
|
||||
const out = [];
|
||||
let rest = String(text ?? '');
|
||||
let m = LEADING_SPAN.exec(rest);
|
||||
while (m) {
|
||||
out.push(m[1].trim());
|
||||
rest = rest.slice(m[0].length);
|
||||
const sep = /^[\s,]+/.exec(rest);
|
||||
m = sep ? LEADING_SPAN.exec(rest.slice(sep[0].length)) : null;
|
||||
if (m) rest = rest.slice(sep[0].length);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* The files a 1.9 `Files:` field names, by the grammar above.
|
||||
* @param {string} text - the field's text (parseStepFields(body).get('Files').text)
|
||||
|
|
@ -520,24 +541,18 @@ export function entryPaths(text) {
|
|||
return { files, issues };
|
||||
}
|
||||
for (const { line, entry } of entries) {
|
||||
let rest = entry;
|
||||
let m = LEADING_SPAN.exec(rest);
|
||||
if (!m) {
|
||||
const spans = leadingSpans(entry);
|
||||
if (spans.length === 0) {
|
||||
issues.push({ line, entry, code: 'PLAN_STEP_PATH_NOT_FIRST' });
|
||||
continue;
|
||||
}
|
||||
while (m) {
|
||||
const span = m[1].trim();
|
||||
for (const span of spans) {
|
||||
if (isPathShape(span)) {
|
||||
const p = norm(span);
|
||||
if (!files.includes(p)) files.push(p);
|
||||
} else {
|
||||
issues.push({ line, entry, code: 'PLAN_STEP_PATH_NOT_A_PATH' });
|
||||
}
|
||||
rest = rest.slice(m[0].length);
|
||||
const sep = /^[\s,]+/.exec(rest);
|
||||
m = sep ? LEADING_SPAN.exec(rest.slice(sep[0].length)) : null;
|
||||
if (m) rest = rest.slice(sep[0].length);
|
||||
}
|
||||
}
|
||||
return { files, issues };
|
||||
|
|
@ -580,8 +595,9 @@ export function stepTestsOnly(body, version) {
|
|||
return stepTestsOnlyFromFields(parseStepFields(body), version);
|
||||
}
|
||||
|
||||
// A line of the `Test first:` field that names its test files (6.3.1's selector).
|
||||
const TEST_FIRST_FILE_LINE = /\bFile:\**[ \t]*`/i;
|
||||
// A line of the `Test first:` field that names its test files (6.3.1's
|
||||
// selector); global, so read it with matchAll only: one match per `File:`.
|
||||
const TEST_FIRST_FILE_LINE = /\bFile:\**[ \t]*`/gi;
|
||||
// From plan_version 1.9: a line that opens with `File:` (after an optional
|
||||
// list marker and `**`), backticked or not.
|
||||
const TEST_FIRST_FILE_LINE_19 = /^\s*(?:[-*+]\s+)?(?:\*\*)?File:(?:\*\*)?/i;
|
||||
|
|
@ -607,9 +623,15 @@ export function testFirstFileEntries(fields) {
|
|||
* The step's test files: every file on the `Test first:` `File:` line.
|
||||
*
|
||||
* Below plan_version 1.9 (or without a version) a line counts as that line
|
||||
* exactly when TEST_FIRST_FILE_LINE matches it, and every backticked span on
|
||||
* the line, from the match on, that has the shape of a path is a test file (a
|
||||
* call `parse()`, an option, a URL is not).
|
||||
* exactly when TEST_FIRST_FILE_LINE matches it. Each `File:` marker on the
|
||||
* line opens a run: the backticked spans that stand right after it, separated
|
||||
* only by commas or whitespace (leadingSpans), and every span in that run with
|
||||
* the shape of a path is a test file (a call `parse()`, an option, a URL is
|
||||
* skipped and the run goes on). A path in the description after the run is no
|
||||
* test file (operator decision 20, 12.10; review `6c31212c`: "covers `parse()`
|
||||
* in `src/parser.mjs`" made the source a test and the step tests-only). The
|
||||
* markers are matched globally because 6.3.1 read the first span after each
|
||||
* `File:`, so a line with two markers keeps at least 6.3.1's files.
|
||||
*
|
||||
* From 1.9 a line counts when it opens with `File:` (testFirstFileEntries),
|
||||
* backticked or not, and its rest is one entry read by entryPaths: the
|
||||
|
|
@ -631,13 +653,12 @@ export function testFirstFiles(fields, version) {
|
|||
return out;
|
||||
}
|
||||
for (const line of String(fields.get('Test first').text ?? '').split('\n')) {
|
||||
const at = TEST_FIRST_FILE_LINE.exec(line);
|
||||
if (!at) continue;
|
||||
for (const m of line.slice(at.index).matchAll(/`([^`\n]+)`/g)) {
|
||||
const s = m[1].trim();
|
||||
if (!(tickedPath(s) || BUILD_FILE.test(s))) continue;
|
||||
const p = norm(s);
|
||||
if (!out.includes(p)) out.push(p);
|
||||
for (const at of line.matchAll(TEST_FIRST_FILE_LINE)) {
|
||||
for (const s of leadingSpans(line.slice(at.index + at[0].length - 1))) {
|
||||
if (!(tickedPath(s) || BUILD_FILE.test(s))) continue;
|
||||
const p = norm(s);
|
||||
if (!out.includes(p)) out.push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue