From 33910ddfc98d092e514d90a2cec3b5daaf3d4277 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Thu, 15 Dec 2016 09:12:40 -0800 Subject: [PATCH] refactor(compiler): store metadata of top level symbols also in summaries (#13289) This allows a build using summaries to not need .metadata.json files at all any more. Part of #12787 --- .../integrationtest/test/i18n_spec.ts | 10 +- .../integrationtest/test/test_ngtools_api.ts | 9 +- .../integrationtest/test/test_summaries.ts | 27 +- modules/@angular/compiler-cli/src/codegen.ts | 10 +- .../compiler-cli/src/compiler_host.ts | 39 +- .../@angular/compiler-cli/src/extractor.ts | 4 +- .../@angular/compiler-cli/src/ngtools_api.ts | 7 +- .../@angular/compiler-cli/test/main_spec.ts | 6 +- modules/@angular/compiler/index.ts | 2 + .../src/animation/animation_parser.ts | 5 +- modules/@angular/compiler/src/aot/compiler.ts | 151 ++++--- .../compiler/src/aot/compiler_factory.ts | 13 +- .../compiler/src/aot/compiler_host.ts | 7 +- .../compiler/src/aot/compiler_options.ts | 2 - .../src/aot/static_reflection_capabilities.ts | 3 +- .../compiler/src/aot/static_reflector.ts | 418 +++++------------- .../compiler/src/aot/static_symbol.ts | 20 + .../src/aot/static_symbol_resolver.ts | 289 ++++++++++++ .../compiler/src/aot/summary_resolver.ts | 139 ++---- .../compiler/src/aot/summary_serializer.ts | 183 ++++++++ modules/@angular/compiler/src/aot/utils.ts | 19 - .../@angular/compiler/src/compile_metadata.ts | 14 +- .../compiler/src/directive_normalizer.ts | 5 +- .../compiler/src/directive_resolver.ts | 6 +- .../src/directive_wrapper_compiler.ts | 5 +- .../compiler/src/expression_parser/lexer.ts | 4 +- .../compiler/src/expression_parser/parser.ts | 5 +- .../@angular/compiler/src/i18n/extractor.ts | 34 +- modules/@angular/compiler/src/i18n/index.ts | 2 +- modules/@angular/compiler/src/injectable.ts | 16 + modules/@angular/compiler/src/jit/compiler.ts | 5 +- .../compiler/src/jit/compiler_factory.ts | 5 +- .../compiler/src/metadata_resolver.ts | 112 +++-- .../compiler/src/ml_parser/html_parser.ts | 4 +- .../compiler/src/ng_module_compiler.ts | 5 +- .../compiler/src/ng_module_resolver.ts | 5 +- .../@angular/compiler/src/output/path_util.ts | 3 +- .../compiler/src/output/ts_emitter.ts | 2 +- .../@angular/compiler/src/pipe_resolver.ts | 5 +- .../src/schema/dom_element_schema_registry.ts | 5 +- .../@angular/compiler/src/style_compiler.ts | 5 +- .../@angular/compiler/src/summary_resolver.ts | 15 +- .../src/template_parser/template_parser.ts | 6 +- modules/@angular/compiler/src/url_resolver.ts | 6 +- .../src/view_compiler/view_compiler.ts | 5 +- .../test/aot/static_reflector_spec.ts | 302 ++----------- .../test/aot/static_symbol_resolver_spec.ts | 372 ++++++++++++++++ .../test/aot/summary_resolver_spec.ts | 149 ++----- .../test/aot/summary_serializer_spec.ts | 199 +++++++++ .../test/output/ts_emitter_node_only_spec.ts | 15 +- modules/@angular/core/src/linker/compiler.ts | 3 +- .../language-service/src/typescript_host.ts | 31 +- 52 files changed, 1702 insertions(+), 1011 deletions(-) create mode 100644 modules/@angular/compiler/src/aot/static_symbol_resolver.ts create mode 100644 modules/@angular/compiler/src/aot/summary_serializer.ts delete mode 100644 modules/@angular/compiler/src/aot/utils.ts create mode 100644 modules/@angular/compiler/src/injectable.ts create mode 100644 modules/@angular/compiler/test/aot/static_symbol_resolver_spec.ts create mode 100644 modules/@angular/compiler/test/aot/summary_serializer_spec.ts diff --git a/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts b/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts index d46ad0d994..e8c22e74d6 100644 --- a/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts +++ b/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts @@ -34,9 +34,9 @@ const EXPECTED_XMB = ` ]> - other-3rdP-component translate me Welcome + other-3rdP-component `; @@ -44,10 +44,6 @@ const EXPECTED_XLIFF = ` - - other-3rdP-component - - translate me @@ -58,6 +54,10 @@ const EXPECTED_XLIFF = ` Welcome + + other-3rdP-component + + diff --git a/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts b/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts index 24d7649c67..66f89b6baf 100644 --- a/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts +++ b/modules/@angular/compiler-cli/integrationtest/test/test_ngtools_api.ts @@ -52,12 +52,9 @@ function codeGenTest() { const config = tsc.readConfiguration(project, basePath); const hostContext = new NodeCompilerHostContext(); const delegateHost = ts.createCompilerHost(config.parsed.options, true); - const host: ts.CompilerHost = Object.assign({}, delegateHost, { - writeFile: (fileName: string, ...rest: any[]) => { - wroteFiles.push(fileName); - return delegateHost.writeFile.call(delegateHost, fileName, ...rest); - } - }); + const host: ts.CompilerHost = Object.assign( + {}, delegateHost, + {writeFile: (fileName: string, ...rest: any[]) => { wroteFiles.push(fileName); }}); const program = ts.createProgram(config.parsed.fileNames, config.parsed.options, host); config.ngOptions.basePath = basePath; diff --git a/modules/@angular/compiler-cli/integrationtest/test/test_summaries.ts b/modules/@angular/compiler-cli/integrationtest/test/test_summaries.ts index faaa19cef3..4f064f2f9c 100644 --- a/modules/@angular/compiler-cli/integrationtest/test/test_summaries.ts +++ b/modules/@angular/compiler-cli/integrationtest/test/test_summaries.ts @@ -27,9 +27,14 @@ function main() { const basePath = path.resolve(__dirname, '..'); const project = path.resolve(basePath, 'tsconfig-build.json'); const readFiles: string[] = []; + const writtenFiles: {fileName: string, content: string}[] = []; class AssertingHostContext extends NodeCompilerHostContext { readFile(fileName: string): string { + if (/.*\/node_modules\/.*/.test(fileName) && !/.*ngsummary\.json$/.test(fileName)) { + // Only allow to read summaries from node_modules + return null; + } readFiles.push(path.relative(basePath, fileName)); return super.readFile(fileName); } @@ -45,16 +50,29 @@ function main() { config.ngOptions.generateCodeForLibraries = false; console.log(`>>> running codegen for ${project}`); - codegen(config, (host) => new AssertingHostContext()) + codegen( + config, + (host) => { + host.writeFile = (fileName: string, content: string) => { + fileName = path.relative(basePath, fileName); + writtenFiles.push({fileName, content}); + }; + return new AssertingHostContext(); + }) .then((exitCode: any) => { console.log(`>>> codegen done, asserting read files`); assertSomeFileMatch(readFiles, /^node_modules\/.*\.ngsummary\.json$/); + assertNoFileMatch(readFiles, /^node_modules\/.*\.metadata.json$/); assertNoFileMatch(readFiles, /^node_modules\/.*\.html$/); assertNoFileMatch(readFiles, /^node_modules\/.*\.css$/); assertNoFileMatch(readFiles, /^src\/.*\.ngsummary\.json$/); assertSomeFileMatch(readFiles, /^src\/.*\.html$/); assertSomeFileMatch(readFiles, /^src\/.*\.css$/); + + console.log(`>>> asserting written files`); + assertWrittenFile(writtenFiles, /^src\/module\.ngfactory\.ts$/, /class MainModuleInjector/); + console.log(`done, no errors.`); process.exit(exitCode); }) @@ -97,4 +115,11 @@ function assertNoFileMatch(fileNames: string[], pattern: RegExp) { `Expected no read files match ${pattern}, but found: \n${matches.join('\n')}`); } +function assertWrittenFile( + files: {fileName: string, content: string}[], filePattern: RegExp, contentPattern: RegExp) { + assert( + files.some(file => filePattern.test(file.fileName) && contentPattern.test(file.content)), + `Expected some written files for ${filePattern} and content ${contentPattern}`); +} + main(); diff --git a/modules/@angular/compiler-cli/src/codegen.ts b/modules/@angular/compiler-cli/src/codegen.ts index 2d4fbdfe01..2bc5561a65 100644 --- a/modules/@angular/compiler-cli/src/codegen.ts +++ b/modules/@angular/compiler-cli/src/codegen.ts @@ -21,9 +21,7 @@ import {CompilerHost, CompilerHostContext, ModuleResolutionHostAdapter} from './ import {PathMappedCompilerHost} from './path_mapped_compiler_host'; import {Console} from './private_import_core'; -const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/; const GENERATED_META_FILES = /\.json$/; -const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$/; const PREAMBLE = `/** * @fileoverview This file is generated by the Angular 2 template compiler. @@ -102,14 +100,8 @@ export class CodeGenerator { debug: options.debug === true, translations: transContent, i18nFormat: cliOptions.i18nFormat, - locale: cliOptions.locale, - excludeFilePattern: options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : - GENERATED_FILES + locale: cliOptions.locale }); return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost); } } - -export function excludeFilePattern(options: AngularCompilerOptions): RegExp { - return options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES; -} diff --git a/modules/@angular/compiler-cli/src/compiler_host.ts b/modules/@angular/compiler-cli/src/compiler_host.ts index 2dbea0db20..f9d6a2ec20 100644 --- a/modules/@angular/compiler-cli/src/compiler_host.ts +++ b/modules/@angular/compiler-cli/src/compiler_host.ts @@ -16,6 +16,8 @@ const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; const DTS = /\.d\.ts$/; const NODE_MODULES = '/node_modules/'; const IS_GENERATED = /\.(ngfactory|ngstyle)$/; +const GENERATED_FILES = /\.ngfactory\.ts$|\.ngstyle\.ts$/; +const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.ngstyle\.ts$/; export interface CompilerHostContext extends ts.ModuleResolutionHost { readResource(fileName: string): Promise; @@ -28,6 +30,7 @@ export class CompilerHost implements AotCompilerHost { protected basePath: string; private genDir: string; private resolverCache = new Map(); + protected resolveModuleNameHost: CompilerHostContext; constructor( protected program: ts.Program, protected options: AngularCompilerOptions, @@ -38,12 +41,31 @@ export class CompilerHost implements AotCompilerHost { const genPath: string = path.relative(this.basePath, this.genDir); this.isGenDirChildOfRootDir = genPath === '' || !genPath.startsWith('..'); + this.resolveModuleNameHost = Object.create(this.context); + + // When calling ts.resolveModuleName, + // additional allow checks for .d.ts files to be done based on + // checks for .ngsummary.json files, + // so that our codegen depends on fewer inputs and requires to be called + // less often. + // This is needed as we use ts.resolveModuleName in reflector_host + // and it should be able to resolve summary file names. + this.resolveModuleNameHost.fileExists = (fileName: string): boolean => { + if (this.context.fileExists(fileName)) { + return true; + } + if (DTS.test(fileName)) { + const base = fileName.substring(0, fileName.length - 5); + return this.context.fileExists(base + '.ngsummary.json'); + } + return false; + }; } // We use absolute paths on disk as canonical. getCanonicalFileName(fileName: string): string { return fileName; } - moduleNameToFileName(m: string, containingFile: string) { + moduleNameToFileName(m: string, containingFile: string): string|null { if (!containingFile || !containingFile.length) { if (m.indexOf('.') === 0) { throw new Error('Resolution of relative paths requires a containing file.'); @@ -53,7 +75,8 @@ export class CompilerHost implements AotCompilerHost { } m = m.replace(EXT, ''); const resolved = - ts.resolveModuleName(m, containingFile.replace(/\\/g, '/'), this.options, this.context) + ts.resolveModuleName( + m, containingFile.replace(/\\/g, '/'), this.options, this.resolveModuleNameHost) .resolvedModule; return resolved ? this.getCanonicalFileName(resolved.resolvedFileName) : null; }; @@ -213,11 +236,21 @@ export class CompilerHost implements AotCompilerHost { loadResource(filePath: string): Promise { return this.context.readResource(filePath); } - loadSummary(filePath: string): string { return this.context.readFile(filePath); } + loadSummary(filePath: string): string|null { + if (this.context.fileExists(filePath)) { + return this.context.readFile(filePath); + } + } getOutputFileName(sourceFilePath: string): string { return sourceFilePath.replace(EXT, '') + '.d.ts'; } + + isSourceFile(filePath: string): boolean { + const excludeRegex = + this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES; + return !excludeRegex.test(filePath); + } } export class CompilerHostContextAdapter { diff --git a/modules/@angular/compiler-cli/src/extractor.ts b/modules/@angular/compiler-cli/src/extractor.ts index 439ce01d50..af49d16372 100644 --- a/modules/@angular/compiler-cli/src/extractor.ts +++ b/modules/@angular/compiler-cli/src/extractor.ts @@ -17,7 +17,6 @@ import * as compiler from '@angular/compiler'; import * as tsc from '@angular/tsc-wrapped'; import * as ts from 'typescript'; -import {excludeFilePattern} from './codegen'; import {CompilerHost, ModuleResolutionHostAdapter} from './compiler_host'; export class Extractor { @@ -36,8 +35,7 @@ export class Extractor { if (!ngCompilerHost) ngCompilerHost = new CompilerHost(program, options, new ModuleResolutionHostAdapter(moduleResolverHost)); - const {extractor: ngExtractor} = compiler.Extractor.create( - ngCompilerHost, {excludeFilePattern: excludeFilePattern(options)}); + const {extractor: ngExtractor} = compiler.Extractor.create(ngCompilerHost); return new Extractor(ngExtractor, ngCompilerHost, program); } } diff --git a/modules/@angular/compiler-cli/src/ngtools_api.ts b/modules/@angular/compiler-cli/src/ngtools_api.ts index 34bc593129..486a72f595 100644 --- a/modules/@angular/compiler-cli/src/ngtools_api.ts +++ b/modules/@angular/compiler-cli/src/ngtools_api.ts @@ -13,7 +13,7 @@ * something else. */ -import {AotCompilerHost, StaticReflector} from '@angular/compiler'; +import {AotCompilerHost, AotSummaryResolver, StaticReflector, StaticSymbolCache, StaticSymbolResolver} from '@angular/compiler'; import {AngularCompilerOptions, NgcCliOptions} from '@angular/tsc-wrapped'; import * as ts from 'typescript'; @@ -111,7 +111,10 @@ export class NgTools_InternalApi_NG_2 { new PathMappedCompilerHost(program, angularCompilerOptions, moduleResolutionHost) : new CompilerHost(program, angularCompilerOptions, moduleResolutionHost); - const staticReflector = new StaticReflector(ngCompilerHost); + const symbolCache = new StaticSymbolCache(); + const summaryResolver = new AotSummaryResolver(ngCompilerHost, symbolCache); + const symbolResolver = new StaticSymbolResolver(ngCompilerHost, symbolCache, summaryResolver); + const staticReflector = new StaticReflector(symbolResolver); const routeMap = listLazyRoutesOfModule(options.entryModule, ngCompilerHost, staticReflector); return Object.keys(routeMap).reduce( diff --git a/modules/@angular/compiler-cli/test/main_spec.ts b/modules/@angular/compiler-cli/test/main_spec.ts index a0f9948db6..370c9556ee 100644 --- a/modules/@angular/compiler-cli/test/main_spec.ts +++ b/modules/@angular/compiler-cli/test/main_spec.ts @@ -29,13 +29,17 @@ describe('compiler-cli', () => { "types": [], "outDir": "built", "declaration": true, - "module": "es2015" + "module": "es2015", + "moduleResolution": "node" }, "angularCompilerOptions": { "annotateForClosureCompiler": true }, "files": ["test.ts"] }`); + const nodeModulesPath = path.resolve(basePath, 'node_modules'); + fs.mkdirSync(nodeModulesPath); + fs.symlinkSync(path.resolve(__dirname, '..', '..'), path.resolve(nodeModulesPath, '@angular')); }); // Restore reflector since AoT compiler will update it with a new static reflector diff --git a/modules/@angular/compiler/index.ts b/modules/@angular/compiler/index.ts index 269417aa3e..c6674f0e4d 100644 --- a/modules/@angular/compiler/index.ts +++ b/modules/@angular/compiler/index.ts @@ -32,7 +32,9 @@ export * from './src/aot/compiler_host'; export * from './src/aot/static_reflector'; export * from './src/aot/static_reflection_capabilities'; export * from './src/aot/static_symbol'; +export * from './src/aot/static_symbol_resolver'; export * from './src/aot/summary_resolver'; +export * from './src/summary_resolver'; export {JitCompiler} from './src/jit/compiler'; export * from './src/jit/compiler_factory'; export * from './src/url_resolver'; diff --git a/modules/@angular/compiler/src/animation/animation_parser.ts b/modules/@angular/compiler/src/animation/animation_parser.ts index b11c6f2b41..3cbf082103 100644 --- a/modules/@angular/compiler/src/animation/animation_parser.ts +++ b/modules/@angular/compiler/src/animation/animation_parser.ts @@ -6,11 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {Injectable} from '@angular/core'; - import {CompileAnimationAnimateMetadata, CompileAnimationEntryMetadata, CompileAnimationGroupMetadata, CompileAnimationKeyframesSequenceMetadata, CompileAnimationMetadata, CompileAnimationSequenceMetadata, CompileAnimationStateDeclarationMetadata, CompileAnimationStateTransitionMetadata, CompileAnimationStyleMetadata, CompileAnimationWithStepsMetadata, CompileDirectiveMetadata, identifierName} from '../compile_metadata'; import {StringMapWrapper} from '../facade/collection'; import {isBlank, isPresent} from '../facade/lang'; +import {CompilerInjectable} from '../injectable'; import {ParseError} from '../parse_util'; import {ANY_STATE, FILL_STYLE_FLAG} from '../private_import_core'; import {ElementSchemaRegistry} from '../schema/element_schema_registry'; @@ -35,7 +34,7 @@ export class AnimationEntryParseResult { constructor(public ast: AnimationEntryAst, public errors: AnimationParseError[]) {} } -@Injectable() +@CompilerInjectable() export class AnimationParser { constructor(private _schema: ElementSchemaRegistry) {} diff --git a/modules/@angular/compiler/src/aot/compiler.ts b/modules/@angular/compiler/src/aot/compiler.ts index 896c1e9ada..6e017cce8c 100644 --- a/modules/@angular/compiler/src/aot/compiler.ts +++ b/modules/@angular/compiler/src/aot/compiler.ts @@ -20,34 +20,34 @@ import {NgModuleCompiler} from '../ng_module_compiler'; import {OutputEmitter} from '../output/abstract_emitter'; import * as o from '../output/output_ast'; import {CompiledStylesheet, StyleCompiler} from '../style_compiler'; +import {SummaryResolver} from '../summary_resolver'; import {TemplateParser} from '../template_parser/template_parser'; import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDependency, ViewCompileResult, ViewCompiler} from '../view_compiler/view_compiler'; -import {AotCompilerOptions} from './compiler_options'; +import {AotCompilerHost} from './compiler_host'; import {GeneratedFile} from './generated_file'; -import {StaticReflector} from './static_reflector'; import {StaticSymbol} from './static_symbol'; -import {AotSummaryResolver} from './summary_resolver'; -import {filterFileByPatterns} from './utils'; +import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver'; +import {serializeSummaries, summaryFileName} from './summary_serializer'; export class AotCompiler { private _animationCompiler = new AnimationCompiler(); constructor( - private _metadataResolver: CompileMetadataResolver, private _templateParser: TemplateParser, - private _styleCompiler: StyleCompiler, private _viewCompiler: ViewCompiler, - private _dirWrapperCompiler: DirectiveWrapperCompiler, + private _host: AotCompilerHost, private _metadataResolver: CompileMetadataResolver, + private _templateParser: TemplateParser, private _styleCompiler: StyleCompiler, + private _viewCompiler: ViewCompiler, private _dirWrapperCompiler: DirectiveWrapperCompiler, private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter, - private _summaryResolver: AotSummaryResolver, private _localeId: string, + private _summaryResolver: SummaryResolver, private _localeId: string, private _translationFormat: string, private _animationParser: AnimationParser, - private _staticReflector: StaticReflector, private _options: AotCompilerOptions) {} + private _symbolResolver: StaticSymbolResolver) {} clearCache() { this._metadataResolver.clearCache(); } compileAll(rootFiles: string[]): Promise { - const programSymbols = extractProgramSymbols(this._staticReflector, rootFiles, this._options); + const programSymbols = extractProgramSymbols(this._symbolResolver, rootFiles, this._host); const {ngModuleByPipeOrDirective, files, ngModules} = - analyzeAndValidateNgModules(programSymbols, this._options, this._metadataResolver); + analyzeAndValidateNgModules(programSymbols, this._host, this._metadataResolver); return Promise .all(ngModules.map( ngModule => this._metadataResolver.loadNgModuleDirectiveAndPipeMetadata( @@ -56,27 +56,21 @@ export class AotCompiler { const sourceModules = files.map( file => this._compileSrcFile( file.srcUrl, ngModuleByPipeOrDirective, file.directives, file.pipes, - file.ngModules)); + file.ngModules, file.injectables)); return ListWrapper.flatten(sourceModules); }); } private _compileSrcFile( srcFileUrl: string, ngModuleByPipeOrDirective: Map, - directives: StaticSymbol[], pipes: StaticSymbol[], - ngModules: StaticSymbol[]): GeneratedFile[] { + directives: StaticSymbol[], pipes: StaticSymbol[], ngModules: StaticSymbol[], + injectables: StaticSymbol[]): GeneratedFile[] { const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1]; const statements: o.Statement[] = []; const exportedVars: string[] = []; const generatedFiles: GeneratedFile[] = []; - // write summary files - const summaries: CompileTypeSummary[] = [ - ...ngModules.map(ref => this._metadataResolver.getNgModuleSummary(ref)), - ...directives.map(ref => this._metadataResolver.getDirectiveSummary(ref)), - ...pipes.map(ref => this._metadataResolver.getPipeSummary(ref)) - ]; - generatedFiles.push(this._summaryResolver.serializeSummaries(srcFileUrl, summaries)); + generatedFiles.push(this._createSummary(srcFileUrl, directives, pipes, ngModules, injectables)); // compile all ng modules exportedVars.push( @@ -121,6 +115,22 @@ export class AotCompiler { return generatedFiles; } + private _createSummary( + srcFileUrl: string, directives: StaticSymbol[], pipes: StaticSymbol[], + ngModules: StaticSymbol[], injectables: StaticSymbol[]): GeneratedFile { + const symbolSummaries = this._symbolResolver.getSymbolsOf(srcFileUrl) + .map(symbol => this._symbolResolver.resolveSymbol(symbol)); + const typeSummaries = [ + ...ngModules.map(ref => this._metadataResolver.getNgModuleSummary(ref)), + ...directives.map(ref => this._metadataResolver.getDirectiveSummary(ref)), + ...pipes.map(ref => this._metadataResolver.getPipeSummary(ref)), + ...injectables.map(ref => this._metadataResolver.getInjectableSummary(ref)) + ]; + const json = serializeSummaries( + this._host, this._summaryResolver, this._symbolResolver, symbolSummaries, typeSummaries); + return new GeneratedFile(srcFileUrl, summaryFileName(srcFileUrl), json); + } + private _compileModule(ngModuleType: StaticSymbol, targetStatements: o.Statement[]): string { const ngModule = this._metadataResolver.getNgModuleMetadata(ngModuleType); const providers: CompileProviderMetadata[] = []; @@ -142,7 +152,7 @@ export class AotCompiler { const appCompileResult = this._ngModuleCompiler.compile(ngModule, providers); appCompileResult.dependencies.forEach((dep) => { - dep.placeholder.reference = this._staticReflector.getStaticSymbol( + dep.placeholder.reference = this._symbolResolver.getStaticSymbol( _ngfactoryModuleUrl(identifierModuleUrl(dep.comp)), _componentFactoryName(dep.comp)); }); @@ -163,7 +173,7 @@ export class AotCompiler { compMeta: CompileDirectiveMetadata, ngModule: CompileNgModuleMetadata, fileSuffix: string, targetStatements: o.Statement[]): string { const hostMeta = createHostComponentMeta( - this._staticReflector.getStaticSymbol( + this._symbolResolver.getStaticSymbol( identifierModuleUrl(compMeta.type), `${identifierName(compMeta.type)}_Host`), compMeta); const hostViewFactoryVar = this._compileComponent( @@ -206,16 +216,16 @@ export class AotCompiler { compMeta, parsedTemplate, stylesExpr, pipes, compiledAnimations); if (componentStyles) { targetStatements.push( - ..._resolveStyleStatements(this._staticReflector, componentStyles, fileSuffix)); + ..._resolveStyleStatements(this._symbolResolver, componentStyles, fileSuffix)); } compiledAnimations.forEach(entry => targetStatements.push(...entry.statements)); - targetStatements.push(..._resolveViewStatements(this._staticReflector, viewResult)); + targetStatements.push(..._resolveViewStatements(this._symbolResolver, viewResult)); return viewResult.viewClassVar; } private _codgenStyles( fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): GeneratedFile { - _resolveStyleStatements(this._staticReflector, stylesCompileResult, fileSuffix); + _resolveStyleStatements(this._symbolResolver, stylesCompileResult, fileSuffix); return this._codegenSourceModule( fileUrl, _stylesModuleUrl( stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix), @@ -232,7 +242,7 @@ export class AotCompiler { } function _resolveViewStatements( - reflector: StaticReflector, compileResult: ViewCompileResult): o.Statement[] { + reflector: StaticSymbolResolver, compileResult: ViewCompileResult): o.Statement[] { compileResult.dependencies.forEach((dep) => { if (dep instanceof ViewClassDependency) { const vfd = dep; @@ -253,7 +263,7 @@ function _resolveViewStatements( function _resolveStyleStatements( - reflector: StaticReflector, compileResult: CompiledStylesheet, + reflector: StaticSymbolResolver, compileResult: CompiledStylesheet, fileSuffix: string): o.Statement[] { compileResult.dependencies.forEach((dep) => { dep.valuePlaceholder.reference = reflector.getStaticSymbol( @@ -303,26 +313,27 @@ export interface NgAnalyzedModules { srcUrl: string, directives: StaticSymbol[], pipes: StaticSymbol[], - ngModules: StaticSymbol[] + ngModules: StaticSymbol[], + injectables: StaticSymbol[] }>; symbolsMissingModule?: StaticSymbol[]; } +export interface NgAnalyzeModulesHost { isSourceFile(filePath: string): boolean; } + // Returns all the source files and a mapping from modules to directives export function analyzeNgModules( - programStaticSymbols: StaticSymbol[], - options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp}, + programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost, metadataResolver: CompileMetadataResolver): NgAnalyzedModules { const {ngModules, symbolsMissingModule} = - _createNgModules(programStaticSymbols, options, metadataResolver); - return _analyzeNgModules(ngModules, symbolsMissingModule); + _createNgModules(programStaticSymbols, host, metadataResolver); + return _analyzeNgModules(programStaticSymbols, ngModules, symbolsMissingModule, metadataResolver); } export function analyzeAndValidateNgModules( - programStaticSymbols: StaticSymbol[], - options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp}, + programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost, metadataResolver: CompileMetadataResolver): NgAnalyzedModules { - const result = analyzeNgModules(programStaticSymbols, options, metadataResolver); + const result = analyzeNgModules(programStaticSymbols, host, metadataResolver); if (result.symbolsMissingModule && result.symbolsMissingModule.length) { const messages = result.symbolsMissingModule.map( s => `Cannot determine the module for class ${s.name} in ${s.filePath}!`); @@ -332,16 +343,27 @@ export function analyzeAndValidateNgModules( } function _analyzeNgModules( - ngModuleMetas: CompileNgModuleMetadata[], - symbolsMissingModule: StaticSymbol[]): NgAnalyzedModules { + programSymbols: StaticSymbol[], ngModuleMetas: CompileNgModuleMetadata[], + symbolsMissingModule: StaticSymbol[], + metadataResolver: CompileMetadataResolver): NgAnalyzedModules { const moduleMetasByRef = new Map(); ngModuleMetas.forEach((ngModule) => moduleMetasByRef.set(ngModule.type.reference, ngModule)); const ngModuleByPipeOrDirective = new Map(); const ngModulesByFile = new Map(); const ngDirectivesByFile = new Map(); const ngPipesByFile = new Map(); + const ngInjectablesByFile = new Map(); const filePaths = new Set(); + // Make sure we produce an analyzed file for each input file + programSymbols.forEach((symbol) => { + const filePath = symbol.filePath; + filePaths.add(filePath); + if (metadataResolver.isInjectable(symbol)) { + ngInjectablesByFile.set(filePath, (ngInjectablesByFile.get(filePath) || []).concat(symbol)); + } + }); + // Looping over all modules to construct: // - a map from file to modules `ngModulesByFile`, // - a map from file to directives `ngDirectivesByFile`, @@ -369,17 +391,20 @@ function _analyzeNgModules( }); }); - const files: - {srcUrl: string, - directives: StaticSymbol[], - pipes: StaticSymbol[], - ngModules: StaticSymbol[]}[] = []; + const files: { + srcUrl: string, + directives: StaticSymbol[], + pipes: StaticSymbol[], + ngModules: StaticSymbol[], + injectables: StaticSymbol[] + }[] = []; filePaths.forEach((srcUrl) => { const directives = ngDirectivesByFile.get(srcUrl) || []; const pipes = ngPipesByFile.get(srcUrl) || []; const ngModules = ngModulesByFile.get(srcUrl) || []; - files.push({srcUrl, directives, pipes, ngModules}); + const injectables = ngInjectablesByFile.get(srcUrl) || []; + files.push({srcUrl, directives, pipes, ngModules, injectables}); }); return { @@ -392,29 +417,20 @@ function _analyzeNgModules( } export function extractProgramSymbols( - staticReflector: StaticReflector, files: string[], - options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}): StaticSymbol[] { + staticSymbolResolver: StaticSymbolResolver, files: string[], + host: NgAnalyzeModulesHost): StaticSymbol[] { const staticSymbols: StaticSymbol[] = []; - files.filter(fileName => filterFileByPatterns(fileName, options)).forEach(sourceFile => { - const moduleMetadata = staticReflector.getModuleMetadata(sourceFile); - if (!moduleMetadata) { - console.error(`WARNING: no metadata found for ${sourceFile}`); - return; - } - - const metadata = moduleMetadata['metadata']; - - if (!metadata) { - return; - } - - for (const symbol of Object.keys(metadata)) { - if (metadata[symbol] && metadata[symbol].__symbolic == 'error') { - // Ignore symbols that are only included to record error information. - continue; + files.filter(fileName => host.isSourceFile(fileName)).forEach(sourceFile => { + staticSymbolResolver.getSymbolsOf(sourceFile).forEach((symbol) => { + const resolvedSymbol = staticSymbolResolver.resolveSymbol(symbol); + const symbolMeta = resolvedSymbol.metadata; + if (symbolMeta) { + if (symbolMeta.__symbolic != 'error') { + // Ignore symbols that are only included to record error information. + staticSymbols.push(resolvedSymbol.symbol); + } } - staticSymbols.push(staticReflector.getStaticSymbol(sourceFile, symbol)); - } + }); }); return staticSymbols; @@ -424,8 +440,7 @@ export function extractProgramSymbols( // that all directives / pipes that are present in the program // are also declared by a module. function _createNgModules( - programStaticSymbols: StaticSymbol[], - options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp}, + programStaticSymbols: StaticSymbol[], host: NgAnalyzeModulesHost, metadataResolver: CompileMetadataResolver): {ngModules: CompileNgModuleMetadata[], symbolsMissingModule: StaticSymbol[]} { const ngModules = new Map(); @@ -433,7 +448,7 @@ function _createNgModules( const ngModulePipesAndDirective = new Set(); const addNgModule = (staticSymbol: any) => { - if (ngModules.has(staticSymbol) || !filterFileByPatterns(staticSymbol.filePath, options)) { + if (ngModules.has(staticSymbol) || !host.isSourceFile(staticSymbol.filePath)) { return false; } const ngModule = metadataResolver.getNgModuleMetadata(staticSymbol, false); diff --git a/modules/@angular/compiler/src/aot/compiler_factory.ts b/modules/@angular/compiler/src/aot/compiler_factory.ts index 1fa7b3a7a9..6ce8fadbb4 100644 --- a/modules/@angular/compiler/src/aot/compiler_factory.ts +++ b/modules/@angular/compiler/src/aot/compiler_factory.ts @@ -34,8 +34,11 @@ import {AotCompilerHost} from './compiler_host'; import {AotCompilerOptions} from './compiler_options'; import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities'; import {StaticReflector} from './static_reflector'; +import {StaticSymbolCache} from './static_symbol'; +import {StaticSymbolResolver} from './static_symbol_resolver'; import {AotSummaryResolver} from './summary_resolver'; + /** * Creates a new AotCompiler based on options and a host. */ @@ -44,7 +47,10 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom let translations: string = options.translations || ''; const urlResolver = createOfflineCompileUrlResolver(); - const staticReflector = new StaticReflector(compilerHost); + const symbolCache = new StaticSymbolCache(); + const summaryResolver = new AotSummaryResolver(compilerHost, symbolCache); + const symbolResolver = new StaticSymbolResolver(compilerHost, symbolCache, summaryResolver); + const staticReflector = new StaticReflector(symbolResolver); StaticAndDynamicReflectionCapabilities.install(staticReflector); const htmlParser = new I18NHtmlParser(new HtmlParser(), translations, options.i18nFormat); const config = new CompilerConfig({ @@ -60,17 +66,16 @@ export function createAotCompiler(compilerHost: AotCompilerHost, options: AotCom const console = new Console(); const tmplParser = new TemplateParser(expressionParser, elementSchemaRegistry, htmlParser, console, []); - const summaryResolver = new AotSummaryResolver(compilerHost, staticReflector, options); const resolver = new CompileMetadataResolver( new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector), new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer, staticReflector); // TODO(vicb): do not pass options.i18nFormat here const compiler = new AotCompiler( - resolver, tmplParser, new StyleCompiler(urlResolver), + compilerHost, resolver, tmplParser, new StyleCompiler(urlResolver), new ViewCompiler(config, elementSchemaRegistry), new DirectiveWrapperCompiler(config, expressionParser, elementSchemaRegistry, console), new NgModuleCompiler(), new TypeScriptEmitter(compilerHost), summaryResolver, options.locale, - options.i18nFormat, new AnimationParser(elementSchemaRegistry), staticReflector, options); + options.i18nFormat, new AnimationParser(elementSchemaRegistry), symbolResolver); return {compiler, reflector: staticReflector}; } diff --git a/modules/@angular/compiler/src/aot/compiler_host.ts b/modules/@angular/compiler/src/aot/compiler_host.ts index f9adcf2b52..9138ecfebe 100644 --- a/modules/@angular/compiler/src/aot/compiler_host.ts +++ b/modules/@angular/compiler/src/aot/compiler_host.ts @@ -8,16 +8,17 @@ import {ImportResolver} from '../output/path_util'; -import {StaticReflectorHost} from './static_reflector'; import {StaticSymbol} from './static_symbol'; +import {StaticSymbolResolverHost} from './static_symbol_resolver'; import {AotSummaryResolverHost} from './summary_resolver'; +import {AotSummarySerializerHost} from './summary_serializer'; /** * The host of the AotCompiler disconnects the implementation from TypeScript / other language * services and from underlying file systems. */ -export interface AotCompilerHost extends StaticReflectorHost, ImportResolver, - AotSummaryResolverHost { +export interface AotCompilerHost extends StaticSymbolResolverHost, ImportResolver, + AotSummaryResolverHost, AotSummarySerializerHost { /** * Loads a resource (e.g. html / css) */ diff --git a/modules/@angular/compiler/src/aot/compiler_options.ts b/modules/@angular/compiler/src/aot/compiler_options.ts index 8794d88840..c52fadc557 100644 --- a/modules/@angular/compiler/src/aot/compiler_options.ts +++ b/modules/@angular/compiler/src/aot/compiler_options.ts @@ -11,6 +11,4 @@ export interface AotCompilerOptions { locale?: string; i18nFormat?: string; translations?: string; - includeFilePattern?: RegExp; - excludeFilePattern?: RegExp; } diff --git a/modules/@angular/compiler/src/aot/static_reflection_capabilities.ts b/modules/@angular/compiler/src/aot/static_reflection_capabilities.ts index f87c49bca2..8b63b2c5c4 100644 --- a/modules/@angular/compiler/src/aot/static_reflection_capabilities.ts +++ b/modules/@angular/compiler/src/aot/static_reflection_capabilities.ts @@ -8,6 +8,7 @@ import {GetterFn, MethodFn, ReflectionCapabilities, SetterFn, reflector} from '../private_import_core'; import {StaticReflector} from './static_reflector'; +import {StaticSymbol} from './static_symbol'; export class StaticAndDynamicReflectionCapabilities { static install(staticDelegate: StaticReflector) { @@ -42,7 +43,7 @@ export class StaticAndDynamicReflectionCapabilities { method(name: string): MethodFn { return this.dynamicDelegate.method(name); } importUri(type: any): string { return this.staticDelegate.importUri(type); } resolveIdentifier(name: string, moduleUrl: string, runtime: any) { - return this.staticDelegate.resolveIdentifier(name, moduleUrl, runtime); + return this.staticDelegate.resolveIdentifier(name, moduleUrl); } resolveEnum(enumIdentifier: any, name: string): any { if (isStaticType(enumIdentifier)) { diff --git a/modules/@angular/compiler/src/aot/static_reflector.ts b/modules/@angular/compiler/src/aot/static_reflector.ts index 8141f295a4..86bfcff60f 100644 --- a/modules/@angular/compiler/src/aot/static_reflector.ts +++ b/modules/@angular/compiler/src/aot/static_reflector.ts @@ -7,10 +7,12 @@ */ import {Attribute, Component, ContentChild, ContentChildren, Directive, Host, HostBinding, HostListener, Inject, Injectable, Input, NgModule, Optional, Output, Pipe, Self, SkipSelf, ViewChild, ViewChildren, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; -import {ReflectorReader} from '../private_import_core'; -import {StaticSymbol} from './static_symbol'; -const SUPPORTED_SCHEMA_VERSION = 3; +import {ReflectorReader} from '../private_import_core'; + +import {StaticSymbol, StaticSymbolCache} from './static_symbol'; +import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver'; + const ANGULAR_IMPORT_LOCATIONS = { coreDecorators: '@angular/core/src/metadata', diDecorators: '@angular/core/src/di/metadata', @@ -22,66 +24,20 @@ const ANGULAR_IMPORT_LOCATIONS = { const HIDDEN_KEY = /^\$.*\$$/; -/** - * The host of the StaticReflector disconnects the implementation from TypeScript / other language - * services and from underlying file systems. - */ -export interface StaticReflectorHost { - /** - * Return a ModuleMetadata for the given module. - * Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is - * produced and the module has exported variables or classes with decorators. Module metadata can - * also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata. - * - * @param modulePath is a string identifier for a module as an absolute path. - * @returns the metadata for the given module. - */ - getMetadataFor(modulePath: string): {[key: string]: any}[]; - - /** - * Converts a module name that is used in an `import` to a file path. - * I.e. - * `path/to/containingFile.ts` containing `import {...} from 'module-name'`. - */ - moduleNameToFileName(moduleName: string, containingFile: string): string; -} - -/** - * A cache of static symbol used by the StaticReflector to return the same symbol for the - * same symbol values. - */ -export class StaticSymbolCache { - private cache = new Map(); - - get(declarationFile: string, name: string, members?: string[]): StaticSymbol { - const memberSuffix = members ? `.${ members.join('.')}` : ''; - const key = `"${declarationFile}".${name}${memberSuffix}`; - let result = this.cache.get(key); - if (!result) { - result = new StaticSymbol(declarationFile, name, members); - this.cache.set(key, result); - } - return result; - } -} - /** * A static reflector implements enough of the Reflector API that is necessary to compile * templates statically. */ export class StaticReflector implements ReflectorReader { - private declarationCache = new Map(); private annotationCache = new Map(); private propertyCache = new Map(); private parameterCache = new Map(); private methodCache = new Map(); - private metadataCache = new Map(); private conversionMap = new Map any>(); private opaqueToken: StaticSymbol; constructor( - private host: StaticReflectorHost, - private staticSymbolCache: StaticSymbolCache = new StaticSymbolCache(), + private symbolResolver: StaticSymbolResolver, knownMetadataClasses: {name: string, filePath: string, ctor: any}[] = [], knownMetadataFunctions: {name: string, filePath: string, fn: any}[] = [], private errorRecorder?: (error: any, fileName: string) => void) { @@ -94,12 +50,26 @@ export class StaticReflector implements ReflectorReader { } importUri(typeOrFunc: StaticSymbol): string { - const staticSymbol = this.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, ''); + const staticSymbol = this.findSymbolDeclaration(typeOrFunc); return staticSymbol ? staticSymbol.filePath : null; } - resolveIdentifier(name: string, moduleUrl: string, runtime: any): any { - return this.findDeclaration(moduleUrl, name, ''); + resolveIdentifier(name: string, moduleUrl: string): StaticSymbol { + return this.findDeclaration(moduleUrl, name); + } + + findDeclaration(moduleUrl: string, name: string, containingFile?: string): StaticSymbol { + return this.findSymbolDeclaration( + this.symbolResolver.getSymbolByModule(moduleUrl, name, containingFile)); + } + + findSymbolDeclaration(symbol: StaticSymbol): StaticSymbol { + const resolvedSymbol = this.symbolResolver.resolveSymbol(symbol); + if (resolvedSymbol && resolvedSymbol.metadata instanceof StaticSymbol) { + return this.findSymbolDeclaration(resolvedSymbol.metadata); + } else { + return symbol; + } } resolveEnum(enumIdentifier: any, name: string): any { @@ -128,7 +98,7 @@ export class StaticReflector implements ReflectorReader { public propMetadata(type: StaticSymbol): {[key: string]: any[]} { let propMetadata = this.propertyCache.get(type); if (!propMetadata) { - const classMetadata = this.getTypeMetadata(type) || {}; + const classMetadata = this.getTypeMetadata(type); propMetadata = {}; if (classMetadata['extends']) { const parentPropMetadata = this.propMetadata(this.simplify(type, classMetadata['extends'])); @@ -203,7 +173,7 @@ export class StaticReflector implements ReflectorReader { private _methodNames(type: any): {[key: string]: boolean} { let methodNames = this.methodCache.get(type); if (!methodNames) { - const classMetadata = this.getTypeMetadata(type) || {}; + const classMetadata = this.getTypeMetadata(type); methodNames = {}; if (classMetadata['extends']) { const parentMethodNames = this._methodNames(this.simplify(type, classMetadata['extends'])); @@ -306,7 +276,7 @@ export class StaticReflector implements ReflectorReader { * @param name the name of the type. */ getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol { - return this.staticSymbolCache.get(declarationFile, name, members); + return this.symbolResolver.getStaticSymbol(declarationFile, name, members); } private reportError(error: Error, context: StaticSymbol, path?: string) { @@ -317,96 +287,6 @@ export class StaticReflector implements ReflectorReader { } } - private resolveExportedSymbol(filePath: string, symbolName: string): StaticSymbol { - const resolveModule = (moduleName: string): string => { - const resolvedModulePath = this.host.moduleNameToFileName(moduleName, filePath); - if (!resolvedModulePath) { - this.reportError( - new Error(`Could not resolve module '${moduleName}' relative to file ${filePath}`), - null, filePath); - } - return resolvedModulePath; - }; - const cacheKey = `${filePath}|${symbolName}`; - let staticSymbol = this.declarationCache.get(cacheKey); - if (staticSymbol) { - return staticSymbol; - } - const metadata = this.getModuleMetadata(filePath); - if (metadata) { - // If we have metadata for the symbol, this is the original exporting location. - if (metadata['metadata'][symbolName]) { - staticSymbol = this.getStaticSymbol(filePath, symbolName); - } - - // If no, try to find the symbol in one of the re-export location - if (!staticSymbol && metadata['exports']) { - // Try and find the symbol in the list of explicitly re-exported symbols. - for (const moduleExport of metadata['exports']) { - if (moduleExport.export) { - const exportSymbol = moduleExport.export.find((symbol: any) => { - if (typeof symbol === 'string') { - return symbol == symbolName; - } else { - return symbol.as == symbolName; - } - }); - if (exportSymbol) { - let symName = symbolName; - if (typeof exportSymbol !== 'string') { - symName = exportSymbol.name; - } - const resolvedModule = resolveModule(moduleExport.from); - if (resolvedModule) { - staticSymbol = - this.resolveExportedSymbol(resolveModule(moduleExport.from), symName); - break; - } - } - } - } - - if (!staticSymbol) { - // Try to find the symbol via export * directives. - for (const moduleExport of metadata['exports']) { - if (!moduleExport.export) { - const resolvedModule = resolveModule(moduleExport.from); - if (resolvedModule) { - const candidateSymbol = this.resolveExportedSymbol(resolvedModule, symbolName); - if (candidateSymbol) { - staticSymbol = candidateSymbol; - break; - } - } - } - } - } - } - } - this.declarationCache.set(cacheKey, staticSymbol); - return staticSymbol; - } - - findDeclaration(module: string, symbolName: string, containingFile?: string): StaticSymbol { - try { - const filePath = this.host.moduleNameToFileName(module, containingFile); - let symbol: StaticSymbol; - if (!filePath) { - // If the file cannot be found the module is probably referencing a declared module - // for which there is no disambiguating file and we also don't need to track - // re-exports. Just use the module name. - symbol = this.getStaticSymbol(module, symbolName); - } else { - symbol = this.resolveExportedSymbol(filePath, symbolName) || - this.getStaticSymbol(filePath, symbolName); - } - return symbol; - } catch (e) { - console.error(`can't resolve module ${module} from ${containingFile}`); - throw e; - } - } - /** @internal */ public simplify(context: StaticSymbol, value: any): any { const self = this; @@ -414,93 +294,41 @@ export class StaticReflector implements ReflectorReader { const calling = new Map(); function simplifyInContext(context: StaticSymbol, value: any, depth: number): any { - function resolveReference(context: StaticSymbol, expression: any): StaticSymbol { - let staticSymbol: StaticSymbol; - if (expression['module']) { - staticSymbol = - self.findDeclaration(expression['module'], expression['name'], context.filePath); - } else { - staticSymbol = self.getStaticSymbol(context.filePath, expression['name']); - } - return staticSymbol; - } - function resolveReferenceValue(staticSymbol: StaticSymbol): any { - const moduleMetadata = self.getModuleMetadata(staticSymbol.filePath); - const declarationValue = - moduleMetadata ? moduleMetadata['metadata'][staticSymbol.name] : null; - return declarationValue; + const resolvedSymbol = self.symbolResolver.resolveSymbol(staticSymbol); + return resolvedSymbol ? resolvedSymbol.metadata : null; } - function isOpaqueToken(context: StaticSymbol, value: any): boolean { - if (value && value.__symbolic === 'new' && value.expression) { - const target = value.expression; - if (target.__symbolic == 'reference') { - return sameSymbol(resolveReference(context, target), self.opaqueToken); + function simplifyCall(functionSymbol: StaticSymbol, targetFunction: any, args: any[]) { + if (targetFunction && targetFunction['__symbolic'] == 'function') { + if (calling.get(functionSymbol)) { + throw new Error('Recursion not supported'); } - } - return false; - } - - function simplifyCall(expression: any) { - let callContext: {[name: string]: string}|undefined = undefined; - if (expression['__symbolic'] == 'call') { - const target = expression['expression']; - let functionSymbol: StaticSymbol; - let targetFunction: any; - if (target) { - switch (target.__symbolic) { - case 'reference': - // Find the function to call. - callContext = {name: target.name}; - functionSymbol = resolveReference(context, target); - targetFunction = resolveReferenceValue(functionSymbol); - break; - case 'select': - // Find the static method to call - if (target.expression.__symbolic == 'reference') { - functionSymbol = resolveReference(context, target.expression); - const classData = resolveReferenceValue(functionSymbol); - if (classData && classData.statics) { - targetFunction = classData.statics[target.member]; - } - } - break; - } - } - if (targetFunction && targetFunction['__symbolic'] == 'function') { - if (calling.get(functionSymbol)) { - throw new Error('Recursion not supported'); - } - calling.set(functionSymbol, true); - try { - const value = targetFunction['value']; - if (value && (depth != 0 || value.__symbolic != 'error')) { - // Determine the arguments - const args: any[] = - (expression['arguments'] || []).map((arg: any) => simplify(arg)); - const parameters: string[] = targetFunction['parameters']; - const defaults: any[] = targetFunction.defaults; - if (defaults && defaults.length > args.length) { - args.push(...defaults.slice(args.length).map((value: any) => simplify(value))); - } - const functionScope = BindingScope.build(); - for (let i = 0; i < parameters.length; i++) { - functionScope.define(parameters[i], args[i]); - } - const oldScope = scope; - let result: any; - try { - scope = functionScope.done(); - result = simplifyInContext(functionSymbol, value, depth + 1); - } finally { - scope = oldScope; - } - return result; + calling.set(functionSymbol, true); + try { + const value = targetFunction['value']; + if (value && (depth != 0 || value.__symbolic != 'error')) { + const parameters: string[] = targetFunction['parameters']; + const defaults: any[] = targetFunction.defaults; + if (defaults && defaults.length > args.length) { + args.push(...defaults.slice(args.length).map((value: any) => simplify(value))); } - } finally { - calling.delete(functionSymbol); + const functionScope = BindingScope.build(); + for (let i = 0; i < parameters.length; i++) { + functionScope.define(parameters[i], args[i]); + } + const oldScope = scope; + let result: any; + try { + scope = functionScope.done(); + result = simplifyInContext(functionSymbol, value, depth + 1); + } finally { + scope = oldScope; + } + return result; } + } finally { + calling.delete(functionSymbol); } } @@ -511,7 +339,7 @@ export class StaticReflector implements ReflectorReader { return {__symbolic: 'ignore'}; } return simplify( - {__symbolic: 'error', message: 'Function call not supported', context: callContext}); + {__symbolic: 'error', message: 'Function call not supported', context: functionSymbol}); } function simplify(expression: any): any { @@ -540,7 +368,18 @@ export class StaticReflector implements ReflectorReader { return result; } if (expression instanceof StaticSymbol) { - return expression; + // Stop simplification at builtin symbols + if (expression === self.opaqueToken || self.conversionMap.has(expression)) { + return expression; + } else { + const staticSymbol = expression; + const declarationValue = resolveReferenceValue(staticSymbol); + if (declarationValue) { + return simplifyInContext(staticSymbol, declarationValue, depth + 1); + } else { + return staticSymbol; + } + } } if (expression) { if (expression['__symbolic']) { @@ -618,50 +457,33 @@ export class StaticReflector implements ReflectorReader { if (indexTarget && isPrimitive(index)) return indexTarget[index]; return null; case 'select': + const member = expression['member']; let selectContext = context; let selectTarget = simplify(expression['expression']); if (selectTarget instanceof StaticSymbol) { - // Access to a static instance variable - const member: string = expression['member']; - const members = selectTarget.members ? - (selectTarget.members as string[]).concat(member) : - [member]; - const declarationValue = resolveReferenceValue(selectTarget); + const members = selectTarget.members.concat(member); selectContext = self.getStaticSymbol(selectTarget.filePath, selectTarget.name, members); - if (declarationValue && declarationValue.statics) { - selectTarget = declarationValue.statics; + const declarationValue = resolveReferenceValue(selectContext); + if (declarationValue) { + return simplifyInContext(selectContext, declarationValue, depth + 1); } else { return selectContext; } } - const member = simplifyInContext(selectContext, expression['member'], depth + 1); if (selectTarget && isPrimitive(member)) return simplifyInContext(selectContext, selectTarget[member], depth + 1); return null; case 'reference': - if (!expression['name']) { - return context; + // Note: This only has to deal with variable references, + // as symbol references have been converted into StaticSymbols already + // in the StaticSymbolResolver! + const name: string = expression['name']; + const localValue = scope.resolve(name); + if (localValue != BindingScope.missing) { + return localValue; } - if (!expression.module) { - const name: string = expression['name']; - const localValue = scope.resolve(name); - if (localValue != BindingScope.missing) { - return localValue; - } - } - staticSymbol = resolveReference(context, expression); - let result: any = staticSymbol; - let declarationValue = resolveReferenceValue(result); - if (declarationValue) { - if (isOpaqueToken(staticSymbol, declarationValue)) { - // If the referenced symbol is initalized by a new OpaqueToken we can keep the - // reference to the symbol. - return staticSymbol; - } - result = simplifyInContext(staticSymbol, declarationValue, depth + 1); - } - return result; + break; case 'class': return context; case 'function': @@ -669,26 +491,26 @@ export class StaticReflector implements ReflectorReader { case 'new': case 'call': // Determine if the function is a built-in conversion - let target = expression['expression']; - if (target['module']) { - staticSymbol = - self.findDeclaration(target['module'], target['name'], context.filePath); - } else { - staticSymbol = self.getStaticSymbol(context.filePath, target['name']); - } - let converter = self.conversionMap.get(staticSymbol); - if (converter) { - let args: any[] = expression['arguments']; - if (!args) { - args = []; + staticSymbol = simplifyInContext(context, expression['expression'], depth + 1); + if (staticSymbol instanceof StaticSymbol) { + if (staticSymbol === self.opaqueToken) { + // if somebody calls new OpaqueToken, don't create an OpaqueToken, + // but rather return the symbol to which the OpaqueToken is assigned to. + return context; + } + const argExpressions: any[] = expression['arguments'] || []; + const args = + argExpressions.map(arg => simplifyInContext(context, arg, depth + 1)); + let converter = self.conversionMap.get(staticSymbol); + if (converter) { + return converter(context, args); + } else { + // Determine if the function is one we can simplify. + const targetFunction = resolveReferenceValue(staticSymbol); + return simplifyCall(staticSymbol, targetFunction, args); } - return converter( - context, args.map(arg => simplifyInContext(context, arg, depth + 1))); } - - // Determine if the function is one we can simplify. - return simplifyCall(expression); - + break; case 'error': let message = produceErrorMessage(expression); if (expression['line']) { @@ -709,7 +531,9 @@ export class StaticReflector implements ReflectorReader { try { return simplify(value); } catch (e) { - const message = `${e.message}, resolving symbol ${context.name} in ${context.filePath}`; + const members = context.members.length ? `.${context.members.join('.')}` : ''; + const message = + `${e.message}, resolving symbol ${context.name}${members} in ${context.filePath}`; if (e.fileName) { throw positionalError(message, e.fileName, e.line, e.column); } @@ -733,40 +557,10 @@ export class StaticReflector implements ReflectorReader { return result; } - /** - * @param module an absolute path to a module file. - */ - public getModuleMetadata(module: string): {[key: string]: any} { - let moduleMetadata = this.metadataCache.get(module); - if (!moduleMetadata) { - const moduleMetadatas = this.host.getMetadataFor(module); - if (moduleMetadatas) { - let maxVersion = -1; - moduleMetadatas.forEach((md) => { - if (md['version'] > maxVersion) { - maxVersion = md['version']; - moduleMetadata = md; - } - }); - } - if (!moduleMetadata) { - moduleMetadata = - {__symbolic: 'module', version: SUPPORTED_SCHEMA_VERSION, module: module, metadata: {}}; - } - if (moduleMetadata['version'] != SUPPORTED_SCHEMA_VERSION) { - const errorMessage = moduleMetadata['version'] == 2 ? - `Unsupported metadata version ${moduleMetadata['version']} for module ${module}. This module should be compiled with a newer version of ngc` : - `Metadata version mismatch for module ${module}, found version ${moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`; - this.reportError(new Error(errorMessage), null); - } - this.metadataCache.set(module, moduleMetadata); - } - return moduleMetadata; - } - private getTypeMetadata(type: StaticSymbol): {[key: string]: any} { - const moduleMetadata = this.getModuleMetadata(type.filePath); - return moduleMetadata['metadata'][type.name] || {__symbolic: 'class'}; + const resolvedSymbol = this.symbolResolver.resolveSymbol(type); + return resolvedSymbol && resolvedSymbol.metadata ? resolvedSymbol.metadata : + {__symbolic: 'class'}; } } @@ -858,7 +652,7 @@ class PopulatedScope extends BindingScope { } function sameSymbol(a: StaticSymbol, b: StaticSymbol): boolean { - return a === b || (a.name == b.name && a.filePath == b.filePath); + return a === b; } function shouldIgnore(value: any): boolean { diff --git a/modules/@angular/compiler/src/aot/static_symbol.ts b/modules/@angular/compiler/src/aot/static_symbol.ts index 02f137907d..f90132e490 100644 --- a/modules/@angular/compiler/src/aot/static_symbol.ts +++ b/modules/@angular/compiler/src/aot/static_symbol.ts @@ -14,3 +14,23 @@ export class StaticSymbol { constructor(public filePath: string, public name: string, public members?: string[]) {} } + +/** + * A cache of static symbol used by the StaticReflector to return the same symbol for the + * same symbol values. + */ +export class StaticSymbolCache { + private cache = new Map(); + + get(declarationFile: string, name: string, members?: string[]): StaticSymbol { + members = members || []; + const memberSuffix = members.length ? `.${ members.join('.')}` : ''; + const key = `"${declarationFile}".${name}${memberSuffix}`; + let result = this.cache.get(key); + if (!result) { + result = new StaticSymbol(declarationFile, name, members); + this.cache.set(key, result); + } + return result; + } +} \ No newline at end of file diff --git a/modules/@angular/compiler/src/aot/static_symbol_resolver.ts b/modules/@angular/compiler/src/aot/static_symbol_resolver.ts new file mode 100644 index 0000000000..b734895678 --- /dev/null +++ b/modules/@angular/compiler/src/aot/static_symbol_resolver.ts @@ -0,0 +1,289 @@ +/** + * @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 {SummaryResolver} from '../summary_resolver'; +import {ValueTransformer, visitValue} from '../util'; + +import {StaticSymbol, StaticSymbolCache} from './static_symbol'; + +export class ResolvedStaticSymbol { + constructor(public symbol: StaticSymbol, public metadata: any) {} +} + +/** + * The host of the SymbolResolverHost disconnects the implementation from TypeScript / other + * language + * services and from underlying file systems. + */ +export interface StaticSymbolResolverHost { + /** + * Return a ModuleMetadata for the given module. + * Angular 2 CLI will produce this metadata for a module whenever a .d.ts files is + * produced and the module has exported variables or classes with decorators. Module metadata can + * also be produced directly from TypeScript sources by using MetadataCollector in tools/metadata. + * + * @param modulePath is a string identifier for a module as an absolute path. + * @returns the metadata for the given module. + */ + getMetadataFor(modulePath: string): {[key: string]: any}[]; + + /** + * Converts a module name that is used in an `import` to a file path. + * I.e. + * `path/to/containingFile.ts` containing `import {...} from 'module-name'`. + */ + moduleNameToFileName(moduleName: string, containingFile: string): string /*|null*/; +} + +const SUPPORTED_SCHEMA_VERSION = 3; + +/** + * This class is responsible for loading metadata per symbol, + * and normalizing references between symbols. + */ +export class StaticSymbolResolver { + private metadataCache = new Map(); + private resolvedSymbols = new Map(); + private resolvedFilePaths = new Set(); + + constructor( + private host: StaticSymbolResolverHost, private staticSymbolCache: StaticSymbolCache, + private summaryResolver: SummaryResolver, + private errorRecorder?: (error: any, fileName: string) => void) {} + + resolveSymbol(staticSymbol: StaticSymbol): ResolvedStaticSymbol { + if (staticSymbol.members.length > 0) { + return this._resolveSymbolMembers(staticSymbol); + } + let result = this._resolveSymbolFromSummary(staticSymbol); + if (!result) { + // Note: Some users use libraries that were not compiled with ngc, i.e. they don't + // have summaries, only .d.ts files. So we always need to check both, the summary + // and metadata. + this._createSymbolsOf(staticSymbol.filePath); + result = this.resolvedSymbols.get(staticSymbol); + } + return result; + } + + private _resolveSymbolMembers(staticSymbol: StaticSymbol): ResolvedStaticSymbol { + const members = staticSymbol.members; + const baseResolvedSymbol = + this.resolveSymbol(this.getStaticSymbol(staticSymbol.filePath, staticSymbol.name)); + if (!baseResolvedSymbol) { + return null; + } + const baseMetadata = baseResolvedSymbol.metadata; + if (baseMetadata instanceof StaticSymbol) { + return new ResolvedStaticSymbol( + staticSymbol, this.getStaticSymbol(baseMetadata.filePath, baseMetadata.name, members)); + } else if (baseMetadata && baseMetadata.__symbolic === 'class') { + if (baseMetadata.statics && members.length === 1) { + return new ResolvedStaticSymbol(staticSymbol, baseMetadata.statics[members[0]]); + } + } else { + let value = baseMetadata; + for (var i = 0; i < members.length && value; i++) { + value = value[members[i]]; + } + return new ResolvedStaticSymbol(staticSymbol, value); + } + return null; + } + + private _resolveSymbolFromSummary(staticSymbol: StaticSymbol): ResolvedStaticSymbol { + const summary = this.summaryResolver.resolveSummary(staticSymbol); + return summary ? new ResolvedStaticSymbol(staticSymbol, summary.metadata) : null; + } + + /** + * getStaticSymbol produces a Type whose metadata is known but whose implementation is not loaded. + * All types passed to the StaticResolver should be pseudo-types returned by this method. + * + * @param declarationFile the absolute path of the file where the symbol is declared + * @param name the name of the type. + */ + getStaticSymbol(declarationFile: string, name: string, members?: string[]): StaticSymbol { + return this.staticSymbolCache.get(declarationFile, name, members); + } + + getSymbolsOf(filePath: string): StaticSymbol[] { + // Note: Some users use libraries that were not compiled with ngc, i.e. they don't + // have summaries, only .d.ts files. So we always need to check both, the summary + // and metadata. + let symbols = new Set(this.summaryResolver.getSymbolsOf(filePath)); + this._createSymbolsOf(filePath); + this.resolvedSymbols.forEach((resolvedSymbol) => { + if (resolvedSymbol.symbol.filePath === filePath) { + symbols.add(resolvedSymbol.symbol); + } + }); + return Array.from(symbols); + } + + private _createSymbolsOf(filePath: string) { + if (this.resolvedFilePaths.has(filePath)) { + return; + } + this.resolvedFilePaths.add(filePath); + const resolvedSymbols: ResolvedStaticSymbol[] = []; + const metadata = this.getModuleMetadata(filePath); + if (metadata['metadata']) { + // handle direct declarations of the symbol + Object.keys(metadata['metadata']).forEach((symbolName) => { + const symbolMeta = metadata['metadata'][symbolName]; + resolvedSymbols.push( + this.createResolvedSymbol(this.getStaticSymbol(filePath, symbolName), symbolMeta)); + }); + } + + // handle the symbols in one of the re-export location + if (metadata['exports']) { + for (const moduleExport of metadata['exports']) { + // handle the symbols in the list of explicitly re-exported symbols. + if (moduleExport.export) { + moduleExport.export.forEach((exportSymbol: any) => { + let symbolName: string; + if (typeof exportSymbol === 'string') { + symbolName = exportSymbol; + } else { + symbolName = exportSymbol.as; + } + let symName = symbolName; + if (typeof exportSymbol !== 'string') { + symName = exportSymbol.name; + } + const resolvedModule = this.resolveModule(moduleExport.from, filePath); + if (resolvedModule) { + const targetSymbol = this.getStaticSymbol(resolvedModule, symName); + const sourceSymbol = this.getStaticSymbol(filePath, symbolName); + resolvedSymbols.push(new ResolvedStaticSymbol(sourceSymbol, targetSymbol)); + } + }); + } else { + // handle the symbols via export * directives. + const resolvedModule = this.resolveModule(moduleExport.from, filePath); + if (resolvedModule) { + const nestedExports = this.getSymbolsOf(resolvedModule); + nestedExports.forEach((targetSymbol) => { + const sourceSymbol = this.getStaticSymbol(filePath, targetSymbol.name); + resolvedSymbols.push(new ResolvedStaticSymbol(sourceSymbol, targetSymbol)); + }); + } + } + } + } + resolvedSymbols.forEach( + (resolvedSymbol) => this.resolvedSymbols.set(resolvedSymbol.symbol, resolvedSymbol)); + } + + private createResolvedSymbol(sourceSymbol: StaticSymbol, metadata: any): ResolvedStaticSymbol { + const self = this; + + class ReferenceTransformer extends ValueTransformer { + visitStringMap(map: {[key: string]: any}, functionParams: string[]): any { + const symbolic = map['__symbolic']; + if (symbolic === 'function') { + const oldLen = functionParams.length; + functionParams.push(...(map['parameters'] || [])); + const result = super.visitStringMap(map, functionParams); + functionParams.length = oldLen; + return result; + } else if (symbolic === 'reference') { + const module = map['module']; + const name = map['name']; + if (!name) { + return null; + } + let filePath: string; + if (module) { + filePath = self.resolveModule(module, sourceSymbol.filePath); + if (!filePath) { + return { + __symbolic: 'error', + message: `Could not resolve ${module} relative to ${sourceSymbol.filePath}.` + }; + } + } else { + const isFunctionParam = functionParams.indexOf(name) >= 0; + if (!isFunctionParam) { + filePath = sourceSymbol.filePath; + } + } + if (filePath) { + return self.getStaticSymbol(filePath, name); + } else { + // reference to a function parameter + return {__symbolic: 'reference', name: name}; + } + } else { + return super.visitStringMap(map, functionParams); + } + } + } + + const transformedMeta = visitValue(metadata, new ReferenceTransformer(), []); + return new ResolvedStaticSymbol(sourceSymbol, transformedMeta); + } + + private reportError(error: Error, context: StaticSymbol, path?: string) { + if (this.errorRecorder) { + this.errorRecorder(error, (context && context.filePath) || path); + } else { + throw error; + } + } + + /** + * @param module an absolute path to a module file. + */ + private getModuleMetadata(module: string): {[key: string]: any} { + let moduleMetadata = this.metadataCache.get(module); + if (!moduleMetadata) { + const moduleMetadatas = this.host.getMetadataFor(module); + if (moduleMetadatas) { + let maxVersion = -1; + moduleMetadatas.forEach((md) => { + if (md['version'] > maxVersion) { + maxVersion = md['version']; + moduleMetadata = md; + } + }); + } + if (!moduleMetadata) { + moduleMetadata = + {__symbolic: 'module', version: SUPPORTED_SCHEMA_VERSION, module: module, metadata: {}}; + } + if (moduleMetadata['version'] != SUPPORTED_SCHEMA_VERSION) { + const errorMessage = moduleMetadata['version'] == 2 ? + `Unsupported metadata version ${moduleMetadata['version']} for module ${module}. This module should be compiled with a newer version of ngc` : + `Metadata version mismatch for module ${module}, found version ${moduleMetadata['version']}, expected ${SUPPORTED_SCHEMA_VERSION}`; + this.reportError(new Error(errorMessage), null); + } + this.metadataCache.set(module, moduleMetadata); + } + return moduleMetadata; + } + + getSymbolByModule(module: string, symbolName: string, containingFile?: string): StaticSymbol { + const filePath = this.resolveModule(module, containingFile); + if (!filePath) { + throw new Error(`Could not resolve module ${module} relative to ${containingFile}`); + } + return this.getStaticSymbol(filePath, symbolName); + } + + private resolveModule(module: string, containingFile: string): string { + try { + return this.host.moduleNameToFileName(module, containingFile); + } catch (e) { + console.error(`Could not resolve module '${module}' relative to file ${containingFile}`); + this.reportError(new e, null, containingFile); + } + } +} diff --git a/modules/@angular/compiler/src/aot/summary_resolver.ts b/modules/@angular/compiler/src/aot/summary_resolver.ts index cfe651bda5..91731e7f3c 100644 --- a/modules/@angular/compiler/src/aot/summary_resolver.ts +++ b/modules/@angular/compiler/src/aot/summary_resolver.ts @@ -5,13 +5,12 @@ * 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 {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata'; -import {SummaryResolver} from '../summary_resolver'; +import {CompileSummaryKind, CompileTypeSummary} from '../compile_metadata'; +import {Summary, SummaryResolver} from '../summary_resolver'; -import {GeneratedFile} from './generated_file'; -import {StaticReflector} from './static_reflector'; -import {StaticSymbol} from './static_symbol'; -import {filterFileByPatterns} from './utils'; +import {StaticSymbol, StaticSymbolCache} from './static_symbol'; +import {ResolvedStaticSymbol} from './static_symbol_resolver'; +import {deserializeSummaries, summaryFileName} from './summary_serializer'; const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; @@ -19,106 +18,60 @@ export interface AotSummaryResolverHost { /** * Loads an NgModule/Directive/Pipe summary file */ - loadSummary(filePath: string): string; + loadSummary(filePath: string): string /*|null*/; /** - * Returns the output file path of a source file. - * E.g. - * `some_file.ts` -> `some_file.d.ts` + * Returns whether a file is a source file or not. */ - getOutputFileName(sourceFilePath: string): string; + isSourceFile(sourceFilePath: string): boolean; } -export interface AotSummaryResolverOptions { - includeFilePattern?: RegExp; - excludeFilePattern?: RegExp; -} +export class AotSummaryResolver implements SummaryResolver { + private summaryCache = new Map>(); + private loadedFilePaths = new Set(); -export class AotSummaryResolver implements SummaryResolver { - private summaryCache: {[cacheKey: string]: CompileTypeSummary} = {}; + constructor(private host: AotSummaryResolverHost, private staticSymbolCache: StaticSymbolCache) {} - constructor( - private host: AotSummaryResolverHost, private staticReflector: StaticReflector, - private options: AotSummaryResolverOptions) {} - - serializeSummaries(srcFileUrl: string, summaries: CompileTypeSummary[]): GeneratedFile { - const jsonReplacer = (key: string, value: any) => { - if (value instanceof StaticSymbol) { - // We convert the source filenames into output filenames, - // as the generated summary file will be used when the current - // compilation unit is used as a library - return { - '__symbolic__': 'symbol', - 'name': value.name, - 'path': this.host.getOutputFileName(value.filePath), - 'members': value.members - }; - } - return value; - }; - const allSummaries = summaries.slice(); - summaries.forEach((summary) => { - if (summary.summaryKind === CompileSummaryKind.NgModule) { - const moduleMeta = summary; - moduleMeta.exportedDirectives.concat(moduleMeta.exportedPipes).forEach((id) => { - if (!filterFileByPatterns(id.reference.filePath, this.options)) { - allSummaries.push(this.resolveSummary(id.reference)); - } - }); - } - }); - - return new GeneratedFile( - srcFileUrl, summaryFileName(srcFileUrl), JSON.stringify(allSummaries, jsonReplacer)); + private _assertNoMembers(symbol: StaticSymbol) { + if (symbol.members.length) { + throw new Error( + `Internal state: StaticSymbols in summaries can't have members! ${JSON.stringify(symbol)}`); + } } - private _cacheKey(symbol: StaticSymbol) { return `${symbol.filePath}|${symbol.name}`; } + resolveSummary(staticSymbol: StaticSymbol): Summary { + this._assertNoMembers(staticSymbol); + let summary = this.summaryCache.get(staticSymbol); + if (!summary) { + this._loadSummaryFile(staticSymbol.filePath); + summary = this.summaryCache.get(staticSymbol); + } + return summary; + } - resolveSummary(staticSymbol: StaticSymbol): any { - const filePath = staticSymbol.filePath; - const name = staticSymbol.name; - const cacheKey = this._cacheKey(staticSymbol); - if (!filterFileByPatterns(filePath, this.options)) { - let summary = this.summaryCache[cacheKey]; + getSymbolsOf(filePath: string): StaticSymbol[] { + this._loadSummaryFile(filePath); + return Array.from(this.summaryCache.keys()).filter((symbol) => symbol.filePath === filePath); + } + + private _loadSummaryFile(filePath: string) { + if (this.loadedFilePaths.has(filePath)) { + return; + } + this.loadedFilePaths.add(filePath); + if (!this.host.isSourceFile(filePath)) { const summaryFilePath = summaryFileName(filePath); - if (!summary) { - try { - const jsonReviver = (key: string, value: any) => { - if (value && value['__symbolic__'] === 'symbol') { - // Note: We can't use staticReflector.findDeclaration here: - // Summary files can contain symbols of transitive compilation units - // (via the providers), and findDeclaration needs .metadata.json / .d.ts files, - // but we don't want to depend on these for transitive dependencies. - return this.staticReflector.getStaticSymbol( - value['path'], value['name'], value['members']); - } else { - return value; - } - }; - const readSummaries: CompileTypeSummary[] = - JSON.parse(this.host.loadSummary(summaryFilePath), jsonReviver); - readSummaries.forEach((summary) => { - const filePath = summary.type.reference.filePath; - this.summaryCache[this._cacheKey(summary.type.reference)] = summary; - }); - summary = this.summaryCache[cacheKey]; - } catch (e) { - console.error(`Error loading summary file ${summaryFilePath}`); - throw e; - } + let json: string; + try { + json = this.host.loadSummary(summaryFilePath); + } catch (e) { + console.error(`Error loading summary file ${summaryFilePath}`); + throw e; } - if (!summary) { - throw new Error( - `Could not find the symbol ${name} in the summary file ${summaryFilePath}!`); + if (json) { + const readSummaries = deserializeSummaries(this.staticSymbolCache, json); + readSummaries.forEach((summary) => { this.summaryCache.set(summary.symbol, summary); }); } - return summary; - } else { - return null; } } } - -function summaryFileName(fileName: string): string { - const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, ''); - return `${fileNameWithoutSuffix}.ngsummary.json`; -} diff --git a/modules/@angular/compiler/src/aot/summary_serializer.ts b/modules/@angular/compiler/src/aot/summary_serializer.ts new file mode 100644 index 0000000000..b8d38db56d --- /dev/null +++ b/modules/@angular/compiler/src/aot/summary_serializer.ts @@ -0,0 +1,183 @@ +/** + * @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 {CompileDirectiveSummary, CompileIdentifierMetadata, CompileNgModuleSummary, CompilePipeSummary, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary, identifierModuleUrl, identifierName} from '../compile_metadata'; +import {Summary, SummaryResolver} from '../summary_resolver'; +import {ValueTransformer, visitValue} from '../util'; + +import {GeneratedFile} from './generated_file'; +import {StaticSymbol, StaticSymbolCache} from './static_symbol'; +import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver'; + +const STRIP_SRC_FILE_SUFFIXES = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/; + +export interface AotSummarySerializerHost { + /** + * Returns the output file path of a source file. + * E.g. + * `some_file.ts` -> `some_file.d.ts` + */ + getOutputFileName(sourceFilePath: string): string; + /** + * Returns whether a file is a source file or not. + */ + isSourceFile(sourceFilePath: string): boolean; +} + +export function serializeSummaries( + host: AotSummarySerializerHost, summaryResolver: SummaryResolver, + symbolResolver: StaticSymbolResolver, + + symbols: ResolvedStaticSymbol[], types: CompileTypeSummary[]): string { + const serializer = new Serializer(host); + + // for symbols, we use everything except for the class metadata itself + // (we keep the statics though), as the class metadata is contained in the + // CompileTypeSummary. + symbols.forEach( + (resolvedSymbol) => serializer.addOrMergeSummary( + {symbol: resolvedSymbol.symbol, metadata: resolvedSymbol.metadata})); + // Add summaries that are referenced by the given symbols (transitively) + // Note: the serializer.symbols array might be growing while + // we execute the loop! + for (let processedIndex = 0; processedIndex < serializer.symbols.length; processedIndex++) { + const symbol = serializer.symbols[processedIndex]; + if (!host.isSourceFile(symbol.filePath)) { + let summary = summaryResolver.resolveSummary(symbol); + if (!summary) { + // some symbols might originate from a plain typescript library + // that just exported .d.ts and .metadata.json files, i.e. where no summary + // files were created. + const resolvedSymbol = symbolResolver.resolveSymbol(symbol); + if (resolvedSymbol) { + summary = {symbol: resolvedSymbol.symbol, metadata: resolvedSymbol.metadata}; + } + } + if (summary) { + serializer.addOrMergeSummary(summary); + } + } + } + + // Add type summaries. + // Note: We don't add the summaries of all referenced symbols as for the ResolvedSymbols, + // as the type summaries already contain the transitive data that they require + // (in a minimal way). + types.forEach((typeSummary) => { + serializer.addOrMergeSummary( + {symbol: typeSummary.type.reference, metadata: {__symbolic: 'class'}, type: typeSummary}); + if (typeSummary.summaryKind === CompileSummaryKind.NgModule) { + const ngModuleSummary = typeSummary; + ngModuleSummary.exportedDirectives.concat(ngModuleSummary.exportedPipes).forEach((id) => { + const symbol: StaticSymbol = id.reference; + if (!host.isSourceFile(symbol.filePath)) { + serializer.addOrMergeSummary(summaryResolver.resolveSummary(symbol)); + } + }); + } + }); + return serializer.serialize(); +} + +export function deserializeSummaries( + symbolCache: StaticSymbolCache, json: string): Summary[] { + const deserializer = new Deserializer(symbolCache); + return deserializer.deserialize(json); +} + +export function summaryFileName(fileName: string): string { + const fileNameWithoutSuffix = fileName.replace(STRIP_SRC_FILE_SUFFIXES, ''); + return `${fileNameWithoutSuffix}.ngsummary.json`; +} + +class Serializer extends ValueTransformer { + symbols: StaticSymbol[] = []; + private indexBySymbol = new Map(); + // This now contains a `__symbol: number` in the place of + // StaticSymbols, but otherwise has the same shape as the original objects. + private processedSummaryBySymbol = new Map(); + private processedSummaries: any[] = []; + + constructor(private host: AotSummarySerializerHost) { super(); } + + addOrMergeSummary(summary: Summary) { + let symbolMeta = summary.metadata; + if (symbolMeta && symbolMeta.__symbolic === 'class') { + // For classes, we only keep their statics, but not the metadata + // of the class itself as that has been captured already via other summaries + // (e.g. DirectiveSummary, ...). + symbolMeta = {__symbolic: 'class', statics: symbolMeta.statics}; + } + + let processedSummary = this.processedSummaryBySymbol.get(summary.symbol); + if (!processedSummary) { + processedSummary = this.processValue({symbol: summary.symbol}); + this.processedSummaries.push(processedSummary); + this.processedSummaryBySymbol.set(summary.symbol, processedSummary); + } + // Note: == by purpose to compare with undefined! + if (processedSummary.metadata == null && symbolMeta != null) { + processedSummary.metadata = this.processValue(symbolMeta); + } + // Note: == by purpose to compare with undefined! + if (processedSummary.type == null && summary.type != null) { + processedSummary.type = this.processValue(summary.type); + } + } + + serialize(): string { + return JSON.stringify({ + summaries: this.processedSummaries, + symbols: this.symbols.map((symbol, index) => { + return { + __symbol: index, + name: symbol.name, + // We convert the source filenames tinto output filenames, + // as the generated summary file will be used when teh current + // compilation unit is used as a library + filePath: this.host.getOutputFileName(symbol.filePath) + }; + }) + }); + } + + private processValue(value: any): any { return visitValue(value, this, null); } + + visitOther(value: any, context: any): any { + if (value instanceof StaticSymbol) { + let index = this.indexBySymbol.get(value); + // Note: == by purpose to compare with undefined! + if (index == null) { + index = this.indexBySymbol.size; + this.indexBySymbol.set(value, index); + this.symbols.push(value); + } + return {__symbol: index}; + } + } +} + +class Deserializer extends ValueTransformer { + private symbols: StaticSymbol[]; + + constructor(private symbolCache: StaticSymbolCache) { super(); } + + deserialize(json: string): Summary[] { + const data: {summaries: any[], symbols: any[]} = JSON.parse(json); + this.symbols = data.symbols.map( + serializedSymbol => this.symbolCache.get(serializedSymbol.filePath, serializedSymbol.name)); + return visitValue(data.summaries, this, null); + } + + visitStringMap(map: {[key: string]: any}, context: any): any { + if ('__symbol' in map) { + return this.symbols[map['__symbol']]; + } else { + return super.visitStringMap(map, context); + } + } +} \ No newline at end of file diff --git a/modules/@angular/compiler/src/aot/utils.ts b/modules/@angular/compiler/src/aot/utils.ts deleted file mode 100644 index 07bcaffa4e..0000000000 --- a/modules/@angular/compiler/src/aot/utils.ts +++ /dev/null @@ -1,19 +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 - */ - -export function filterFileByPatterns( - fileName: string, options: {includeFilePattern?: RegExp, excludeFilePattern?: RegExp} = {}) { - let match = true; - if (options.includeFilePattern) { - match = match && !!options.includeFilePattern.exec(fileName); - } - if (options.excludeFilePattern) { - match = match && !options.excludeFilePattern.exec(fileName); - } - return match; -} \ No newline at end of file diff --git a/modules/@angular/compiler/src/compile_metadata.ts b/modules/@angular/compiler/src/compile_metadata.ts index ea74092c5f..7f03f03eca 100644 --- a/modules/@angular/compiler/src/compile_metadata.ts +++ b/modules/@angular/compiler/src/compile_metadata.ts @@ -115,10 +115,10 @@ export function identifierModuleUrl(compileIdentifier: CompileIdentifierMetadata export interface CompileIdentifierMetadata { reference: any; } export enum CompileSummaryKind { - Template, Pipe, Directive, - NgModule + NgModule, + Injectable } /** @@ -126,9 +126,10 @@ export enum CompileSummaryKind { * in other modules / components. However, this data is not enough to compile * the directive / module itself. */ -export interface CompileSummary { summaryKind: CompileSummaryKind; } - -export interface CompileTypeSummary extends CompileSummary { type: CompileTypeMetadata; } +export interface CompileTypeSummary { + summaryKind: CompileSummaryKind + type: CompileTypeMetadata; +} export interface CompileDiDependencyMetadata { isAttribute?: boolean; @@ -210,7 +211,7 @@ export class CompileStylesheetMetadata { /** * Summary Metadata regarding compilation of a template. */ -export interface CompileTemplateSummary extends CompileSummary { +export interface CompileTemplateSummary { animations: string[]; ngContentSelectors: string[]; encapsulation: ViewEncapsulation; @@ -258,7 +259,6 @@ export class CompileTemplateMetadata { toSummary(): CompileTemplateSummary { return { - summaryKind: CompileSummaryKind.Template, animations: this.animations.map(anim => anim.name), ngContentSelectors: this.ngContentSelectors, encapsulation: this.encapsulation diff --git a/modules/@angular/compiler/src/directive_normalizer.ts b/modules/@angular/compiler/src/directive_normalizer.ts index 5e3a407271..b3af0da518 100644 --- a/modules/@angular/compiler/src/directive_normalizer.ts +++ b/modules/@angular/compiler/src/directive_normalizer.ts @@ -6,11 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Injectable, ViewEncapsulation} from '@angular/core'; +import {Component, ViewEncapsulation} from '@angular/core'; import {CompileAnimationEntryMetadata, CompileDirectiveMetadata, CompileStylesheetMetadata, CompileTemplateMetadata, CompileTypeMetadata} from './compile_metadata'; import {CompilerConfig} from './config'; import {isBlank, isPresent, stringify} from './facade/lang'; +import {CompilerInjectable} from './injectable'; import * as html from './ml_parser/ast'; import {HtmlParser} from './ml_parser/html_parser'; import {InterpolationConfig} from './ml_parser/interpolation_config'; @@ -32,7 +33,7 @@ export interface PrenormalizedTemplateMetadata { animations?: CompileAnimationEntryMetadata[]; } -@Injectable() +@CompilerInjectable() export class DirectiveNormalizer { private _resourceLoaderCache = new Map>(); diff --git a/modules/@angular/compiler/src/directive_resolver.ts b/modules/@angular/compiler/src/directive_resolver.ts index 16e45bd597..2eacc7103f 100644 --- a/modules/@angular/compiler/src/directive_resolver.ts +++ b/modules/@angular/compiler/src/directive_resolver.ts @@ -6,14 +6,16 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Directive, HostBinding, HostListener, Injectable, Input, Output, Query, Type, resolveForwardRef} from '@angular/core'; +import {Component, Directive, HostBinding, HostListener, Input, Output, Query, Type, resolveForwardRef} from '@angular/core'; import {ListWrapper, StringMapWrapper} from './facade/collection'; import {stringify} from './facade/lang'; +import {CompilerInjectable} from './injectable'; import {ReflectorReader, reflector} from './private_import_core'; import {splitAtColon} from './util'; + /* * Resolve a `Type` for {@link Directive}. * @@ -21,7 +23,7 @@ import {splitAtColon} from './util'; * * See {@link Compiler} */ -@Injectable() +@CompilerInjectable() export class DirectiveResolver { constructor(private _reflector: ReflectorReader = reflector) {} diff --git a/modules/@angular/compiler/src/directive_wrapper_compiler.ts b/modules/@angular/compiler/src/directive_wrapper_compiler.ts index d919a62b56..d6405366c2 100644 --- a/modules/@angular/compiler/src/directive_wrapper_compiler.ts +++ b/modules/@angular/compiler/src/directive_wrapper_compiler.ts @@ -6,8 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -import {Injectable} from '@angular/core'; - import {CompileDirectiveMetadata, CompileDirectiveSummary, CompileIdentifierMetadata, identifierModuleUrl, identifierName} from './compile_metadata'; import {createCheckBindingField, createCheckBindingStmt} from './compiler_util/binding_util'; import {EventHandlerVars, convertActionBinding, convertPropertyBinding} from './compiler_util/expression_converter'; @@ -15,6 +13,7 @@ import {triggerAnimation, writeToRenderer} from './compiler_util/render_util'; import {CompilerConfig} from './config'; import {Parser} from './expression_parser/parser'; import {Identifiers, createIdentifier} from './identifiers'; +import {CompilerInjectable} from './injectable'; import {DEFAULT_INTERPOLATION_CONFIG} from './ml_parser/interpolation_config'; import {ClassBuilder, createClassStmt} from './output/class_builder'; import * as o from './output/output_ast'; @@ -51,7 +50,7 @@ const RESET_CHANGES_STMT = o.THIS_EXPR.prop(CHANGES_FIELD_NAME).set(o.literalMap * * So far, only `@Input` and the lifecycle hooks have been implemented. */ -@Injectable() +@CompilerInjectable() export class DirectiveWrapperCompiler { static dirWrapperClassName(id: CompileIdentifierMetadata) { return `Wrapper_${identifierName(id)}`; diff --git a/modules/@angular/compiler/src/expression_parser/lexer.ts b/modules/@angular/compiler/src/expression_parser/lexer.ts index 15566e4a65..c94972ee0b 100644 --- a/modules/@angular/compiler/src/expression_parser/lexer.ts +++ b/modules/@angular/compiler/src/expression_parser/lexer.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {Injectable} from '@angular/core'; import * as chars from '../chars'; import {NumberWrapper, isPresent} from '../facade/lang'; +import {CompilerInjectable} from '../injectable'; export enum TokenType { Character, @@ -22,7 +22,7 @@ export enum TokenType { const KEYWORDS = ['var', 'let', 'null', 'undefined', 'true', 'false', 'if', 'else', 'this']; -@Injectable() +@CompilerInjectable() export class Lexer { tokenize(text: string): Token[] { const scanner = new _Scanner(text); diff --git a/modules/@angular/compiler/src/expression_parser/parser.ts b/modules/@angular/compiler/src/expression_parser/parser.ts index a4264b1df0..f90ada465d 100644 --- a/modules/@angular/compiler/src/expression_parser/parser.ts +++ b/modules/@angular/compiler/src/expression_parser/parser.ts @@ -6,10 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {Injectable} from '@angular/core'; - import * as chars from '../chars'; import {escapeRegExp, isBlank, isPresent} from '../facade/lang'; +import {CompilerInjectable} from '../injectable'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../ml_parser/interpolation_config'; import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, ParserError, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, TemplateBinding} from './ast'; @@ -31,7 +30,7 @@ function _createInterpolateRegExp(config: InterpolationConfig): RegExp { return new RegExp(pattern, 'g'); } -@Injectable() +@CompilerInjectable() export class Parser { private errors: ParserError[] = []; diff --git a/modules/@angular/compiler/src/i18n/extractor.ts b/modules/@angular/compiler/src/i18n/extractor.ts index fce0f41208..16e92c6499 100644 --- a/modules/@angular/compiler/src/i18n/extractor.ts +++ b/modules/@angular/compiler/src/i18n/extractor.ts @@ -14,7 +14,9 @@ import {ViewEncapsulation} from '@angular/core'; import {analyzeAndValidateNgModules, extractProgramSymbols} from '../aot/compiler'; import {StaticAndDynamicReflectionCapabilities} from '../aot/static_reflection_capabilities'; -import {StaticReflector, StaticReflectorHost} from '../aot/static_reflector'; +import {StaticReflector} from '../aot/static_reflector'; +import {StaticSymbolCache} from '../aot/static_symbol'; +import {StaticSymbolResolver, StaticSymbolResolverHost} from '../aot/static_symbol_resolver'; import {AotSummaryResolver, AotSummaryResolverHost} from '../aot/summary_resolver'; import {CompileDirectiveMetadata} from '../compile_metadata'; import {CompilerConfig} from '../config'; @@ -33,16 +35,11 @@ import {createOfflineCompileUrlResolver} from '../url_resolver'; import {I18NHtmlParser} from './i18n_html_parser'; import {MessageBundle} from './message_bundle'; -export interface ExtractorOptions { - includeFilePattern?: RegExp; - excludeFilePattern?: RegExp; -} - /** * The host of the Extractor disconnects the implementation from TypeScript / other language * services and from underlying file systems. */ -export interface ExtractorHost extends StaticReflectorHost, AotSummaryResolverHost { +export interface ExtractorHost extends StaticSymbolResolverHost, AotSummaryResolverHost { /** * Loads a resource (e.g. html / css) */ @@ -51,14 +48,13 @@ export interface ExtractorHost extends StaticReflectorHost, AotSummaryResolverHo export class Extractor { constructor( - private options: ExtractorOptions, public host: ExtractorHost, - private staticReflector: StaticReflector, private messageBundle: MessageBundle, - private metadataResolver: CompileMetadataResolver) {} + public host: ExtractorHost, private staticSymbolResolver: StaticSymbolResolver, + private messageBundle: MessageBundle, private metadataResolver: CompileMetadataResolver) {} extract(rootFiles: string[]): Promise { - const programSymbols = extractProgramSymbols(this.staticReflector, rootFiles, this.options); + const programSymbols = extractProgramSymbols(this.staticSymbolResolver, rootFiles, this.host); const {ngModuleByPipeOrDirective, files, ngModules} = - analyzeAndValidateNgModules(programSymbols, this.options, this.metadataResolver); + analyzeAndValidateNgModules(programSymbols, this.host, this.metadataResolver); return Promise .all(ngModules.map( ngModule => this.metadataResolver.loadNgModuleDirectiveAndPipeMetadata( @@ -91,12 +87,14 @@ export class Extractor { }); } - static create(host: ExtractorHost, options: ExtractorOptions): - {extractor: Extractor, staticReflector: StaticReflector} { + static create(host: ExtractorHost): {extractor: Extractor, staticReflector: StaticReflector} { const htmlParser = new I18NHtmlParser(new HtmlParser()); const urlResolver = createOfflineCompileUrlResolver(); - const staticReflector = new StaticReflector(host); + const symbolCache = new StaticSymbolCache(); + const summaryResolver = new AotSummaryResolver(host, symbolCache); + const staticSymbolResolver = new StaticSymbolResolver(host, symbolCache, summaryResolver); + const staticReflector = new StaticReflector(staticSymbolResolver); StaticAndDynamicReflectionCapabilities.install(staticReflector); const config = new CompilerConfig({ @@ -111,13 +109,13 @@ export class Extractor { const elementSchemaRegistry = new DomElementSchemaRegistry(); const resolver = new CompileMetadataResolver( new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector), - new PipeResolver(staticReflector), new AotSummaryResolver(host, staticReflector, options), - elementSchemaRegistry, normalizer, staticReflector); + new PipeResolver(staticReflector), summaryResolver, elementSchemaRegistry, normalizer, + staticReflector); // TODO(vicb): implicit tags & attributes const messageBundle = new MessageBundle(htmlParser, [], {}); - const extractor = new Extractor(options, host, staticReflector, messageBundle, resolver); + const extractor = new Extractor(host, staticSymbolResolver, messageBundle, resolver); return {extractor, staticReflector}; } } diff --git a/modules/@angular/compiler/src/i18n/index.ts b/modules/@angular/compiler/src/i18n/index.ts index aeb57917b2..eb05fb1c48 100644 --- a/modules/@angular/compiler/src/i18n/index.ts +++ b/modules/@angular/compiler/src/i18n/index.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -export {Extractor, ExtractorHost, ExtractorOptions} from './extractor'; +export {Extractor, ExtractorHost} from './extractor'; export {I18NHtmlParser} from './i18n_html_parser'; export {MessageBundle} from './message_bundle'; export {Serializer} from './serializers/serializer'; diff --git a/modules/@angular/compiler/src/injectable.ts b/modules/@angular/compiler/src/injectable.ts new file mode 100644 index 0000000000..59b94d13a1 --- /dev/null +++ b/modules/@angular/compiler/src/injectable.ts @@ -0,0 +1,16 @@ +/** + * @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 + */ + +/** + * A replacement for @Injectable to be used in the compiler, so that + * we don't try to evaluate the metadata in the compiler during AoT. + * This decorator is enough to make the compiler work with the ReflectiveInjector though. + */ +export function CompilerInjectable(): (data: any) => any { + return (x) => x; +} diff --git a/modules/@angular/compiler/src/jit/compiler.ts b/modules/@angular/compiler/src/jit/compiler.ts index 159f05b6b4..84ad9a4e6c 100644 --- a/modules/@angular/compiler/src/jit/compiler.ts +++ b/modules/@angular/compiler/src/jit/compiler.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Compiler, ComponentFactory, Injectable, Injector, ModuleWithComponentFactories, NgModuleFactory, SchemaMetadata, Type} from '@angular/core'; +import {Compiler, ComponentFactory, Injector, ModuleWithComponentFactories, NgModuleFactory, SchemaMetadata, Type} from '@angular/core'; import {AnimationCompiler} from '../animation/animation_compiler'; import {AnimationParser} from '../animation/animation_parser'; @@ -15,6 +15,7 @@ import {CompilerConfig} from '../config'; import {DirectiveNormalizer} from '../directive_normalizer'; import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler'; import {stringify} from '../facade/lang'; +import {CompilerInjectable} from '../injectable'; import {CompileMetadataResolver} from '../metadata_resolver'; import {NgModuleCompiler} from '../ng_module_compiler'; import * as ir from '../output/output_ast'; @@ -36,7 +37,7 @@ import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewClassDepende * from a trusted source. Attacker-controlled data introduced by a template could expose your * application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security). */ -@Injectable() +@CompilerInjectable() export class JitCompiler implements Compiler { private _compiledTemplateCache = new Map, CompiledTemplate>(); private _compiledHostTemplateCache = new Map, CompiledTemplate>(); diff --git a/modules/@angular/compiler/src/jit/compiler_factory.ts b/modules/@angular/compiler/src/jit/compiler_factory.ts index a7c09cbb6b..8f19e817c6 100644 --- a/modules/@angular/compiler/src/jit/compiler_factory.ts +++ b/modules/@angular/compiler/src/jit/compiler_factory.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Injectable, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core'; +import {COMPILER_OPTIONS, Compiler, CompilerFactory, CompilerOptions, Inject, Optional, PLATFORM_INITIALIZER, PlatformRef, Provider, ReflectiveInjector, TRANSLATIONS, TRANSLATIONS_FORMAT, Type, ViewEncapsulation, createPlatformFactory, isDevMode, platformCore} from '@angular/core'; import {AnimationParser} from '../animation/animation_parser'; import {CompilerConfig} from '../config'; @@ -16,6 +16,7 @@ import {DirectiveWrapperCompiler} from '../directive_wrapper_compiler'; import {Lexer} from '../expression_parser/lexer'; import {Parser} from '../expression_parser/parser'; import * as i18n from '../i18n/index'; +import {CompilerInjectable} from '../injectable'; import {CompileMetadataResolver} from '../metadata_resolver'; import {HtmlParser} from '../ml_parser/html_parser'; import {NgModuleCompiler} from '../ng_module_compiler'; @@ -83,7 +84,7 @@ export const COMPILER_PROVIDERS: Array|{[k: string]: any}|any[]> = ]; -@Injectable() +@CompilerInjectable() export class JitCompilerFactory implements CompilerFactory { private _defaultOptions: CompilerOptions[]; constructor(@Inject(COMPILER_OPTIONS) defaultOptions: CompilerOptions[]) { diff --git a/modules/@angular/compiler/src/metadata_resolver.ts b/modules/@angular/compiler/src/metadata_resolver.ts index 5613fe42dc..310b37b30e 100644 --- a/modules/@angular/compiler/src/metadata_resolver.ts +++ b/modules/@angular/compiler/src/metadata_resolver.ts @@ -16,6 +16,7 @@ import {DirectiveResolver} from './directive_resolver'; import {ListWrapper, StringMapWrapper} from './facade/collection'; import {isBlank, isPresent, stringify} from './facade/lang'; import {Identifiers, createIdentifierToken, resolveIdentifier} from './identifiers'; +import {CompilerInjectable} from './injectable'; import {hasLifecycleHook} from './lifecycle_reflector'; import {NgModuleResolver} from './ng_module_resolver'; import {PipeResolver} from './pipe_resolver'; @@ -35,7 +36,7 @@ export const ERROR_COLLECTOR_TOKEN = new OpaqueToken('ErrorCollector'); // But we want to report errors even when the async work is // not required to check that the user would have been able // to wait correctly. -@Injectable() +@CompilerInjectable() export class CompileMetadataResolver { private _directiveCache = new Map, cpl.CompileDirectiveMetadata>(); private _summaryCache = new Map, cpl.CompileTypeSummary>(); @@ -45,7 +46,7 @@ export class CompileMetadataResolver { constructor( private _ngModuleResolver: NgModuleResolver, private _directiveResolver: DirectiveResolver, - private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver, + private _pipeResolver: PipeResolver, private _summaryResolver: SummaryResolver, private _schemaRegistry: ElementSchemaRegistry, private _directiveNormalizer: DirectiveNormalizer, private _reflector: ReflectorReader = reflector, @@ -128,12 +129,13 @@ export class CompileMetadataResolver { } private _loadSummary(type: any, kind: cpl.CompileSummaryKind): cpl.CompileTypeSummary { - let summary = this._summaryCache.get(type); - if (!summary) { - summary = this._summaryResolver.resolveSummary(type); - this._summaryCache.set(type, summary); + let typeSummary = this._summaryCache.get(type); + if (!typeSummary) { + const summary = this._summaryResolver.resolveSummary(type); + typeSummary = summary ? summary.type : null; + this._summaryCache.set(type, typeSummary); } - return summary && summary.summaryKind === kind ? summary : null; + return typeSummary && typeSummary.summaryKind === kind ? typeSummary : null; } private _loadDirectiveMetadata(directiveType: any, isSync: boolean): Promise { @@ -237,7 +239,7 @@ export class CompileMetadataResolver { if (dirMeta.viewProviders) { viewProviders = this._getProvidersMetadata( dirMeta.viewProviders, entryComponentMetadata, - `viewProviders for "${stringify(directiveType)}"`, [], directiveType); + `viewProviders for "${stringifyType(directiveType)}"`, [], directiveType); } if (dirMeta.entryComponents) { entryComponentMetadata = flattenAndDedupeArray(dirMeta.entryComponents) @@ -251,7 +253,7 @@ export class CompileMetadataResolver { // Directive if (!selector) { this._reportError( - new Error(`Directive ${stringify(directiveType)} has no selector, please add it!`), + new Error(`Directive ${stringifyType(directiveType)} has no selector, please add it!`), directiveType); selector = 'error'; } @@ -260,8 +262,8 @@ export class CompileMetadataResolver { let providers: cpl.CompileProviderMetadata[] = []; if (isPresent(dirMeta.providers)) { providers = this._getProvidersMetadata( - dirMeta.providers, entryComponentMetadata, `providers for "${stringify(directiveType)}"`, - [], directiveType); + dirMeta.providers, entryComponentMetadata, + `providers for "${stringifyType(directiveType)}"`, [], directiveType); } let queries: cpl.CompileQueryMetadata[] = []; let viewQueries: cpl.CompileQueryMetadata[] = []; @@ -298,7 +300,7 @@ export class CompileMetadataResolver { if (!dirMeta) { this._reportError( new Error( - `Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringify(directiveType)}.`), + `Illegal state: getDirectiveMetadata can only be called after loadNgModuleMetadata for a module that declares it. Directive ${stringifyType(directiveType)}.`), directiveType); } return dirMeta; @@ -310,7 +312,7 @@ export class CompileMetadataResolver { if (!dirSummary) { this._reportError( new Error( - `Illegal state: Could not load the summary for directive ${stringify(dirType)}.`), + `Illegal state: Could not load the summary for directive ${stringifyType(dirType)}.`), dirType); } return dirSummary; @@ -383,7 +385,8 @@ export class CompileMetadataResolver { if (moduleWithProviders.providers) { providers.push(...this._getProvidersMetadata( moduleWithProviders.providers, entryComponents, - `provider for the NgModule '${stringify(importedModuleType)}'`, [], importedType)); + `provider for the NgModule '${stringifyType(importedModuleType)}'`, [], + importedType)); } } @@ -392,7 +395,7 @@ export class CompileMetadataResolver { if (!importedModuleSummary) { this._reportError( new Error( - `Unexpected ${this._getTypeDescriptor(importedType)} '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`), + `Unexpected ${this._getTypeDescriptor(importedType)} '${stringifyType(importedType)}' imported by the module '${stringifyType(moduleType)}'`), moduleType); return; } @@ -400,7 +403,7 @@ export class CompileMetadataResolver { } else { this._reportError( new Error( - `Unexpected value '${stringify(importedType)}' imported by the module '${stringify(moduleType)}'`), + `Unexpected value '${stringifyType(importedType)}' imported by the module '${stringifyType(moduleType)}'`), moduleType); return; } @@ -412,7 +415,7 @@ export class CompileMetadataResolver { if (!isValidType(exportedType)) { this._reportError( new Error( - `Unexpected value '${stringify(exportedType)}' exported by the module '${stringify(moduleType)}'`), + `Unexpected value '${stringifyType(exportedType)}' exported by the module '${stringifyType(moduleType)}'`), moduleType); return; } @@ -433,7 +436,7 @@ export class CompileMetadataResolver { if (!isValidType(declaredType)) { this._reportError( new Error( - `Unexpected value '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`), + `Unexpected value '${stringifyType(declaredType)}' declared by the module '${stringifyType(moduleType)}'`), moduleType); return; } @@ -450,7 +453,7 @@ export class CompileMetadataResolver { } else { this._reportError( new Error( - `Unexpected ${this._getTypeDescriptor(declaredType)} '${stringify(declaredType)}' declared by the module '${stringify(moduleType)}'`), + `Unexpected ${this._getTypeDescriptor(declaredType)} '${stringifyType(declaredType)}' declared by the module '${stringifyType(moduleType)}'`), moduleType); return; } @@ -469,7 +472,7 @@ export class CompileMetadataResolver { } else { this._reportError( new Error( - `Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringify(exportedId.reference)} from ${stringify(moduleType)} as it was neither declared nor imported!`), + `Can't export ${this._getTypeDescriptor(exportedId.reference)} ${stringifyType(exportedId.reference)} from ${stringifyType(moduleType)} as it was neither declared nor imported!`), moduleType); } }); @@ -478,13 +481,13 @@ export class CompileMetadataResolver { // so that they overwrite any other provider we already added. if (meta.providers) { providers.push(...this._getProvidersMetadata( - meta.providers, entryComponents, `provider for the NgModule '${stringify(moduleType)}'`, - [], moduleType)); + meta.providers, entryComponents, + `provider for the NgModule '${stringifyType(moduleType)}'`, [], moduleType)); } if (meta.entryComponents) { - entryComponents.push( - ...flattenAndDedupeArray(meta.entryComponents).map(type => this._getTypeMetadata(type))); + entryComponents.push(...flattenAndDedupeArray(meta.entryComponents) + .map(type => this._getIdentifierMetadata(type))); } if (meta.bootstrap) { @@ -492,11 +495,11 @@ export class CompileMetadataResolver { if (!isValidType(type)) { this._reportError( new Error( - `Unexpected value '${stringify(type)}' used in the bootstrap property of module '${stringify(moduleType)}'`), + `Unexpected value '${stringifyType(type)}' used in the bootstrap property of module '${stringifyType(moduleType)}'`), moduleType); return; } - bootstrapComponents.push(this._getTypeMetadata(type)); + bootstrapComponents.push(this._getIdentifierMetadata(type)); }); } @@ -555,9 +558,9 @@ export class CompileMetadataResolver { if (oldModule && oldModule !== moduleType) { this._reportError( new Error( - `Type ${stringify(type)} is part of the declarations of 2 modules: ${stringify(oldModule)} and ${stringify(moduleType)}! ` + - `Please consider moving ${stringify(type)} to a higher module that imports ${stringify(oldModule)} and ${stringify(moduleType)}. ` + - `You can also create a new NgModule that exports and includes ${stringify(type)} then import that NgModule in ${stringify(oldModule)} and ${stringify(moduleType)}.`), + `Type ${stringifyType(type)} is part of the declarations of 2 modules: ${stringifyType(oldModule)} and ${stringifyType(moduleType)}! ` + + `Please consider moving ${stringifyType(type)} to a higher module that imports ${stringifyType(oldModule)} and ${stringifyType(moduleType)}. ` + + `You can also create a new NgModule that exports and includes ${stringifyType(type)} then import that NgModule in ${stringifyType(oldModule)} and ${stringifyType(moduleType)}.`), moduleType); } this._ngModuleOfTypes.set(type, moduleType); @@ -606,6 +609,26 @@ export class CompileMetadataResolver { return {reference: type}; } + isInjectable(type: any): boolean { + const annotations = this._reflector.annotations(type); + // Note: We need an exact check here as @Component / @Directive / ... inherit + // from @CompilerInjectable! + return annotations.some(ann => ann.constructor === Injectable); + } + + getInjectableSummary(type: any): cpl.CompileTypeSummary { + return {summaryKind: cpl.CompileSummaryKind.Injectable, type: this._getTypeMetadata(type)}; + } + + private _getInjectableMetadata(type: Type, dependencies: any[] = null): + cpl.CompileTypeMetadata { + const typeSummary = this._loadSummary(type, cpl.CompileSummaryKind.Injectable); + if (typeSummary) { + return typeSummary.type; + } + return this._getTypeMetadata(type, dependencies); + } + private _getTypeMetadata(type: Type, dependencies: any[] = null): cpl.CompileTypeMetadata { const identifier = this._getIdentifierMetadata(type); return { @@ -631,7 +654,7 @@ export class CompileMetadataResolver { if (!pipeMeta) { this._reportError( new Error( - `Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringify(pipeType)}.`), + `Illegal state: getPipeMetadata can only be called after loadNgModuleMetadata for a module that declares it. Pipe ${stringifyType(pipeType)}.`), pipeType); } return pipeMeta; @@ -642,7 +665,8 @@ export class CompileMetadataResolver { this._loadSummary(pipeType, cpl.CompileSummaryKind.Pipe); if (!pipeSummary) { this._reportError( - new Error(`Illegal state: Could not load the summary for pipe ${stringify(pipeType)}.`), + new Error( + `Illegal state: Could not load the summary for pipe ${stringifyType(pipeType)}.`), pipeType); } return pipeSummary; @@ -722,9 +746,10 @@ export class CompileMetadataResolver { if (hasUnknownDeps) { const depsTokens = - dependenciesMetadata.map((dep) => dep ? stringify(dep.token) : '?').join(', '); + dependenciesMetadata.map((dep) => dep ? stringifyType(dep.token) : '?').join(', '); this._reportError( - new Error(`Can't resolve all parameters for ${stringify(typeOrFunc)}: (${depsTokens}).`), + new Error( + `Can't resolve all parameters for ${stringifyType(typeOrFunc)}: (${depsTokens}).`), typeOrFunc); } @@ -761,9 +786,9 @@ export class CompileMetadataResolver { (providers.reduce( (soFar: string[], seenProvider: any, seenProviderIdx: number) => { if (seenProviderIdx < providerIdx) { - soFar.push(`${stringify(seenProvider)}`); + soFar.push(`${stringifyType(seenProvider)}`); } else if (seenProviderIdx == providerIdx) { - soFar.push(`?${stringify(seenProvider)}?`); + soFar.push(`?${stringifyType(seenProvider)}?`); } else if (seenProviderIdx == providerIdx + 1) { soFar.push('...'); } @@ -805,7 +830,8 @@ export class CompileMetadataResolver { extractIdentifiers(provider.useValue, collectedIdentifiers); collectedIdentifiers.forEach((identifier) => { - if (this._directiveResolver.isDirective(identifier.reference)) { + if (this._directiveResolver.isDirective(identifier.reference) || + this._loadSummary(identifier.reference, cpl.CompileSummaryKind.Directive)) { components.push(identifier); } }); @@ -819,7 +845,7 @@ export class CompileMetadataResolver { let token: cpl.CompileTokenMetadata = this._getTokenMetadata(provider.token); if (provider.useClass) { - compileTypeMetadata = this._getTypeMetadata(provider.useClass, provider.dependencies); + compileTypeMetadata = this._getInjectableMetadata(provider.useClass, provider.dependencies); compileDeps = compileTypeMetadata.diDeps; if (provider.token === provider.useClass) { // use the compileTypeMetadata as it contains information about lifecycleHooks... @@ -868,7 +894,7 @@ export class CompileMetadataResolver { if (!q.selector) { this._reportError( new Error( - `Can't construct a query for the property "${propertyName}" of "${stringify(typeOrFunc)}" since the query selector wasn't defined.`), + `Can't construct a query for the property "${propertyName}" of "${stringifyType(typeOrFunc)}" since the query selector wasn't defined.`), typeOrFunc); } selectors = [this._getTokenMetadata(q.selector)]; @@ -936,7 +962,7 @@ export function componentModuleUrl( return scheme ? moduleId : `package:${moduleId}${MODULE_SUFFIX}`; } else if (moduleId !== null && moduleId !== void 0) { throw new Error( - `moduleId should be a string in "${stringify(type)}". See https://goo.gl/wIDDiL for more information.\n` + + `moduleId should be a string in "${stringifyType(type)}". See https://goo.gl/wIDDiL for more information.\n` + `If you're using Webpack you should inline the template and the styles, see https://goo.gl/X2J8zc.`); } @@ -952,3 +978,11 @@ class _CompileValueConverter extends ValueTransformer { targetIdentifiers.push({reference: value}); } } + +function stringifyType(type: any): string { + if (type instanceof StaticSymbol) { + return `${type.name} in ${type.filePath}`; + } else { + return stringify(type); + } +} \ No newline at end of file diff --git a/modules/@angular/compiler/src/ml_parser/html_parser.ts b/modules/@angular/compiler/src/ml_parser/html_parser.ts index c6fa6599e8..ad69f6c2d9 100644 --- a/modules/@angular/compiler/src/ml_parser/html_parser.ts +++ b/modules/@angular/compiler/src/ml_parser/html_parser.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Injectable} from '@angular/core'; +import {CompilerInjectable} from '../injectable'; import {getHtmlTagDefinition} from './html_tags'; import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from './interpolation_config'; @@ -14,7 +14,7 @@ import {ParseTreeResult, Parser} from './parser'; export {ParseTreeResult, TreeError} from './parser'; -@Injectable() +@CompilerInjectable() export class HtmlParser extends Parser { constructor() { super(getHtmlTagDefinition); } diff --git a/modules/@angular/compiler/src/ng_module_compiler.ts b/modules/@angular/compiler/src/ng_module_compiler.ts index cd5c334633..aa210ede5f 100644 --- a/modules/@angular/compiler/src/ng_module_compiler.ts +++ b/modules/@angular/compiler/src/ng_module_compiler.ts @@ -6,12 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {Injectable} from '@angular/core'; - import {CompileDiDependencyMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompileProviderMetadata, CompileTokenMetadata, identifierModuleUrl, identifierName, tokenName, tokenReference} from './compile_metadata'; import {createDiTokenExpression} from './compiler_util/identifier_util'; import {isPresent} from './facade/lang'; import {Identifiers, createIdentifier, createIdentifierToken, resolveIdentifier} from './identifiers'; +import {CompilerInjectable} from './injectable'; import {ClassBuilder, createClassStmt} from './output/class_builder'; import * as o from './output/output_ast'; import {convertValueToOutputAst} from './output/value_util'; @@ -31,7 +30,7 @@ export class NgModuleCompileResult { public dependencies: ComponentFactoryDependency[]) {} } -@Injectable() +@CompilerInjectable() export class NgModuleCompiler { compile(ngModuleMeta: CompileNgModuleMetadata, extraProviders: CompileProviderMetadata[]): NgModuleCompileResult { diff --git a/modules/@angular/compiler/src/ng_module_resolver.ts b/modules/@angular/compiler/src/ng_module_resolver.ts index e74128c6da..e29eb78f85 100644 --- a/modules/@angular/compiler/src/ng_module_resolver.ts +++ b/modules/@angular/compiler/src/ng_module_resolver.ts @@ -6,10 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {Injectable, NgModule, Type} from '@angular/core'; +import {NgModule, Type} from '@angular/core'; import {ListWrapper} from './facade/collection'; import {isPresent, stringify} from './facade/lang'; +import {CompilerInjectable} from './injectable'; import {ReflectorReader, reflector} from './private_import_core'; function _isNgModuleMetadata(obj: any): obj is NgModule { @@ -19,7 +20,7 @@ function _isNgModuleMetadata(obj: any): obj is NgModule { /** * Resolves types to {@link NgModule}. */ -@Injectable() +@CompilerInjectable() export class NgModuleResolver { constructor(private _reflector: ReflectorReader = reflector) {} diff --git a/modules/@angular/compiler/src/output/path_util.ts b/modules/@angular/compiler/src/output/path_util.ts index bf8615cf68..e9869cdc31 100644 --- a/modules/@angular/compiler/src/output/path_util.ts +++ b/modules/@angular/compiler/src/output/path_util.ts @@ -14,5 +14,6 @@ export abstract class ImportResolver { * Converts a file path to a module name that can be used as an `import. * I.e. `path/to/importedFile.ts` should be imported by `path/to/containingFile.ts`. */ - abstract fileNameToModuleName(importedFilePath: string, containingFilePath: string): string; + abstract fileNameToModuleName(importedFilePath: string, containingFilePath: string): string + /*|null*/; } diff --git a/modules/@angular/compiler/src/output/ts_emitter.ts b/modules/@angular/compiler/src/output/ts_emitter.ts index e84e1080c8..dbf65ced31 100644 --- a/modules/@angular/compiler/src/output/ts_emitter.ts +++ b/modules/@angular/compiler/src/output/ts_emitter.ts @@ -335,7 +335,7 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor } ctx.print(`${prefix}.`); } - if (value.reference && value.reference.members) { + if (value.reference && value.reference.members && value.reference.members.length) { ctx.print(value.reference.name); ctx.print('.'); ctx.print(value.reference.members.join('.')); diff --git a/modules/@angular/compiler/src/pipe_resolver.ts b/modules/@angular/compiler/src/pipe_resolver.ts index 61df4ca6ee..94de46ee54 100644 --- a/modules/@angular/compiler/src/pipe_resolver.ts +++ b/modules/@angular/compiler/src/pipe_resolver.ts @@ -6,10 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {Injectable, Pipe, Type, resolveForwardRef} from '@angular/core'; +import {Pipe, Type, resolveForwardRef} from '@angular/core'; import {ListWrapper} from './facade/collection'; import {isPresent, stringify} from './facade/lang'; +import {CompilerInjectable} from './injectable'; import {ReflectorReader, reflector} from './private_import_core'; function _isPipeMetadata(type: any): boolean { @@ -23,7 +24,7 @@ function _isPipeMetadata(type: any): boolean { * * See {@link Compiler} */ -@Injectable() +@CompilerInjectable() export class PipeResolver { constructor(private _reflector: ReflectorReader = reflector) {} diff --git a/modules/@angular/compiler/src/schema/dom_element_schema_registry.ts b/modules/@angular/compiler/src/schema/dom_element_schema_registry.ts index b077fa999c..da70c8546a 100644 --- a/modules/@angular/compiler/src/schema/dom_element_schema_registry.ts +++ b/modules/@angular/compiler/src/schema/dom_element_schema_registry.ts @@ -6,7 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {AUTO_STYLE, CUSTOM_ELEMENTS_SCHEMA, Injectable, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '@angular/core'; +import {AUTO_STYLE, CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata, SecurityContext} from '@angular/core'; +import {CompilerInjectable} from '../injectable'; import {dashCaseToCamelCase} from '../util'; @@ -238,7 +239,7 @@ const _ATTR_TO_PROP: {[name: string]: string} = { 'tabindex': 'tabIndex', }; -@Injectable() +@CompilerInjectable() export class DomElementSchemaRegistry extends ElementSchemaRegistry { private _schema: {[element: string]: {[property: string]: string}} = {}; diff --git a/modules/@angular/compiler/src/style_compiler.ts b/modules/@angular/compiler/src/style_compiler.ts index a54f593551..aeb67b2a68 100644 --- a/modules/@angular/compiler/src/style_compiler.ts +++ b/modules/@angular/compiler/src/style_compiler.ts @@ -6,9 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {Injectable, ViewEncapsulation} from '@angular/core'; +import {ViewEncapsulation} from '@angular/core'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileStylesheetMetadata, identifierModuleUrl, identifierName} from './compile_metadata'; +import {CompilerInjectable} from './injectable'; import * as o from './output/output_ast'; import {ShadowCss} from './shadow_css'; import {UrlResolver} from './url_resolver'; @@ -36,7 +37,7 @@ export class CompiledStylesheet { public meta: CompileStylesheetMetadata) {} } -@Injectable() +@CompilerInjectable() export class StyleCompiler { private _shadowCss: ShadowCss = new ShadowCss(); diff --git a/modules/@angular/compiler/src/summary_resolver.ts b/modules/@angular/compiler/src/summary_resolver.ts index 12ca821c26..02d6e05243 100644 --- a/modules/@angular/compiler/src/summary_resolver.ts +++ b/modules/@angular/compiler/src/summary_resolver.ts @@ -5,10 +5,17 @@ * 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 {Injectable} from '@angular/core'; import {CompileTypeSummary} from './compile_metadata'; +import {CompilerInjectable} from './injectable'; -@Injectable() -export class SummaryResolver { - resolveSummary(reference: any): CompileTypeSummary { return null; } +export interface Summary { + symbol: T; + metadata: any; + type?: CompileTypeSummary; +} + +@CompilerInjectable() +export class SummaryResolver { + resolveSummary(reference: T): Summary { return null; }; + getSymbolsOf(filePath: string): T[] { return []; } } diff --git a/modules/@angular/compiler/src/template_parser/template_parser.ts b/modules/@angular/compiler/src/template_parser/template_parser.ts index 2f9d2ad9ee..edb97a339c 100644 --- a/modules/@angular/compiler/src/template_parser/template_parser.ts +++ b/modules/@angular/compiler/src/template_parser/template_parser.ts @@ -6,13 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {Inject, Injectable, OpaqueToken, Optional, SchemaMetadata} from '@angular/core'; - +import {Inject, OpaqueToken, Optional, SchemaMetadata} from '@angular/core'; import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CompileTemplateSummary, CompileTokenMetadata, CompileTypeMetadata, identifierName} from '../compile_metadata'; import {Parser} from '../expression_parser/parser'; import {isPresent} from '../facade/lang'; import {I18NHtmlParser} from '../i18n/i18n_html_parser'; import {Identifiers, createIdentifierToken, identifierToken} from '../identifiers'; +import {CompilerInjectable} from '../injectable'; import * as html from '../ml_parser/ast'; import {ParseTreeResult} from '../ml_parser/html_parser'; import {expandNodes} from '../ml_parser/icu_ast_expander'; @@ -79,7 +79,7 @@ export class TemplateParseResult { constructor(public templateAst?: TemplateAst[], public errors?: ParseError[]) {} } -@Injectable() +@CompilerInjectable() export class TemplateParser { constructor( private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry, diff --git a/modules/@angular/compiler/src/url_resolver.ts b/modules/@angular/compiler/src/url_resolver.ts index 8d9c031c08..cd2638d297 100644 --- a/modules/@angular/compiler/src/url_resolver.ts +++ b/modules/@angular/compiler/src/url_resolver.ts @@ -6,9 +6,11 @@ * found in the LICENSE file at https://angular.io/license */ -import {Inject, Injectable, PACKAGE_ROOT_URL} from '@angular/core'; +import {Inject, PACKAGE_ROOT_URL} from '@angular/core'; import {isBlank, isPresent} from './facade/lang'; +import {CompilerInjectable} from './injectable'; + /** * Create a {@link UrlResolver} with no package prefix. @@ -45,7 +47,7 @@ export var DEFAULT_PACKAGE_URL_PROVIDER = { * Attacker-controlled data introduced by a template could expose your * application to XSS risks. For more detail, see the [Security Guide](http://g.co/ng/security). */ -@Injectable() +@CompilerInjectable() export class UrlResolver { constructor(@Inject(PACKAGE_ROOT_URL) private _packagePrefix: string = null) {} diff --git a/modules/@angular/compiler/src/view_compiler/view_compiler.ts b/modules/@angular/compiler/src/view_compiler/view_compiler.ts index e4fcd9fac5..aaaa5e2d1a 100644 --- a/modules/@angular/compiler/src/view_compiler/view_compiler.ts +++ b/modules/@angular/compiler/src/view_compiler/view_compiler.ts @@ -6,11 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {Injectable} from '@angular/core'; - import {AnimationEntryCompileResult} from '../animation/animation_compiler'; import {CompileDirectiveMetadata, CompilePipeSummary} from '../compile_metadata'; import {CompilerConfig} from '../config'; +import {CompilerInjectable} from '../injectable'; import * as o from '../output/output_ast'; import {ElementSchemaRegistry} from '../schema/element_schema_registry'; import {TemplateAst} from '../template_parser/template_ast'; @@ -30,7 +29,7 @@ export class ViewCompileResult { Array) {} } -@Injectable() +@CompilerInjectable() export class ViewCompiler { constructor(private _genConfig: CompilerConfig, private _schemaRegistry: ElementSchemaRegistry) {} diff --git a/modules/@angular/compiler/test/aot/static_reflector_spec.ts b/modules/@angular/compiler/test/aot/static_reflector_spec.ts index ebd09116f0..321808c8b8 100644 --- a/modules/@angular/compiler/test/aot/static_reflector_spec.ts +++ b/modules/@angular/compiler/test/aot/static_reflector_spec.ts @@ -6,26 +6,25 @@ * found in the LICENSE file at https://angular.io/license */ -import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler'; +import {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler'; import {HostListener, Inject, animate, group, keyframes, sequence, state, style, transition, trigger} from '@angular/core'; -import {MetadataCollector} from '@angular/tsc-wrapped'; -import * as ts from 'typescript'; - - -// This matches .ts files but not .d.ts files. -const TS_EXT = /(^.|(?!\.d)..)\.ts$/; +import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec'; describe('StaticReflector', () => { - const noContext = new StaticSymbol('', ''); - let host: StaticReflectorHost; + let noContext: StaticSymbol; + let host: StaticSymbolResolverHost; + let symbolResolver: StaticSymbolResolver; let reflector: StaticReflector; function init( testData: {[key: string]: any} = DEFAULT_TEST_DATA, decorators: {name: string, filePath: string, ctor: any}[] = []) { - host = new MockStaticReflectorHost(testData); - reflector = new StaticReflector(host, undefined, decorators); + const symbolCache = new StaticSymbolCache(); + host = new MockStaticSymbolResolverHost(testData); + symbolResolver = new StaticSymbolResolver(host, symbolCache, new MockSummaryResolver([])); + reflector = new StaticReflector(symbolResolver, decorators); + noContext = reflector.getStaticSymbol('', ''); } beforeEach(() => init()); @@ -77,24 +76,22 @@ describe('StaticReflector', () => { ])]); }); - it('should throw an exception for unsupported metadata versions', () => { - expect(() => reflector.findDeclaration('src/version-error', 'e')) - .toThrow(new Error( - 'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 3')); - }); - - it('should throw an exception for version 2 metadata', () => { - expect(() => reflector.findDeclaration('src/version-2-error', 'e')) - .toThrowError( - 'Unsupported metadata version 2 for module /tmp/src/version-2-error.d.ts. This module should be compiled with a newer version of ngc'); - }); - it('should get and empty annotation list for an unknown class', () => { const UnknownClass = reflector.findDeclaration('src/app/app.component', 'UnknownClass'); const annotations = reflector.annotations(UnknownClass); expect(annotations).toEqual([]); }); + it('should get and empty annotation list for a symbol with null value', () => { + init({ + '/tmp/test.ts': ` + export var x = null; + ` + }); + const annotations = reflector.annotations(reflector.getStaticSymbol('/tmp/test.ts', 'x')); + expect(annotations).toEqual([]); + }); + it('should get propMetadata for HeroDetailComponent', () => { const HeroDetailComponent = reflector.findDeclaration('src/app/hero-detail.component', 'HeroDetailComponent'); @@ -129,7 +126,7 @@ describe('StaticReflector', () => { }); it('should simplify a static symbol into itself', () => { - const staticSymbol = new StaticSymbol('', ''); + const staticSymbol = reflector.getStaticSymbol('', ''); expect(simplify(noContext, staticSymbol)).toBe(staticSymbol); }); @@ -306,49 +303,43 @@ describe('StaticReflector', () => { expect(simplify(noContext, expr)).toBe(2); }); - it('should simplify a module reference', () => { + it('should simplify a file reference', () => { expect(simplify( - new StaticSymbol('/src/cases', ''), - ({__symbolic: 'reference', module: './extern', name: 's'}))) + reflector.getStaticSymbol('/src/cases', ''), + reflector.getStaticSymbol('/src/extern.d.ts', 's'))) .toEqual('s'); }); - it('should not simplify a module reference without a name', () => { - const staticSymbol = new StaticSymbol('/src/cases', ''); - expect(simplify(staticSymbol, ({__symbolic: 'reference', module: './extern', name: ''}))) - .toEqual(staticSymbol); - }); - it('should simplify a non existing reference as a static symbol', () => { expect(simplify( - new StaticSymbol('/src/cases', ''), - ({__symbolic: 'reference', module: './extern', name: 'nonExisting'}))) + reflector.getStaticSymbol('/src/cases', ''), + reflector.getStaticSymbol('/src/extern.d.ts', 'nonExisting'))) .toEqual(reflector.getStaticSymbol('/src/extern.d.ts', 'nonExisting')); }); it('should simplify a function reference as a static symbol', () => { expect(simplify( - new StaticSymbol('/src/cases', 'myFunction'), + reflector.getStaticSymbol('/src/cases', 'myFunction'), ({__symbolic: 'function', parameters: ['a'], value: []}))) .toEqual(reflector.getStaticSymbol('/src/cases', 'myFunction')); }); it('should simplify values initialized with a function call', () => { - expect(simplify(new StaticSymbol('/tmp/src/function-reference.ts', ''), { - __symbolic: 'reference', - name: 'one' - })).toEqual(['some-value']); - expect(simplify(new StaticSymbol('/tmp/src/function-reference.ts', ''), { - __symbolic: 'reference', - name: 'three' - })).toEqual(3); + expect(simplify( + reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''), + reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'one'))) + .toEqual(['some-value']); + expect(simplify( + reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''), + reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'three'))) + .toEqual(3); }); it('should error on direct recursive calls', () => { expect( () => simplify( - new StaticSymbol('/tmp/src/function-reference.ts', ''), - {__symbolic: 'reference', name: 'recursion'})) + reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''), + reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'recursion'))) .toThrow(new Error( 'Recursion not supported, resolving symbol recursive in /tmp/src/function-recursive.d.ts, resolving symbol recursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts')); }); @@ -362,7 +353,8 @@ describe('StaticReflector', () => { expect(moduleMetadata).toBeDefined(); const classData: any = moduleMetadata['InvalidMetadata']; expect(classData).toBeDefined(); - simplify(new StaticSymbol('/tmp/src/invalid-metadata.ts', ''), classData.decorators[0]); + simplify( + reflector.getStaticSymbol('/tmp/src/invalid-metadata.ts', ''), classData.decorators[0]); } catch (e) { expect(e.fileName).toBe('/tmp/src/invalid-metadata.ts'); threw = true; @@ -373,48 +365,17 @@ describe('StaticReflector', () => { it('should error on indirect recursive calls', () => { expect( () => simplify( - new StaticSymbol('/tmp/src/function-reference.ts', ''), - {__symbolic: 'reference', name: 'indirectRecursion'})) + reflector.getStaticSymbol('/tmp/src/function-reference.ts', ''), + reflector.getStaticSymbol('/tmp/src/function-reference.ts', 'indirectRecursion'))) .toThrow(new Error( 'Recursion not supported, resolving symbol indirectRecursion2 in /tmp/src/function-recursive.d.ts, resolving symbol indirectRecursion1 in /tmp/src/function-recursive.d.ts, resolving symbol indirectRecursion in /tmp/src/function-reference.ts, resolving symbol in /tmp/src/function-reference.ts')); }); it('should simplify a spread expression', () => { - expect(simplify(new StaticSymbol('/tmp/src/spread.ts', ''), { - __symbolic: 'reference', - name: 'spread' - })).toEqual([0, 1, 2, 3, 4, 5]); - }); - - it('should be able to get metadata from a ts file', () => { - const metadata = reflector.getModuleMetadata('/tmp/src/custom-decorator-reference.ts'); - expect(metadata).toEqual({ - __symbolic: 'module', - version: 3, - metadata: { - Foo: { - __symbolic: 'class', - decorators: [{ - __symbolic: 'call', - expression: - {__symbolic: 'reference', module: './custom-decorator', name: 'CustomDecorator'} - }], - members: { - foo: [{ - __symbolic: 'property', - decorators: [{ - __symbolic: 'call', - expression: { - __symbolic: 'reference', - module: './custom-decorator', - name: 'CustomDecorator' - } - }] - }] - } - } - } - }); + expect(simplify( + reflector.getStaticSymbol('/tmp/src/spread.ts', ''), + reflector.getStaticSymbol('/tmp/src/spread.ts', 'spread'))) + .toEqual([0, 1, 2, 3, 4, 5]); }); it('should be able to get metadata for a class containing a custom decorator', () => { @@ -488,62 +449,6 @@ describe('StaticReflector', () => { expect(annotations[0].providers[0].useValue.members[0]).toEqual('staticMethod'); }); - it('should be able to produce a symbol for an exported symbol', () => { - expect(reflector.findDeclaration('@angular/router', 'foo', 'main.ts')).toBeDefined(); - }); - - it('should be able to produce a symbol for values space only reference', () => { - expect(reflector.findDeclaration('@angular/router/src/providers', 'foo', 'main.ts')) - .toBeDefined(); - }); - - it('should be produce the same symbol if asked twice', () => { - const foo1 = reflector.getStaticSymbol('main.ts', 'foo'); - const foo2 = reflector.getStaticSymbol('main.ts', 'foo'); - expect(foo1).toBe(foo2); - }); - - it('should be able to produce a symbol for a module with no file', - () => { expect(reflector.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined(); }); - - it('should be able to trace a named export', () => { - const symbol = reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts'); - expect(symbol.name).toEqual('One'); - expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts'); - }); - - it('should be able to trace a renamed export', () => { - const symbol = reflector.findDeclaration('./reexport/reexport', 'Four', '/tmp/src/main.ts'); - expect(symbol.name).toEqual('Three'); - expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts'); - }); - - it('should be able to trace an export * export', () => { - const symbol = reflector.findDeclaration('./reexport/reexport', 'Five', '/tmp/src/main.ts'); - expect(symbol.name).toEqual('Five'); - expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin5.d.ts'); - }); - - it('should be able to trace a multi-level re-export', () => { - const symbol = reflector.findDeclaration('./reexport/reexport', 'Thirty', '/tmp/src/main.ts'); - expect(symbol.name).toEqual('Thirty'); - expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts'); - }); - - it('should cache tracing a named export', () => { - const moduleNameToFileNameSpy = spyOn(host, 'moduleNameToFileName').and.callThrough(); - const getMetadataForSpy = spyOn(host, 'getMetadataFor').and.callThrough(); - reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts'); - moduleNameToFileNameSpy.calls.reset(); - getMetadataForSpy.calls.reset(); - - const symbol = reflector.findDeclaration('./reexport/reexport', 'One', '/tmp/src/main.ts'); - expect(moduleNameToFileNameSpy.calls.count()).toBe(1); - expect(getMetadataForSpy.calls.count()).toBe(0); - expect(symbol.name).toEqual('One'); - expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts'); - }); - describe('inheritance', () => { class ClassDecorator { constructor(public value: any) {} @@ -706,78 +611,6 @@ describe('StaticReflector', () => { }); -export class MockStaticReflectorHost implements StaticReflectorHost { - private collector = new MetadataCollector(); - - constructor(private data: {[key: string]: any}) {} - - // In tests, assume that symbols are not re-exported - moduleNameToFileName(modulePath: string, containingFile?: string): string { - function splitPath(path: string): string[] { return path.split(/\/|\\/g); } - - function resolvePath(pathParts: string[]): string { - const result: string[] = []; - pathParts.forEach((part, index) => { - switch (part) { - case '': - case '.': - if (index > 0) return; - break; - case '..': - if (index > 0 && result.length != 0) result.pop(); - return; - } - result.push(part); - }); - return result.join('/'); - } - - function pathTo(from: string, to: string): string { - let result = to; - if (to.startsWith('.')) { - const fromParts = splitPath(from); - fromParts.pop(); // remove the file name. - const toParts = splitPath(to); - result = resolvePath(fromParts.concat(toParts)); - } - return result; - } - - if (modulePath.indexOf('.') === 0) { - const baseName = pathTo(containingFile, modulePath); - const tsName = baseName + '.ts'; - if (this._getMetadataFor(tsName)) { - return tsName; - } - return baseName + '.d.ts'; - } - return '/tmp/' + modulePath + '.d.ts'; - } - - getMetadataFor(moduleId: string): any { return this._getMetadataFor(moduleId); } - - private _getMetadataFor(moduleId: string): any { - if (this.data[moduleId] && moduleId.match(TS_EXT)) { - const text = this.data[moduleId]; - if (typeof text === 'string') { - const sf = ts.createSourceFile( - moduleId, this.data[moduleId], ts.ScriptTarget.ES5, /* setParentNodes */ true); - const diagnostics: ts.Diagnostic[] = (sf).parseDiagnostics; - if (diagnostics && diagnostics.length) { - throw Error(`Error encountered during parse of file ${moduleId}`); - } - return [this.collector.getMetadata(sf)]; - } - } - const result = this.data[moduleId]; - if (result) { - return Array.isArray(result) ? result : [result]; - } else { - return null; - } - } -} - const DEFAULT_TEST_DATA: {[key: string]: any} = { '/tmp/@angular/common/src/forms-deprecated/directives.d.ts': [{ '__symbolic': 'module', @@ -1008,8 +841,6 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = { } }, '/src/extern.d.ts': {'__symbolic': 'module', 'version': 3, metadata: {s: 's'}}, - '/tmp/src/version-error.d.ts': {'__symbolic': 'module', 'version': 100, metadata: {e: 's'}}, - '/tmp/src/version-2-error.d.ts': {'__symbolic': 'module', 'version': 2, metadata: {e: 's'}}, '/tmp/src/error-reporting.d.ts': { __symbolic: 'module', version: 3, @@ -1343,47 +1174,4 @@ const DEFAULT_TEST_DATA: {[key: string]: any} = { @Input f: Forward; } `, - '/tmp/src/reexport/reexport.d.ts': { - __symbolic: 'module', - version: 3, - metadata: {}, - exports: [ - {from: './src/origin1', export: ['One', 'Two', {name: 'Three', as: 'Four'}]}, - {from: './src/origin5'}, {from: './src/reexport2'} - ] - }, - '/tmp/src/reexport/src/origin1.d.ts': { - __symbolic: 'module', - version: 3, - metadata: { - One: {__symbolic: 'class'}, - Two: {__symbolic: 'class'}, - Three: {__symbolic: 'class'}, - }, - }, - '/tmp/src/reexport/src/origin5.d.ts': { - __symbolic: 'module', - version: 3, - metadata: { - Five: {__symbolic: 'class'}, - }, - }, - '/tmp/src/reexport/src/origin30.d.ts': { - __symbolic: 'module', - version: 3, - metadata: { - Thirty: {__symbolic: 'class'}, - }, - }, - '/tmp/src/reexport/src/originNone.d.ts': { - __symbolic: 'module', - version: 3, - metadata: {}, - }, - '/tmp/src/reexport/src/reexport2.d.ts': { - __symbolic: 'module', - version: 3, - metadata: {}, - exports: [{from: './originNone'}, {from: './origin30'}] - } }; diff --git a/modules/@angular/compiler/test/aot/static_symbol_resolver_spec.ts b/modules/@angular/compiler/test/aot/static_symbol_resolver_spec.ts new file mode 100644 index 0000000000..f68c8adbc8 --- /dev/null +++ b/modules/@angular/compiler/test/aot/static_symbol_resolver_spec.ts @@ -0,0 +1,372 @@ +/** + * @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 {StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost, Summary, SummaryResolver} from '@angular/compiler'; +import {MetadataCollector} from '@angular/tsc-wrapped'; +import * as ts from 'typescript'; + + + +// This matches .ts files but not .d.ts files. +const TS_EXT = /(^.|(?!\.d)..)\.ts$/; + +describe('StaticSymbolResolver', () => { + const noContext = new StaticSymbol('', ''); + let host: StaticSymbolResolverHost; + let symbolResolver: StaticSymbolResolver; + let symbolCache: StaticSymbolCache; + + beforeEach(() => { symbolCache = new StaticSymbolCache(); }); + + function init( + testData: {[key: string]: any} = DEFAULT_TEST_DATA, summaries: Summary[] = []) { + host = new MockStaticSymbolResolverHost(testData); + symbolResolver = + new StaticSymbolResolver(host, symbolCache, new MockSummaryResolver(summaries)); + } + + beforeEach(() => init()); + + it('should throw an exception for unsupported metadata versions', () => { + expect( + () => symbolResolver.resolveSymbol( + symbolResolver.getSymbolByModule('src/version-error', 'e'))) + .toThrow(new Error( + 'Metadata version mismatch for module /tmp/src/version-error.d.ts, found version 100, expected 3')); + }); + + it('should throw an exception for version 2 metadata', () => { + expect( + () => symbolResolver.resolveSymbol( + symbolResolver.getSymbolByModule('src/version-2-error', 'e'))) + .toThrowError( + 'Unsupported metadata version 2 for module /tmp/src/version-2-error.d.ts. This module should be compiled with a newer version of ngc'); + }); + + it('should be produce the same symbol if asked twice', () => { + const foo1 = symbolResolver.getStaticSymbol('main.ts', 'foo'); + const foo2 = symbolResolver.getStaticSymbol('main.ts', 'foo'); + expect(foo1).toBe(foo2); + }); + + it('should be able to produce a symbol for a module with no file', () => { + expect(symbolResolver.getStaticSymbol('angularjs', 'SomeAngularSymbol')).toBeDefined(); + }); + + it('should be able to split the metadata per symbol', () => { + init({ + '/tmp/src/test.ts': ` + export var a = 1; + export var b = 2; + ` + }); + expect(symbolResolver.resolveSymbol(symbolResolver.getStaticSymbol('/tmp/src/test.ts', 'a')) + .metadata) + .toBe(1); + expect(symbolResolver.resolveSymbol(symbolResolver.getStaticSymbol('/tmp/src/test.ts', 'b')) + .metadata) + .toBe(2); + }); + + it('should be able to resolve static symbols with members', () => { + init({ + '/tmp/src/test.ts': ` + export {exportedObj} from './export'; + + export var obj = {a: 1}; + export class SomeClass { + static someField = 2; + } + `, + '/tmp/src/export.ts': ` + export var exportedObj = {}; + `, + }); + expect(symbolResolver + .resolveSymbol(symbolResolver.getStaticSymbol('/tmp/src/test.ts', 'obj', ['a'])) + .metadata) + .toBe(1); + expect(symbolResolver + .resolveSymbol( + symbolResolver.getStaticSymbol('/tmp/src/test.ts', 'SomeClass', ['someField'])) + .metadata) + .toBe(2); + expect(symbolResolver + .resolveSymbol(symbolResolver.getStaticSymbol( + '/tmp/src/test.ts', 'exportedObj', ['someMember'])) + .metadata) + .toBe(symbolResolver.getStaticSymbol('/tmp/src/export.ts', 'exportedObj', ['someMember'])); + }); + + it('should use summaries in resolveSymbol and prefer them over regular metadata', () => { + const someSymbol = symbolCache.get('/test.ts', 'a'); + init({'/test.ts': 'export var a = 2'}, [{symbol: someSymbol, metadata: 1}]); + expect(symbolResolver.resolveSymbol(someSymbol).metadata).toBe(1); + }); + + it('should be able to get all exported symbols of a file', () => { + expect(symbolResolver.getSymbolsOf('/tmp/src/reexport/src/origin1.d.ts')).toEqual([ + symbolResolver.getStaticSymbol('/tmp/src/reexport/src/origin1.d.ts', 'One'), + symbolResolver.getStaticSymbol('/tmp/src/reexport/src/origin1.d.ts', 'Two'), + symbolResolver.getStaticSymbol('/tmp/src/reexport/src/origin1.d.ts', 'Three'), + ]); + }); + + it('should be able to get all reexported symbols of a file', () => { + expect(symbolResolver.getSymbolsOf('/tmp/src/reexport/reexport.d.ts')).toEqual([ + symbolResolver.getStaticSymbol('/tmp/src/reexport/reexport.d.ts', 'One'), + symbolResolver.getStaticSymbol('/tmp/src/reexport/reexport.d.ts', 'Two'), + symbolResolver.getStaticSymbol('/tmp/src/reexport/reexport.d.ts', 'Four'), + symbolResolver.getStaticSymbol('/tmp/src/reexport/reexport.d.ts', 'Five'), + symbolResolver.getStaticSymbol('/tmp/src/reexport/reexport.d.ts', 'Thirty') + ]); + }); + + it('should merge the exported symbols of a file with the exported symbols of its summary', () => { + const someSymbol = symbolCache.get('/test.ts', 'a'); + init( + {'/test.ts': 'export var b = 2'}, + [{symbol: symbolCache.get('/test.ts', 'a'), metadata: 1}]); + expect(symbolResolver.getSymbolsOf('/test.ts')).toEqual([ + symbolCache.get('/test.ts', 'a'), symbolCache.get('/test.ts', 'b') + ]); + }); + + it('should replace references by StaticSymbols', () => { + init({ + '/test.ts': ` + import {b, y} from './test2'; + export var a = b; + export var x = [y]; + + export function simpleFn(fnArg) { + return [a, y, fnArg]; + } + `, + '/test2.ts': ` + export var b; + export var y; + ` + }); + expect(symbolResolver.resolveSymbol(symbolCache.get('/test.ts', 'a')).metadata) + .toEqual(symbolCache.get('/test2.ts', 'b')); + expect(symbolResolver.resolveSymbol(symbolCache.get('/test.ts', 'x')).metadata).toEqual([ + symbolCache.get('/test2.ts', 'y') + ]); + expect(symbolResolver.resolveSymbol(symbolCache.get('/test.ts', 'simpleFn')).metadata).toEqual({ + __symbolic: 'function', + parameters: ['fnArg'], + value: [ + symbolCache.get('/test.ts', 'a'), symbolCache.get('/test2.ts', 'y'), + Object({__symbolic: 'reference', name: 'fnArg'}) + ] + }); + }); + + it('should ignore module references without a name', () => { + init({ + '/test.ts': ` + import Default from './test2'; + export {Default}; + ` + }); + + expect(symbolResolver.resolveSymbol(symbolCache.get('/test.ts', 'Default')).metadata) + .toBeFalsy(); + }); + + it('should be able to trace a named export', () => { + const symbol = symbolResolver + .resolveSymbol(symbolResolver.getSymbolByModule( + './reexport/reexport', 'One', '/tmp/src/main.ts')) + .metadata; + expect(symbol.name).toEqual('One'); + expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts'); + }); + + it('should be able to trace a renamed export', () => { + const symbol = symbolResolver + .resolveSymbol(symbolResolver.getSymbolByModule( + './reexport/reexport', 'Four', '/tmp/src/main.ts')) + .metadata; + expect(symbol.name).toEqual('Three'); + expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts'); + }); + + it('should be able to trace an export * export', () => { + const symbol = symbolResolver + .resolveSymbol(symbolResolver.getSymbolByModule( + './reexport/reexport', 'Five', '/tmp/src/main.ts')) + .metadata; + expect(symbol.name).toEqual('Five'); + expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin5.d.ts'); + }); + + it('should be able to trace a multi-level re-export', () => { + const symbol1 = symbolResolver + .resolveSymbol(symbolResolver.getSymbolByModule( + './reexport/reexport', 'Thirty', '/tmp/src/main.ts')) + .metadata; + expect(symbol1.name).toEqual('Thirty'); + expect(symbol1.filePath).toEqual('/tmp/src/reexport/src/reexport2.d.ts'); + const symbol2 = symbolResolver.resolveSymbol(symbol1).metadata; + expect(symbol2.name).toEqual('Thirty'); + expect(symbol2.filePath).toEqual('/tmp/src/reexport/src/origin30.d.ts'); + }); + + it('should cache tracing a named export', () => { + const moduleNameToFileNameSpy = spyOn(host, 'moduleNameToFileName').and.callThrough(); + const getMetadataForSpy = spyOn(host, 'getMetadataFor').and.callThrough(); + symbolResolver.resolveSymbol( + symbolResolver.getSymbolByModule('./reexport/reexport', 'One', '/tmp/src/main.ts')); + moduleNameToFileNameSpy.calls.reset(); + getMetadataForSpy.calls.reset(); + + const symbol = symbolResolver + .resolveSymbol(symbolResolver.getSymbolByModule( + './reexport/reexport', 'One', '/tmp/src/main.ts')) + .metadata; + expect(moduleNameToFileNameSpy.calls.count()).toBe(1); + expect(getMetadataForSpy.calls.count()).toBe(0); + expect(symbol.name).toEqual('One'); + expect(symbol.filePath).toEqual('/tmp/src/reexport/src/origin1.d.ts'); + }); + +}); + +export class MockSummaryResolver implements SummaryResolver { + constructor(private summaries: Summary[] = []) {} + + resolveSummary(reference: StaticSymbol): Summary { + return this.summaries.find(summary => summary.symbol === reference); + }; + getSymbolsOf(filePath: string): StaticSymbol[] { + return this.summaries.filter(summary => summary.symbol.filePath === filePath) + .map(summary => summary.symbol); + } +} + +export class MockStaticSymbolResolverHost implements StaticSymbolResolverHost { + private collector = new MetadataCollector(); + + constructor(private data: {[key: string]: any}) {} + + // In tests, assume that symbols are not re-exported + moduleNameToFileName(modulePath: string, containingFile?: string): string { + function splitPath(path: string): string[] { return path.split(/\/|\\/g); } + + function resolvePath(pathParts: string[]): string { + const result: string[] = []; + pathParts.forEach((part, index) => { + switch (part) { + case '': + case '.': + if (index > 0) return; + break; + case '..': + if (index > 0 && result.length != 0) result.pop(); + return; + } + result.push(part); + }); + return result.join('/'); + } + + function pathTo(from: string, to: string): string { + let result = to; + if (to.startsWith('.')) { + const fromParts = splitPath(from); + fromParts.pop(); // remove the file name. + const toParts = splitPath(to); + result = resolvePath(fromParts.concat(toParts)); + } + return result; + } + + if (modulePath.indexOf('.') === 0) { + const baseName = pathTo(containingFile, modulePath); + const tsName = baseName + '.ts'; + if (this._getMetadataFor(tsName)) { + return tsName; + } + return baseName + '.d.ts'; + } + return '/tmp/' + modulePath + '.d.ts'; + } + + getMetadataFor(moduleId: string): any { return this._getMetadataFor(moduleId); } + + private _getMetadataFor(filePath: string): any { + if (this.data[filePath] && filePath.match(TS_EXT)) { + const text = this.data[filePath]; + if (typeof text === 'string') { + const sf = ts.createSourceFile( + filePath, this.data[filePath], ts.ScriptTarget.ES5, /* setParentNodes */ true); + const diagnostics: ts.Diagnostic[] = (sf).parseDiagnostics; + if (diagnostics && diagnostics.length) { + throw Error(`Error encountered during parse of file ${filePath}`); + } + return [this.collector.getMetadata(sf)]; + } + } + const result = this.data[filePath]; + if (result) { + return Array.isArray(result) ? result : [result]; + } else { + return null; + } + } +} + +const DEFAULT_TEST_DATA: {[key: string]: any} = { + '/tmp/src/version-error.d.ts': {'__symbolic': 'module', 'version': 100, metadata: {e: 's'}}, + '/tmp/src/version-2-error.d.ts': {'__symbolic': 'module', 'version': 2, metadata: {e: 's'}}, + '/tmp/src/reexport/reexport.d.ts': { + __symbolic: 'module', + version: 3, + metadata: {}, + exports: [ + {from: './src/origin1', export: ['One', 'Two', {name: 'Three', as: 'Four'}]}, + {from: './src/origin5'}, {from: './src/reexport2'} + ] + }, + '/tmp/src/reexport/src/origin1.d.ts': { + __symbolic: 'module', + version: 3, + metadata: { + One: {__symbolic: 'class'}, + Two: {__symbolic: 'class'}, + Three: {__symbolic: 'class'}, + }, + }, + '/tmp/src/reexport/src/origin5.d.ts': { + __symbolic: 'module', + version: 3, + metadata: { + Five: {__symbolic: 'class'}, + }, + }, + '/tmp/src/reexport/src/origin30.d.ts': { + __symbolic: 'module', + version: 3, + metadata: { + Thirty: {__symbolic: 'class'}, + }, + }, + '/tmp/src/reexport/src/originNone.d.ts': { + __symbolic: 'module', + version: 3, + metadata: {}, + }, + '/tmp/src/reexport/src/reexport2.d.ts': { + __symbolic: 'module', + version: 3, + metadata: {}, + exports: [{from: './originNone'}, {from: './origin30'}] + } +}; diff --git a/modules/@angular/compiler/test/aot/summary_resolver_spec.ts b/modules/@angular/compiler/test/aot/summary_resolver_spec.ts index cc22a84847..0941c9ac46 100644 --- a/modules/@angular/compiler/test/aot/summary_resolver_spec.ts +++ b/modules/@angular/compiler/test/aot/summary_resolver_spec.ts @@ -6,128 +6,68 @@ * found in the LICENSE file at https://angular.io/license */ -import {AotSummaryResolver, AotSummaryResolverHost, CompileNgModuleSummary, CompileSummaryKind, CompileTypeSummary, StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler'; +import {AotSummaryResolver, AotSummaryResolverHost, CompileSummaryKind, CompileTypeSummary, ResolvedStaticSymbol, StaticSymbol, StaticSymbolCache, StaticSymbolResolver} from '@angular/compiler'; +import {AotSummarySerializerHost} from '@angular/compiler/src/aot/summary_serializer'; +import {deserializeSummaries, serializeSummaries} from '@angular/compiler/src/aot/summary_serializer'; import * as path from 'path'; -import {MockStaticReflectorHost} from './static_reflector_spec'; +import {MockStaticSymbolResolverHost, MockSummaryResolver} from './static_symbol_resolver_spec'; const EXT = /\.ts$|.d.ts$/; export function main() { describe('AotSummaryResolver', () => { - let resolver: AotSummaryResolver; - let staticReflector: StaticReflector; + let summaryResolver: AotSummaryResolver; + let symbolCache: StaticSymbolCache; + let host: MockAotSummaryResolverHost; + + beforeEach(() => { symbolCache = new StaticSymbolCache(); }); function init(summaries: {[filePath: string]: string} = {}) { - // Note: We don't give the static reflector metadata files, - // so that we can test that we can deserialize summary files - // without reading metadata files. This is important - // as summary files can contain references to files of transitive compilation - // dependencies, and we don't want to read their metadata files. - staticReflector = new StaticReflector(new MockStaticReflectorHost({})); - const host = new MockAotSummaryResolverHost(summaries); - resolver = new AotSummaryResolver(host, staticReflector, {excludeFilePattern: /\.d\.ts$/}); + host = new MockAotSummaryResolverHost(summaries); + summaryResolver = new AotSummaryResolver(host, symbolCache); } - it('should add .ngsummary.json to the filename', () => { - init(); - expect(resolver.serializeSummaries('a.ts', []).genFileUrl).toBe('a.ngsummary.json'); - expect(resolver.serializeSummaries('a.d.ts', []).genFileUrl).toBe('a.ngsummary.json'); - expect(resolver.serializeSummaries('a.js', []).genFileUrl).toBe('a.ngsummary.json'); + function serialize(symbols: ResolvedStaticSymbol[], types: CompileTypeSummary[]): string { + // Note: Don't use the top level host / summaryResolver as they might not be created yet + const mockSummaryResolver = new MockSummaryResolver([]); + const symbolResolver = new StaticSymbolResolver( + new MockStaticSymbolResolverHost({}), symbolCache, mockSummaryResolver); + return serializeSummaries( + new MockAotSummarySerializerHost(), mockSummaryResolver, symbolResolver, symbols, types); + } + + it('should load serialized summary files', () => { + const asymbol = symbolCache.get('/a.d.ts', 'a'); + init({'/a.ngsummary.json': serialize([{symbol: asymbol, metadata: 1}], [])}); + expect(summaryResolver.resolveSummary(asymbol)).toEqual({symbol: asymbol, metadata: 1}); }); - it('should serialize various data correctly', () => { - init(); - const serializedData = resolver.serializeSummaries( - '/tmp/some_pipe.ts', [{ - summaryKind: CompileSummaryKind.Pipe, - type: { - reference: staticReflector.getStaticSymbol('/tmp/some_pipe.ts', 'SomePipe'), - }, - aNumber: 1, - aString: 'hello', - anArray: [1, 2], - aStaticSymbol: - staticReflector.getStaticSymbol('/tmp/some_symbol.ts', 'someName', ['someMember']) - }]); + it('should not load summaries for source files', () => { + init({}); + spyOn(host, 'loadSummary').and.callThrough(); - // Note: this creates a new staticReflector! - init({[serializedData.genFileUrl]: serializedData.source}); - - const deserialized = resolver.resolveSummary( - staticReflector.getStaticSymbol('/tmp/some_pipe.d.ts', 'SomePipe')); - expect(deserialized.aNumber).toBe(1); - expect(deserialized.aString).toBe('hello'); - expect(deserialized.anArray).toEqual([1, 2]); - expect(deserialized.aStaticSymbol instanceof StaticSymbol).toBe(true); - // Note: change from .ts to .d.ts is expected - expect(deserialized.aStaticSymbol) - .toEqual( - staticReflector.getStaticSymbol('/tmp/some_symbol.d.ts', 'someName', ['someMember'])); + expect(summaryResolver.resolveSummary(symbolCache.get('/a.ts', 'a'))).toBeFalsy(); + expect(host.loadSummary).not.toHaveBeenCalled(); }); - it('should store reexports in the same file', () => { - init(); - const reexportedData = resolver.serializeSummaries( - '/tmp/some_pipe.ts', [{ - summaryKind: CompileSummaryKind.Pipe, - type: { - reference: staticReflector.getStaticSymbol('/tmp/some_pipe.ts', 'SomeReexportedPipe'), - diDeps: [], - lifecycleHooks: [] - }, - }]); - - init({[reexportedData.genFileUrl]: reexportedData.source}); - const serializedData = resolver.serializeSummaries('/tmp/some_module.ts', [ - { - summaryKind: CompileSummaryKind.NgModule, - type: { - reference: staticReflector.getStaticSymbol('/tmp/some_module.ts', 'SomeModule'), - diDeps: [], - lifecycleHooks: [] - }, - exportedPipes: [{ - reference: staticReflector.getStaticSymbol('/tmp/some_pipe.d.ts', 'SomeReexportedPipe') - }], - exportedDirectives: [], - providers: [], - entryComponents: [], - modules: [] - } - ]); - - init({[serializedData.genFileUrl]: serializedData.source}); - - resolver.resolveSummary( - staticReflector.getStaticSymbol('/tmp/some_module.d.ts', 'SomeModule')); - expect(resolver.resolveSummary( - staticReflector.getStaticSymbol('/tmp/some_pipe.d.ts', 'SomeReexportedPipe'))) - .toEqual({ - summaryKind: CompileSummaryKind.Pipe, - type: { - reference: - staticReflector.getStaticSymbol('/tmp/some_pipe.d.ts', 'SomeReexportedPipe'), - diDeps: [], - lifecycleHooks: [] - }, - }); + it('should cache summaries', () => { + const asymbol = symbolCache.get('/a.d.ts', 'a'); + init({'/a.ngsummary.json': serialize([{symbol: asymbol, metadata: 1}], [])}); + expect(summaryResolver.resolveSummary(asymbol)).toBe(summaryResolver.resolveSummary(asymbol)); }); + it('should return all sumbols in a summary', () => { + const asymbol = symbolCache.get('/a.d.ts', 'a'); + init({'/a.ngsummary.json': serialize([{symbol: asymbol, metadata: 1}], [])}); + expect(summaryResolver.getSymbolsOf('/a.d.ts')).toEqual([asymbol]); + + }); }); } -class MockAotSummaryResolverHost implements AotSummaryResolverHost { - constructor(private summaries: {[fileName: string]: string}) {} - - loadSummary(filePath: string): string { - const result = this.summaries[filePath]; - if (!result) { - throw new Error(`Could not find summary for ${filePath}`); - } - return result; - } +export class MockAotSummarySerializerHost implements AotSummarySerializerHost { fileNameToModuleName(fileName: string): string { return './' + path.basename(fileName).replace(EXT, ''); } @@ -135,4 +75,13 @@ class MockAotSummaryResolverHost implements AotSummaryResolverHost { getOutputFileName(sourceFileName: string): string { return sourceFileName.replace(EXT, '') + '.d.ts'; } + + isSourceFile(filePath: string) { return !filePath.endsWith('.d.ts'); } +} + +export class MockAotSummaryResolverHost extends MockAotSummarySerializerHost implements + AotSummaryResolverHost { + constructor(private summaries: {[fileName: string]: string}) { super(); } + + loadSummary(filePath: string): string { return this.summaries[filePath]; } } \ No newline at end of file diff --git a/modules/@angular/compiler/test/aot/summary_serializer_spec.ts b/modules/@angular/compiler/test/aot/summary_serializer_spec.ts new file mode 100644 index 0000000000..064f3a72a4 --- /dev/null +++ b/modules/@angular/compiler/test/aot/summary_serializer_spec.ts @@ -0,0 +1,199 @@ +/** + * @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 {AotSummaryResolver, AotSummaryResolverHost, CompileSummaryKind, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler'; +import {AotSummarySerializerHost, deserializeSummaries, serializeSummaries, summaryFileName} from '@angular/compiler/src/aot/summary_serializer'; + +import {MockStaticSymbolResolverHost} from './static_symbol_resolver_spec'; +import {MockAotSummaryResolverHost} from './summary_resolver_spec'; + + +export function main() { + describe('summary serializer', () => { + let summaryResolver: AotSummaryResolver; + let symbolResolver: StaticSymbolResolver; + let symbolCache: StaticSymbolCache; + let host: MockAotSummaryResolverHost; + + beforeEach(() => { symbolCache = new StaticSymbolCache(); }); + + function init( + summaries: {[filePath: string]: string} = {}, metadata: {[key: string]: any} = {}) { + host = new MockAotSummaryResolverHost(summaries); + summaryResolver = new AotSummaryResolver(host, symbolCache); + symbolResolver = new StaticSymbolResolver( + new MockStaticSymbolResolverHost(metadata), symbolCache, summaryResolver); + } + + describe('summaryFileName', () => { + it('should add .ngsummary.json to the filename', () => { + init(); + expect(summaryFileName('a.ts')).toBe('a.ngsummary.json'); + expect(summaryFileName('a.d.ts')).toBe('a.ngsummary.json'); + expect(summaryFileName('a.js')).toBe('a.ngsummary.json'); + }); + }); + + it('should serialize various data correctly', () => { + init(); + const serializedData = serializeSummaries( + host, summaryResolver, symbolResolver, + [ + { + symbol: symbolCache.get('/tmp/some_values.ts', 'Values'), + metadata: { + aNumber: 1, + aString: 'hello', + anArray: [1, 2], + aStaticSymbol: symbolCache.get('/tmp/some_symbol.ts', 'someName') + } + }, + { + symbol: symbolCache.get('/tmp/some_service.ts', 'SomeService'), + metadata: { + __symbolic: 'class', + members: {'aMethod': {__symbolic: 'function'}}, + statics: {aStatic: true} + } + } + ], + [{ + summaryKind: CompileSummaryKind.Injectable, + type: { + reference: symbolCache.get('/tmp/some_service.ts', 'SomeService'), + }, + }]); + + + const summaries = deserializeSummaries(symbolCache, serializedData); + expect(summaries.length).toBe(2); + + // Note: change from .ts to .d.ts is expected + expect(summaries[0].symbol).toBe(symbolCache.get('/tmp/some_values.d.ts', 'Values')); + expect(summaries[0].metadata).toEqual({ + aNumber: 1, + aString: 'hello', + anArray: [1, 2], + aStaticSymbol: symbolCache.get('/tmp/some_symbol.d.ts', 'someName') + }); + + expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/some_service.d.ts', 'SomeService')); + // serialization should only keep the statics... + expect(summaries[1].metadata).toEqual({__symbolic: 'class', statics: {aStatic: true}}); + expect(summaries[1].type.type.reference) + .toBe(symbolCache.get('/tmp/some_service.d.ts', 'SomeService')); + }); + + it('should automatically add exported directives / pipes of NgModules that are not source files', + () => { + init({}); + const externalSerialized = serializeSummaries(host, summaryResolver, symbolResolver, [], [ + { + summaryKind: CompileSummaryKind.Pipe, + type: { + reference: symbolCache.get('/tmp/external.ts', 'SomeExternalPipe'), + } + }, + { + summaryKind: CompileSummaryKind.Directive, + type: { + reference: symbolCache.get('/tmp/external.ts', 'SomeExternalDir'), + } + } + ]); + init({ + '/tmp/external.ngsummary.json': externalSerialized, + }); + + const serialized = serializeSummaries( + host, summaryResolver, symbolResolver, [], [{ + summaryKind: CompileSummaryKind.NgModule, + type: {reference: symbolCache.get('/tmp/some_module.ts', 'SomeModule')}, + exportedPipes: [ + {reference: symbolCache.get('/tmp/some_pipe.ts', 'SomePipe')}, + {reference: symbolCache.get('/tmp/external.d.ts', 'SomeExternalPipe')} + ], + exportedDirectives: [ + {reference: symbolCache.get('/tmp/some_dir.ts', 'SomeDir')}, + {reference: symbolCache.get('/tmp/external.d.ts', 'SomeExternalDir')} + ] + }]); + + const summaries = deserializeSummaries(symbolCache, serialized); + expect(summaries.length).toBe(3); + expect(summaries[0].symbol).toBe(symbolCache.get('/tmp/some_module.d.ts', 'SomeModule')); + expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/external.d.ts', 'SomeExternalDir')); + expect(summaries[2].symbol) + .toBe(symbolCache.get('/tmp/external.d.ts', 'SomeExternalPipe')); + }); + + it('should automatically add the metadata of referenced symbols that are not in the soure files', + () => { + const externalSerialized = serializeSummaries( + host, summaryResolver, symbolResolver, + [ + { + symbol: symbolCache.get('/tmp/external.ts', 'PROVIDERS'), + metadata: [symbolCache.get('/tmp/external_svc.ts', 'SomeService')] + }, + { + symbol: symbolCache.get('/tmp/external_svc.ts', 'SomeService'), + metadata: {__symbolic: 'class'} + } + ], + [{ + summaryKind: CompileSummaryKind.Injectable, + type: { + reference: symbolCache.get('/tmp/external_svc.ts', 'SomeService'), + } + }]); + init( + { + '/tmp/external.ngsummary.json': externalSerialized, + }, + { + '/tmp/local.ts': ` + export var local = 'a'; + `, + '/tmp/non_summary.d.ts': + {__symbolic: 'module', version: 3, metadata: {'external': 'b'}} + }); + const serialized = serializeSummaries( + host, summaryResolver, symbolResolver, [{ + symbol: symbolCache.get('/tmp/test.ts', 'main'), + metadata: { + local: symbolCache.get('/tmp/local.ts', 'local'), + external: symbolCache.get('/tmp/external.d.ts', 'PROVIDERS'), + externalNonSummary: symbolCache.get('/tmp/non_summary.d.ts', 'external') + } + }], + []); + + const summaries = deserializeSummaries(symbolCache, serialized); + // Note: local should not show up! + expect(summaries.length).toBe(4); + expect(summaries[0].symbol).toBe(symbolCache.get('/tmp/test.d.ts', 'main')); + expect(summaries[0].metadata).toEqual({ + local: symbolCache.get('/tmp/local.d.ts', 'local'), + external: symbolCache.get('/tmp/external.d.ts', 'PROVIDERS'), + externalNonSummary: symbolCache.get('/tmp/non_summary.d.ts', 'external') + }); + expect(summaries[1].symbol).toBe(symbolCache.get('/tmp/external.d.ts', 'PROVIDERS')); + expect(summaries[1].metadata).toEqual([symbolCache.get( + '/tmp/external_svc.d.ts', 'SomeService')]); + // there was no summary for non_summary, but it should have + // been serialized as well. + expect(summaries[2].symbol).toBe(symbolCache.get('/tmp/non_summary.d.ts', 'external')); + expect(summaries[2].metadata).toEqual('b'); + // SomService is a transitive dep, but sould have been serialized as well. + expect(summaries[3].symbol).toBe(symbolCache.get('/tmp/external_svc.d.ts', 'SomeService')); + expect(summaries[3].type.type.reference) + .toBe(symbolCache.get('/tmp/external_svc.d.ts', 'SomeService')); + }); + }); +} diff --git a/modules/@angular/compiler/test/output/ts_emitter_node_only_spec.ts b/modules/@angular/compiler/test/output/ts_emitter_node_only_spec.ts index 22c6c3d206..affcf18b2a 100644 --- a/modules/@angular/compiler/test/output/ts_emitter_node_only_spec.ts +++ b/modules/@angular/compiler/test/output/ts_emitter_node_only_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {StaticReflector, StaticReflectorHost, StaticSymbol} from '@angular/compiler'; +import {StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, StaticSymbolResolverHost} from '@angular/compiler'; import * as o from '@angular/compiler/src/output/output_ast'; import {ImportResolver} from '@angular/compiler/src/output/path_util'; import {TypeScriptEmitter} from '@angular/compiler/src/output/ts_emitter'; @@ -14,6 +14,8 @@ import {convertValueToOutputAst} from '@angular/compiler/src/output/value_util'; import {MetadataCollector, isClassMetadata, isMetadataSymbolicCallExpression} from '@angular/tsc-wrapped'; import * as ts from 'typescript'; +import {MockSummaryResolver} from '../aot/static_symbol_resolver_spec'; + describe('TypeScriptEmitter (node only)', () => { it('should quote identifiers quoted in the source', () => { const sourceText = ` @@ -27,7 +29,10 @@ describe('TypeScriptEmitter (node only)', () => { const source = ts.createSourceFile('test.ts', sourceText, ts.ScriptTarget.Latest); const collector = new MetadataCollector({quotedNames: true}); const stubHost = new StubReflectorHost(); - const reflector = new StaticReflector(stubHost); + const symbolCache = new StaticSymbolCache(); + const symbolResolver = + new StaticSymbolResolver(stubHost, symbolCache, new MockSummaryResolver()); + const reflector = new StaticReflector(symbolResolver); // Get the metadata from the above source const metadata = collector.getMetadata(source); @@ -62,11 +67,11 @@ describe('TypeScriptEmitter (node only)', () => { }); }); -class StubReflectorHost implements StaticReflectorHost { +class StubReflectorHost implements StaticSymbolResolverHost { getMetadataFor(modulePath: string): {[key: string]: any}[] { return []; } - moduleNameToFileName(moduleName: string, containingFile: string): string { return ''; } + moduleNameToFileName(moduleName: string, containingFile: string): string { return 'somePath'; } } class StubImportResolver extends ImportResolver { fileNameToModuleName(importedFilePath: string, containingFilePath: string): string { return ''; } -} \ No newline at end of file +} diff --git a/modules/@angular/core/src/linker/compiler.ts b/modules/@angular/core/src/linker/compiler.ts index 856053ad7f..a635c119d9 100644 --- a/modules/@angular/core/src/linker/compiler.ts +++ b/modules/@angular/core/src/linker/compiler.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {OpaqueToken} from '../di'; +import {Injectable, OpaqueToken} from '../di'; import {BaseError} from '../facade/errors'; import {stringify} from '../facade/lang'; import {ViewEncapsulation} from '../metadata'; @@ -54,6 +54,7 @@ function _throwError() { * of components. * @stable */ +@Injectable() export class Compiler { /** * Compiles the given NgModule and all of its components. All templates of the components listed diff --git a/modules/@angular/language-service/src/typescript_host.ts b/modules/@angular/language-service/src/typescript_host.ts index f47ef5df44..4e9c94cc02 100644 --- a/modules/@angular/language-service/src/typescript_host.ts +++ b/modules/@angular/language-service/src/typescript_host.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {CompileDirectiveMetadata, CompilerConfig, StaticReflector, StaticSymbol, StaticSymbolCache, componentModuleUrl, createOfflineCompileUrlResolver} from '@angular/compiler'; +import {AotSummaryResolver, CompileDirectiveMetadata, CompilerConfig, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, componentModuleUrl, createOfflineCompileUrlResolver} from '@angular/compiler'; import {NgAnalyzedModules, analyzeNgModules, extractProgramSymbols} from '@angular/compiler/src/aot/compiler'; import {DirectiveNormalizer} from '@angular/compiler/src/directive_normalizer'; import {DirectiveResolver} from '@angular/compiler/src/directive_resolver'; @@ -30,6 +30,7 @@ import {ReflectorHost} from './reflector_host'; import {BuiltinType, CompletionKind, Declaration, DeclarationError, Declarations, Definition, LanguageService, LanguageServiceHost, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable, TemplateSource, TemplateSources} from './types'; + /** * Create a `LanguageServiceHost` */ @@ -75,6 +76,7 @@ export class DummyResourceLoader extends ResourceLoader { export class TypeScriptServiceHost implements LanguageServiceHost { private _resolver: CompileMetadataResolver; private _staticSymbolCache = new StaticSymbolCache(); + private _staticSymbolResolver: StaticSymbolResolver; private _reflector: StaticReflector; private _reflectorHost: ReflectorHost; private _checker: ts.TypeChecker; @@ -159,10 +161,13 @@ export class TypeScriptServiceHost implements LanguageServiceHost { private ensureAnalyzedModules(): NgAnalyzedModules { let analyzedModules = this.analyzedModules; if (!analyzedModules) { + const analyzeHost = {isSourceFile(filePath: string) { return true; }}; const programSymbols = extractProgramSymbols( - this.reflector, this.program.getSourceFiles().map(sf => sf.fileName), {}); + this.staticSymbolResolver, this.program.getSourceFiles().map(sf => sf.fileName), + analyzeHost); - analyzedModules = this.analyzedModules = analyzeNgModules(programSymbols, {}, this.resolver); + analyzedModules = this.analyzedModules = + analyzeNgModules(programSymbols, analyzeHost, this.resolver); } return analyzedModules; } @@ -224,6 +229,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost { if (this.modulesOutOfDate) { this.analyzedModules = null; this._reflector = null; + this._staticSymbolResolver = null; this.templateReferences = null; this.fileToComponent = null; this.ensureAnalyzedModules(); @@ -385,12 +391,27 @@ export class TypeScriptServiceHost implements LanguageServiceHost { errors.push(error); } + private get staticSymbolResolver(): StaticSymbolResolver { + let result = this._staticSymbolResolver; + if (!result) { + const summaryResolver = new AotSummaryResolver( + { + loadSummary(filePath: string) { return null; }, + isSourceFile(sourceFilePath: string) { return true; } + }, + this._staticSymbolCache); + result = this._staticSymbolResolver = new StaticSymbolResolver( + this.reflectorHost, this._staticSymbolCache, summaryResolver, + (e, filePath) => this.collectError(e, filePath)); + } + return result; + } + private get reflector(): StaticReflector { let result = this._reflector; if (!result) { result = this._reflector = new StaticReflector( - this.reflectorHost, this._staticSymbolCache, [], [], - (e, filePath) => this.collectError(e, filePath)); + this.staticSymbolResolver, [], [], (e, filePath) => this.collectError(e, filePath)); } return result; }