fix(plan): a list joiner no longer ends the read of a plain path below 1.9

Review finding 182ceb93.

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

View file

@ -353,7 +353,8 @@ export function onFailureAction(text) {
* in src/parse.mjs") counts only where a list starts (after "and", "or", "+" * in src/parse.mjs") counts only where a list starts (after "and", "or", "+"
* or "&"). Right after the backticked path ("`a.test.mjs` src/b.mjs"), and in * or "&"). Right after the backticked path ("`a.test.mjs` src/b.mjs"), and in
* an entry not opened by one (", the CLI entry src/cli.mjs", round 7), it is * an entry not opened by one (", the CLI entry src/cli.mjs", round 7), it is
* read as before. * read as before. A list joiner is no word of description: "`a.mjs` +
* `b.mjs` src/c.mjs" reads all three.
* - 6.4 / regression C: a new line that is no list item and holds only a * - 6.4 / regression C: a new line that is no list item and holds only a
* backticked capitalised name ("`LICENSE`") ends the description, and the * backticked capitalised name ("`LICENSE`") ends the description, and the
* name is a file. On the description's own line it stays no file (round 10). * name is a file. On the description's own line it stays no file (round 10).
@ -449,7 +450,7 @@ export function fieldPaths(text) {
if (DESCRIPTION_DASH.test(t)) described = true; if (DESCRIPTION_DASH.test(t)) described = true;
const w = plainWord(t); const w = plainWord(t);
const file = isFileWord(w); const file = isFileWord(w);
if (!file) prose = true; if (!file && !LIST_JOINER.test(t)) prose = true;
if (described || !file) continue; if (described || !file) continue;
if ((w.includes('/') && !(tickedEntry && prose)) || i === 0 || LIST_JOINER.test(tokens[i - 1])) out.push(w); if ((w.includes('/') && !(tickedEntry && prose)) || i === 0 || LIST_JOINER.test(tokens[i - 1])) out.push(w);
if (t.endsWith(':')) described = true; if (t.endsWith(':')) described = true;

View file

@ -10,7 +10,7 @@ import { test } from 'node:test';
import { strict as assert } from 'node:assert'; import { strict as assert } from 'node:assert';
import { buildPlan } from '../helpers/plan-v18-builder.mjs'; import { buildPlan } from '../helpers/plan-v18-builder.mjs';
import { validatePlanContent } from '../../lib/validators/plan-validator.mjs'; import { validatePlanContent } from '../../lib/validators/plan-validator.mjs';
import { stepTestsOnly } from '../../lib/parsers/plan-schema.mjs'; import { stepTestsOnly, untickedPaths } from '../../lib/parsers/plan-schema.mjs';
const step = (files) => `- **Files:** ${files}\n- **Changes:** x\n`; const step = (files) => `- **Files:** ${files}\n- **Changes:** x\n`;
@ -49,6 +49,14 @@ const CONTROLS = [
'`tests/a.test.mjs` — covers `parse`, `Parser`, `ParseError`', ['tests/a.test.mjs']], '`tests/a.test.mjs` — covers `parse`, `Parser`, `ParseError`', ['tests/a.test.mjs']],
['B\'s neighbour: a plain path right after the backticked one is read', ['B\'s neighbour: a plain path right after the backticked one is read',
'`tests/a.test.mjs` src/b.mjs', ['tests/a.test.mjs', 'src/b.mjs']], '`tests/a.test.mjs` src/b.mjs', ['tests/a.test.mjs', 'src/b.mjs']],
['B\'s neighbour: a plain path after backticked paths joined by "+" is read',
'`src/a.mjs` + `src/b.mjs` src/c.mjs', ['src/a.mjs', 'src/b.mjs', 'src/c.mjs']],
['B\'s neighbour: a plain path after backticked paths joined by "&" is read',
'`src/a.mjs` & `src/b.mjs` src/c.mjs', ['src/a.mjs', 'src/b.mjs', 'src/c.mjs']],
['B\'s neighbour: a plain path after backticked paths joined by "and" is read',
'`src/a.mjs` and `src/b.mjs` src/c.mjs', ['src/a.mjs', 'src/b.mjs', 'src/c.mjs']],
['B\'s neighbour: a plain source path after two joined test paths makes the step no tests-only step',
'`tests/a.test.mjs` + `tests/b.test.mjs` src/c.mjs', ['tests/a.test.mjs', 'tests/b.test.mjs', 'src/c.mjs']],
]; ];
for (const [name, files, paths] of CONTROLS) { for (const [name, files, paths] of CONTROLS) {
@ -59,3 +67,7 @@ for (const [name, files, paths] of CONTROLS) {
`PLAN_STEP_TESTS_ONLY ${testsOnly ? 'fires' : 'stays quiet'} on a 1.8 plan: ${files}`); `PLAN_STEP_TESTS_ONLY ${testsOnly ? 'fires' : 'stays quiet'} on a 1.8 plan: ${files}`);
}); });
} }
test('6.4 / control: untickedPaths names the plain path after backticked paths joined by "+"', () => {
assert.deepEqual(untickedPaths('`src/a.mjs` + `src/b.mjs` src/c.mjs'), ['src/c.mjs']);
});