refactor(compiler): remove all source-level traces to tsc-wrapped (#18966)

- temporarily keeps the old sources under packages/tsc-wrapped
  until the build scripts are changed to use compiler-cli everywhere.
- removes the compiler options `disableTransformerPipeline` that was introduced
  in a previous beta of Angular 5, i.e. the transformer based compiler
  is now always enabled.

PR Close #18966
This commit is contained in:
Matias Niemelä 2017-09-13 16:55:42 -07:00
parent 42c69d3ba6
commit 4695c69cf1
44 changed files with 5350 additions and 516 deletions

View File

@ -9,10 +9,11 @@
"typescript": "^2.4.2"
},
"dependencies": {
"@bazel/typescript": "0.1.x"
"@bazel/typescript": "0.1.x",
"@types/node": "6.0.84"
},
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.git"
}
}
}

View File

@ -5,6 +5,8 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// TODO(tbosch): figure out why we need this as it breaks node code within ngc-wrapped
/// <reference types="node" />
import * as ng from '@angular/compiler-cli';
import {BazelOptions, CachedFileLoader, CompilerHost, FileCache, FileLoader, UncachedFileLoader, constructManifest, debug, parseTsconfig, runAsWorker, runWorkerLoop} from '@bazel/typescript';
import * as fs from 'fs';

View File

