Skip to content

Commit f7678ac

Browse files
committed
fix(lint): report physical JSONL line numbers (blank lines no longer shift them)
1 parent 9ef7c36 commit f7678ac

2 files changed

Lines changed: 19 additions & 12 deletions

File tree

src/commands/test.test.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3014,10 +3014,12 @@ describe('runLint', () => {
30143014
expect(report).toMatchObject({ checked: 1, valid: 1, issues: [] });
30153015
});
30163016

3017-
it('a JSONL file reports each bad line with its line number', async () => {
3017+
it('a JSONL file reports each bad line with its PHYSICAL line number (blank lines skipped, not renumbered)', async () => {
30183018
const dir = mkdtempSync(join(tmpdir(), 'cli-lint-jsonl-'));
30193019
const file = join(dir, 'plans.jsonl');
3020-
writeFileSync(file, `${VALID_PLAN}\nnot json at all\n${INVALID_PLAN}\n`, 'utf8');
3020+
// A blank separator line sits between entries: line 1 valid, line 2 blank,
3021+
// line 3 bad JSON, line 4 invalid plan. Reported numbers must be 3 and 4.
3022+
writeFileSync(file, `${VALID_PLAN}\n\nnot json at all\n${INVALID_PLAN}\n`, 'utf8');
30213023
const out: string[] = [];
30223024
const rejection = await runLint(
30233025
{ profile: 'default', output: 'json', debug: false, plans: file },
@@ -3026,8 +3028,10 @@ describe('runLint', () => {
30263028
expect(rejection).toMatchObject({ exitCode: 5 });
30273029
const report = JSON.parse(out.join('')) as { checked: number; issues: Array<{ file: string }> };
30283030
expect(report.checked).toBe(3);
3029-
expect(report.issues.some(issue => issue.file.endsWith(':2'))).toBe(true);
30303031
expect(report.issues.some(issue => issue.file.endsWith(':3'))).toBe(true);
3032+
expect(report.issues.some(issue => issue.file.endsWith(':4'))).toBe(true);
3033+
// The blank line itself is not an entry and never reports.
3034+
expect(report.issues.some(issue => issue.file.endsWith(':2'))).toBe(false);
30313035
});
30323036

30333037
it('requires exactly one input source', async () => {

src/commands/test.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3952,27 +3952,30 @@ export async function runLint(opts: LintOptions, deps: TestDeps = {}): Promise<C
39523952
} catch {
39533953
throw localValidationError('plans', `cannot read file: ${absolute}`);
39543954
}
3955-
const lines = content
3955+
// Index lines BEFORE dropping blanks so every reported `file:N` points at
3956+
// the PHYSICAL line in the file (a blank separator line must not shift all
3957+
// subsequent line numbers).
3958+
const numberedLines = content
39563959
.split('\n')
3957-
.map(line => line.trim())
3958-
.filter(line => line.length > 0);
3959-
if (lines.length === 0) throw localValidationError('plans', 'contains no plan lines');
3960-
lines.forEach((line, index) => {
3961-
collect(`${opts.plans}:${index + 1}`, () => {
3960+
.map((rawLine, physicalIndex) => ({ line: rawLine.trim(), lineNo: physicalIndex + 1 }))
3961+
.filter(entry => entry.line.length > 0);
3962+
if (numberedLines.length === 0) throw localValidationError('plans', 'contains no plan lines');
3963+
for (const { line, lineNo } of numberedLines) {
3964+
collect(`${opts.plans}:${lineNo}`, () => {
39623965
let parsed: unknown;
39633966
try {
39643967
parsed = JSON.parse(line);
39653968
} catch {
39663969
throw localValidationError(
39673970
'plans',
3968-
`line ${index + 1} is not valid JSON`,
3971+
`line ${lineNo} is not valid JSON`,
39693972
undefined,
39703973
'field',
39713974
);
39723975
}
3973-
assertPlanShape(parsed, { specIndex: index });
3976+
assertPlanShape(parsed, { specIndex: lineNo - 1 });
39743977
});
3975-
});
3978+
}
39763979
}
39773980

39783981
const filesWithIssues = new Set(issues.map(issue => issue.file)).size;

0 commit comments

Comments
 (0)