chore(tools): Remove use of TypeChecker from metadata collector.
The metadata collector was modified to look up references in the import list instead of resolving the symbol using the TypeChecker making the use of the TypeChecker vestigial. This change removes all uses of the TypeChecker. Modified the schema to be able to record global and local (non-module specific references). Added error messages to the schema and errors are recorded in the metadata file allowing the static reflector to throw errors if an unsupported construct is referenced by metadata. Closes #8966 Fixes #8893 Fixes #8894
This commit is contained in:
@ -1,36 +1,99 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
// TOOD: Remove when tools directory is upgraded to support es6 target
|
||||
interface Map<K, V> {
|
||||
has(v: V): boolean;
|
||||
set(k: K, v: V): void;
|
||||
get(k: K): V;
|
||||
}
|
||||
interface MapConstructor {
|
||||
new<K, V>(): Map<K, V>;
|
||||
}
|
||||
declare var Map: MapConstructor;
|
||||
import {MetadataValue} from './schema';
|
||||
|
||||
var a: Array<number>;
|
||||
|
||||
/**
|
||||
* A symbol table of ts.Symbol to a folded value used during expression folding in Evaluator.
|
||||
*
|
||||
* This is a thin wrapper around a Map<> using the first declaration location instead of the symbol
|
||||
* itself as the key. In the TypeScript binder and type checker, mulitple symbols are sometimes
|
||||
* created for a symbol depending on what scope it is in (e.g. export vs. local). Using the
|
||||
* declaration node as the key results in these duplicate symbols being treated as identical.
|
||||
*/
|
||||
export class Symbols {
|
||||
private map = new Map<ts.Node, any>();
|
||||
private _symbols: Map<string, MetadataValue>;
|
||||
|
||||
public has(symbol: ts.Symbol): boolean { return this.map.has(symbol.getDeclarations()[0]); }
|
||||
constructor(private sourceFile: ts.SourceFile) {}
|
||||
|
||||
public set(symbol: ts.Symbol, value: any): void {
|
||||
this.map.set(symbol.getDeclarations()[0], value);
|
||||
resolve(name: string): MetadataValue|undefined { return this.symbols.get(name); }
|
||||
|
||||
define(name: string, value: MetadataValue) { this.symbols.set(name, value); }
|
||||
|
||||
has(name: string): boolean { return this.symbols.has(name); }
|
||||
|
||||
private get symbols(): Map<string, MetadataValue> {
|
||||
let result = this._symbols;
|
||||
if (!result) {
|
||||
result = this._symbols = new Map();
|
||||
populateBuiltins(result);
|
||||
this.buildImports();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public get(symbol: ts.Symbol): any { return this.map.get(symbol.getDeclarations()[0]); }
|
||||
|
||||
static empty: Symbols = new Symbols();
|
||||
private buildImports(): void {
|
||||
let symbols = this._symbols;
|
||||
// Collect the imported symbols into this.symbols
|
||||
const stripQuotes = (s: string) => s.replace(/^['"]|['"]$/g, '');
|
||||
const visit = (node: ts.Node) => {
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.ImportEqualsDeclaration:
|
||||
const importEqualsDeclaration = <ts.ImportEqualsDeclaration>node;
|
||||
if (importEqualsDeclaration.moduleReference.kind ===
|
||||
ts.SyntaxKind.ExternalModuleReference) {
|
||||
const externalReference =
|
||||
<ts.ExternalModuleReference>importEqualsDeclaration.moduleReference;
|
||||
// An `import <identifier> = require(<module-specifier>);
|
||||
const from = stripQuotes(externalReference.expression.getText());
|
||||
symbols.set(importEqualsDeclaration.name.text, {__symbolic: 'reference', module: from});
|
||||
} else {
|
||||
symbols.set(
|
||||
importEqualsDeclaration.name.text,
|
||||
{__symbolic: 'error', message: `Unsupported import syntax`});
|
||||
}
|
||||
break;
|
||||
case ts.SyntaxKind.ImportDeclaration:
|
||||
const importDecl = <ts.ImportDeclaration>node;
|
||||
if (!importDecl.importClause) {
|
||||
// An `import <module-specifier>` clause which does not bring symbols into scope.
|
||||
break;
|
||||
}
|
||||
const from = stripQuotes(importDecl.moduleSpecifier.getText());
|
||||
if (importDecl.importClause.name) {
|
||||
// An `import <identifier> form <module-specifier>` clause. Record the defualt symbol.
|
||||
symbols.set(
|
||||
importDecl.importClause.name.text,
|
||||
{__symbolic: 'reference', module: from, default: true});
|
||||
}
|
||||
const bindings = importDecl.importClause.namedBindings;
|
||||
if (bindings) {
|
||||
switch (bindings.kind) {
|
||||
case ts.SyntaxKind.NamedImports:
|
||||
// An `import { [<identifier> [, <identifier>] } from <module-specifier>` clause
|
||||
for (let binding of (<ts.NamedImports>bindings).elements) {
|
||||
symbols.set(binding.name.text, {
|
||||
__symbolic: 'reference',
|
||||
module: from,
|
||||
name: binding.propertyName ? binding.propertyName.text : binding.name.text
|
||||
});
|
||||
}
|
||||
break;
|
||||
case ts.SyntaxKind.NamespaceImport:
|
||||
// An `input * as <identifier> from <module-specifier>` clause.
|
||||
symbols.set(
|
||||
(<ts.NamespaceImport>bindings).name.text,
|
||||
{__symbolic: 'reference', module: from});
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
if (this.sourceFile) {
|
||||
ts.forEachChild(this.sourceFile, visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function populateBuiltins(symbols: Map<string, MetadataValue>) {
|
||||
// From lib.core.d.ts (all "define const")
|
||||
['Object', 'Function', 'String', 'Number', 'Array', 'Boolean', 'Map', 'NaN', 'Infinity', 'Math',
|
||||
'Date', 'RegExp', 'Error', 'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError',
|
||||
'TypeError', 'URIError', 'JSON', 'ArrayBuffer', 'DataView', 'Int8Array', 'Uint8Array',
|
||||
'Uint8ClampedArray', 'Uint16Array', 'Int16Array', 'Int32Array', 'Uint32Array', 'Float32Array',
|
||||
'Float64Array']
|
||||
.forEach(name => symbols.set(name, {__symbolic: 'reference', name}));
|
||||
}
|
Reference in New Issue
Block a user