@ -8,19 +8,20 @@
export {AotCompilerHost, AotCompilerHost as StaticReflectorHost, StaticReflector, StaticSymbol} from '@angular/compiler';
export {CodeGenerator} from './src/codegen';
export {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './src/compiler_host';
export {Extractor} from './src/extractor';
export * from '@angular/tsc-wrapped';
export {VERSION} from './src/version';
export {DiagnosticTemplateInfo, getTemplateExpressionDiagnostics, getExpressionScope} from './src/diagnostics/expression_diagnostics';
export {DiagnosticTemplateInfo, getExpressionScope, getTemplateExpressionDiagnostics} from './src/diagnostics/expression_diagnostics';
export {AstType, ExpressionDiagnosticsContext} from './src/diagnostics/expression_type';
export {getClassMembersFromDeclaration, getPipesTable, getSymbolQuery} from './src/diagnostics/typescript_symbols';
export {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './src/diagnostics/symbols';
export {getClassMembersFromDeclaration, getPipesTable, getSymbolQuery} from './src/diagnostics/typescript_symbols';
export {Extractor} from './src/extractor';
export {VERSION} from './src/version';
export * from './src/transformers/api';
export * from './src/transformers/entry_points';
export * from './src/perform_compile';
// TODO(tbosch): remove this once everyone is on transformers
export {CompilerOptions as AngularCompilerOptions} from './src/transformers/api';
// TODO(hansl): moving to Angular 4 need to update this API.
export {NgTools_InternalApi_NG_2 as __NGTOOLS_PRIVATE_API_2} from './src/ngtools_api';

View File

@ -15,7 +15,7 @@ import * as path from 'path';
import * as ts from 'typescript';
import * as assert from 'assert';
import {tsc} from '@angular/tsc-wrapped/src/tsc';
import {AngularCompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext} from '@angular/compiler-cli';
import {CompilerOptions, CodeGenerator, CompilerHostContext, NodeCompilerHostContext} from '@angular/compiler-cli';
/**
* Main method.
@ -89,7 +89,7 @@ function main() {
* Simple adaption of tsc-wrapped main to just run codegen with a CompilerHostContext
*/
function codegen(
config: {parsed: ts.ParsedCommandLine, ngOptions: AngularCompilerOptions},
config: {parsed: ts.ParsedCommandLine, ngOptions: CompilerOptions},
hostContextFactory: (host: ts.CompilerHost) => CompilerHostContext) {
const host = ts.createCompilerHost(config.parsed.options, true);

View File

@ -11,12 +11,12 @@
* Intended to be used in a build step.
*/
import * as compiler from '@angular/compiler';
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
import {readFileSync} from 'fs';
import * as ts from 'typescript';
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {CompilerOptions} from './transformers/api';
const GENERATED_META_FILES = /\.json$/;
@ -36,11 +36,11 @@ export interface CodeGeneratorI18nOptions {
missingTranslation: string|null;
}
// TODO(tbosch): remove this once G3 uses the transformer compiler!
export class CodeGenerator {
constructor(
private options: AngularCompilerOptions, private program: ts.Program,
public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
private ngCompilerHost: CompilerHost) {}
private options: CompilerOptions, private program: ts.Program, public host: ts.CompilerHost,
private compiler: compiler.AotCompiler, private ngCompilerHost: CompilerHost) {}
codegen(): Promise<string[]> {
return this.compiler
@ -67,7 +67,7 @@ export class CodeGenerator {
}
static create(
options: AngularCompilerOptions, i18nOptions: CodeGeneratorI18nOptions, program: ts.Program,
options: CompilerOptions, i18nOptions: CodeGeneratorI18nOptions, program: ts.Program,
tsCompilerHost: ts.CompilerHost, compilerHostContext?: CompilerHostContext,
ngCompilerHost?: CompilerHost): CodeGenerator {
if (!ngCompilerHost) {

View File

@ -7,11 +7,13 @@
*/
import {AotCompilerHost, StaticSymbol, UrlResolver, createOfflineCompileUrlResolver, syntaxError} from '@angular/compiler';
import {AngularCompilerOptions, CollectorOptions, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {CollectorOptions, MetadataCollector, ModuleMetadata} from './metadata/index';
import {CompilerOptions} from './transformers/api';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
const NODE_MODULES = '/node_modules/';
@ -34,8 +36,7 @@ export abstract class BaseAotCompilerHost<C extends BaseAotCompilerHostContext>
private flatModuleIndexRedirectNames = new Set<string>();
constructor(
protected program: ts.Program, protected options: AngularCompilerOptions,
protected context: C,
protected program: ts.Program, protected options: CompilerOptions, protected context: C,
protected metadataProvider: MetadataProvider = new MetadataCollector()) {}
abstract moduleNameToFileName(m: string, containingFile: string): string|null;
@ -230,6 +231,7 @@ export interface CompilerHostContext extends ts.ModuleResolutionHost {
assumeFileExists(fileName: string): void;
}
// TODO(tbosch): remove this once G3 uses the transformer compiler!
export class CompilerHost extends BaseAotCompilerHost<CompilerHostContext> {
protected basePath: string;
private moduleFileNames = new Map<string, string|null>();
@ -239,7 +241,7 @@ export class CompilerHost extends BaseAotCompilerHost<CompilerHostContext> {
private urlResolver: UrlResolver;
constructor(
program: ts.Program, options: AngularCompilerOptions, context: CompilerHostContext,
program: ts.Program, options: CompilerOptions, context: CompilerHostContext,
collectorOptions?: CollectorOptions,
metadataProvider: MetadataProvider = new MetadataCollector(collectorOptions)) {
super(program, options, context, metadataProvider);

View File

@ -15,11 +15,12 @@
import 'reflect-metadata';
import * as api from './transformers/api';
import {ParsedConfiguration} from './perform_compile';
import {mainSync, readCommandLineAndConfiguration} from './main';
import {main, readCommandLineAndConfiguration} from './main';
export function main(args: string[], consoleError: (msg: string) => void = console.error): number {
export function mainXi18n(
args: string[], consoleError: (msg: string) => void = console.error): number {
const config = readXi18nCommandLineAndConfiguration(args);
return mainSync(args, consoleError, config);
return main(args, consoleError, config);
}
function readXi18nCommandLineAndConfiguration(args: string[]): ParsedConfiguration {
@ -41,5 +42,5 @@ function readXi18nCommandLineAndConfiguration(args: string[]): ParsedConfigurati
// Entry point
if (require.main === module) {
const args = process.argv.slice(2);
process.exitCode = main(args);
}
process.exitCode = mainXi18n(args);
}

View File

@ -14,17 +14,17 @@
import 'reflect-metadata';
import * as compiler from '@angular/compiler';
import * as tsc from '@angular/tsc-wrapped';
import * as path from 'path';
import * as ts from 'typescript';
import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './compiler_host';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {CompilerOptions} from './transformers/api';
import {i18nExtract, i18nGetExtension, i18nSerialize} from './transformers/program';
export class Extractor {
constructor(
private options: tsc.AngularCompilerOptions, private ngExtractor: compiler.Extractor,
private options: CompilerOptions, private ngExtractor: compiler.Extractor,
public host: ts.CompilerHost, private ngCompilerHost: CompilerHost,
private program: ts.Program) {}
@ -47,7 +47,7 @@ export class Extractor {
getExtension(formatName: string): string { return i18nGetExtension(formatName); }
static create(
options: tsc.AngularCompilerOptions, program: ts.Program, tsCompilerHost: ts.CompilerHost,
options: CompilerOptions, program: ts.Program, tsCompilerHost: ts.CompilerHost,
locale?: string|null, compilerHostContext?: CompilerHostContext,
ngCompilerHost?: CompilerHost): Extractor {
if (!ngCompilerHost) {

View File

@ -14,11 +14,10 @@ Angular modules and Typescript as this will indirectly add a dependency
to the language service.
*/
export {AngularCompilerOptions} from '@angular/tsc-wrapped';
export {CompilerHost, CompilerHostContext, MetadataProvider, ModuleResolutionHostAdapter, NodeCompilerHostContext} from './compiler_host';
export {TypeChecker} from './diagnostics/check_types';
export {DiagnosticTemplateInfo, ExpressionDiagnostic, getExpressionDiagnostics, getExpressionScope, getTemplateExpressionDiagnostics} from './diagnostics/expression_diagnostics';
export {AstType, DiagnosticKind, ExpressionDiagnosticsContext, TypeDiagnostic} from './diagnostics/expression_type';
export {BuiltinType, DeclarationKind, Definition, Location, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './diagnostics/symbols';
export {getClassFromStaticSymbol, getClassMembers, getClassMembersFromDeclaration, getPipesTable, getSymbolQuery} from './diagnostics/typescript_symbols';
export {CompilerOptions} from './transformers/api';

View File

@ -11,7 +11,6 @@
import 'reflect-metadata';
import * as ts from 'typescript';
import * as tsc from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as tsickle from 'tsickle';
@ -21,29 +20,8 @@ import * as ngc from './transformers/entry_points';
import {exitCodeFromResult, performCompilation, readConfiguration, formatDiagnostics, Diagnostics, ParsedConfiguration, PerformCompilationResult} from './perform_compile';
import {performWatchCompilation, createPerformWatchHost} from './perform_watch';
import {isSyntaxError} from '@angular/compiler';
import {CodeGenerator} from './codegen';
// TODO(tbosch): remove this old entrypoint once we drop `disableTransformerPipeline`.
export function main(
args: string[], consoleError: (s: string) => void = console.error): Promise<number> {
let {project, rootNames, options, errors: configErrors, watch} =
readNgcCommandLineAndConfiguration(args);
if (configErrors.length) {
return Promise.resolve(reportErrorsAndExit(options, configErrors, consoleError));
}
if (watch) {
const result = watchMode(project, options, consoleError);
return Promise.resolve(reportErrorsAndExit({}, result.firstCompileResult, consoleError));
}
if (options.disableTransformerPipeline) {
return disabledTransformerPipelineNgcMain(args, consoleError);
}
const {diagnostics: compileDiags} =
performCompilation({rootNames, options, emitCallback: createEmitCallback(options)});
return Promise.resolve(reportErrorsAndExit(options, compileDiags, consoleError));
}
export function mainSync(
args: string[], consoleError: (s: string) => void = console.error,
config?: NgcParsedConfiguration): number {
let {project, rootNames, options, errors: configErrors, watch, emitFlags} =
@ -160,35 +138,8 @@ export function watchMode(
}, options, options => createEmitCallback(options)));
}
function disabledTransformerPipelineNgcMain(
args: string[], consoleError: (s: string) => void = console.error): Promise<number> {
const parsedArgs = require('minimist')(args);
const cliOptions = new tsc.NgcCliOptions(parsedArgs);
const project = parsedArgs.p || parsedArgs.project || '.';
return tsc.main(project, cliOptions, disabledTransformerPipelineCodegen)
.then(() => 0)
.catch(e => {
if (e instanceof tsc.UserError || isSyntaxError(e)) {
consoleError(e.message);
} else {
consoleError(e.stack);
}
return Promise.resolve(1);
});
}
function disabledTransformerPipelineCodegen(
ngOptions: api.CompilerOptions, cliOptions: tsc.NgcCliOptions, program: ts.Program,
host: ts.CompilerHost) {
if (ngOptions.enableSummariesForJit === undefined) {
// default to false
ngOptions.enableSummariesForJit = false;
}
return CodeGenerator.create(ngOptions, cliOptions, program, host).codegen();
}
// CLI entry point
if (require.main === module) {
const args = process.argv.slice(2);
process.exitCode = mainSync(args);
process.exitCode = main(args);
}

View File

@ -0,0 +1,89 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {CompilerOptions} from '../transformers/api';
import {CompilerHostAdapter, MetadataBundler} from './bundler';
import {privateEntriesToIndex} from './index_writer';
const DTS = /\.d\.ts$/;
const JS_EXT = /(\.js|)$/;
function createSyntheticIndexHost<H extends ts.CompilerHost>(
delegate: H, syntheticIndex: {name: string, content: string, metadata: string}): H {
const normalSyntheticIndexName = path.normalize(syntheticIndex.name);
const indexContent = syntheticIndex.content;
const indexMetadata = syntheticIndex.metadata;
const newHost = Object.create(delegate);
newHost.fileExists = (fileName: string): boolean => {
return path.normalize(fileName) == normalSyntheticIndexName || delegate.fileExists(fileName);
};
newHost.readFile = (fileName: string) => {
return path.normalize(fileName) == normalSyntheticIndexName ? indexContent :
delegate.readFile(fileName);
};
newHost.getSourceFile =
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) => {
if (path.normalize(fileName) == normalSyntheticIndexName) {
return ts.createSourceFile(fileName, indexContent, languageVersion, true);
}
return delegate.getSourceFile(fileName, languageVersion, onError);
};
newHost.writeFile =
(fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
delegate.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
if (fileName.match(DTS) && sourceFiles && sourceFiles.length == 1 &&
path.normalize(sourceFiles[0].fileName) == normalSyntheticIndexName) {
// If we are writing the synthetic index, write the metadata along side.
const metadataName = fileName.replace(DTS, '.metadata.json');
fs.writeFileSync(metadataName, indexMetadata, {encoding: 'utf8'});
}
};
return newHost;
}
export function createBundleIndexHost<H extends ts.CompilerHost>(
ngOptions: CompilerOptions, rootFiles: string[],
host: H): {host: H, indexName?: string, errors?: ts.Diagnostic[]} {
const files = rootFiles.filter(f => !DTS.test(f));
if (files.length != 1) {
return {
host,
errors: [{
file: null as any as ts.SourceFile,
start: null as any as number,
length: null as any as number,
messageText:
'Angular compiler option "flatModuleIndex" requires one and only one .ts file in the "files" field.',
category: ts.DiagnosticCategory.Error,
code: 0
}]
};
}
const file = files[0];
const indexModule = file.replace(/\.ts$/, '');
const bundler =
new MetadataBundler(indexModule, ngOptions.flatModuleId, new CompilerHostAdapter(host));
const metadataBundle = bundler.getMetadataBundle();
const metadata = JSON.stringify(metadataBundle.metadata);
const name =
path.join(path.dirname(indexModule), ngOptions.flatModuleOutFile !.replace(JS_EXT, '.ts'));
const libraryIndex = `./${path.basename(indexModule)}`;
const content = privateEntriesToIndex(libraryIndex, metadataBundle.privates);
host = createSyntheticIndexHost(host, {name, content, metadata});
return {host, indexName: name};
}

View File

@ -0,0 +1,628 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as path from 'path';
import * as ts from 'typescript';
import {MetadataCollector} from '../metadata/collector';
import {ClassMetadata, ConstructorMetadata, FunctionMetadata, MemberMetadata, MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataMap, MetadataObject, MetadataSymbolicExpression, MetadataSymbolicReferenceExpression, MetadataValue, MethodMetadata, ModuleExportMetadata, ModuleMetadata, VERSION, isClassMetadata, isConstructorMetadata, isFunctionMetadata, isInterfaceMetadata, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicExpression, isMethodMetadata} from '../metadata/schema';
// The character set used to produce private names.
const PRIVATE_NAME_CHARS = 'abcdefghijklmnopqrstuvwxyz';
interface Symbol {
module: string;
name: string;
// Produced by indirectly by exportAll() for symbols re-export another symbol.
exports?: Symbol;
// Produced by indirectly by exportAll() for symbols are re-exported by another symbol.
reexportedAs?: Symbol;
// Produced by canonicalizeSymbols() for all symbols. A symbol is private if it is not
// exported by the index.
isPrivate?: boolean;
// Produced by canonicalizeSymbols() for all symbols. This is the one symbol that
// respresents all other symbols and is the only symbol that, among all the re-exported
// aliases, whose fields can be trusted to contain the correct information.
// For private symbols this is the declaration symbol. For public symbols this is the
// symbol that is exported.
canonicalSymbol?: Symbol;
// Produced by canonicalizeSymbols() for all symbols. This the symbol that originally
// declared the value and should be used to fetch the value.
declaration?: Symbol;
// A symbol is referenced if it is exported from index or referenced by the value of
// a referenced symbol's value.
referenced?: boolean;
// A symbol is marked as a re-export the symbol was rexported from a module that is
// not part of the flat module bundle.
reexport?: boolean;
// Only valid for referenced canonical symbols. Produces by convertSymbols().
value?: MetadataEntry;
// Only valid for referenced private symbols. It is the name to use to import the symbol from
// the bundle index. Produce by assignPrivateNames();
privateName?: string;
}
export interface BundleEntries { [name: string]: MetadataEntry; }
export interface BundlePrivateEntry {
privateName: string;
name: string;
module: string;
}
export interface BundledModule {
metadata: ModuleMetadata;
privates: BundlePrivateEntry[];
}
export interface MetadataBundlerHost {
getMetadataFor(moduleName: string): ModuleMetadata|undefined;
}
type StaticsMetadata = {
[name: string]: MetadataValue | FunctionMetadata;
};
export class MetadataBundler {
private symbolMap = new Map<string, Symbol>();
private metadataCache = new Map<string, ModuleMetadata|undefined>();
private exports = new Map<string, Symbol[]>();
private rootModule: string;
private exported: Set<Symbol>;
constructor(
private root: string, private importAs: string|undefined, private host: MetadataBundlerHost) {
this.rootModule = `./${path.basename(root)}`;
}
getMetadataBundle(): BundledModule {
// Export the root module. This also collects the transitive closure of all values referenced by
// the exports.
const exportedSymbols = this.exportAll(this.rootModule);
this.canonicalizeSymbols(exportedSymbols);
// TODO: exports? e.g. a module re-exports a symbol from another bundle
const metadata = this.getEntries(exportedSymbols);
const privates = Array.from(this.symbolMap.values())
.filter(s => s.referenced && s.isPrivate)
.map(s => ({
privateName: s.privateName !,
name: s.declaration !.name,
module: s.declaration !.module
}));
const origins = Array.from(this.symbolMap.values())
.filter(s => s.referenced && !s.reexport)
.reduce<{[name: string]: string}>((p, s) => {
p[s.isPrivate ? s.privateName ! : s.name] = s.declaration !.module;
return p;
}, {});
const exports = this.getReExports(exportedSymbols);
return {
metadata: {
__symbolic: 'module',
version: VERSION,
exports: exports.length ? exports : undefined, metadata, origins,
importAs: this.importAs !
},
privates
};
}
static resolveModule(importName: string, from: string): string {
return resolveModule(importName, from);
}
private getMetadata(moduleName: string): ModuleMetadata|undefined {
let result = this.metadataCache.get(moduleName);
if (!result) {
if (moduleName.startsWith('.')) {
const fullModuleName = resolveModule(moduleName, this.root);
result = this.host.getMetadataFor(fullModuleName);
}
this.metadataCache.set(moduleName, result);
}
return result;
}
private exportAll(moduleName: string): Symbol[] {
const module = this.getMetadata(moduleName);
let result = this.exports.get(moduleName);
if (result) {
return result;
}
result = [];
const exportSymbol = (exportedSymbol: Symbol, exportAs: string) => {
const symbol = this.symbolOf(moduleName, exportAs);
result !.push(symbol);
exportedSymbol.reexportedAs = symbol;
symbol.exports = exportedSymbol;
};
// Export all the symbols defined in this module.
if (module && module.metadata) {
for (let key in module.metadata) {
const data = module.metadata[key];
if (isMetadataImportedSymbolReferenceExpression(data)) {
// This is a re-export of an imported symbol. Record this as a re-export.
const exportFrom = resolveModule(data.module, moduleName);
this.exportAll(exportFrom);
const symbol = this.symbolOf(exportFrom, data.name);
exportSymbol(symbol, key);
} else {
// Record that this symbol is exported by this module.
result.push(this.symbolOf(moduleName, key));
}
}
}
// Export all the re-exports from this module
if (module && module.exports) {
for (const exportDeclaration of module.exports) {
const exportFrom = resolveModule(exportDeclaration.from, moduleName);
// Record all the exports from the module even if we don't use it directly.
const exportedSymbols = this.exportAll(exportFrom);
if (exportDeclaration.export) {
// Re-export all the named exports from a module.
for (const exportItem of exportDeclaration.export) {
const name = typeof exportItem == 'string' ? exportItem : exportItem.name;
const exportAs = typeof exportItem == 'string' ? exportItem : exportItem.as;
const symbol = this.symbolOf(exportFrom, name);
if (exportedSymbols && exportedSymbols.length == 1 && exportedSymbols[0].reexport &&
exportedSymbols[0].name == '*') {
// This is a named export from a module we have no metadata about. Record the named
// export as a re-export.
symbol.reexport = true;
}
exportSymbol(this.symbolOf(exportFrom, name), exportAs);
}
} else {
// Re-export all the symbols from the module
const exportedSymbols = this.exportAll(exportFrom);
for (const exportedSymbol of exportedSymbols) {
const name = exportedSymbol.name;
exportSymbol(exportedSymbol, name);
}
}
}
}
if (!module) {
// If no metadata is found for this import then it is considered external to the
// library and should be recorded as a re-export in the final metadata if it is
// eventually re-exported.
const symbol = this.symbolOf(moduleName, '*');
symbol.reexport = true;
result.push(symbol);
}
this.exports.set(moduleName, result);
return result;
}
/**
* Fill in the canonicalSymbol which is the symbol that should be imported by factories.
* The canonical symbol is the one exported by the index file for the bundle or definition
* symbol for private symbols that are not exported by bundle index.
*/
private canonicalizeSymbols(exportedSymbols: Symbol[]) {
const symbols = Array.from(this.symbolMap.values());
this.exported = new Set(exportedSymbols);
symbols.forEach(this.canonicalizeSymbol, this);
}
private canonicalizeSymbol(symbol: Symbol) {
const rootExport = getRootExport(symbol);
const declaration = getSymbolDeclaration(symbol);
const isPrivate = !this.exported.has(rootExport);
const canonicalSymbol = isPrivate ? declaration : rootExport;
symbol.isPrivate = isPrivate;
symbol.declaration = declaration;
symbol.canonicalSymbol = canonicalSymbol;
symbol.reexport = declaration.reexport;
}
private getEntries(exportedSymbols: Symbol[]): BundleEntries {
const result: BundleEntries = {};
const exportedNames = new Set(exportedSymbols.map(s => s.name));
let privateName = 0;
function newPrivateName(): string {
while (true) {
let digits: string[] = [];
let index = privateName++;
let base = PRIVATE_NAME_CHARS;
while (!digits.length || index > 0) {
digits.unshift(base[index % base.length]);
index = Math.floor(index / base.length);
}
digits.unshift('\u0275');
const result = digits.join('');
if (!exportedNames.has(result)) return result;
}
}
exportedSymbols.forEach(symbol => this.convertSymbol(symbol));
const symbolsMap = new Map<string, string[]>();
Array.from(this.symbolMap.values()).forEach(symbol => {
if (symbol.referenced && !symbol.reexport) {
let name = symbol.name;
const declaredName = symbol.declaration !.name;
if (symbol.isPrivate && !symbol.privateName) {
name = newPrivateName();
symbol.privateName = name;
}
if (symbolsMap.has(declaredName)) {
const names = symbolsMap.get(declaredName);
names !.push(name);
} else {
symbolsMap.set(declaredName, [name]);
}
result[name] = symbol.value !;
}
});
// check for duplicated entries
symbolsMap.forEach((names: string[], declaredName: string) => {
if (names.length > 1) {
// prefer the export that uses the declared name (if any)
let reference = names.indexOf(declaredName);
if (reference === -1) {
reference = 0;
}
// keep one entry and replace the others by references
names.forEach((name: string, i: number) => {
if (i !== reference) {
result[name] = {__symbolic: 'reference', name: names[reference]};
}
});
}
});
return result;
}
private getReExports(exportedSymbols: Symbol[]): ModuleExportMetadata[] {
type ExportClause = {name: string, as: string}[];
const modules = new Map<string, ExportClause>();
const exportAlls = new Set<string>();
for (const symbol of exportedSymbols) {
if (symbol.reexport) {
// symbol.declaration is guarenteed to be defined during the phase this method is called.
const declaration = symbol.declaration !;
const module = declaration.module;
if (declaration !.name == '*') {
// Reexport all the symbols.
exportAlls.add(declaration.module);
} else {
// Re-export the symbol as the exported name.
let entry = modules.get(module);
if (!entry) {
entry = [];
modules.set(module, entry);
}
const as = symbol.name;
const name = declaration.name;
entry.push({name, as});
}
}
}
return [
...Array.from(exportAlls.values()).map(from => ({from})),
...Array.from(modules.entries()).map(([from, exports]) => ({export: exports, from}))
];
}
private convertSymbol(symbol: Symbol) {
// canonicalSymbol is ensured to be defined before this is called.
const canonicalSymbol = symbol.canonicalSymbol !;
if (!canonicalSymbol.referenced) {
canonicalSymbol.referenced = true;
// declaration is ensured to be definded before this method is called.
const declaration = canonicalSymbol.declaration !;
const module = this.getMetadata(declaration.module);
if (module) {
const value = module.metadata[declaration.name];
if (value && !declaration.name.startsWith('___')) {
canonicalSymbol.value = this.convertEntry(declaration.module, value);
}
}
}
}
private convertEntry(moduleName: string, value: MetadataEntry): MetadataEntry {
if (isClassMetadata(value)) {
return this.convertClass(moduleName, value);
}
if (isFunctionMetadata(value)) {
return this.convertFunction(moduleName, value);
}
if (isInterfaceMetadata(value)) {
return value;
}
return this.convertValue(moduleName, value);
}
private convertClass(moduleName: string, value: ClassMetadata): ClassMetadata {
return {
__symbolic: 'class',
arity: value.arity,
extends: this.convertExpression(moduleName, value.extends) !,
decorators:
value.decorators && value.decorators.map(d => this.convertExpression(moduleName, d) !),
members: this.convertMembers(moduleName, value.members !),
statics: value.statics && this.convertStatics(moduleName, value.statics)
};
}
private convertMembers(moduleName: string, members: MetadataMap): MetadataMap {
const result: MetadataMap = {};
for (const name in members) {
const value = members[name];
result[name] = value.map(v => this.convertMember(moduleName, v));
}
return result;
}
private convertMember(moduleName: string, member: MemberMetadata) {
const result: MemberMetadata = {__symbolic: member.__symbolic};
result.decorators =
member.decorators && member.decorators.map(d => this.convertExpression(moduleName, d) !);
if (isMethodMetadata(member)) {
(result as MethodMetadata).parameterDecorators = member.parameterDecorators &&
member.parameterDecorators.map(
d => d && d.map(p => this.convertExpression(moduleName, p) !));
if (isConstructorMetadata(member)) {
if (member.parameters) {
(result as ConstructorMetadata).parameters =
member.parameters.map(p => this.convertExpression(moduleName, p));
}
}
}
return result;
}
private convertStatics(moduleName: string, statics: StaticsMetadata): StaticsMetadata {
let result: StaticsMetadata = {};
for (const key in statics) {
const value = statics[key];
result[key] = isFunctionMetadata(value) ? this.convertFunction(moduleName, value) : value;
}
return result;
}
private convertFunction(moduleName: string, value: FunctionMetadata): FunctionMetadata {
return {
__symbolic: 'function',
parameters: value.parameters,
defaults: value.defaults && value.defaults.map(v => this.convertValue(moduleName, v)),
value: this.convertValue(moduleName, value.value)
};
}
private convertValue(moduleName: string, value: MetadataValue): MetadataValue {
if (isPrimitive(value)) {
return value;
}
if (isMetadataError(value)) {
return this.convertError(moduleName, value);
}
if (isMetadataSymbolicExpression(value)) {
return this.convertExpression(moduleName, value) !;
}
if (Array.isArray(value)) {
return value.map(v => this.convertValue(moduleName, v));
}
// Otherwise it is a metadata object.
const object = value as MetadataObject;
const result: MetadataObject = {};
for (const key in object) {
result[key] = this.convertValue(moduleName, object[key]);
}
return result;
}
private convertExpression(
moduleName: string, value: MetadataSymbolicExpression|MetadataError|null|
undefined): MetadataSymbolicExpression|MetadataError|undefined|null {
if (value) {
switch (value.__symbolic) {
case 'error':
return this.convertError(moduleName, value as MetadataError);
case 'reference':
return this.convertReference(moduleName, value as MetadataSymbolicReferenceExpression);
default:
return this.convertExpressionNode(moduleName, value);
}
}
return value;
}
private convertError(module: string, value: MetadataError): MetadataError {
return {
__symbolic: 'error',
message: value.message,
line: value.line,
character: value.character,
context: value.context, module
};
}
private convertReference(moduleName: string, value: MetadataSymbolicReferenceExpression):
MetadataSymbolicReferenceExpression|MetadataError|undefined {
const createReference = (symbol: Symbol): MetadataSymbolicReferenceExpression => {
const declaration = symbol.declaration !;
if (declaration.module.startsWith('.')) {
// Reference to a symbol defined in the module. Ensure it is converted then return a
// references to the final symbol.
this.convertSymbol(symbol);
return {
__symbolic: 'reference',
get name() {
// Resolved lazily because private names are assigned late.
const canonicalSymbol = symbol.canonicalSymbol !;
if (canonicalSymbol.isPrivate == null) {
throw Error('Invalid state: isPrivate was not initialized');
}
return canonicalSymbol.isPrivate ? canonicalSymbol.privateName ! : canonicalSymbol.name;
}
};
} else {
// The symbol was a re-exported symbol from another module. Return a reference to the
// original imported symbol.
return {__symbolic: 'reference', name: declaration.name, module: declaration.module};
}
};
if (isMetadataGlobalReferenceExpression(value)) {
const metadata = this.getMetadata(moduleName);
if (metadata && metadata.metadata && metadata.metadata[value.name]) {
// Reference to a symbol defined in the module
return createReference(this.canonicalSymbolOf(moduleName, value.name));
}
// If a reference has arguments, the arguments need to be converted.
if (value.arguments) {
return {
__symbolic: 'reference',
name: value.name,
arguments: value.arguments.map(a => this.convertValue(moduleName, a))
};
}
// Global references without arguments (such as to Math or JSON) are unmodified.
return value;
}
if (isMetadataImportedSymbolReferenceExpression(value)) {
// References to imported symbols are separated into two, references to bundled modules and
// references to modules external to the bundle. If the module reference is relative it is
// assumed to be in the bundle. If it is Global it is assumed to be outside the bundle.
// References to symbols outside the bundle are left unmodified. References to symbol inside
// the bundle need to be converted to a bundle import reference reachable from the bundle
// index.
if (value.module.startsWith('.')) {
// Reference is to a symbol defined inside the module. Convert the reference to a reference
// to the canonical symbol.
const referencedModule = resolveModule(value.module, moduleName);
const referencedName = value.name;
return createReference(this.canonicalSymbolOf(referencedModule, referencedName));
}
// Value is a reference to a symbol defined outside the module.
if (value.arguments) {
// If a reference has arguments the arguments need to be converted.
return {
__symbolic: 'reference',
name: value.name,
module: value.module,
arguments: value.arguments.map(a => this.convertValue(moduleName, a))
};
}
return value;
}
if (isMetadataModuleReferenceExpression(value)) {
// Cannot support references to bundled modules as the internal modules of a bundle are erased
// by the bundler.
if (value.module.startsWith('.')) {
return {
__symbolic: 'error',
message: 'Unsupported bundled module reference',
context: {module: value.module}
};
}
// References to unbundled modules are unmodified.
return value;
}
}
private convertExpressionNode(moduleName: string, value: MetadataSymbolicExpression):
MetadataSymbolicExpression {
const result: MetadataSymbolicExpression = {__symbolic: value.__symbolic};
for (const key in value) {
(result as any)[key] = this.convertValue(moduleName, (value as any)[key]);
}
return result;
}
private symbolOf(module: string, name: string): Symbol {
const symbolKey = `${module}:${name}`;
let symbol = this.symbolMap.get(symbolKey);
if (!symbol) {
symbol = {module, name};
this.symbolMap.set(symbolKey, symbol);
}
return symbol;
}
private canonicalSymbolOf(module: string, name: string): Symbol {
// Ensure the module has been seen.
this.exportAll(module);
const symbol = this.symbolOf(module, name);
if (!symbol.canonicalSymbol) {
this.canonicalizeSymbol(symbol);
}
return symbol;
}
}
export class CompilerHostAdapter implements MetadataBundlerHost {
private collector = new MetadataCollector();
constructor(private host: ts.CompilerHost) {}
getMetadataFor(fileName: string): ModuleMetadata|undefined {
const sourceFile = this.host.getSourceFile(fileName + '.ts', ts.ScriptTarget.Latest);
return this.collector.getMetadata(sourceFile);
}
}
function resolveModule(importName: string, from: string): string {
if (importName.startsWith('.') && from) {
let normalPath = path.normalize(path.join(path.dirname(from), importName));
if (!normalPath.startsWith('.') && from.startsWith('.')) {
// path.normalize() preserves leading '../' but not './'. This adds it back.
normalPath = `.${path.sep}${normalPath}`;
}
// Replace windows path delimiters with forward-slashes. Otherwise the paths are not
// TypeScript compatible when building the bundle.
return normalPath.replace(/\\/g, '/');
}
return importName;
}
function isPrimitive(o: any): o is boolean|string|number {
return o === null || (typeof o !== 'function' && typeof o !== 'object');
}
function getRootExport(symbol: Symbol): Symbol {
return symbol.reexportedAs ? getRootExport(symbol.reexportedAs) : symbol;
}
function getSymbolDeclaration(symbol: Symbol): Symbol {
return symbol.exports ? getSymbolDeclaration(symbol.exports) : symbol;
}

View File

@ -0,0 +1,774 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {Evaluator, errorSymbol} from './evaluator';
import {ClassMetadata, ConstructorMetadata, FunctionMetadata, InterfaceMetadata, MemberMetadata, MetadataEntry, MetadataError, MetadataMap, MetadataSymbolicBinaryExpression, MetadataSymbolicCallExpression, MetadataSymbolicExpression, MetadataSymbolicIfExpression, MetadataSymbolicIndexExpression, MetadataSymbolicPrefixExpression, MetadataSymbolicReferenceExpression, MetadataSymbolicSelectExpression, MetadataSymbolicSpreadExpression, MetadataValue, MethodMetadata, ModuleExportMetadata, ModuleMetadata, VERSION, isClassMetadata, isConstructorMetadata, isFunctionMetadata, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataSymbolicExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSelectExpression, isMethodMetadata} from './schema';
import {Symbols} from './symbols';
// In TypeScript 2.1 these flags moved
// These helpers work for both 2.0 and 2.1.
const isExport = (ts as any).ModifierFlags ?
((node: ts.Node) =>
!!((ts as any).getCombinedModifierFlags(node) & (ts as any).ModifierFlags.Export)) :
((node: ts.Node) => !!((node.flags & (ts as any).NodeFlags.Export)));
const isStatic = (ts as any).ModifierFlags ?
((node: ts.Node) =>
!!((ts as any).getCombinedModifierFlags(node) & (ts as any).ModifierFlags.Static)) :
((node: ts.Node) => !!((node.flags & (ts as any).NodeFlags.Static)));
/**
* A set of collector options to use when collecting metadata.
*/
export interface CollectorOptions {
/**
* Version of the metadata to collect.
*/
version?: number;
/**
* Collect a hidden field "$quoted$" in objects literals that record when the key was quoted in
* the source.
*/
quotedNames?: boolean;
/**
* Do not simplify invalid expressions.
*/
verboseInvalidExpression?: boolean;
/**
* An expression substitution callback.
*/
substituteExpression?: (value: MetadataValue, node: ts.Node) => MetadataValue;
}
/**
* Collect decorator metadata from a TypeScript module.
*/
export class MetadataCollector {
constructor(private options: CollectorOptions = {}) {}
/**
* 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, strict: boolean = false,
substituteExpression?: (value: MetadataValue, node: ts.Node) => MetadataValue): ModuleMetadata
|undefined {
const locals = new Symbols(sourceFile);
const nodeMap =
new Map<MetadataValue|ClassMetadata|InterfaceMetadata|FunctionMetadata, ts.Node>();
const composedSubstituter = substituteExpression && this.options.substituteExpression ?
(value: MetadataValue, node: ts.Node) =>
this.options.substituteExpression !(substituteExpression(value, node), node) :
substituteExpression;
const evaluatorOptions = substituteExpression ?
{...this.options, substituteExpression: composedSubstituter} :
this.options;
let metadata: {[name: string]: MetadataValue | ClassMetadata | FunctionMetadata}|undefined;
const evaluator = new Evaluator(locals, nodeMap, evaluatorOptions, (name, value) => {
if (!metadata) metadata = {};
metadata[name] = value;
});
let exports: ModuleExportMetadata[]|undefined = undefined;
function objFromDecorator(decoratorNode: ts.Decorator): MetadataSymbolicExpression {
return <MetadataSymbolicExpression>evaluator.evaluateNode(decoratorNode.expression);
}
function recordEntry<T extends MetadataEntry>(entry: T, node: ts.Node): T {
nodeMap.set(entry, node);
return entry;
}
function errorSym(
message: string, node?: ts.Node, context?: {[name: string]: string}): MetadataError {
return errorSymbol(message, node, context, sourceFile);
}
function maybeGetSimpleFunction(
functionDeclaration: ts.FunctionDeclaration |
ts.MethodDeclaration): {func: FunctionMetadata, name: string}|undefined {
if (functionDeclaration.name && functionDeclaration.name.kind == ts.SyntaxKind.Identifier) {
const nameNode = <ts.Identifier>functionDeclaration.name;
const functionName = nameNode.text;
const functionBody = functionDeclaration.body;
if (functionBody && functionBody.statements.length == 1) {
const statement = functionBody.statements[0];
if (statement.kind === ts.SyntaxKind.ReturnStatement) {
const returnStatement = <ts.ReturnStatement>statement;
if (returnStatement.expression) {
const func: FunctionMetadata = {
__symbolic: 'function',
parameters: namesOf(functionDeclaration.parameters),
value: evaluator.evaluateNode(returnStatement.expression)
};
if (functionDeclaration.parameters.some(p => p.initializer != null)) {
func.defaults = functionDeclaration.parameters.map(
p => p.initializer && evaluator.evaluateNode(p.initializer));
}
return recordEntry({func, name: functionName}, functionDeclaration);
}
}
}
}
}
function classMetadataOf(classDeclaration: ts.ClassDeclaration): ClassMetadata {
const result: ClassMetadata = {__symbolic: 'class'};
function getDecorators(decorators: ts.Decorator[] | undefined): MetadataSymbolicExpression[]|
undefined {
if (decorators && decorators.length)
return decorators.map(decorator => objFromDecorator(decorator));
return undefined;
}
function referenceFrom(node: ts.Node): MetadataSymbolicReferenceExpression|MetadataError|
MetadataSymbolicSelectExpression {
const result = evaluator.evaluateNode(node);
if (isMetadataError(result) || isMetadataSymbolicReferenceExpression(result) ||
isMetadataSymbolicSelectExpression(result)) {
return result;
} else {
return errorSym('Symbol reference expected', node);
}
}
// Add class parents
if (classDeclaration.heritageClauses) {
classDeclaration.heritageClauses.forEach((hc) => {
if (hc.token === ts.SyntaxKind.ExtendsKeyword && hc.types) {
hc.types.forEach(type => result.extends = referenceFrom(type.expression));
}
});
}
// Add arity if the type is generic
const typeParameters = classDeclaration.typeParameters;
if (typeParameters && typeParameters.length) {
result.arity = typeParameters.length;
}
// Add class decorators
if (classDeclaration.decorators) {
result.decorators = getDecorators(classDeclaration.decorators);
}
// member decorators
let members: MetadataMap|null = null;
function recordMember(name: string, metadata: MemberMetadata) {
if (!members) members = {};
const data = members.hasOwnProperty(name) ? members[name] : [];
data.push(metadata);
members[name] = data;
}
// static member
let statics: {[name: string]: MetadataValue | FunctionMetadata}|null = null;
function recordStaticMember(name: string, value: MetadataValue | FunctionMetadata) {
if (!statics) statics = {};
statics[name] = value;
}
for (const member of classDeclaration.members) {
let isConstructor = false;
switch (member.kind) {
case ts.SyntaxKind.Constructor:
case ts.SyntaxKind.MethodDeclaration:
isConstructor = member.kind === ts.SyntaxKind.Constructor;
const method = <ts.MethodDeclaration|ts.ConstructorDeclaration>member;
if (isStatic(method)) {
const maybeFunc = maybeGetSimpleFunction(<ts.MethodDeclaration>method);
if (maybeFunc) {
recordStaticMember(maybeFunc.name, maybeFunc.func);
}
continue;
}
const methodDecorators = getDecorators(method.decorators);
const parameters = method.parameters;
const parameterDecoratorData:
((MetadataSymbolicExpression | MetadataError)[] | undefined)[] = [];
const parametersData:
(MetadataSymbolicReferenceExpression | MetadataError |
MetadataSymbolicSelectExpression | null)[] = [];
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) {
if (parameter.type) {
parametersData.push(referenceFrom(parameter.type));
} else {
parametersData.push(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;
}
if (!isMetadataError(name)) {
recordMember(name, data);
}
break;
case ts.SyntaxKind.PropertyDeclaration:
case ts.SyntaxKind.GetAccessor:
case ts.SyntaxKind.SetAccessor:
const property = <ts.PropertyDeclaration>member;
if (isStatic(property)) {
const name = evaluator.nameOf(property.name);
if (!isMetadataError(name)) {
if (property.initializer) {
const value = evaluator.evaluateNode(property.initializer);
recordStaticMember(name, value);
} else {
recordStaticMember(name, errorSym('Variable not initialized', property.name));
}
}
}
const propertyDecorators = getDecorators(property.decorators);
if (propertyDecorators) {
const name = evaluator.nameOf(property.name);
if (!isMetadataError(name)) {
recordMember(name, {__symbolic: 'property', decorators: propertyDecorators});
}
}
break;
}
}
if (members) {
result.members = members;
}
if (statics) {
result.statics = statics;
}
return recordEntry(result, classDeclaration);
}
// Collect all exported symbols from an exports clause.
const exportMap = new Map<string, string>();
ts.forEachChild(sourceFile, node => {
switch (node.kind) {
case ts.SyntaxKind.ExportDeclaration:
const exportDeclaration = <ts.ExportDeclaration>node;
const {moduleSpecifier, exportClause} = exportDeclaration;
if (!moduleSpecifier) {
// If there is a module specifier there is also an exportClause
exportClause !.elements.forEach(spec => {
const exportedAs = spec.name.text;
const name = (spec.propertyName || spec.name).text;
exportMap.set(name, exportedAs);
});
}
}
});
const isExportedIdentifier = (identifier?: ts.Identifier) =>
identifier && exportMap.has(identifier.text);
const isExported =
(node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.TypeAliasDeclaration |
ts.InterfaceDeclaration | ts.EnumDeclaration) =>
isExport(node) || isExportedIdentifier(node.name);
const exportedIdentifierName = (identifier?: ts.Identifier) =>
identifier && (exportMap.get(identifier.text) || identifier.text);
const exportedName =
(node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.InterfaceDeclaration |
ts.TypeAliasDeclaration | ts.EnumDeclaration) => exportedIdentifierName(node.name);
// Predeclare classes and functions
ts.forEachChild(sourceFile, node => {
switch (node.kind) {
case ts.SyntaxKind.ClassDeclaration:
const classDeclaration = <ts.ClassDeclaration>node;
if (classDeclaration.name) {
const className = classDeclaration.name.text;
if (isExported(classDeclaration)) {
locals.define(
className, {__symbolic: 'reference', name: exportedName(classDeclaration)});
} else {
locals.define(
className, errorSym('Reference to non-exported class', node, {className}));
}
}
break;
case ts.SyntaxKind.InterfaceDeclaration:
const interfaceDeclaration = <ts.InterfaceDeclaration>node;
if (interfaceDeclaration.name) {
const interfaceName = interfaceDeclaration.name.text;
// All references to interfaces should be converted to references to `any`.
locals.define(interfaceName, {__symbolic: 'reference', name: 'any'});
}
break;
case ts.SyntaxKind.FunctionDeclaration:
const functionDeclaration = <ts.FunctionDeclaration>node;
if (!isExported(functionDeclaration)) {
// Report references to this function as an error.
const nameNode = functionDeclaration.name;
if (nameNode && nameNode.text) {
locals.define(
nameNode.text,
errorSym(
'Reference to a non-exported function', nameNode, {name: nameNode.text}));
}
}
break;
}
});
ts.forEachChild(sourceFile, node => {
switch (node.kind) {
case ts.SyntaxKind.ExportDeclaration:
// Record export declarations
const exportDeclaration = <ts.ExportDeclaration>node;
const {moduleSpecifier, exportClause} = exportDeclaration;
if (!moduleSpecifier) {
// no module specifier -> export {propName as name};
if (exportClause) {
exportClause.elements.forEach(spec => {
const name = spec.name.text;
// If the symbol was not already exported, export a reference since it is a
// reference to an import
if (!metadata || !metadata[name]) {
const propNode = spec.propertyName || spec.name;
const value: MetadataValue = evaluator.evaluateNode(propNode);
if (!metadata) metadata = {};
metadata[name] = recordEntry(value, node);
}
});
}
}
if (moduleSpecifier && moduleSpecifier.kind == ts.SyntaxKind.StringLiteral) {
// Ignore exports that don't have string literals as exports.
// This is allowed by the syntax but will be flagged as an error by the type checker.
const from = (<ts.StringLiteral>moduleSpecifier).text;
const moduleExport: ModuleExportMetadata = {from};
if (exportClause) {
moduleExport.export = exportClause.elements.map(
spec => spec.propertyName ? {name: spec.propertyName.text, as: spec.name.text} :
spec.name.text);
}
if (!exports) exports = [];
exports.push(moduleExport);
}
break;
case ts.SyntaxKind.ClassDeclaration:
const classDeclaration = <ts.ClassDeclaration>node;
if (classDeclaration.name) {
if (isExported(classDeclaration)) {
const name = exportedName(classDeclaration);
if (name) {
if (!metadata) metadata = {};
metadata[name] = classMetadataOf(classDeclaration);
}
}
}
// Otherwise don't record metadata for the class.
break;
case ts.SyntaxKind.TypeAliasDeclaration:
const typeDeclaration = <ts.TypeAliasDeclaration>node;
if (typeDeclaration.name && isExported(typeDeclaration)) {
const name = exportedName(typeDeclaration);
if (name) {
if (!metadata) metadata = {};
metadata[name] = {__symbolic: 'interface'};
}
}
break;
case ts.SyntaxKind.InterfaceDeclaration:
const interfaceDeclaration = <ts.InterfaceDeclaration>node;
if (interfaceDeclaration.name && isExported(interfaceDeclaration)) {
const name = exportedName(interfaceDeclaration);
if (name) {
if (!metadata) metadata = {};
metadata[name] = {__symbolic: 'interface'};
}
}
break;
case ts.SyntaxKind.FunctionDeclaration:
// Record functions that return a single value. Record the parameter
// names substitution will be performed by the StaticReflector.
const functionDeclaration = <ts.FunctionDeclaration>node;
if (isExported(functionDeclaration) && functionDeclaration.name) {
const name = exportedName(functionDeclaration);
const maybeFunc = maybeGetSimpleFunction(functionDeclaration);
if (name) {
if (!metadata) metadata = {};
metadata[name] =
maybeFunc ? recordEntry(maybeFunc.func, node) : {__symbolic: 'function'};
}
}
break;
case ts.SyntaxKind.EnumDeclaration:
const enumDeclaration = <ts.EnumDeclaration>node;
if (isExported(enumDeclaration)) {
const enumValueHolder: {[name: string]: MetadataValue} = {};
const enumName = exportedName(enumDeclaration);
let nextDefaultValue: MetadataValue = 0;
let writtenMembers = 0;
for (const member of enumDeclaration.members) {
let enumValue: MetadataValue;
if (!member.initializer) {
enumValue = nextDefaultValue;
} else {
enumValue = evaluator.evaluateNode(member.initializer);
}
let name: string|undefined = undefined;
if (member.name.kind == ts.SyntaxKind.Identifier) {
const identifier = <ts.Identifier>member.name;
name = identifier.text;
enumValueHolder[name] = enumValue;
writtenMembers++;
}
if (typeof enumValue === 'number') {
nextDefaultValue = enumValue + 1;
} else if (name) {
nextDefaultValue = {
__symbolic: 'binary',
operator: '+',
left: {
__symbolic: 'select',
expression: recordEntry({__symbolic: 'reference', name: enumName}, node), name
}
};
} else {
nextDefaultValue =
recordEntry(errorSym('Unsuppported enum member name', member.name), node);
}
}
if (writtenMembers) {
if (enumName) {
if (!metadata) metadata = {};
metadata[enumName] = recordEntry(enumValueHolder, node);
}
}
}
break;
case ts.SyntaxKind.VariableStatement:
const variableStatement = <ts.VariableStatement>node;
for (const variableDeclaration of variableStatement.declarationList.declarations) {
if (variableDeclaration.name.kind == ts.SyntaxKind.Identifier) {
const nameNode = <ts.Identifier>variableDeclaration.name;
let varValue: MetadataValue;
if (variableDeclaration.initializer) {
varValue = evaluator.evaluateNode(variableDeclaration.initializer);
} else {
varValue = recordEntry(errorSym('Variable not initialized', nameNode), nameNode);
}
let exported = false;
if (isExport(variableStatement) || isExport(variableDeclaration) ||
isExportedIdentifier(nameNode)) {
const name = exportedIdentifierName(nameNode);
if (name) {
if (!metadata) metadata = {};
metadata[name] = recordEntry(varValue, node);
}
exported = true;
}
if (typeof varValue == 'string' || typeof varValue == 'number' ||
typeof varValue == 'boolean') {
locals.define(nameNode.text, varValue);
if (exported) {
locals.defineReference(
nameNode.text, {__symbolic: 'reference', name: nameNode.text});
}
} else if (!exported) {
if (varValue && !isMetadataError(varValue)) {
locals.define(nameNode.text, recordEntry(varValue, node));
} else {
locals.define(
nameNode.text,
recordEntry(
errorSym('Reference to a local symbol', nameNode, {name: nameNode.text}),
node));
}
}
} else {
// Destructuring (or binding) declarations are not supported,
// var {<identifier>[, <identifier>]+} = <expression>;
// or
// var [<identifier>[, <identifier}+] = <expression>;
// are not supported.
const report: (nameNode: ts.Node) => void = (nameNode: ts.Node) => {
switch (nameNode.kind) {
case ts.SyntaxKind.Identifier:
const name = <ts.Identifier>nameNode;
const varValue = errorSym('Destructuring not supported', name);
locals.define(name.text, varValue);
if (isExport(node)) {
if (!metadata) metadata = {};
metadata[name.text] = varValue;
}
break;
case ts.SyntaxKind.BindingElement:
const bindingElement = <ts.BindingElement>nameNode;
report(bindingElement.name);
break;
case ts.SyntaxKind.ObjectBindingPattern:
case ts.SyntaxKind.ArrayBindingPattern:
const bindings = <ts.BindingPattern>nameNode;
(bindings as any).elements.forEach(report);
break;
}
};
report(variableDeclaration.name);
}
}
break;
}
});
if (metadata || exports) {
if (!metadata)
metadata = {};
else if (strict) {
validateMetadata(sourceFile, nodeMap, metadata);
}
const result: ModuleMetadata = {
__symbolic: 'module',
version: this.options.version || VERSION, metadata
};
if (exports) result.exports = exports;
return result;
}
}
}
// This will throw if the metadata entry given contains an error node.
function validateMetadata(
sourceFile: ts.SourceFile, nodeMap: Map<MetadataEntry, ts.Node>,
metadata: {[name: string]: MetadataEntry}) {
let locals: Set<string> = new Set(['Array', 'Object', 'Set', 'Map', 'string', 'number', 'any']);
function validateExpression(
expression: MetadataValue | MetadataSymbolicExpression | MetadataError) {
if (!expression) {
return;
} else if (Array.isArray(expression)) {
expression.forEach(validateExpression);
} else if (typeof expression === 'object' && !expression.hasOwnProperty('__symbolic')) {
Object.getOwnPropertyNames(expression).forEach(v => validateExpression((<any>expression)[v]));
} else if (isMetadataError(expression)) {
reportError(expression);
} else if (isMetadataGlobalReferenceExpression(expression)) {
if (!locals.has(expression.name)) {
const reference = <MetadataValue>metadata[expression.name];
if (reference) {
validateExpression(reference);
}
}
} else if (isFunctionMetadata(expression)) {
validateFunction(<any>expression);
} else if (isMetadataSymbolicExpression(expression)) {
switch (expression.__symbolic) {
case 'binary':
const binaryExpression = <MetadataSymbolicBinaryExpression>expression;
validateExpression(binaryExpression.left);
validateExpression(binaryExpression.right);
break;
case 'call':
case 'new':
const callExpression = <MetadataSymbolicCallExpression>expression;
validateExpression(callExpression.expression);
if (callExpression.arguments) callExpression.arguments.forEach(validateExpression);
break;
case 'index':
const indexExpression = <MetadataSymbolicIndexExpression>expression;
validateExpression(indexExpression.expression);
validateExpression(indexExpression.index);
break;
case 'pre':
const prefixExpression = <MetadataSymbolicPrefixExpression>expression;
validateExpression(prefixExpression.operand);
break;
case 'select':
const selectExpression = <MetadataSymbolicSelectExpression>expression;
validateExpression(selectExpression.expression);
break;
case 'spread':
const spreadExpression = <MetadataSymbolicSpreadExpression>expression;
validateExpression(spreadExpression.expression);
break;
case 'if':
const ifExpression = <MetadataSymbolicIfExpression>expression;
validateExpression(ifExpression.condition);
validateExpression(ifExpression.elseExpression);
validateExpression(ifExpression.thenExpression);
break;
}
}
}
function validateMember(classData: ClassMetadata, member: MemberMetadata) {
if (member.decorators) {
member.decorators.forEach(validateExpression);
}
if (isMethodMetadata(member) && member.parameterDecorators) {
member.parameterDecorators.forEach(validateExpression);
}
// Only validate parameters of classes for which we know that are used with our DI
if (classData.decorators && isConstructorMetadata(member) && member.parameters) {
member.parameters.forEach(validateExpression);
}
}
function validateClass(classData: ClassMetadata) {
if (classData.decorators) {
classData.decorators.forEach(validateExpression);
}
if (classData.members) {
Object.getOwnPropertyNames(classData.members)
.forEach(name => classData.members ![name].forEach((m) => validateMember(classData, m)));
}
if (classData.statics) {
Object.getOwnPropertyNames(classData.statics).forEach(name => {
const staticMember = classData.statics ![name];
if (isFunctionMetadata(staticMember)) {
validateExpression(staticMember.value);
} else {
validateExpression(staticMember);
}
});
}
}
function validateFunction(functionDeclaration: FunctionMetadata) {
if (functionDeclaration.value) {
const oldLocals = locals;
if (functionDeclaration.parameters) {
locals = new Set(oldLocals.values());
if (functionDeclaration.parameters)
functionDeclaration.parameters.forEach(n => locals.add(n));
}
validateExpression(functionDeclaration.value);
locals = oldLocals;
}
}
function shouldReportNode(node: ts.Node | undefined) {
if (node) {
const nodeStart = node.getStart();
return !(
node.pos != nodeStart &&
sourceFile.text.substring(node.pos, nodeStart).indexOf('@dynamic') >= 0);
}
return true;
}
function reportError(error: MetadataError) {
const node = nodeMap.get(error);
if (shouldReportNode(node)) {
const lineInfo = error.line != undefined ?
error.character != undefined ? `:${error.line + 1}:${error.character + 1}` :
`:${error.line + 1}` :
'';
throw new Error(
`${sourceFile.fileName}${lineInfo}: Metadata collected contains an error that will be reported at runtime: ${expandedMessage(error)}.\n ${JSON.stringify(error)}`);
}
}
Object.getOwnPropertyNames(metadata).forEach(name => {
const entry = metadata[name];
try {
if (isClassMetadata(entry)) {
validateClass(entry);
}
} catch (e) {
const node = nodeMap.get(entry);
if (shouldReportNode(node)) {
if (node) {
const {line, character} = sourceFile.getLineAndCharacterOfPosition(node.getStart());
throw new Error(
`${sourceFile.fileName}:${line + 1}:${character + 1}: Error encountered in metadata generated for exported symbol '${name}': \n ${e.message}`);
}
throw new Error(
`Error encountered in metadata generated for exported symbol ${name}: \n ${e.message}`);
}
}
});
}
// Collect parameter names from a function.
function namesOf(parameters: ts.NodeArray<ts.ParameterDeclaration>): string[] {
const result: string[] = [];
function addNamesOf(name: ts.Identifier | ts.BindingPattern) {
if (name.kind == ts.SyntaxKind.Identifier) {
const identifier = <ts.Identifier>name;
result.push(identifier.text);
} else {
const bindingPattern = <ts.BindingPattern>name;
for (const element of bindingPattern.elements) {
const name = (element as any).name;
if (name) {
addNamesOf(name);
}
}
}
}
for (const parameter of parameters) {
addNamesOf(parameter.name);
}
return result;
}
function expandedMessage(error: any): string {
switch (error.message) {
case 'Reference to non-exported class':
if (error.context && error.context.className) {
return `Reference to a non-exported class ${error.context.className}. Consider exporting the class`;
}
break;
case 'Variable not initialized':
return 'Only initialized variables and constants can be referenced because the value of this variable is needed by the template compiler';
case 'Destructuring not supported':
return 'Referencing an exported destructured variable or constant is not supported by the template compiler. Consider simplifying this to avoid destructuring';
case 'Could not resolve type':
if (error.context && error.context.typeName) {
return `Could not resolve type ${error.context.typeName}`;
}
break;
case 'Function call not supported':
let prefix =
error.context && error.context.name ? `Calling function '${error.context.name}', f` : 'F';
return prefix +
'unction calls are not supported. Consider replacing the function or lambda with a reference to an exported function';
case 'Reference to a local symbol':
if (error.context && error.context.name) {
return `Reference to a local (non-exported) symbol '${error.context.name}'. Consider exporting the symbol`;
}
}
return error.message;
}

View File

@ -0,0 +1,680 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {CollectorOptions} from './collector';
import {MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataSymbolicCallExpression, MetadataValue, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicReferenceExpression, isMetadataSymbolicSpreadExpression} from './schema';
import {Symbols} from './symbols';
// In TypeScript 2.1 the spread element kind was renamed.
const spreadElementSyntaxKind: ts.SyntaxKind =
(ts.SyntaxKind as any).SpreadElement || (ts.SyntaxKind as any).SpreadElementExpression;
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));
}
export 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'
}
function getSourceFileOfNode(node: ts.Node | undefined): ts.SourceFile {
while (node && node.kind != ts.SyntaxKind.SourceFile) {
node = node.parent;
}
return <ts.SourceFile>node;
}
/* @internal */
export function errorSymbol(
message: string, node?: ts.Node, context?: {[name: string]: string},
sourceFile?: ts.SourceFile): MetadataError {
let result: MetadataError|undefined = undefined;
if (node) {
sourceFile = sourceFile || getSourceFileOfNode(node);
if (sourceFile) {
const {line, character} =
ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile));
result = {__symbolic: 'error', message, line, character};
}
}
if (!result) {
result = {__symbolic: 'error', message};
}
if (context) {
result.context = context;
}
return result;
}
/**
* Produce a symbolic representation of an expression folding values into their final value when
* possible.
*/
export class Evaluator {
constructor(
private symbols: Symbols, private nodeMap: Map<MetadataEntry, ts.Node>,
private options: CollectorOptions = {},
private recordExport?: (name: string, value: MetadataValue) => void) {}
nameOf(node: ts.Node|undefined): string|MetadataError {
if (node && node.kind == ts.SyntaxKind.Identifier) {
return (<ts.Identifier>node).text;
}
const result = node && this.evaluateNode(node);
if (isMetadataError(result) || typeof result === 'string') {
return result;
} else {
return errorSymbol(
'Name expected', node, {received: (node && node.getText()) || '<missing>'});
}
}
/**
* 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|undefined, 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') &&
arrayOrEmpty(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') &&
arrayOrEmpty(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:
case ts.SyntaxKind.TemplateHead:
case ts.SyntaxKind.TemplateMiddle:
case ts.SyntaxKind.TemplateTail:
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);
default:
return false;
}
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 identifier = <ts.Identifier>node;
let reference = this.symbols.resolve(identifier.text);
if (reference !== undefined && isPrimitive(reference)) {
return true;
}
break;
case ts.SyntaxKind.TemplateExpression:
const templateExpression = <ts.TemplateExpression>node;
return templateExpression.templateSpans.every(
span => this.isFoldableWorker(span.expression, folding));
}
}
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, preferReference?: boolean): MetadataValue {
const t = this;
let error: MetadataError|undefined;
function recordEntry(entry: MetadataValue, node: ts.Node): MetadataValue {
if (t.options.substituteExpression) {
const newEntry = t.options.substituteExpression(entry, node);
if (t.recordExport && newEntry != entry && isMetadataGlobalReferenceExpression(newEntry)) {
t.recordExport(newEntry.name, entry);
}
entry = newEntry;
}
t.nodeMap.set(entry, node);
return entry;
}
function isFoldableError(value: any): value is MetadataError {
return !t.options.verboseInvalidExpression && isMetadataError(value);
}
const resolveName = (name: string, preferReference?: boolean): MetadataValue => {
const reference = this.symbols.resolve(name, preferReference);
if (reference === undefined) {
// Encode as a global reference. StaticReflector will check the reference.
return recordEntry({__symbolic: 'reference', name}, node);
}
return reference;
};
switch (node.kind) {
case ts.SyntaxKind.ObjectLiteralExpression:
let obj: {[name: string]: any} = {};
let quoted: string[] = [];
ts.forEachChild(node, child => {
switch (child.kind) {
case ts.SyntaxKind.ShorthandPropertyAssignment:
case ts.SyntaxKind.PropertyAssignment:
const assignment = <ts.PropertyAssignment|ts.ShorthandPropertyAssignment>child;
if (assignment.name.kind == ts.SyntaxKind.StringLiteral) {
const name = (assignment.name as ts.StringLiteral).text;
quoted.push(name);
}
const propertyName = this.nameOf(assignment.name);
if (isFoldableError(propertyName)) {
error = propertyName;
return true;
}
const propertyValue = isPropertyAssignment(assignment) ?
this.evaluateNode(assignment.initializer, /* preferReference */ true) :
resolveName(propertyName, /* preferReference */ true);
if (isFoldableError(propertyValue)) {
error = propertyValue;
return true; // Stop the forEachChild.
} else {
obj[<string>propertyName] = isPropertyAssignment(assignment) ?
recordEntry(propertyValue, assignment.initializer) :
propertyValue;
}
}
});
if (error) return error;
if (this.options.quotedNames && quoted.length) {
obj['$quoted$'] = quoted;
}
return recordEntry(obj, node);
case ts.SyntaxKind.ArrayLiteralExpression:
let arr: MetadataValue[] = [];
ts.forEachChild(node, child => {
const value = this.evaluateNode(child, /* preferReference */ true);
// Check for error
if (isFoldableError(value)) {
error = value;
return true; // Stop the forEachChild.
}
// Handle spread expressions
if (isMetadataSymbolicSpreadExpression(value)) {
if (Array.isArray(value.expression)) {
for (const spreadValue of value.expression) {
arr.push(spreadValue);
}
return;
}
}
arr.push(value);
});
if (error) return error;
return recordEntry(arr, node);
case spreadElementSyntaxKind:
let spreadExpression = this.evaluateNode((node as any).expression);
return recordEntry({__symbolic: 'spread', expression: spreadExpression}, node);
case ts.SyntaxKind.CallExpression:
const callExpression = <ts.CallExpression>node;
if (isCallOf(callExpression, 'forwardRef') &&
arrayOrEmpty(callExpression.arguments).length === 1) {
const firstArgument = callExpression.arguments[0];
if (firstArgument.kind == ts.SyntaxKind.ArrowFunction) {
const arrowFunction = <ts.ArrowFunction>firstArgument;
return recordEntry(this.evaluateNode(arrowFunction.body), node);
}
}
const args = arrayOrEmpty(callExpression.arguments).map(arg => this.evaluateNode(arg));
if (!this.options.verboseInvalidExpression && args.some(isMetadataError)) {
return args.find(isMetadataError);
}
if (this.isFoldable(callExpression)) {
if (isMethodCallOf(callExpression, 'concat')) {
const arrayValue = <MetadataValue[]>this.evaluateNode(
(<ts.PropertyAccessExpression>callExpression.expression).expression);
if (isFoldableError(arrayValue)) return arrayValue;
return arrayValue.concat(args[0]);
}
}
// Always fold a CONST_EXPR even if the argument is not foldable.
if (isCallOf(callExpression, 'CONST_EXPR') &&
arrayOrEmpty(callExpression.arguments).length === 1) {
return recordEntry(args[0], node);
}
const expression = this.evaluateNode(callExpression.expression);
if (isFoldableError(expression)) {
return recordEntry(expression, node);
}
let result: MetadataSymbolicCallExpression = {__symbolic: 'call', expression: expression};
if (args && args.length) {
result.arguments = args;
}
return recordEntry(result, node);
case ts.SyntaxKind.NewExpression:
const newExpression = <ts.NewExpression>node;
const newArgs = arrayOrEmpty(newExpression.arguments).map(arg => this.evaluateNode(arg));
if (!this.options.verboseInvalidExpression && newArgs.some(isMetadataError)) {
return recordEntry(newArgs.find(isMetadataError), node);
}
const newTarget = this.evaluateNode(newExpression.expression);
if (isMetadataError(newTarget)) {
return recordEntry(newTarget, node);
}
const call: MetadataSymbolicCallExpression = {__symbolic: 'new', expression: newTarget};
if (newArgs.length) {
call.arguments = newArgs;
}
return recordEntry(call, node);
case ts.SyntaxKind.PropertyAccessExpression: {
const propertyAccessExpression = <ts.PropertyAccessExpression>node;
const expression = this.evaluateNode(propertyAccessExpression.expression);
if (isFoldableError(expression)) {
return recordEntry(expression, node);
}
const member = this.nameOf(propertyAccessExpression.name);
if (isFoldableError(member)) {
return recordEntry(member, node);
}
if (expression && this.isFoldable(propertyAccessExpression.expression))
return (<any>expression)[<string>member];
if (isMetadataModuleReferenceExpression(expression)) {
// A select into a module reference and be converted into a reference to the symbol
// in the module
return recordEntry(
{__symbolic: 'reference', module: expression.module, name: member}, node);
}
return recordEntry({__symbolic: 'select', expression, member}, node);
}
case ts.SyntaxKind.ElementAccessExpression: {
const elementAccessExpression = <ts.ElementAccessExpression>node;
const expression = this.evaluateNode(elementAccessExpression.expression);
if (isFoldableError(expression)) {
return recordEntry(expression, node);
}
if (!elementAccessExpression.argumentExpression) {
return recordEntry(errorSymbol('Expression form not supported', node), node);
}
const index = this.evaluateNode(elementAccessExpression.argumentExpression);
if (isFoldableError(expression)) {
return recordEntry(expression, node);
}
if (this.isFoldable(elementAccessExpression.expression) &&
this.isFoldable(elementAccessExpression.argumentExpression))
return (<any>expression)[<string|number>index];
return recordEntry({__symbolic: 'index', expression, index}, node);
}
case ts.SyntaxKind.Identifier:
const identifier = <ts.Identifier>node;
const name = identifier.text;
return resolveName(name, preferReference);
case ts.SyntaxKind.TypeReference:
const typeReferenceNode = <ts.TypeReferenceNode>node;
const typeNameNode = typeReferenceNode.typeName;
const getReference: (typeNameNode: ts.Identifier | ts.QualifiedName) => MetadataValue =
node => {
if (typeNameNode.kind === ts.SyntaxKind.QualifiedName) {
const qualifiedName = <ts.QualifiedName>node;
const left = this.evaluateNode(qualifiedName.left);
if (isMetadataModuleReferenceExpression(left)) {
return recordEntry(
<MetadataImportedSymbolReferenceExpression>{
__symbolic: 'reference',
module: left.module,
name: qualifiedName.right.text
},
node);
}
// Record a type reference to a declared type as a select.
return {__symbolic: 'select', expression: left, member: qualifiedName.right.text};
} else {
const identifier = <ts.Identifier>typeNameNode;
const symbol = this.symbols.resolve(identifier.text);
if (isFoldableError(symbol) || isMetadataSymbolicReferenceExpression(symbol)) {
return recordEntry(symbol, node);
}
return recordEntry(
errorSymbol('Could not resolve type', node, {typeName: identifier.text}), node);
}
};
const typeReference = getReference(typeNameNode);
if (isFoldableError(typeReference)) {
return recordEntry(typeReference, node);
}
if (!isMetadataModuleReferenceExpression(typeReference) &&
typeReferenceNode.typeArguments && typeReferenceNode.typeArguments.length) {
const args = typeReferenceNode.typeArguments.map(element => this.evaluateNode(element));
// TODO: Remove typecast when upgraded to 2.0 as it will be corretly inferred.
// Some versions of 1.9 do not infer this correctly.
(<MetadataImportedSymbolReferenceExpression>typeReference).arguments = args;
}
return recordEntry(typeReference, node);
case ts.SyntaxKind.UnionType:
const unionType = <ts.UnionTypeNode>node;
// Remove null and undefined from the list of unions.
const references = unionType.types
.filter(
n => n.kind != ts.SyntaxKind.NullKeyword &&
n.kind != ts.SyntaxKind.UndefinedKeyword)
.map(n => this.evaluateNode(n));
// The remmaining reference must be the same. If two have type arguments consider them
// different even if the type arguments are the same.
let candidate: any = null;
for (let i = 0; i < references.length; i++) {
const reference = references[i];
if (isMetadataSymbolicReferenceExpression(reference)) {
if (candidate) {
if ((reference as any).name == candidate.name &&
(reference as any).module == candidate.module && !(reference as any).arguments) {
candidate = reference;
}
} else {
candidate = reference;
}
} else {
return reference;
}
}
if (candidate) return candidate;
break;
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
case ts.SyntaxKind.StringLiteral:
case ts.SyntaxKind.TemplateHead:
case ts.SyntaxKind.TemplateTail:
case ts.SyntaxKind.TemplateMiddle:
return (<ts.LiteralLikeNode>node).text;
case ts.SyntaxKind.NumericLiteral:
return parseFloat((<ts.LiteralExpression>node).text);
case ts.SyntaxKind.AnyKeyword:
return recordEntry({__symbolic: 'reference', name: 'any'}, node);
case ts.SyntaxKind.StringKeyword:
return recordEntry({__symbolic: 'reference', name: 'string'}, node);
case ts.SyntaxKind.NumberKeyword:
return recordEntry({__symbolic: 'reference', name: 'number'}, node);
case ts.SyntaxKind.BooleanKeyword:
return recordEntry({__symbolic: 'reference', name: 'boolean'}, node);
case ts.SyntaxKind.ArrayType:
const arrayTypeNode = <ts.ArrayTypeNode>node;
return recordEntry(
{
__symbolic: 'reference',
name: 'Array',
arguments: [this.evaluateNode(arrayTypeNode.elementType)]
},
node);
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 as any);
case ts.SyntaxKind.MinusToken:
return -(operand as any);
case ts.SyntaxKind.TildeToken:
return ~(operand as any);
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 recordEntry({__symbolic: 'pre', operator: operatorText, operand: operand}, node);
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 recordEntry(
{
__symbolic: 'binop',
operator: binaryExpression.operatorToken.getText(),
left: left,
right: right
},
node);
}
break;
case ts.SyntaxKind.ConditionalExpression:
const conditionalExpression = <ts.ConditionalExpression>node;
const condition = this.evaluateNode(conditionalExpression.condition);
const thenExpression = this.evaluateNode(conditionalExpression.whenTrue);
const elseExpression = this.evaluateNode(conditionalExpression.whenFalse);
if (isPrimitive(condition)) {
return condition ? thenExpression : elseExpression;
}
return recordEntry({__symbolic: 'if', condition, thenExpression, elseExpression}, node);
case ts.SyntaxKind.FunctionExpression:
case ts.SyntaxKind.ArrowFunction:
return recordEntry(errorSymbol('Function call not supported', node), node);
case ts.SyntaxKind.TaggedTemplateExpression:
return recordEntry(
errorSymbol('Tagged template expressions are not supported in metadata', node), node);
case ts.SyntaxKind.TemplateExpression:
const templateExpression = <ts.TemplateExpression>node;
if (this.isFoldable(node)) {
return templateExpression.templateSpans.reduce(
(previous, current) => previous + <string>this.evaluateNode(current.expression) +
<string>this.evaluateNode(current.literal),
this.evaluateNode(templateExpression.head));
} else {
return templateExpression.templateSpans.reduce((previous, current) => {
const expr = this.evaluateNode(current.expression);
const literal = this.evaluateNode(current.literal);
if (isFoldableError(expr)) return expr;
if (isFoldableError(literal)) return literal;
if (typeof previous === 'string' && typeof expr === 'string' &&
typeof literal === 'string') {
return previous + expr + literal;
}
let result = expr;
if (previous !== '') {
result = {__symbolic: 'binop', operator: '+', left: previous, right: expr};
}
if (literal != '') {
result = {__symbolic: 'binop', operator: '+', left: result, right: literal};
}
return result;
}, this.evaluateNode(templateExpression.head));
}
case ts.SyntaxKind.AsExpression:
const asExpression = <ts.AsExpression>node;
return this.evaluateNode(asExpression.expression);
case ts.SyntaxKind.ClassExpression:
return {__symbolic: 'class'};
}
return recordEntry(errorSymbol('Expression form not supported', node), node);
}
}
function isPropertyAssignment(node: ts.Node): node is ts.PropertyAssignment {
return node.kind == ts.SyntaxKind.PropertyAssignment;
}
const empty = ts.createNodeArray<any>();
function arrayOrEmpty<T extends ts.Node>(v: ts.NodeArray<T>| undefined): ts.NodeArray<T> {
return v || empty;
}

View File

@ -0,0 +1,11 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export * from './collector';
export * from './schema';
export * from './bundle_index_host';

View File

@ -0,0 +1,58 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {BundlePrivateEntry} from './bundler';
const INDEX_HEADER = `/**
* Generated bundle index. Do not edit.
*/
`;
type MapEntry = [string, BundlePrivateEntry[]];
export function privateEntriesToIndex(index: string, privates: BundlePrivateEntry[]): string {
const results: string[] = [INDEX_HEADER];
// Export all of the index symbols.
results.push(`export * from '${index}';`, '');
// Simplify the exports
const exports = new Map<string, BundlePrivateEntry[]>();
for (const entry of privates) {
let entries = exports.get(entry.module);
if (!entries) {
entries = [];
exports.set(entry.module, entries);
}
entries.push(entry);
}
const compareEntries = compare((e: BundlePrivateEntry) => e.name);
const compareModules = compare((e: MapEntry) => e[0]);
const orderedExports =
Array.from(exports)
.map(([module, entries]) => <MapEntry>[module, entries.sort(compareEntries)])
.sort(compareModules);
for (const [module, entries] of orderedExports) {
let symbols = entries.map(e => `${e.name} as ${e.privateName}`);
results.push(`export {${symbols}} from '${module}';`);
}
return results.join('\n');
}
function compare<E, T>(select: (e: E) => T): (a: E, b: E) => number {
return (a, b) => {
const ak = select(a);
const bk = select(b);
return ak > bk ? 1 : ak < bk ? -1 : 0;
};
}

View File

@ -0,0 +1,284 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// Metadata Schema
// If you make a backwards incompatible change to the schema, increment the VERSION number.
// If you make a backwards compatible change to the metadata (such as adding an option field) then
// leave VERSION the same. If possible, as many versions of the metadata that can represent the
// semantics of the file in an array. For example, when generating a version 2 file, if version 1
// can accurately represent the metadata, generate both version 1 and version 2 in an array.
export const VERSION = 3;
export type MetadataEntry = ClassMetadata | InterfaceMetadata | FunctionMetadata | MetadataValue;
export interface ModuleMetadata {
__symbolic: 'module';
version: number;
exports?: ModuleExportMetadata[];
importAs?: string;
metadata: {[name: string]: MetadataEntry};
origins?: {[name: string]: string};
}
export function isModuleMetadata(value: any): value is ModuleMetadata {
return value && value.__symbolic === 'module';
}
export interface ModuleExportMetadata {
export?: (string|{name: string, as: string})[];
from: string;
}
export interface ClassMetadata {
__symbolic: 'class';
extends?: MetadataSymbolicExpression|MetadataError;
arity?: number;
decorators?: (MetadataSymbolicExpression|MetadataError)[];
members?: MetadataMap;
statics?: {[name: string]: MetadataValue | FunctionMetadata};
}
export function isClassMetadata(value: any): value is ClassMetadata {
return value && value.__symbolic === 'class';
}
export interface InterfaceMetadata { __symbolic: 'interface'; }
export function isInterfaceMetadata(value: any): value is InterfaceMetadata {
return value && value.__symbolic === 'interface';
}
export interface MetadataMap { [name: string]: MemberMetadata[]; }
export interface MemberMetadata {
__symbolic: 'constructor'|'method'|'property';
decorators?: (MetadataSymbolicExpression|MetadataError)[];
}
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 | MetadataError)[]|undefined)[];
}
export function isMethodMetadata(value: any): value is MethodMetadata {
return value && (value.__symbolic === 'constructor' || value.__symbolic === 'method');
}
export interface ConstructorMetadata extends MethodMetadata {
__symbolic: 'constructor';
parameters?: (MetadataSymbolicExpression|MetadataError|null|undefined)[];
}
export function isConstructorMetadata(value: any): value is ConstructorMetadata {
return value && value.__symbolic === 'constructor';
}
export interface FunctionMetadata {
__symbolic: 'function';
parameters: string[];
defaults?: MetadataValue[];
value: MetadataValue;
}
export function isFunctionMetadata(value: any): value is FunctionMetadata {
return value && value.__symbolic === 'function';
}
export type MetadataValue = string | number | boolean | undefined | null | MetadataObject |
MetadataArray | MetadataSymbolicExpression | MetadataError;
export interface MetadataObject { [name: string]: MetadataValue; }
export interface MetadataArray { [name: number]: MetadataValue; }
export interface MetadataSymbolicExpression {
__symbolic: 'binary'|'call'|'index'|'new'|'pre'|'reference'|'select'|'spread'|'if';
}
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':
case 'spread':
case 'if':
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 MetadataSymbolicIfExpression extends MetadataSymbolicExpression {
__symbolic: 'if';
condition: MetadataValue;
thenExpression: MetadataValue;
elseExpression: MetadataValue;
}
export function isMetadataSymbolicIfExpression(value: any): value is MetadataSymbolicIfExpression {
return value && value.__symbolic === 'if';
}
export interface MetadataGlobalReferenceExpression extends MetadataSymbolicExpression {
__symbolic: 'reference';
name: string;
arguments?: MetadataValue[];
}
export function isMetadataGlobalReferenceExpression(value: any):
value is MetadataGlobalReferenceExpression {
return value && value.name && !value.module && isMetadataSymbolicReferenceExpression(value);
}
export interface MetadataModuleReferenceExpression extends MetadataSymbolicExpression {
__symbolic: 'reference';
module: string;
}
export function isMetadataModuleReferenceExpression(value: any):
value is MetadataModuleReferenceExpression {
return value && value.module && !value.name && !value.default &&
isMetadataSymbolicReferenceExpression(value);
}
export interface MetadataImportedSymbolReferenceExpression extends MetadataSymbolicExpression {
__symbolic: 'reference';
module: string;
name: string;
arguments?: MetadataValue[];
}
export function isMetadataImportedSymbolReferenceExpression(value: any):
value is MetadataImportedSymbolReferenceExpression {
return value && value.module && !!value.name && isMetadataSymbolicReferenceExpression(value);
}
export interface MetadataImportedDefaultReferenceExpression extends MetadataSymbolicExpression {
__symbolic: 'reference';
module: string;
default:
boolean;
arguments?: MetadataValue[];
}
export function isMetadataImportDefaultReference(value: any):
value is MetadataImportedDefaultReferenceExpression {
return value.module && value.default && isMetadataSymbolicReferenceExpression(value);
}
export type MetadataSymbolicReferenceExpression = MetadataGlobalReferenceExpression |
MetadataModuleReferenceExpression | MetadataImportedSymbolReferenceExpression |
MetadataImportedDefaultReferenceExpression;
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';
}
export interface MetadataSymbolicSpreadExpression extends MetadataSymbolicExpression {
__symbolic: 'spread';
expression: MetadataValue;
}
export function isMetadataSymbolicSpreadExpression(value: any):
value is MetadataSymbolicSpreadExpression {
return value && value.__symbolic === 'spread';
}
export interface MetadataError {
__symbolic: 'error';
/**
* This message should be short and relatively discriptive and should be fixed once it is created.
* If the reader doesn't recognize the message, it will display the message unmodified. If the
* reader recognizes the error message is it free to use substitute message the is more
* descriptive and/or localized.
*/
message: string;
/**
* The line number of the error in the .ts file the metadata was created for.
*/
line?: number;
/**
* The number of utf8 code-units from the beginning of the file of the error.
*/
character?: number;
/**
* The module of the error (only used in bundled metadata)
*/
module?: string;
/**
* Context information that can be used to generate a more descriptive error message. The content
* of the context is dependent on the error message.
*/
context?: {[name: string]: string};
}
export function isMetadataError(value: any): value is MetadataError {
return value && value.__symbolic === 'error';
}

