fix(compiler): walk third party modules (#12453)

fixes #11889
fixes #12428
This commit is contained in:
Victor Berchet
2016-10-24 13:28:23 -07:00
committed by Igor Minar
parent bfc97ff2cd
commit a838aba756
24 changed files with 463 additions and 239 deletions

View File

@ -40,50 +40,47 @@ export class CodeGeneratorModuleCollector {
private staticReflector: StaticReflector, private reflectorHost: StaticReflectorHost,
private program: ts.Program, private options: AngularCompilerOptions) {}
getModuleSymbols(program: ts.Program): {fileMetas: FileMetadata[], ngModules: StaticSymbol[]} {
getModuleSymbols(): StaticSymbol[] {
// Compare with false since the default should be true
const skipFileNames = (this.options.generateCodeForLibraries === false) ?
GENERATED_OR_DTS_FILES :
GENERATED_FILES;
let filePaths = this.program.getSourceFiles()
.filter(sf => !skipFileNames.test(sf.fileName))
.map(sf => this.reflectorHost.getCanonicalFileName(sf.fileName));
const fileMetas = filePaths.map((filePath) => this.readFileMetadata(filePath));
const ngModules = fileMetas.reduce((ngModules, fileMeta) => {
ngModules.push(...fileMeta.ngModules);
return ngModules;
}, <StaticSymbol[]>[]);
return {fileMetas, ngModules};
}
const skipFileNames =
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
private readFileMetadata(absSourcePath: string): FileMetadata {
const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath);
const result: FileMetadata = {directives: [], 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 Directive) {
result.directives.push(staticType);
}
});
}
return result;
const ngModules: StaticSymbol[] = [];
this.program.getSourceFiles()
.filter(sourceFile => !skipFileNames.test(sourceFile.fileName))
.forEach(sourceFile => {
const absSrcPath = this.reflectorHost.getCanonicalFileName(sourceFile.fileName);
const moduleMetadata = this.staticReflector.getModuleMetadata(absSrcPath);
if (!moduleMetadata) {
console.log(`WARNING: no metadata found for ${absSrcPath}`);
return;
}
const metadata = moduleMetadata['metadata'];
if (!metadata) {
return;
}
for (const symbol of Object.keys(metadata)) {
if (metadata[symbol] && metadata[symbol].__symbolic == 'error') {
// Ignore symbols that are only included to record error information.
continue;
}
const staticType = this.reflectorHost.findDeclaration(absSrcPath, symbol, absSrcPath);
const annotations = this.staticReflector.annotations(staticType);
annotations.some((annotation) => {
if (annotation instanceof NgModule) {
ngModules.push(staticType);
return true;
}
});
}
});
return ngModules;
}
}
@ -101,7 +98,7 @@ export class CodeGenerator {
// Write codegen in a directory structure matching the sources.
private calculateEmitPath(filePath: string): string {
let root = this.options.basePath;
for (let eachRootDir of this.options.rootDirs || []) {
for (const eachRootDir of this.options.rootDirs || []) {
if (this.options.trace) {
console.log(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
}
@ -111,31 +108,27 @@ export class CodeGenerator {
}
// transplant the codegen path to be inside the `genDir`
var relativePath: string = path.relative(root, filePath);
let relativePath: string = path.relative(root, filePath);
while (relativePath.startsWith('..' + path.sep)) {
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
// into `genDir`.
relativePath = relativePath.substr(3);
}
return path.join(this.options.genDir, relativePath);
}
codegen(): Promise<any> {
const {fileMetas, ngModules} = this.moduleCollector.getModuleSymbols(this.program);
const analyzedNgModules = this.compiler.analyzeModules(ngModules);
return Promise.all(fileMetas.map(
(fileMeta) =>
this.compiler
.compile(
fileMeta.fileUrl, analyzedNgModules, fileMeta.directives, fileMeta.ngModules)
.then((generatedModules) => {
generatedModules.forEach((generatedModule) => {
const sourceFile = this.program.getSourceFile(fileMeta.fileUrl);
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
this.host.writeFile(
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
});
})));
const ngModules = this.moduleCollector.getModuleSymbols();
return this.compiler.compileModules(ngModules).then(generatedModules => {
generatedModules.forEach(generatedModule => {
const sourceFile = this.program.getSourceFile(generatedModule.fileUrl);
const emitPath = this.calculateEmitPath(generatedModule.moduleUrl);
this.host.writeFile(
emitPath, PREAMBLE + generatedModule.source, false, () => {}, [sourceFile]);
});
});
}
static create(
@ -201,9 +194,3 @@ export class CodeGenerator {
options, program, compilerHost, staticReflector, offlineCompiler, reflectorHost);
}
}
export interface FileMetadata {
fileUrl: string;
directives: StaticSymbol[];
ngModules: StaticSymbol[];
}

View File

@ -10,8 +10,6 @@
/**
* 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';

View File

@ -18,16 +18,13 @@ 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(
@ -37,82 +34,82 @@ export class Extractor {
private metadataResolver: compiler.CompileMetadataResolver,
private directiveNormalizer: compiler.DirectiveNormalizer) {}
private readFileMetadata(absSourcePath: string): FileMetadata {
private readModuleSymbols(absSourcePath: string): StaticSymbol[] {
const moduleMetadata = this.staticReflector.getModuleMetadata(absSourcePath);
const result: FileMetadata = {components: [], ngModules: [], fileUrl: absSourcePath};
const modSymbols: StaticSymbol[] = [];
if (!moduleMetadata) {
console.log(`WARNING: no metadata found for ${absSourcePath}`);
return result;
return modSymbols;
}
const metadata = moduleMetadata['metadata'];
const symbols = metadata && Object.keys(metadata);
if (!symbols || !symbols.length) {
return result;
return modSymbols;
}
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);
annotations.some(a => {
if (a instanceof NgModule) {
modSymbols.push(staticType);
return true;
}
});
}
return result;
return modSymbols;
}
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[] = [];
this.program.getSourceFiles().map(sf => sf.fileName).filter(f => !GENERATED_FILES.test(f));
const ngModules: StaticSymbol[] = [];
filePaths.forEach((filePath) => ngModules.push(...this.readModuleSymbols(filePath)));
const files = compiler.analyzeNgModules(ngModules, this.metadataResolver).files;
const errors: compiler.ParseError[] = [];
const filePromises: Promise<any>[] = [];
files.forEach(file => {
const cmpPromises: Promise<compiler.CompileDirectiveMetadata>[] = [];
file.directives.forEach(directiveType => {
const dirMeta = this.metadataResolver.getDirectiveMetadata(directiveType);
if (dirMeta.isComponent) {
cmpPromises.push(this.directiveNormalizer.normalizeDirective(dirMeta).asyncResult);
}
});
if (cmpPromises.length) {
const done =
Promise.all(cmpPromises).then((compMetas: compiler.CompileDirectiveMetadata[]) => {
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));
});
});
filePromises.push(done);
}
});
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;
return Promise.all(filePromises).then(_ => this.messageBundle);
}
static create(
@ -148,10 +145,4 @@ export class Extractor {
options, program, compilerHost, staticReflector, messageBundle, reflectorHost, resolver,
normalizer);
}
}
interface FileMetadata {
fileUrl: string;
components: StaticSymbol[];
ngModules: StaticSymbol[];
}
}

View File

@ -250,6 +250,12 @@ export class ReflectorHost implements StaticReflectorHost, ImportGenerator {
} else {
const sf = this.program.getSourceFile(filePath);
if (!sf) {
if (this.context.fileExists(filePath)) {
const sourceText = this.context.readFile(filePath);
return this.metadataCollector.getMetadata(
ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true));
}
throw new Error(`Source file ${filePath} not present in program.`);
}
return this.metadataCollector.getMetadata(sf);