fix(tools): harden colletor against invalid asts (#12793)

This commit is contained in:
Chuck Jazdzewski 2016-11-10 11:58:55 -08:00 committed by Victor Berchet
parent f224ca1461
commit 69f87ca075
3 changed files with 61 additions and 16 deletions

View File

@ -205,6 +205,7 @@ export class MetadataCollector {
switch (node.kind) { switch (node.kind) {
case ts.SyntaxKind.ClassDeclaration: case ts.SyntaxKind.ClassDeclaration:
const classDeclaration = <ts.ClassDeclaration>node; const classDeclaration = <ts.ClassDeclaration>node;
if (classDeclaration.name) {
const className = classDeclaration.name.text; const className = classDeclaration.name.text;
if (node.flags & ts.NodeFlags.Export) { if (node.flags & ts.NodeFlags.Export) {
locals.define(className, {__symbolic: 'reference', name: className}); locals.define(className, {__symbolic: 'reference', name: className});
@ -212,15 +213,19 @@ export class MetadataCollector {
locals.define( locals.define(
className, errorSym('Reference to non-exported class', node, {className})); className, errorSym('Reference to non-exported class', node, {className}));
} }
}
break; break;
case ts.SyntaxKind.FunctionDeclaration: case ts.SyntaxKind.FunctionDeclaration:
if (!(node.flags & ts.NodeFlags.Export)) { if (!(node.flags & ts.NodeFlags.Export)) {
// Report references to this function as an error. // Report references to this function as an error.
const functionDeclaration = <ts.FunctionDeclaration>node; const functionDeclaration = <ts.FunctionDeclaration>node;
const nameNode = functionDeclaration.name; const nameNode = functionDeclaration.name;
if (nameNode && nameNode.text) {
locals.define( locals.define(
nameNode.text, nameNode.text,
errorSym('Reference to a non-exported function', nameNode, {name: nameNode.text})); errorSym(
'Reference to a non-exported function', nameNode, {name: nameNode.text}));
}
} }
break; break;
} }
@ -248,6 +253,7 @@ export class MetadataCollector {
break; break;
case ts.SyntaxKind.ClassDeclaration: case ts.SyntaxKind.ClassDeclaration:
const classDeclaration = <ts.ClassDeclaration>node; const classDeclaration = <ts.ClassDeclaration>node;
if (classDeclaration.name) {
const className = classDeclaration.name.text; const className = classDeclaration.name.text;
if (node.flags & ts.NodeFlags.Export) { if (node.flags & ts.NodeFlags.Export) {
if (classDeclaration.decorators) { if (classDeclaration.decorators) {
@ -255,6 +261,7 @@ export class MetadataCollector {
metadata[className] = classMetadataOf(classDeclaration); metadata[className] = classMetadataOf(classDeclaration);
} }
} }
}
// Otherwise don't record metadata for the class. // Otherwise don't record metadata for the class.
break; break;
case ts.SyntaxKind.FunctionDeclaration: case ts.SyntaxKind.FunctionDeclaration:

View File

@ -15,7 +15,7 @@ import {Directory, Host, expectValidSources} from './typescript.mocks';
describe('Collector', () => { describe('Collector', () => {
let documentRegistry = ts.createDocumentRegistry(); let documentRegistry = ts.createDocumentRegistry();
let host: ts.LanguageServiceHost; let host: Host;
let service: ts.LanguageService; let service: ts.LanguageService;
let program: ts.Program; let program: ts.Program;
let collector: MetadataCollector; let collector: MetadataCollector;
@ -589,6 +589,28 @@ describe('Collector', () => {
.toThrowError(/Reference to non-exported class/); .toThrowError(/Reference to non-exported class/);
}); });
}); });
describe('with invalid input', () => {
it('should not throw with a class with no name', () => {
const fileName = '/invalid-class.ts';
override(fileName, 'export class');
let invalidClass = program.getSourceFile(fileName);
expect(() => collector.getMetadata(invalidClass)).not.toThrow();
});
it('should not throw with a function with no name', () => {
const fileName = '/invalid-function.ts';
override(fileName, 'export function');
let invalidFunction = program.getSourceFile(fileName);
expect(() => collector.getMetadata(invalidFunction)).not.toThrow();
});
});
function override(fileName: string, content: string) {
host.overrideFile(fileName, content);
host.addFile(fileName);
program = service.getProgram();
}
}); });
// TODO: Do not use \` in a template literal as it confuses clang-format // TODO: Do not use \` in a template literal as it confuses clang-format

View File

@ -5,6 +5,9 @@ import * as ts from 'typescript';
export interface Directory { [name: string]: (Directory|string); } export interface Directory { [name: string]: (Directory|string); }
export class Host implements ts.LanguageServiceHost { export class Host implements ts.LanguageServiceHost {
private overrides = new Map<string, string>();
private version = 1;
constructor(private directory: Directory, private scripts: string[]) {} constructor(private directory: Directory, private scripts: string[]) {}
getCompilationSettings(): ts.CompilerOptions { getCompilationSettings(): ts.CompilerOptions {
@ -17,7 +20,7 @@ export class Host implements ts.LanguageServiceHost {
getScriptFileNames(): string[] { return this.scripts; } getScriptFileNames(): string[] { return this.scripts; }
getScriptVersion(fileName: string): string { return '1'; } getScriptVersion(fileName: string): string { return this.version.toString(); }
getScriptSnapshot(fileName: string): ts.IScriptSnapshot { getScriptSnapshot(fileName: string): ts.IScriptSnapshot {
let content = this.getFileContent(fileName); let content = this.getFileContent(fileName);
@ -28,7 +31,20 @@ export class Host implements ts.LanguageServiceHost {
getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; } getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; }
overrideFile(fileName: string, content: string) {
this.overrides.set(fileName, content);
this.version++;
}
addFile(fileName: string) {
this.scripts.push(fileName);
this.version++;
}
private getFileContent(fileName: string): string { private getFileContent(fileName: string): string {
if (this.overrides.has(fileName)) {
return this.overrides.get(fileName);
}
const names = fileName.split('/'); const names = fileName.split('/');
if (names[names.length - 1] === 'lib.d.ts') { if (names[names.length - 1] === 'lib.d.ts') {
return fs.readFileSync(ts.getDefaultLibFilePath(this.getCompilationSettings()), 'utf8'); return fs.readFileSync(ts.getDefaultLibFilePath(this.getCompilationSettings()), 'utf8');