View File

@ -0,0 +1,130 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {MetadataSymbolicReferenceExpression, MetadataValue} from './schema';
export class Symbols {
private _symbols: Map<string, MetadataValue>;
private references = new Map<string, MetadataSymbolicReferenceExpression>();
constructor(private sourceFile: ts.SourceFile) {}
resolve(name: string, preferReference?: boolean): MetadataValue|undefined {
return (preferReference && this.references.get(name)) || this.symbols.get(name);
}
define(name: string, value: MetadataValue) { this.symbols.set(name, value); }
defineReference(name: string, value: MetadataSymbolicReferenceExpression) {
this.references.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<string, MetadataValue>();
populateBuiltins(result);
this.buildImports();
}
return result;
}
private buildImports(): void {
const 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;
if (externalReference.expression) {
// An `import <identifier> = require(<module-specifier>);
if (!externalReference.expression.parent) {
// The `parent` field of a node is set by the TypeScript binder (run as
// part of the type checker). Setting it here allows us to call `getText()`
// even if the `SourceFile` was not type checked (which looks for `SourceFile`
// in the parent chain). This doesn't damage the node as the binder unconditionally
// sets the parent.
externalReference.expression.parent = externalReference;
externalReference.parent = this.sourceFile as any;
}
const from = stripQuotes(externalReference.expression.getText());
symbols.set(
importEqualsDeclaration.name.text, {__symbolic: 'reference', module: from});
break;
}
}
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;
}
if (!importDecl.moduleSpecifier.parent) {
// See note above in the `ImportEqualDeclaration` case.
importDecl.moduleSpecifier.parent = importDecl;
importDecl.parent = this.sourceFile;
}
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 (const 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}));
}

