2016-06-01 17:58:11 -04:00
|
|
|
#!/usr/bin/env node
|
2016-06-23 12:47:54 -04:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
|
2016-06-01 17:58:11 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Extract i18n messages from source code
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Must be imported first, because angular2 decorators throws on load.
|
|
|
|
import 'reflect-metadata';
|
|
|
|
|
|
|
|
import * as ts from 'typescript';
|
|
|
|
import * as tsc from '@angular/tsc-wrapped';
|
|
|
|
import * as path from 'path';
|
|
|
|
import * as compiler from '@angular/compiler';
|
2016-07-07 17:42:46 -04:00
|
|
|
import {ViewEncapsulation} from '@angular/core';
|
2016-06-01 17:58:11 -04:00
|
|
|
|
|
|
|
import {StaticReflector} from './static_reflector';
|
2016-06-28 12:54:42 -04:00
|
|
|
import {CompileMetadataResolver, HtmlParser, DirectiveNormalizer, Lexer, Parser, DomElementSchemaRegistry, TypeScriptEmitter, MessageExtractor, removeDuplicates, ExtractionResult, Message, ParseError, serializeXmb,} from './compiler_private';
|
2016-07-28 13:39:10 -04:00
|
|
|
import {Console} from './core_private';
|
2016-06-01 17:58:11 -04:00
|
|
|
|
2016-06-09 17:51:53 -04:00
|
|
|
import {ReflectorHost} from './reflector_host';
|
2016-06-01 17:58:11 -04:00
|
|
|
import {StaticAndDynamicReflectionCapabilities} from './static_reflection_capabilities';
|
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
function extract(
|
|
|
|
ngOptions: tsc.AngularCompilerOptions, program: ts.Program, host: ts.CompilerHost) {
|
2016-06-01 17:58:11 -04:00
|
|
|
return Extractor.create(ngOptions, program, host).extract();
|
|
|
|
}
|
|
|
|
|
2016-06-14 20:50:23 -04:00
|
|
|
const _dirPaths = new Map<compiler.CompileDirectiveMetadata, string>();
|
|
|
|
|
|
|
|
const _GENERATED_FILES = /\.ngfactory\.ts$|\.css\.ts$|\.css\.shim\.ts$/;
|
2016-06-01 17:58:11 -04:00
|
|
|
|
|
|
|
class Extractor {
|
2016-06-08 19:38:52 -04:00
|
|
|
constructor(
|
2016-06-14 20:50:23 -04:00
|
|
|
private _options: tsc.AngularCompilerOptions, private _program: ts.Program,
|
2016-06-08 19:38:52 -04:00
|
|
|
public host: ts.CompilerHost, private staticReflector: StaticReflector,
|
2016-06-28 12:54:42 -04:00
|
|
|
private _resolver: CompileMetadataResolver, private _normalizer: DirectiveNormalizer,
|
2016-07-07 17:42:46 -04:00
|
|
|
private _reflectorHost: ReflectorHost, private _extractor: MessageExtractor) {}
|
2016-06-08 19:38:52 -04:00
|
|
|
|
2016-06-28 12:54:42 -04:00
|
|
|
private _extractCmpMessages(components: compiler.CompileDirectiveMetadata[]): ExtractionResult {
|
|
|
|
if (!components || !components.length) {
|
2016-06-01 17:58:11 -04:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2016-06-28 12:54:42 -04:00
|
|
|
let messages: Message[] = [];
|
|
|
|
let errors: ParseError[] = [];
|
|
|
|
components.forEach(metadata => {
|
|
|
|
let url = _dirPaths.get(metadata);
|
|
|
|
let result = this._extractor.extract(metadata.template.template, url);
|
|
|
|
errors = errors.concat(result.errors);
|
|
|
|
messages = messages.concat(result.messages);
|
|
|
|
});
|
2016-06-01 17:58:11 -04:00
|
|
|
|
2016-06-28 12:54:42 -04:00
|
|
|
// Extraction Result might contain duplicate messages at this point
|
|
|
|
return new ExtractionResult(messages, errors);
|
2016-06-01 17:58:11 -04:00
|
|
|
}
|
|
|
|
|
2016-06-14 20:50:23 -04:00
|
|
|
private _readComponents(absSourcePath: string): Promise<compiler.CompileDirectiveMetadata>[] {
|
2016-06-01 17:58:11 -04:00
|
|
|
const result: Promise<compiler.CompileDirectiveMetadata>[] = [];
|
|
|
|
const metadata = this.staticReflector.getModuleMetadata(absSourcePath);
|
|
|
|
if (!metadata) {
|
|
|
|
console.log(`WARNING: no metadata found for ${absSourcePath}`);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
const symbols = Object.keys(metadata['metadata']);
|
|
|
|
if (!symbols || !symbols.length) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
for (const symbol of symbols) {
|
2016-06-14 20:50:23 -04:00
|
|
|
const staticType = this._reflectorHost.findDeclaration(absSourcePath, symbol, absSourcePath);
|
2016-06-01 17:58:11 -04:00
|
|
|
let directive: compiler.CompileDirectiveMetadata;
|
2016-07-18 06:50:31 -04:00
|
|
|
directive = this._resolver.getDirectiveMetadata(<any>staticType, false);
|
2016-06-01 17:58:11 -04:00
|
|
|
|
2016-06-14 20:50:23 -04:00
|
|
|
if (directive && directive.isComponent) {
|
2016-06-28 12:54:42 -04:00
|
|
|
let promise = this._normalizer.normalizeDirective(directive).asyncResult;
|
2016-06-14 20:50:23 -04:00
|
|
|
promise.then(md => _dirPaths.set(md, absSourcePath));
|
|
|
|
result.push(promise);
|
2016-06-01 17:58:11 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
extract(): Promise<any> {
|
2016-06-14 20:50:23 -04:00
|
|
|
_dirPaths.clear();
|
|
|
|
|
|
|
|
const promises = this._program.getSourceFiles()
|
2016-06-08 19:38:52 -04:00
|
|
|
.map(sf => sf.fileName)
|
2016-06-14 20:50:23 -04:00
|
|
|
.filter(f => !_GENERATED_FILES.test(f))
|
2016-06-08 19:38:52 -04:00
|
|
|
.map(
|
|
|
|
(absSourcePath: string): Promise<any> =>
|
2016-06-14 20:50:23 -04:00
|
|
|
Promise.all(this._readComponents(absSourcePath))
|
|
|
|
.then(metadatas => this._extractCmpMessages(metadatas))
|
2016-06-08 19:38:52 -04:00
|
|
|
.catch(e => console.error(e.stack)));
|
2016-06-01 17:58:11 -04:00
|
|
|
|
|
|
|
let messages: Message[] = [];
|
|
|
|
let errors: ParseError[] = [];
|
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
return Promise.all(promises).then(extractionResults => {
|
|
|
|
extractionResults.filter(result => !!result).forEach(result => {
|
|
|
|
messages = messages.concat(result.messages);
|
|
|
|
errors = errors.concat(result.errors);
|
|
|
|
});
|
2016-06-01 17:58:11 -04:00
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
if (errors.length) {
|
2016-06-14 20:50:23 -04:00
|
|
|
throw new Error(errors.map(e => e.toString()).join('\n'));
|
2016-06-08 19:38:52 -04:00
|
|
|
}
|
2016-06-01 17:58:11 -04:00
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
messages = removeDuplicates(messages);
|
2016-06-01 17:58:11 -04:00
|
|
|
|
2016-06-14 20:50:23 -04:00
|
|
|
let genPath = path.join(this._options.genDir, 'messages.xmb');
|
2016-06-08 19:38:52 -04:00
|
|
|
let msgBundle = serializeXmb(messages);
|
2016-06-01 17:58:11 -04:00
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
this.host.writeFile(genPath, msgBundle, false);
|
|
|
|
});
|
2016-06-01 17:58:11 -04:00
|
|
|
}
|
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
static create(
|
|
|
|
options: tsc.AngularCompilerOptions, program: ts.Program,
|
|
|
|
compilerHost: ts.CompilerHost): Extractor {
|
2016-06-28 23:07:00 -04:00
|
|
|
const xhr: compiler.XHR = {
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
};
|
2016-06-01 17:58:11 -04:00
|
|
|
const urlResolver: compiler.UrlResolver = compiler.createOfflineCompileUrlResolver();
|
2016-06-09 17:51:53 -04:00
|
|
|
const reflectorHost = new ReflectorHost(program, compilerHost, options);
|
2016-06-01 17:58:11 -04:00
|
|
|
const staticReflector = new StaticReflector(reflectorHost);
|
|
|
|
StaticAndDynamicReflectionCapabilities.install(staticReflector);
|
2016-06-24 17:31:35 -04:00
|
|
|
const htmlParser = new HtmlParser();
|
2016-06-13 13:06:40 -04:00
|
|
|
const config = new compiler.CompilerConfig({
|
|
|
|
genDebugInfo: true,
|
|
|
|
defaultEncapsulation: ViewEncapsulation.Emulated,
|
|
|
|
logBindingUpdate: false,
|
2016-07-08 16:40:54 -04:00
|
|
|
useJit: false
|
2016-06-13 13:06:40 -04:00
|
|
|
});
|
2016-06-01 17:58:11 -04:00
|
|
|
const normalizer = new DirectiveNormalizer(xhr, urlResolver, htmlParser, config);
|
2016-06-24 17:31:35 -04:00
|
|
|
const expressionParser = new Parser(new Lexer());
|
2016-07-28 13:39:10 -04:00
|
|
|
const elementSchemaRegistry = new DomElementSchemaRegistry();
|
|
|
|
const console = new Console();
|
2016-06-01 17:58:11 -04:00
|
|
|
const resolver = new CompileMetadataResolver(
|
2016-07-18 06:50:31 -04:00
|
|
|
new compiler.NgModuleResolver(staticReflector),
|
2016-06-08 19:38:52 -04:00
|
|
|
new compiler.DirectiveResolver(staticReflector), new compiler.PipeResolver(staticReflector),
|
2016-07-28 09:31:26 -04:00
|
|
|
config, console, elementSchemaRegistry, staticReflector);
|
2016-06-01 17:58:11 -04:00
|
|
|
|
|
|
|
// TODO(vicb): handle implicit
|
2016-06-22 20:25:42 -04:00
|
|
|
const extractor = new MessageExtractor(htmlParser, expressionParser, [], {});
|
2016-06-01 17:58:11 -04:00
|
|
|
|
2016-06-08 19:38:52 -04:00
|
|
|
return new Extractor(
|
2016-06-28 12:54:42 -04:00
|
|
|
options, program, compilerHost, staticReflector, resolver, normalizer, reflectorHost,
|
2016-06-08 19:38:52 -04:00
|
|
|
extractor);
|
2016-06-01 17:58:11 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Entry point
|
|
|
|
if (require.main === module) {
|
|
|
|
const args = require('minimist')(process.argv.slice(2));
|
|
|
|
tsc.main(args.p || args.project || '.', args.basePath, extract)
|
2016-06-08 19:38:52 -04:00
|
|
|
.then(exitCode => process.exit(exitCode))
|
|
|
|
.catch(e => {
|
|
|
|
console.error(e.stack);
|
|
|
|
console.error('Compilation failed');
|
|
|
|
process.exit(1);
|
|
|
|
});
|
2016-06-01 17:58:11 -04:00
|
|
|
}
|