refactor(i18n): extract Extractor from extract_i18n (#12417)
I put an extractor into your extract so you can extract while you extract. This allows integrators to call Extractor as a library. Also refactors Extractor a bit so that callers need fewer arguments or arguments that are at the right semantic level. The refactoring causes no function change.
This commit is contained in:
parent
57051f01ce
commit
bfc97ff2cd
@ -7,6 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export {CodeGenerator} from './src/codegen';
|
export {CodeGenerator} from './src/codegen';
|
||||||
|
export {Extractor} from './src/extractor';
|
||||||
export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host';
|
export {NodeReflectorHostContext, ReflectorHost, ReflectorHostContext} from './src/reflector_host';
|
||||||
export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector';
|
export {StaticReflector, StaticReflectorHost, StaticSymbol} from './src/static_reflector';
|
||||||
|
|
||||||
|
@ -17,20 +17,27 @@
|
|||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
|
|
||||||
import * as compiler from '@angular/compiler';
|
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 path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import * as tsc from '@angular/tsc-wrapped';
|
|
||||||
import {Console} from './private_import_core';
|
import {Extractor} from './extractor';
|
||||||
import {ReflectorHost, ReflectorHostContext} from './reflector_host';
|
|
||||||
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
|
||||||
import {StaticReflector, StaticSymbol} from './static_reflector';
|
|
||||||
|
|
||||||
function extract(
|
function extract(
|
||||||
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
|
ngOptions: tsc.AngularCompilerOptions, cliOptions: tsc.I18nExtractionCliOptions,
|
||||||
program: ts.Program, host: ts.CompilerHost) {
|
program: ts.Program, host: ts.CompilerHost) {
|
||||||
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
|
const resourceLoader: compiler.ResourceLoader = {
|
||||||
const extractor = Extractor.create(ngOptions, cliOptions.i18nFormat, program, host, htmlParser);
|
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<compiler.MessageBundle> = extractor.extract();
|
const bundlePromise: Promise<compiler.MessageBundle> = extractor.extract();
|
||||||
|
|
||||||
return (bundlePromise).then(messageBundle => {
|
return (bundlePromise).then(messageBundle => {
|
||||||
@ -46,6 +53,7 @@ function extract(
|
|||||||
case 'xliff':
|
case 'xliff':
|
||||||
case 'xlf':
|
case 'xlf':
|
||||||
default:
|
default:
|
||||||
|
const htmlParser = new compiler.I18NHtmlParser(new compiler.HtmlParser());
|
||||||
ext = 'xlf';
|
ext = 'xlf';
|
||||||
serializer = new compiler.Xliff(htmlParser, compiler.DEFAULT_INTERPOLATION_CONFIG);
|
serializer = new compiler.Xliff(htmlParser, compiler.DEFAULT_INTERPOLATION_CONFIG);
|
||||||
break;
|
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<compiler.MessageBundle> {
|
|
||||||
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;
|
|
||||||
}, <StaticSymbol[]>[]);
|
|
||||||
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(<any>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
|
// Entry point
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
const args = require('minimist')(process.argv.slice(2));
|
const args = require('minimist')(process.argv.slice(2));
|
||||||
|
157
modules/@angular/compiler-cli/src/extractor.ts
Normal file
157
modules/@angular/compiler-cli/src/extractor.ts
Normal file
@ -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<compiler.MessageBundle> {
|
||||||
|
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;
|
||||||
|
}, <StaticSymbol[]>[]);
|
||||||
|
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(<any>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[];
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user