Skip to content

Commit e78cf0a

Browse files
committed
fix(@angular/build): preserve diagnostic locations at offset 0 and guard empty files
Ensure `convertTypeScriptDiagnosticInfo` checks `info.start !== undefined` so diagnostics and related information starting at file offset `0` retain their line, column, and lineText location properties. Also clamp `info.file.text.length - 1` to `0` when computing `lastLineOfFile` to prevent a TypeScript `Debug Failure` when reporting diagnostics on empty files. (cherry picked from commit cd70beb)
1 parent 176ed90 commit e78cf0a

2 files changed

Lines changed: 113 additions & 2 deletions

File tree

‎packages/angular/build/src/tools/angular/compilation/diagnostics.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function convertTypeScriptDiagnosticInfo(
3737
};
3838

3939
// Calculate the line/column location and extract the full line text that has the diagnostic
40-
if (info.start) {
40+
if (info.start !== undefined) {
4141
const { line, character } = typescript.getLineAndCharacterOfPosition(info.file, info.start);
4242
note.location.line = line + 1;
4343
note.location.column = character;
@@ -50,7 +50,7 @@ function convertTypeScriptDiagnosticInfo(
5050
// will error if a nonexistent line is passed).
5151
const { line: lastLineOfFile } = typescript.getLineAndCharacterOfPosition(
5252
info.file,
53-
info.file.text.length - 1,
53+
Math.max(0, info.file.text.length - 1),
5454
);
5555
const lineEndPosition =
5656
line < lastLineOfFile
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import ts from 'typescript';
10+
import { convertTypeScriptDiagnostic } from './diagnostics';
11+
12+
describe('convertTypeScriptDiagnostic', () => {
13+
it('populates line, column, and lineText when diagnostic or relatedInformation starts at offset 0', () => {
14+
const sourceFile = ts.createSourceFile(
15+
'/src/app/app.component.ts',
16+
'const invalid: number = "string";\nconst secondLine = 2;\n',
17+
ts.ScriptTarget.Latest,
18+
);
19+
const templateFile = ts.createSourceFile(
20+
'/src/app/app.component.html',
21+
'<unknown-element></unknown-element>\n<div>Valid</div>\n',
22+
ts.ScriptTarget.Latest,
23+
);
24+
25+
const diagnostic: ts.Diagnostic = {
26+
category: ts.DiagnosticCategory.Error,
27+
code: -998001,
28+
file: templateFile,
29+
start: 0,
30+
length: 17,
31+
messageText: "'unknown-element' is not a known element.",
32+
relatedInformation: [
33+
{
34+
category: ts.DiagnosticCategory.Message,
35+
code: 0,
36+
file: sourceFile,
37+
start: 0,
38+
length: 5,
39+
messageText: 'Error occurs in the template of component AppComponent.',
40+
},
41+
],
42+
};
43+
44+
const result = convertTypeScriptDiagnostic(ts, diagnostic);
45+
46+
expect(result.text).toBe("NG8001: 'unknown-element' is not a known element.");
47+
expect(result.location).toEqual({
48+
file: '/src/app/app.component.html',
49+
length: 17,
50+
line: 1,
51+
column: 0,
52+
lineText: '<unknown-element></unknown-element>',
53+
});
54+
55+
expect(result.notes).toEqual([
56+
{
57+
text: 'Error occurs in the template of component AppComponent.',
58+
location: {
59+
file: '/src/app/app.component.ts',
60+
length: 5,
61+
line: 1,
62+
column: 0,
63+
lineText: 'const invalid: number = "string";',
64+
},
65+
},
66+
]);
67+
});
68+
69+
it('does not throw a TypeScript Debug Failure when diagnostic is reported on an empty file', () => {
70+
const emptyFile = ts.createSourceFile('/src/empty.ts', '', ts.ScriptTarget.Latest);
71+
72+
const diagnosticAtZero: ts.Diagnostic = {
73+
category: ts.DiagnosticCategory.Error,
74+
code: 2306,
75+
file: emptyFile,
76+
start: 0,
77+
length: 0,
78+
messageText: "File '/src/empty.ts' is not a module.",
79+
};
80+
81+
expect(() => convertTypeScriptDiagnostic(ts, diagnosticAtZero)).not.toThrow();
82+
expect(convertTypeScriptDiagnostic(ts, diagnosticAtZero)).toEqual({
83+
text: "TS2306: File '/src/empty.ts' is not a module.",
84+
location: {
85+
file: '/src/empty.ts',
86+
length: 0,
87+
line: 1,
88+
column: 0,
89+
lineText: '',
90+
},
91+
});
92+
93+
const diagnosticPastEnd: ts.Diagnostic = {
94+
category: ts.DiagnosticCategory.Error,
95+
code: 1005,
96+
file: emptyFile,
97+
start: 1,
98+
length: 0,
99+
messageText: 'Unexpected end of text.',
100+
};
101+
102+
expect(() => convertTypeScriptDiagnostic(ts, diagnosticPastEnd)).not.toThrow();
103+
expect(convertTypeScriptDiagnostic(ts, diagnosticPastEnd).location).toEqual({
104+
file: '/src/empty.ts',
105+
length: 0,
106+
line: 1,
107+
column: 1,
108+
lineText: '',
109+
});
110+
});
111+
});

0 commit comments

Comments
 (0)