refactor(compiler): Reintroduce `ReflectorHost` and move `Extractor` into `@angular/compiler`
This commit is contained in:
parent
3c06a5dc25
commit
2a5bd2f345
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<compiler.MessageBundle> = extractor.extract();
|
||||
|
||||
|
|
|
@ -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<compiler.MessageBundle> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
*/
|
||||
|
|
|
@ -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<StaticSymbol, (context: StaticSymbol, args: any[]) => 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, '');
|
||||
|
|
|
@ -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<string>;
|
||||
}
|
||||
|
||||
export class Extractor {
|
||||
constructor(
|
||||
private options: ExtractorOptions, public host: ExtractorHost,
|
||||
private staticReflector: StaticReflector, private messageBundle: MessageBundle,
|
||||
private metadataResolver: CompileMetadataResolver) {}
|
||||
|
||||
extract(rootFiles: string[]): Promise<MessageBundle> {
|
||||
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};
|
||||
}
|
||||
}
|
|
@ -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';
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<string> { 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); }
|
||||
|
|
Loading…
Reference in New Issue