fix(plan): a 1.9 wrapped line keeps its paths only after a comma or in an empty entry
Operator decision 29 (14.10); review findings d21ff81e and eaeb398e. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1786df999e
commit
b70f242e53
2 changed files with 115 additions and 21 deletions
|
|
@ -477,12 +477,15 @@ export function fieldPaths(text) {
|
|||
//
|
||||
// - The entries are the field's first non-empty line and each list-item line
|
||||
// (`-`, `*`, `+`, `1.`, `1)`). Any other non-empty line continues the entry
|
||||
// above it and is appended to that entry's text with one space before the
|
||||
// entry is read, so a list wrapped after a comma ("`tests/a.test.mjs`," and
|
||||
// "`src/a.mjs`" on the next line) names both files, a wrapped description
|
||||
// line stays description, and an empty first line before a nested list is
|
||||
// no entry. An issue carries the line number of the entry's first line
|
||||
// (review `b4fa1927`).
|
||||
// above it: when the entry so far ends with a comma it is appended with one
|
||||
// space before the entry is read, so a list wrapped after a comma
|
||||
// ("`tests/a.test.mjs`," and "`src/a.mjs`" on the next line) names both
|
||||
// files; otherwise it and every later wrapped line of that entry are
|
||||
// description and never read (operator decision 29, 14.10; reviews
|
||||
// `d21ff81e`, `eaeb398e`). An entry with nothing on its own line (a `File:`
|
||||
// line with nothing after it) takes its first wrapped line. An empty first
|
||||
// line before a nested list is no entry. An issue carries the line number
|
||||
// of the entry's first line (review `b4fa1927`).
|
||||
// - An entry's files are the backticked spans it opens with, separated only by
|
||||
// commas or whitespace. Everything from the first other text on is
|
||||
// description and is never read ("`a.test.mjs` - covers `src/parse.mjs`"
|
||||
|
|
@ -494,9 +497,14 @@ export function fieldPaths(text) {
|
|||
//
|
||||
// - A `Test first:` `File:` line is a line that opens with `File:` (or
|
||||
// `**File:**`) after an optional list marker of the same set (`-`, `*`, `+`,
|
||||
// `1.`, `1)`); its rest is one entry under this grammar. The non-list lines
|
||||
// right after it are appended to it with one space, as above; any list item
|
||||
// (a `Verifies:` or `Pattern:` line too) ends it, and a non-list line after
|
||||
// `1.`, `1)`); its rest is one entry under this grammar. Any other
|
||||
// non-empty line right after it continues it by the same rule: when the
|
||||
// entry so far ends with a comma it is appended with one space, so a list
|
||||
// wrapped after a comma names both files; otherwise it and every later
|
||||
// wrapped line of that entry are description and never read (operator
|
||||
// decision 29, 14.10; reviews `d21ff81e`, `eaeb398e`). A `File:` line with
|
||||
// nothing after it takes its first wrapped line. Any list item (a
|
||||
// `Verifies:` or `Pattern:` line too) ends it, and a non-list line after
|
||||
// such an item belongs to that item, never to the `File:` entry. A line that
|
||||
// opens with `File:` always starts a new entry; a `File:` mid-line names no
|
||||
// test (review `4b6460e5`).
|
||||
|
|
@ -540,6 +548,26 @@ function leadingSpans(text) {
|
|||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a wrapped line (a non-empty line that is no list item) joins the
|
||||
* 1.9 entry above it (operator decision 29, 14.10; reviews `d21ff81e`,
|
||||
* `eaeb398e`). An entry with nothing on its own line (a `File:` line with
|
||||
* nothing after it) takes its first wrapped line; an entry whose text so far
|
||||
* ends with a comma gets the line appended with one space, so a list wrapped
|
||||
* after a comma names both files. Otherwise the line is description: null,
|
||||
* and the caller closes the entry, so every later wrapped line of it is
|
||||
* description too.
|
||||
* @param {string} text - the entry so far
|
||||
* @param {string} line - the wrapped line
|
||||
* @returns {string|null} the entry's new text, or null when the line is description
|
||||
*/
|
||||
function wrapLine(text, line) {
|
||||
const so = String(text ?? '');
|
||||
if (so.trim() === '') return line.trim();
|
||||
if (so.trimEnd().endsWith(',')) return `${so} ${line.trim()}`.trim();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The files a 1.9 `Files:` field names, by the grammar above.
|
||||
* @param {string} text - the field's text (parseStepFields(body).get('Files').text)
|
||||
|
|
@ -554,13 +582,19 @@ export function entryPaths(text) {
|
|||
String(text ?? '').split('\n').forEach((line, i) => {
|
||||
const item = LIST_ITEM.exec(line);
|
||||
const rest = line.trim();
|
||||
if (item) entries.push({ line: i + 1, entry: line.slice(item[0].length).trim() });
|
||||
if (item) entries.push({ line: i + 1, entry: line.slice(item[0].length).trim(), closed: false });
|
||||
else if (rest === '') return;
|
||||
else if (entries.length === 0) entries.push({ line: i + 1, entry: rest });
|
||||
else if (entries.length === 0) entries.push({ line: i + 1, entry: rest, closed: false });
|
||||
else {
|
||||
// A wrapped continuation line belongs to the entry above it.
|
||||
// A wrapped line joins the entry above it only after a comma, or when
|
||||
// the entry is empty (wrapLine); otherwise it closes the entry, and it
|
||||
// and every later wrapped line of that entry are description, never read
|
||||
// (operator decision 29).
|
||||
const last = entries[entries.length - 1];
|
||||
last.entry = `${last.entry} ${rest}`.trim();
|
||||
if (last.closed) return;
|
||||
const joined = wrapLine(last.entry, rest);
|
||||
if (joined === null) last.closed = true;
|
||||
else last.entry = joined;
|
||||
}
|
||||
});
|
||||
if (entries.length === 0) {
|
||||
|
|
@ -631,10 +665,15 @@ const TEST_FIRST_FILE_LINE_19 = /^\s*(?:(?:[-*+]|\d+[.)])\s+)?(?:\*\*)?File:(?:\
|
|||
|
||||
/**
|
||||
* plan_version 1.9: the rest of each `Test first:` `File:` line (after `File:`
|
||||
* and any `**`), with the non-list lines right after it appended (one space
|
||||
* each), one entry per `File:` line, in order. Any list item ends the entry;
|
||||
* a line that opens with `File:` starts a new one. The one selector both
|
||||
* testFirstFiles and the plan validator's grammar check read.
|
||||
* and any `**`), one entry per `File:` line, in order. Any other non-empty
|
||||
* line right after it continues the entry above it: when the entry so far
|
||||
* ends with a comma it is appended with one space, so a list wrapped after a
|
||||
* comma names both files; otherwise it and every later wrapped line of that
|
||||
* entry are description and never read (operator decision 29, 14.10; reviews
|
||||
* `d21ff81e`, `eaeb398e`). An entry with nothing on its own line (a `File:`
|
||||
* line with nothing after it) takes its first wrapped line. Any list item ends
|
||||
* the entry; a line that opens with `File:` starts a new one. The one
|
||||
* selector both testFirstFiles and the plan validator's grammar check read.
|
||||
* @param {Map<string, {text: string}>} fields - parseStepFields(body)
|
||||
* @returns {string[]}
|
||||
*/
|
||||
|
|
@ -651,7 +690,9 @@ export function testFirstFileEntries(fields) {
|
|||
} else if (LIST_ITEM.test(line) || line.trim() === '') {
|
||||
open = false;
|
||||
} else if (open) {
|
||||
out[out.length - 1] = `${out[out.length - 1]} ${line.trim()}`.trim();
|
||||
const joined = wrapLine(out[out.length - 1], line);
|
||||
if (joined === null) open = false;
|
||||
else out[out.length - 1] = joined;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
|
|
@ -673,9 +714,13 @@ export function testFirstFileEntries(fields) {
|
|||
*
|
||||
* From 1.9 a line counts when it opens with `File:` (testFirstFileEntries),
|
||||
* backticked or not, after an optional list marker (`1.` and `1)` included);
|
||||
* its rest, with its wrapped continuation lines, is one entry read by
|
||||
* entryPaths: the leading backticked paths are the test files, the rest is
|
||||
* description.
|
||||
* its rest is one entry read by entryPaths: the leading backticked paths are
|
||||
* the test files, the rest is description. Any other non-empty line right
|
||||
* after it continues the entry: when the entry so far ends with a comma it is
|
||||
* appended with one space, so a list wrapped after a comma names both files;
|
||||
* otherwise it and every later wrapped line of that entry are description and
|
||||
* never read (operator decision 29, 14.10; reviews `d21ff81e`, `eaeb398e`). A
|
||||
* `File:` line with nothing after it takes its first wrapped line.
|
||||
*
|
||||
* Normalised, in order, deduplicated. A `Verifies:` line without a `File:` is
|
||||
* never read.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue