diff --git a/modules/@angular/compiler-cli/index.ts b/modules/@angular/compiler-cli/index.ts index 9a6b5322ed..ecfd8b7272 100644 --- a/modules/@angular/compiler-cli/index.ts +++ b/modules/@angular/compiler-cli/index.ts @@ -7,6 +7,7 @@ */ export {CodeGenerator} from './src/codegen'; +export {Extractor} from './src/extractor'; export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host'; export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector'; diff --git a/modules/@angular/compiler-cli/src/extract_i18n.ts b/modules/@angular/compiler-cli/src/extract_i18n.ts index 91b7c663e1..e2e976d147 100644 --- a/modules/@angular/compiler-cli/src/extract_i18n.ts +++ b/modules/@angular/compiler-cli/src/extract_i18n.ts @@ -17,20 +17,27 @@ 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 * as tsc from '@angular/tsc-wrapped'; -import {Console} from './private_import_core'; -import {ReflectorHost, ReflectorHostContext} from './reflector_host'; -import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities'; -import {StaticReflector, StaticSymbol} from './static_reflector'; + +import {Extractor} from './extractor'; function extract( ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions, program: ts.Program, host: ts.CompilerHost) { - const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser()); - const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host, htmlParser); + 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 bundlePromise: Promise = extractor.extract(); return (bundlePromise).then(messageBundle => { @@ -46,6 +53,7 @@ function extract( case 'xliff': case 'xlf': default: + const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser()); ext = 'xlf'; serializer = new compiler.Xliff(htmlParser, compiler.DEFAULT_INTERPOLATION_CONFIG); break; @@ -56,139 +64,6 @@ function extract( }); } -const GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/; - -export class Extractor { - constructor( - private program: ts.Program, public host: ts.CompilerHost, - private staticReflector: StaticReflector, private messageBundle: compiler.MessageBundle, - private reflectorHost: ReflectorHost, - private metadataResolver: compiler.CompileMetadataResolver, - private directiveNormalizer: compiler.DirectiveNormalizer) {} - - private readFileMetadata(absSourcePath: string): FileMetadata { - const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath); - const result: FileMetadata = {components: [], 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 Component) { - result.components.push(staticType); - } - }); - } - return result; - } - - extract(): Promise { - const filePaths = - this.program.getSourceFiles().map(sf => sf.fileName).filter(f => !GENERATED_FILES.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[] = []; - - 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; - } - - static create( - options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program, - compilerHost: ts.CompilerHost, htmlParser: compiler.I18NHtmlParser, - reflectorHostContext?: ReflectorHostContext): Extractor { - const resourceLoader: compiler.ResourceLoader = { - get: (s: string) => { - if (!compilerHost.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(compilerHost.readFile(s)); - } - }; - - const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver(); - const reflectorHost = new ReflectorHost(program, compilerHost, options, reflectorHostContext); - const staticReflector = new StaticReflector(reflectorHost); - 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, staticReflector); - - // TODO(vicb): implicit tags & attributes - let messageBundle = new compiler.MessageBundle(htmlParser, [], {}); - - return new Extractor( - program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver, normalizer); - } -} - -interface FileMetadata { - fileUrl: string; - components: StaticSymbol[]; - ngModules: StaticSymbol[]; -} - // Entry point if (require.main === module) { const args = require('minimist')(process.argv.slice(2)); diff --git a/modules/@angular/compiler-cli/src/extractor.ts b/modules/@angular/compiler-cli/src/extractor.ts new file mode 100644 index 0000000000..9a336a9717 --- /dev/null +++ b/modules/@angular/compiler-cli/src/extractor.ts @@ -0,0 +1,157 @@ +/** + * @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 + * + * TODO(vicb): factorize code with the CodeGenerator + */ +// Must be imported first, because angular2 decorators throws on load. +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( + private options: tsc.AngularCompilerOptions, private program: ts.Program, + public host: ts.CompilerHost, private staticReflector: StaticReflector, + private messageBundle: compiler.MessageBundle, private reflectorHost: ReflectorHost, + private metadataResolver: compiler.CompileMetadataResolver, + private directiveNormalizer: compiler.DirectiveNormalizer) {} + + private readFileMetadata(absSourcePath: string): FileMetadata { + const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath); + const result: FileMetadata = {components: [], 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 Component) { + result.components.push(staticType); + } + }); + } + return result; + } + + 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[] = []; + + 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; + } + + static create( + options: tsc.AngularCompilerOptions, translationsFormat: string, program: ts.Program, + compilerHost: ts.CompilerHost, resourceLoader: compiler.ResourceLoader, + reflectorHost?: ReflectorHost): Extractor { + const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser()); + + const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver(); + if (!reflectorHost) reflectorHost = new ReflectorHost(program, compilerHost, options); + const staticReflector = new StaticReflector(reflectorHost); + 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, staticReflector); + + // TODO(vicb): implicit tags & attributes + let messageBundle = new compiler.MessageBundle(htmlParser, [], {}); + + return new Extractor( + options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver, + normalizer); + } +} + +interface FileMetadata { + fileUrl: string; + components: StaticSymbol[]; + ngModules: StaticSymbol[]; +}