diff --git a/packages/bazel/package.json b/packages/bazel/package.json
index e770464f89..1360531fba 100644
--- a/packages/bazel/package.json
+++ b/packages/bazel/package.json
@@ -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"
}
-}
\ No newline at end of file
+}
diff --git a/packages/bazel/src/ngc-wrapped/index.ts b/packages/bazel/src/ngc-wrapped/index.ts
index 9fb767f6d6..6d45e0c904 100644
--- a/packages/bazel/src/ngc-wrapped/index.ts
+++ b/packages/bazel/src/ngc-wrapped/index.ts
@@ -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
+///
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';
diff --git a/packages/compiler-cli/index.ts b/packages/compiler-cli/index.ts
index 94cc324c6c..b94cacfc5d 100644
--- a/packages/compiler-cli/index.ts
+++ b/packages/compiler-cli/index.ts
@@ -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';
diff --git a/packages/compiler-cli/integrationtest/test/test_summaries.ts b/packages/compiler-cli/integrationtest/test/test_summaries.ts
index cc6abeceda..e73b1cf2df 100644
--- a/packages/compiler-cli/integrationtest/test/test_summaries.ts
+++ b/packages/compiler-cli/integrationtest/test/test_summaries.ts
@@ -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);
diff --git a/packages/compiler-cli/src/codegen.ts b/packages/compiler-cli/src/codegen.ts
index 3b7a7ca985..5b00a9b412 100644
--- a/packages/compiler-cli/src/codegen.ts
+++ b/packages/compiler-cli/src/codegen.ts
@@ -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 {
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) {
diff --git a/packages/compiler-cli/src/compiler_host.ts b/packages/compiler-cli/src/compiler_host.ts
index 63b7d595d4..9c8f143e61 100644
--- a/packages/compiler-cli/src/compiler_host.ts
+++ b/packages/compiler-cli/src/compiler_host.ts
@@ -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
private flatModuleIndexRedirectNames = new Set();
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 {
protected basePath: string;
private moduleFileNames = new Map();
@@ -239,7 +241,7 @@ export class CompilerHost extends BaseAotCompilerHost {
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);
diff --git a/packages/compiler-cli/src/extract_i18n.ts b/packages/compiler-cli/src/extract_i18n.ts
index 67867515e8..7cc9bb4359 100644
--- a/packages/compiler-cli/src/extract_i18n.ts
+++ b/packages/compiler-cli/src/extract_i18n.ts
@@ -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);
-}
\ No newline at end of file
+ process.exitCode = mainXi18n(args);
+}
diff --git a/packages/compiler-cli/src/extractor.ts b/packages/compiler-cli/src/extractor.ts
index e4b23e607a..4a55f3b07f 100644
--- a/packages/compiler-cli/src/extractor.ts
+++ b/packages/compiler-cli/src/extractor.ts
@@ -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) {
diff --git a/packages/compiler-cli/src/language_services.ts b/packages/compiler-cli/src/language_services.ts
index 34fd77fd23..a875c96a7f 100644
--- a/packages/compiler-cli/src/language_services.ts
+++ b/packages/compiler-cli/src/language_services.ts
@@ -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';
diff --git a/packages/compiler-cli/src/main.ts b/packages/compiler-cli/src/main.ts
index 50bc5cac69..318e754c08 100644
--- a/packages/compiler-cli/src/main.ts
+++ b/packages/compiler-cli/src/main.ts
@@ -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 {
- 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 {
- 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);
}
diff --git a/packages/compiler-cli/src/metadata/bundle_index_host.ts b/packages/compiler-cli/src/metadata/bundle_index_host.ts
new file mode 100644
index 0000000000..fb7af2ea1f
--- /dev/null
+++ b/packages/compiler-cli/src/metadata/bundle_index_host.ts
@@ -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(
+ 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(
+ 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};
+}
\ No newline at end of file
diff --git a/packages/compiler-cli/src/metadata/bundler.ts b/packages/compiler-cli/src/metadata/bundler.ts
new file mode 100644
index 0000000000..6024b68bcf
--- /dev/null
+++ b/packages/compiler-cli/src/metadata/bundler.ts
@@ -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();
+ private metadataCache = new Map();
+ private exports = new Map();
+ private rootModule: string;
+ private exported: Set;
+
+ 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();
+ 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();
+ const exportAlls = new Set();
+ 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;
+}
diff --git a/packages/compiler-cli/src/metadata/collector.ts b/packages/compiler-cli/src/metadata/collector.ts
new file mode 100644
index 0000000000..b9b5f3a45b
--- /dev/null
+++ b/packages/compiler-cli/src/metadata/collector.ts
@@ -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();
+ 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 evaluator.evaluateNode(decoratorNode.expression);
+ }
+
+ function recordEntry(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 = 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 = 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 = member;
+ if (isStatic(method)) {
+ const maybeFunc = maybeGetSimpleFunction(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) {
+ (data).parameters = parametersData;
+ }
+ if (!isMetadataError(name)) {
+ recordMember(name, data);
+ }
+ break;
+ case ts.SyntaxKind.PropertyDeclaration:
+ case ts.SyntaxKind.GetAccessor:
+ case ts.SyntaxKind.SetAccessor:
+ const property = 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();
+ ts.forEachChild(sourceFile, node => {
+ switch (node.kind) {
+ case ts.SyntaxKind.ExportDeclaration:
+ const 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 = 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 = 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 = 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 = 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 = (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 = 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 = 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 = 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 = 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 = 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 = 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 = node;
+ for (const variableDeclaration of variableStatement.declarationList.declarations) {
+ if (variableDeclaration.name.kind == ts.SyntaxKind.Identifier) {
+ const nameNode = 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 {[, ]+} = ;
+ // or
+ // var [[, ;
+ // are not supported.
+ const report: (nameNode: ts.Node) => void = (nameNode: ts.Node) => {
+ switch (nameNode.kind) {
+ case ts.SyntaxKind.Identifier:
+ const name = 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 = nameNode;
+ report(bindingElement.name);
+ break;
+ case ts.SyntaxKind.ObjectBindingPattern:
+ case ts.SyntaxKind.ArrayBindingPattern:
+ const bindings = 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,
+ metadata: {[name: string]: MetadataEntry}) {
+ let locals: Set = 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((expression)[v]));
+ } else if (isMetadataError(expression)) {
+ reportError(expression);
+ } else if (isMetadataGlobalReferenceExpression(expression)) {
+ if (!locals.has(expression.name)) {
+ const reference = metadata[expression.name];
+ if (reference) {
+ validateExpression(reference);
+ }
+ }
+ } else if (isFunctionMetadata(expression)) {
+ validateFunction(expression);
+ } else if (isMetadataSymbolicExpression(expression)) {
+ switch (expression.__symbolic) {
+ case 'binary':
+ const binaryExpression = expression;
+ validateExpression(binaryExpression.left);
+ validateExpression(binaryExpression.right);
+ break;
+ case 'call':
+ case 'new':
+ const callExpression = expression;
+ validateExpression(callExpression.expression);
+ if (callExpression.arguments) callExpression.arguments.forEach(validateExpression);
+ break;
+ case 'index':
+ const indexExpression = expression;
+ validateExpression(indexExpression.expression);
+ validateExpression(indexExpression.index);
+ break;
+ case 'pre':
+ const prefixExpression = expression;
+ validateExpression(prefixExpression.operand);
+ break;
+ case 'select':
+ const selectExpression = expression;
+ validateExpression(selectExpression.expression);
+ break;
+ case 'spread':
+ const spreadExpression = expression;
+ validateExpression(spreadExpression.expression);
+ break;
+ case 'if':
+ const ifExpression = 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): string[] {
+ const result: string[] = [];
+
+ function addNamesOf(name: ts.Identifier | ts.BindingPattern) {
+ if (name.kind == ts.SyntaxKind.Identifier) {
+ const identifier = name;
+ result.push(identifier.text);
+ } else {
+ const 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;
+}
diff --git a/packages/compiler-cli/src/metadata/evaluator.ts b/packages/compiler-cli/src/metadata/evaluator.ts
new file mode 100644
index 0000000000..db02169a2b
--- /dev/null
+++ b/packages/compiler-cli/src/metadata/evaluator.ts
@@ -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 = 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 = 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 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,
+ 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 (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()) || ''});
+ }
+ }
+
+ /**
+ * 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());
+ }
+
+ private isFoldableWorker(node: ts.Node|undefined, folding: Map): boolean {
+ if (node) {
+ switch (node.kind) {
+ case ts.SyntaxKind.ObjectLiteralExpression:
+ return everyNodeChild(node, child => {
+ if (child.kind === ts.SyntaxKind.PropertyAssignment) {
+ const 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 = node;
+ // We can fold a .concat().
+ if (isMethodCallOf(callExpression, 'concat') &&
+ arrayOrEmpty(callExpression.arguments).length === 1) {
+ const arrayNode = (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 = node;
+ return this.isFoldableWorker(parenthesizedExpression.expression, folding);
+ case ts.SyntaxKind.BinaryExpression:
+ const 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 = node;
+ return this.isFoldableWorker(propertyAccessExpression.expression, folding);
+ case ts.SyntaxKind.ElementAccessExpression:
+ const elementAccessExpression = node;
+ return this.isFoldableWorker(elementAccessExpression.expression, folding) &&
+ this.isFoldableWorker(elementAccessExpression.argumentExpression, folding);
+ case ts.SyntaxKind.Identifier:
+ let identifier = node;
+ let reference = this.symbols.resolve(identifier.text);
+ if (reference !== undefined && isPrimitive(reference)) {
+ return true;
+ }
+ break;
+ case ts.SyntaxKind.TemplateExpression:
+ const 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 = 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[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 = node;
+ if (isCallOf(callExpression, 'forwardRef') &&
+ arrayOrEmpty(callExpression.arguments).length === 1) {
+ const firstArgument = callExpression.arguments[0];
+ if (firstArgument.kind == ts.SyntaxKind.ArrowFunction) {
+ const 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 = this.evaluateNode(
+ (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 = 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 = 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 (expression)[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 = 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 (expression)[index];
+ return recordEntry({__symbolic: 'index', expression, index}, node);
+ }
+ case ts.SyntaxKind.Identifier:
+ const identifier = node;
+ const name = identifier.text;
+ return resolveName(name, preferReference);
+ case ts.SyntaxKind.TypeReference:
+ const typeReferenceNode = node;
+ const typeNameNode = typeReferenceNode.typeName;
+ const getReference: (typeNameNode: ts.Identifier | ts.QualifiedName) => MetadataValue =
+ node => {
+ if (typeNameNode.kind === ts.SyntaxKind.QualifiedName) {
+ const qualifiedName = node;
+ const left = this.evaluateNode(qualifiedName.left);
+ if (isMetadataModuleReferenceExpression(left)) {
+ return recordEntry(
+ {
+ __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 = 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.
+ (typeReference).arguments = args;
+ }
+ return recordEntry(typeReference, node);
+ case ts.SyntaxKind.UnionType:
+ const unionType = 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 (node).text;
+ case ts.SyntaxKind.NumericLiteral:
+ return parseFloat((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 = 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 = node;
+ return this.evaluateNode(parenthesizedExpression.expression);
+ case ts.SyntaxKind.TypeAssertionExpression:
+ const typeAssertion = node;
+ return this.evaluateNode(typeAssertion.expression);
+ case ts.SyntaxKind.PrefixUnaryExpression:
+ const 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 = 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 left || right;
+ case ts.SyntaxKind.AmpersandAmpersandToken:
+ return left && right;
+ case ts.SyntaxKind.AmpersandToken:
+ return left & right;
+ case ts.SyntaxKind.BarToken:
+ return left | right;
+ case ts.SyntaxKind.CaretToken:
+ return left ^ right;
+ case ts.SyntaxKind.EqualsEqualsToken:
+ return left == right;
+ case ts.SyntaxKind.ExclamationEqualsToken:
+ return left != right;
+ case ts.SyntaxKind.EqualsEqualsEqualsToken:
+ return left === right;
+ case ts.SyntaxKind.ExclamationEqualsEqualsToken:
+ return left !== right;
+ case ts.SyntaxKind.LessThanToken:
+ return left < right;
+ case ts.SyntaxKind.GreaterThanToken:
+ return left > right;
+ case ts.SyntaxKind.LessThanEqualsToken:
+ return left <= right;
+ case ts.SyntaxKind.GreaterThanEqualsToken:
+ return left >= right;
+ case ts.SyntaxKind.LessThanLessThanToken:
+ return (left) << (right);
+ case ts.SyntaxKind.GreaterThanGreaterThanToken:
+ return left >> right;
+ case ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken:
+ return left >>> right;
+ case ts.SyntaxKind.PlusToken:
+ return left + right;
+ case ts.SyntaxKind.MinusToken:
+ return left - right;
+ case ts.SyntaxKind.AsteriskToken:
+ return left * right;
+ case ts.SyntaxKind.SlashToken:
+ return left / right;
+ case ts.SyntaxKind.PercentToken:
+ return left % right;
+ }
+ return recordEntry(
+ {
+ __symbolic: 'binop',
+ operator: binaryExpression.operatorToken.getText(),
+ left: left,
+ right: right
+ },
+ node);
+ }
+ break;
+ case ts.SyntaxKind.ConditionalExpression:
+ const 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 = node;
+ if (this.isFoldable(node)) {
+ return templateExpression.templateSpans.reduce(
+ (previous, current) => previous + this.evaluateNode(current.expression) +
+ 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 = 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();
+
+function arrayOrEmpty(v: ts.NodeArray| undefined): ts.NodeArray {
+ return v || empty;
+}
\ No newline at end of file
diff --git a/packages/compiler-cli/src/metadata/index.ts b/packages/compiler-cli/src/metadata/index.ts
new file mode 100644
index 0000000000..d8aa3ff8f7
--- /dev/null
+++ b/packages/compiler-cli/src/metadata/index.ts
@@ -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';
\ No newline at end of file
diff --git a/packages/compiler-cli/src/metadata/index_writer.ts b/packages/compiler-cli/src/metadata/index_writer.ts
new file mode 100644
index 0000000000..2c209f1c5b
--- /dev/null
+++ b/packages/compiler-cli/src/metadata/index_writer.ts
@@ -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();
+
+ 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]) => [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(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;
+ };
+}
\ No newline at end of file
diff --git a/packages/compiler-cli/src/metadata/schema.ts b/packages/compiler-cli/src/metadata/schema.ts
new file mode 100644
index 0000000000..20fcb9932b
--- /dev/null
+++ b/packages/compiler-cli/src/metadata/schema.ts
@@ -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';
+}
diff --git a/packages/compiler-cli/src/metadata/symbols.ts b/packages/compiler-cli/src/metadata/symbols.ts
new file mode 100644
index 0000000000..6e29dd267f
--- /dev/null
+++ b/packages/compiler-cli/src/metadata/symbols.ts
@@ -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;
+ private references = new Map();
+
+ 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 {
+ let result = this._symbols;
+ if (!result) {
+ result = this._symbols = new Map();
+ 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 = node;
+ if (importEqualsDeclaration.moduleReference.kind ===
+ ts.SyntaxKind.ExternalModuleReference) {
+ const externalReference =
+ importEqualsDeclaration.moduleReference;
+ if (externalReference.expression) {
+ // An `import = require();
+ 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 = node;
+ if (!importDecl.importClause) {
+ // An `import ` 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 form ` 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 { [ [, ] } from ` clause
+ for (const binding of (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 from ` clause.
+ symbols.set(
+ (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) {
+ // 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}));
+}
diff --git a/packages/compiler-cli/src/ngtools_api.ts b/packages/compiler-cli/src/ngtools_api.ts
index eff9d22509..97b85e0472 100644
--- a/packages/compiler-cli/src/ngtools_api.ts
+++ b/packages/compiler-cli/src/ngtools_api.ts
@@ -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;
// Every new property under this line should be optional.
diff --git a/packages/compiler-cli/src/path_mapped_compiler_host.ts b/packages/compiler-cli/src/path_mapped_compiler_host.ts
index f17b0a5800..4df2e31740 100644
--- a/packages/compiler-cli/src/path_mapped_compiler_host.ts
+++ b/packages/compiler-cli/src/path_mapped_compiler_host.ts
@@ -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);
}
diff --git a/packages/compiler-cli/src/perform_compile.ts b/packages/compiler-cli/src/perform_compile.ts
index 65f4d1560a..7408a0b29b 100644
--- a/packages/compiler-cli/src/perform_compile.ts
+++ b/packages/compiler-cli/src/perform_compile.ts
@@ -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 = [];
diff --git a/packages/compiler-cli/src/transformers/lower_expressions.ts b/packages/compiler-cli/src/transformers/lower_expressions.ts
index e6a2476b3d..e182a4d448 100644
--- a/packages/compiler-cli/src/transformers/lower_expressions.ts
+++ b/packages/compiler-cli/src/transformers/lower_expressions.ts
@@ -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;
diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts
index 3d686a37f8..17035dff26 100644
--- a/packages/compiler-cli/src/transformers/program.ts
+++ b/packages/compiler-cli/src/transformers/program.ts
@@ -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';
diff --git a/packages/compiler-cli/test/aot_host_spec.ts b/packages/compiler-cli/test/aot_host_spec.ts
index aad08036c3..09983231ef 100644
--- a/packages/compiler-cli/test/aot_host_spec.ts
+++ b/packages/compiler-cli/test/aot_host_spec.ts
@@ -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';
diff --git a/packages/compiler-cli/test/diagnostics/expression_diagnostics_spec.ts b/packages/compiler-cli/test/diagnostics/expression_diagnostics_spec.ts
index 45a9d1a1e9..6d95a5b9df 100644
--- a/packages/compiler-cli/test/diagnostics/expression_diagnostics_spec.ts
+++ b/packages/compiler-cli/test/diagnostics/expression_diagnostics_spec.ts
@@ -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});
diff --git a/packages/compiler-cli/test/diagnostics/symbol_query_spec.ts b/packages/compiler-cli/test/diagnostics/symbol_query_spec.ts
index 317b26ed3e..624b810b30 100644
--- a/packages/compiler-cli/test/diagnostics/symbol_query_spec.ts
+++ b/packages/compiler-cli/test/diagnostics/symbol_query_spec.ts
@@ -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});
diff --git a/packages/compiler-cli/test/extract_i18n_spec.ts b/packages/compiler-cli/test/extract_i18n_spec.ts
index ff2477b8c8..ffba4b9d5e 100644
--- a/packages/compiler-cli/test/extract_i18n_spec.ts
+++ b/packages/compiler-cli/test/extract_i18n_spec.ts
@@ -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();
});
-});
\ No newline at end of file
+});
diff --git a/packages/compiler-cli/test/main_spec.ts b/packages/compiler-cli/test/main_spec.ts
deleted file mode 100644
index 1243d05c9c..0000000000
--- a/packages/compiler-cli/test/main_spec.ts
+++ /dev/null
@@ -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));
- });
- });
-});
diff --git a/packages/compiler-cli/test/metadata/bundler_spec.ts b/packages/compiler-cli/test/metadata/bundler_spec.ts
new file mode 100644
index 0000000000..f00a620300
--- /dev/null
+++ b/packages/compiler-cli/test/metadata/bundler_spec.ts
@@ -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];
+ `
+ }
+ }
+ }
+};
diff --git a/packages/compiler-cli/test/metadata/collector_spec.ts b/packages/compiler-cli/test/metadata/collector_spec.ts
new file mode 100644
index 0000000000..5282067278
--- /dev/null
+++ b/packages/compiler-cli/test/metadata/collector_spec.ts
@@ -0,0 +1,1484 @@
+/**
+ * @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 {MetadataCollector} from '../../src/metadata/collector';
+import {ClassMetadata, ConstructorMetadata, MetadataEntry, ModuleMetadata, isClassMetadata, isMetadataGlobalReferenceExpression} from '../../src/metadata/schema';
+
+import {Directory, Host, expectValidSources} from './typescript.mocks';
+
+describe('Collector', () => {
+ const documentRegistry = ts.createDocumentRegistry();
+ let host: Host;
+ let service: ts.LanguageService;
+ let program: ts.Program;
+ let collector: MetadataCollector;
+
+ beforeEach(() => {
+ host = new Host(FILES, [
+ '/app/app.component.ts',
+ '/app/cases-data.ts',
+ '/app/error-cases.ts',
+ '/promise.ts',
+ '/unsupported-1.ts',
+ '/unsupported-2.ts',
+ '/unsupported-3.ts',
+ 'class-arity.ts',
+ 'import-star.ts',
+ 'exported-classes.ts',
+ 'exported-functions.ts',
+ 'exported-enum.ts',
+ 'exported-type.ts',
+ 'exported-consts.ts',
+ 'local-symbol-ref.ts',
+ 'local-function-ref.ts',
+ 'local-symbol-ref-func.ts',
+ 'local-symbol-ref-func-dynamic.ts',
+ 'private-enum.ts',
+ 're-exports.ts',
+ 're-exports-2.ts',
+ 'export-as.d.ts',
+ 'static-field-reference.ts',
+ 'static-method.ts',
+ 'static-method-call.ts',
+ 'static-method-with-if.ts',
+ 'static-method-with-default.ts',
+ 'class-inheritance.ts',
+ 'class-inheritance-parent.ts',
+ 'class-inheritance-declarations.d.ts',
+ 'interface-reference.ts'
+ ]);
+ service = ts.createLanguageService(host, documentRegistry);
+ program = service.getProgram();
+ collector = new MetadataCollector({quotedNames: true});
+ });
+
+ it('should not have errors in test data', () => { expectValidSources(service, program); });
+
+ it('should return undefined for modules that have no metadata', () => {
+ const sourceFile = program.getSourceFile('app/empty.ts');
+ const metadata = collector.getMetadata(sourceFile);
+ expect(metadata).toBeUndefined();
+ });
+
+ it('should return an interface reference for types', () => {
+ const sourceFile = program.getSourceFile('/exported-type.ts');
+ const metadata = collector.getMetadata(sourceFile);
+ expect(metadata).toEqual(
+ {__symbolic: 'module', version: 3, metadata: {SomeType: {__symbolic: 'interface'}}});
+ });
+
+ it('should return an interface reference for interfaces', () => {
+ const sourceFile = program.getSourceFile('app/hero.ts');
+ const metadata = collector.getMetadata(sourceFile);
+ expect(metadata).toEqual(
+ {__symbolic: 'module', version: 3, metadata: {Hero: {__symbolic: 'interface'}}});
+ });
+
+ it('should be able to collect a simple component\'s metadata', () => {
+ const sourceFile = program.getSourceFile('app/hero-detail.component.ts');
+ const metadata = collector.getMetadata(sourceFile);
+ expect(metadata).toEqual({
+ __symbolic: 'module',
+ version: 3,
+ metadata: {
+ HeroDetailComponent: {
+ __symbolic: 'class',
+ decorators: [{
+ __symbolic: 'call',
+ expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
+ arguments: [{
+ selector: 'my-hero-detail',
+ template: `
+
+
{{hero.name}} details!
+
{{hero.id}}
+
+
+
+
+
+ `
+ }]
+ }],
+ members: {
+ hero: [{
+ __symbolic: 'property',
+ decorators: [{
+ __symbolic: 'call',
+ expression:
+ {__symbolic: 'reference', module: 'angular2/core', name: 'Input'}
+ }]
+ }]
+ }
+ }
+ }
+ });
+ });
+
+ it('should be able to get a more complicated component\'s metadata', () => {
+ const sourceFile = program.getSourceFile('/app/app.component.ts');
+ const metadata = collector.getMetadata(sourceFile);
+ expect(metadata).toEqual({
+ __symbolic: 'module',
+ version: 3,
+ metadata: {
+ AppComponent: {
+ __symbolic: 'class',
+ decorators: [{
+ __symbolic: 'call',
+ expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
+ arguments: [{
+ selector: 'my-app',
+ template: `
+ My Heroes
+
+ -
+ {{hero.id | lowercase}} {{hero.name | uppercase}}
+
+
+
+ `,
+ directives: [
+ {
+ __symbolic: 'reference',
+ module: './hero-detail.component',
+ name: 'HeroDetailComponent',
+ },
+ {__symbolic: 'reference', module: 'angular2/common', name: 'NgFor'}
+ ],
+ providers: [{__symbolic: 'reference', module: './hero.service', default: true}],
+ pipes: [
+ {__symbolic: 'reference', module: 'angular2/common', name: 'LowerCasePipe'},
+ {__symbolic: 'reference', module: 'angular2/common', name: 'UpperCasePipe'}
+ ]
+ }]
+ }],
+ members: {
+ __ctor__: [{
+ __symbolic: 'constructor',
+ parameters: [{__symbolic: 'reference', module: './hero.service', default: true}]
+ }],
+ onSelect: [{__symbolic: 'method'}],
+ ngOnInit: [{__symbolic: 'method'}],
+ getHeroes: [{__symbolic: 'method'}]
+ }
+ }
+ }
+ });
+ });
+
+ it('should return the values of exported variables', () => {
+ const sourceFile = program.getSourceFile('/app/mock-heroes.ts');
+ const metadata = collector.getMetadata(sourceFile);
+ expect(metadata).toEqual({
+ __symbolic: 'module',
+ version: 3,
+ metadata: {
+ HEROES: [
+ {'id': 11, 'name': 'Mr. Nice', '$quoted$': ['id', 'name']},
+ {'id': 12, 'name': 'Narco', '$quoted$': ['id', 'name']},
+ {'id': 13, 'name': 'Bombasto', '$quoted$': ['id', 'name']},
+ {'id': 14, 'name': 'Celeritas', '$quoted$': ['id', 'name']},
+ {'id': 15, 'name': 'Magneta', '$quoted$': ['id', 'name']},
+ {'id': 16, 'name': 'RubberMan', '$quoted$': ['id', 'name']},
+ {'id': 17, 'name': 'Dynama', '$quoted$': ['id', 'name']},
+ {'id': 18, 'name': 'Dr IQ', '$quoted$': ['id', 'name']},
+ {'id': 19, 'name': 'Magma', '$quoted$': ['id', 'name']},
+ {'id': 20, 'name': 'Tornado', '$quoted$': ['id', 'name']}
+ ]
+ }
+ });
+ });
+
+ let casesFile: ts.SourceFile;
+ let casesMetadata: ModuleMetadata;
+
+ beforeEach(() => {
+ casesFile = program.getSourceFile('/app/cases-data.ts');
+ casesMetadata = collector.getMetadata(casesFile) !;
+ });
+
+ it('should provide any reference for an any ctor parameter type', () => {
+ const casesAny = casesMetadata.metadata['CaseAny'];
+ expect(casesAny).toBeTruthy();
+ const ctorData = casesAny.members !['__ctor__'];
+ expect(ctorData).toEqual(
+ [{__symbolic: 'constructor', parameters: [{__symbolic: 'reference', name: 'any'}]}]);
+ });
+
+ it('should record annotations on set and get declarations', () => {
+ const propertyData = {
+ name: [{
+ __symbolic: 'property',
+ decorators: [{
+ __symbolic: 'call',
+ expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Input'},
+ arguments: ['firstName']
+ }]
+ }]
+ };
+ const caseGetProp = casesMetadata.metadata['GetProp'];
+ expect(caseGetProp.members).toEqual(propertyData);
+ const caseSetProp = casesMetadata.metadata['SetProp'];
+ expect(caseSetProp.members).toEqual(propertyData);
+ const caseFullProp = casesMetadata.metadata['FullProp'];
+ expect(caseFullProp.members).toEqual(propertyData);
+ });
+
+ it('should record references to parameterized types', () => {
+ const casesForIn = casesMetadata.metadata['NgFor'];
+ expect(casesForIn).toEqual({
+ __symbolic: 'class',
+ decorators: [{
+ __symbolic: 'call',
+ expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Injectable'}
+ }],
+ members: {
+ __ctor__: [{
+ __symbolic: 'constructor',
+ parameters: [{
+ __symbolic: 'reference',
+ name: 'ClassReference',
+ arguments: [{__symbolic: 'reference', name: 'NgForRow'}]
+ }]
+ }]
+ }
+ });
+ });
+
+ it('should report errors for destructured imports', () => {
+ const unsupported1 = program.getSourceFile('/unsupported-1.ts');
+ const metadata = collector.getMetadata(unsupported1);
+ expect(metadata).toEqual({
+ __symbolic: 'module',
+ version: 3,
+ metadata: {
+ a: {__symbolic: 'error', message: 'Destructuring not supported', line: 1, character: 16},
+ b: {__symbolic: 'error', message: 'Destructuring not supported', line: 1, character: 19},
+ c: {__symbolic: 'error', message: 'Destructuring not supported', line: 2, character: 16},
+ d: {__symbolic: 'error', message: 'Destructuring not supported', line: 2, character: 19},
+ e: {__symbolic: 'error', message: 'Variable not initialized', line: 3, character: 15}
+ }
+ });
+ });
+
+ it('should report an error for references to unexpected types', () => {
+ const unsupported1 = program.getSourceFile('/unsupported-2.ts');
+ const metadata = collector.getMetadata(unsupported1) !;
+ const barClass = metadata.metadata['Bar'];
+ const ctor = barClass.members !['__ctor__'][0];
+ const parameter = ctor.parameters ![0];
+ expect(parameter).toEqual({
+ __symbolic: 'error',
+ message: 'Reference to non-exported class',
+ line: 3,
+ character: 4,
+ context: {className: 'Foo'}
+ });
+ });
+
+ it('should be able to handle import star type references', () => {
+ const importStar = program.getSourceFile('/import-star.ts');
+ const metadata = collector.getMetadata(importStar) !;
+ const someClass = metadata.metadata['SomeClass'];
+ const ctor = someClass.members !['__ctor__'][0];
+ const parameters = ctor.parameters;
+ expect(parameters).toEqual([
+ {__symbolic: 'reference', module: 'angular2/common', name: 'NgFor'}
+ ]);
+ });
+
+ it('should record all exported classes', () => {
+ const sourceFile = program.getSourceFile('/exported-classes.ts');
+ const metadata = collector.getMetadata(sourceFile);
+ expect(metadata).toEqual({
+ __symbolic: 'module',
+ version: 3,
+ metadata: {
+ SimpleClass: {__symbolic: 'class'},
+ AbstractClass: {__symbolic: 'class'},
+ DeclaredClass: {__symbolic: 'class'}
+ }
+ });
+ });
+
+ it('should be able to record functions', () => {
+ const exportedFunctions = program.getSourceFile('/exported-functions.ts');
+ const metadata = collector.getMetadata(exportedFunctions);
+ expect(metadata).toEqual({
+ __symbolic: 'module',
+ version: 3,
+ metadata: {
+ one: {
+ __symbolic: 'function',
+ parameters: ['a', 'b', 'c'],
+ value: {
+ a: {__symbolic: 'reference', name: 'a'},
+ b: {__symbolic: 'reference', name: 'b'},
+ c: {__symbolic: 'reference', name: 'c'}
+ }
+ },
+ two: {
+ __symbolic: 'function',
+ parameters: ['a', 'b', 'c'],
+ value: {
+ a: {__symbolic: 'reference', name: 'a'},
+ b: {__symbolic: 'reference', name: 'b'},
+ c: {__symbolic: 'reference', name: 'c'}
+ }
+ },
+ three: {
+ __symbolic: 'function',
+ parameters: ['a', 'b', 'c'],
+ value: [
+ {__symbolic: 'reference', name: 'a'}, {__symbolic: 'reference', name: 'b'},
+ {__symbolic: 'reference', name: 'c'}
+ ]
+ },
+ supportsState: {
+ __symbolic: 'function',
+ parameters: [],
+ value: {
+ __symbolic: 'pre',
+ operator: '!',
+ operand: {
+ __symbolic: 'pre',
+ operator: '!',
+ operand: {
+ __symbolic: 'select',
+ expression: {
+ __symbolic: 'select',
+ expression: {__symbolic: 'reference', name: 'window'},
+ member: 'history'
+ },
+ member: 'pushState'
+ }
+ }
+ }
+ },
+ complexFn: {__symbolic: 'function'},
+ declaredFn: {__symbolic: 'function'}
+ }
+ });
+ });
+
+ it('should be able to handle import star type references', () => {
+ const importStar = program.getSourceFile('/import-star.ts');
+ const metadata = collector.getMetadata(importStar) !;
+ const someClass = metadata.metadata['SomeClass'];
+ const ctor = someClass.members !['__ctor__'][0];
+ const parameters = ctor.parameters;
+ expect(parameters).toEqual([
+ {__symbolic: 'reference', module: 'angular2/common', name: 'NgFor'}
+ ]);
+ });
+
+ it('should be able to collect the value of an enum', () => {
+ const enumSource = program.getSourceFile('/exported-enum.ts');
+ const metadata = collector.getMetadata(enumSource) !;
+ const someEnum: any = metadata.metadata['SomeEnum'];
+ expect(someEnum).toEqual({A: 0, B: 1, C: 100, D: 101});
+ });
+
+ it('should ignore a non-export enum', () => {
+ const enumSource = program.getSourceFile('/private-enum.ts');
+ const metadata = collector.getMetadata(enumSource) !;
+ const publicEnum: any = metadata.metadata['PublicEnum'];
+ const privateEnum: any = metadata.metadata['PrivateEnum'];
+ expect(publicEnum).toEqual({a: 0, b: 1, c: 2});
+ expect(privateEnum).toBeUndefined();
+ });
+
+ it('should be able to collect enums initialized from consts', () => {
+ const enumSource = program.getSourceFile('/exported-enum.ts');
+ const metadata = collector.getMetadata(enumSource) !;
+ const complexEnum: any = metadata.metadata['ComplexEnum'];
+ expect(complexEnum).toEqual({
+ A: 0,
+ B: 1,
+ C: 30,
+ D: 40,
+ E: {__symbolic: 'reference', module: './exported-consts', name: 'constValue'}
+ });
+ });
+
+ it('should be able to collect a simple static method', () => {
+ const staticSource = program.getSourceFile('/static-method.ts');
+ const metadata = collector.getMetadata(staticSource) !;
+ expect(metadata).toBeDefined();
+ const classData = metadata.metadata['MyModule'];
+ expect(classData).toBeDefined();
+ expect(classData.statics).toEqual({
+ with: {
+ __symbolic: 'function',
+ parameters: ['comp'],
+ value: [
+ {__symbolic: 'reference', name: 'MyModule'},
+ {provider: 'a', useValue: {__symbolic: 'reference', name: 'comp'}}
+ ]
+ }
+ });
+ });
+
+ it('should be able to collect a call to a static method', () => {
+ const staticSource = program.getSourceFile('/static-method-call.ts');
+ const metadata = collector.getMetadata(staticSource) !;
+ expect(metadata).toBeDefined();
+ const classData = metadata.metadata['Foo'];
+ expect(classData).toBeDefined();
+ expect(classData.decorators).toEqual([{
+ __symbolic: 'call',
+ expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
+ arguments: [{
+ providers: {
+ __symbolic: 'call',
+ expression: {
+ __symbolic: 'select',
+ expression: {__symbolic: 'reference', module: './static-method', name: 'MyModule'},
+ member: 'with'
+ },
+ arguments: ['a']
+ }
+ }]
+ }]);
+ });
+
+ it('should be able to collect a static field', () => {
+ const staticSource = program.getSourceFile('/static-field.ts');
+ const metadata = collector.getMetadata(staticSource) !;
+ expect(metadata).toBeDefined();
+ const classData = metadata.metadata['MyModule'];
+ expect(classData).toBeDefined();
+ expect(classData.statics).toEqual({VALUE: 'Some string'});
+ });
+
+ it('should be able to collect a reference to a static field', () => {
+ const staticSource = program.getSourceFile('/static-field-reference.ts');
+ const metadata = collector.getMetadata(staticSource) !;
+ expect(metadata).toBeDefined();
+ const classData = metadata.metadata['Foo'];
+ expect(classData).toBeDefined();
+ expect(classData.decorators).toEqual([{
+ __symbolic: 'call',
+ expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
+ arguments: [{
+ providers: [{
+ provide: 'a',
+ useValue: {
+ __symbolic: 'select',
+ expression: {__symbolic: 'reference', module: './static-field', name: 'MyModule'},
+ member: 'VALUE'
+ }
+ }]
+ }]
+ }]);
+ });
+
+ it('should be able to collect a method with a conditional expression', () => {
+ const source = program.getSourceFile('/static-method-with-if.ts');
+ const metadata = collector.getMetadata(source) !;
+ expect(metadata).toBeDefined();
+ const classData = metadata.metadata['MyModule'];
+ expect(classData).toBeDefined();
+ expect(classData.statics).toEqual({
+ with: {
+ __symbolic: 'function',
+ parameters: ['cond'],
+ value: [
+ {__symbolic: 'reference', name: 'MyModule'}, {
+ provider: 'a',
+ useValue: {
+ __symbolic: 'if',
+ condition: {__symbolic: 'reference', name: 'cond'},
+ thenExpression: '1',
+ elseExpression: '2'
+ }
+ }
+ ]
+ }
+ });
+ });
+
+ it('should be able to collect a method with a default parameter', () => {
+ const source = program.getSourceFile('/static-method-with-default.ts');
+ const metadata = collector.getMetadata(source) !;
+ expect(metadata).toBeDefined();
+ const classData = metadata.metadata['MyModule'];
+ expect(classData).toBeDefined();
+ expect(classData.statics).toEqual({
+ with: {
+ __symbolic: 'function',
+ parameters: ['comp', 'foo', 'bar'],
+ defaults: [undefined, true, false],
+ value: [
+ {__symbolic: 'reference', name: 'MyModule'}, {
+ __symbolic: 'if',
+ condition: {__symbolic: 'reference', name: 'foo'},
+ thenExpression: {provider: 'a', useValue: {__symbolic: 'reference', name: 'comp'}},
+ elseExpression: {provider: 'b', useValue: {__symbolic: 'reference', name: 'comp'}}
+ },
+ {
+ __symbolic: 'if',
+ condition: {__symbolic: 'reference', name: 'bar'},
+ thenExpression: {provider: 'c', useValue: {__symbolic: 'reference', name: 'comp'}},
+ elseExpression: {provider: 'd', useValue: {__symbolic: 'reference', name: 'comp'}}
+ }
+ ]
+ }
+ });
+ });
+
+ it('should be able to collect re-exported symbols', () => {
+ const source = program.getSourceFile('/re-exports.ts');
+ const metadata = collector.getMetadata(source) !;
+ expect(metadata.exports).toEqual([
+ {from: './static-field', export: ['MyModule']},
+ {from: './static-field-reference', export: [{name: 'Foo', as: 'OtherModule'}]},
+ {from: 'angular2/core'}
+ ]);
+ });
+
+ it('should be able to collect a export as symbol', () => {
+ const source = program.getSourceFile('export-as.d.ts');
+ const metadata = collector.getMetadata(source) !;
+ expect(metadata.metadata).toEqual({SomeFunction: {__symbolic: 'function'}});
+ });
+
+ it('should be able to collect exports with no module specifier', () => {
+ const source = program.getSourceFile('/re-exports-2.ts');
+ const metadata = collector.getMetadata(source) !;
+ expect(metadata.metadata).toEqual({
+ MyClass: Object({__symbolic: 'class'}),
+ OtherModule: {__symbolic: 'reference', module: './static-field-reference', name: 'Foo'},
+ MyOtherModule: {__symbolic: 'reference', module: './static-field', name: 'MyModule'}
+ });
+ });
+
+ it('should collect an error symbol if collecting a reference to a non-exported symbol', () => {
+ const source = program.getSourceFile('/local-symbol-ref.ts');
+ const metadata = collector.getMetadata(source) !;
+ expect(metadata.metadata).toEqual({
+ REQUIRED_VALIDATOR: {
+ __symbolic: 'error',
+ message: 'Reference to a local symbol',
+ line: 3,
+ character: 8,
+ context: {name: 'REQUIRED'}
+ },
+ SomeComponent: {
+ __symbolic: 'class',
+ decorators: [{
+ __symbolic: 'call',
+ expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
+ arguments: [{providers: [{__symbolic: 'reference', name: 'REQUIRED_VALIDATOR'}]}]
+ }]
+ }
+ });
+ });
+
+ it('should collect an error symbol if collecting a reference to a non-exported function', () => {
+ const source = program.getSourceFile('/local-function-ref.ts');
+ const metadata = collector.getMetadata(source) !;
+ expect(metadata.metadata).toEqual({
+ REQUIRED_VALIDATOR: {
+ __symbolic: 'error',
+ message: 'Reference to a non-exported function',
+ line: 3,
+ character: 13,
+ context: {name: 'required'}
+ },
+ SomeComponent: {
+ __symbolic: 'class',
+ decorators: [{
+ __symbolic: 'call',
+ expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Component'},
+ arguments: [{providers: [{__symbolic: 'reference', name: 'REQUIRED_VALIDATOR'}]}]
+ }]
+ }
+ });
+ });
+
+ it('should collect an error for a simple function that references a local variable', () => {
+ const source = program.getSourceFile('/local-symbol-ref-func.ts');
+ const metadata = collector.getMetadata(source) !;
+ expect(metadata.metadata).toEqual({
+ foo: {
+ __symbolic: 'function',
+ parameters: ['index'],
+ value: {
+ __symbolic: 'error',
+ message: 'Reference to a local symbol',
+ line: 1,
+ character: 8,
+ context: {name: 'localSymbol'}
+ }
+ }
+ });
+ });
+
+ it('should collect any for interface parameter reference', () => {
+ const source = program.getSourceFile('/interface-reference.ts');
+ const metadata = collector.getMetadata(source) !;
+ expect((metadata.metadata['SomeClass'] as ClassMetadata).members).toEqual({
+ __ctor__: [{
+ __symbolic: 'constructor',
+ parameterDecorators: [[{
+ __symbolic: 'call',
+ expression: {__symbolic: 'reference', module: 'angular2/core', name: 'Inject'},
+ arguments: ['a']
+ }]],
+ parameters: [{__symbolic: 'reference', name: 'any'}]
+ }]
+ });
+ });
+
+ describe('with interpolations', () => {
+ function e(expr: string, prefix?: string) {
+ const metadata = collectSource(`${prefix || ''} export let value = ${expr};`);
+ return expect(metadata.metadata['value']);
+ }
+
+ it('should be able to collect a raw interpolated string',
+ () => { e('`simple value`').toBe('simple value'); });
+
+ it('should be able to interpolate a single value',
+ () => { e('`${foo}`', 'const foo = "foo value"').toBe('foo value'); });
+
+ it('should be able to interpolate multiple values', () => {
+ e('`foo:${foo}, bar:${bar}, end`', 'const foo = "foo"; const bar = "bar";')
+ .toBe('foo:foo, bar:bar, end');
+ });
+
+ it('should be able to interpolate with an imported reference', () => {
+ e('`external:${external}`', 'import {external} from "./external";').toEqual({
+ __symbolic: 'binop',
+ operator: '+',
+ left: 'external:',
+ right: {
+ __symbolic: 'reference',
+ module: './external',
+ name: 'external',
+ }
+ });
+ });
+
+ it('should simplify a redundant template', () => {
+ e('`${external}`', 'import {external} from "./external";')
+ .toEqual({__symbolic: 'reference', module: './external', name: 'external'});
+ });
+
+ it('should be able to collect complex template with imported references', () => {
+ e('`foo:${foo}, bar:${bar}, end`', 'import {foo, bar} from "./external";').toEqual({
+ __symbolic: 'binop',
+ operator: '+',
+ left: {
+ __symbolic: 'binop',
+ operator: '+',
+ left: {
+ __symbolic: 'binop',
+ operator: '+',
+ left: {
+ __symbolic: 'binop',
+ operator: '+',
+ left: 'foo:',
+ right: {__symbolic: 'reference', module: './external', name: 'foo'}
+ },
+ right: ', bar:'
+ },
+ right: {__symbolic: 'reference', module: './external', name: 'bar'}
+ },
+ right: ', end'
+ });
+ });
+
+ it('should reject a tagged literal', () => {
+ e('tag`some value`').toEqual({
+ __symbolic: 'error',
+ message: 'Tagged template expressions are not supported in metadata',
+ line: 0,
+ character: 20
+ });
+ });
+ });
+
+ it('should ignore |null or |undefined in type expressions', () => {
+ const metadata = collectSource(`
+ import {Foo} from './foo';
+ export class SomeClass {
+ constructor (a: Foo, b: Foo | null, c: Foo | undefined, d: Foo | undefined | null, e: Foo | undefined | null | Foo) {}
+ }
+ `);
+ expect((metadata.metadata['SomeClass'] as ClassMetadata).members).toEqual({
+ __ctor__: [{
+ __symbolic: 'constructor',
+ parameters: [
+ {__symbolic: 'reference', module: './foo', name: 'Foo'},
+ {__symbolic: 'reference', module: './foo', name: 'Foo'},
+ {__symbolic: 'reference', module: './foo', name: 'Foo'},
+ {__symbolic: 'reference', module: './foo', name: 'Foo'},
+ {__symbolic: 'reference', module: './foo', name: 'Foo'}
+ ]
+ }]
+ });
+ });
+
+ it('should treat exported class expressions as a class', () => {
+ const source = ts.createSourceFile(
+ '', `
+ export const InjectionToken: {new(desc: string): InjectionToken;} = class {
+ constructor(protected _desc: string) {}
+
+ toString(): string { return \`InjectionToken ${this._desc}\`; }
+ } as any;`,
+ ts.ScriptTarget.Latest, true);
+ const metadata = collector.getMetadata(source) !;
+ expect(metadata.metadata).toEqual({InjectionToken: {__symbolic: 'class'}});
+ });
+
+ describe('in strict mode', () => {
+ it('should throw if an error symbol is collecting a reference to a non-exported symbol', () => {
+ const source = program.getSourceFile('/local-symbol-ref.ts');
+ expect(() => collector.getMetadata(source, true)).toThrowError(/Reference to a local symbol/);
+ });
+
+ it('should throw if an error if collecting a reference to a non-exported function', () => {
+ const source = program.getSourceFile('/local-function-ref.ts');
+ expect(() => collector.getMetadata(source, true))
+ .toThrowError(/Reference to a non-exported function/);
+ });
+
+ it('should throw for references to unexpected types', () => {
+ const unsupported2 = program.getSourceFile('/unsupported-2.ts');
+ expect(() => collector.getMetadata(unsupported2, true))
+ .toThrowError(/Reference to non-exported class/);
+ });
+
+ it('should throw for errors in a static method', () => {
+ const unsupported3 = program.getSourceFile('/unsupported-3.ts');
+ expect(() => collector.getMetadata(unsupported3, true))
+ .toThrowError(/Reference to a non-exported class/);
+ });
+ });
+
+ describe('with invalid input', () => {
+ it('should not throw with a class with no name', () => {
+ const fileName = '/invalid-class.ts';
+ override(fileName, 'export class');
+ const invalidClass = program.getSourceFile(fileName);
+ expect(() => collector.getMetadata(invalidClass)).not.toThrow();
+ });
+
+ it('should not throw with a function with no name', () => {
+ const fileName = '/invalid-function.ts';
+ override(fileName, 'export function');
+ const invalidFunction = program.getSourceFile(fileName);
+ expect(() => collector.getMetadata(invalidFunction)).not.toThrow();
+ });
+ });
+
+ describe('inheritance', () => {
+ it('should record `extends` clauses for declared classes', () => {
+ const metadata = collector.getMetadata(program.getSourceFile('/class-inheritance.ts')) !;
+ expect(metadata.metadata['DeclaredChildClass'])
+ .toEqual({__symbolic: 'class', extends: {__symbolic: 'reference', name: 'ParentClass'}});
+ });
+
+ it('should record `extends` clauses for classes in the same file', () => {
+ const metadata = collector.getMetadata(program.getSourceFile('/class-inheritance.ts')) !;
+ expect(metadata.metadata['ChildClassSameFile'])
+ .toEqual({__symbolic: 'class', extends: {__symbolic: 'reference', name: 'ParentClass'}});
+ });
+
+ it('should record `extends` clauses for classes in a different file', () => {
+ const metadata = collector.getMetadata(program.getSourceFile('/class-inheritance.ts')) !;
+ expect(metadata.metadata['ChildClassOtherFile']).toEqual({
+ __symbolic: 'class',
+ extends: {
+ __symbolic: 'reference',
+ module: './class-inheritance-parent',
+ name: 'ParentClassFromOtherFile'
+ }
+ });
+ });
+
+ function expectClass(entry: MetadataEntry): entry is ClassMetadata {
+ const result = isClassMetadata(entry);
+ expect(result).toBeTruthy();
+ return result;
+ }
+
+ it('should collect the correct arity for a class', () => {
+ const metadata = collector.getMetadata(program.getSourceFile('/class-arity.ts')) !;
+
+ const zero = metadata.metadata['Zero'];
+ if (expectClass(zero)) expect(zero.arity).toBeUndefined();
+ const one = metadata.metadata['One'];
+ if (expectClass(one)) expect(one.arity).toBe(1);
+ const two = metadata.metadata['Two'];
+ if (expectClass(two)) expect(two.arity).toBe(2);
+ const three = metadata.metadata['Three'];
+ if (expectClass(three)) expect(three.arity).toBe(3);
+ const nine = metadata.metadata['Nine'];
+ if (expectClass(nine)) expect(nine.arity).toBe(9);
+ });
+ });
+
+ describe('regerssion', () => {
+ it('should be able to collect a short-hand property value', () => {
+ const metadata = collectSource(`
+ const children = { f1: 1 };
+ export const r = [
+ {path: ':locale', children}
+ ];
+ `);
+ expect(metadata.metadata).toEqual({r: [{path: ':locale', children: {f1: 1}}]});
+ });
+
+ // #17518
+ it('should skip a default function', () => {
+ const metadata = collectSource(`
+ export default function () {
+
+ const mainRoutes = [
+ {name: 'a', abstract: true, component: 'main'},
+
+ {name: 'a.welcome', url: '/welcome', component: 'welcome'}
+ ];
+
+ return mainRoutes;
+
+ }`);
+ expect(metadata).toBeUndefined();
+ });
+
+ it('should skip a named default export', () => {
+ const metadata = collectSource(`
+ function mainRoutes() {
+
+ const mainRoutes = [
+ {name: 'a', abstract: true, component: 'main'},
+
+ {name: 'a.welcome', url: '/welcome', component: 'welcome'}
+ ];
+
+ return mainRoutes;
+
+ }
+
+ exports = foo;
+ `);
+ expect(metadata).toBeUndefined();
+ });
+
+ it('should be able to collect an invalid access expression', () => {
+ const source = createSource(`
+ import {Component} from '@angular/core';
+
+ const value = [];
+ @Component({
+ provider: [{provide: 'some token', useValue: value[]}]
+ })
+ export class MyComponent {}
+ `);
+ const metadata = collector.getMetadata(source) !;
+ expect(metadata.metadata.MyComponent).toEqual({
+ __symbolic: 'class',
+ decorators: [{
+ __symbolic: 'error',
+ message: 'Expression form not supported',
+ line: 5,
+ character: 55
+ }]
+ });
+ });
+ });
+
+ describe('references', () => {
+ beforeEach(() => { collector = new MetadataCollector({quotedNames: true}); });
+
+ it('should record a reference to an exported field of a useValue', () => {
+ const metadata = collectSource(`
+ export var someValue = 1;
+ export const v = {
+ useValue: someValue
+ };
+ `);
+ expect(metadata.metadata['someValue']).toEqual(1);
+ expect(metadata.metadata['v']).toEqual({
+ useValue: {__symbolic: 'reference', name: 'someValue'}
+ });
+ });
+
+ it('should leave external references in place in an object literal', () => {
+ const metadata = collectSource(`
+ export const myLambda = () => [1, 2, 3];
+ const indirect = [{a: 1, b: 3: c: myLambda}];
+ export const v = {
+ v: {i: indirect}
+ }
+ `);
+ expect(metadata.metadata['v']).toEqual({
+ v: {i: [{a: 1, b: 3, c: {__symbolic: 'reference', name: 'myLambda'}}]}
+ });
+ });
+
+ it('should leave an external reference in place in an array literal', () => {
+ const metadata = collectSource(`
+ export const myLambda = () => [1, 2, 3];
+ const indirect = [1, 3, myLambda}];
+ export const v = {
+ v: {i: indirect}
+ }
+ `);
+ expect(metadata.metadata['v']).toEqual({
+ v: {i: [1, 3, {__symbolic: 'reference', name: 'myLambda'}]}
+ });
+ });
+ });
+
+ describe('substitutions', () => {
+ const lambdaTemp = 'lambdaTemp';
+
+ it('should be able to substitute a lambda', () => {
+ const source = createSource(`
+ const b = 1;
+ export const a = () => b;
+ `);
+ const metadata = collector.getMetadata(source, /* strict */ false, (value, node) => {
+ if (node.kind === ts.SyntaxKind.ArrowFunction) {
+ return {__symbolic: 'reference', name: lambdaTemp};
+ }
+ return value;
+ });
+ expect(metadata !.metadata['a']).toEqual({__symbolic: 'reference', name: lambdaTemp});
+ });
+
+ it('should compose substitution functions', () => {
+ const collector = new MetadataCollector({
+ substituteExpression: (value, node) => isMetadataGlobalReferenceExpression(value) &&
+ value.name == lambdaTemp ?
+ {__symbolic: 'reference', name: value.name + '2'} :
+ value
+ });
+ const source = createSource(`
+ const b = 1;
+ export const a = () => b;
+ `);
+ const metadata = collector.getMetadata(source, /* strict */ false, (value, node) => {
+ if (node.kind === ts.SyntaxKind.ArrowFunction) {
+ return {__symbolic: 'reference', name: lambdaTemp};
+ }
+ return value;
+ });
+ expect(metadata !.metadata['a']).toEqual({__symbolic: 'reference', name: lambdaTemp + '2'});
+ });
+ });
+
+ function override(fileName: string, content: string) {
+ host.overrideFile(fileName, content);
+ host.addFile(fileName);
+ program = service.getProgram();
+ }
+
+ function collectSource(content: string): ModuleMetadata {
+ const sourceFile = createSource(content);
+ return collector.getMetadata(sourceFile) !;
+ }
+});
+
+// TODO: Do not use \` in a template literal as it confuses clang-format
+const FILES: Directory = {
+ 'app': {
+ 'app.component.ts': `
+ import {Component as MyComponent, OnInit} from 'angular2/core';
+ import * as common from 'angular2/common';
+ import {Hero} from './hero';
+ import {HeroDetailComponent} from './hero-detail.component';
+ import HeroService from './hero.service';
+ // thrown away
+ import 'angular2/core';
+
+ @MyComponent({
+ selector: 'my-app',
+ template:` +
+ '`' +
+ `
+ My Heroes
+
+ -
+ {{hero.id | lowercase}} {{hero.name | uppercase}}
+
+
+
+ ` +
+ '`' +
+ `,
+ directives: [HeroDetailComponent, common.NgFor],
+ providers: [HeroService],
+ pipes: [common.LowerCasePipe, common.UpperCasePipe]
+ })
+ export class AppComponent implements OnInit {
+ public title = 'Tour of Heroes';
+ public heroes: Hero[];
+ public selectedHero: Hero;
+
+ constructor(private _heroService: HeroService) { }
+
+ onSelect(hero: Hero) { this.selectedHero = hero; }
+
+ ngOnInit() {
+ this.getHeroes()
+ }
+
+ getHeroes() {
+ this._heroService.getHeroesSlowly().then(heroes => this.heroes = heroes);
+ }
+ }`,
+ 'hero.ts': `
+ export interface Hero {
+ id: number;
+ name: string;
+ }`,
+ 'empty.ts': ``,
+ 'hero-detail.component.ts': `
+ import {Component, Input} from 'angular2/core';
+ import {Hero} from './hero';
+
+ @Component({
+ selector: 'my-hero-detail',
+ template: ` +
+ '`' +
+ `
+
+
{{hero.name}} details!
+
{{hero.id}}
+
+
+
+
+
+ ` +
+ '`' +
+ `,
+ })
+ export class HeroDetailComponent {
+ @Input() public hero: Hero;
+ }`,
+ 'mock-heroes.ts': `
+ import {Hero as Hero} from './hero';
+
+ export const HEROES: Hero[] = [
+ {"id": 11, "name": "Mr. Nice"},
+ {"id": 12, "name": "Narco"},
+ {"id": 13, "name": "Bombasto"},
+ {"id": 14, "name": "Celeritas"},
+ {"id": 15, "name": "Magneta"},
+ {"id": 16, "name": "RubberMan"},
+ {"id": 17, "name": "Dynama"},
+ {"id": 18, "name": "Dr IQ"},
+ {"id": 19, "name": "Magma"},
+ {"id": 20, "name": "Tornado"}
+ ];`,
+ 'default-exporter.ts': `
+ let a: string;
+ export default a;
+ `,
+ 'hero.service.ts': `
+ import {Injectable} from 'angular2/core';
+ import {HEROES} from './mock-heroes';
+ import {Hero} from './hero';
+
+ @Injectable()
+ class HeroService {
+ getHeros() {
+ return Promise.resolve(HEROES);
+ }
+
+ getHeroesSlowly() {
+ return new Promise(resolve =>
+ setTimeout(()=>resolve(HEROES), 2000)); // 2 seconds
+ }
+ }
+ export default HeroService;`,
+ 'cases-data.ts': `
+ import {Injectable, Input} from 'angular2/core';
+
+ @Injectable()
+ export class CaseAny {
+ constructor(param: any) {}
+ }
+
+ @Injectable()
+ export class GetProp {
+ private _name: string;
+ @Input('firstName') get name(): string {
+ return this._name;
+ }
+ }
+
+ @Injectable()
+ export class SetProp {
+ private _name: string;
+ @Input('firstName') set name(value: string) {
+ this._name = value;
+ }
+ }
+
+ @Injectable()
+ export class FullProp {
+ private _name: string;
+ @Input('firstName') get name(): string {
+ return this._name;
+ }
+ set name(value: string) {
+ this._name = value;
+ }
+ }
+
+ export class ClassReference { }
+ export class NgForRow {
+
+ }
+
+ @Injectable()
+ export class NgFor {
+ constructor (public ref: ClassReference) {}
+ }
+ `,
+ 'error-cases.ts': `
+ import HeroService from './hero.service';
+
+ export class CaseCtor {
+ constructor(private _heroService: HeroService) { }
+ }
+ `
+ },
+ 'promise.ts': `
+ interface PromiseLike {
+ then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => TResult | PromiseLike): PromiseLike;
+ then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => void): PromiseLike;
+ }
+
+ interface Promise {
+ then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => TResult | PromiseLike): Promise;
+ then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => void): Promise;
+ catch(onrejected?: (reason: any) => T | PromiseLike): Promise;
+ catch(onrejected?: (reason: any) => void): Promise;
+ }
+
+ interface PromiseConstructor {
+ prototype: Promise;
+ new (executor: (resolve: (value?: T | PromiseLike) => void, reject: (reason?: any) => void) => void): Promise;
+ reject(reason: any): Promise;
+ reject(reason: any): Promise;
+ resolve(value: T | PromiseLike): Promise;
+ resolve(): Promise;
+ }
+
+ declare var Promise: PromiseConstructor;
+ `,
+ 'class-arity.ts': `
+ export class Zero {}
+ export class One {}
+ export class Two {}
+ export class Three {}
+ export class Nine {}
+ `,
+ 'unsupported-1.ts': `
+ export let {a, b} = {a: 1, b: 2};
+ export let [c, d] = [1, 2];
+ export let e;
+ `,
+ 'unsupported-2.ts': `
+ import {Injectable} from 'angular2/core';
+
+ class Foo {}
+
+ @Injectable()
+ export class Bar {
+ constructor(private f: Foo) {}
+ }
+ `,
+ 'unsupported-3.ts': `
+ class Foo {}
+
+ export class SomeClass {
+ static someStatic() {
+ return Foo;
+ }
+ }
+ `,
+ 'interface-reference.ts': `
+ import {Injectable, Inject} from 'angular2/core';
+ export interface Test {}
+
+ @Injectable()
+ export class SomeClass {
+ constructor(@Inject("a") test: Test) {}
+ }
+ `,
+ 'import-star.ts': `
+ import {Injectable} from 'angular2/core';
+ import * as common from 'angular2/common';
+
+ @Injectable()
+ export class SomeClass {
+ constructor(private f: common.NgFor) {}
+ }
+ `,
+ 'exported-classes.ts': `
+ export class SimpleClass {}
+ export abstract class AbstractClass {}
+ export declare class DeclaredClass {}
+ `,
+ 'class-inheritance-parent.ts': `
+ export class ParentClassFromOtherFile {}
+ `,
+ 'class-inheritance.ts': `
+ import {ParentClassFromOtherFile} from './class-inheritance-parent';
+
+ export class ParentClass {}
+
+ export declare class DeclaredChildClass extends ParentClass {}
+
+ export class ChildClassSameFile extends ParentClass {}
+
+ export class ChildClassOtherFile extends ParentClassFromOtherFile {}
+ `,
+ 'exported-functions.ts': `
+ export function one(a: string, b: string, c: string) {
+ return {a: a, b: b, c: c};
+ }
+ export function two(a: string, b: string, c: string) {
+ return {a, b, c};
+ }
+ export function three({a, b, c}: {a: string, b: string, c: string}) {
+ return [a, b, c];
+ }
+ export function supportsState(): boolean {
+ return !!window.history.pushState;
+ }
+ export function complexFn(x: any): boolean {
+ if (x) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ export declare function declaredFn();
+ `,
+ 'exported-type.ts': `
+ export type SomeType = 'a' | 'b';
+ `,
+ 'exported-enum.ts': `
+ import {constValue} from './exported-consts';
+
+ export const someValue = 30;
+ export enum SomeEnum { A, B, C = 100, D };
+ export enum ComplexEnum { A, B, C = someValue, D = someValue + 10, E = constValue };
+ `,
+ 'exported-consts.ts': `
+ export const constValue = 100;
+ `,
+ 'static-method.ts': `
+ export class MyModule {
+ static with(comp: any): any[] {
+ return [
+ MyModule,
+ { provider: 'a', useValue: comp }
+ ];
+ }
+ }
+ `,
+ 'static-method-with-default.ts': `
+ export class MyModule {
+ static with(comp: any, foo: boolean = true, bar: boolean = false): any[] {
+ return [
+ MyModule,
+ foo ? { provider: 'a', useValue: comp } : {provider: 'b', useValue: comp},
+ bar ? { provider: 'c', useValue: comp } : {provider: 'd', useValue: comp}
+ ];
+ }
+ }
+ `,
+ 'static-method-call.ts': `
+ import {Component} from 'angular2/core';
+ import {MyModule} from './static-method';
+
+ @Component({
+ providers: MyModule.with('a')
+ })
+ export class Foo { }
+ `,
+ 'static-field.ts': `
+ export class MyModule {
+ static VALUE = 'Some string';
+ }
+ `,
+ 'static-field-reference.ts': `
+ import {Component} from 'angular2/core';
+ import {MyModule} from './static-field';
+
+ @Component({
+ providers: [ { provide: 'a', useValue: MyModule.VALUE } ]
+ })
+ export class Foo { }
+ `,
+ 'static-method-with-if.ts': `
+ export class MyModule {
+ static with(cond: boolean): any[] {
+ return [
+ MyModule,
+ { provider: 'a', useValue: cond ? '1' : '2' }
+ ];
+ }
+ }
+ `,
+ 're-exports.ts': `
+ export {MyModule} from './static-field';
+ export {Foo as OtherModule} from './static-field-reference';
+ export * from 'angular2/core';
+ `,
+ 're-exports-2.ts': `
+ import {MyModule} from './static-field';
+ import {Foo as OtherModule} from './static-field-reference';
+ class MyClass {}
+ export {OtherModule, MyModule as MyOtherModule, MyClass};
+ `,
+ 'export-as.d.ts': `
+ declare function someFunction(): void;
+ export { someFunction as SomeFunction };
+ `,
+ 'local-symbol-ref.ts': `
+ import {Component, Validators} from 'angular2/core';
+
+ var REQUIRED;
+
+ export const REQUIRED_VALIDATOR: any = {
+ provide: 'SomeToken',
+ useValue: REQUIRED,
+ multi: true
+ };
+
+ @Component({
+ providers: [REQUIRED_VALIDATOR]
+ })
+ export class SomeComponent {}
+ `,
+ 'private-enum.ts': `
+ export enum PublicEnum { a, b, c }
+ enum PrivateEnum { e, f, g }
+ `,
+ 'local-function-ref.ts': `
+ import {Component, Validators} from 'angular2/core';
+
+ function required() {}
+
+ export const REQUIRED_VALIDATOR: any = {
+ provide: 'SomeToken',
+ useValue: required,
+ multi: true
+ };
+
+ @Component({
+ providers: [REQUIRED_VALIDATOR]
+ })
+ export class SomeComponent {}
+ `,
+ 'local-symbol-ref-func.ts': `
+ var localSymbol: any[];
+
+ export function foo(index: number): string {
+ return localSymbol[index];
+ }
+ `,
+ 'node_modules': {
+ 'angular2': {
+ 'core.d.ts': `
+ export interface Type extends Function { }
+ export interface TypeDecorator {
+ (type: T): T;
+ (target: Object, propertyKey?: string | symbol, parameterIndex?: number): void;
+ annotations: any[];
+ }
+ export interface ComponentDecorator extends TypeDecorator { }
+ export interface ComponentFactory {
+ (obj: {
+ selector?: string;
+ inputs?: string[];
+ outputs?: string[];
+ properties?: string[];
+ events?: string[];
+ host?: {
+ [key: string]: string;
+ };
+ bindings?: any[];
+ providers?: any[];
+ exportAs?: string;
+ moduleId?: string;
+ queries?: {
+ [key: string]: any;
+ };
+ viewBindings?: any[];
+ viewProviders?: any[];
+ templateUrl?: string;
+ template?: string;
+ styleUrls?: string[];
+ styles?: string[];
+ directives?: Array;
+ pipes?: Array;
+ }): ComponentDecorator;
+ }
+ export declare var Component: ComponentFactory;
+ export interface InputFactory {
+ (bindingPropertyName?: string): any;
+ new (bindingPropertyName?: string): any;
+ }
+ export declare var Input: InputFactory;
+ export interface InjectableFactory {
+ (): any;
+ }
+ export declare var Injectable: InjectableFactory;
+ export interface InjectFactory {
+ (binding?: any): any;
+ new (binding?: any): any;
+ }
+ export declare var Inject: InjectFactory;
+ export interface OnInit {
+ ngOnInit(): any;
+ }
+ export class Validators {
+ static required(): void;
+ }
+ `,
+ 'common.d.ts': `
+ export declare class NgFor {
+ ngForOf: any;
+ ngForTemplate: any;
+ ngDoCheck(): void;
+ }
+ export declare class LowerCasePipe {
+ transform(value: string, args?: any[]): string;
+ }
+ export declare class UpperCasePipe {
+ transform(value: string, args?: any[]): string;
+ }
+ `
+ }
+ }
+};
+
+function createSource(text: string): ts.SourceFile {
+ return ts.createSourceFile('', text, ts.ScriptTarget.Latest, true);
+}
diff --git a/packages/compiler-cli/test/metadata/evaluator_spec.ts b/packages/compiler-cli/test/metadata/evaluator_spec.ts
new file mode 100644
index 0000000000..d097e1955d
--- /dev/null
+++ b/packages/compiler-cli/test/metadata/evaluator_spec.ts
@@ -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 == '1');
+ expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bNotEqual')))
+ .toEqual(1 != '1');
+ expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bIdentical')))
+ .toEqual(1 === '1');
+ expect(evaluator.evaluateNode(findVarInitializer(expressions, 'bNotIdentical')))
+ .toEqual(1 !== '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 == "1"; // true
+ export var bNotEqual = 1 != "1"; // false
+ export var bIdentical = 1 === "1"; // false
+ export var bNotIdentical = 1 !== "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';
+ `
+};
diff --git a/packages/compiler-cli/test/metadata/index_writer_spec.ts b/packages/compiler-cli/test/metadata/index_writer_spec.ts
new file mode 100644
index 0000000000..ee0678d6ac
--- /dev/null
+++ b/packages/compiler-cli/test/metadata/index_writer_spec.ts
@@ -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';`);
+ });
+});
\ No newline at end of file
diff --git a/packages/compiler-cli/test/metadata/symbols_spec.ts b/packages/compiler-cli/test/metadata/symbols_spec.ts
new file mode 100644
index 0000000000..c0cacb0db6
--- /dev/null
+++ b/packages/compiler-cli/test/metadata/symbols_spec.ts
@@ -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 = node;
+ const nameNode = 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';
+ `
+};
diff --git a/packages/compiler-cli/test/metadata/typescript.mocks.ts b/packages/compiler-cli/test/metadata/typescript.mocks.ts
new file mode 100644
index 0000000000..5b95e994c8
--- /dev/null
+++ b/packages/compiler-cli/test/metadata/typescript.mocks.ts
@@ -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