Skip to content

Commit 69f87ca

Browse files
chuckjazvicb
authored andcommitted
fix(tools): harden colletor against invalid asts (angular#12793)
1 parent f224ca1 commit 69f87ca

File tree

3 files changed

+61
-16
lines changed

3 files changed

+61
-16
lines changed

tools/@angular/tsc-wrapped/src/collector.ts

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -205,22 +205,27 @@ export class MetadataCollector {
205205
switch (node.kind) {
206206
case ts.SyntaxKind.ClassDeclaration:
207207
const classDeclaration = <ts.ClassDeclaration>node;
208-
const className = classDeclaration.name.text;
209-
if (node.flags & ts.NodeFlags.Export) {
210-
locals.define(className, {__symbolic: 'reference', name: className});
211-
} else {
212-
locals.define(
213-
className, errorSym('Reference to non-exported class', node, {className}));
208+
if (classDeclaration.name) {
209+
const className = classDeclaration.name.text;
210+
if (node.flags & ts.NodeFlags.Export) {
211+
locals.define(className, {__symbolic: 'reference', name: className});
212+
} else {
213+
locals.define(
214+
className, errorSym('Reference to non-exported class', node, {className}));
215+
}
214216
}
215217
break;
216218
case ts.SyntaxKind.FunctionDeclaration:
217219
if (!(node.flags & ts.NodeFlags.Export)) {
218220
// Report references to this function as an error.
219221
const functionDeclaration = <ts.FunctionDeclaration>node;
220222
const nameNode = functionDeclaration.name;
221-
locals.define(
222-
nameNode.text,
223-
errorSym('Reference to a non-exported function', nameNode, {name: nameNode.text}));
223+
if (nameNode && nameNode.text) {
224+
locals.define(
225+
nameNode.text,
226+
errorSym(
227+
'Reference to a non-exported function', nameNode, {name: nameNode.text}));
228+
}
224229
}
225230
break;
226231
}
@@ -248,11 +253,13 @@ export class MetadataCollector {
248253
break;
249254
case ts.SyntaxKind.ClassDeclaration:
250255
const classDeclaration = <ts.ClassDeclaration>node;
251-
const className = classDeclaration.name.text;
252-
if (node.flags & ts.NodeFlags.Export) {
253-
if (classDeclaration.decorators) {
254-
if (!metadata) metadata = {};
255-
metadata[className] = classMetadataOf(classDeclaration);
256+
if (classDeclaration.name) {
257+
const className = classDeclaration.name.text;
258+
if (node.flags & ts.NodeFlags.Export) {
259+
if (classDeclaration.decorators) {
260+
if (!metadata) metadata = {};
261+
metadata[className] = classMetadataOf(classDeclaration);
262+
}
256263
}
257264
}
258265
// Otherwise don't record metadata for the class.

tools/@angular/tsc-wrapped/test/collector.spec.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {Directory, Host, expectValidSources} from './typescript.mocks';
1515

1616
describe('Collector', () => {
1717
let documentRegistry = ts.createDocumentRegistry();
18-
let host: ts.LanguageServiceHost;
18+
let host: Host;
1919
let service: ts.LanguageService;
2020
let program: ts.Program;
2121
let collector: MetadataCollector;
@@ -589,6 +589,28 @@ describe('Collector', () => {
589589
.toThrowError(/Reference to non-exported class/);
590590
});
591591
});
592+
593+
describe('with invalid input', () => {
594+
it('should not throw with a class with no name', () => {
595+
const fileName = '/invalid-class.ts';
596+
override(fileName, 'export class');
597+
let invalidClass = program.getSourceFile(fileName);
598+
expect(() => collector.getMetadata(invalidClass)).not.toThrow();
599+
});
600+
601+
it('should not throw with a function with no name', () => {
602+
const fileName = '/invalid-function.ts';
603+
override(fileName, 'export function');
604+
let invalidFunction = program.getSourceFile(fileName);
605+
expect(() => collector.getMetadata(invalidFunction)).not.toThrow();
606+
});
607+
});
608+
609+
function override(fileName: string, content: string) {
610+
host.overrideFile(fileName, content);
611+
host.addFile(fileName);
612+
program = service.getProgram();
613+
}
592614
});
593615

594616
// TODO: Do not use \` in a template literal as it confuses clang-format

tools/@angular/tsc-wrapped/test/typescript.mocks.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import * as ts from 'typescript';
55
export interface Directory { [name: string]: (Directory|string); }
66

77
export class Host implements ts.LanguageServiceHost {
8+
private overrides = new Map<string, string>();
9+
private version = 1;
10+
811
constructor(private directory: Directory, private scripts: string[]) {}
912

1013
getCompilationSettings(): ts.CompilerOptions {
@@ -17,7 +20,7 @@ export class Host implements ts.LanguageServiceHost {
1720

1821
getScriptFileNames(): string[] { return this.scripts; }
1922

20-
getScriptVersion(fileName: string): string { return '1'; }
23+
getScriptVersion(fileName: string): string { return this.version.toString(); }
2124

2225
getScriptSnapshot(fileName: string): ts.IScriptSnapshot {
2326
let content = this.getFileContent(fileName);
@@ -28,7 +31,20 @@ export class Host implements ts.LanguageServiceHost {
2831

2932
getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; }
3033

34+
overrideFile(fileName: string, content: string) {
35+
this.overrides.set(fileName, content);
36+
this.version++;
37+
}
38+
39+
addFile(fileName: string) {
40+
this.scripts.push(fileName);
41+
this.version++;
42+
}
43+
3144
private getFileContent(fileName: string): string {
45+
if (this.overrides.has(fileName)) {
46+
return this.overrides.get(fileName);
47+
}
3248
const names = fileName.split('/');
3349
if (names[names.length - 1] === 'lib.d.ts') {
3450
return fs.readFileSync(ts.getDefaultLibFilePath(this.getCompilationSettings()), 'utf8');

0 commit comments

Comments
 (0)