View File

@ -14,7 +14,6 @@
*/
import {AotCompilerHost, AotSummaryResolver, StaticReflector, StaticSymbolCache, StaticSymbolResolver} from '@angular/compiler';
import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
import {CodeGenerator} from './codegen';
@ -22,6 +21,7 @@ import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './
import {Extractor} from './extractor';
import {listLazyRoutesOfModule} from './ngtools_impl';
import {PathMappedCompilerHost} from './path_mapped_compiler_host';
import {CompilerOptions} from './transformers/api';
export interface NgTools_InternalApi_NG2_CodeGen_Options {
basePath: string;
@ -29,7 +29,7 @@ export interface NgTools_InternalApi_NG2_CodeGen_Options {
program: ts.Program;
host: ts.CompilerHost;
angularCompilerOptions: AngularCompilerOptions;
angularCompilerOptions: CompilerOptions;
// i18n options.
i18nFormat?: string;
@ -45,7 +45,7 @@ export interface NgTools_InternalApi_NG2_CodeGen_Options {
export interface NgTools_InternalApi_NG2_ListLazyRoutes_Options {
program: ts.Program;
host: ts.CompilerHost;
angularCompilerOptions: AngularCompilerOptions;
angularCompilerOptions: CompilerOptions;
entryModule: string;
// Every new property under this line should be optional.
@ -58,7 +58,7 @@ export interface NgTools_InternalApi_NG2_ExtractI18n_Options {
compilerOptions: ts.CompilerOptions;
program: ts.Program;
host: ts.CompilerHost;
angularCompilerOptions: AngularCompilerOptions;
angularCompilerOptions: CompilerOptions;
i18nFormat?: string;
readResource: (fileName: string) => Promise<string>;
// Every new property under this line should be optional.

View File

@ -7,12 +7,13 @@
*/
import {StaticSymbol} from '@angular/compiler';
import {AngularCompilerOptions, ModuleMetadata} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {CompilerHost, CompilerHostContext} from './compiler_host';
import {ModuleMetadata} from './metadata/index';
import {CompilerOptions} from './transformers/api';
const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
const DTS = /\.d\.ts$/;
@ -25,7 +26,7 @@ const DTS = /\.d\.ts$/;
* loader what to do.
*/
export class PathMappedCompilerHost extends CompilerHost {
constructor(program: ts.Program, options: AngularCompilerOptions, context: CompilerHostContext) {
constructor(program: ts.Program, options: CompilerOptions, context: CompilerHostContext) {
super(program, options, context);
}

View File

@ -187,7 +187,6 @@ export function performCompilation({rootNames, options, host, oldProgram, emitCa
return {diagnostics: allDiagnostics, program};
}
}
function defaultGatherDiagnostics(program: api.Program): Diagnostics {
const allDiagnostics: Diagnostics = [];

View File

@ -6,8 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CollectorOptions, MetadataCollector, MetadataError, MetadataValue, ModuleMetadata, isMetadataGlobalReferenceExpression} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
import {CollectorOptions, MetadataCollector, MetadataValue, ModuleMetadata, isMetadataGlobalReferenceExpression} from '../metadata/index';
export interface LoweringRequest {
kind: ts.SyntaxKind;

View File

@ -7,13 +7,13 @@
*/
import {AotCompiler, AotCompilerHost, AotCompilerOptions, GeneratedFile, MessageBundle, NgAnalyzedModules, Serializer, Xliff, Xliff2, Xmb, core, createAotCompiler, getParseErrors, isSyntaxError, toTypeScript} from '@angular/compiler';
import {createBundleIndexHost} from '@angular/tsc-wrapped';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {BaseAotCompilerHost} from '../compiler_host';
import {TypeChecker} from '../diagnostics/check_types';
import {createBundleIndexHost} from '../metadata/index';
import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, EmitFlags, Program, SOURCE, TsEmitArguments, TsEmitCallback} from './api';
import {LowerMetadataCache, getExpressionLoweringTransformFactory} from './lower_expressions';

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ModuleMetadata} from '@angular/tsc-wrapped';
import {ModuleMetadata} from '@angular/compiler-cli/src/metadata/index';
import * as ts from 'typescript';
import {CompilerHost} from '../src/compiler_host';

View File

@ -7,10 +7,11 @@
*/
import {StaticSymbol} from '@angular/compiler';
import {AngularCompilerOptions, CompilerHost} from '@angular/compiler-cli';
import {CompilerHost} from '@angular/compiler-cli';
import * as ts from 'typescript';
import {getExpressionDiagnostics, getTemplateExpressionDiagnostics} from '../../src/diagnostics/expression_diagnostics';
import {CompilerOptions} from '../../src/transformers/api';
import {Directory} from '../mocks';
import {DiagnosticContext, MockLanguageServiceHost, getDiagnosticTemplateInfo} from './mocks';
@ -29,7 +30,7 @@ describe('expression diagnostics', () => {
service = ts.createLanguageService(host, registry);
const program = service.getProgram();
const checker = program.getTypeChecker();
const options: AngularCompilerOptions = Object.create(host.getCompilationSettings());
const options: CompilerOptions = Object.create(host.getCompilationSettings());
options.genDir = '/dist';
options.basePath = '/src';
aotHost = new CompilerHost(program, options, host, {verboseInvalidExpression: true});

View File

@ -7,12 +7,13 @@
*/
import {StaticSymbol} from '@angular/compiler';
import {AngularCompilerOptions, CompilerHost} from '@angular/compiler-cli';
import {CompilerHost} from '@angular/compiler-cli';
import {EmittingCompilerHost, MockAotCompilerHost, MockCompilerHost, MockData, MockDirectory, MockMetadataBundlerHost, arrayToMockDir, arrayToMockMap, isSource, settings, setup, toMockFileArray} from '@angular/compiler/test/aot/test_util';
import * as ts from 'typescript';
import {Symbol, SymbolQuery, SymbolTable} from '../../src/diagnostics/symbols';
import {getSymbolQuery} from '../../src/diagnostics/typescript_symbols';
import {CompilerOptions} from '../../src/transformers/api';
import {Directory} from '../mocks';
import {DiagnosticContext, MockLanguageServiceHost} from './mocks';
@ -40,7 +41,7 @@ describe('symbol query', () => {
program = service.getProgram();
checker = program.getTypeChecker();
sourceFile = program.getSourceFile('/quickstart/app/app.component.ts');
const options: AngularCompilerOptions = Object.create(host.getCompilationSettings());
const options: CompilerOptions = Object.create(host.getCompilationSettings());
options.genDir = '/dist';
options.basePath = '/quickstart';
const aotHost = new CompilerHost(program, options, host, {verboseInvalidExpression: true});

View File

@ -6,12 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/
import {makeTempDir} from '@angular/tsc-wrapped/test/test_support';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {main} from '../src/extract_i18n';
import {mainXi18n} from '../src/extract_i18n';
import {makeTempDir} from './test_support';
function getNgRootDir() {
const moduleFilename = module.filename.replace(/\\/g, '/');
@ -172,7 +173,7 @@ describe('extract_i18n command line', () => {
writeSources();
const exitCode =
main(['-p', basePath, '--i18nFormat=xmb', '--outFile=custom_file.xmb'], errorSpy);
mainXi18n(['-p', basePath, '--i18nFormat=xmb', '--outFile=custom_file.xmb'], errorSpy);
expect(errorSpy).not.toHaveBeenCalled();
expect(exitCode).toBe(0);
@ -186,7 +187,7 @@ describe('extract_i18n command line', () => {
writeConfig();
writeSources();
const exitCode = main(['-p', basePath, '--i18nFormat=xlf', '--locale=fr'], errorSpy);
const exitCode = mainXi18n(['-p', basePath, '--i18nFormat=xlf', '--locale=fr'], errorSpy);
expect(errorSpy).not.toHaveBeenCalled();
expect(exitCode).toBe(0);
@ -201,7 +202,7 @@ describe('extract_i18n command line', () => {
writeSources();
const exitCode =
main(['-p', basePath, '--i18nFormat=xlf2', '--outFile=messages.xliff2.xlf'], errorSpy);
mainXi18n(['-p', basePath, '--i18nFormat=xlf2', '--outFile=messages.xliff2.xlf'], errorSpy);
expect(errorSpy).not.toHaveBeenCalled();
expect(exitCode).toBe(0);
@ -216,11 +217,11 @@ describe('extract_i18n command line', () => {
writeSources();
const exitCode =
main(['-p', basePath, '--i18nFormat=xlf2', '--outFile=messages.xliff2.xlf'], errorSpy);
mainXi18n(['-p', basePath, '--i18nFormat=xlf2', '--outFile=messages.xliff2.xlf'], errorSpy);
expect(errorSpy).not.toHaveBeenCalled();
expect(exitCode).toBe(0);
const moduleOutput = path.join(outDir, 'src', 'module.js');
expect(fs.existsSync(moduleOutput)).toBeFalsy();
});
});
});

View File

@ -1,313 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {makeTempDir} from '@angular/tsc-wrapped/test/test_support';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {main} from '../src/main';
function getNgRootDir() {
const moduleFilename = module.filename.replace(/\\/g, '/');
const distIndex = moduleFilename.indexOf('/dist/all');
return moduleFilename.substr(0, distIndex);
}
describe('compiler-cli with disableTransformerPipeline', () => {
let basePath: string;
let outDir: string;
let write: (fileName: string, content: string) => void;
let errorSpy: jasmine.Spy&((s: string) => void);
function writeConfig(tsconfig: string = '{"extends": "./tsconfig-base.json"}') {
const json = JSON.parse(tsconfig);
// Note: 'extends' does not work for "angularCompilerOptions" yet.
const ngOptions = json['angularCompilerOptions'] = json['angularCompilerOptions'] || {};
ngOptions['disableTransformerPipeline'] = true;
write('tsconfig.json', JSON.stringify(json));
}
beforeEach(() => {
errorSpy = jasmine.createSpy('consoleError');
basePath = makeTempDir();
write = (fileName: string, content: string) => {
fs.writeFileSync(path.join(basePath, fileName), content, {encoding: 'utf-8'});
};
write('tsconfig-base.json', `{
"compilerOptions": {
"experimentalDecorators": true,
"types": [],
"outDir": "built",
"declaration": true,
"module": "es2015",
"moduleResolution": "node",
"lib": ["es6", "dom"]
}
}`);
outDir = path.resolve(basePath, 'built');
const ngRootDir = getNgRootDir();
const nodeModulesPath = path.resolve(basePath, 'node_modules');
fs.mkdirSync(nodeModulesPath);
fs.symlinkSync(
path.resolve(ngRootDir, 'dist', 'all', '@angular'),
path.resolve(nodeModulesPath, '@angular'));
fs.symlinkSync(
path.resolve(ngRootDir, 'node_modules', 'rxjs'), path.resolve(nodeModulesPath, 'rxjs'));
});
it('should compile without errors', (done) => {
writeConfig();
write('test.ts', 'export const A = 1;');
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(errorSpy).not.toHaveBeenCalled();
expect(exitCode).toEqual(0);
done();
})
.catch(e => done.fail(e));
});
it('should not print the stack trace if user input file does not exist', (done) => {
writeConfig(`{
"extends": "./tsconfig-base.json",
"files": ["test.ts"]
}`);
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(errorSpy).toHaveBeenCalledWith(
`Error File '` + path.join(basePath, 'test.ts') + `' not found.`);
expect(exitCode).toEqual(1);
done();
})
.catch(e => done.fail(e));
});
it('should not print the stack trace if user input file is malformed', (done) => {
writeConfig();
write('test.ts', 'foo;');
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(errorSpy).toHaveBeenCalledWith(
'Error at ' + path.join(basePath, 'test.ts') + `:1:1: Cannot find name 'foo'.`);
expect(exitCode).toEqual(1);
done();
})
.catch(e => done.fail(e));
});
it('should not print the stack trace if cannot find the imported module', (done) => {
writeConfig();
write('test.ts', `import {MyClass} from './not-exist-deps';`);
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(errorSpy).toHaveBeenCalledWith(
'Error at ' + path.join(basePath, 'test.ts') +
`:1:23: Cannot find module './not-exist-deps'.`);
expect(exitCode).toEqual(1);
done();
})
.catch(e => done.fail(e));
});
it('should not print the stack trace if cannot import', (done) => {
writeConfig();
write('empty-deps.ts', 'export const A = 1;');
write('test.ts', `import {MyClass} from './empty-deps';`);
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(errorSpy).toHaveBeenCalledWith(
'Error at ' + path.join(basePath, 'test.ts') + `:1:9: Module '"` +
path.join(basePath, 'empty-deps') + `"' has no exported member 'MyClass'.`);
expect(exitCode).toEqual(1);
done();
})
.catch(e => done.fail(e));
});
it('should not print the stack trace if type mismatches', (done) => {
writeConfig();
write('empty-deps.ts', 'export const A = "abc";');
write('test.ts', `
import {A} from './empty-deps';
A();
`);
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(errorSpy).toHaveBeenCalledWith(
'Error at ' + path.join(basePath, 'test.ts') +
':3:7: Cannot invoke an expression whose type lacks a call signature. ' +
'Type \'String\' has no compatible call signatures.');
expect(exitCode).toEqual(1);
done();
})
.catch(e => done.fail(e));
});
it('should print the stack trace on compiler internal errors', (done) => {
write('test.ts', 'export const A = 1;');
main(['-p', 'not-exist'], errorSpy)
.then((exitCode) => {
expect(errorSpy).toHaveBeenCalled();
expect(errorSpy.calls.mostRecent().args[0]).toContain('no such file or directory');
expect(errorSpy.calls.mostRecent().args[0]).toContain('at Error (native)');
expect(exitCode).toEqual(2);
done();
})
.catch(e => done.fail(e));
});
describe('compile ngfactory files', () => {
it('should only compile ngfactory files that are referenced by root files by default',
(done) => {
writeConfig(`{
"extends": "./tsconfig-base.json",
"files": ["mymodule.ts"]
}`);
write('mymodule.ts', `
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
@NgModule({
imports: [CommonModule]
})
export class MyModule {}
`);
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(exitCode).toEqual(0);
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(false);
done();
})
.catch(e => done.fail(e));
});
it('should report errors for ngfactory files that are not referenced by root files', (done) => {
writeConfig(`{
"extends": "./tsconfig-base.json",
"files": ["mymodule.ts"],
"angularCompilerOptions": {
"alwaysCompileGeneratedCode": true
}
}`);
write('mymodule.ts', `
import {NgModule, Component} from '@angular/core';
@Component({template: '{{unknownProp}}'})
export class MyComp {}
@NgModule({declarations: [MyComp]})
export class MyModule {}
`);
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(errorSpy).toHaveBeenCalledTimes(1);
expect(errorSpy.calls.mostRecent().args[0])
.toContain('Error at ' + path.join(basePath, 'mymodule.ngfactory.ts'));
expect(errorSpy.calls.mostRecent().args[0])
.toContain(`Property 'unknownProp' does not exist on type 'MyComp'`);
expect(exitCode).toEqual(1);
done();
})
.catch(e => done.fail(e));
});
it('should compile ngfactory files that are not referenced by root files', (done) => {
writeConfig(`{
"extends": "./tsconfig-base.json",
"files": ["mymodule.ts"],
"angularCompilerOptions": {
"alwaysCompileGeneratedCode": true
}
}`);
write('mymodule.ts', `
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
@NgModule({
imports: [CommonModule]
})
export class MyModule {}
`);
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(exitCode).toEqual(0);
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true);
expect(fs.existsSync(path.resolve(
outDir, 'node_modules', '@angular', 'core', 'src',
'application_module.ngfactory.js')))
.toBe(true);
done();
})
.catch(e => done.fail(e));
});
it('should not produce ngsummary files by default', (done) => {
writeConfig(`{
"extends": "./tsconfig-base.json",
"files": ["mymodule.ts"]
}`);
write('mymodule.ts', `
import {NgModule} from '@angular/core';
@NgModule()
export class MyModule {}
`);
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(exitCode).toEqual(0);
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngsummary.js'))).toBe(false);
done();
})
.catch(e => done.fail(e));
});
it('should produce ngsummary files if configured', (done) => {
writeConfig(`{
"extends": "./tsconfig-base.json",
"files": ["mymodule.ts"],
"angularCompilerOptions": {
"enableSummariesForJit": true,
"alwaysCompileGeneratedCode": true
}
}`);
write('mymodule.ts', `
import {NgModule} from '@angular/core';
@NgModule()
export class MyModule {}
`);
main(['-p', basePath], errorSpy)
.then((exitCode) => {
expect(exitCode).toEqual(0);
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngsummary.js'))).toBe(true);
done();
})
.catch(e => done.fail(e));
});
});
});

