diff --git a/modules/@angular/compiler-cli/src/codegen.ts b/modules/@angular/compiler-cli/src/codegen.ts index 6e552fe617..8e9dfff358 100644 --- a/modules/@angular/compiler-cli/src/codegen.ts +++ b/modules/@angular/compiler-cli/src/codegen.ts @@ -36,8 +36,8 @@ const PREAMBLE = `/** export class CodeGenerator { constructor( private options: AngularCompilerOptions, private program: ts.Program, - public host: ts.CompilerHost, private staticReflector: compiler.StaticReflector, - private compiler: compiler.AotCompiler, private ngCompilerHost: CompilerHost) {} + public host: ts.CompilerHost, private compiler: compiler.AotCompiler, + private ngCompilerHost: CompilerHost) {} // Write codegen in a directory structure matching the sources. private calculateEmitPath(filePath: string): string { @@ -96,7 +96,7 @@ export class CodeGenerator { } transContent = readFileSync(transFile, 'utf8'); } - const {compiler: aotCompiler, reflector} = compiler.createAotCompiler(ngCompilerHost, { + const {compiler: aotCompiler} = compiler.createAotCompiler(ngCompilerHost, { debug: options.debug === true, translations: transContent, i18nFormat: cliOptions.i18nFormat, @@ -104,18 +104,10 @@ export class CodeGenerator { excludeFilePattern: options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES }); - return new CodeGenerator( - options, program, tsCompilerHost, reflector, aotCompiler, ngCompilerHost); + return new CodeGenerator(options, program, tsCompilerHost, aotCompiler, ngCompilerHost); } } -export function extractProgramSymbols( - program: ts.Program, staticReflector: compiler.StaticReflector, compilerHost: CompilerHost, - options: AngularCompilerOptions): compiler.StaticSymbol[] { - return compiler.extractProgramSymbols( - staticReflector, - program.getSourceFiles().map(sf => compilerHost.getCanonicalFileName(sf.fileName)), { - excludeFilePattern: options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : - GENERATED_FILES - }); +export function excludeFilePattern(options: AngularCompilerOptions): RegExp { + return options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES; } diff --git a/modules/@angular/compiler-cli/src/extract_i18n.ts b/modules/@angular/compiler-cli/src/extract_i18n.ts index 4efcf1cbea..16de0992bf 100644 --- a/modules/@angular/compiler-cli/src/extract_i18n.ts +++ b/modules/@angular/compiler-cli/src/extract_i18n.ts @@ -24,17 +24,7 @@ import {Extractor} from './extractor'; function extract( ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions, program: ts.Program, host: ts.CompilerHost) { - const resourceLoader: compiler.ResourceLoader = { - get: (s: string) => { - if (!host.fileExists(s)) { - // TODO: We should really have a test for error cases like this! - throw new Error(`Compilation failed. Resource file not found: ${s}`); - } - return Promise.resolve(host.readFile(s)); - } - }; - const extractor = - Extractor.create(ngOptions, cliOptions.i18nFormat, program, host, resourceLoader); + const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host); const bundlePromise: Promise = extractor.extract(); diff --git a/modules/@angular/compiler-cli/src/extractor.ts b/modules/@angular/compiler-cli/src/extractor.ts index 38913416be..33d1f89d27 100644 --- a/modules/@angular/compiler-cli/src/extractor.ts +++ b/modules/@angular/compiler-cli/src/extractor.ts @@ -14,84 +14,28 @@ import 'reflect-metadata'; import * as compiler from '@angular/compiler'; -import {ViewEncapsulation} from '@angular/core'; import * as tsc from '@angular/tsc-wrapped'; import * as ts from 'typescript'; -import {extractProgramSymbols} from './codegen'; +import {excludeFilePattern} from './codegen'; import {CompilerHost} from './compiler_host'; export class Extractor { constructor( - private options: tsc.AngularCompilerOptions, private program: ts.Program, - public host: ts.CompilerHost, private staticReflector: compiler.StaticReflector, - private messageBundle: compiler.MessageBundle, private compilerHost: CompilerHost, - private metadataResolver: compiler.CompileMetadataResolver) {} + private ngExtractor: compiler.Extractor, private ngCompilerHost: CompilerHost, + private program: ts.Program) {} extract(): Promise { - const programSymbols: compiler.StaticSymbol[] = - extractProgramSymbols(this.program, this.staticReflector, this.compilerHost, this.options); - - const {ngModules, files} = - compiler.analyzeAndValidateNgModules(programSymbols, {}, this.metadataResolver); - return compiler.loadNgModuleDirectives(ngModules).then(() => { - const errors: compiler.ParseError[] = []; - - files.forEach(file => { - const compMetas: compiler.CompileDirectiveMetadata[] = []; - file.directives.forEach(directiveType => { - const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType); - if (dirMeta && dirMeta.isComponent) { - compMetas.push(dirMeta); - } - }); - 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)); - }); - }); - - if (errors.length) { - throw new Error(errors.map(e => e.toString()).join('\n')); - } - - return this.messageBundle; - }); + return this.ngExtractor.extract(this.program.getSourceFiles().map( + sf => this.ngCompilerHost.getCanonicalFileName(sf.fileName))) } static create( options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program, - tsCompilerHost: ts.CompilerHost, resourceLoader: compiler.ResourceLoader, - ngCompilerHost?: CompilerHost): Extractor { - const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser()); - - const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver(); + tsCompilerHost: ts.CompilerHost, ngCompilerHost?: CompilerHost): Extractor { if (!ngCompilerHost) ngCompilerHost = new CompilerHost(program, tsCompilerHost, options); - const staticReflector = new compiler.StaticReflector(ngCompilerHost); - compiler.StaticAndDynamicReflectionCapabilities.install(staticReflector); - - const config = new compiler.CompilerConfig({ - genDebugInfo: options.debug === true, - defaultEncapsulation: ViewEncapsulation.Emulated, - logBindingUpdate: false, - useJit: false - }); - - const normalizer = - new compiler.DirectiveNormalizer(resourceLoader, urlResolver, htmlParser, config); - const elementSchemaRegistry = new compiler.DomElementSchemaRegistry(); - const resolver = new compiler.CompileMetadataResolver( - new compiler.NgModuleResolver(staticReflector), - new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector), - elementSchemaRegistry, normalizer, staticReflector); - - // TODO(vicb): implicit tags & attributes - const messageBundle = new compiler.MessageBundle(htmlParser, [], {}); - - return new Extractor( - options, program, tsCompilerHost, staticReflector, messageBundle, ngCompilerHost, resolver); + const {extractor: ngExtractor} = compiler.Extractor.create( + ngCompilerHost, {excludeFilePattern: excludeFilePattern(options)}); + return new Extractor(ngExtractor, ngCompilerHost, program); } -} \ No newline at end of file +} diff --git a/modules/@angular/compiler/src/aot/compiler_host.ts b/modules/@angular/compiler/src/aot/compiler_host.ts index ef5a37443c..2d108578ef 100644 --- a/modules/@angular/compiler/src/aot/compiler_host.ts +++ b/modules/@angular/compiler/src/aot/compiler_host.ts @@ -6,37 +6,17 @@ * found in the LICENSE file at https://angular.io/license */ +import {ImportResolver} from '../output/path_util'; + +import {StaticReflectorHost} from './static_reflector'; import {StaticSymbol} from './static_symbol'; + /** * The host of the AotCompiler disconnects the implementation from TypeScript / other language * services and from underlying file systems. */ -export interface AotCompilerHost { - /** - * 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; - - /** - * 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`. - */ - fileNameToModuleName(importedFile: string, containingFile: string): string; - +export interface AotCompilerHost extends StaticReflectorHost, ImportResolver { /** * Loads a resource (e.g. html / css) */ diff --git a/modules/@angular/compiler/src/aot/static_reflector.ts b/modules/@angular/compiler/src/aot/static_reflector.ts index 460769dfb7..f07aa7e8d3 100644 --- a/modules/@angular/compiler/src/aot/static_reflector.ts +++ b/modules/@angular/compiler/src/aot/static_reflector.ts @@ -8,7 +8,6 @@ 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 {AotCompilerHost} from './compiler_host'; import {StaticSymbol} from './static_symbol'; const SUPPORTED_SCHEMA_VERSION = 2; @@ -21,6 +20,30 @@ const ANGULAR_IMPORT_LOCATIONS = { provider: '@angular/core/src/di/provider' }; +/** + * 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 static reflector implements enough of the Reflector API that is necessary to compile * templates statically. @@ -35,7 +58,7 @@ export class StaticReflector implements ReflectorReader { private conversionMap = new Map any>(); private opaqueToken: StaticSymbol; - constructor(private host: AotCompilerHost) { this.initializeConversionMap(); } + constructor(private host: StaticReflectorHost) { this.initializeConversionMap(); } importUri(typeOrFunc: StaticSymbol): string { const staticSymbol = this.findDeclaration(typeOrFunc.filePath, typeOrFunc.name, ''); diff --git a/modules/@angular/compiler/src/i18n/extractor.ts b/modules/@angular/compiler/src/i18n/extractor.ts new file mode 100644 index 0000000000..b1bad2f136 --- /dev/null +++ b/modules/@angular/compiler/src/i18n/extractor.ts @@ -0,0 +1,117 @@ +/** + * @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 + */ + + +/** + * Extract i18n messages from source code + */ +import {ViewEncapsulation} from '@angular/core'; + +import {analyzeAndValidateNgModules, extractProgramSymbols, loadNgModuleDirectives} from '../aot/compiler'; +import {StaticAndDynamicReflectionCapabilities} from '../aot/static_reflection_capabilities'; +import {StaticReflector, StaticReflectorHost} from '../aot/static_reflector'; +import {CompileDirectiveMetadata} from '../compile_metadata'; +import {CompilerConfig} from '../config'; +import {DirectiveNormalizer} from '../directive_normalizer'; +import {DirectiveResolver} from '../directive_resolver'; +import {CompileMetadataResolver} from '../metadata_resolver'; +import {HtmlParser} from '../ml_parser/html_parser'; +import {InterpolationConfig} from '../ml_parser/interpolation_config'; +import {NgModuleResolver} from '../ng_module_resolver'; +import {ParseError} from '../parse_util'; +import {PipeResolver} from '../pipe_resolver'; +import {Console} from '../private_import_core'; +import {DomElementSchemaRegistry} from '../schema/dom_element_schema_registry'; +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 { + /** + * Loads a resource (e.g. html / css) + */ + loadResource(path: string): Promise; +} + +export class Extractor { + constructor( + private options: ExtractorOptions, public host: ExtractorHost, + private staticReflector: StaticReflector, private messageBundle: MessageBundle, + private metadataResolver: CompileMetadataResolver) {} + + extract(rootFiles: string[]): Promise { + const programSymbols = extractProgramSymbols(this.staticReflector, rootFiles, this.options); + const {ngModuleByPipeOrDirective, files, ngModules} = + analyzeAndValidateNgModules(programSymbols, this.options, this.metadataResolver); + return loadNgModuleDirectives(ngModules).then(() => { + const errors: ParseError[] = []; + + files.forEach(file => { + const compMetas: CompileDirectiveMetadata[] = []; + file.directives.forEach(directiveType => { + const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType); + if (dirMeta && dirMeta.isComponent) { + compMetas.push(dirMeta); + } + }); + compMetas.forEach(compMeta => { + const html = compMeta.template.template; + const interpolationConfig = + InterpolationConfig.fromArray(compMeta.template.interpolation); + errors.push( + ...this.messageBundle.updateFromTemplate(html, file.srcUrl, interpolationConfig)); + }); + }); + + if (errors.length) { + throw new Error(errors.map(e => e.toString()).join('\n')); + } + + return this.messageBundle; + }); + } + + static create(host: ExtractorHost, options: ExtractorOptions): + {extractor: Extractor, staticReflector: StaticReflector} { + const htmlParser = new I18NHtmlParser(new HtmlParser()); + + const urlResolver = createOfflineCompileUrlResolver(); + const staticReflector = new StaticReflector(host); + StaticAndDynamicReflectionCapabilities.install(staticReflector); + + const config = new CompilerConfig({ + genDebugInfo: false, + defaultEncapsulation: ViewEncapsulation.Emulated, + logBindingUpdate: false, + useJit: false + }); + + const normalizer = new DirectiveNormalizer( + {get: (url: string) => host.loadResource(url)}, urlResolver, htmlParser, config); + const elementSchemaRegistry = new DomElementSchemaRegistry(); + const resolver = new CompileMetadataResolver( + new NgModuleResolver(staticReflector), new DirectiveResolver(staticReflector), + new PipeResolver(staticReflector), elementSchemaRegistry, normalizer, staticReflector); + + // TODO(vicb): implicit tags & attributes + const messageBundle = new MessageBundle(htmlParser, [], {}); + + const extractor = new Extractor(options, host, staticReflector, messageBundle, resolver); + return {extractor, staticReflector}; + } +} diff --git a/modules/@angular/compiler/src/i18n/index.ts b/modules/@angular/compiler/src/i18n/index.ts index 67b61a891f..aca404c024 100644 --- a/modules/@angular/compiler/src/i18n/index.ts +++ b/modules/@angular/compiler/src/i18n/index.ts @@ -12,3 +12,4 @@ export {Serializer} from './serializers/serializer'; export {Xliff} from './serializers/xliff'; export {Xmb} from './serializers/xmb'; export {Xtb} from './serializers/xtb'; +export * from './extractor'; \ No newline at end of file diff --git a/modules/@angular/compiler/src/output/path_util.ts b/modules/@angular/compiler/src/output/path_util.ts index ed5a570f80..bf8615cf68 100644 --- a/modules/@angular/compiler/src/output/path_util.ts +++ b/modules/@angular/compiler/src/output/path_util.ts @@ -10,5 +10,9 @@ * Interface that defines how import statements should be generated. */ 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; } diff --git a/modules/@angular/compiler/test/aot/static_reflector_spec.ts b/modules/@angular/compiler/test/aot/static_reflector_spec.ts index d65275377a..91dd008e18 100644 --- a/modules/@angular/compiler/test/aot/static_reflector_spec.ts +++ b/modules/@angular/compiler/test/aot/static_reflector_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AotCompilerHost, StaticReflector, StaticSymbol} from '@angular/compiler'; +import {StaticReflector, StaticReflectorHost, StaticSymbol} 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'; @@ -18,11 +18,11 @@ const TS_EXT = /(^.|(?!\.d)..)\.ts$/; describe('StaticReflector', () => { const noContext = new StaticSymbol('', ''); - let host: AotCompilerHost; + let host: StaticReflectorHost; let reflector: StaticReflector; beforeEach(() => { - host = new MockAotCompilerHost(); + host = new MockStaticReflectorHost(); reflector = new StaticReflector(host); }); @@ -519,17 +519,9 @@ describe('StaticReflector', () => { }); -class MockAotCompilerHost implements AotCompilerHost { +class MockStaticReflectorHost implements StaticReflectorHost { private collector = new MetadataCollector(); - constructor() {} - - loadResource(filePath: string): Promise { throw new Error('Should not be called!'); } - - fileNameToModuleName(importedFilePath: string, containingFilePath: string): string { - throw new Error('Should not be called!'); - } - // In tests, assume that symbols are not re-exported moduleNameToFileName(modulePath: string, containingFile?: string): string { function splitPath(path: string): string[] { return path.split(/\/|\\/g); }