From a838aba756df510daa3899f73c3732ef4fc571f2 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Mon, 24 Oct 2016 13:28:23 -0700 Subject: [PATCH] fix(compiler): walk third party modules (#12453) fixes #11889 fixes #12428 --- .../integrationtest/src/comp_using_3rdp.ts | 18 +++ .../integrationtest/src/messages.fi.xlf | 4 + .../integrationtest/src/messages.fi.xtb | 1 + .../integrationtest/src/module.ts | 39 ++++-- .../integrationtest/test/basic_spec.ts | 18 ++- .../integrationtest/test/i18n_spec.ts | 5 + .../integrationtest/test/ng_module_spec.ts | 35 ++++-- .../integrationtest/third_party_src/README.md | 8 ++ .../integrationtest/third_party_src/comp.ts | 16 +++ .../third_party_src/directive.ts | 17 +++ .../integrationtest/third_party_src/module.ts | 28 +++++ .../third_party_src/other_comp.ts | 16 +++ .../third_party_src/other_module.ts | 17 +++ .../third_party_src/tsconfig-build.json | 20 +++ .../integrationtest/tsconfig-build.json | 29 +++++ .../integrationtest/tsconfig.json | 29 ----- modules/@angular/compiler-cli/src/codegen.ts | 117 ++++++++---------- .../@angular/compiler-cli/src/extract_i18n.ts | 2 - .../@angular/compiler-cli/src/extractor.ts | 107 ++++++++-------- .../compiler-cli/src/reflector_host.ts | 6 + .../@angular/compiler/src/compile_metadata.ts | 5 +- .../compiler/src/metadata_resolver.ts | 35 +++--- .../@angular/compiler/src/offline_compiler.ts | 108 +++++++++++----- scripts/ci-lite/offline_compiler_test.sh | 22 ++-- 24 files changed, 463 insertions(+), 239 deletions(-) create mode 100644 modules/@angular/compiler-cli/integrationtest/src/comp_using_3rdp.ts create mode 100644 modules/@angular/compiler-cli/integrationtest/third_party_src/README.md create mode 100644 modules/@angular/compiler-cli/integrationtest/third_party_src/comp.ts create mode 100644 modules/@angular/compiler-cli/integrationtest/third_party_src/directive.ts create mode 100644 modules/@angular/compiler-cli/integrationtest/third_party_src/module.ts create mode 100644 modules/@angular/compiler-cli/integrationtest/third_party_src/other_comp.ts create mode 100644 modules/@angular/compiler-cli/integrationtest/third_party_src/other_module.ts create mode 100644 modules/@angular/compiler-cli/integrationtest/third_party_src/tsconfig-build.json create mode 100644 modules/@angular/compiler-cli/integrationtest/tsconfig-build.json delete mode 100644 modules/@angular/compiler-cli/integrationtest/tsconfig.json diff --git a/modules/@angular/compiler-cli/integrationtest/src/comp_using_3rdp.ts b/modules/@angular/compiler-cli/integrationtest/src/comp_using_3rdp.ts new file mode 100644 index 0000000000..6f864a79a7 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/src/comp_using_3rdp.ts @@ -0,0 +1,18 @@ +/** + * @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 {Component} from '@angular/core'; + +@Component({ + selector: 'use-third-party', + template: '' + + '', +}) +export class ComponentUsingThirdParty { + title: string = 'from 3rd party'; +} diff --git a/modules/@angular/compiler-cli/integrationtest/src/messages.fi.xlf b/modules/@angular/compiler-cli/integrationtest/src/messages.fi.xlf index 8ed5cbf868..1bc59fc41f 100644 --- a/modules/@angular/compiler-cli/integrationtest/src/messages.fi.xlf +++ b/modules/@angular/compiler-cli/integrationtest/src/messages.fi.xlf @@ -12,6 +12,10 @@ Welcome tervetuloa + + other-3rdP-component + other-3rdP-component + \ No newline at end of file diff --git a/modules/@angular/compiler-cli/integrationtest/src/messages.fi.xtb b/modules/@angular/compiler-cli/integrationtest/src/messages.fi.xtb index 5c24884c84..533660d0bf 100644 --- a/modules/@angular/compiler-cli/integrationtest/src/messages.fi.xtb +++ b/modules/@angular/compiler-cli/integrationtest/src/messages.fi.xtb @@ -9,4 +9,5 @@ käännä teksti tervetuloa + other-3rdP-component diff --git a/modules/@angular/compiler-cli/integrationtest/src/module.ts b/modules/@angular/compiler-cli/integrationtest/src/module.ts index c852877186..9a39e82b2f 100644 --- a/modules/@angular/compiler-cli/integrationtest/src/module.ts +++ b/modules/@angular/compiler-cli/integrationtest/src/module.ts @@ -11,9 +11,12 @@ import {FormsModule} from '@angular/forms'; import {BrowserModule} from '@angular/platform-browser'; import {MdButtonModule} from '@angular2-material/button'; +import {ThirdpartyModule} from '../third_party_src/module'; + import {MultipleComponentsMyComp, NextComp} from './a/multiple_components'; import {AnimateCmp} from './animate'; import {BasicComp} from './basic'; +import {ComponentUsingThirdParty} from './comp_using_3rdp'; import {CompWithAnalyzeEntryComponentsProvider, CompWithEntryComponents} from './entry_components'; import {CompConsumingEvents, CompUsingPipes, CompWithProviders, CompWithReferences, DirPublishingEvents, ModuleUsingCustomElements} from './features'; import {CompUsingRootModuleDirectiveAndPipe, SomeDirectiveInRootModule, SomePipeInRootModule, SomeService, someLibModuleWithProviders} from './module_fixtures'; @@ -22,35 +25,47 @@ import {CompForChildQuery, CompWithChildQuery, CompWithDirectiveChild, Directive @NgModule({ declarations: [ - SomeDirectiveInRootModule, - SomePipeInRootModule, AnimateCmp, BasicComp, + CompConsumingEvents, CompForChildQuery, - CompWithEntryComponents, + CompUsingPipes, + CompUsingRootModuleDirectiveAndPipe, CompWithAnalyzeEntryComponentsProvider, - ProjectingComp, CompWithChildQuery, CompWithDirectiveChild, + CompWithEntryComponents, CompWithNgContent, - CompUsingRootModuleDirectiveAndPipe, CompWithProviders, CompWithReferences, - CompUsingPipes, - CompConsumingEvents, + DirectiveForQuery, DirPublishingEvents, MultipleComponentsMyComp, - DirectiveForQuery, NextComp, + ProjectingComp, + SomeDirectiveInRootModule, + SomePipeInRootModule, + ComponentUsingThirdParty, ], imports: [ - BrowserModule, FormsModule, someLibModuleWithProviders(), ModuleUsingCustomElements, - MdButtonModule + BrowserModule, + FormsModule, + MdButtonModule, + ModuleUsingCustomElements, + someLibModuleWithProviders(), + ThirdpartyModule, ], providers: [SomeService], entryComponents: [ - AnimateCmp, BasicComp, CompWithEntryComponents, CompWithAnalyzeEntryComponentsProvider, - ProjectingComp, CompWithChildQuery, CompUsingRootModuleDirectiveAndPipe, CompWithReferences + AnimateCmp, + BasicComp, + CompUsingRootModuleDirectiveAndPipe, + CompWithAnalyzeEntryComponentsProvider, + CompWithChildQuery, + CompWithEntryComponents, + CompWithReferences, + ProjectingComp, + ComponentUsingThirdParty, ] }) export class MainModule { diff --git a/modules/@angular/compiler-cli/integrationtest/test/basic_spec.ts b/modules/@angular/compiler-cli/integrationtest/test/basic_spec.ts index 81f8d9e2da..ff57690237 100644 --- a/modules/@angular/compiler-cli/integrationtest/test/basic_spec.ts +++ b/modules/@angular/compiler-cli/integrationtest/test/basic_spec.ts @@ -43,13 +43,13 @@ describe('template codegen output', () => { }); it('should be able to create the basic component', () => { - var compFixture = createComponent(BasicComp); + const compFixture = createComponent(BasicComp); expect(compFixture.componentInstance).toBeTruthy(); }); it('should support ngIf', () => { - var compFixture = createComponent(BasicComp); - var debugElement = compFixture.debugElement; + const compFixture = createComponent(BasicComp); + const debugElement = compFixture.debugElement; expect(debugElement.children.length).toBe(3); compFixture.componentInstance.ctxBool = true; @@ -59,8 +59,8 @@ describe('template codegen output', () => { }); it('should support ngFor', () => { - var compFixture = createComponent(BasicComp); - var debugElement = compFixture.debugElement; + const compFixture = createComponent(BasicComp); + const debugElement = compFixture.debugElement; expect(debugElement.children.length).toBe(3); // test NgFor @@ -83,11 +83,9 @@ describe('template codegen output', () => { }); it('should support i18n for content tags', () => { - const compFixture = createComponent(BasicComp); - const debugElement = compFixture.debugElement; - const containerElement = debugElement.nativeElement; - const pElement = containerElement.children.find((c: any) => c.name == 'p'); - const pText = pElement.children.map((c: any) => c.data).join('').trim(); + const containerElement = createComponent(BasicComp).nativeElement; + const pElement = containerElement.children.find((c: any) => c.name == 'p'); + const pText = pElement.children.map((c: any) => c.data).join('').trim(); expect(pText).toBe('tervetuloa'); }); }); diff --git a/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts b/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts index ace7ec35a5..5346d88d7f 100644 --- a/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts +++ b/modules/@angular/compiler-cli/integrationtest/test/i18n_spec.ts @@ -34,6 +34,7 @@ const EXPECTED_XMB = ` ]> + other-3rdP-component translate me Welcome @@ -43,6 +44,10 @@ const EXPECTED_XLIFF = ` + + other-3rdP-component + + translate me diff --git a/modules/@angular/compiler-cli/integrationtest/test/ng_module_spec.ts b/modules/@angular/compiler-cli/integrationtest/test/ng_module_spec.ts index a0a565d7f4..812fc6b9ad 100644 --- a/modules/@angular/compiler-cli/integrationtest/test/ng_module_spec.ts +++ b/modules/@angular/compiler-cli/integrationtest/test/ng_module_spec.ts @@ -7,6 +7,7 @@ */ import './init'; +import {ComponentUsingThirdParty} from '../src/comp_using_3rdp'; import {MainModule} from '../src/module'; import {CompUsingLibModuleDirectiveAndPipe, CompUsingRootModuleDirectiveAndPipe, SOME_TOKEN, ServiceUsingLibModule, SomeLibModule, SomeService} from '../src/module_fixtures'; @@ -15,9 +16,9 @@ import {createComponent, createModule} from './util'; describe('NgModule', () => { it('should support providers', () => { const moduleRef = createModule(); - expect(moduleRef.instance instanceof MainModule).toBe(true); - expect(moduleRef.injector.get(MainModule) instanceof MainModule).toBe(true); - expect(moduleRef.injector.get(SomeService) instanceof SomeService).toBe(true); + expect(moduleRef.instance instanceof MainModule).toEqual(true); + expect(moduleRef.injector.get(MainModule) instanceof MainModule).toEqual(true); + expect(moduleRef.injector.get(SomeService) instanceof SomeService).toEqual(true); }); it('should support entryComponents components', () => { @@ -26,7 +27,7 @@ describe('NgModule', () => { CompUsingRootModuleDirectiveAndPipe); expect(cf.componentType).toBe(CompUsingRootModuleDirectiveAndPipe); const compRef = cf.create(moduleRef.injector); - expect(compRef.instance instanceof CompUsingRootModuleDirectiveAndPipe).toBe(true); + expect(compRef.instance instanceof CompUsingRootModuleDirectiveAndPipe).toEqual(true); }); it('should support entryComponents via the ANALYZE_FOR_ENTRY_COMPONENTS provider and function providers in components', @@ -42,12 +43,30 @@ describe('NgModule', () => { ]); }); + describe('third-party modules', () => { + // https://github.com/angular/angular/issues/11889 + it('should support third party entryComponents components', () => { + const fixture = createComponent(ComponentUsingThirdParty); + const thirdPComps = fixture.nativeElement.children; + expect(thirdPComps[0].children[0].children[0].data).toEqual('3rdP-component'); + expect(thirdPComps[1].children[0].children[0].data).toEqual('other-3rdP-component'); + }); + + // https://github.com/angular/angular/issues/12428 + it('should support third party directives', () => { + const fixture = createComponent(ComponentUsingThirdParty); + const debugElement = fixture.debugElement; + fixture.detectChanges(); + expect(debugElement.children[0].properties['title']).toEqual('from 3rd party'); + }); + }); + it('should support module directives and pipes', () => { const compFixture = createComponent(CompUsingRootModuleDirectiveAndPipe); compFixture.detectChanges(); const debugElement = compFixture.debugElement; - expect(debugElement.children[0].properties['title']).toBe('transformed someValue'); + expect(debugElement.children[0].properties['title']).toEqual('transformed someValue'); }); it('should support module directives and pipes on lib modules', () => { @@ -55,10 +74,10 @@ describe('NgModule', () => { compFixture.detectChanges(); const debugElement = compFixture.debugElement; - expect(debugElement.children[0].properties['title']).toBe('transformed someValue'); + expect(debugElement.children[0].properties['title']).toEqual('transformed someValue'); - expect(debugElement.injector.get(SomeLibModule) instanceof SomeLibModule).toBe(true); + expect(debugElement.injector.get(SomeLibModule) instanceof SomeLibModule).toEqual(true); expect(debugElement.injector.get(ServiceUsingLibModule) instanceof ServiceUsingLibModule) - .toBe(true); + .toEqual(true); }); }); diff --git a/modules/@angular/compiler-cli/integrationtest/third_party_src/README.md b/modules/@angular/compiler-cli/integrationtest/third_party_src/README.md new file mode 100644 index 0000000000..e9b4a035ab --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/third_party_src/README.md @@ -0,0 +1,8 @@ +This folder emulates consuming precompiled modules and components. +It is compiled separately from the other sources under `src` +to only generate `*.js` / `*.d.ts` / `*.metadata.json` files, +but no `*.ngfactory.ts` files. + +** WARNING ** +Do not import components/directives from here directly as we want to test that ngc still compiles +them when they are not imported. \ No newline at end of file diff --git a/modules/@angular/compiler-cli/integrationtest/third_party_src/comp.ts b/modules/@angular/compiler-cli/integrationtest/third_party_src/comp.ts new file mode 100644 index 0000000000..b34d317e9c --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/third_party_src/comp.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 + */ + +import {Component} from '@angular/core'; + +@Component({ + selector: 'third-party-comp', + template: '
3rdP-component
', +}) +export class ThirdPartyComponent { +} \ No newline at end of file diff --git a/modules/@angular/compiler-cli/integrationtest/third_party_src/directive.ts b/modules/@angular/compiler-cli/integrationtest/third_party_src/directive.ts new file mode 100644 index 0000000000..53d2335ade --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/third_party_src/directive.ts @@ -0,0 +1,17 @@ +/** + * @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 {Directive, Input} from '@angular/core'; + +@Directive({ + selector: '[thirdParty]', + host: {'[title]': 'thirdParty'}, +}) +export class ThirdPartyDirective { + @Input() thirdParty: string; +} diff --git a/modules/@angular/compiler-cli/integrationtest/third_party_src/module.ts b/modules/@angular/compiler-cli/integrationtest/third_party_src/module.ts new file mode 100644 index 0000000000..05b90e9eb6 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/third_party_src/module.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {NgModule} from '@angular/core'; + +import {ThirdPartyComponent} from './comp'; +import {ThirdPartyDirective} from './directive'; +import {AnotherThirdPartyModule} from './other_module'; + +@NgModule({ + declarations: [ + ThirdPartyComponent, + ThirdPartyDirective, + ], + exports: [ + AnotherThirdPartyModule, + ThirdPartyComponent, + ThirdPartyDirective, + ], + imports: [AnotherThirdPartyModule] +}) +export class ThirdpartyModule { +} \ No newline at end of file diff --git a/modules/@angular/compiler-cli/integrationtest/third_party_src/other_comp.ts b/modules/@angular/compiler-cli/integrationtest/third_party_src/other_comp.ts new file mode 100644 index 0000000000..553e08ba45 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/third_party_src/other_comp.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 + */ + +import {Component} from '@angular/core'; + +@Component({ + selector: 'another-third-party-comp', + template: '
other-3rdP-component
', +}) +export class AnotherThirdpartyComponent { +} \ No newline at end of file diff --git a/modules/@angular/compiler-cli/integrationtest/third_party_src/other_module.ts b/modules/@angular/compiler-cli/integrationtest/third_party_src/other_module.ts new file mode 100644 index 0000000000..711a1582b9 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/third_party_src/other_module.ts @@ -0,0 +1,17 @@ +/** + * @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 {NgModule} from '@angular/core'; +import {AnotherThirdpartyComponent} from './other_comp'; + +@NgModule({ + declarations: [AnotherThirdpartyComponent], + exports: [AnotherThirdpartyComponent], +}) +export class AnotherThirdPartyModule { +} \ No newline at end of file diff --git a/modules/@angular/compiler-cli/integrationtest/third_party_src/tsconfig-build.json b/modules/@angular/compiler-cli/integrationtest/third_party_src/tsconfig-build.json new file mode 100644 index 0000000000..fa3e79f5f6 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/third_party_src/tsconfig-build.json @@ -0,0 +1,20 @@ +{ + "angularCompilerOptions": { + // For TypeScript 1.8, we have to lay out generated files + // in the same source directory with your code. + "genDir": ".", + "debug": true + }, + + "compilerOptions": { + "target": "es5", + "experimentalDecorators": true, + "noImplicitAny": true, + "moduleResolution": "node", + "rootDir": "", + "declaration": true, + "lib": ["es6", "dom"], + "baseUrl": ".", + "outDir": "../node_modules/third_party" + } +} \ No newline at end of file diff --git a/modules/@angular/compiler-cli/integrationtest/tsconfig-build.json b/modules/@angular/compiler-cli/integrationtest/tsconfig-build.json new file mode 100644 index 0000000000..3072122976 --- /dev/null +++ b/modules/@angular/compiler-cli/integrationtest/tsconfig-build.json @@ -0,0 +1,29 @@ +{ + "angularCompilerOptions": { + // For TypeScript 1.8, we have to lay out generated files + // in the same source directory with your code. + "genDir": ".", + "debug": true + }, + + "compilerOptions": { + "target": "es5", + "experimentalDecorators": true, + "noImplicitAny": true, + "moduleResolution": "node", + "rootDir": "", + "declaration": true, + "lib": ["es6", "dom"], + "baseUrl": "." + }, + + "files": [ + "src/module", + "src/bootstrap", + "test/all_spec", + "benchmarks/src/tree/ng2/index_aot.ts", + "benchmarks/src/tree/ng2_switch/index_aot.ts", + "benchmarks/src/largetable/ng2/index_aot.ts", + "benchmarks/src/largetable/ng2_switch/index_aot.ts" + ] +} \ No newline at end of file diff --git a/modules/@angular/compiler-cli/integrationtest/tsconfig.json b/modules/@angular/compiler-cli/integrationtest/tsconfig.json deleted file mode 100644 index 054b56c206..0000000000 --- a/modules/@angular/compiler-cli/integrationtest/tsconfig.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "angularCompilerOptions": { - // For TypeScript 1.8, we have to lay out generated files - // in the same source directory with your code. - "genDir": ".", - "debug": true - }, - - "compilerOptions": { - "target": "es5", - "experimentalDecorators": true, - "noImplicitAny": true, - "moduleResolution": "node", - "rootDir": "", - "declaration": true, - "lib": ["es6", "dom"], - "baseUrl": "." - }, - - "files": [ - "src/module", - "src/bootstrap", - "test/all_spec", - "benchmarks/src/tree/ng2/index_aot.ts", - "benchmarks/src/tree/ng2_switch/index_aot.ts", - "benchmarks/src/largetable/ng2/index_aot.ts", - "benchmarks/src/largetable/ng2_switch/index_aot.ts" - ] -} diff --git a/modules/@angular/compiler-cli/src/codegen.ts b/modules/@angular/compiler-cli/src/codegen.ts index b1e56725db..569e66080b 100644 --- a/modules/@angular/compiler-cli/src/codegen.ts +++ b/modules/@angular/compiler-cli/src/codegen.ts @@ -40,50 +40,47 @@ export class CodeGeneratorModuleCollector { private staticReflector: StaticReflector, private reflectorHost: StaticReflectorHost, private program: ts.Program, private options: AngularCompilerOptions) {} - getModuleSymbols(program: ts.Program): {fileMetas: FileMetadata[], ngModules: StaticSymbol[]} { + getModuleSymbols(): StaticSymbol[] { // Compare with false since the default should be true - const skipFileNames = (this.options.generateCodeForLibraries === false) ? - GENERATED_OR_DTS_FILES : - GENERATED_FILES; - let filePaths = this.program.getSourceFiles() - .filter(sf => !skipFileNames.test(sf.fileName)) - .map(sf => this.reflectorHost.getCanonicalFileName(sf.fileName)); - const fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath)); - const ngModules = fileMetas.reduce((ngModules, fileMeta) => { - ngModules.push(...fileMeta.ngModules); - return ngModules; - }, []); - return {fileMetas, ngModules}; - } + const skipFileNames = + this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES; - private readFileMetadata(absSourcePath: string): FileMetadata { - const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath); - const result: FileMetadata = {directives: [], ngModules: [], fileUrl: absSourcePath}; - if (!moduleMetadata) { - console.log(`WARNING: no metadata found for ${absSourcePath}`); - return result; - } - const metadata = moduleMetadata['metadata']; - const symbols = metadata && Object.keys(metadata); - if (!symbols || !symbols.length) { - return result; - } - for (const symbol of symbols) { - if (metadata[symbol] && metadata[symbol].__symbolic == 'error') { - // Ignore symbols that are only included to record error information. - continue; - } - const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath); - const annotations = this.staticReflector.annotations(staticType); - annotations.forEach((annotation) => { - if (annotation instanceof NgModule) { - result.ngModules.push(staticType); - } else if (annotation instanceof Directive) { - result.directives.push(staticType); - } - }); - } - return result; + const ngModules: StaticSymbol[] = []; + + this.program.getSourceFiles() + .filter(sourceFile => !skipFileNames.test(sourceFile.fileName)) + .forEach(sourceFile => { + const absSrcPath = this.reflectorHost.getCanonicalFileName(sourceFile.fileName); + + const moduleMetadata = this.staticReflector.getModuleMetadata(absSrcPath); + if (!moduleMetadata) { + console.log(`WARNING: no metadata found for ${absSrcPath}`); + 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; + } + const staticType = this.reflectorHost.findDeclaration(absSrcPath, symbol, absSrcPath); + const annotations = this.staticReflector.annotations(staticType); + annotations.some((annotation) => { + if (annotation instanceof NgModule) { + ngModules.push(staticType); + return true; + } + }); + } + }); + + return ngModules; } } @@ -101,7 +98,7 @@ export class CodeGenerator { // Write codegen in a directory structure matching the sources. private calculateEmitPath(filePath: string): string { let root = this.options.basePath; - for (let eachRootDir of this.options.rootDirs || []) { + for (const eachRootDir of this.options.rootDirs || []) { if (this.options.trace) { console.log(`Check if ${filePath} is under rootDirs element ${eachRootDir}`); } @@ -111,31 +108,27 @@ export class CodeGenerator { } // transplant the codegen path to be inside the `genDir` - var relativePath: string = path.relative(root, filePath); + let relativePath: string = path.relative(root, filePath); while (relativePath.startsWith('..' + path.sep)) { // Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything // into `genDir`. relativePath = relativePath.substr(3); } + return path.join(this.options.genDir, relativePath); } codegen(): Promise { - const {fileMetas, ngModules} = this.moduleCollector.getModuleSymbols(this.program); - const analyzedNgModules = this.compiler.analyzeModules(ngModules); - return Promise.all(fileMetas.map( - (fileMeta) => - this.compiler - .compile( - fileMeta.fileUrl, analyzedNgModules, fileMeta.directives, fileMeta.ngModules) - .then((generatedModules) => { - generatedModules.forEach((generatedModule) => { - const sourceFile = this.program.getSourceFile(fileMeta.fileUrl); - const emitPath = this.calculateEmitPath(generatedModule.moduleUrl); - this.host.writeFile( - emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]); - }); - }))); + const ngModules = this.moduleCollector.getModuleSymbols(); + + return this.compiler.compileModules(ngModules).then(generatedModules => { + generatedModules.forEach(generatedModule => { + const sourceFile = this.program.getSourceFile(generatedModule.fileUrl); + const emitPath = this.calculateEmitPath(generatedModule.moduleUrl); + this.host.writeFile( + emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]); + }); + }); } static create( @@ -201,9 +194,3 @@ export class CodeGenerator { options, program, compilerHost, staticReflector, offlineCompiler, reflectorHost); } } - -export interface FileMetadata { - fileUrl: string; - directives: StaticSymbol[]; - ngModules: StaticSymbol[]; -} diff --git a/modules/@angular/compiler-cli/src/extract_i18n.ts b/modules/@angular/compiler-cli/src/extract_i18n.ts index e2e976d147..4efcf1cbea 100644 --- a/modules/@angular/compiler-cli/src/extract_i18n.ts +++ b/modules/@angular/compiler-cli/src/extract_i18n.ts @@ -10,8 +10,6 @@ /** * Extract i18n messages from source code - * - * TODO(vicb): factorize code with the CodeGenerator */ // Must be imported first, because angular2 decorators throws on load. import 'reflect-metadata'; diff --git a/modules/@angular/compiler-cli/src/extractor.ts b/modules/@angular/compiler-cli/src/extractor.ts index 9a336a9717..cd93036626 100644 --- a/modules/@angular/compiler-cli/src/extractor.ts +++ b/modules/@angular/compiler-cli/src/extractor.ts @@ -18,16 +18,13 @@ import 'reflect-metadata'; import * as compiler from '@angular/compiler'; import {Component, NgModule, ViewEncapsulation} from '@angular/core'; import * as tsc from '@angular/tsc-wrapped'; -import * as path from 'path'; import * as ts from 'typescript'; -import {Console} from './private_import_core'; import {ReflectorHost, ReflectorHostContext} from './reflector_host'; import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities'; import {StaticReflector, StaticSymbol} from './static_reflector'; const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/; -const GENERATED_OR_DTS_FILES = /\.d\.ts$|\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/; export class Extractor { constructor( @@ -37,82 +34,82 @@ export class Extractor { private metadataResolver: compiler.CompileMetadataResolver, private directiveNormalizer: compiler.DirectiveNormalizer) {} - private readFileMetadata(absSourcePath: string): FileMetadata { + private readModuleSymbols(absSourcePath: string): StaticSymbol[] { const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath); - const result: FileMetadata = {components: [], ngModules: [], fileUrl: absSourcePath}; + const modSymbols: StaticSymbol[] = []; if (!moduleMetadata) { console.log(`WARNING: no metadata found for ${absSourcePath}`); - return result; + return modSymbols; } + const metadata = moduleMetadata['metadata']; const symbols = metadata && Object.keys(metadata); if (!symbols || !symbols.length) { - return result; + return modSymbols; } + for (const symbol of symbols) { if (metadata[symbol] && metadata[symbol].__symbolic == 'error') { // Ignore symbols that are only included to record error information. continue; } + const staticType = this.reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath); const annotations = this.staticReflector.annotations(staticType); - annotations.forEach((annotation) => { - if (annotation instanceof NgModule) { - result.ngModules.push(staticType); - } else if (annotation instanceof Component) { - result.components.push(staticType); + + annotations.some(a => { + if (a instanceof NgModule) { + modSymbols.push(staticType); + return true; } }); } - return result; + + return modSymbols; } extract(): Promise { - const skipFileNames = (this.options.generateCodeForLibraries === false) ? - GENERATED_OR_DTS_FILES : - GENERATED_FILES; const filePaths = - this.program.getSourceFiles().map(sf => sf.fileName).filter(f => !skipFileNames.test(f)); - const fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath)); - const ngModules = fileMetas.reduce((ngModules, fileMeta) => { - ngModules.push(...fileMeta.ngModules); - return ngModules; - }, []); - const analyzedNgModules = compiler.analyzeModules(ngModules, this.metadataResolver); - const errors: compiler.ParseError[] = []; + this.program.getSourceFiles().map(sf => sf.fileName).filter(f => !GENERATED_FILES.test(f)); + const ngModules: StaticSymbol[] = []; + + filePaths.forEach((filePath) => ngModules.push(...this.readModuleSymbols(filePath))); + + const files = compiler.analyzeNgModules(ngModules, this.metadataResolver).files; + const errors: compiler.ParseError[] = []; + const filePromises: Promise[] = []; + + files.forEach(file => { + const cmpPromises: Promise[] = []; + file.directives.forEach(directiveType => { + const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType); + if (dirMeta.isComponent) { + cmpPromises.push(this.directiveNormalizer.normalizeDirective(dirMeta).asyncResult); + } + }); + + if (cmpPromises.length) { + const done = + Promise.all(cmpPromises).then((compMetas: compiler.CompileDirectiveMetadata[]) => { + compMetas.forEach(compMeta => { + const html = compMeta.template.template; + const interpolationConfig = + compiler.InterpolationConfig.fromArray(compMeta.template.interpolation); + errors.push(...this.messageBundle.updateFromTemplate( + html, file.srcUrl, interpolationConfig)); + }); + }); + + filePromises.push(done); + } + }); - let bundlePromise = - Promise - .all(fileMetas.map((fileMeta) => { - const url = fileMeta.fileUrl; - return Promise.all(fileMeta.components.map(compType => { - const compMeta = this.metadataResolver.getDirectiveMetadata(compType); - const ngModule = analyzedNgModules.ngModuleByDirective.get(compType); - if (!ngModule) { - throw new Error( - `Cannot determine the module for component ${compMeta.type.name}!`); - } - return Promise - .all([compMeta, ...ngModule.transitiveModule.directives].map( - dirMeta => - this.directiveNormalizer.normalizeDirective(dirMeta).asyncResult)) - .then((normalizedCompWithDirectives) => { - const compMeta = normalizedCompWithDirectives[0]; - const html = compMeta.template.template; - const interpolationConfig = - compiler.InterpolationConfig.fromArray(compMeta.template.interpolation); - errors.push( - ...this.messageBundle.updateFromTemplate(html, url, interpolationConfig)); - }); - })); - })) - .then(_ => this.messageBundle); if (errors.length) { throw new Error(errors.map(e => e.toString()).join('\n')); } - return bundlePromise; + return Promise.all(filePromises).then(_ => this.messageBundle); } static create( @@ -148,10 +145,4 @@ export class Extractor { options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver, normalizer); } -} - -interface FileMetadata { - fileUrl: string; - components: StaticSymbol[]; - ngModules: StaticSymbol[]; -} +} \ No newline at end of file diff --git a/modules/@angular/compiler-cli/src/reflector_host.ts b/modules/@angular/compiler-cli/src/reflector_host.ts index 9a8d32e13f..b7d701b6fc 100644 --- a/modules/@angular/compiler-cli/src/reflector_host.ts +++ b/modules/@angular/compiler-cli/src/reflector_host.ts @@ -250,6 +250,12 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator { } else { const sf = this.program.getSourceFile(filePath); if (!sf) { + if (this.context.fileExists(filePath)) { + const sourceText = this.context.readFile(filePath); + return this.metadataCollector.getMetadata( + ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true)); + } + throw new Error(`Source file ${filePath} not present in program.`); } return this.metadataCollector.getMetadata(sf); diff --git a/modules/@angular/compiler/src/compile_metadata.ts b/modules/@angular/compiler/src/compile_metadata.ts index e645ad5131..1699b35b0e 100644 --- a/modules/@angular/compiler/src/compile_metadata.ts +++ b/modules/@angular/compiler/src/compile_metadata.ts @@ -508,7 +508,7 @@ export class CompilePipeMetadata implements CompileMetadataWithIdentifier { } /** - * Metadata regarding compilation of a directive. + * Metadata regarding compilation of a module. */ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier { type: CompileTypeMetadata; @@ -568,6 +568,7 @@ export class CompileNgModuleMetadata implements CompileMetadataWithIdentifier { export class TransitiveCompileNgModuleMetadata { directivesSet = new Set>(); pipesSet = new Set>(); + constructor( public modules: CompileNgModuleMetadata[], public providers: CompileProviderMetadata[], public entryComponents: CompileTypeMetadata[], public directives: CompileDirectiveMetadata[], @@ -580,11 +581,13 @@ export class TransitiveCompileNgModuleMetadata { export function removeIdentifierDuplicates(items: T[]): T[] { const map = new Map(); + items.forEach((item) => { if (!map.get(item.identifier.reference)) { map.set(item.identifier.reference, item); } }); + return MapWrapper.values(map); } diff --git a/modules/@angular/compiler/src/metadata_resolver.ts b/modules/@angular/compiler/src/metadata_resolver.ts index 56806d717c..530906b6a7 100644 --- a/modules/@angular/compiler/src/metadata_resolver.ts +++ b/modules/@angular/compiler/src/metadata_resolver.ts @@ -378,15 +378,15 @@ export class CompileMetadataResolver { } private _getTypeDescriptor(type: Type): string { - if (this._directiveResolver.resolve(type, false) !== null) { + if (this._directiveResolver.resolve(type, false)) { return 'directive'; } - if (this._pipeResolver.resolve(type, false) !== null) { + if (this._pipeResolver.resolve(type, false)) { return 'pipe'; } - if (this._ngModuleResolver.resolve(type, false) !== null) { + if (this._ngModuleResolver.resolve(type, false)) { return 'module'; } @@ -508,7 +508,7 @@ export class CompileMetadataResolver { let isOptional = false; let query: Query = null; let viewQuery: Query = null; - var token: any = null; + let token: any = null; if (Array.isArray(param)) { param.forEach((paramEntry) => { if (paramEntry instanceof Host) { @@ -605,19 +605,20 @@ export class CompileMetadataResolver { } else if (isValidType(provider)) { compileProvider = this.getTypeMetadata(provider, staticTypeModuleUrl(provider)); } else { - let providersInfo = (providers.reduce( - (soFar: string[], seenProvider: any, seenProviderIdx: number) => { - if (seenProviderIdx < providerIdx) { - soFar.push(`${stringify(seenProvider)}`); - } else if (seenProviderIdx == providerIdx) { - soFar.push(`?${stringify(seenProvider)}?`); - } else if (seenProviderIdx == providerIdx + 1) { - soFar.push('...'); - } - return soFar; - }, - [])) - .join(', '); + const providersInfo = + (providers.reduce( + (soFar: string[], seenProvider: any, seenProviderIdx: number) => { + if (seenProviderIdx < providerIdx) { + soFar.push(`${stringify(seenProvider)}`); + } else if (seenProviderIdx == providerIdx) { + soFar.push(`?${stringify(seenProvider)}?`); + } else if (seenProviderIdx == providerIdx + 1) { + soFar.push('...'); + } + return soFar; + }, + [])) + .join(', '); throw new Error( `Invalid ${debugInfo ? debugInfo : 'provider'} - only instances of Provider and Type are allowed, got: [${providersInfo}]`); diff --git a/modules/@angular/compiler/src/offline_compiler.ts b/modules/@angular/compiler/src/offline_compiler.ts index 1cc084180d..7ae27e5c51 100644 --- a/modules/@angular/compiler/src/offline_compiler.ts +++ b/modules/@angular/compiler/src/offline_compiler.ts @@ -13,6 +13,7 @@ import {AnimationParser} from './animation/animation_parser'; import {CompileDirectiveMetadata, CompileIdentifierMetadata, CompileNgModuleMetadata, CompilePipeMetadata, CompileProviderMetadata, StaticSymbol, createHostComponentMeta} from './compile_metadata'; import {DirectiveNormalizer} from './directive_normalizer'; import {DirectiveWrapperCompileResult, DirectiveWrapperCompiler} from './directive_wrapper_compiler'; +import {ListWrapper, MapWrapper} from './facade/collection'; import {Identifiers, resolveIdentifier, resolveIdentifierToken} from './identifiers'; import {CompileMetadataResolver} from './metadata_resolver'; import {NgModuleCompiler} from './ng_module_compiler'; @@ -23,28 +24,63 @@ import {TemplateParser} from './template_parser/template_parser'; import {ComponentFactoryDependency, DirectiveWrapperDependency, ViewCompileResult, ViewCompiler, ViewFactoryDependency} from './view_compiler/view_compiler'; export class SourceModule { - constructor(public moduleUrl: string, public source: string) {} + constructor(public fileUrl: string, public moduleUrl: string, public source: string) {} } -export class NgModulesSummary { - constructor( - public ngModuleByDirective: Map, - public ngModules: CompileNgModuleMetadata[]) {} -} +// Returns all the source files and a mapping from modules to directives +export function analyzeNgModules( + ngModules: StaticSymbol[], metadataResolver: CompileMetadataResolver): { + ngModuleByDirective: Map, + files: Array<{srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}> +} { + const moduleMetasByRef = new Map(); -export function analyzeModules( - ngModules: StaticSymbol[], metadataResolver: CompileMetadataResolver) { + // For every input modules add the list of transitively included modules + ngModules.forEach(ngModule => { + const modMeta = metadataResolver.getNgModuleMetadata(ngModule); + modMeta.transitiveModule.modules.forEach( + modMeta => { moduleMetasByRef.set(modMeta.type.reference, modMeta); }); + }); + + const ngModuleMetas = MapWrapper.values(moduleMetasByRef); const ngModuleByDirective = new Map(); - const modules: CompileNgModuleMetadata[] = []; + const ngModulesByFile = new Map(); + const ngDirectivesByFile = new Map(); + const srcFileUrls = new Set(); + + // Looping over all modules to construct: + // - a map from files to modules `ngModulesByFile`, + // - a map from files to directives `ngDirectivesByFile`, + // - a map from modules to directives `ngModuleByDirective`. + ngModuleMetas.forEach((ngModuleMeta) => { + const srcFileUrl = ngModuleMeta.type.reference.filePath; + srcFileUrls.add(srcFileUrl); + ngModulesByFile.set( + srcFileUrl, (ngModulesByFile.get(srcFileUrl) || []).concat(ngModuleMeta.type.reference)); - ngModules.forEach((ngModule) => { - const ngModuleMeta = metadataResolver.getNgModuleMetadata(ngModule); - modules.push(ngModuleMeta); ngModuleMeta.declaredDirectives.forEach((dirMeta: CompileDirectiveMetadata) => { + const fileUrl = dirMeta.type.reference.filePath; + srcFileUrls.add(fileUrl); + ngDirectivesByFile.set( + fileUrl, (ngDirectivesByFile.get(fileUrl) || []).concat(dirMeta.type.reference)); ngModuleByDirective.set(dirMeta.type.reference, ngModuleMeta); }); }); - return new NgModulesSummary(ngModuleByDirective, modules); + + const files: {srcUrl: string, directives: StaticSymbol[], ngModules: StaticSymbol[]}[] = []; + + srcFileUrls.forEach((srcUrl) => { + const directives = ngDirectivesByFile.get(srcUrl) || []; + const ngModules = ngModulesByFile.get(srcUrl) || []; + files.push({srcUrl, directives, ngModules}); + }); + + return { + // map from modules to declared directives + ngModuleByDirective, + // list of modules and directives for every source file + files, + }; } export class OfflineCompiler { @@ -59,19 +95,26 @@ export class OfflineCompiler { private _ngModuleCompiler: NgModuleCompiler, private _outputEmitter: OutputEmitter, private _localeId: string, private _translationFormat: string) {} - analyzeModules(ngModules: StaticSymbol[]): NgModulesSummary { - return analyzeModules(ngModules, this._metadataResolver); - } - clearCache() { this._directiveNormalizer.clearCache(); this._metadataResolver.clearCache(); } - compile( - moduleUrl: string, ngModulesSummary: NgModulesSummary, directives: StaticSymbol[], - ngModules: StaticSymbol[]): Promise { - const fileSuffix = _splitTypescriptSuffix(moduleUrl)[1]; + compileModules(ngModules: StaticSymbol[]): Promise { + const {ngModuleByDirective, files} = analyzeNgModules(ngModules, this._metadataResolver); + + const sourceModules = files.map( + file => this._compileSrcFile( + file.srcUrl, ngModuleByDirective, file.directives, file.ngModules)); + + return Promise.all(sourceModules) + .then((modules: SourceModule[][]) => ListWrapper.flatten(modules)); + } + + private _compileSrcFile( + srcFileUrl: string, ngModuleByDirective: Map, + directives: StaticSymbol[], ngModules: StaticSymbol[]): Promise { + const fileSuffix = _splitTypescriptSuffix(srcFileUrl)[1]; const statements: o.Statement[] = []; const exportedVars: string[] = []; const outputSourceModules: SourceModule[] = []; @@ -91,7 +134,7 @@ export class OfflineCompiler { if (!compMeta.isComponent) { return Promise.resolve(null); } - const ngModule = ngModulesSummary.ngModuleByDirective.get(dirType); + const ngModule = ngModuleByDirective.get(dirType); if (!ngModule) { throw new Error(`Cannot determine the module for component ${compMeta.type.name}!`); } @@ -106,7 +149,8 @@ export class OfflineCompiler { // compile styles const stylesCompileResults = this._styleCompiler.compileComponent(compMeta); stylesCompileResults.externalStylesheets.forEach((compiledStyleSheet) => { - outputSourceModules.push(this._codgenStyles(compiledStyleSheet, fileSuffix)); + outputSourceModules.push( + this._codgenStyles(srcFileUrl, compiledStyleSheet, fileSuffix)); }); // compile components @@ -119,8 +163,9 @@ export class OfflineCompiler { })) .then(() => { if (statements.length > 0) { - outputSourceModules.unshift(this._codegenSourceModule( - _ngfactoryModuleUrl(moduleUrl), statements, exportedVars)); + const srcModule = this._codegenSourceModule( + srcFileUrl, _ngfactoryModuleUrl(srcFileUrl), statements, exportedVars); + outputSourceModules.unshift(srcModule); } return outputSourceModules; }); @@ -209,18 +254,21 @@ export class OfflineCompiler { return viewResult.viewFactoryVar; } - private _codgenStyles(stylesCompileResult: CompiledStylesheet, fileSuffix: string): SourceModule { + private _codgenStyles( + fileUrl: string, stylesCompileResult: CompiledStylesheet, fileSuffix: string): SourceModule { _resolveStyleStatements(stylesCompileResult, fileSuffix); return this._codegenSourceModule( - _stylesModuleUrl( - stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix), + fileUrl, _stylesModuleUrl( + stylesCompileResult.meta.moduleUrl, stylesCompileResult.isShimmed, fileSuffix), stylesCompileResult.statements, [stylesCompileResult.stylesVar]); } private _codegenSourceModule( - moduleUrl: string, statements: o.Statement[], exportedVars: string[]): SourceModule { + fileUrl: string, moduleUrl: string, statements: o.Statement[], + exportedVars: string[]): SourceModule { return new SourceModule( - moduleUrl, this._outputEmitter.emitStatements(moduleUrl, statements, exportedVars)); + fileUrl, moduleUrl, + this._outputEmitter.emitStatements(moduleUrl, statements, exportedVars)); } } diff --git a/scripts/ci-lite/offline_compiler_test.sh b/scripts/ci-lite/offline_compiler_test.sh index 239f548902..3ae0414de7 100755 --- a/scripts/ci-lite/offline_compiler_test.sh +++ b/scripts/ci-lite/offline_compiler_test.sh @@ -9,8 +9,8 @@ LINKABLE_PKGS=( PKGS=( reflect-metadata@0.1.8 typescript@2.0.2 - zone.js@0.6.21 - rxjs@5.0.0-beta.11 + zone.js@0.6.25 + rxjs@5.0.0-beta.12 @types/{node@6.0.38,jasmine@2.2.33} jasmine@2.4.1 webpack@2.1.0-beta.21 @@ -35,12 +35,20 @@ cp -v package.json $TMP npm install ${LINKABLE_PKGS[*]} ./node_modules/.bin/tsc --version + # Compile the compiler-cli third_party simulation. + # Use ngc-wrapped directly so we don't produce *.ngfactory.ts files! + # Compile the compiler-cli integration tests # TODO(vicb): restore the test for .xtb - #./node_modules/.bin/ngc --i18nFile=src/messages.fi.xtb --locale=fi --i18nFormat=xtb - ./node_modules/.bin/ngc --i18nFile=src/messages.fi.xlf --locale=fi --i18nFormat=xlf - ./node_modules/.bin/ng-xi18n --i18nFormat=xlf - ./node_modules/.bin/ng-xi18n --i18nFormat=xmb + #./node_modules/.bin/ngc -p tsconfig-build.json --i18nFile=src/messages.fi.xtb --locale=fi --i18nFormat=xtb + + # Generate the metadata for the third-party modules + node ./node_modules/@angular/tsc-wrapped/src/main -p third_party_src/tsconfig-build.json + + ./node_modules/.bin/ngc -p tsconfig-build.json --i18nFile=src/messages.fi.xlf --locale=fi --i18nFormat=xlf + + ./node_modules/.bin/ng-xi18n -p tsconfig-build.json --i18nFormat=xlf + ./node_modules/.bin/ng-xi18n -p tsconfig-build.json --i18nFormat=xmb ./node_modules/.bin/jasmine init # Run compiler-cli integration tests in node @@ -48,6 +56,6 @@ cp -v package.json $TMP ./node_modules/.bin/jasmine ./all_spec.js # Compile again with a differently named tsconfig file - mv tsconfig.json othername.json + mv tsconfig-build.json othername.json ./node_modules/.bin/ngc -p othername.json )