View File

@ -0,0 +1,274 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as path from 'path';
import * as ts from 'typescript';
import {MetadataBundler, MetadataBundlerHost} from '../../src/metadata/bundler';
import {MetadataCollector} from '../../src/metadata/collector';
import {ClassMetadata, MetadataGlobalReferenceExpression, ModuleMetadata} from '../../src/metadata/schema';
import {Directory, open} from './typescript.mocks';
describe('metadata bundler', () => {
it('should be able to bundle a simple library', () => {
const host = new MockStringBundlerHost('/', SIMPLE_LIBRARY);
const bundler = new MetadataBundler('/lib/index', undefined, host);
const result = bundler.getMetadataBundle();
expect(Object.keys(result.metadata.metadata).sort()).toEqual([
'ONE_CLASSES', 'One', 'OneMore', 'TWO_CLASSES', 'Two', 'TwoMore', 'ɵa', 'ɵb'
]);
const originalOne = './src/one';
const originalTwo = './src/two/index';
expect(Object.keys(result.metadata.origins)
.sort()
.map(name => ({name, value: result.metadata.origins ![name]})))
.toEqual([
{name: 'ONE_CLASSES', value: originalOne}, {name: 'One', value: originalOne},
{name: 'OneMore', value: originalOne}, {name: 'TWO_CLASSES', value: originalTwo},
{name: 'Two', value: originalTwo}, {name: 'TwoMore', value: originalTwo},
{name: 'ɵa', value: originalOne}, {name: 'ɵb', value: originalTwo}
]);
expect(result.privates).toEqual([
{privateName: 'ɵa', name: 'PrivateOne', module: originalOne},
{privateName: 'ɵb', name: 'PrivateTwo', module: originalTwo}
]);
});
it('should be able to bundle an oddly constructed library', () => {
const host = new MockStringBundlerHost('/', {
'lib': {
'index.ts': `
export * from './src/index';
`,
'src': {
'index.ts': `
export {One, OneMore, ONE_CLASSES} from './one';
export {Two, TwoMore, TWO_CLASSES} from './two/index';
`,
'one.ts': `
class One {}
class OneMore extends One {}
class PrivateOne {}
const ONE_CLASSES = [One, OneMore, PrivateOne];
export {One, OneMore, PrivateOne, ONE_CLASSES};
`,
'two': {
'index.ts': `
class Two {}
class TwoMore extends Two {}
class PrivateTwo {}
const TWO_CLASSES = [Two, TwoMore, PrivateTwo];
export {Two, TwoMore, PrivateTwo, TWO_CLASSES};
`
}
}
}
});
const bundler = new MetadataBundler('/lib/index', undefined, host);
const result = bundler.getMetadataBundle();
expect(Object.keys(result.metadata.metadata).sort()).toEqual([
'ONE_CLASSES', 'One', 'OneMore', 'TWO_CLASSES', 'Two', 'TwoMore', 'ɵa', 'ɵb'
]);
expect(result.privates).toEqual([
{privateName: 'ɵa', name: 'PrivateOne', module: './src/one'},
{privateName: 'ɵb', name: 'PrivateTwo', module: './src/two/index'}
]);
});
it('should not output windows paths in metadata', () => {
const host = new MockStringBundlerHost('/', {
'index.ts': `
export * from './exports/test';
`,
'exports': {'test.ts': `export class TestExport {}`}
});
const bundler = new MetadataBundler('/index', undefined, host);
const result = bundler.getMetadataBundle();
expect(result.metadata.origins).toEqual({'TestExport': './exports/test'});
});
it('should convert re-exported to the export', () => {
const host = new MockStringBundlerHost('/', {
'index.ts': `
export * from './bar';
export * from './foo';
`,
'bar.ts': `
import {Foo} from './foo';
export class Bar extends Foo {
}
`,
'foo.ts': `
export {Foo} from 'foo';
`
});
const bundler = new MetadataBundler('/index', undefined, host);
const result = bundler.getMetadataBundle();
// Expect the extends reference to refer to the imported module
expect((result.metadata.metadata as any).Bar.extends.module).toEqual('foo');
expect(result.privates).toEqual([]);
});
it('should treat import then export as a simple export', () => {
const host = new MockStringBundlerHost('/', {
'index.ts': `
export * from './a';
export * from './c';
`,
'a.ts': `
import { B } from './b';
export { B };
`,
'b.ts': `
export class B { }
`,
'c.ts': `
import { B } from './b';
export class C extends B { }
`
});
const bundler = new MetadataBundler('/index', undefined, host);
const result = bundler.getMetadataBundle();
expect(Object.keys(result.metadata.metadata).sort()).toEqual(['B', 'C']);
expect(result.privates).toEqual([]);
});
it('should be able to bundle a private from a un-exported module', () => {
const host = new MockStringBundlerHost('/', {
'index.ts': `
export * from './foo';
`,
'foo.ts': `
import {Bar} from './bar';
export class Foo extends Bar {
}
`,
'bar.ts': `
export class Bar {}
`
});
const bundler = new MetadataBundler('/index', undefined, host);
const result = bundler.getMetadataBundle();
expect(Object.keys(result.metadata.metadata).sort()).toEqual(['Foo', 'ɵa']);
expect(result.privates).toEqual([{privateName: 'ɵa', name: 'Bar', module: './bar'}]);
});
it('should be able to bundle a library with re-exported symbols', () => {
const host = new MockStringBundlerHost('/', {
'public-api.ts': `
export * from './src/core';
export * from './src/externals';
`,
'src': {
'core.ts': `
export class A {}
export class B extends A {}
`,
'externals.ts': `
export {E, F, G} from 'external_one';
export * from 'external_two';
`
}
});
const bundler = new MetadataBundler('/public-api', undefined, host);
const result = bundler.getMetadataBundle();
expect(result.metadata.exports).toEqual([
{from: 'external_two'}, {
export: [{name: 'E', as: 'E'}, {name: 'F', as: 'F'}, {name: 'G', as: 'G'}],
from: 'external_one'
}
]);
expect(result.metadata.origins !['E']).toBeUndefined();
});
it('should be able to de-duplicate symbols of re-exported modules', () => {
const host = new MockStringBundlerHost('/', {
'public-api.ts': `
export {A as A2, A, B as B1, B as B2} from './src/core';
`,
'src': {
'core.ts': `
export class A {}
export class B {}
`,
}
});
const bundler = new MetadataBundler('/public-api', undefined, host);
const result = bundler.getMetadataBundle();
const {A, A2, B1, B2} = result.metadata.metadata as{
A: ClassMetadata,
A2: MetadataGlobalReferenceExpression,
B1: ClassMetadata,
B2: MetadataGlobalReferenceExpression
};
expect(A.__symbolic).toEqual('class');
expect(A2.__symbolic).toEqual('reference');
expect(A2.name).toEqual('A');
expect(B1.__symbolic).toEqual('class');
expect(B2.__symbolic).toEqual('reference');
expect(B2.name).toEqual('B1');
});
});
export class MockStringBundlerHost implements MetadataBundlerHost {
collector = new MetadataCollector();
constructor(private dirName: string, private directory: Directory) {}
getMetadataFor(moduleName: string): ModuleMetadata|undefined {
const fileName = path.join(this.dirName, moduleName) + '.ts';
const text = open(this.directory, fileName);
if (typeof text == 'string') {
const sourceFile = ts.createSourceFile(
fileName, text, ts.ScriptTarget.Latest, /* setParent */ true, ts.ScriptKind.TS);
const diagnostics: ts.Diagnostic[] = (sourceFile as any).parseDiagnostics;
if (diagnostics && diagnostics.length) {
throw Error('Unexpected syntax error in test');
}
const result = this.collector.getMetadata(sourceFile);
return result;
}
}
}
export const SIMPLE_LIBRARY = {
'lib': {
'index.ts': `
export * from './src/index';
`,
'src': {
'index.ts': `
export {One, OneMore, ONE_CLASSES} from './one';
export {Two, TwoMore, TWO_CLASSES} from './two/index';
`,
'one.ts': `
export class One {}
export class OneMore extends One {}
export class PrivateOne {}
export const ONE_CLASSES = [One, OneMore, PrivateOne];
`,
'two': {
'index.ts': `
export class Two {}
export class TwoMore extends Two {}
export class PrivateTwo {}
export const TWO_CLASSES = [Two, TwoMore, PrivateTwo];
`
}
}
}
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,373 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import * as ts from 'typescript';
import {Evaluator} from '../../src/metadata/evaluator';
import {Symbols} from '../../src/metadata/symbols';
import {Directory, Host, expectNoDiagnostics, findVar, findVarInitializer} from './typescript.mocks';
describe('Evaluator', () => {
const documentRegistry = ts.createDocumentRegistry();
let host: ts.LanguageServiceHost;
let service: ts.LanguageService;
let program: ts.Program;
let typeChecker: ts.TypeChecker;
let symbols: Symbols;
let evaluator: Evaluator;
beforeEach(() => {
host = new Host(FILES, [
'expressions.ts', 'consts.ts', 'const_expr.ts', 'forwardRef.ts', 'classes.ts',
'newExpression.ts', 'errors.ts', 'declared.ts'
]);
service = ts.createLanguageService(host, documentRegistry);
program = service.getProgram();
typeChecker = program.getTypeChecker();
symbols = new Symbols(null as any as ts.SourceFile);
evaluator = new Evaluator(symbols, new Map());
});
it('should not have typescript errors in test data', () => {
expectNoDiagnostics(service.getCompilerOptionsDiagnostics());
for (const sourceFile of program.getSourceFiles()) {
expectNoDiagnostics(service.getSyntacticDiagnostics(sourceFile.fileName));
if (sourceFile.fileName != 'errors.ts') {
// Skip errors.ts because we it has intentional semantic errors that we are testing for.
expectNoDiagnostics(service.getSemanticDiagnostics(sourceFile.fileName));
}
}
});
it('should be able to fold literal expressions', () => {
const consts = program.getSourceFile('consts.ts');
expect(evaluator.isFoldable(findVarInitializer(consts, 'someName'))).toBeTruthy();
expect(evaluator.isFoldable(findVarInitializer(consts, 'someBool'))).toBeTruthy();
expect(evaluator.isFoldable(findVarInitializer(consts, 'one'))).toBeTruthy();
expect(evaluator.isFoldable(findVarInitializer(consts, 'two'))).toBeTruthy();
});
it('should be able to fold expressions with foldable references', () => {
const expressions = program.getSourceFile('expressions.ts');
symbols.define('someName', 'some-name');
symbols.define('someBool', true);
symbols.define('one', 1);
symbols.define('two', 2);
expect(evaluator.isFoldable(findVarInitializer(expressions, 'three'))).toBeTruthy();
expect(evaluator.isFoldable(findVarInitializer(expressions, 'four'))).toBeTruthy();
symbols.define('three', 3);
symbols.define('four', 4);
expect(evaluator.isFoldable(findVarInitializer(expressions, 'obj'))).toBeTruthy();
expect(evaluator.isFoldable(findVarInitializer(expressions, 'arr'))).toBeTruthy();
});
it('should be able to evaluate literal expressions', () => {
const consts = program.getSourceFile('consts.ts');
expect(evaluator.evaluateNode(findVarInitializer(consts, 'someName'))).toBe('some-name');
expect(evaluator.evaluateNode(findVarInitializer(consts, 'someBool'))).toBe(true);
expect(evaluator.evaluateNode(findVarInitializer(consts, 'one'))).toBe(1);
expect(evaluator.evaluateNode(findVarInitializer(consts, 'two'))).toBe(2);
});
it('should be able to evaluate expressions', () => {
const expressions = program.getSourceFile('expressions.ts');
symbols.define('someName', 'some-name');
symbols.define('someBool', true);
symbols.define('one', 1);
symbols.define('two', 2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'three'))).toBe(3);
symbols.define('three', 3);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'four'))).toBe(4);
symbols.define('four', 4);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'obj')))
.toEqual({one: 1, two: 2, three: 3, four: 4});
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'arr'))).toEqual([1, 2, 3, 4]);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bTrue'))).toEqual(true);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bFalse'))).toEqual(false);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bAnd'))).toEqual(true);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bOr'))).toEqual(true);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'nDiv'))).toEqual(2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'nMod'))).toEqual(1);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bLOr'))).toEqual(false || true);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bLAnd'))).toEqual(true && true);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bBOr'))).toEqual(0x11 | 0x22);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bBAnd'))).toEqual(0x11 & 0x03);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bXor'))).toEqual(0x11 ^ 0x21);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bEqual')))
.toEqual(1 == <any>'1');
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bNotEqual')))
.toEqual(1 != <any>'1');
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bIdentical')))
.toEqual(1 === <any>'1');
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bNotIdentical')))
.toEqual(1 !== <any>'1');
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bLessThan'))).toEqual(1 < 2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bGreaterThan'))).toEqual(1 > 2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bLessThanEqual')))
.toEqual(1 <= 2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bGreaterThanEqual')))
.toEqual(1 >= 2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bShiftLeft'))).toEqual(1 << 2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bShiftRight'))).toEqual(-1 >> 2);
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bShiftRightU')))
.toEqual(-1 >>> 2);
});
it('should report recursive references as symbolic', () => {
const expressions = program.getSourceFile('expressions.ts');
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'recursiveA')))
.toEqual({__symbolic: 'reference', name: 'recursiveB'});
expect(evaluator.evaluateNode(findVarInitializer(expressions, 'recursiveB')))
.toEqual({__symbolic: 'reference', name: 'recursiveA'});
});
it('should correctly handle special cases for CONST_EXPR', () => {
const const_expr = program.getSourceFile('const_expr.ts');
expect(evaluator.evaluateNode(findVarInitializer(const_expr, 'bTrue'))).toEqual(true);
expect(evaluator.evaluateNode(findVarInitializer(const_expr, 'bFalse'))).toEqual(false);
});
it('should resolve a forwardRef', () => {
const forwardRef = program.getSourceFile('forwardRef.ts');
expect(evaluator.evaluateNode(findVarInitializer(forwardRef, 'bTrue'))).toEqual(true);
expect(evaluator.evaluateNode(findVarInitializer(forwardRef, 'bFalse'))).toEqual(false);
});
it('should return new expressions', () => {
symbols.define('Value', {__symbolic: 'reference', module: './classes', name: 'Value'});
evaluator = new Evaluator(symbols, new Map());
const newExpression = program.getSourceFile('newExpression.ts');
expect(evaluator.evaluateNode(findVarInitializer(newExpression, 'someValue'))).toEqual({
__symbolic: 'new',
expression: {__symbolic: 'reference', name: 'Value', module: './classes'},
arguments: ['name', 12]
});
expect(evaluator.evaluateNode(findVarInitializer(newExpression, 'complex'))).toEqual({
__symbolic: 'new',
expression: {__symbolic: 'reference', name: 'Value', module: './classes'},
arguments: ['name', 12]
});
});
it('should support referene to a declared module type', () => {
const declared = program.getSourceFile('declared.ts');
const aDecl = findVar(declared, 'a') !;
expect(evaluator.evaluateNode(aDecl.type !)).toEqual({
__symbolic: 'select',
expression: {__symbolic: 'reference', name: 'Foo'},
member: 'A'
});
});
it('should return errors for unsupported expressions', () => {
const errors = program.getSourceFile('errors.ts');
const fDecl = findVar(errors, 'f') !;
expect(evaluator.evaluateNode(fDecl.initializer !))
.toEqual(
{__symbolic: 'error', message: 'Function call not supported', line: 1, character: 12});
const eDecl = findVar(errors, 'e') !;
expect(evaluator.evaluateNode(eDecl.type !)).toEqual({
__symbolic: 'error',
message: 'Could not resolve type',
line: 2,
character: 11,
context: {typeName: 'NotFound'}
});
const sDecl = findVar(errors, 's') !;
expect(evaluator.evaluateNode(sDecl.initializer !)).toEqual({
__symbolic: 'error',
message: 'Name expected',
line: 3,
character: 14,
context: {received: '1'}
});
const tDecl = findVar(errors, 't') !;
expect(evaluator.evaluateNode(tDecl.initializer !)).toEqual({
__symbolic: 'error',
message: 'Expression form not supported',
line: 4,
character: 12
});
});
it('should be able to fold an array spread', () => {
const expressions = program.getSourceFile('expressions.ts');
symbols.define('arr', [1, 2, 3, 4]);
const arrSpread = findVar(expressions, 'arrSpread') !;
expect(evaluator.evaluateNode(arrSpread.initializer !)).toEqual([0, 1, 2, 3, 4, 5]);
});
it('should be able to produce a spread expression', () => {
const expressions = program.getSourceFile('expressions.ts');
const arrSpreadRef = findVar(expressions, 'arrSpreadRef') !;
expect(evaluator.evaluateNode(arrSpreadRef.initializer !)).toEqual([
0, {__symbolic: 'spread', expression: {__symbolic: 'reference', name: 'arrImport'}}, 5
]);
});
it('should be able to handle a new expression with no arguments', () => {
const source = sourceFileOf(`
export var a = new f;
`);
const expr = findVar(source, 'a') !;
expect(evaluator.evaluateNode(expr.initializer !))
.toEqual({__symbolic: 'new', expression: {__symbolic: 'reference', name: 'f'}});
});
describe('with substitution', () => {
let evaluator: Evaluator;
const lambdaTemp = 'lambdaTemp';
beforeEach(() => {
evaluator = new Evaluator(symbols, new Map(), {
substituteExpression: (value, node) => {
if (node.kind == ts.SyntaxKind.ArrowFunction) {
return {__symbolic: 'reference', name: lambdaTemp};
}
return value;
}
});
});
it('should be able to substitute a lambda with a reference', () => {
const source = sourceFileOf(`
var b = 1;
export var a = () => b;
`);
const expr = findVar(source, 'a');
expect(evaluator.evaluateNode(expr !.initializer !))
.toEqual({__symbolic: 'reference', name: lambdaTemp});
});
it('should be able to substitute a lambda in an expression', () => {
const source = sourceFileOf(`
var b = 1;
export var a = [
{ provide: 'someValue': useFactory: () => b }
];
`);
const expr = findVar(source, 'a');
expect(evaluator.evaluateNode(expr !.initializer !)).toEqual([
{provide: 'someValue', useFactory: {__symbolic: 'reference', name: lambdaTemp}}
]);
});
});
});
function sourceFileOf(text: string): ts.SourceFile {
return ts.createSourceFile('test.ts', text, ts.ScriptTarget.Latest, true);
}
const FILES: Directory = {
'directives.ts': `
export function Pipe(options: { name?: string, pure?: boolean}) {
return function(fn: Function) { }
}
`,
'classes.ts': `
export class Value {
constructor(public name: string, public value: any) {}
}
`,
'consts.ts': `
export var someName = 'some-name';
export var someBool = true;
export var one = 1;
export var two = 2;
export var arrImport = [1, 2, 3, 4];
`,
'expressions.ts': `
import {arrImport} from './consts';
export var someName = 'some-name';
export var someBool = true;
export var one = 1;
export var two = 2;
export var three = one + two;
export var four = two * two;
export var obj = { one: one, two: two, three: three, four: four };
export var arr = [one, two, three, four];
export var bTrue = someBool;
export var bFalse = !someBool;
export var bAnd = someBool && someBool;
export var bOr = someBool || someBool;
export var nDiv = four / two;
export var nMod = (four + one) % two;
export var bLOr = false || true; // true
export var bLAnd = true && true; // true
export var bBOr = 0x11 | 0x22; // 0x33
export var bBAnd = 0x11 & 0x03; // 0x01
export var bXor = 0x11 ^ 0x21; // 0x20
export var bEqual = 1 == <any>"1"; // true
export var bNotEqual = 1 != <any>"1"; // false
export var bIdentical = 1 === <any>"1"; // false
export var bNotIdentical = 1 !== <any>"1"; // true
export var bLessThan = 1 < 2; // true
export var bGreaterThan = 1 > 2; // false
export var bLessThanEqual = 1 <= 2; // true
export var bGreaterThanEqual = 1 >= 2; // false
export var bShiftLeft = 1 << 2; // 0x04
export var bShiftRight = -1 >> 2; // -1
export var bShiftRightU = -1 >>> 2; // 0x3fffffff
export var arrSpread = [0, ...arr, 5];
export var arrSpreadRef = [0, ...arrImport, 5];
export var recursiveA = recursiveB;
export var recursiveB = recursiveA;
`,
'A.ts': `
import {Pipe} from './directives';
@Pipe({name: 'A', pure: false})
export class A {}`,
'B.ts': `
import {Pipe} from './directives';
import {someName, someBool} from './consts';
@Pipe({name: someName, pure: someBool})
export class B {}`,
'const_expr.ts': `
function CONST_EXPR(value: any) { return value; }
export var bTrue = CONST_EXPR(true);
export var bFalse = CONST_EXPR(false);
`,
'forwardRef.ts': `
function forwardRef(value: any) { return value; }
export var bTrue = forwardRef(() => true);
export var bFalse = forwardRef(() => false);
`,
'newExpression.ts': `
import {Value} from './classes';
function CONST_EXPR(value: any) { return value; }
function forwardRef(value: any) { return value; }
export const someValue = new Value("name", 12);
export const complex = CONST_EXPR(new Value("name", forwardRef(() => 12)));
`,
'errors.ts': `
let f = () => 1;
let e: NotFound;
let s = { 1: 1, 2: 2 };
let t = typeof 12;
`,
'declared.ts': `
declare namespace Foo {
type A = string;
}
let a: Foo.A = 'some value';
`
};

