build(ngc): run integration test hermetically
This ensures we run in a clean directory, using our real distribution. It finds bugs like @internal APIs needed to type-check in the offline compiler, as well as problems in package.json. Also move tsc-wrapped under tools/@angular
This commit is contained in:
191
tools/@angular/tsc-wrapped/src/collector.ts
Normal file
191
tools/@angular/tsc-wrapped/src/collector.ts
Normal file
@ -0,0 +1,191 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Evaluator, ImportMetadata, ImportSpecifierMetadata} from './evaluator';
|
||||
import {ClassMetadata, ConstructorMetadata, ModuleMetadata, MemberMetadata, MetadataMap, MetadataSymbolicExpression, MetadataSymbolicReferenceExpression, MetadataValue, MethodMetadata} from './schema';
|
||||
import {Symbols} from './symbols';
|
||||
|
||||
|
||||
/**
|
||||
* Collect decorator metadata from a TypeScript module.
|
||||
*/
|
||||
export class MetadataCollector {
|
||||
constructor() {}
|
||||
|
||||
collectImports(sourceFile: ts.SourceFile) {
|
||||
let imports: ImportMetadata[] = [];
|
||||
const stripQuotes = (s: string) => s.replace(/^['"]|['"]$/g, '');
|
||||
function visit(node: ts.Node) {
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.ImportDeclaration:
|
||||
const importDecl = <ts.ImportDeclaration>node;
|
||||
const from = stripQuotes(importDecl.moduleSpecifier.getText());
|
||||
const newImport = {from};
|
||||
if (!importDecl.importClause) {
|
||||
// Bare imports do not bring symbols into scope, so we don't need to record them
|
||||
break;
|
||||
}
|
||||
if (importDecl.importClause.name) {
|
||||
(<any>newImport)['defaultName'] = importDecl.importClause.name.text;
|
||||
}
|
||||
const bindings = importDecl.importClause.namedBindings;
|
||||
if (bindings) {
|
||||
switch (bindings.kind) {
|
||||
case ts.SyntaxKind.NamedImports:
|
||||
const namedImports: ImportSpecifierMetadata[] = [];
|
||||
(<ts.NamedImports>bindings).elements.forEach(i => {
|
||||
const namedImport = {name: i.name.text};
|
||||
if (i.propertyName) {
|
||||
(<any>namedImport)['propertyName'] = i.propertyName.text;
|
||||
}
|
||||
namedImports.push(namedImport);
|
||||
});
|
||||
(<any>newImport)['namedImports'] = namedImports;
|
||||
break;
|
||||
case ts.SyntaxKind.NamespaceImport:
|
||||
(<any>newImport)['namespace'] = (<ts.NamespaceImport>bindings).name.text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
imports.push(newImport);
|
||||
break;
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
}
|
||||
ts.forEachChild(sourceFile, visit);
|
||||
return imports;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON.stringify friendly form describing the decorators of the exported classes from
|
||||
* the source file that is expected to correspond to a module.
|
||||
*/
|
||||
public getMetadata(sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker): ModuleMetadata {
|
||||
const locals = new Symbols();
|
||||
const evaluator = new Evaluator(typeChecker, locals, this.collectImports(sourceFile));
|
||||
|
||||
function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression {
|
||||
return <MetadataSymbolicExpression>evaluator.evaluateNode(decoratorNode.expression);
|
||||
}
|
||||
|
||||
function referenceFromType(type: ts.Type): MetadataSymbolicReferenceExpression {
|
||||
if (type) {
|
||||
let symbol = type.getSymbol();
|
||||
if (symbol) {
|
||||
return evaluator.symbolReference(symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function classMetadataOf(classDeclaration: ts.ClassDeclaration): ClassMetadata {
|
||||
let result: ClassMetadata = {__symbolic: 'class'};
|
||||
|
||||
function getDecorators(decorators: ts.Decorator[]): MetadataSymbolicExpression[] {
|
||||
if (decorators && decorators.length)
|
||||
return decorators.map(decorator => objFromDecorator(decorator));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Add class decorators
|
||||
if (classDeclaration.decorators) {
|
||||
result.decorators = getDecorators(classDeclaration.decorators);
|
||||
}
|
||||
|
||||
// member decorators
|
||||
let members: MetadataMap = null;
|
||||
function recordMember(name: string, metadata: MemberMetadata) {
|
||||
if (!members) members = {};
|
||||
let data = members.hasOwnProperty(name) ? members[name] : [];
|
||||
data.push(metadata);
|
||||
members[name] = data;
|
||||
}
|
||||
for (const member of classDeclaration.members) {
|
||||
let isConstructor = false;
|
||||
switch (member.kind) {
|
||||
case ts.SyntaxKind.Constructor:
|
||||
isConstructor = true;
|
||||
// fallthrough
|
||||
case ts.SyntaxKind.MethodDeclaration:
|
||||
const method = <ts.MethodDeclaration|ts.ConstructorDeclaration>member;
|
||||
const methodDecorators = getDecorators(method.decorators);
|
||||
const parameters = method.parameters;
|
||||
const parameterDecoratorData: MetadataSymbolicExpression[][] = [];
|
||||
const parametersData: MetadataSymbolicReferenceExpression[] = [];
|
||||
let hasDecoratorData: boolean = false;
|
||||
let hasParameterData: boolean = false;
|
||||
for (const parameter of parameters) {
|
||||
const parameterData = getDecorators(parameter.decorators);
|
||||
parameterDecoratorData.push(parameterData);
|
||||
hasDecoratorData = hasDecoratorData || !!parameterData;
|
||||
if (isConstructor) {
|
||||
const parameterType = typeChecker.getTypeAtLocation(parameter);
|
||||
parametersData.push(referenceFromType(parameterType) || null);
|
||||
hasParameterData = true;
|
||||
}
|
||||
}
|
||||
const data: MethodMetadata = {__symbolic: isConstructor ? 'constructor' : 'method'};
|
||||
const name = isConstructor ? '__ctor__' : evaluator.nameOf(member.name);
|
||||
if (methodDecorators) {
|
||||
data.decorators = methodDecorators;
|
||||
}
|
||||
if (hasDecoratorData) {
|
||||
data.parameterDecorators = parameterDecoratorData;
|
||||
}
|
||||
if (hasParameterData) {
|
||||
(<ConstructorMetadata>data).parameters = parametersData;
|
||||
}
|
||||
recordMember(name, data);
|
||||
break;
|
||||
case ts.SyntaxKind.PropertyDeclaration:
|
||||
case ts.SyntaxKind.GetAccessor:
|
||||
case ts.SyntaxKind.SetAccessor:
|
||||
const property = <ts.PropertyDeclaration>member;
|
||||
const propertyDecorators = getDecorators(property.decorators);
|
||||
if (propertyDecorators) {
|
||||
recordMember(
|
||||
evaluator.nameOf(property.name),
|
||||
{__symbolic: 'property', decorators: propertyDecorators});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (members) {
|
||||
result.members = members;
|
||||
}
|
||||
|
||||
return result.decorators || members ? result : undefined;
|
||||
}
|
||||
|
||||
let metadata: {[name: string]: (ClassMetadata | MetadataValue)};
|
||||
const symbols = typeChecker.getSymbolsInScope(sourceFile, ts.SymbolFlags.ExportValue);
|
||||
for (var symbol of symbols) {
|
||||
for (var declaration of symbol.getDeclarations()) {
|
||||
switch (declaration.kind) {
|
||||
case ts.SyntaxKind.ClassDeclaration:
|
||||
const classDeclaration = <ts.ClassDeclaration>declaration;
|
||||
if (classDeclaration.decorators) {
|
||||
if (!metadata) metadata = {};
|
||||
metadata[classDeclaration.name.text] = classMetadataOf(classDeclaration);
|
||||
}
|
||||
break;
|
||||
case ts.SyntaxKind.VariableDeclaration:
|
||||
const variableDeclaration = <ts.VariableDeclaration>declaration;
|
||||
if (variableDeclaration.initializer) {
|
||||
const value = evaluator.evaluateNode(variableDeclaration.initializer);
|
||||
if (value !== undefined) {
|
||||
if (evaluator.isFoldable(variableDeclaration.initializer)) {
|
||||
// Record the value for use in other initializers
|
||||
locals.set(symbol, value);
|
||||
}
|
||||
if (!metadata) metadata = {};
|
||||
metadata[evaluator.nameOf(variableDeclaration.name)] =
|
||||
evaluator.evaluateNode(variableDeclaration.initializer);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return metadata && {__symbolic: 'module', metadata};
|
||||
}
|
||||
}
|
106
tools/@angular/tsc-wrapped/src/compiler_host.ts
Normal file
106
tools/@angular/tsc-wrapped/src/compiler_host.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import {writeFileSync} from 'fs';
|
||||
import {convertDecorators} from 'tsickle';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {MetadataCollector} from './collector';
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of CompilerHost that forwards all methods to another instance.
|
||||
* Useful for partial implementations to override only methods they care about.
|
||||
*/
|
||||
export abstract class DelegatingHost implements ts.CompilerHost {
|
||||
constructor(protected delegate: ts.CompilerHost) {}
|
||||
getSourceFile =
|
||||
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) =>
|
||||
this.delegate.getSourceFile(fileName, languageVersion, onError);
|
||||
|
||||
getCancellationToken = () => this.delegate.getCancellationToken();
|
||||
getDefaultLibFileName = (options: ts.CompilerOptions) =>
|
||||
this.delegate.getDefaultLibFileName(options);
|
||||
getDefaultLibLocation = () => this.delegate.getDefaultLibLocation();
|
||||
writeFile: ts.WriteFileCallback = this.delegate.writeFile;
|
||||
getCurrentDirectory = () => this.delegate.getCurrentDirectory();
|
||||
getCanonicalFileName = (fileName: string) => this.delegate.getCanonicalFileName(fileName);
|
||||
useCaseSensitiveFileNames = () => this.delegate.useCaseSensitiveFileNames();
|
||||
getNewLine = () => this.delegate.getNewLine();
|
||||
fileExists = (fileName: string) => this.delegate.fileExists(fileName);
|
||||
readFile = (fileName: string) => this.delegate.readFile(fileName);
|
||||
trace = (s: string) => this.delegate.trace(s);
|
||||
directoryExists = (directoryName: string) => this.delegate.directoryExists(directoryName);
|
||||
}
|
||||
|
||||
export class TsickleHost extends DelegatingHost {
|
||||
// Additional diagnostics gathered by pre- and post-emit transformations.
|
||||
public diagnostics: ts.Diagnostic[] = [];
|
||||
private TSICKLE_SUPPORT = `
|
||||
interface DecoratorInvocation {
|
||||
type: Function;
|
||||
args?: any[];
|
||||
}
|
||||
`;
|
||||
constructor(delegate: ts.CompilerHost) { super(delegate); }
|
||||
|
||||
getSourceFile =
|
||||
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) => {
|
||||
const originalContent = this.delegate.readFile(fileName);
|
||||
let newContent = originalContent;
|
||||
if (!/\.d\.ts$/.test(fileName)) {
|
||||
const converted = convertDecorators(fileName, originalContent);
|
||||
if (converted.diagnostics) {
|
||||
this.diagnostics.push(...converted.diagnostics);
|
||||
}
|
||||
newContent = converted.output + this.TSICKLE_SUPPORT;
|
||||
}
|
||||
return ts.createSourceFile(fileName, newContent, languageVersion, true);
|
||||
};
|
||||
}
|
||||
|
||||
const IGNORED_FILES = /\.ngfactory\.js$|\.css\.js$|\.css\.shim\.js$/;
|
||||
|
||||
export class MetadataWriterHost extends DelegatingHost {
|
||||
private metadataCollector = new MetadataCollector();
|
||||
constructor(delegate: ts.CompilerHost, private program: ts.Program) { super(delegate); }
|
||||
|
||||
private writeMetadata(emitFilePath: string, sourceFile: ts.SourceFile) {
|
||||
// TODO: replace with DTS filePath when https://github.com/Microsoft/TypeScript/pull/8412 is
|
||||
// released
|
||||
if (/*DTS*/ /\.js$/.test(emitFilePath)) {
|
||||
const path = emitFilePath.replace(/*DTS*/ /\.js$/, '.metadata.json');
|
||||
const metadata =
|
||||
this.metadataCollector.getMetadata(sourceFile, this.program.getTypeChecker());
|
||||
if (metadata && metadata.metadata) {
|
||||
const metadataText = JSON.stringify(metadata);
|
||||
writeFileSync(path, metadataText, {encoding: 'utf-8'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeFile: ts.WriteFileCallback =
|
||||
(fileName: string, data: string, writeByteOrderMark: boolean,
|
||||
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
|
||||
if (/\.d\.ts$/.test(fileName)) {
|
||||
// Let the original file be written first; this takes care of creating parent directories
|
||||
this.delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
||||
|
||||
// TODO: remove this early return after https://github.com/Microsoft/TypeScript/pull/8412
|
||||
// is
|
||||
// released
|
||||
return;
|
||||
}
|
||||
|
||||
if (IGNORED_FILES.test(fileName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sourceFiles) {
|
||||
throw new Error(
|
||||
'Metadata emit requires the sourceFiles are passed to WriteFileCallback. ' +
|
||||
'Update to TypeScript ^1.9.0-dev');
|
||||
}
|
||||
if (sourceFiles.length > 1) {
|
||||
throw new Error('Bundled emit with --out is not supported');
|
||||
}
|
||||
this.writeMetadata(fileName, sourceFiles[0]);
|
||||
};
|
||||
}
|
467
tools/@angular/tsc-wrapped/src/evaluator.ts
Normal file
467
tools/@angular/tsc-wrapped/src/evaluator.ts
Normal file
@ -0,0 +1,467 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {MetadataValue, MetadataSymbolicCallExpression, MetadataSymbolicReferenceExpression} from './schema';
|
||||
import {Symbols} from './symbols';
|
||||
|
||||
|
||||
// TOOD: Remove when tools directory is upgraded to support es6 target
|
||||
interface Map<K, V> {
|
||||
has(k: K): boolean;
|
||||
set(k: K, v: V): void;
|
||||
get(k: K): V;
|
||||
delete (k: K): void;
|
||||
}
|
||||
interface MapConstructor {
|
||||
new<K, V>(): Map<K, V>;
|
||||
}
|
||||
declare var Map: MapConstructor;
|
||||
|
||||
function isMethodCallOf(callExpression: ts.CallExpression, memberName: string): boolean {
|
||||
const expression = callExpression.expression;
|
||||
if (expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
|
||||
const propertyAccessExpression = <ts.PropertyAccessExpression>expression;
|
||||
const name = propertyAccessExpression.name;
|
||||
if (name.kind == ts.SyntaxKind.Identifier) {
|
||||
return name.text === memberName;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isCallOf(callExpression: ts.CallExpression, ident: string): boolean {
|
||||
const expression = callExpression.expression;
|
||||
if (expression.kind === ts.SyntaxKind.Identifier) {
|
||||
const identifier = <ts.Identifier>expression;
|
||||
return identifier.text === ident;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ts.forEachChild stops iterating children when the callback return a truthy value.
|
||||
* This method inverts this to implement an `every` style iterator. It will return
|
||||
* true if every call to `cb` returns `true`.
|
||||
*/
|
||||
function everyNodeChild(node: ts.Node, cb: (node: ts.Node) => boolean) {
|
||||
return !ts.forEachChild(node, node => !cb(node));
|
||||
}
|
||||
|
||||
function isPrimitive(value: any): boolean {
|
||||
return Object(value) !== value;
|
||||
}
|
||||
|
||||
function isDefined(obj: any): boolean {
|
||||
return obj !== undefined;
|
||||
}
|
||||
|
||||
// import {propertyName as name} from 'place'
|
||||
// import {name} from 'place'
|
||||
export interface ImportSpecifierMetadata {
|
||||
name: string;
|
||||
propertyName?: string;
|
||||
}
|
||||
export interface ImportMetadata {
|
||||
defaultName?: string; // import d from 'place'
|
||||
namespace?: string; // import * as d from 'place'
|
||||
namedImports?: ImportSpecifierMetadata[]; // import {a} from 'place'
|
||||
from: string; // from 'place'
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a symbolic representation of an expression folding values into their final value when
|
||||
* possible.
|
||||
*/
|
||||
export class Evaluator {
|
||||
constructor(
|
||||
private typeChecker: ts.TypeChecker, private symbols: Symbols,
|
||||
private imports: ImportMetadata[]) {}
|
||||
|
||||
symbolReference(symbol: ts.Symbol): MetadataSymbolicReferenceExpression {
|
||||
if (symbol) {
|
||||
let module: string;
|
||||
let name = symbol.name;
|
||||
for (const eachImport of this.imports) {
|
||||
if (symbol.name === eachImport.defaultName) {
|
||||
module = eachImport.from;
|
||||
name = undefined;
|
||||
}
|
||||
if (eachImport.namedImports) {
|
||||
for (const named of eachImport.namedImports) {
|
||||
if (symbol.name === named.name) {
|
||||
name = named.propertyName ? named.propertyName : named.name;
|
||||
module = eachImport.from;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {__symbolic: 'reference', name, module};
|
||||
}
|
||||
}
|
||||
|
||||
private findImportNamespace(node: ts.Node) {
|
||||
if (node.kind === ts.SyntaxKind.PropertyAccessExpression) {
|
||||
const lhs = (<ts.PropertyAccessExpression>node).expression;
|
||||
if (lhs.kind === ts.SyntaxKind.Identifier) {
|
||||
// TOOD: Use Array.find when tools directory is upgraded to support es6 target
|
||||
for (const eachImport of this.imports) {
|
||||
if (eachImport.namespace === (<ts.Identifier>lhs).text) {
|
||||
return eachImport;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private nodeSymbolReference(node: ts.Node): MetadataSymbolicReferenceExpression {
|
||||
const importNamespace = this.findImportNamespace(node);
|
||||
if (importNamespace) {
|
||||
const result = this.symbolReference(
|
||||
this.typeChecker.getSymbolAtLocation((<ts.PropertyAccessExpression>node).name));
|
||||
result.module = importNamespace.from;
|
||||
return result;
|
||||
}
|
||||
return this.symbolReference(this.typeChecker.getSymbolAtLocation(node));
|
||||
}
|
||||
|
||||
nameOf(node: ts.Node): string {
|
||||
if (node.kind == ts.SyntaxKind.Identifier) {
|
||||
return (<ts.Identifier>node).text;
|
||||
}
|
||||
return <string>this.evaluateNode(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the expression represented by `node` can be folded into a literal expression.
|
||||
*
|
||||
* For example, a literal is always foldable. This means that literal expressions such as `1.2`
|
||||
* `"Some value"` `true` `false` are foldable.
|
||||
*
|
||||
* - An object literal is foldable if all the properties in the literal are foldable.
|
||||
* - An array literal is foldable if all the elements are foldable.
|
||||
* - A call is foldable if it is a call to a Array.prototype.concat or a call to CONST_EXPR.
|
||||
* - A property access is foldable if the object is foldable.
|
||||
* - A array index is foldable if index expression is foldable and the array is foldable.
|
||||
* - Binary operator expressions are foldable if the left and right expressions are foldable and
|
||||
* it is one of '+', '-', '*', '/', '%', '||', and '&&'.
|
||||
* - An identifier is foldable if a value can be found for its symbol in the evaluator symbol
|
||||
* table.
|
||||
*/
|
||||
public isFoldable(node: ts.Node): boolean {
|
||||
return this.isFoldableWorker(node, new Map<ts.Node, boolean>());
|
||||
}
|
||||
|
||||
private isFoldableWorker(node: ts.Node, folding: Map<ts.Node, boolean>): boolean {
|
||||
if (node) {
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.ObjectLiteralExpression:
|
||||
return everyNodeChild(node, child => {
|
||||
if (child.kind === ts.SyntaxKind.PropertyAssignment) {
|
||||
const propertyAssignment = <ts.PropertyAssignment>child;
|
||||
return this.isFoldableWorker(propertyAssignment.initializer, folding);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
case ts.SyntaxKind.ArrayLiteralExpression:
|
||||
return everyNodeChild(node, child => this.isFoldableWorker(child, folding));
|
||||
case ts.SyntaxKind.CallExpression:
|
||||
const callExpression = <ts.CallExpression>node;
|
||||
// We can fold a <array>.concat(<v>).
|
||||
if (isMethodCallOf(callExpression, 'concat') && callExpression.arguments.length === 1) {
|
||||
const arrayNode = (<ts.PropertyAccessExpression>callExpression.expression).expression;
|
||||
if (this.isFoldableWorker(arrayNode, folding) &&
|
||||
this.isFoldableWorker(callExpression.arguments[0], folding)) {
|
||||
// It needs to be an array.
|
||||
const arrayValue = this.evaluateNode(arrayNode);
|
||||
if (arrayValue && Array.isArray(arrayValue)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can fold a call to CONST_EXPR
|
||||
if (isCallOf(callExpression, 'CONST_EXPR') && callExpression.arguments.length === 1)
|
||||
return this.isFoldableWorker(callExpression.arguments[0], folding);
|
||||
return false;
|
||||
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||
case ts.SyntaxKind.StringLiteral:
|
||||
case ts.SyntaxKind.NumericLiteral:
|
||||
case ts.SyntaxKind.NullKeyword:
|
||||
case ts.SyntaxKind.TrueKeyword:
|
||||
case ts.SyntaxKind.FalseKeyword:
|
||||
return true;
|
||||
case ts.SyntaxKind.ParenthesizedExpression:
|
||||
const parenthesizedExpression = <ts.ParenthesizedExpression>node;
|
||||
return this.isFoldableWorker(parenthesizedExpression.expression, folding);
|
||||
case ts.SyntaxKind.BinaryExpression:
|
||||
const binaryExpression = <ts.BinaryExpression>node;
|
||||
switch (binaryExpression.operatorToken.kind) {
|
||||
case ts.SyntaxKind.PlusToken:
|
||||
case ts.SyntaxKind.MinusToken:
|
||||
case ts.SyntaxKind.AsteriskToken:
|
||||
case ts.SyntaxKind.SlashToken:
|
||||
case ts.SyntaxKind.PercentToken:
|
||||
case ts.SyntaxKind.AmpersandAmpersandToken:
|
||||
case ts.SyntaxKind.BarBarToken:
|
||||
return this.isFoldableWorker(binaryExpression.left, folding) &&
|
||||
this.isFoldableWorker(binaryExpression.right, folding);
|
||||
}
|
||||
case ts.SyntaxKind.PropertyAccessExpression:
|
||||
const propertyAccessExpression = <ts.PropertyAccessExpression>node;
|
||||
return this.isFoldableWorker(propertyAccessExpression.expression, folding);
|
||||
case ts.SyntaxKind.ElementAccessExpression:
|
||||
const elementAccessExpression = <ts.ElementAccessExpression>node;
|
||||
return this.isFoldableWorker(elementAccessExpression.expression, folding) &&
|
||||
this.isFoldableWorker(elementAccessExpression.argumentExpression, folding);
|
||||
case ts.SyntaxKind.Identifier:
|
||||
let symbol = this.typeChecker.getSymbolAtLocation(node);
|
||||
if (symbol.flags & ts.SymbolFlags.Alias) {
|
||||
symbol = this.typeChecker.getAliasedSymbol(symbol);
|
||||
}
|
||||
if (this.symbols.has(symbol)) return true;
|
||||
|
||||
// If this is a reference to a foldable variable then it is foldable too.
|
||||
const variableDeclaration = <ts.VariableDeclaration>(
|
||||
symbol.declarations && symbol.declarations.length && symbol.declarations[0]);
|
||||
if (variableDeclaration.kind === ts.SyntaxKind.VariableDeclaration) {
|
||||
const initializer = variableDeclaration.initializer;
|
||||
if (folding.has(initializer)) {
|
||||
// A recursive reference is not foldable.
|
||||
return false;
|
||||
}
|
||||
folding.set(initializer, true);
|
||||
const result = this.isFoldableWorker(initializer, folding);
|
||||
folding.delete(initializer);
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a JSON serialiable object representing `node`. The foldable values in the expression
|
||||
* tree are folded. For example, a node representing `1 + 2` is folded into `3`.
|
||||
*/
|
||||
public evaluateNode(node: ts.Node): MetadataValue {
|
||||
switch (node.kind) {
|
||||
case ts.SyntaxKind.ObjectLiteralExpression:
|
||||
let obj: MetadataValue = {};
|
||||
let allPropertiesDefined = true;
|
||||
ts.forEachChild(node, child => {
|
||||
switch (child.kind) {
|
||||
case ts.SyntaxKind.PropertyAssignment:
|
||||
const assignment = <ts.PropertyAssignment>child;
|
||||
const propertyName = this.nameOf(assignment.name);
|
||||
const propertyValue = this.evaluateNode(assignment.initializer);
|
||||
(<any>obj)[propertyName] = propertyValue;
|
||||
allPropertiesDefined = isDefined(propertyValue) && allPropertiesDefined;
|
||||
}
|
||||
});
|
||||
if (allPropertiesDefined) return obj;
|
||||
break;
|
||||
case ts.SyntaxKind.ArrayLiteralExpression:
|
||||
let arr: MetadataValue[] = [];
|
||||
let allElementsDefined = true;
|
||||
ts.forEachChild(node, child => {
|
||||
const value = this.evaluateNode(child);
|
||||
arr.push(value);
|
||||
allElementsDefined = isDefined(value) && allElementsDefined;
|
||||
});
|
||||
if (allElementsDefined) return arr;
|
||||
break;
|
||||
case ts.SyntaxKind.CallExpression:
|
||||
const callExpression = <ts.CallExpression>node;
|
||||
const args = callExpression.arguments.map(arg => this.evaluateNode(arg));
|
||||
if (this.isFoldable(callExpression)) {
|
||||
if (isMethodCallOf(callExpression, 'concat')) {
|
||||
const arrayValue = <MetadataValue[]>this.evaluateNode(
|
||||
(<ts.PropertyAccessExpression>callExpression.expression).expression);
|
||||
return arrayValue.concat(args[0]);
|
||||
}
|
||||
}
|
||||
// Always fold a CONST_EXPR even if the argument is not foldable.
|
||||
if (isCallOf(callExpression, 'CONST_EXPR') && callExpression.arguments.length === 1) {
|
||||
return args[0];
|
||||
}
|
||||
if (isCallOf(callExpression, 'forwardRef') && callExpression.arguments.length === 1) {
|
||||
const firstArgument = callExpression.arguments[0];
|
||||
if (firstArgument.kind == ts.SyntaxKind.ArrowFunction) {
|
||||
const arrowFunction = <ts.ArrowFunction>firstArgument;
|
||||
return this.evaluateNode(arrowFunction.body);
|
||||
}
|
||||
}
|
||||
const expression = this.evaluateNode(callExpression.expression);
|
||||
if (isDefined(expression) && args.every(isDefined)) {
|
||||
const result:
|
||||
MetadataSymbolicCallExpression = {__symbolic: 'call', expression: expression};
|
||||
if (args && args.length) {
|
||||
result.arguments = args;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
case ts.SyntaxKind.NewExpression:
|
||||
const newExpression = <ts.NewExpression>node;
|
||||
const newArgs = newExpression.arguments.map(arg => this.evaluateNode(arg));
|
||||
const newTarget = this.evaluateNode(newExpression.expression);
|
||||
if (isDefined(newTarget) && newArgs.every(isDefined)) {
|
||||
const result: MetadataSymbolicCallExpression = {__symbolic: 'new', expression: newTarget};
|
||||
if (newArgs.length) {
|
||||
result.arguments = newArgs;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
case ts.SyntaxKind.PropertyAccessExpression: {
|
||||
const propertyAccessExpression = <ts.PropertyAccessExpression>node;
|
||||
const expression = this.evaluateNode(propertyAccessExpression.expression);
|
||||
const member = this.nameOf(propertyAccessExpression.name);
|
||||
if (this.isFoldable(propertyAccessExpression.expression)) return (<any>expression)[member];
|
||||
if (this.findImportNamespace(propertyAccessExpression)) {
|
||||
return this.nodeSymbolReference(propertyAccessExpression);
|
||||
}
|
||||
if (isDefined(expression)) {
|
||||
return {__symbolic: 'select', expression, member};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ts.SyntaxKind.ElementAccessExpression: {
|
||||
const elementAccessExpression = <ts.ElementAccessExpression>node;
|
||||
const expression = this.evaluateNode(elementAccessExpression.expression);
|
||||
const index = this.evaluateNode(elementAccessExpression.argumentExpression);
|
||||
if (this.isFoldable(elementAccessExpression.expression) &&
|
||||
this.isFoldable(elementAccessExpression.argumentExpression))
|
||||
return (<any>expression)[<string|number>index];
|
||||
if (isDefined(expression) && isDefined(index)) {
|
||||
return {__symbolic: 'index', expression, index};
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ts.SyntaxKind.Identifier:
|
||||
let symbol = this.typeChecker.getSymbolAtLocation(node);
|
||||
if (symbol.flags & ts.SymbolFlags.Alias) {
|
||||
symbol = this.typeChecker.getAliasedSymbol(symbol);
|
||||
}
|
||||
if (this.symbols.has(symbol)) return this.symbols.get(symbol);
|
||||
if (this.isFoldable(node)) {
|
||||
// isFoldable implies, in this context, symbol declaration is a VariableDeclaration
|
||||
const variableDeclaration = <ts.VariableDeclaration>(
|
||||
symbol.declarations && symbol.declarations.length && symbol.declarations[0]);
|
||||
return this.evaluateNode(variableDeclaration.initializer);
|
||||
}
|
||||
return this.nodeSymbolReference(node);
|
||||
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
|
||||
return (<ts.LiteralExpression>node).text;
|
||||
case ts.SyntaxKind.StringLiteral:
|
||||
return (<ts.StringLiteral>node).text;
|
||||
case ts.SyntaxKind.NumericLiteral:
|
||||
return parseFloat((<ts.LiteralExpression>node).text);
|
||||
case ts.SyntaxKind.NullKeyword:
|
||||
return null;
|
||||
case ts.SyntaxKind.TrueKeyword:
|
||||
return true;
|
||||
case ts.SyntaxKind.FalseKeyword:
|
||||
return false;
|
||||
case ts.SyntaxKind.ParenthesizedExpression:
|
||||
const parenthesizedExpression = <ts.ParenthesizedExpression>node;
|
||||
return this.evaluateNode(parenthesizedExpression.expression);
|
||||
case ts.SyntaxKind.TypeAssertionExpression:
|
||||
const typeAssertion = <ts.TypeAssertion>node;
|
||||
return this.evaluateNode(typeAssertion.expression);
|
||||
case ts.SyntaxKind.PrefixUnaryExpression:
|
||||
const prefixUnaryExpression = <ts.PrefixUnaryExpression>node;
|
||||
const operand = this.evaluateNode(prefixUnaryExpression.operand);
|
||||
if (isDefined(operand) && isPrimitive(operand)) {
|
||||
switch (prefixUnaryExpression.operator) {
|
||||
case ts.SyntaxKind.PlusToken:
|
||||
return +operand;
|
||||
case ts.SyntaxKind.MinusToken:
|
||||
return -operand;
|
||||
case ts.SyntaxKind.TildeToken:
|
||||
return ~operand;
|
||||
case ts.SyntaxKind.ExclamationToken:
|
||||
return !operand;
|
||||
}
|
||||
}
|
||||
let operatorText: string;
|
||||
switch (prefixUnaryExpression.operator) {
|
||||
case ts.SyntaxKind.PlusToken:
|
||||
operatorText = '+';
|
||||
break;
|
||||
case ts.SyntaxKind.MinusToken:
|
||||
operatorText = '-';
|
||||
break;
|
||||
case ts.SyntaxKind.TildeToken:
|
||||
operatorText = '~';
|
||||
break;
|
||||
case ts.SyntaxKind.ExclamationToken:
|
||||
operatorText = '!';
|
||||
break;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
return {__symbolic: 'pre', operator: operatorText, operand: operand};
|
||||
case ts.SyntaxKind.BinaryExpression:
|
||||
const binaryExpression = <ts.BinaryExpression>node;
|
||||
const left = this.evaluateNode(binaryExpression.left);
|
||||
const right = this.evaluateNode(binaryExpression.right);
|
||||
if (isDefined(left) && isDefined(right)) {
|
||||
if (isPrimitive(left) && isPrimitive(right))
|
||||
switch (binaryExpression.operatorToken.kind) {
|
||||
case ts.SyntaxKind.BarBarToken:
|
||||
return <any>left || <any>right;
|
||||
case ts.SyntaxKind.AmpersandAmpersandToken:
|
||||
return <any>left && <any>right;
|
||||
case ts.SyntaxKind.AmpersandToken:
|
||||
return <any>left & <any>right;
|
||||
case ts.SyntaxKind.BarToken:
|
||||
return <any>left | <any>right;
|
||||
case ts.SyntaxKind.CaretToken:
|
||||
return <any>left ^ <any>right;
|
||||
case ts.SyntaxKind.EqualsEqualsToken:
|
||||
return <any>left == <any>right;
|
||||
case ts.SyntaxKind.ExclamationEqualsToken:
|
||||
return <any>left != <any>right;
|
||||
case ts.SyntaxKind.EqualsEqualsEqualsToken:
|
||||
return <any>left === <any>right;
|
||||
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
|
||||
return <any>left !== <any>right;
|
||||
case ts.SyntaxKind.LessThanToken:
|
||||
return <any>left < <any>right;
|
||||
case ts.SyntaxKind.GreaterThanToken:
|
||||
return <any>left > <any>right;
|
||||
case ts.SyntaxKind.LessThanEqualsToken:
|
||||
return <any>left <= <any>right;
|
||||
case ts.SyntaxKind.GreaterThanEqualsToken:
|
||||
return <any>left >= <any>right;
|
||||
case ts.SyntaxKind.LessThanLessThanToken:
|
||||
return (<any>left) << (<any>right);
|
||||
case ts.SyntaxKind.GreaterThanGreaterThanToken:
|
||||
return <any>left >> <any>right;
|
||||
case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
|
||||
return <any>left >>> <any>right;
|
||||
case ts.SyntaxKind.PlusToken:
|
||||
return <any>left + <any>right;
|
||||
case ts.SyntaxKind.MinusToken:
|
||||
return <any>left - <any>right;
|
||||
case ts.SyntaxKind.AsteriskToken:
|
||||
return <any>left * <any>right;
|
||||
case ts.SyntaxKind.SlashToken:
|
||||
return <any>left / <any>right;
|
||||
case ts.SyntaxKind.PercentToken:
|
||||
return <any>left % <any>right;
|
||||
}
|
||||
return {
|
||||
__symbolic: 'binop',
|
||||
operator: binaryExpression.operatorToken.getText(),
|
||||
left: left,
|
||||
right: right
|
||||
};
|
||||
}
|
||||
break;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
64
tools/@angular/tsc-wrapped/src/main.ts
Normal file
64
tools/@angular/tsc-wrapped/src/main.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
import {tsc, check} from './tsc';
|
||||
import NgOptions from './options';
|
||||
import {MetadataWriterHost, TsickleHost} from './compiler_host';
|
||||
|
||||
export type CodegenExtension = (ngOptions: NgOptions, program: ts.Program, host: ts.CompilerHost) =>
|
||||
Promise<any>;
|
||||
|
||||
export function main(project: string, basePath?: string, codegen?: CodegenExtension): Promise<any> {
|
||||
try {
|
||||
let projectDir = project;
|
||||
if (fs.lstatSync(project).isFile()) {
|
||||
projectDir = path.dirname(project);
|
||||
}
|
||||
// file names in tsconfig are resolved relative to this absolute path
|
||||
basePath = path.join(process.cwd(), basePath || projectDir);
|
||||
|
||||
// read the configuration options from wherever you store them
|
||||
const {parsed, ngOptions} = tsc.readConfiguration(project, basePath);
|
||||
ngOptions.basePath = basePath;
|
||||
|
||||
const host = ts.createCompilerHost(parsed.options, true);
|
||||
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
|
||||
const errors = program.getOptionsDiagnostics();
|
||||
check(errors);
|
||||
|
||||
if (ngOptions.skipTemplateCodegen || !codegen) {
|
||||
codegen = () => Promise.resolve(null);
|
||||
}
|
||||
return codegen(ngOptions, program, host).then(() => {
|
||||
tsc.typeCheck(host, program);
|
||||
|
||||
// Emit *.js with Decorators lowered to Annotations, and also *.js.map
|
||||
const tsicklePreProcessor = new TsickleHost(host);
|
||||
tsc.emit(tsicklePreProcessor, program);
|
||||
|
||||
if (!ngOptions.skipMetadataEmit) {
|
||||
// Emit *.metadata.json and *.d.ts
|
||||
// Not in the same emit pass with above, because tsickle erases
|
||||
// decorators which we want to read or document.
|
||||
// Do this emit second since TypeScript will create missing directories for us
|
||||
// in the standard emit.
|
||||
const metadataWriter = new MetadataWriterHost(host, program);
|
||||
tsc.emit(metadataWriter, program);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
// CLI entry point
|
||||
if (require.main === module) {
|
||||
const args = require('minimist')(process.argv.slice(2));
|
||||
main(args.p || args.project || '.', args.basePath)
|
||||
.then(exitCode => process.exit(exitCode))
|
||||
.catch(e => {
|
||||
console.error(e.stack);
|
||||
console.error('Compilation failed');
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
20
tools/@angular/tsc-wrapped/src/options.ts
Normal file
20
tools/@angular/tsc-wrapped/src/options.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
interface Options extends ts.CompilerOptions {
|
||||
// Absolute path to a directory where generated file structure is written
|
||||
genDir: string;
|
||||
|
||||
// Path to the directory containing the tsconfig.json file.
|
||||
basePath: string;
|
||||
|
||||
// Don't produce .metadata.json files (they don't work for bundled emit with --out)
|
||||
skipMetadataEmit: boolean;
|
||||
|
||||
// Don't produce .ngfactory.ts or .css.shim.ts files
|
||||
skipTemplateCodegen: boolean;
|
||||
|
||||
// Print extra information while running the compiler
|
||||
trace: boolean;
|
||||
}
|
||||
|
||||
export default Options;
|
138
tools/@angular/tsc-wrapped/src/schema.ts
Normal file
138
tools/@angular/tsc-wrapped/src/schema.ts
Normal file
@ -0,0 +1,138 @@
|
||||
export interface ModuleMetadata {
|
||||
__symbolic: 'module';
|
||||
metadata: {[name: string]: (ClassMetadata | MetadataValue)};
|
||||
}
|
||||
export function isModuleMetadata(value: any): value is ModuleMetadata {
|
||||
return value && value.__symbolic === 'module';
|
||||
}
|
||||
|
||||
export interface ClassMetadata {
|
||||
__symbolic: 'class';
|
||||
decorators?: MetadataSymbolicExpression[];
|
||||
members?: MetadataMap;
|
||||
}
|
||||
export function isClassMetadata(value: any): value is ClassMetadata {
|
||||
return value && value.__symbolic === 'class';
|
||||
}
|
||||
|
||||
export interface MetadataMap { [name: string]: MemberMetadata[]; }
|
||||
|
||||
export interface MemberMetadata {
|
||||
__symbolic: 'constructor'|'method'|'property';
|
||||
decorators?: MetadataSymbolicExpression[];
|
||||
}
|
||||
export function isMemberMetadata(value: any): value is MemberMetadata {
|
||||
if (value) {
|
||||
switch (value.__symbolic) {
|
||||
case 'constructor':
|
||||
case 'method':
|
||||
case 'property':
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export interface MethodMetadata extends MemberMetadata {
|
||||
__symbolic: 'constructor'|'method';
|
||||
parameterDecorators?: MetadataSymbolicExpression[][];
|
||||
}
|
||||
export function isMethodMetadata(value: any): value is MemberMetadata {
|
||||
return value && (value.__symbolic === 'constructor' || value.__symbolic === 'method');
|
||||
}
|
||||
|
||||
export interface ConstructorMetadata extends MethodMetadata {
|
||||
__symbolic: 'constructor';
|
||||
parameters?: MetadataSymbolicExpression[];
|
||||
}
|
||||
export function isConstructorMetadata(value: any): value is ConstructorMetadata {
|
||||
return value && value.__symbolic === 'constructor';
|
||||
}
|
||||
|
||||
export type MetadataValue =
|
||||
string | number | boolean | MetadataObject | MetadataArray | MetadataSymbolicExpression;
|
||||
|
||||
export interface MetadataObject { [name: string]: MetadataValue; }
|
||||
|
||||
export interface MetadataArray { [name: number]: MetadataValue; }
|
||||
|
||||
export interface MetadataSymbolicExpression {
|
||||
__symbolic: 'binary'|'call'|'index'|'new'|'pre'|'reference'|'select'
|
||||
}
|
||||
export function isMetadataSymbolicExpression(value: any): value is MetadataSymbolicExpression {
|
||||
if (value) {
|
||||
switch (value.__symbolic) {
|
||||
case 'binary':
|
||||
case 'call':
|
||||
case 'index':
|
||||
case 'new':
|
||||
case 'pre':
|
||||
case 'reference':
|
||||
case 'select':
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export interface MetadataSymbolicBinaryExpression extends MetadataSymbolicExpression {
|
||||
__symbolic: 'binary';
|
||||
operator: '&&'|'||'|'|'|'^'|'&'|'=='|'!='|'==='|'!=='|'<'|'>'|'<='|'>='|'instanceof'|'in'|'as'|
|
||||
'<<'|'>>'|'>>>'|'+'|'-'|'*'|'/'|'%'|'**';
|
||||
left: MetadataValue;
|
||||
right: MetadataValue;
|
||||
}
|
||||
export function isMetadataSymbolicBinaryExpression(value: any):
|
||||
value is MetadataSymbolicBinaryExpression {
|
||||
return value && value.__symbolic === 'binary';
|
||||
}
|
||||
|
||||
export interface MetadataSymbolicIndexExpression extends MetadataSymbolicExpression {
|
||||
__symbolic: 'index';
|
||||
expression: MetadataValue;
|
||||
index: MetadataValue;
|
||||
}
|
||||
export function isMetadataSymbolicIndexExpression(value: any):
|
||||
value is MetadataSymbolicIndexExpression {
|
||||
return value && value.__symbolic === 'index';
|
||||
}
|
||||
|
||||
export interface MetadataSymbolicCallExpression extends MetadataSymbolicExpression {
|
||||
__symbolic: 'call'|'new';
|
||||
expression: MetadataValue;
|
||||
arguments?: MetadataValue[];
|
||||
}
|
||||
export function isMetadataSymbolicCallExpression(value: any):
|
||||
value is MetadataSymbolicCallExpression {
|
||||
return value && (value.__symbolic === 'call' || value.__symbolic === 'new');
|
||||
}
|
||||
|
||||
export interface MetadataSymbolicPrefixExpression extends MetadataSymbolicExpression {
|
||||
__symbolic: 'pre';
|
||||
operator: '+'|'-'|'~'|'!';
|
||||
operand: MetadataValue;
|
||||
}
|
||||
export function isMetadataSymbolicPrefixExpression(value: any):
|
||||
value is MetadataSymbolicPrefixExpression {
|
||||
return value && value.__symbolic === 'pre';
|
||||
}
|
||||
|
||||
export interface MetadataSymbolicReferenceExpression extends MetadataSymbolicExpression {
|
||||
__symbolic: 'reference';
|
||||
name: string;
|
||||
module: string;
|
||||
}
|
||||
export function isMetadataSymbolicReferenceExpression(value: any):
|
||||
value is MetadataSymbolicReferenceExpression {
|
||||
return value && value.__symbolic === 'reference';
|
||||
}
|
||||
|
||||
export interface MetadataSymbolicSelectExpression extends MetadataSymbolicExpression {
|
||||
__symbolic: 'select';
|
||||
expression: MetadataValue;
|
||||
name: string;
|
||||
}
|
||||
export function isMetadataSymbolicSelectExpression(value: any):
|
||||
value is MetadataSymbolicSelectExpression {
|
||||
return value && value.__symbolic === 'select';
|
||||
}
|
36
tools/@angular/tsc-wrapped/src/symbols.ts
Normal file
36
tools/@angular/tsc-wrapped/src/symbols.ts
Normal file
@ -0,0 +1,36 @@
|
||||
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;
|
||||
|
||||
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>();
|
||||
|
||||
public has(symbol: ts.Symbol): boolean { return this.map.has(symbol.getDeclarations()[0]); }
|
||||
|
||||
public set(symbol: ts.Symbol, value: any): void {
|
||||
this.map.set(symbol.getDeclarations()[0], value);
|
||||
}
|
||||
|
||||
public get(symbol: ts.Symbol): any { return this.map.get(symbol.getDeclarations()[0]); }
|
||||
|
||||
static empty: Symbols = new Symbols();
|
||||
}
|
110
tools/@angular/tsc-wrapped/src/tsc.ts
Normal file
110
tools/@angular/tsc-wrapped/src/tsc.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import AngularCompilerOptions from './options';
|
||||
import {TsickleHost} from './compiler_host';
|
||||
|
||||
/**
|
||||
* Our interface to the TypeScript standard compiler.
|
||||
* If you write an Angular compiler plugin for another build tool,
|
||||
* you should implement a similar interface.
|
||||
*/
|
||||
export interface CompilerInterface {
|
||||
readConfiguration(project: string, basePath: string):
|
||||
{parsed: ts.ParsedCommandLine, ngOptions: AngularCompilerOptions};
|
||||
typeCheck(compilerHost: ts.CompilerHost, program: ts.Program): void;
|
||||
emit(compilerHost: ts.CompilerHost, program: ts.Program): number;
|
||||
}
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
function debug(msg: string, ...o: any[]) {
|
||||
if (DEBUG) console.log(msg, ...o);
|
||||
}
|
||||
|
||||
export function formatDiagnostics(diags: ts.Diagnostic[]): string {
|
||||
return diags
|
||||
.map((d) => {
|
||||
let res = ts.DiagnosticCategory[d.category];
|
||||
if (d.file) {
|
||||
res += ' at ' + d.file.fileName + ':';
|
||||
const {line, character} = d.file.getLineAndCharacterOfPosition(d.start);
|
||||
res += (line + 1) + ':' + (character + 1) + ':';
|
||||
}
|
||||
res += ' ' + ts.flattenDiagnosticMessageText(d.messageText, '\n');
|
||||
return res;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
export function check(diags: ts.Diagnostic[]) {
|
||||
if (diags && diags.length && diags[0]) {
|
||||
throw new Error(formatDiagnostics(diags));
|
||||
}
|
||||
}
|
||||
|
||||
export class Tsc implements CompilerInterface {
|
||||
public ngOptions: AngularCompilerOptions;
|
||||
public parsed: ts.ParsedCommandLine;
|
||||
private basePath: string;
|
||||
|
||||
constructor(private readFile = ts.sys.readFile, private readDirectory = ts.sys.readDirectory) {}
|
||||
|
||||
readConfiguration(project: string, basePath: string) {
|
||||
this.basePath = basePath;
|
||||
|
||||
// Allow a directory containing tsconfig.json as the project value
|
||||
try {
|
||||
this.readDirectory(project);
|
||||
project = path.join(project, 'tsconfig.json');
|
||||
} catch (e) {
|
||||
// Was not a directory, continue on assuming it's a file
|
||||
}
|
||||
|
||||
const {config, error} = ts.readConfigFile(project, this.readFile);
|
||||
check([error]);
|
||||
|
||||
this.parsed =
|
||||
ts.parseJsonConfigFileContent(config, {readDirectory: this.readDirectory}, basePath);
|
||||
|
||||
check(this.parsed.errors);
|
||||
|
||||
// Default codegen goes to the current directory
|
||||
// Parsed options are already converted to absolute paths
|
||||
this.ngOptions = config.angularCompilerOptions || {};
|
||||
this.ngOptions.genDir = path.join(basePath, this.ngOptions.genDir || '.');
|
||||
for (const key of Object.keys(this.parsed.options)) {
|
||||
this.ngOptions[key] = this.parsed.options[key];
|
||||
}
|
||||
return {parsed: this.parsed, ngOptions: this.ngOptions};
|
||||
}
|
||||
|
||||
typeCheck(compilerHost: ts.CompilerHost, oldProgram: ts.Program): void {
|
||||
// Create a new program since codegen files were created after making the old program
|
||||
const program =
|
||||
ts.createProgram(this.parsed.fileNames, this.parsed.options, compilerHost, oldProgram);
|
||||
debug('Checking global diagnostics...');
|
||||
check(program.getGlobalDiagnostics());
|
||||
|
||||
let diagnostics: ts.Diagnostic[] = [];
|
||||
debug('Type checking...');
|
||||
|
||||
for (let sf of program.getSourceFiles()) {
|
||||
diagnostics.push(...ts.getPreEmitDiagnostics(program, sf));
|
||||
}
|
||||
check(diagnostics);
|
||||
}
|
||||
|
||||
emit(compilerHost: TsickleHost, oldProgram: ts.Program): number {
|
||||
// Create a new program since the host may be different from the old program.
|
||||
const program = ts.createProgram(this.parsed.fileNames, this.parsed.options, compilerHost);
|
||||
debug('Emitting outputs...');
|
||||
const emitResult = program.emit();
|
||||
let diagnostics: ts.Diagnostic[] = [];
|
||||
diagnostics.push(...emitResult.diagnostics);
|
||||
|
||||
check(compilerHost.diagnostics);
|
||||
return emitResult.emitSkipped ? 1 : 0;
|
||||
}
|
||||
}
|
||||
export var tsc: CompilerInterface = new Tsc();
|
Reference in New Issue
Block a user