fix(ivy): ngcc - fixes to support compiling Material library (#26403)
1) The `DecorationAnalyzer now analyzes all source files, rather than just the entry-point files, which fixes #26183. 2) The `DecoratorAnalyzer` now runs all the `handler.analyze()` calls across the whole entry-point *before* running `handler.compile()`. This ensures that dependencies between the decorated classes *within* an entry-point are known to the handlers when running the compile process. 3) The `Renderer` now does the transformation of the typings (.d.ts) files which allows us to support packages that only have flat format entry-points better, and is faster, since we won't parse `.d.ts` files twice. PR Close #26403
This commit is contained in:
parent
dff10085e8
commit
030d43b9f3
|
@ -13,24 +13,29 @@ import {BaseDefDecoratorHandler, ComponentDecoratorHandler, DirectiveDecoratorHa
|
|||
import {CompileResult, DecoratorHandler} from '../../../ngtsc/transform';
|
||||
|
||||
import {DecoratedClass} from '../host/decorated_class';
|
||||
import {DecoratedFile} from '../host/decorated_file';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
import {isDefined} from '../utils';
|
||||
|
||||
export interface AnalyzedClass<A = any, M = any> extends DecoratedClass {
|
||||
handler: DecoratorHandler<A, M>;
|
||||
analysis: any;
|
||||
diagnostics?: ts.Diagnostic[];
|
||||
compilation: CompileResult[];
|
||||
export interface AnalyzedFile {
|
||||
sourceFile: ts.SourceFile;
|
||||
analyzedClasses: AnalyzedClass[];
|
||||
}
|
||||
|
||||
export interface DecorationAnalysis {
|
||||
analyzedClasses: AnalyzedClass[];
|
||||
export interface AnalyzedClass extends DecoratedClass {
|
||||
diagnostics?: ts.Diagnostic[];
|
||||
handler: DecoratorHandler<any, any>;
|
||||
analysis: any;
|
||||
}
|
||||
|
||||
export interface CompiledClass extends AnalyzedClass { compilation: CompileResult[]; }
|
||||
|
||||
export interface CompiledFile {
|
||||
compiledClasses: CompiledClass[];
|
||||
sourceFile: ts.SourceFile;
|
||||
constantPool: ConstantPool;
|
||||
}
|
||||
|
||||
export type DecorationAnalyses = Map<ts.SourceFile, DecorationAnalysis>;
|
||||
export type DecorationAnalyses = Map<ts.SourceFile, CompiledFile>;
|
||||
export const DecorationAnalyses = Map;
|
||||
|
||||
export interface MatchingHandler<A, M> {
|
||||
|
@ -72,58 +77,59 @@ export class DecorationAnalyzer {
|
|||
* @returns a map of the source files to the analysis for those files.
|
||||
*/
|
||||
analyzeProgram(program: ts.Program): DecorationAnalyses {
|
||||
const analyzedFiles = new DecorationAnalyses();
|
||||
program.getRootFileNames().forEach(fileName => {
|
||||
const entryPoint = program.getSourceFile(fileName) !;
|
||||
const decoratedFiles = this.host.findDecoratedFiles(entryPoint);
|
||||
decoratedFiles.forEach(
|
||||
decoratedFile =>
|
||||
analyzedFiles.set(decoratedFile.sourceFile, this.analyzeFile(decoratedFile)));
|
||||
});
|
||||
return analyzedFiles;
|
||||
const decorationAnalyses = new DecorationAnalyses();
|
||||
const analysedFiles =
|
||||
program.getSourceFiles().map(sourceFile => this.analyzeFile(sourceFile)).filter(isDefined);
|
||||
const compiledFiles = analysedFiles.map(analysedFile => this.compileFile(analysedFile));
|
||||
compiledFiles.forEach(
|
||||
compiledFile => decorationAnalyses.set(compiledFile.sourceFile, compiledFile));
|
||||
return decorationAnalyses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze a decorated file to generate the information about decorated classes that
|
||||
* should be converted to use ivy definitions.
|
||||
* @param file The file to be analysed for decorated classes.
|
||||
* @returns the analysis of the file
|
||||
*/
|
||||
protected analyzeFile(file: DecoratedFile): DecorationAnalysis {
|
||||
const constantPool = new ConstantPool();
|
||||
const analyzedClasses =
|
||||
file.decoratedClasses.map(clazz => this.analyzeClass(constantPool, clazz))
|
||||
.filter(isDefined);
|
||||
|
||||
return {
|
||||
analyzedClasses,
|
||||
sourceFile: file.sourceFile, constantPool,
|
||||
};
|
||||
protected analyzeFile(sourceFile: ts.SourceFile): AnalyzedFile|undefined {
|
||||
const decoratedClasses = this.host.findDecoratedClasses(sourceFile);
|
||||
return decoratedClasses.length ? {
|
||||
sourceFile,
|
||||
analyzedClasses: decoratedClasses.map(clazz => this.analyzeClass(clazz)).filter(isDefined)
|
||||
} :
|
||||
undefined;
|
||||
}
|
||||
|
||||
protected analyzeClass(pool: ConstantPool, clazz: DecoratedClass): AnalyzedClass|undefined {
|
||||
protected analyzeClass(clazz: DecoratedClass): AnalyzedClass|null {
|
||||
const matchingHandlers = this.handlers
|
||||
.map(handler => ({
|
||||
handler,
|
||||
match: handler.detect(clazz.declaration, clazz.decorators),
|
||||
}))
|
||||
.map(handler => {
|
||||
const match =
|
||||
handler.detect(clazz.declaration, clazz.decorators);
|
||||
return {handler, match};
|
||||
})
|
||||
.filter(isMatchingHandler);
|
||||
|
||||
if (matchingHandlers.length > 1) {
|
||||
throw new Error('TODO.Diagnostic: Class has multiple Angular decorators.');
|
||||
}
|
||||
|
||||
if (matchingHandlers.length === 0) {
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
|
||||
const {handler, match} = matchingHandlers[0];
|
||||
const {analysis, diagnostics} = handler.analyze(clazz.declaration, match);
|
||||
let compilation = handler.compile(clazz.declaration, analysis, pool);
|
||||
return {...clazz, handler, analysis, diagnostics};
|
||||
}
|
||||
|
||||
protected compileFile(analyzedFile: AnalyzedFile): CompiledFile {
|
||||
const constantPool = new ConstantPool();
|
||||
const compiledClasses: CompiledClass[] = analyzedFile.analyzedClasses.map(analyzedClass => {
|
||||
const compilation = this.compileClass(analyzedClass, constantPool);
|
||||
return {...analyzedClass, compilation};
|
||||
});
|
||||
return {constantPool, sourceFile: analyzedFile.sourceFile, compiledClasses};
|
||||
}
|
||||
|
||||
protected compileClass(clazz: AnalyzedClass, constantPool: ConstantPool): CompileResult[] {
|
||||
let compilation = clazz.handler.compile(clazz.declaration, clazz.analysis, constantPool);
|
||||
if (!Array.isArray(compilation)) {
|
||||
compilation = [compilation];
|
||||
}
|
||||
return {...clazz, handler, analysis, diagnostics, compilation};
|
||||
return compilation;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import {DecoratedClass} from './decorated_class';
|
||||
|
||||
/**
|
||||
* Information about a source file that contains decorated exported classes.
|
||||
*/
|
||||
export class DecoratedFile {
|
||||
/**
|
||||
* The decorated exported classes that have been found in the file.
|
||||
*/
|
||||
public decoratedClasses: DecoratedClass[] = [];
|
||||
constructor(public sourceFile: ts.SourceFile) {}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
import {relative, resolve} from 'canonical-path';
|
||||
|
||||
/**
|
||||
* Map source files to their associated typings definitions files.
|
||||
*/
|
||||
export class DtsMapper {
|
||||
constructor(private sourceRoot: string, private dtsRoot: string) {}
|
||||
|
||||
/**
|
||||
* Given the absolute path to a source file, return the absolute path to the corresponding `.d.ts`
|
||||
* file. Assume that source files and `.d.ts` files have the same directory layout and the names
|
||||
* of the `.d.ts` files can be derived by replacing the `.js` extension of the source file with
|
||||
* `.d.ts`.
|
||||
*
|
||||
* @param sourceFileName The absolute path to the source file whose corresponding `.d.ts` file
|
||||
* should be returned.
|
||||
*
|
||||
* @returns The absolute path to the `.d.ts` file that corresponds to the specified source file.
|
||||
*/
|
||||
getDtsFileNameFor(sourceFileName: string): string {
|
||||
const relativeSourcePath = relative(this.sourceRoot, sourceFileName);
|
||||
return resolve(this.dtsRoot, relativeSourcePath).replace(/\.js$/, '.d.ts');
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {readFileSync} from 'fs';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassMember, ClassMemberKind, CtorParameter, Decorator, Import} from '../../../ngtsc/host';
|
||||
|
@ -14,8 +13,6 @@ import {TypeScriptReflectionHost, reflectObjectLiteral} from '../../../ngtsc/met
|
|||
import {findAll, getNameText, getOriginalSymbol, isDefined} from '../utils';
|
||||
|
||||
import {DecoratedClass} from './decorated_class';
|
||||
import {DecoratedFile} from './decorated_file';
|
||||
import {DtsMapper} from './dts_mapper';
|
||||
import {NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
|
||||
|
||||
export const DECORATORS = 'decorators' as ts.__String;
|
||||
|
@ -51,8 +48,14 @@ export const CONSTRUCTOR_PARAMS = 'ctorParameters' as ts.__String;
|
|||
* a static method called `ctorParameters`.
|
||||
*/
|
||||
export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements NgccReflectionHost {
|
||||
constructor(protected isCore: boolean, checker: ts.TypeChecker, protected dtsMapper?: DtsMapper) {
|
||||
protected dtsClassMap: Map<string, ts.ClassDeclaration>|null;
|
||||
constructor(
|
||||
protected isCore: boolean, checker: ts.TypeChecker, dtsRootFileName?: string,
|
||||
dtsProgram?: ts.Program|null) {
|
||||
super(checker);
|
||||
this.dtsClassMap = (dtsRootFileName && dtsProgram) ?
|
||||
this.computeDtsClassMap(dtsRootFileName, dtsProgram) :
|
||||
null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,12 +76,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
if (!symbol) {
|
||||
return null;
|
||||
}
|
||||
const decoratorsProperty = this.getStaticProperty(symbol, DECORATORS);
|
||||
if (decoratorsProperty) {
|
||||
return this.getClassDecoratorsFromStaticProperty(decoratorsProperty);
|
||||
} else {
|
||||
return this.getClassDecoratorsFromHelperCall(symbol);
|
||||
}
|
||||
return this.getDecoratorsOfSymbol(symbol);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -206,10 +204,11 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
if (ts.isClassDeclaration(declaration)) {
|
||||
return declaration.name && this.checker.getSymbolAtLocation(declaration.name);
|
||||
}
|
||||
if (ts.isVariableDeclaration(declaration) && declaration.initializer &&
|
||||
ts.isClassExpression(declaration.initializer)) {
|
||||
return declaration.initializer.name &&
|
||||
this.checker.getSymbolAtLocation(declaration.initializer.name);
|
||||
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
||||
declaration = declaration.initializer;
|
||||
}
|
||||
if (ts.isClassExpression(declaration)) {
|
||||
return declaration.name && this.checker.getSymbolAtLocation(declaration.name);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
@ -299,46 +298,29 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
return super.getImportOfIdentifier(id) || this.getImportOfNamespacedIdentifier(id);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find all the files accessible via an entry-point, that contain decorated classes.
|
||||
* @param entryPoint The starting point file for finding files that contain decorated classes.
|
||||
* @returns A collection of files objects that hold info about the decorated classes and import
|
||||
* information.
|
||||
/**
|
||||
* Find all the classes that contain decorations in a given file.
|
||||
* @param sourceFile The source file to search for decorated classes.
|
||||
* @returns An array of decorated classes.
|
||||
*/
|
||||
findDecoratedFiles(entryPoint: ts.SourceFile): Map<ts.SourceFile, DecoratedFile> {
|
||||
const moduleSymbol = this.checker.getSymbolAtLocation(entryPoint);
|
||||
const map = new Map<ts.SourceFile, DecoratedFile>();
|
||||
if (moduleSymbol) {
|
||||
const exportedSymbols =
|
||||
this.checker.getExportsOfModule(moduleSymbol).map(getOriginalSymbol(this.checker));
|
||||
const exportedDeclarations =
|
||||
exportedSymbols.map(exportSymbol => exportSymbol.valueDeclaration).filter(isDefined);
|
||||
|
||||
const decoratedClasses =
|
||||
exportedDeclarations
|
||||
.map(declaration => {
|
||||
if (ts.isClassDeclaration(declaration) || ts.isVariableDeclaration(declaration)) {
|
||||
const name = declaration.name && ts.isIdentifier(declaration.name) ?
|
||||
declaration.name.text :
|
||||
undefined;
|
||||
const decorators = this.getDecoratorsOfDeclaration(declaration);
|
||||
return decorators && isDefined(name) ?
|
||||
new DecoratedClass(name, declaration, decorators) :
|
||||
undefined;
|
||||
findDecoratedClasses(sourceFile: ts.SourceFile): DecoratedClass[] {
|
||||
const classes: DecoratedClass[] = [];
|
||||
sourceFile.statements.map(statement => {
|
||||
if (ts.isVariableStatement(statement)) {
|
||||
statement.declarationList.declarations.forEach(declaration => {
|
||||
const decoratedClass = this.getDecoratedClassFromSymbol(this.getClassSymbol(declaration));
|
||||
if (decoratedClass) {
|
||||
classes.push(decoratedClass);
|
||||
}
|
||||
return undefined;
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
decoratedClasses.forEach(clazz => {
|
||||
const file = clazz.declaration.getSourceFile();
|
||||
if (!map.has(file)) {
|
||||
map.set(file, new DecoratedFile(file));
|
||||
}
|
||||
map.get(file) !.decoratedClasses.push(clazz);
|
||||
});
|
||||
} else if (ts.isClassDeclaration(statement)) {
|
||||
const decoratedClass = this.getDecoratedClassFromSymbol(this.getClassSymbol(statement));
|
||||
if (decoratedClass) {
|
||||
classes.push(decoratedClass);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
});
|
||||
return classes;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -348,21 +330,38 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
* is not a class or has an unknown number of type parameters.
|
||||
*/
|
||||
getGenericArityOfClass(clazz: ts.Declaration): number|null {
|
||||
if (this.dtsMapper && ts.isClassDeclaration(clazz) && clazz.name) {
|
||||
const sourcePath = clazz.getSourceFile();
|
||||
const dtsPath = this.dtsMapper.getDtsFileNameFor(sourcePath.fileName);
|
||||
const dtsContents = readFileSync(dtsPath, 'utf8');
|
||||
// TODO: investigate caching parsed .d.ts files as they're needed for several different
|
||||
// purposes in ngcc.
|
||||
const dtsFile = ts.createSourceFile(
|
||||
dtsPath, dtsContents, ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
||||
|
||||
for (let i = dtsFile.statements.length - 1; i >= 0; i--) {
|
||||
const stmt = dtsFile.statements[i];
|
||||
if (ts.isClassDeclaration(stmt) && stmt.name !== undefined &&
|
||||
stmt.name.text === clazz.name.text) {
|
||||
return stmt.typeParameters ? stmt.typeParameters.length : 0;
|
||||
const dtsClass = this.getDtsDeclarationOfClass(clazz);
|
||||
if (dtsClass) {
|
||||
return dtsClass.typeParameters ? dtsClass.typeParameters.length : 0;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take an exported declaration of a class (maybe downleveled to a variable) and look up the
|
||||
* declaration of its type in a separate .d.ts tree.
|
||||
*
|
||||
* This function is allowed to return `null` if the current compilation unit does not have a
|
||||
* separate .d.ts tree. When compiling TypeScript code this is always the case, since .d.ts files
|
||||
* are produced only during the emit of such a compilation. When compiling .js code, however,
|
||||
* there is frequently a parallel .d.ts tree which this method exposes.
|
||||
*
|
||||
* Note that the `ts.ClassDeclaration` returned from this function may not be from the same
|
||||
* `ts.Program` as the input declaration.
|
||||
*/
|
||||
getDtsDeclarationOfClass(declaration: ts.Declaration): ts.ClassDeclaration|null {
|
||||
if (this.dtsClassMap) {
|
||||
if (ts.isClassDeclaration(declaration)) {
|
||||
if (!declaration.name || !ts.isIdentifier(declaration.name)) {
|
||||
throw new Error(
|
||||
`Cannot get the dts file for a class declaration that has no indetifier: ${declaration.getText()} in ${declaration.getSourceFile().fileName}`);
|
||||
}
|
||||
const dtsDeclaration = this.dtsClassMap.get(declaration.name.text);
|
||||
if (!dtsDeclaration) {
|
||||
throw new Error(
|
||||
`Unable to find matching typings (.d.ts) declaration for ${declaration.name.text} in ${declaration.getSourceFile().fileName}`);
|
||||
}
|
||||
return dtsDeclaration;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -371,6 +370,25 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
|
||||
///////////// Protected Helpers /////////////
|
||||
|
||||
protected getDecoratorsOfSymbol(symbol: ts.Symbol): Decorator[]|null {
|
||||
const decoratorsProperty = this.getStaticProperty(symbol, DECORATORS);
|
||||
if (decoratorsProperty) {
|
||||
return this.getClassDecoratorsFromStaticProperty(decoratorsProperty);
|
||||
} else {
|
||||
return this.getClassDecoratorsFromHelperCall(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
protected getDecoratedClassFromSymbol(symbol: ts.Symbol|undefined): DecoratedClass|null {
|
||||
if (symbol) {
|
||||
const decorators = this.getDecoratorsOfSymbol(symbol);
|
||||
if (decorators && decorators.length) {
|
||||
return new DecoratedClass(symbol.name, symbol.valueDeclaration, decorators);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk the AST looking for an assignment to the specified symbol.
|
||||
* @param node The current node we are searching.
|
||||
|
@ -691,7 +709,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
let value: ts.Expression|null = null;
|
||||
let name: string|null = null;
|
||||
let nameNode: ts.Identifier|null = null;
|
||||
let type = null;
|
||||
|
||||
|
||||
const node = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0];
|
||||
|
@ -744,6 +761,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
node.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
|
||||
}
|
||||
|
||||
const type: ts.TypeNode = (node as any).type || null;
|
||||
return {
|
||||
node,
|
||||
implementation: node, kind, type, name, nameNode, value, isStatic,
|
||||
|
@ -967,13 +985,41 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
*
|
||||
* @param decorator the decorator to test.
|
||||
*/
|
||||
isFromCore(decorator: Decorator): boolean {
|
||||
protected isFromCore(decorator: Decorator): boolean {
|
||||
if (this.isCore) {
|
||||
return !decorator.import || /^\./.test(decorator.import.from);
|
||||
} else {
|
||||
return !!decorator.import && decorator.import.from === '@angular/core';
|
||||
}
|
||||
}
|
||||
|
||||
protected computeDtsClassMap(dtsRootFileName: string, dtsProgram: ts.Program):
|
||||
Map<string, ts.ClassDeclaration> {
|
||||
const dtsClassMap = new Map<string, ts.ClassDeclaration>();
|
||||
const checker = dtsProgram.getTypeChecker();
|
||||
const dtsRootFile = dtsProgram.getSourceFile(dtsRootFileName);
|
||||
const rootModule = dtsRootFile && checker.getSymbolAtLocation(dtsRootFile);
|
||||
const moduleExports = rootModule && checker.getExportsOfModule(rootModule);
|
||||
if (moduleExports) {
|
||||
moduleExports.forEach(exportedSymbol => {
|
||||
if (exportedSymbol.flags & ts.SymbolFlags.Alias) {
|
||||
exportedSymbol = checker.getAliasedSymbol(exportedSymbol);
|
||||
}
|
||||
const declaration = exportedSymbol.declarations[0];
|
||||
if (declaration && ts.isClassDeclaration(declaration)) {
|
||||
const name = exportedSymbol.name;
|
||||
const previousDeclaration = dtsClassMap.get(name);
|
||||
if (previousDeclaration && previousDeclaration !== declaration) {
|
||||
console.warn(
|
||||
`Ambiguous class name ${name} in typings files: ${previousDeclaration.getSourceFile().fileName} and ${declaration.getSourceFile().fileName}`);
|
||||
} else {
|
||||
dtsClassMap.set(name, declaration);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return dtsClassMap;
|
||||
}
|
||||
}
|
||||
|
||||
///////////// Exported Helpers /////////////
|
||||
|
|
|
@ -10,10 +10,8 @@ import * as ts from 'typescript';
|
|||
|
||||
import {ClassMember, ClassMemberKind, Decorator, FunctionDefinition, Parameter} from '../../../ngtsc/host';
|
||||
import {reflectObjectLiteral} from '../../../ngtsc/metadata';
|
||||
import {getNameText, getOriginalSymbol, isDefined} from '../utils';
|
||||
import {getNameText} from '../utils';
|
||||
|
||||
import {DecoratedClass} from './decorated_class';
|
||||
import {DecoratedFile} from './decorated_file';
|
||||
import {Esm2015ReflectionHost, ParamInfo, getPropertyValueFromSymbol, isAssignmentStatement} from './esm2015_host';
|
||||
|
||||
|
||||
|
@ -125,41 +123,6 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
|||
return {node, body: statements || null, parameters};
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all the files accessible via an entry-point, that contain decorated classes.
|
||||
* @param entryPoint The starting point file for finding files that contain decorated classes.
|
||||
* @returns A collection of files objects that hold info about the decorated classes and import
|
||||
* information.
|
||||
*/
|
||||
findDecoratedFiles(entryPoint: ts.SourceFile): Map<ts.SourceFile, DecoratedFile> {
|
||||
const moduleSymbol = this.checker.getSymbolAtLocation(entryPoint);
|
||||
const map = new Map<ts.SourceFile, DecoratedFile>();
|
||||
const getParsedClass = (declaration: ts.VariableDeclaration) => {
|
||||
const decorators = this.getDecoratorsOfDeclaration(declaration);
|
||||
if (decorators) {
|
||||
return new DecoratedClass(getNameText(declaration.name), declaration, decorators);
|
||||
}
|
||||
};
|
||||
|
||||
if (moduleSymbol) {
|
||||
const classDeclarations = this.checker.getExportsOfModule(moduleSymbol)
|
||||
.map(getOriginalSymbol(this.checker))
|
||||
.map(exportSymbol => exportSymbol.valueDeclaration)
|
||||
.filter(isDefined)
|
||||
.filter(ts.isVariableDeclaration);
|
||||
|
||||
const decoratedClasses = classDeclarations.map(getParsedClass).filter(isDefined);
|
||||
|
||||
decoratedClasses.forEach(clazz => {
|
||||
const file = clazz.declaration.getSourceFile();
|
||||
if (!map.has(file)) {
|
||||
map.set(file, new DecoratedFile(file));
|
||||
}
|
||||
map.get(file) !.decoratedClasses.push(clazz);
|
||||
});
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
///////////// Protected Helpers /////////////
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
import * as ts from 'typescript';
|
||||
import {ReflectionHost} from '../../../ngtsc/host';
|
||||
import {DecoratedFile} from './decorated_file';
|
||||
import {DecoratedClass} from './decorated_class';
|
||||
|
||||
export const PRE_R3_MARKER = '__PRE_R3__';
|
||||
export const POST_R3_MARKER = '__POST_R3__';
|
||||
|
@ -40,10 +40,9 @@ export interface NgccReflectionHost extends ReflectionHost {
|
|||
getSwitchableDeclarations(module: ts.Node): SwitchableVariableDeclaration[];
|
||||
|
||||
/**
|
||||
* Find all the files accessible via an entry-point, that contain decorated classes.
|
||||
* @param entryPoint The starting point file for finding files that contain decorated classes.
|
||||
* @returns A collection of files objects that hold info about the decorated classes and import
|
||||
* information.
|
||||
* Find all the classes that contain decorations in a given file.
|
||||
* @param sourceFile The source file to search for decorated classes.
|
||||
* @returns An array of decorated classes.
|
||||
*/
|
||||
findDecoratedFiles(entryPoint: ts.SourceFile): Map<ts.SourceFile, DecoratedFile>;
|
||||
findDecoratedClasses(sourceFile: ts.SourceFile): DecoratedClass[];
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as path from 'canonical-path';
|
||||
import {existsSync, lstatSync, readFileSync, readdirSync} from 'fs';
|
||||
import * as yargs from 'yargs';
|
||||
|
||||
import {DependencyHost} from './packages/dependency_host';
|
||||
|
@ -48,8 +47,14 @@ export function mainNgcc(args: string[]): number {
|
|||
|
||||
try {
|
||||
const {entryPoints} = finder.findEntryPoints(sourcePath);
|
||||
entryPoints.forEach(
|
||||
entryPoint => formats.forEach(format => transformer.transform(entryPoint, format)));
|
||||
entryPoints.forEach(entryPoint => {
|
||||
// We transform the d.ts typings files while transforming one of the formats.
|
||||
// This variable decides with which of the available formats to do this transform.
|
||||
// It is marginally faster to process via the flat file if available.
|
||||
const dtsTranformFormat: EntryPointFormat = entryPoint.fesm2015 ? 'fesm2015' : 'esm2015';
|
||||
formats.forEach(
|
||||
format => transformer.transform(entryPoint, format, format === dtsTranformFormat));
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
return 1;
|
||||
|
|
|
@ -12,7 +12,6 @@ import * as ts from 'typescript';
|
|||
|
||||
import {DecorationAnalyzer} from '../analysis/decoration_analyzer';
|
||||
import {SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer';
|
||||
import {DtsMapper} from '../host/dts_mapper';
|
||||
import {Esm2015ReflectionHost} from '../host/esm2015_host';
|
||||
import {Esm5ReflectionHost} from '../host/esm5_host';
|
||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||
|
@ -44,7 +43,7 @@ import {EntryPoint, EntryPointFormat} from './entry_point';
|
|||
export class Transformer {
|
||||
constructor(private sourcePath: string, private targetPath: string) {}
|
||||
|
||||
transform(entryPoint: EntryPoint, format: EntryPointFormat): void {
|
||||
transform(entryPoint: EntryPoint, format: EntryPointFormat, transformDts: boolean): void {
|
||||
if (checkMarkerFile(entryPoint, format)) {
|
||||
console.warn(`Skipping ${entryPoint.name} : ${format} (already built).`);
|
||||
return;
|
||||
|
@ -73,19 +72,24 @@ export class Transformer {
|
|||
const r3SymbolsPath = isCore ? this.findR3SymbolsPath(dirname(entryPointFilePath)) : null;
|
||||
const rootPaths = r3SymbolsPath ? [entryPointFilePath, r3SymbolsPath] : [entryPointFilePath];
|
||||
const packageProgram = ts.createProgram(rootPaths, options, host);
|
||||
const dtsMapper = new DtsMapper(dirname(entryPointFilePath), dirname(entryPoint.typings));
|
||||
const reflectionHost = this.getHost(isCore, format, packageProgram, dtsMapper);
|
||||
console.time(entryPoint.name + '(dtsmappper creation)');
|
||||
const dtsFilePath = entryPoint.typings;
|
||||
const dtsProgram = transformDts ? ts.createProgram([entryPoint.typings], options, host) : null;
|
||||
console.timeEnd(entryPoint.name + '(dtsmappper creation)');
|
||||
const reflectionHost = this.getHost(isCore, format, packageProgram, dtsFilePath, dtsProgram);
|
||||
const r3SymbolsFile = r3SymbolsPath && packageProgram.getSourceFile(r3SymbolsPath) || null;
|
||||
|
||||
// Parse and analyze the files.
|
||||
const {decorationAnalyses, switchMarkerAnalyses} =
|
||||
this.analyzeProgram(packageProgram, reflectionHost, rootDirs, isCore);
|
||||
|
||||
console.time(entryPoint.name + '(rendering)');
|
||||
// Transform the source files and source maps.
|
||||
const renderer =
|
||||
this.getRenderer(format, packageProgram, reflectionHost, isCore, r3SymbolsFile, dtsMapper);
|
||||
this.getRenderer(format, packageProgram, reflectionHost, isCore, r3SymbolsFile);
|
||||
const renderedFiles =
|
||||
renderer.renderProgram(packageProgram, decorationAnalyses, switchMarkerAnalyses);
|
||||
console.timeEnd(entryPoint.name + '(rendering)');
|
||||
|
||||
// Write out all the transformed files.
|
||||
renderedFiles.forEach(file => this.writeFile(file));
|
||||
|
@ -104,12 +108,13 @@ export class Transformer {
|
|||
}
|
||||
}
|
||||
|
||||
getHost(isCore: boolean, format: string, program: ts.Program, dtsMapper: DtsMapper):
|
||||
NgccReflectionHost {
|
||||
getHost(
|
||||
isCore: boolean, format: string, program: ts.Program, dtsFilePath: string,
|
||||
dtsProgram: ts.Program|null): NgccReflectionHost {
|
||||
switch (format) {
|
||||
case 'esm2015':
|
||||
case 'fesm2015':
|
||||
return new Esm2015ReflectionHost(isCore, program.getTypeChecker(), dtsMapper);
|
||||
return new Esm2015ReflectionHost(isCore, program.getTypeChecker(), dtsFilePath, dtsProgram);
|
||||
case 'esm5':
|
||||
case 'fesm5':
|
||||
return new Esm5ReflectionHost(isCore, program.getTypeChecker());
|
||||
|
@ -120,13 +125,14 @@ export class Transformer {
|
|||
|
||||
getRenderer(
|
||||
format: string, program: ts.Program, host: NgccReflectionHost, isCore: boolean,
|
||||
rewriteCoreImportsTo: ts.SourceFile|null, dtsMapper: DtsMapper|null): Renderer {
|
||||
rewriteCoreImportsTo: ts.SourceFile|null): Renderer {
|
||||
switch (format) {
|
||||
case 'esm2015':
|
||||
case 'esm5':
|
||||
case 'fesm2015':
|
||||
case 'fesm5':
|
||||
return new EsmRenderer(host, isCore, rewriteCoreImportsTo, this.sourcePath, this.targetPath, dtsMapper);
|
||||
return new EsmRenderer(
|
||||
host, isCore, rewriteCoreImportsTo, this.sourcePath, this.targetPath);
|
||||
default:
|
||||
throw new Error(`Renderer for "${format}" not yet implemented.`);
|
||||
}
|
||||
|
|
|
@ -7,17 +7,16 @@
|
|||
*/
|
||||
import * as ts from 'typescript';
|
||||
import MagicString from 'magic-string';
|
||||
import {DtsMapper} from '../host/dts_mapper';
|
||||
import {NgccReflectionHost, POST_R3_MARKER, PRE_R3_MARKER, SwitchableVariableDeclaration} from '../host/ngcc_host';
|
||||
import {AnalyzedClass} from '../analysis/decoration_analyzer';
|
||||
import {CompiledClass} from '../analysis/decoration_analyzer';
|
||||
import {Renderer} from './renderer';
|
||||
|
||||
export class EsmRenderer extends Renderer {
|
||||
constructor(
|
||||
protected host: NgccReflectionHost, protected isCore: boolean,
|
||||
protected rewriteCoreImportsTo: ts.SourceFile|null, protected sourcePath: string,
|
||||
protected targetPath: string, dtsMapper: DtsMapper|null) {
|
||||
super(host, isCore, rewriteCoreImportsTo, sourcePath, targetPath, dtsMapper);
|
||||
protected targetPath: string) {
|
||||
super(host, isCore, rewriteCoreImportsTo, sourcePath, targetPath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,10 +44,10 @@ export class EsmRenderer extends Renderer {
|
|||
/**
|
||||
* Add the definitions to each decorated class
|
||||
*/
|
||||
addDefinitions(output: MagicString, analyzedClass: AnalyzedClass, definitions: string): void {
|
||||
const classSymbol = this.host.getClassSymbol(analyzedClass.declaration);
|
||||
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void {
|
||||
const classSymbol = this.host.getClassSymbol(compiledClass.declaration);
|
||||
if (!classSymbol) {
|
||||
throw new Error(`Analyzed class does not have a valid symbol: ${analyzedClass.name}`);
|
||||
throw new Error(`Compiled class does not have a valid symbol: ${compiledClass.name}`);
|
||||
}
|
||||
const insertionPoint = classSymbol.valueDeclaration !.getEnd();
|
||||
output.appendLeft(insertionPoint, '\n' + definitions);
|
||||
|
|
|
@ -14,13 +14,12 @@ import {SourceMapConsumer, SourceMapGenerator, RawSourceMap} from 'source-map';
|
|||
import * as ts from 'typescript';
|
||||
|
||||
import {Decorator} from '../../../ngtsc/host';
|
||||
import {DtsFileTransformer} from '../../../ngtsc/transform';
|
||||
import {translateStatement} from '../../../ngtsc/translator';
|
||||
import {CompileResult} from '@angular/compiler-cli/src/ngtsc/transform';
|
||||
import {translateStatement, translateType} from '../../../ngtsc/translator';
|
||||
import {NgccImportManager} from './ngcc_import_manager';
|
||||
import {AnalyzedClass, DecorationAnalysis, DecorationAnalyses} from '../analysis/decoration_analyzer';
|
||||
import {CompiledClass, CompiledFile, DecorationAnalyses} from '../analysis/decoration_analyzer';
|
||||
import {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer';
|
||||
import {IMPORT_PREFIX} from '../constants';
|
||||
import {DtsMapper} from '../host/dts_mapper';
|
||||
import {NgccReflectionHost, SwitchableVariableDeclaration} from '../host/ngcc_host';
|
||||
|
||||
interface SourceMapInfo {
|
||||
|
@ -29,20 +28,6 @@ interface SourceMapInfo {
|
|||
isInline: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The results of rendering an analyzed file.
|
||||
*/
|
||||
export interface RenderResult {
|
||||
/**
|
||||
* The rendered source file.
|
||||
*/
|
||||
source: FileInfo;
|
||||
/**
|
||||
* The rendered source map file.
|
||||
*/
|
||||
map: FileInfo|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about a file that has been rendered.
|
||||
*/
|
||||
|
@ -57,6 +42,11 @@ export interface FileInfo {
|
|||
contents: string;
|
||||
}
|
||||
|
||||
interface DtsClassInfo {
|
||||
dtsDeclaration: ts.Declaration;
|
||||
compilation: CompileResult[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A base-class for rendering an `AnalyzedFile`.
|
||||
*
|
||||
|
@ -67,39 +57,38 @@ export abstract class Renderer {
|
|||
constructor(
|
||||
protected host: NgccReflectionHost, protected isCore: boolean,
|
||||
protected rewriteCoreImportsTo: ts.SourceFile|null, protected sourcePath: string,
|
||||
protected targetPath: string, protected dtsMapper: DtsMapper|null) {}
|
||||
protected targetPath: string) {}
|
||||
|
||||
renderProgram(
|
||||
program: ts.Program, decorationAnalyses: DecorationAnalyses,
|
||||
switchMarkerAnalyses: SwitchMarkerAnalyses): FileInfo[] {
|
||||
const renderedFiles: FileInfo[] = [];
|
||||
|
||||
// Transform the source files, source maps and typings files.
|
||||
// Transform the source files.
|
||||
program.getSourceFiles().map(sourceFile => {
|
||||
const decorationAnalysis = decorationAnalyses.get(sourceFile);
|
||||
const compiledFile = decorationAnalyses.get(sourceFile);
|
||||
const switchMarkerAnalysis = switchMarkerAnalyses.get(sourceFile);
|
||||
|
||||
if (decorationAnalysis || switchMarkerAnalysis) {
|
||||
const targetPath = resolve(this.targetPath, relative(this.sourcePath, sourceFile.fileName));
|
||||
renderedFiles.push(
|
||||
...this.renderFile(sourceFile, decorationAnalysis, switchMarkerAnalysis, targetPath));
|
||||
}
|
||||
|
||||
if (decorationAnalyses) {
|
||||
renderedFiles.push(...this.renderTypings(decorationAnalyses));
|
||||
if (compiledFile || switchMarkerAnalysis) {
|
||||
renderedFiles.push(...this.renderFile(sourceFile, compiledFile, switchMarkerAnalysis));
|
||||
}
|
||||
});
|
||||
|
||||
// Transform the .d.ts files
|
||||
const dtsFiles = this.getTypingsFilesToRender(decorationAnalyses);
|
||||
dtsFiles.forEach((classes, file) => renderedFiles.push(...this.renderDtsFile(file, classes)));
|
||||
|
||||
return renderedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the source code and source-map for an Analyzed file.
|
||||
* @param decorationAnalysis The analyzed file to render.
|
||||
* @param compiledFile The analyzed file to render.
|
||||
* @param targetPath The absolute path where the rendered file will be written.
|
||||
*/
|
||||
renderFile(
|
||||
sourceFile: ts.SourceFile, decorationAnalysis: DecorationAnalysis|undefined,
|
||||
switchMarkerAnalysis: SwitchMarkerAnalysis|undefined, targetPath: string): FileInfo[] {
|
||||
sourceFile: ts.SourceFile, compiledFile: CompiledFile|undefined,
|
||||
switchMarkerAnalysis: SwitchMarkerAnalysis|undefined): FileInfo[] {
|
||||
const input = this.extractSourceMap(sourceFile);
|
||||
const outputText = new MagicString(input.source);
|
||||
|
||||
|
@ -108,46 +97,59 @@ export abstract class Renderer {
|
|||
outputText, switchMarkerAnalysis.sourceFile, switchMarkerAnalysis.declarations);
|
||||
}
|
||||
|
||||
if (decorationAnalysis) {
|
||||
if (compiledFile) {
|
||||
const importManager =
|
||||
new NgccImportManager(!this.rewriteCoreImportsTo, this.isCore, IMPORT_PREFIX);
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
|
||||
decorationAnalysis.analyzedClasses.forEach(clazz => {
|
||||
const renderedDefinition =
|
||||
renderDefinitions(decorationAnalysis.sourceFile, clazz, importManager);
|
||||
compiledFile.compiledClasses.forEach(clazz => {
|
||||
const renderedDefinition = renderDefinitions(compiledFile.sourceFile, clazz, importManager);
|
||||
this.addDefinitions(outputText, clazz, renderedDefinition);
|
||||
this.trackDecorators(clazz.decorators, decoratorsToRemove);
|
||||
});
|
||||
|
||||
this.addConstants(
|
||||
outputText,
|
||||
renderConstantPool(
|
||||
decorationAnalysis.sourceFile, decorationAnalysis.constantPool, importManager),
|
||||
decorationAnalysis.sourceFile);
|
||||
renderConstantPool(compiledFile.sourceFile, compiledFile.constantPool, importManager),
|
||||
compiledFile.sourceFile);
|
||||
|
||||
this.addImports(
|
||||
outputText, importManager.getAllImports(
|
||||
decorationAnalysis.sourceFile.fileName, this.rewriteCoreImportsTo));
|
||||
outputText,
|
||||
importManager.getAllImports(compiledFile.sourceFile.fileName, this.rewriteCoreImportsTo));
|
||||
|
||||
// TODO: remove contructor param metadata and property decorators (we need info from the
|
||||
// handlers to do this)
|
||||
this.removeDecorators(outputText, decoratorsToRemove);
|
||||
}
|
||||
|
||||
const {source, map} = this.renderSourceAndMap(sourceFile, input, outputText, targetPath);
|
||||
const renderedFiles = [source];
|
||||
if (map) {
|
||||
renderedFiles.push(map);
|
||||
return this.renderSourceAndMap(sourceFile, input, outputText);
|
||||
}
|
||||
return renderedFiles;
|
||||
|
||||
renderDtsFile(dtsFile: ts.SourceFile, dtsClasses: DtsClassInfo[]): FileInfo[] {
|
||||
const input = this.extractSourceMap(dtsFile);
|
||||
const outputText = new MagicString(input.source);
|
||||
const importManager = new NgccImportManager(false, this.isCore, IMPORT_PREFIX);
|
||||
|
||||
dtsClasses.forEach(dtsClass => {
|
||||
const endOfClass = dtsClass.dtsDeclaration.getEnd();
|
||||
dtsClass.compilation.forEach(declaration => {
|
||||
const type = translateType(declaration.type, importManager);
|
||||
const newStatement = ` static ${declaration.name}: ${type};\n`;
|
||||
outputText.appendRight(endOfClass - 1, newStatement);
|
||||
});
|
||||
});
|
||||
|
||||
this.addImports(
|
||||
outputText, importManager.getAllImports(dtsFile.fileName, this.rewriteCoreImportsTo));
|
||||
|
||||
return this.renderSourceAndMap(dtsFile, input, outputText);
|
||||
}
|
||||
|
||||
protected abstract addConstants(output: MagicString, constants: string, file: ts.SourceFile):
|
||||
void;
|
||||
protected abstract addImports(output: MagicString, imports: {name: string, as: string}[]): void;
|
||||
protected abstract addDefinitions(
|
||||
output: MagicString, analyzedClass: AnalyzedClass, definitions: string): void;
|
||||
output: MagicString, compiledClass: CompiledClass, definitions: string): void;
|
||||
protected abstract removeDecorators(
|
||||
output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>): void;
|
||||
protected abstract rewriteSwitchableDeclarations(
|
||||
|
@ -219,8 +221,8 @@ export abstract class Renderer {
|
|||
* with an appropriate source-map comment pointing to the merged source-map.
|
||||
*/
|
||||
protected renderSourceAndMap(
|
||||
sourceFile: ts.SourceFile, input: SourceMapInfo, output: MagicString,
|
||||
outputPath: string): RenderResult {
|
||||
sourceFile: ts.SourceFile, input: SourceMapInfo, output: MagicString): FileInfo[] {
|
||||
const outputPath = resolve(this.targetPath, relative(this.sourcePath, sourceFile.fileName));
|
||||
const outputMapPath = `${outputPath}.map`;
|
||||
const outputMap = output.generateMap({
|
||||
source: sourceFile.fileName,
|
||||
|
@ -235,41 +237,34 @@ export abstract class Renderer {
|
|||
const mergedMap =
|
||||
mergeSourceMaps(input.map && input.map.toObject(), JSON.parse(outputMap.toString()));
|
||||
|
||||
const result: FileInfo[] = [];
|
||||
if (input.isInline) {
|
||||
return {
|
||||
source: {path: outputPath, contents: `${output.toString()}\n${mergedMap.toComment()}`},
|
||||
map: null
|
||||
};
|
||||
result.push({path: outputPath, contents: `${output.toString()}\n${mergedMap.toComment()}`});
|
||||
} else {
|
||||
return {
|
||||
source: {
|
||||
result.push({
|
||||
path: outputPath,
|
||||
contents: `${output.toString()}\n${generateMapFileComment(outputMapPath)}`
|
||||
},
|
||||
map: {path: outputMapPath, contents: mergedMap.toJSON()}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(gkalpak): What about `.d.ts` source maps? (See
|
||||
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#new---declarationmap.)
|
||||
renderTypings(decorationAnalyses: DecorationAnalyses): FileInfo[] {
|
||||
const renderedFiles: FileInfo[] = [];
|
||||
if (this.dtsMapper) {
|
||||
const dtsTransformer = new DtsFileTransformer(this.rewriteCoreImportsTo, IMPORT_PREFIX);
|
||||
decorationAnalyses.forEach((analysis, sourceFile) => {
|
||||
const sourceFileName = sourceFile.fileName;
|
||||
const dtsFileName = this.dtsMapper !.getDtsFileNameFor(sourceFileName);
|
||||
const dtsContents = readFileSync(dtsFileName, 'utf8');
|
||||
analysis.analyzedClasses.forEach(analyzedClass => dtsTransformer.recordStaticField(analyzedClass.name, analyzedClass.compilation));
|
||||
const newDtsFileName = resolve(this.targetPath, relative(this.sourcePath, dtsFileName));
|
||||
const newDtsContents = dtsTransformer.transform(dtsContents, sourceFileName);
|
||||
renderedFiles.push({path: newDtsFileName, contents: newDtsContents});
|
||||
});
|
||||
result.push({path: outputMapPath, contents: mergedMap.toJSON()});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return renderedFiles;
|
||||
|
||||
// }
|
||||
protected getTypingsFilesToRender(analyses: DecorationAnalyses):
|
||||
Map<ts.SourceFile, DtsClassInfo[]> {
|
||||
const dtsMap = new Map<ts.SourceFile, DtsClassInfo[]>();
|
||||
analyses.forEach(compiledFile => {
|
||||
compiledFile.compiledClasses.forEach(compiledClass => {
|
||||
const dtsDeclaration = this.host.getDtsDeclarationOfClass(compiledClass.declaration);
|
||||
if (dtsDeclaration) {
|
||||
const dtsFile = dtsDeclaration.getSourceFile();
|
||||
const classes = dtsMap.get(dtsFile) || [];
|
||||
classes.push({dtsDeclaration, compilation: compiledClass.compilation});
|
||||
dtsMap.set(dtsFile, classes);
|
||||
}
|
||||
});
|
||||
});
|
||||
return dtsMap;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -321,11 +316,11 @@ export function renderConstantPool(
|
|||
* @param imports An object that tracks the imports that are needed by the rendered definitions.
|
||||
*/
|
||||
export function renderDefinitions(
|
||||
sourceFile: ts.SourceFile, analyzedClass: AnalyzedClass, imports: NgccImportManager): string {
|
||||
sourceFile: ts.SourceFile, compiledClass: CompiledClass, imports: NgccImportManager): string {
|
||||
const printer = ts.createPrinter();
|
||||
const name = (analyzedClass.declaration as ts.NamedDeclaration).name !;
|
||||
const name = (compiledClass.declaration as ts.NamedDeclaration).name !;
|
||||
const definitions =
|
||||
analyzedClass.compilation
|
||||
compiledClass.compilation
|
||||
.map(
|
||||
c => c.statements.map(statement => translateStatement(statement, imports))
|
||||
.concat(translateStatement(
|
||||
|
|
|
@ -27,6 +27,34 @@ const TEST_PROGRAM = {
|
|||
`
|
||||
};
|
||||
|
||||
const INTERNAL_COMPONENT_PROGRAM = [
|
||||
{
|
||||
name: 'entrypoint.js',
|
||||
contents: `
|
||||
import {Component, NgModule} from '@angular/core';
|
||||
import {ImportedComponent} from './component';
|
||||
|
||||
export class LocalComponent {}
|
||||
LocalComponent.decorators = [{type: Component}];
|
||||
|
||||
export class MyModule {}
|
||||
MyModule.decorators = [{type: NgModule, args: [{
|
||||
declarations: [ImportedComponent, LocalComponent],
|
||||
exports: [ImportedComponent, LocalComponent],
|
||||
},] }];
|
||||
`
|
||||
},
|
||||
{
|
||||
name: 'component.js',
|
||||
contents: `
|
||||
import {Component} from '@angular/core';
|
||||
export class ImportedComponent {}
|
||||
ImportedComponent.decorators = [{type: Component}];
|
||||
`,
|
||||
isRoot: false,
|
||||
}
|
||||
];
|
||||
|
||||
function createTestHandler() {
|
||||
const handler = jasmine.createSpyObj<DecoratorHandler<any, any>>('TestDecoratorHandler', [
|
||||
'detect',
|
||||
|
@ -79,9 +107,9 @@ describe('DecorationAnalyzer', () => {
|
|||
|
||||
it('should return an object containing the classes that were analyzed', () => {
|
||||
const file = program.getSourceFile(TEST_PROGRAM.name) !;
|
||||
const analysis = result.get(file) !;
|
||||
expect(analysis.analyzedClasses.length).toEqual(1);
|
||||
expect(analysis.analyzedClasses[0].name).toEqual('MyComponent');
|
||||
const compiledFile = result.get(file) !;
|
||||
expect(compiledFile.compiledClasses.length).toEqual(1);
|
||||
expect(compiledFile.compiledClasses[0].name).toEqual('MyComponent');
|
||||
});
|
||||
|
||||
it('should analyze and compile the classes that are detected', () => {
|
||||
|
@ -91,5 +119,41 @@ describe('DecorationAnalyzer', () => {
|
|||
expect(testHandler.compile).toHaveBeenCalledTimes(1);
|
||||
expect(testHandler.compile.calls.allArgs()[0][1]).toEqual('Component');
|
||||
});
|
||||
|
||||
describe('internal components', () => {
|
||||
// The problem of exposing the type of these internal components in the .d.ts typing files
|
||||
// is not yet solved.
|
||||
it('should analyze an internally imported component, which is not publicly exported from the entry-point',
|
||||
() => {
|
||||
const program = makeProgram(...INTERNAL_COMPONENT_PROGRAM);
|
||||
const analyzer = new DecorationAnalyzer(
|
||||
program.getTypeChecker(), new Esm2015ReflectionHost(false, program.getTypeChecker()),
|
||||
[''], false);
|
||||
const testHandler = createTestHandler();
|
||||
analyzer.handlers = [testHandler];
|
||||
const result = analyzer.analyzeProgram(program);
|
||||
const file = program.getSourceFile('component.js') !;
|
||||
const analysis = result.get(file) !;
|
||||
expect(analysis).toBeDefined();
|
||||
const ImportedComponent =
|
||||
analysis.compiledClasses.find(f => f.name === 'ImportedComponent') !;
|
||||
expect(ImportedComponent).toBeDefined();
|
||||
});
|
||||
|
||||
it('should analyze an internally defined component, which is not exported at all', () => {
|
||||
const program = makeProgram(...INTERNAL_COMPONENT_PROGRAM);
|
||||
const analyzer = new DecorationAnalyzer(
|
||||
program.getTypeChecker(), new Esm2015ReflectionHost(false, program.getTypeChecker()),
|
||||
[''], false);
|
||||
const testHandler = createTestHandler();
|
||||
analyzer.handlers = [testHandler];
|
||||
const result = analyzer.analyzeProgram(program);
|
||||
const file = program.getSourceFile('entrypoint.js') !;
|
||||
const analysis = result.get(file) !;
|
||||
expect(analysis).toBeDefined();
|
||||
const LocalComponent = analysis.compiledClasses.find(f => f.name === 'LocalComponent') !;
|
||||
expect(LocalComponent).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,10 +6,8 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as ts from 'typescript';
|
||||
import {ClassMemberKind, Import} from '../../../ngtsc/host';
|
||||
import {DtsMapper} from '../../src/host/dts_mapper';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
import {getDeclaration, makeProgram} from '../helpers/utils';
|
||||
|
||||
|
@ -399,6 +397,7 @@ const DECORATED_FILES = [
|
|||
name: '/primary.js',
|
||||
contents: `
|
||||
import {Directive} from '@angular/core';
|
||||
import {D} from '/secondary';
|
||||
class A {}
|
||||
A.decorators = [
|
||||
{ type: Directive, args: [{ selector: '[a]' }] }
|
||||
|
@ -411,7 +410,6 @@ const DECORATED_FILES = [
|
|||
];
|
||||
class C {}
|
||||
export { A, x, C };
|
||||
export { D } from '/secondary';
|
||||
`
|
||||
},
|
||||
{
|
||||
|
@ -439,13 +437,38 @@ const ARITY_CLASSES = [
|
|||
{
|
||||
name: '/typings/class.d.ts',
|
||||
contents: `
|
||||
export class NoTypeParam {}
|
||||
export class OneTypeParam<T> {}
|
||||
export class TwoTypeParams<T, K> {}
|
||||
export declare class NoTypeParam {}
|
||||
export declare class OneTypeParam<T> {}
|
||||
export declare class TwoTypeParams<T, K> {}
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
const TYPINGS_SRC_FILES = [
|
||||
{name: '/src/index.js', contents: `export * from './class1'; export * from './class2';`},
|
||||
{name: '/src/class1.js', contents: 'export class Class1 {}\nexport class MissingClass1 {}'},
|
||||
{name: '/src/class2.js', contents: 'export class Class2 {}'},
|
||||
{name: '/src/missing-class.js', contents: 'export class MissingClass2 {}'}, {
|
||||
name: '/src/flat-file.js',
|
||||
contents:
|
||||
'export class Class1 {}\nexport class MissingClass1 {}\nexport class MissingClass2 {}\class Class3 {}\nexport {Class3 as xClass3};',
|
||||
}
|
||||
];
|
||||
|
||||
const TYPINGS_DTS_FILES = [
|
||||
{name: '/typings/index.d.ts', contents: `export * from './class1'; export * from './class2';`},
|
||||
{
|
||||
name: '/typings/class1.d.ts',
|
||||
contents: `export declare class Class1 {}\nexport declare class OtherClass {}`
|
||||
},
|
||||
{
|
||||
name: '/typings/class2.d.ts',
|
||||
contents:
|
||||
`export declare class Class2 {}\nexport declare interface SomeInterface {}\nexport {Class3 as xClass3} from './class3';`
|
||||
},
|
||||
{name: '/typings/class3.d.ts', contents: `export declare class Class3 {}`},
|
||||
];
|
||||
|
||||
describe('Fesm2015ReflectionHost', () => {
|
||||
|
||||
describe('getDecoratorsOfDeclaration()', () => {
|
||||
|
@ -1186,12 +1209,10 @@ describe('Fesm2015ReflectionHost', () => {
|
|||
|
||||
describe('getGenericArityOfClass()', () => {
|
||||
it('should properly count type parameters', () => {
|
||||
// Mock out reading the `d.ts` file from disk
|
||||
const readFileSyncSpy = spyOn(fs, 'readFileSync').and.returnValue(ARITY_CLASSES[1].contents);
|
||||
const dtsProgram = makeProgram(ARITY_CLASSES[1]);
|
||||
const program = makeProgram(ARITY_CLASSES[0]);
|
||||
|
||||
const dtsMapper = new DtsMapper('/src', '/typings');
|
||||
const host = new Esm2015ReflectionHost(false, program.getTypeChecker(), dtsMapper);
|
||||
const host = new Esm2015ReflectionHost(
|
||||
false, program.getTypeChecker(), ARITY_CLASSES[1].name, dtsProgram);
|
||||
const noTypeParamClass =
|
||||
getDeclaration(program, '/src/class.js', 'NoTypeParam', ts.isClassDeclaration);
|
||||
expect(host.getGenericArityOfClass(noTypeParamClass)).toBe(0);
|
||||
|
@ -1217,30 +1238,95 @@ describe('Fesm2015ReflectionHost', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('findDecoratedFiles()', () => {
|
||||
it('should return an array of objects for each file that has exported and decorated classes',
|
||||
() => {
|
||||
describe('findDecoratedClasses()', () => {
|
||||
it('should return an array of all decorated classes in the given source file', () => {
|
||||
const program = makeProgram(...DECORATED_FILES);
|
||||
const host = new Esm2015ReflectionHost(false, program.getTypeChecker());
|
||||
const primaryFile = program.getSourceFile(DECORATED_FILES[0].name) !;
|
||||
const secondaryFile = program.getSourceFile(DECORATED_FILES[1].name) !;
|
||||
const decoratedFiles = host.findDecoratedFiles(primaryFile);
|
||||
|
||||
expect(decoratedFiles.size).toEqual(2);
|
||||
|
||||
const primary = decoratedFiles.get(primaryFile) !;
|
||||
expect(primary.decoratedClasses.length).toEqual(1);
|
||||
const classA = primary.decoratedClasses.find(c => c.name === 'A') !;
|
||||
expect(classA.name).toEqual('A');
|
||||
const primaryDecoratedClasses = host.findDecoratedClasses(primaryFile);
|
||||
expect(primaryDecoratedClasses.length).toEqual(2);
|
||||
const classA = primaryDecoratedClasses.find(c => c.name === 'A') !;
|
||||
expect(ts.isClassDeclaration(classA.declaration)).toBeTruthy();
|
||||
expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
|
||||
// Note that `B` is not exported from `primary.js`
|
||||
const classB = primaryDecoratedClasses.find(c => c.name === 'B') !;
|
||||
expect(ts.isClassDeclaration(classB.declaration)).toBeTruthy();
|
||||
expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
|
||||
|
||||
const secondary = decoratedFiles.get(secondaryFile) !;
|
||||
expect(secondary.decoratedClasses.length).toEqual(1);
|
||||
const classD = secondary.decoratedClasses.find(c => c.name === 'D') !;
|
||||
const secondaryDecoratedClasses = host.findDecoratedClasses(secondaryFile) !;
|
||||
expect(secondaryDecoratedClasses.length).toEqual(1);
|
||||
// Note that `D` is exported from `secondary.js` but not exported from `primary.js`
|
||||
const classD = secondaryDecoratedClasses.find(c => c.name === 'D') !;
|
||||
expect(classD.name).toEqual('D');
|
||||
expect(ts.isClassDeclaration(classD.declaration)).toBeTruthy();
|
||||
expect(classD.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDtsDeclarationsOfClass()', () => {
|
||||
it('should find the dts declaration that has the same relative path to the source file', () => {
|
||||
const srcProgram = makeProgram(...TYPINGS_SRC_FILES);
|
||||
const dtsProgram = makeProgram(...TYPINGS_DTS_FILES);
|
||||
const class1 = getDeclaration(srcProgram, '/src/class1.js', 'Class1', ts.isClassDeclaration);
|
||||
const host = new Esm2015ReflectionHost(
|
||||
false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram);
|
||||
|
||||
const dtsDeclaration = host.getDtsDeclarationOfClass(class1);
|
||||
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class1.d.ts');
|
||||
});
|
||||
|
||||
it('should throw an error if there is no matching class in the matching dts file', () => {
|
||||
const srcProgram = makeProgram(...TYPINGS_SRC_FILES);
|
||||
const dtsProgram = makeProgram(...TYPINGS_DTS_FILES);
|
||||
const missingClass =
|
||||
getDeclaration(srcProgram, '/src/class1.js', 'MissingClass1', ts.isClassDeclaration);
|
||||
const host = new Esm2015ReflectionHost(
|
||||
false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram);
|
||||
|
||||
expect(() => host.getDtsDeclarationOfClass(missingClass))
|
||||
.toThrowError(
|
||||
'Unable to find matching typings (.d.ts) declaration for MissingClass1 in /src/class1.js');
|
||||
});
|
||||
|
||||
it('should throw an error if there is no matching dts file', () => {
|
||||
const srcProgram = makeProgram(...TYPINGS_SRC_FILES);
|
||||
const dtsProgram = makeProgram(...TYPINGS_DTS_FILES);
|
||||
const missingClass = getDeclaration(
|
||||
srcProgram, '/src/missing-class.js', 'MissingClass2', ts.isClassDeclaration);
|
||||
const host = new Esm2015ReflectionHost(
|
||||
false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram);
|
||||
|
||||
expect(() => host.getDtsDeclarationOfClass(missingClass))
|
||||
.toThrowError(
|
||||
'Unable to find matching typings (.d.ts) declaration for MissingClass2 in /src/missing-class.js');
|
||||
});
|
||||
|
||||
it('should find the dts file that contains a matching class declaration, even if the source files do not match',
|
||||
() => {
|
||||
const srcProgram = makeProgram(...TYPINGS_SRC_FILES);
|
||||
const dtsProgram = makeProgram(...TYPINGS_DTS_FILES);
|
||||
const class1 =
|
||||
getDeclaration(srcProgram, '/src/flat-file.js', 'Class1', ts.isClassDeclaration);
|
||||
const host = new Esm2015ReflectionHost(
|
||||
false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram);
|
||||
|
||||
const dtsDeclaration = host.getDtsDeclarationOfClass(class1);
|
||||
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class1.d.ts');
|
||||
});
|
||||
|
||||
it('should find aliased exports', () => {
|
||||
const srcProgram = makeProgram(...TYPINGS_SRC_FILES);
|
||||
const dtsProgram = makeProgram(...TYPINGS_DTS_FILES);
|
||||
const class3 =
|
||||
getDeclaration(srcProgram, '/src/flat-file.js', 'Class3', ts.isClassDeclaration);
|
||||
const host = new Esm2015ReflectionHost(
|
||||
false, srcProgram.getTypeChecker(), TYPINGS_DTS_FILES[0].name, dtsProgram);
|
||||
|
||||
const dtsDeclaration = host.getDtsDeclarationOfClass(class3);
|
||||
expect(dtsDeclaration !.getSourceFile().fileName).toEqual('/typings/class3.d.ts');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -432,6 +432,7 @@ const DECORATED_FILES = [
|
|||
name: '/primary.js',
|
||||
contents: `
|
||||
import {Directive} from '@angular/core';
|
||||
import { D } from '/secondary';
|
||||
var A = (function() {
|
||||
function A() {}
|
||||
A.decorators = [
|
||||
|
@ -453,7 +454,6 @@ const DECORATED_FILES = [
|
|||
return C;
|
||||
});
|
||||
export { A, x, C };
|
||||
export { D } from '/secondary';
|
||||
`
|
||||
},
|
||||
{
|
||||
|
@ -1252,24 +1252,25 @@ describe('Esm5ReflectionHost', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('fileDecoratedFiles()', () => {
|
||||
it('should return an array of objects for each file that has exported and decorated classes',
|
||||
() => {
|
||||
describe('findDecoratedClasses()', () => {
|
||||
it('should return an array of all decorated classes in the given source file', () => {
|
||||
const program = makeProgram(...DECORATED_FILES);
|
||||
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
||||
const primary = program.getSourceFile(DECORATED_FILES[0].name) !;
|
||||
const decoratedFiles = host.findDecoratedFiles(primary);
|
||||
expect(decoratedFiles.size).toEqual(2);
|
||||
const primaryClasses = decoratedFiles.get(primary) !.decoratedClasses;
|
||||
expect(primaryClasses.length).toEqual(1);
|
||||
const classA = primaryClasses.find(c => c.name === 'A') !;
|
||||
expect(classA.name).toEqual('A');
|
||||
|
||||
const primaryDecoratedClasses = host.findDecoratedClasses(primary);
|
||||
expect(primaryDecoratedClasses.length).toEqual(2);
|
||||
const classA = primaryDecoratedClasses.find(c => c.name === 'A') !;
|
||||
expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
|
||||
// Note that `B` is not exported from `primary.js`
|
||||
const classB = primaryDecoratedClasses.find(c => c.name === 'B') !;
|
||||
expect(classB.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
|
||||
|
||||
const secondary = program.getSourceFile(DECORATED_FILES[1].name) !;
|
||||
const secondaryClasses = decoratedFiles.get(secondary) !.decoratedClasses;
|
||||
expect(secondaryClasses.length).toEqual(1);
|
||||
const classD = secondaryClasses.find(c => c.name === 'D') !;
|
||||
const secondaryDecoratedClasses = host.findDecoratedClasses(secondary);
|
||||
expect(secondaryDecoratedClasses.length).toEqual(1);
|
||||
// Note that `D` is exported from `secondary.js` but not exported from `primary.js`
|
||||
const classD = secondaryDecoratedClasses.find(c => c.name === 'D') !;
|
||||
expect(classD.name).toEqual('D');
|
||||
expect(classD.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
|
||||
});
|
||||
|
|
|
@ -12,7 +12,6 @@ import MagicString from 'magic-string';
|
|||
import {makeProgram} from '../helpers/utils';
|
||||
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
||||
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
|
||||
import {DtsMapper} from '../../src/host/dts_mapper';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
import {EsmRenderer} from '../../src/rendering/esm_renderer';
|
||||
|
||||
|
@ -24,7 +23,7 @@ function setup(file: {name: string, contents: string}, transformDts: boolean = f
|
|||
const decorationAnalyses =
|
||||
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
|
||||
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program);
|
||||
const renderer = new EsmRenderer(host, false, null, dir, dir, null);
|
||||
const renderer = new EsmRenderer(host, false, null, dir, dir);
|
||||
return {host, program, sourceFile, renderer, decorationAnalyses, switchMarkerAnalyses};
|
||||
}
|
||||
|
||||
|
@ -161,8 +160,9 @@ export class A {}`);
|
|||
it('should insert the definitions directly after the class declaration', () => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const analyzedClass = decorationAnalyses.get(sourceFile) !.analyzedClasses[0];
|
||||
renderer.addDefinitions(output, analyzedClass, 'SOME DEFINITION TEXT');
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||
renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT');
|
||||
expect(output.toString()).toContain(`
|
||||
export class A {}
|
||||
SOME DEFINITION TEXT
|
||||
|
@ -179,9 +179,9 @@ A.decorators = [
|
|||
() => {
|
||||
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const analyzedClass =
|
||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
|
||||
const decorator = analyzedClass.decorators[0];
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||
const decorator = compiledClass.decorators[0];
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
|
@ -198,9 +198,9 @@ A.decorators = [
|
|||
() => {
|
||||
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const analyzedClass =
|
||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
|
||||
const decorator = analyzedClass.decorators[0];
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
|
||||
const decorator = compiledClass.decorators[0];
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
|
@ -217,9 +217,9 @@ A.decorators = [
|
|||
() => {
|
||||
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const analyzedClass =
|
||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
|
||||
const decorator = analyzedClass.decorators[0];
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
|
||||
const decorator = compiledClass.decorators[0];
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
|
@ -238,9 +238,9 @@ A.decorators = [
|
|||
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||
const analyzedClass =
|
||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
|
||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
|
@ -255,9 +255,9 @@ A.decorators = [
|
|||
() => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||
const analyzedClass =
|
||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
|
||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
|
||||
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
|
@ -273,9 +273,9 @@ A.decorators = [
|
|||
() => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||
const analyzedClass =
|
||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
|
||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
|
||||
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
|
|
|
@ -20,7 +20,7 @@ function setup(file: {name: string, contents: string}) {
|
|||
const decorationAnalyses =
|
||||
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
|
||||
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).analyzeProgram(program);
|
||||
const renderer = new EsmRenderer(host, false, null, '', '', null);
|
||||
const renderer = new EsmRenderer(host, false, null, '', '');
|
||||
return {host, program, sourceFile, renderer, decorationAnalyses, switchMarkerAnalyses};
|
||||
}
|
||||
|
||||
|
@ -182,8 +182,9 @@ var A = (function() {`);
|
|||
it('should insert the definitions directly after the class declaration', () => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const analyzedClass = decorationAnalyses.get(sourceFile) !.analyzedClasses[0];
|
||||
renderer.addDefinitions(output, analyzedClass, 'SOME DEFINITION TEXT');
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||
renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT');
|
||||
expect(output.toString()).toContain(`
|
||||
function A() {}
|
||||
SOME DEFINITION TEXT
|
||||
|
@ -199,9 +200,9 @@ SOME DEFINITION TEXT
|
|||
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const analyzedClass =
|
||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
|
||||
const decorator = analyzedClass.decorators[0];
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||
const decorator = compiledClass.decorators[0];
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
|
@ -217,9 +218,9 @@ SOME DEFINITION TEXT
|
|||
() => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const analyzedClass =
|
||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
|
||||
const decorator = analyzedClass.decorators[0];
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
|
||||
const decorator = compiledClass.decorators[0];
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
|
@ -236,9 +237,9 @@ SOME DEFINITION TEXT
|
|||
() => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||
const output = new MagicString(PROGRAM.contents);
|
||||
const analyzedClass =
|
||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
|
||||
const decorator = analyzedClass.decorators[0];
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
|
||||
const decorator = compiledClass.decorators[0];
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
|
@ -257,9 +258,9 @@ SOME DEFINITION TEXT
|
|||
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||
const analyzedClass =
|
||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
|
||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
|
@ -274,9 +275,9 @@ SOME DEFINITION TEXT
|
|||
() => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||
const analyzedClass =
|
||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
|
||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
|
||||
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
|
@ -292,9 +293,9 @@ SOME DEFINITION TEXT
|
|||
() => {
|
||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||
const analyzedClass =
|
||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
|
||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
||||
const compiledClass =
|
||||
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
|
||||
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
|
||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||
renderer.removeDecorators(output, decoratorsToRemove);
|
||||
|
|
|
@ -11,20 +11,20 @@ import * as ts from 'typescript';
|
|||
import MagicString from 'magic-string';
|
||||
import {fromObject, generateMapFileComment} from 'convert-source-map';
|
||||
import {makeProgram} from '../helpers/utils';
|
||||
import {AnalyzedClass, DecorationAnalyzer, DecorationAnalyses} from '../../src/analysis/decoration_analyzer';
|
||||
import {CompiledClass, DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
||||
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
import {Renderer} from '../../src/rendering/renderer';
|
||||
|
||||
class TestRenderer extends Renderer {
|
||||
constructor(host: Esm2015ReflectionHost) { super(host, false, null, '/src', '/dist', null); }
|
||||
constructor(host: Esm2015ReflectionHost) { super(host, false, null, '/src', '/dist'); }
|
||||
addImports(output: MagicString, imports: {name: string, as: string}[]) {
|
||||
output.prepend('\n// ADD IMPORTS\n');
|
||||
}
|
||||
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
||||
output.prepend('\n// ADD CONSTANTS\n');
|
||||
}
|
||||
addDefinitions(output: MagicString, analyzedClass: AnalyzedClass, definitions: string) {
|
||||
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string) {
|
||||
output.prepend('\n// ADD DEFINITIONS\n');
|
||||
}
|
||||
removeDecorators(output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>) {
|
||||
|
|
|
@ -12,12 +12,13 @@ import * as path from 'path';
|
|||
import * as ts from 'typescript';
|
||||
|
||||
export function makeProgram(
|
||||
files: {name: string, contents: string}[], options?: ts.CompilerOptions,
|
||||
files: {name: string, contents: string, isRoot?: boolean}[], options?: ts.CompilerOptions,
|
||||
host: ts.CompilerHost = new InMemoryHost(),
|
||||
checkForErrors: boolean = true): {program: ts.Program, host: ts.CompilerHost} {
|
||||
files.forEach(file => host.writeFile(file.name, file.contents, false, undefined, []));
|
||||
|
||||
const rootNames = files.map(file => host.getCanonicalFileName(file.name));
|
||||
const rootNames =
|
||||
files.filter(file => file.isRoot !== false).map(file => host.getCanonicalFileName(file.name));
|
||||
const program = ts.createProgram(
|
||||
rootNames, {
|
||||
noLib: true,
|
||||
|
|
Loading…
Reference in New Issue