View File

@ -0,0 +1,25 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {MetadataBundler} from '../../src/metadata/bundler';
import {MetadataCollector} from '../../src/metadata/collector';
import {privateEntriesToIndex} from '../../src/metadata/index_writer';
import {MockStringBundlerHost, SIMPLE_LIBRARY} from './bundler_spec';
describe('index_writer', () => {
it('should be able to write the index of a simple library', () => {
const host = new MockStringBundlerHost('/', SIMPLE_LIBRARY);
const bundler = new MetadataBundler('/lib/index', undefined, host);
const bundle = bundler.getMetadataBundle();
const result = privateEntriesToIndex('./index', bundle.privates);
expect(result).toContain(`export * from './index';`);
expect(result).toContain(`export {PrivateOne as ɵa} from './src/one';`);
expect(result).toContain(`export {PrivateTwo as ɵb} from './src/two/index';`);
});
});

View File

@ -0,0 +1,133 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {isMetadataGlobalReferenceExpression} from '../../src/metadata/schema';
import {Symbols} from '../../src/metadata/symbols';
import {Directory, Host, expectNoDiagnostics} from './typescript.mocks';
describe('Symbols', () => {
let symbols: Symbols;
const someValue = 'some-value';
beforeEach(() => symbols = new Symbols(null as any as ts.SourceFile));
it('should be able to add a symbol', () => symbols.define('someSymbol', someValue));
beforeEach(() => symbols.define('someSymbol', someValue));
it('should be able to `has` a symbol', () => expect(symbols.has('someSymbol')).toBeTruthy());
it('should be able to `get` a symbol value',
() => expect(symbols.resolve('someSymbol')).toBe(someValue));
it('should be able to `get` a symbol value',
() => expect(symbols.resolve('someSymbol')).toBe(someValue));
it('should be able to determine symbol is missing',
() => expect(symbols.has('missingSymbol')).toBeFalsy());
it('should return undefined from `get` for a missing symbol',
() => expect(symbols.resolve('missingSymbol')).toBeUndefined());
let host: ts.LanguageServiceHost;
let service: ts.LanguageService;
let program: ts.Program;
let expressions: ts.SourceFile;
let imports: ts.SourceFile;
beforeEach(() => {
host = new Host(FILES, ['consts.ts', 'expressions.ts', 'imports.ts']);
service = ts.createLanguageService(host);
program = service.getProgram();
expressions = program.getSourceFile('expressions.ts');
imports = program.getSourceFile('imports.ts');
});
it('should not have syntax errors in the test sources', () => {
expectNoDiagnostics(service.getCompilerOptionsDiagnostics());
for (const sourceFile of program.getSourceFiles()) {
expectNoDiagnostics(service.getSyntacticDiagnostics(sourceFile.fileName));
}
});
it('should be able to find the source files', () => {
expect(expressions).toBeDefined();
expect(imports).toBeDefined();
});
it('should be able to create symbols for a source file', () => {
const symbols = new Symbols(expressions);
expect(symbols).toBeDefined();
});
it('should be able to find symbols in expression', () => {
const symbols = new Symbols(expressions);
expect(symbols.has('someName')).toBeTruthy();
expect(symbols.resolve('someName'))
.toEqual({__symbolic: 'reference', module: './consts', name: 'someName'});
expect(symbols.has('someBool')).toBeTruthy();
expect(symbols.resolve('someBool'))
.toEqual({__symbolic: 'reference', module: './consts', name: 'someBool'});
});
it('should be able to detect a * import', () => {
const symbols = new Symbols(imports);
expect(symbols.resolve('b')).toEqual({__symbolic: 'reference', module: 'b'});
});
it('should be able to detect importing a default export', () => {
const symbols = new Symbols(imports);
expect(symbols.resolve('d')).toEqual({__symbolic: 'reference', module: 'd', default: true});
});
it('should be able to import a renamed symbol', () => {
const symbols = new Symbols(imports);
expect(symbols.resolve('g')).toEqual({__symbolic: 'reference', name: 'f', module: 'f'});
});
it('should be able to resolve any symbol in core global scope', () => {
const core = program.getSourceFiles().find(source => source.fileName.endsWith('lib.d.ts'));
expect(core).toBeDefined();
const visit = (node: ts.Node): boolean => {
switch (node.kind) {
case ts.SyntaxKind.VariableStatement:
case ts.SyntaxKind.VariableDeclarationList:
return !!ts.forEachChild(node, visit);
case ts.SyntaxKind.VariableDeclaration:
const variableDeclaration = <ts.VariableDeclaration>node;
const nameNode = <ts.Identifier>variableDeclaration.name;
const name = nameNode.text;
const result = symbols.resolve(name);
expect(isMetadataGlobalReferenceExpression(result) && result.name).toEqual(name);
// Ignore everything after Float64Array as it is IE specific.
return name === 'Float64Array';
}
return false;
};
ts.forEachChild(core !, visit);
});
});
const FILES: Directory = {
'consts.ts': `
export var someName = 'some-name';
export var someBool = true;
export var one = 1;
export var two = 2;
`,
'expressions.ts': `
import {someName, someBool, one, two} from './consts';
`,
'imports.ts': `
import * as b from 'b';
import 'c';
import d from 'd';
import {f as g} from 'f';
`
};

View File

@ -0,0 +1,204 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import * as ts from 'typescript';
export interface Directory { [name: string]: (Directory|string); }
export class Host implements ts.LanguageServiceHost {
private overrides = new Map<string, string>();
private version = 1;
constructor(private directory: Directory, private scripts: string[]) {}
getCompilationSettings(): ts.CompilerOptions {
return {
experimentalDecorators: true,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES5
};
}
getScriptFileNames(): string[] { return this.scripts; }
getScriptVersion(fileName: string): string { return this.version.toString(); }
getScriptSnapshot(fileName: string): ts.IScriptSnapshot|undefined {
const content = this.getFileContent(fileName);
if (content) return ts.ScriptSnapshot.fromString(content);
}
fileExists(fileName: string): boolean { return this.getFileContent(fileName) != null; }
getCurrentDirectory(): string { return '/'; }
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|undefined {
if (this.overrides.has(fileName)) {
return this.overrides.get(fileName);
}
if (fileName.endsWith('lib.d.ts')) {
return fs.readFileSync(ts.getDefaultLibFilePath(this.getCompilationSettings()), 'utf8');
}
const current = open(this.directory, fileName);
if (typeof current === 'string') return current;
}
}
export function open(directory: Directory, fileName: string): Directory|string|undefined {
// Path might be normalized by the current node environment. But it could also happen that this
// path directly comes from the compiler in POSIX format. Support both separators for development.
const names = fileName.split(/[\\/]/);
let current: Directory|string = directory;
if (names.length && names[0] === '') names.shift();
for (const name of names) {
if (!current || typeof current === 'string') return undefined;
current = current[name];
}
return current;
}
export class MockNode implements ts.Node {
constructor(
public kind: ts.SyntaxKind = ts.SyntaxKind.Identifier, public flags: ts.NodeFlags = 0,
public pos: number = 0, public end: number = 0) {}
getSourceFile(): ts.SourceFile { return null as any as ts.SourceFile; }
getChildCount(sourceFile?: ts.SourceFile): number { return 0; }
getChildAt(index: number, sourceFile?: ts.SourceFile): ts.Node { return null as any as ts.Node; }
getChildren(sourceFile?: ts.SourceFile): ts.Node[] { return []; }
getStart(sourceFile?: ts.SourceFile): number { return 0; }
getFullStart(): number { return 0; }
getEnd(): number { return 0; }
getWidth(sourceFile?: ts.SourceFile): number { return 0; }
getFullWidth(): number { return 0; }
getLeadingTriviaWidth(sourceFile?: ts.SourceFile): number { return 0; }
getFullText(sourceFile?: ts.SourceFile): string { return ''; }
getText(sourceFile?: ts.SourceFile): string { return ''; }
getFirstToken(sourceFile?: ts.SourceFile): ts.Node { return null as any as ts.Node; }
getLastToken(sourceFile?: ts.SourceFile): ts.Node { return null as any as ts.Node; }
forEachChild<T>(
cbNode: (node: ts.Node) => T | undefined,
cbNodeArray?: (nodes: ts.NodeArray<ts.Node>) => T | undefined): T|undefined {
return undefined;
}
}
export class MockIdentifier extends MockNode implements ts.Identifier {
public text: string;
// tslint:disable
public _primaryExpressionBrand: any;
public _memberExpressionBrand: any;
public _leftHandSideExpressionBrand: any;
public _incrementExpressionBrand: any;
public _unaryExpressionBrand: any;
public _expressionBrand: any;
public _updateExpressionBrand: any;
// tslint:enable
constructor(
public name: string, public kind: ts.SyntaxKind.Identifier = ts.SyntaxKind.Identifier,
flags: ts.NodeFlags = 0, pos: number = 0, end: number = 0) {
super(kind, flags, pos, end);
this.text = name;
}
}
export class MockVariableDeclaration extends MockNode implements ts.VariableDeclaration {
// tslint:disable-next-line
public _declarationBrand: any;
constructor(
public name: ts.Identifier,
public kind: ts.SyntaxKind.VariableDeclaration = ts.SyntaxKind.VariableDeclaration,
flags: ts.NodeFlags = 0, pos: number = 0, end: number = 0) {
super(kind, flags, pos, end);
}
static of (name: string): MockVariableDeclaration {
return new MockVariableDeclaration(new MockIdentifier(name));
}
}
export class MockSymbol implements ts.Symbol {
constructor(
public name: string, private node: ts.Declaration = MockVariableDeclaration.of(name),
public flags: ts.SymbolFlags = 0) {}
getFlags(): ts.SymbolFlags { return this.flags; }
getName(): string { return this.name; }
getDeclarations(): ts.Declaration[] { return [this.node]; }
getDocumentationComment(): ts.SymbolDisplayPart[] { return []; }
// TODO(vicb): removed in TS 2.2
getJsDocTags(): any[] { return []; }
static of (name: string): MockSymbol { return new MockSymbol(name); }
}
export function expectNoDiagnostics(diagnostics: ts.Diagnostic[]) {
for (const diagnostic of diagnostics) {
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
if (diagnostic.file && diagnostic.start) {
const {line, character} = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
// tslint:disable-next-line:no-console
console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
}
}
expect(diagnostics.length).toBe(0);
}
export function expectValidSources(service: ts.LanguageService, program: ts.Program) {
expectNoDiagnostics(service.getCompilerOptionsDiagnostics());
for (const sourceFile of program.getSourceFiles()) {
expectNoDiagnostics(service.getSyntacticDiagnostics(sourceFile.fileName));
expectNoDiagnostics(service.getSemanticDiagnostics(sourceFile.fileName));
}
}
export function allChildren<T>(node: ts.Node, cb: (node: ts.Node) => T | undefined): T|undefined {
return ts.forEachChild(node, child => cb(node) || allChildren(child, cb));
}
export function findClass(sourceFile: ts.SourceFile, name: string): ts.ClassDeclaration|undefined {
return ts.forEachChild(
sourceFile, node => isClass(node) && isNamed(node.name, name) ? node : undefined);
}
export function findVar(sourceFile: ts.SourceFile, name: string): ts.VariableDeclaration|undefined {
return allChildren(
sourceFile, node => isVar(node) && isNamed(node.name, name) ? node : undefined);
}
export function findVarInitializer(sourceFile: ts.SourceFile, name: string): ts.Expression {
const v = findVar(sourceFile, name);
expect(v && v.initializer).toBeDefined();
return v !.initializer !;
}
export function isClass(node: ts.Node): node is ts.ClassDeclaration {
return node.kind === ts.SyntaxKind.ClassDeclaration;
}
export function isNamed(node: ts.Node | undefined, name: string): node is ts.Identifier {
return !!node && node.kind === ts.SyntaxKind.Identifier && (<ts.Identifier>node).text === name;
}
export function isVar(node: ts.Node): node is ts.VariableDeclaration {
return node.kind === ts.SyntaxKind.VariableDeclaration;
}

View File

@ -6,12 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/
import {makeTempDir} from '@angular/tsc-wrapped/test/test_support';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {mainSync, readCommandLineAndConfiguration, watchMode} from '../src/main';
import {main, readCommandLineAndConfiguration, watchMode} from '../src/main';
import {makeTempDir} from './test_support';
function getNgRootDir() {
const moduleFilename = module.filename.replace(/\\/g, '/');
@ -84,7 +84,7 @@ describe('ngc transformer command-line', () => {
writeConfig();
write('test.ts', 'export const A = 1;');
const exitCode = mainSync(['-p', basePath], errorSpy);
const exitCode = main(['-p', basePath], errorSpy);
expect(errorSpy).not.toHaveBeenCalled();
expect(exitCode).toBe(0);
});
@ -99,7 +99,7 @@ describe('ngc transformer command-line', () => {
"files": ["test.ts"]
}`);
const exitCode = mainSync(['-p', basePath], errorSpy);
const exitCode = main(['-p', basePath], errorSpy);
expect(errorSpy).toHaveBeenCalledWith(
`error TS6053: File '` + path.join(basePath, 'test.ts') + `' not found.` +
'\n');
@ -110,7 +110,7 @@ describe('ngc transformer command-line', () => {
writeConfig();
write('test.ts', 'foo;');
const exitCode = mainSync(['-p', basePath], errorSpy);
const exitCode = main(['-p', basePath], errorSpy);
expect(errorSpy).toHaveBeenCalledWith(
`test.ts(1,1): error TS2304: Cannot find name 'foo'.` +
'\n');
@ -121,7 +121,7 @@ describe('ngc transformer command-line', () => {
writeConfig();
write('test.ts', `import {MyClass} from './not-exist-deps';`);
const exitCode = mainSync(['-p', basePath], errorSpy);
const exitCode = main(['-p', basePath], errorSpy);
expect(errorSpy).toHaveBeenCalledWith(
`test.ts(1,23): error TS2307: Cannot find module './not-exist-deps'.` +
'\n');
@ -133,7 +133,7 @@ describe('ngc transformer command-line', () => {
write('empty-deps.ts', 'export const A = 1;');
write('test.ts', `import {MyClass} from './empty-deps';`);
const exitCode = mainSync(['-p', basePath], errorSpy);
const exitCode = main(['-p', basePath], errorSpy);
expect(errorSpy).toHaveBeenCalledWith(
`test.ts(1,9): error TS2305: Module '"` + path.join(basePath, 'empty-deps') +
`"' has no exported member 'MyClass'.` +
@ -149,7 +149,7 @@ describe('ngc transformer command-line', () => {
A();
`);
const exitCode = mainSync(['-p', basePath], errorSpy);
const exitCode = main(['-p', basePath], errorSpy);
expect(errorSpy).toHaveBeenCalledWith(
'test.ts(3,9): error TS2349: Cannot invoke an expression whose type lacks a call signature. ' +
'Type \'String\' has no compatible call signatures.\n');
@ -159,7 +159,7 @@ describe('ngc transformer command-line', () => {
it('should print the stack trace on compiler internal errors', () => {
write('test.ts', 'export const A = 1;');
const exitCode = mainSync(['-p', 'not-exist'], errorSpy);
const exitCode = main(['-p', 'not-exist'], errorSpy);
expect(errorSpy).toHaveBeenCalledTimes(1);
expect(errorSpy.calls.mostRecent().args[0]).toContain('no such file or directory');
expect(errorSpy.calls.mostRecent().args[0]).toContain('at Error (native)');
@ -181,7 +181,7 @@ describe('ngc transformer command-line', () => {
export class MyModule {}
`);
const exitCode = mainSync(['-p', basePath], errorSpy);
const exitCode = main(['-p', basePath], errorSpy);
expect(errorSpy).toHaveBeenCalledTimes(1);
expect(errorSpy.calls.mostRecent().args[0])
.toContain('Error at ng://' + path.join(basePath, 'mymodule.ts.MyComp.html'));
@ -212,7 +212,7 @@ describe('ngc transformer command-line', () => {
export class MyModule {}
`);
const exitCode = mainSync(['-p', basePath], errorSpy);
const exitCode = main(['-p', basePath], errorSpy);
expect(errorSpy).toHaveBeenCalledTimes(1);
expect(errorSpy.calls.mostRecent().args[0])
.toContain('Error at ng://' + path.join(basePath, 'my.component.html(1,5):'));
@ -240,7 +240,7 @@ describe('ngc transformer command-line', () => {
export class MyModule {}
`);
const exitCode = mainSync(['-p', basePath], errorSpy);
const exitCode = main(['-p', basePath], errorSpy);
expect(exitCode).toEqual(0);
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true);
@ -265,7 +265,7 @@ describe('ngc transformer command-line', () => {
export class MyModule {}
`);
const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);
expect(fs.existsSync(path.resolve(outDir, 'mymodule.ngfactory.js'))).toBe(true);
expect(fs.existsSync(path.resolve(
@ -367,7 +367,7 @@ describe('ngc transformer command-line', () => {
},
"include": ["src/**/*.ts"]
}`);
const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);
outDir = path.resolve(basePath, 'built', 'src');
expectJsDtsMetadataJsonToExist();
@ -387,7 +387,7 @@ describe('ngc transformer command-line', () => {
},
"include": ["src/**/*.ts"]
}`);
let exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
let exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);
outDir = path.resolve(basePath, 'lib', 'src');
modules.forEach(moduleName => {
@ -420,7 +420,7 @@ describe('ngc transformer command-line', () => {
}`);
write('lib/src/plain.css', 'div {}');
write('lib/src/emulated.css', 'div {}');
exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);
outDir = path.resolve(basePath, 'built', 'lib', 'src');
expectAllGeneratedFilesToExist();
@ -443,7 +443,7 @@ describe('ngc transformer command-line', () => {
export class MyModule {}
`);
const exitCode = mainSync(['-p', basePath], errorSpy);
const exitCode = main(['-p', basePath], errorSpy);
expect(exitCode).toEqual(0);
const mymodulejs = path.resolve(outDir, 'mymodule.js');
@ -472,7 +472,7 @@ describe('ngc transformer command-line', () => {
export class MyModule {}
`);
const exitCode = mainSync(['-p', basePath], errorSpy);
const exitCode = main(['-p', basePath], errorSpy);
expect(exitCode).toEqual(0);
const mymodulejs = path.resolve(outDir, 'mymodule.js');
@ -501,7 +501,7 @@ describe('ngc transformer command-line', () => {
export class MyModule {}
`);
const exitCode = mainSync(['-p', basePath], errorSpy);
const exitCode = main(['-p', basePath], errorSpy);
expect(exitCode).toEqual(0);
const mymodulejs = path.resolve(outDir, 'mymodule.js');
@ -519,7 +519,8 @@ describe('ngc transformer command-line', () => {
});
function compile(): number {
const result = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
errorSpy.calls.reset();
const result = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(errorSpy).not.toHaveBeenCalled();
return result;
}
@ -710,7 +711,7 @@ describe('ngc transformer command-line', () => {
}
`);
expect(mainSync(['-p', basePath], errorSpy)).toBe(0);
expect(main(['-p', basePath], errorSpy)).toBe(0);
shouldExist('module.js');
});
});
@ -756,7 +757,7 @@ describe('ngc transformer command-line', () => {
export class FlatModule {
}`);
const exitCode = mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);
shouldExist('index.js');
shouldExist('index.metadata.json');
@ -794,7 +795,7 @@ describe('ngc transformer command-line', () => {
});
it('should compile without error', () => {
expect(mainSync(['-p', path.join(basePath, 'tsconfig.json')], errorSpy)).toBe(0);
expect(main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy)).toBe(0);
});
});
@ -883,10 +884,10 @@ describe('ngc transformer command-line', () => {
}
`);
expect(mainSync(['-p', path.join(basePath, 'tsconfig-ng.json')], errorSpy)).toBe(0);
expect(mainSync(['-p', path.join(basePath, 'lib1', 'tsconfig-lib1.json')], errorSpy)).toBe(0);
expect(mainSync(['-p', path.join(basePath, 'lib2', 'tsconfig-lib2.json')], errorSpy)).toBe(0);
expect(mainSync(['-p', path.join(basePath, 'app', 'tsconfig-app.json')], errorSpy)).toBe(0);
expect(main(['-p', path.join(basePath, 'tsconfig-ng.json')], errorSpy)).toBe(0);
expect(main(['-p', path.join(basePath, 'lib1', 'tsconfig-lib1.json')], errorSpy)).toBe(0);
expect(main(['-p', path.join(basePath, 'lib2', 'tsconfig-lib2.json')], errorSpy)).toBe(0);
expect(main(['-p', path.join(basePath, 'app', 'tsconfig-app.json')], errorSpy)).toBe(0);
// library 1
// make `shouldExist` / `shouldNotExist` relative to `node_modules`
@ -915,6 +916,80 @@ describe('ngc transformer command-line', () => {
});
});
describe('expression lowering', () => {
const shouldExist = (fileName: string) => {
if (!fs.existsSync(path.resolve(basePath, fileName))) {
throw new Error(`Expected ${fileName} to be emitted (basePath: ${basePath})`);
}
};
it('should be able to lower supported expressions', () => {
writeConfig(`{
"extends": "./tsconfig-base.json",
"files": ["module.ts"]
}`);
write('module.ts', `
import {NgModule, InjectionToken} from '@angular/core';
import {AppComponent} from './app';
export interface Info {
route: string;
data: string;
}
export const T1 = new InjectionToken<string>('t1');
export const T2 = new InjectionToken<string>('t2');
export const T3 = new InjectionToken<number>('t3');
export const T4 = new InjectionToken<Info[]>('t4');
enum SomeEnum {
OK,
Cancel
}
function calculateString() {
return 'someValue';
}
const routeLikeData = [{
route: '/home',
data: calculateString()
}];
@NgModule({
declarations: [AppComponent],
providers: [
{ provide: T1, useValue: calculateString() },
{ provide: T2, useFactory: () => 'someValue' },
{ provide: T3, useValue: SomeEnum.OK },
{ provide: T4, useValue: routeLikeData }
]
})
export class MyModule {}
`);
write('app.ts', `
import {Component, Inject} from '@angular/core';
import * as m from './module';
@Component({
selector: 'my-app',
template: ''
})
export class AppComponent {
constructor(
@Inject(m.T1) private t1: string,
@Inject(m.T2) private t2: string,
@Inject(m.T3) private t3: number,
@Inject(m.T4) private t4: m.Info[],
) {}
}
`);
expect(main(['-p', basePath], s => {})).toBe(0);
shouldExist('built/module.js');
});
});
describe('watch mode', () => {
let timer: (() => void)|undefined = undefined;
let results: ((message: string) => void)|undefined = undefined;

View File

@ -0,0 +1,28 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
const tmpdir = process.env.TEST_TMPDIR || os.tmpdir();
export function writeTempFile(name: string, contents: string): string {
// TEST_TMPDIR is set by bazel.
const id = (Math.random() * 1000000).toFixed(0);
const fn = path.join(tmpdir, `tmp.${id}.${name}`);
fs.writeFileSync(fn, contents);
return fn;
}
export function makeTempDir(): string {
const id = (Math.random() * 1000000).toFixed(0);
const dir = path.join(tmpdir, `tmp.${id}`);
fs.mkdirSync(dir);
return dir;
}

View File

@ -6,9 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {ModuleMetadata} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
import {ModuleMetadata} from '../../src/metadata/index';
import {LowerMetadataCache, LoweringRequest, RequestLocationMap, getExpressionLoweringTransformFactory} from '../../src/transformers/lower_expressions';
import {Directory, MockAotContext, MockCompilerHost} from '../mocks';

View File

@ -7,8 +7,9 @@
*/
import {AotSummaryResolver, GeneratedFile, StaticSymbolCache, StaticSymbolResolver, toTypeScript} from '@angular/compiler';
import {MetadataBundler} from '@angular/compiler-cli/src/metadata/bundler';
import {privateEntriesToIndex} from '@angular/compiler-cli/src/metadata/index_writer';
import {NodeFlags} from '@angular/core/src/view/index';
import {MetadataBundler, privateEntriesToIndex} from '@angular/tsc-wrapped';
import * as ts from 'typescript';
import {extractSourceMap, originalPositionFor} from '../output/source_map_util';

View File

@ -7,7 +7,7 @@
*/
import {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost, core as compilerCore} from '@angular/compiler';
import {CollectorOptions} from '@angular/tsc-wrapped';
import {CollectorOptions} from '@angular/compiler-cli/src/metadata/index';
import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec';

View File

@ -7,7 +7,8 @@
*/
import {StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost, Summary, SummaryResolver} from '@angular/compiler';
import {CollectorOptions, MetadataCollector} from '@angular/tsc-wrapped';
import {MetadataCollector} from '@angular/compiler-cli/src/metadata/collector';
import {CollectorOptions} from '@angular/compiler-cli/src/metadata/index';
import * as ts from 'typescript';

View File

@ -7,7 +7,9 @@
*/
import {AotCompilerHost, AotCompilerOptions, GeneratedFile, createAotCompiler, toTypeScript} from '@angular/compiler';
import {MetadataBundlerHost, MetadataCollector, ModuleMetadata} from '@angular/tsc-wrapped';
import {MetadataBundlerHost} from '@angular/compiler-cli/src/metadata/bundler';
import {MetadataCollector} from '@angular/compiler-cli/src/metadata/collector';
import {ModuleMetadata} from '@angular/compiler-cli/src/metadata/index';
import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';

View File

@ -7,7 +7,7 @@
*/
import {AotCompilerHost} from '@angular/compiler';
import {AngularCompilerOptions, CompilerHost, ModuleResolutionHostAdapter} from '@angular/compiler-cli/src/language_services';
import {CompilerHost, CompilerOptions, ModuleResolutionHostAdapter} from '@angular/compiler-cli/src/language_services';
import * as ts from 'typescript';
class ReflectorModuleModuleResolutionHost implements ts.ModuleResolutionHost {
@ -37,7 +37,7 @@ class ReflectorModuleModuleResolutionHost implements ts.ModuleResolutionHost {
export class ReflectorHost extends CompilerHost {
constructor(
private getProgram: () => ts.Program, serviceHost: ts.LanguageServiceHost,
options: AngularCompilerOptions) {
options: CompilerOptions) {
super(
// The ancestor value for program is overridden below so passing null here is safe.
/* program */ null !, options,

View File

@ -7,7 +7,7 @@
*/
import {AotSummaryResolver, CompileMetadataResolver, CompilerConfig, DEFAULT_INTERPOLATION_CONFIG, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, HtmlParser, InterpolationConfig, JitSummaryResolver, NgAnalyzedModules, NgModuleResolver, ParseTreeResult, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, SummaryResolver, analyzeNgModules, createOfflineCompileUrlResolver, extractProgramSymbols} from '@angular/compiler';
import {AngularCompilerOptions, getClassMembersFromDeclaration, getPipesTable, getSymbolQuery} from '@angular/compiler-cli/src/language_services';
import {CompilerOptions, getClassMembersFromDeclaration, getPipesTable, getSymbolQuery} from '@angular/compiler-cli/src/language_services';
import {ViewEncapsulation, ɵConsole as Console} from '@angular/core';
import * as fs from 'fs';
import * as path from 'path';
@ -372,7 +372,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
const tsConfigPath = findTsConfig(source.fileName);
const basePath = path.dirname(tsConfigPath || this.context);
const options: AngularCompilerOptions = {basePath, genDir: basePath};
const options: CompilerOptions = {basePath, genDir: basePath};
const compilerOptions = this.host.getCompilationSettings();
if (compilerOptions && compilerOptions.baseUrl) {
options.baseUrl = compilerOptions.baseUrl;

View File

@ -1,67 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var fs = require('fs');
var path = require('path');
module.exports = function(gulp, plugins, config) {
function symlink(relativeFolder, linkDir) {
var sourceDir = path.join('..', relativeFolder);
if (!fs.existsSync(linkDir)) {
console.log('creating link', linkDir, sourceDir);
try {
fs.symlinkSync(sourceDir, linkDir, 'dir');
} catch (e) {
var sourceDir = path.join(config.dir, relativeFolder);
console.log('linking failed: trying to hard copy', linkDir, sourceDir);
copyRecursiveSync(sourceDir, linkDir);
}
}
}
return function() {
var nodeModulesDir = path.join(config.dir, 'node_modules');
if (!fs.existsSync(nodeModulesDir)) {
fs.mkdirSync(nodeModulesDir);
}
getSubdirs(config.dir).forEach(function(relativeFolder) {
if (relativeFolder === 'node_modules') {
return;
}
var linkDir = path.join(nodeModulesDir, relativeFolder);
symlink(relativeFolder, linkDir);
});
// Also symlink tools we release independently to NPM, so tests can require metadata, etc.
symlink('../../tools/metadata', path.join(nodeModulesDir, 'tsc-wrapped'));
};
};
function copyRecursiveSync(src, dest) {
if (fs.existsSync(src)) {
var stats = fs.statSync(src);
if (stats.isDirectory()) {
fs.mkdirSync(dest);
fs.readdirSync(src).forEach(function(childItemName) {
copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName));
});
} else {
fs.writeFileSync(dest, fs.readFileSync(src));
}
}
}
function getSubdirs(rootDir) {
return fs.readdirSync(rootDir).filter(function(file) {
if (file[0] === '.') {
return false;
}
var dirPath = path.join(rootDir, file);
return fs.statSync(dirPath).isDirectory();
});
}