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 {CompileResult, DecoratorHandler} from '../../../ngtsc/transform';
|
||||||
|
|
||||||
import {DecoratedClass} from '../host/decorated_class';
|
import {DecoratedClass} from '../host/decorated_class';
|
||||||
import {DecoratedFile} from '../host/decorated_file';
|
|
||||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||||
import {isDefined} from '../utils';
|
import {isDefined} from '../utils';
|
||||||
|
|
||||||
export interface AnalyzedClass<A = any, M = any> extends DecoratedClass {
|
export interface AnalyzedFile {
|
||||||
handler: DecoratorHandler<A, M>;
|
sourceFile: ts.SourceFile;
|
||||||
analysis: any;
|
analyzedClasses: AnalyzedClass[];
|
||||||
diagnostics?: ts.Diagnostic[];
|
|
||||||
compilation: CompileResult[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DecorationAnalysis {
|
export interface AnalyzedClass extends DecoratedClass {
|
||||||
analyzedClasses: AnalyzedClass[];
|
diagnostics?: ts.Diagnostic[];
|
||||||
|
handler: DecoratorHandler<any, any>;
|
||||||
|
analysis: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompiledClass extends AnalyzedClass { compilation: CompileResult[]; }
|
||||||
|
|
||||||
|
export interface CompiledFile {
|
||||||
|
compiledClasses: CompiledClass[];
|
||||||
sourceFile: ts.SourceFile;
|
sourceFile: ts.SourceFile;
|
||||||
constantPool: ConstantPool;
|
constantPool: ConstantPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DecorationAnalyses = Map<ts.SourceFile, DecorationAnalysis>;
|
export type DecorationAnalyses = Map<ts.SourceFile, CompiledFile>;
|
||||||
export const DecorationAnalyses = Map;
|
export const DecorationAnalyses = Map;
|
||||||
|
|
||||||
export interface MatchingHandler<A, M> {
|
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.
|
* @returns a map of the source files to the analysis for those files.
|
||||||
*/
|
*/
|
||||||
analyzeProgram(program: ts.Program): DecorationAnalyses {
|
analyzeProgram(program: ts.Program): DecorationAnalyses {
|
||||||
const analyzedFiles = new DecorationAnalyses();
|
const decorationAnalyses = new DecorationAnalyses();
|
||||||
program.getRootFileNames().forEach(fileName => {
|
const analysedFiles =
|
||||||
const entryPoint = program.getSourceFile(fileName) !;
|
program.getSourceFiles().map(sourceFile => this.analyzeFile(sourceFile)).filter(isDefined);
|
||||||
const decoratedFiles = this.host.findDecoratedFiles(entryPoint);
|
const compiledFiles = analysedFiles.map(analysedFile => this.compileFile(analysedFile));
|
||||||
decoratedFiles.forEach(
|
compiledFiles.forEach(
|
||||||
decoratedFile =>
|
compiledFile => decorationAnalyses.set(compiledFile.sourceFile, compiledFile));
|
||||||
analyzedFiles.set(decoratedFile.sourceFile, this.analyzeFile(decoratedFile)));
|
return decorationAnalyses;
|
||||||
});
|
|
||||||
return analyzedFiles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected analyzeFile(sourceFile: ts.SourceFile): AnalyzedFile|undefined {
|
||||||
* Analyze a decorated file to generate the information about decorated classes that
|
const decoratedClasses = this.host.findDecoratedClasses(sourceFile);
|
||||||
* should be converted to use ivy definitions.
|
return decoratedClasses.length ? {
|
||||||
* @param file The file to be analysed for decorated classes.
|
sourceFile,
|
||||||
* @returns the analysis of the file
|
analyzedClasses: decoratedClasses.map(clazz => this.analyzeClass(clazz)).filter(isDefined)
|
||||||
*/
|
} :
|
||||||
protected analyzeFile(file: DecoratedFile): DecorationAnalysis {
|
undefined;
|
||||||
const constantPool = new ConstantPool();
|
|
||||||
const analyzedClasses =
|
|
||||||
file.decoratedClasses.map(clazz => this.analyzeClass(constantPool, clazz))
|
|
||||||
.filter(isDefined);
|
|
||||||
|
|
||||||
return {
|
|
||||||
analyzedClasses,
|
|
||||||
sourceFile: file.sourceFile, constantPool,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected analyzeClass(pool: ConstantPool, clazz: DecoratedClass): AnalyzedClass|undefined {
|
protected analyzeClass(clazz: DecoratedClass): AnalyzedClass|null {
|
||||||
const matchingHandlers = this.handlers
|
const matchingHandlers = this.handlers
|
||||||
.map(handler => ({
|
.map(handler => {
|
||||||
handler,
|
const match =
|
||||||
match: handler.detect(clazz.declaration, clazz.decorators),
|
handler.detect(clazz.declaration, clazz.decorators);
|
||||||
}))
|
return {handler, match};
|
||||||
|
})
|
||||||
.filter(isMatchingHandler);
|
.filter(isMatchingHandler);
|
||||||
|
|
||||||
if (matchingHandlers.length > 1) {
|
if (matchingHandlers.length > 1) {
|
||||||
throw new Error('TODO.Diagnostic: Class has multiple Angular decorators.');
|
throw new Error('TODO.Diagnostic: Class has multiple Angular decorators.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchingHandlers.length === 0) {
|
if (matchingHandlers.length === 0) {
|
||||||
return undefined;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {handler, match} = matchingHandlers[0];
|
const {handler, match} = matchingHandlers[0];
|
||||||
const {analysis, diagnostics} = handler.analyze(clazz.declaration, match);
|
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)) {
|
if (!Array.isArray(compilation)) {
|
||||||
compilation = [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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {readFileSync} from 'fs';
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ClassMember, ClassMemberKind, CtorParameter, Decorator, Import} from '../../../ngtsc/host';
|
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 {findAll, getNameText, getOriginalSymbol, isDefined} from '../utils';
|
||||||
|
|
||||||
import {DecoratedClass} from './decorated_class';
|
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';
|
import {NgccReflectionHost, PRE_R3_MARKER, SwitchableVariableDeclaration, isSwitchableVariableDeclaration} from './ngcc_host';
|
||||||
|
|
||||||
export const DECORATORS = 'decorators' as ts.__String;
|
export const DECORATORS = 'decorators' as ts.__String;
|
||||||
|
@ -51,8 +48,14 @@ export const CONSTRUCTOR_PARAMS = 'ctorParameters' as ts.__String;
|
||||||
* a static method called `ctorParameters`.
|
* a static method called `ctorParameters`.
|
||||||
*/
|
*/
|
||||||
export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements NgccReflectionHost {
|
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);
|
super(checker);
|
||||||
|
this.dtsClassMap = (dtsRootFileName && dtsProgram) ?
|
||||||
|
this.computeDtsClassMap(dtsRootFileName, dtsProgram) :
|
||||||
|
null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,12 +76,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||||
if (!symbol) {
|
if (!symbol) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const decoratorsProperty = this.getStaticProperty(symbol, DECORATORS);
|
return this.getDecoratorsOfSymbol(symbol);
|
||||||
if (decoratorsProperty) {
|
|
||||||
return this.getClassDecoratorsFromStaticProperty(decoratorsProperty);
|
|
||||||
} else {
|
|
||||||
return this.getClassDecoratorsFromHelperCall(symbol);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -206,10 +204,11 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||||
if (ts.isClassDeclaration(declaration)) {
|
if (ts.isClassDeclaration(declaration)) {
|
||||||
return declaration.name && this.checker.getSymbolAtLocation(declaration.name);
|
return declaration.name && this.checker.getSymbolAtLocation(declaration.name);
|
||||||
}
|
}
|
||||||
if (ts.isVariableDeclaration(declaration) && declaration.initializer &&
|
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
||||||
ts.isClassExpression(declaration.initializer)) {
|
declaration = declaration.initializer;
|
||||||
return declaration.initializer.name &&
|
}
|
||||||
this.checker.getSymbolAtLocation(declaration.initializer.name);
|
if (ts.isClassExpression(declaration)) {
|
||||||
|
return declaration.name && this.checker.getSymbolAtLocation(declaration.name);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -299,46 +298,29 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||||
return super.getImportOfIdentifier(id) || this.getImportOfNamespacedIdentifier(id);
|
return super.getImportOfIdentifier(id) || this.getImportOfNamespacedIdentifier(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Find all the files accessible via an entry-point, that contain decorated classes.
|
* Find all the classes that contain decorations in a given file.
|
||||||
* @param entryPoint The starting point file for finding files that contain decorated classes.
|
* @param sourceFile The source file to search for decorated classes.
|
||||||
* @returns A collection of files objects that hold info about the decorated classes and import
|
* @returns An array of decorated classes.
|
||||||
* information.
|
|
||||||
*/
|
*/
|
||||||
findDecoratedFiles(entryPoint: ts.SourceFile): Map<ts.SourceFile, DecoratedFile> {
|
findDecoratedClasses(sourceFile: ts.SourceFile): DecoratedClass[] {
|
||||||
const moduleSymbol = this.checker.getSymbolAtLocation(entryPoint);
|
const classes: DecoratedClass[] = [];
|
||||||
const map = new Map<ts.SourceFile, DecoratedFile>();
|
sourceFile.statements.map(statement => {
|
||||||
if (moduleSymbol) {
|
if (ts.isVariableStatement(statement)) {
|
||||||
const exportedSymbols =
|
statement.declarationList.declarations.forEach(declaration => {
|
||||||
this.checker.getExportsOfModule(moduleSymbol).map(getOriginalSymbol(this.checker));
|
const decoratedClass = this.getDecoratedClassFromSymbol(this.getClassSymbol(declaration));
|
||||||
const exportedDeclarations =
|
if (decoratedClass) {
|
||||||
exportedSymbols.map(exportSymbol => exportSymbol.valueDeclaration).filter(isDefined);
|
classes.push(decoratedClass);
|
||||||
|
}
|
||||||
const decoratedClasses =
|
});
|
||||||
exportedDeclarations
|
} else if (ts.isClassDeclaration(statement)) {
|
||||||
.map(declaration => {
|
const decoratedClass = this.getDecoratedClassFromSymbol(this.getClassSymbol(statement));
|
||||||
if (ts.isClassDeclaration(declaration) || ts.isVariableDeclaration(declaration)) {
|
if (decoratedClass) {
|
||||||
const name = declaration.name && ts.isIdentifier(declaration.name) ?
|
classes.push(decoratedClass);
|
||||||
declaration.name.text :
|
|
||||||
undefined;
|
|
||||||
const decorators = this.getDecoratorsOfDeclaration(declaration);
|
|
||||||
return decorators && isDefined(name) ?
|
|
||||||
new DecoratedClass(name, declaration, decorators) :
|
|
||||||
undefined;
|
|
||||||
}
|
|
||||||
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);
|
}
|
||||||
});
|
});
|
||||||
}
|
return classes;
|
||||||
return map;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -348,21 +330,38 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||||
* is not a class or has an unknown number of type parameters.
|
* is not a class or has an unknown number of type parameters.
|
||||||
*/
|
*/
|
||||||
getGenericArityOfClass(clazz: ts.Declaration): number|null {
|
getGenericArityOfClass(clazz: ts.Declaration): number|null {
|
||||||
if (this.dtsMapper && ts.isClassDeclaration(clazz) && clazz.name) {
|
const dtsClass = this.getDtsDeclarationOfClass(clazz);
|
||||||
const sourcePath = clazz.getSourceFile();
|
if (dtsClass) {
|
||||||
const dtsPath = this.dtsMapper.getDtsFileNameFor(sourcePath.fileName);
|
return dtsClass.typeParameters ? dtsClass.typeParameters.length : 0;
|
||||||
const dtsContents = readFileSync(dtsPath, 'utf8');
|
}
|
||||||
// TODO: investigate caching parsed .d.ts files as they're needed for several different
|
return null;
|
||||||
// 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];
|
* Take an exported declaration of a class (maybe downleveled to a variable) and look up the
|
||||||
if (ts.isClassDeclaration(stmt) && stmt.name !== undefined &&
|
* declaration of its type in a separate .d.ts tree.
|
||||||
stmt.name.text === clazz.name.text) {
|
*
|
||||||
return stmt.typeParameters ? stmt.typeParameters.length : 0;
|
* 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;
|
return null;
|
||||||
|
@ -371,6 +370,25 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||||
|
|
||||||
///////////// Protected Helpers /////////////
|
///////////// 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.
|
* Walk the AST looking for an assignment to the specified symbol.
|
||||||
* @param node The current node we are searching.
|
* @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 value: ts.Expression|null = null;
|
||||||
let name: string|null = null;
|
let name: string|null = null;
|
||||||
let nameNode: ts.Identifier|null = null;
|
let nameNode: ts.Identifier|null = null;
|
||||||
let type = null;
|
|
||||||
|
|
||||||
|
|
||||||
const node = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0];
|
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);
|
node.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const type: ts.TypeNode = (node as any).type || null;
|
||||||
return {
|
return {
|
||||||
node,
|
node,
|
||||||
implementation: node, kind, type, name, nameNode, value, isStatic,
|
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.
|
* @param decorator the decorator to test.
|
||||||
*/
|
*/
|
||||||
isFromCore(decorator: Decorator): boolean {
|
protected isFromCore(decorator: Decorator): boolean {
|
||||||
if (this.isCore) {
|
if (this.isCore) {
|
||||||
return !decorator.import || /^\./.test(decorator.import.from);
|
return !decorator.import || /^\./.test(decorator.import.from);
|
||||||
} else {
|
} else {
|
||||||
return !!decorator.import && decorator.import.from === '@angular/core';
|
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 /////////////
|
///////////// Exported Helpers /////////////
|
||||||
|
|
|
@ -10,10 +10,8 @@ import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ClassMember, ClassMemberKind, Decorator, FunctionDefinition, Parameter} from '../../../ngtsc/host';
|
import {ClassMember, ClassMemberKind, Decorator, FunctionDefinition, Parameter} from '../../../ngtsc/host';
|
||||||
import {reflectObjectLiteral} from '../../../ngtsc/metadata';
|
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';
|
import {Esm2015ReflectionHost, ParamInfo, getPropertyValueFromSymbol, isAssignmentStatement} from './esm2015_host';
|
||||||
|
|
||||||
|
|
||||||
|
@ -125,41 +123,6 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||||
return {node, body: statements || null, parameters};
|
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 /////////////
|
///////////// Protected Helpers /////////////
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {ReflectionHost} from '../../../ngtsc/host';
|
import {ReflectionHost} from '../../../ngtsc/host';
|
||||||
import {DecoratedFile} from './decorated_file';
|
import {DecoratedClass} from './decorated_class';
|
||||||
|
|
||||||
export const PRE_R3_MARKER = '__PRE_R3__';
|
export const PRE_R3_MARKER = '__PRE_R3__';
|
||||||
export const POST_R3_MARKER = '__POST_R3__';
|
export const POST_R3_MARKER = '__POST_R3__';
|
||||||
|
@ -40,10 +40,9 @@ export interface NgccReflectionHost extends ReflectionHost {
|
||||||
getSwitchableDeclarations(module: ts.Node): SwitchableVariableDeclaration[];
|
getSwitchableDeclarations(module: ts.Node): SwitchableVariableDeclaration[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all the files accessible via an entry-point, that contain decorated classes.
|
* Find all the classes that contain decorations in a given file.
|
||||||
* @param entryPoint The starting point file for finding files that contain decorated classes.
|
* @param sourceFile The source file to search for decorated classes.
|
||||||
* @returns A collection of files objects that hold info about the decorated classes and import
|
* @returns An array of decorated classes.
|
||||||
* information.
|
|
||||||
*/
|
*/
|
||||||
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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
import * as path from 'canonical-path';
|
import * as path from 'canonical-path';
|
||||||
import {existsSync, lstatSync, readFileSync, readdirSync} from 'fs';
|
|
||||||
import * as yargs from 'yargs';
|
import * as yargs from 'yargs';
|
||||||
|
|
||||||
import {DependencyHost} from './packages/dependency_host';
|
import {DependencyHost} from './packages/dependency_host';
|
||||||
|
@ -48,8 +47,14 @@ export function mainNgcc(args: string[]): number {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {entryPoints} = finder.findEntryPoints(sourcePath);
|
const {entryPoints} = finder.findEntryPoints(sourcePath);
|
||||||
entryPoints.forEach(
|
entryPoints.forEach(entryPoint => {
|
||||||
entryPoint => formats.forEach(format => transformer.transform(entryPoint, format)));
|
// 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) {
|
} catch (e) {
|
||||||
console.error(e.stack);
|
console.error(e.stack);
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
@ -12,7 +12,6 @@ import * as ts from 'typescript';
|
||||||
|
|
||||||
import {DecorationAnalyzer} from '../analysis/decoration_analyzer';
|
import {DecorationAnalyzer} from '../analysis/decoration_analyzer';
|
||||||
import {SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer';
|
import {SwitchMarkerAnalyzer} from '../analysis/switch_marker_analyzer';
|
||||||
import {DtsMapper} from '../host/dts_mapper';
|
|
||||||
import {Esm2015ReflectionHost} from '../host/esm2015_host';
|
import {Esm2015ReflectionHost} from '../host/esm2015_host';
|
||||||
import {Esm5ReflectionHost} from '../host/esm5_host';
|
import {Esm5ReflectionHost} from '../host/esm5_host';
|
||||||
import {NgccReflectionHost} from '../host/ngcc_host';
|
import {NgccReflectionHost} from '../host/ngcc_host';
|
||||||
|
@ -44,7 +43,7 @@ import {EntryPoint, EntryPointFormat} from './entry_point';
|
||||||
export class Transformer {
|
export class Transformer {
|
||||||
constructor(private sourcePath: string, private targetPath: string) {}
|
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)) {
|
if (checkMarkerFile(entryPoint, format)) {
|
||||||
console.warn(`Skipping ${entryPoint.name} : ${format} (already built).`);
|
console.warn(`Skipping ${entryPoint.name} : ${format} (already built).`);
|
||||||
return;
|
return;
|
||||||
|
@ -73,19 +72,24 @@ export class Transformer {
|
||||||
const r3SymbolsPath = isCore ? this.findR3SymbolsPath(dirname(entryPointFilePath)) : null;
|
const r3SymbolsPath = isCore ? this.findR3SymbolsPath(dirname(entryPointFilePath)) : null;
|
||||||
const rootPaths = r3SymbolsPath ? [entryPointFilePath, r3SymbolsPath] : [entryPointFilePath];
|
const rootPaths = r3SymbolsPath ? [entryPointFilePath, r3SymbolsPath] : [entryPointFilePath];
|
||||||
const packageProgram = ts.createProgram(rootPaths, options, host);
|
const packageProgram = ts.createProgram(rootPaths, options, host);
|
||||||
const dtsMapper = new DtsMapper(dirname(entryPointFilePath), dirname(entryPoint.typings));
|
console.time(entryPoint.name + '(dtsmappper creation)');
|
||||||
const reflectionHost = this.getHost(isCore, format, packageProgram, dtsMapper);
|
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;
|
const r3SymbolsFile = r3SymbolsPath && packageProgram.getSourceFile(r3SymbolsPath) || null;
|
||||||
|
|
||||||
// Parse and analyze the files.
|
// Parse and analyze the files.
|
||||||
const {decorationAnalyses, switchMarkerAnalyses} =
|
const {decorationAnalyses, switchMarkerAnalyses} =
|
||||||
this.analyzeProgram(packageProgram, reflectionHost, rootDirs, isCore);
|
this.analyzeProgram(packageProgram, reflectionHost, rootDirs, isCore);
|
||||||
|
|
||||||
|
console.time(entryPoint.name + '(rendering)');
|
||||||
// Transform the source files and source maps.
|
// Transform the source files and source maps.
|
||||||
const renderer =
|
const renderer =
|
||||||
this.getRenderer(format, packageProgram, reflectionHost, isCore, r3SymbolsFile, dtsMapper);
|
this.getRenderer(format, packageProgram, reflectionHost, isCore, r3SymbolsFile);
|
||||||
const renderedFiles =
|
const renderedFiles =
|
||||||
renderer.renderProgram(packageProgram, decorationAnalyses, switchMarkerAnalyses);
|
renderer.renderProgram(packageProgram, decorationAnalyses, switchMarkerAnalyses);
|
||||||
|
console.timeEnd(entryPoint.name + '(rendering)');
|
||||||
|
|
||||||
// Write out all the transformed files.
|
// Write out all the transformed files.
|
||||||
renderedFiles.forEach(file => this.writeFile(file));
|
renderedFiles.forEach(file => this.writeFile(file));
|
||||||
|
@ -104,12 +108,13 @@ export class Transformer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getHost(isCore: boolean, format: string, program: ts.Program, dtsMapper: DtsMapper):
|
getHost(
|
||||||
NgccReflectionHost {
|
isCore: boolean, format: string, program: ts.Program, dtsFilePath: string,
|
||||||
|
dtsProgram: ts.Program|null): NgccReflectionHost {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case 'esm2015':
|
case 'esm2015':
|
||||||
case 'fesm2015':
|
case 'fesm2015':
|
||||||
return new Esm2015ReflectionHost(isCore, program.getTypeChecker(), dtsMapper);
|
return new Esm2015ReflectionHost(isCore, program.getTypeChecker(), dtsFilePath, dtsProgram);
|
||||||
case 'esm5':
|
case 'esm5':
|
||||||
case 'fesm5':
|
case 'fesm5':
|
||||||
return new Esm5ReflectionHost(isCore, program.getTypeChecker());
|
return new Esm5ReflectionHost(isCore, program.getTypeChecker());
|
||||||
|
@ -120,13 +125,14 @@ export class Transformer {
|
||||||
|
|
||||||
getRenderer(
|
getRenderer(
|
||||||
format: string, program: ts.Program, host: NgccReflectionHost, isCore: boolean,
|
format: string, program: ts.Program, host: NgccReflectionHost, isCore: boolean,
|
||||||
rewriteCoreImportsTo: ts.SourceFile|null, dtsMapper: DtsMapper|null): Renderer {
|
rewriteCoreImportsTo: ts.SourceFile|null): Renderer {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case 'esm2015':
|
case 'esm2015':
|
||||||
case 'esm5':
|
case 'esm5':
|
||||||
case 'fesm2015':
|
case 'fesm2015':
|
||||||
case 'fesm5':
|
case 'fesm5':
|
||||||
return new EsmRenderer(host, isCore, rewriteCoreImportsTo, this.sourcePath, this.targetPath, dtsMapper);
|
return new EsmRenderer(
|
||||||
|
host, isCore, rewriteCoreImportsTo, this.sourcePath, this.targetPath);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Renderer for "${format}" not yet implemented.`);
|
throw new Error(`Renderer for "${format}" not yet implemented.`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,17 +7,16 @@
|
||||||
*/
|
*/
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import MagicString from 'magic-string';
|
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 {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';
|
import {Renderer} from './renderer';
|
||||||
|
|
||||||
export class EsmRenderer extends Renderer {
|
export class EsmRenderer extends Renderer {
|
||||||
constructor(
|
constructor(
|
||||||
protected host: NgccReflectionHost, protected isCore: boolean,
|
protected host: NgccReflectionHost, protected isCore: boolean,
|
||||||
protected rewriteCoreImportsTo: ts.SourceFile|null, protected sourcePath: string,
|
protected rewriteCoreImportsTo: ts.SourceFile|null, protected sourcePath: string,
|
||||||
protected targetPath: string, dtsMapper: DtsMapper|null) {
|
protected targetPath: string) {
|
||||||
super(host, isCore, rewriteCoreImportsTo, sourcePath, targetPath, dtsMapper);
|
super(host, isCore, rewriteCoreImportsTo, sourcePath, targetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,10 +44,10 @@ export class EsmRenderer extends Renderer {
|
||||||
/**
|
/**
|
||||||
* Add the definitions to each decorated class
|
* Add the definitions to each decorated class
|
||||||
*/
|
*/
|
||||||
addDefinitions(output: MagicString, analyzedClass: AnalyzedClass, definitions: string): void {
|
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void {
|
||||||
const classSymbol = this.host.getClassSymbol(analyzedClass.declaration);
|
const classSymbol = this.host.getClassSymbol(compiledClass.declaration);
|
||||||
if (!classSymbol) {
|
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();
|
const insertionPoint = classSymbol.valueDeclaration !.getEnd();
|
||||||
output.appendLeft(insertionPoint, '\n' + definitions);
|
output.appendLeft(insertionPoint, '\n' + definitions);
|
||||||
|
|
|
@ -14,13 +14,12 @@ import {SourceMapConsumer, SourceMapGenerator, RawSourceMap} from 'source-map';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {Decorator} from '../../../ngtsc/host';
|
import {Decorator} from '../../../ngtsc/host';
|
||||||
import {DtsFileTransformer} from '../../../ngtsc/transform';
|
import {CompileResult} from '@angular/compiler-cli/src/ngtsc/transform';
|
||||||
import {translateStatement} from '../../../ngtsc/translator';
|
import {translateStatement, translateType} from '../../../ngtsc/translator';
|
||||||
import {NgccImportManager} from './ngcc_import_manager';
|
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 {SwitchMarkerAnalyses, SwitchMarkerAnalysis} from '../analysis/switch_marker_analyzer';
|
||||||
import {IMPORT_PREFIX} from '../constants';
|
import {IMPORT_PREFIX} from '../constants';
|
||||||
import {DtsMapper} from '../host/dts_mapper';
|
|
||||||
import {NgccReflectionHost, SwitchableVariableDeclaration} from '../host/ngcc_host';
|
import {NgccReflectionHost, SwitchableVariableDeclaration} from '../host/ngcc_host';
|
||||||
|
|
||||||
interface SourceMapInfo {
|
interface SourceMapInfo {
|
||||||
|
@ -29,20 +28,6 @@ interface SourceMapInfo {
|
||||||
isInline: boolean;
|
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.
|
* Information about a file that has been rendered.
|
||||||
*/
|
*/
|
||||||
|
@ -57,6 +42,11 @@ export interface FileInfo {
|
||||||
contents: string;
|
contents: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DtsClassInfo {
|
||||||
|
dtsDeclaration: ts.Declaration;
|
||||||
|
compilation: CompileResult[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A base-class for rendering an `AnalyzedFile`.
|
* A base-class for rendering an `AnalyzedFile`.
|
||||||
*
|
*
|
||||||
|
@ -67,39 +57,38 @@ export abstract class Renderer {
|
||||||
constructor(
|
constructor(
|
||||||
protected host: NgccReflectionHost, protected isCore: boolean,
|
protected host: NgccReflectionHost, protected isCore: boolean,
|
||||||
protected rewriteCoreImportsTo: ts.SourceFile|null, protected sourcePath: string,
|
protected rewriteCoreImportsTo: ts.SourceFile|null, protected sourcePath: string,
|
||||||
protected targetPath: string, protected dtsMapper: DtsMapper|null) {}
|
protected targetPath: string) {}
|
||||||
|
|
||||||
renderProgram(
|
renderProgram(
|
||||||
program: ts.Program, decorationAnalyses: DecorationAnalyses,
|
program: ts.Program, decorationAnalyses: DecorationAnalyses,
|
||||||
switchMarkerAnalyses: SwitchMarkerAnalyses): FileInfo[] {
|
switchMarkerAnalyses: SwitchMarkerAnalyses): FileInfo[] {
|
||||||
const renderedFiles: FileInfo[] = [];
|
const renderedFiles: FileInfo[] = [];
|
||||||
|
|
||||||
// Transform the source files, source maps and typings files.
|
// Transform the source files.
|
||||||
program.getSourceFiles().map(sourceFile => {
|
program.getSourceFiles().map(sourceFile => {
|
||||||
const decorationAnalysis = decorationAnalyses.get(sourceFile);
|
const compiledFile = decorationAnalyses.get(sourceFile);
|
||||||
const switchMarkerAnalysis = switchMarkerAnalyses.get(sourceFile);
|
const switchMarkerAnalysis = switchMarkerAnalyses.get(sourceFile);
|
||||||
|
|
||||||
if (decorationAnalysis || switchMarkerAnalysis) {
|
if (compiledFile || switchMarkerAnalysis) {
|
||||||
const targetPath = resolve(this.targetPath, relative(this.sourcePath, sourceFile.fileName));
|
renderedFiles.push(...this.renderFile(sourceFile, compiledFile, switchMarkerAnalysis));
|
||||||
renderedFiles.push(
|
|
||||||
...this.renderFile(sourceFile, decorationAnalysis, switchMarkerAnalysis, targetPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decorationAnalyses) {
|
|
||||||
renderedFiles.push(...this.renderTypings(decorationAnalyses));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Transform the .d.ts files
|
||||||
|
const dtsFiles = this.getTypingsFilesToRender(decorationAnalyses);
|
||||||
|
dtsFiles.forEach((classes, file) => renderedFiles.push(...this.renderDtsFile(file, classes)));
|
||||||
|
|
||||||
return renderedFiles;
|
return renderedFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the source code and source-map for an Analyzed file.
|
* 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.
|
* @param targetPath The absolute path where the rendered file will be written.
|
||||||
*/
|
*/
|
||||||
renderFile(
|
renderFile(
|
||||||
sourceFile: ts.SourceFile, decorationAnalysis: DecorationAnalysis|undefined,
|
sourceFile: ts.SourceFile, compiledFile: CompiledFile|undefined,
|
||||||
switchMarkerAnalysis: SwitchMarkerAnalysis|undefined, targetPath: string): FileInfo[] {
|
switchMarkerAnalysis: SwitchMarkerAnalysis|undefined): FileInfo[] {
|
||||||
const input = this.extractSourceMap(sourceFile);
|
const input = this.extractSourceMap(sourceFile);
|
||||||
const outputText = new MagicString(input.source);
|
const outputText = new MagicString(input.source);
|
||||||
|
|
||||||
|
@ -108,46 +97,59 @@ export abstract class Renderer {
|
||||||
outputText, switchMarkerAnalysis.sourceFile, switchMarkerAnalysis.declarations);
|
outputText, switchMarkerAnalysis.sourceFile, switchMarkerAnalysis.declarations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decorationAnalysis) {
|
if (compiledFile) {
|
||||||
const importManager =
|
const importManager =
|
||||||
new NgccImportManager(!this.rewriteCoreImportsTo, this.isCore, IMPORT_PREFIX);
|
new NgccImportManager(!this.rewriteCoreImportsTo, this.isCore, IMPORT_PREFIX);
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
|
|
||||||
decorationAnalysis.analyzedClasses.forEach(clazz => {
|
compiledFile.compiledClasses.forEach(clazz => {
|
||||||
const renderedDefinition =
|
const renderedDefinition = renderDefinitions(compiledFile.sourceFile, clazz, importManager);
|
||||||
renderDefinitions(decorationAnalysis.sourceFile, clazz, importManager);
|
|
||||||
this.addDefinitions(outputText, clazz, renderedDefinition);
|
this.addDefinitions(outputText, clazz, renderedDefinition);
|
||||||
this.trackDecorators(clazz.decorators, decoratorsToRemove);
|
this.trackDecorators(clazz.decorators, decoratorsToRemove);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addConstants(
|
this.addConstants(
|
||||||
outputText,
|
outputText,
|
||||||
renderConstantPool(
|
renderConstantPool(compiledFile.sourceFile, compiledFile.constantPool, importManager),
|
||||||
decorationAnalysis.sourceFile, decorationAnalysis.constantPool, importManager),
|
compiledFile.sourceFile);
|
||||||
decorationAnalysis.sourceFile);
|
|
||||||
|
|
||||||
this.addImports(
|
this.addImports(
|
||||||
outputText, importManager.getAllImports(
|
outputText,
|
||||||
decorationAnalysis.sourceFile.fileName, this.rewriteCoreImportsTo));
|
importManager.getAllImports(compiledFile.sourceFile.fileName, this.rewriteCoreImportsTo));
|
||||||
|
|
||||||
// TODO: remove contructor param metadata and property decorators (we need info from the
|
// TODO: remove contructor param metadata and property decorators (we need info from the
|
||||||
// handlers to do this)
|
// handlers to do this)
|
||||||
this.removeDecorators(outputText, decoratorsToRemove);
|
this.removeDecorators(outputText, decoratorsToRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
const {source, map} = this.renderSourceAndMap(sourceFile, input, outputText, targetPath);
|
return this.renderSourceAndMap(sourceFile, input, outputText);
|
||||||
const renderedFiles = [source];
|
}
|
||||||
if (map) {
|
|
||||||
renderedFiles.push(map);
|
renderDtsFile(dtsFile: ts.SourceFile, dtsClasses: DtsClassInfo[]): FileInfo[] {
|
||||||
}
|
const input = this.extractSourceMap(dtsFile);
|
||||||
return renderedFiles;
|
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):
|
protected abstract addConstants(output: MagicString, constants: string, file: ts.SourceFile):
|
||||||
void;
|
void;
|
||||||
protected abstract addImports(output: MagicString, imports: {name: string, as: string}[]): void;
|
protected abstract addImports(output: MagicString, imports: {name: string, as: string}[]): void;
|
||||||
protected abstract addDefinitions(
|
protected abstract addDefinitions(
|
||||||
output: MagicString, analyzedClass: AnalyzedClass, definitions: string): void;
|
output: MagicString, compiledClass: CompiledClass, definitions: string): void;
|
||||||
protected abstract removeDecorators(
|
protected abstract removeDecorators(
|
||||||
output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>): void;
|
output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>): void;
|
||||||
protected abstract rewriteSwitchableDeclarations(
|
protected abstract rewriteSwitchableDeclarations(
|
||||||
|
@ -219,8 +221,8 @@ export abstract class Renderer {
|
||||||
* with an appropriate source-map comment pointing to the merged source-map.
|
* with an appropriate source-map comment pointing to the merged source-map.
|
||||||
*/
|
*/
|
||||||
protected renderSourceAndMap(
|
protected renderSourceAndMap(
|
||||||
sourceFile: ts.SourceFile, input: SourceMapInfo, output: MagicString,
|
sourceFile: ts.SourceFile, input: SourceMapInfo, output: MagicString): FileInfo[] {
|
||||||
outputPath: string): RenderResult {
|
const outputPath = resolve(this.targetPath, relative(this.sourcePath, sourceFile.fileName));
|
||||||
const outputMapPath = `${outputPath}.map`;
|
const outputMapPath = `${outputPath}.map`;
|
||||||
const outputMap = output.generateMap({
|
const outputMap = output.generateMap({
|
||||||
source: sourceFile.fileName,
|
source: sourceFile.fileName,
|
||||||
|
@ -235,41 +237,34 @@ export abstract class Renderer {
|
||||||
const mergedMap =
|
const mergedMap =
|
||||||
mergeSourceMaps(input.map && input.map.toObject(), JSON.parse(outputMap.toString()));
|
mergeSourceMaps(input.map && input.map.toObject(), JSON.parse(outputMap.toString()));
|
||||||
|
|
||||||
|
const result: FileInfo[] = [];
|
||||||
if (input.isInline) {
|
if (input.isInline) {
|
||||||
return {
|
result.push({path: outputPath, contents: `${output.toString()}\n${mergedMap.toComment()}`});
|
||||||
source: {path: outputPath, contents: `${output.toString()}\n${mergedMap.toComment()}`},
|
|
||||||
map: null
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
result.push({
|
||||||
source: {
|
path: outputPath,
|
||||||
path: outputPath,
|
contents: `${output.toString()}\n${generateMapFileComment(outputMapPath)}`
|
||||||
contents: `${output.toString()}\n${generateMapFileComment(outputMapPath)}`
|
});
|
||||||
},
|
result.push({path: outputMapPath, contents: mergedMap.toJSON()});
|
||||||
map: {path: outputMapPath, contents: mergedMap.toJSON()}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(gkalpak): What about `.d.ts` source maps? (See
|
protected getTypingsFilesToRender(analyses: DecorationAnalyses):
|
||||||
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-9.html#new---declarationmap.)
|
Map<ts.SourceFile, DtsClassInfo[]> {
|
||||||
renderTypings(decorationAnalyses: DecorationAnalyses): FileInfo[] {
|
const dtsMap = new Map<ts.SourceFile, DtsClassInfo[]>();
|
||||||
const renderedFiles: FileInfo[] = [];
|
analyses.forEach(compiledFile => {
|
||||||
if (this.dtsMapper) {
|
compiledFile.compiledClasses.forEach(compiledClass => {
|
||||||
const dtsTransformer = new DtsFileTransformer(this.rewriteCoreImportsTo, IMPORT_PREFIX);
|
const dtsDeclaration = this.host.getDtsDeclarationOfClass(compiledClass.declaration);
|
||||||
decorationAnalyses.forEach((analysis, sourceFile) => {
|
if (dtsDeclaration) {
|
||||||
const sourceFileName = sourceFile.fileName;
|
const dtsFile = dtsDeclaration.getSourceFile();
|
||||||
const dtsFileName = this.dtsMapper !.getDtsFileNameFor(sourceFileName);
|
const classes = dtsMap.get(dtsFile) || [];
|
||||||
const dtsContents = readFileSync(dtsFileName, 'utf8');
|
classes.push({dtsDeclaration, compilation: compiledClass.compilation});
|
||||||
analysis.analyzedClasses.forEach(analyzedClass => dtsTransformer.recordStaticField(analyzedClass.name, analyzedClass.compilation));
|
dtsMap.set(dtsFile, classes);
|
||||||
const newDtsFileName = resolve(this.targetPath, relative(this.sourcePath, dtsFileName));
|
}
|
||||||
const newDtsContents = dtsTransformer.transform(dtsContents, sourceFileName);
|
|
||||||
renderedFiles.push({path: newDtsFileName, contents: newDtsContents});
|
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
return renderedFiles;
|
return dtsMap;
|
||||||
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,11 +316,11 @@ export function renderConstantPool(
|
||||||
* @param imports An object that tracks the imports that are needed by the rendered definitions.
|
* @param imports An object that tracks the imports that are needed by the rendered definitions.
|
||||||
*/
|
*/
|
||||||
export function renderDefinitions(
|
export function renderDefinitions(
|
||||||
sourceFile: ts.SourceFile, analyzedClass: AnalyzedClass, imports: NgccImportManager): string {
|
sourceFile: ts.SourceFile, compiledClass: CompiledClass, imports: NgccImportManager): string {
|
||||||
const printer = ts.createPrinter();
|
const printer = ts.createPrinter();
|
||||||
const name = (analyzedClass.declaration as ts.NamedDeclaration).name !;
|
const name = (compiledClass.declaration as ts.NamedDeclaration).name !;
|
||||||
const definitions =
|
const definitions =
|
||||||
analyzedClass.compilation
|
compiledClass.compilation
|
||||||
.map(
|
.map(
|
||||||
c => c.statements.map(statement => translateStatement(statement, imports))
|
c => c.statements.map(statement => translateStatement(statement, imports))
|
||||||
.concat(translateStatement(
|
.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() {
|
function createTestHandler() {
|
||||||
const handler = jasmine.createSpyObj<DecoratorHandler<any, any>>('TestDecoratorHandler', [
|
const handler = jasmine.createSpyObj<DecoratorHandler<any, any>>('TestDecoratorHandler', [
|
||||||
'detect',
|
'detect',
|
||||||
|
@ -79,9 +107,9 @@ describe('DecorationAnalyzer', () => {
|
||||||
|
|
||||||
it('should return an object containing the classes that were analyzed', () => {
|
it('should return an object containing the classes that were analyzed', () => {
|
||||||
const file = program.getSourceFile(TEST_PROGRAM.name) !;
|
const file = program.getSourceFile(TEST_PROGRAM.name) !;
|
||||||
const analysis = result.get(file) !;
|
const compiledFile = result.get(file) !;
|
||||||
expect(analysis.analyzedClasses.length).toEqual(1);
|
expect(compiledFile.compiledClasses.length).toEqual(1);
|
||||||
expect(analysis.analyzedClasses[0].name).toEqual('MyComponent');
|
expect(compiledFile.compiledClasses[0].name).toEqual('MyComponent');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should analyze and compile the classes that are detected', () => {
|
it('should analyze and compile the classes that are detected', () => {
|
||||||
|
@ -91,5 +119,41 @@ describe('DecorationAnalyzer', () => {
|
||||||
expect(testHandler.compile).toHaveBeenCalledTimes(1);
|
expect(testHandler.compile).toHaveBeenCalledTimes(1);
|
||||||
expect(testHandler.compile.calls.allArgs()[0][1]).toEqual('Component');
|
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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {ClassMemberKind, Import} from '../../../ngtsc/host';
|
import {ClassMemberKind, Import} from '../../../ngtsc/host';
|
||||||
import {DtsMapper} from '../../src/host/dts_mapper';
|
|
||||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||||
import {getDeclaration, makeProgram} from '../helpers/utils';
|
import {getDeclaration, makeProgram} from '../helpers/utils';
|
||||||
|
|
||||||
|
@ -399,6 +397,7 @@ const DECORATED_FILES = [
|
||||||
name: '/primary.js',
|
name: '/primary.js',
|
||||||
contents: `
|
contents: `
|
||||||
import {Directive} from '@angular/core';
|
import {Directive} from '@angular/core';
|
||||||
|
import {D} from '/secondary';
|
||||||
class A {}
|
class A {}
|
||||||
A.decorators = [
|
A.decorators = [
|
||||||
{ type: Directive, args: [{ selector: '[a]' }] }
|
{ type: Directive, args: [{ selector: '[a]' }] }
|
||||||
|
@ -411,7 +410,6 @@ const DECORATED_FILES = [
|
||||||
];
|
];
|
||||||
class C {}
|
class C {}
|
||||||
export { A, x, C };
|
export { A, x, C };
|
||||||
export { D } from '/secondary';
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -422,7 +420,7 @@ const DECORATED_FILES = [
|
||||||
D.decorators = [
|
D.decorators = [
|
||||||
{ type: Directive, args: [{ selector: '[d]' }] }
|
{ type: Directive, args: [{ selector: '[d]' }] }
|
||||||
];
|
];
|
||||||
export { D };
|
export {D};
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -439,13 +437,38 @@ const ARITY_CLASSES = [
|
||||||
{
|
{
|
||||||
name: '/typings/class.d.ts',
|
name: '/typings/class.d.ts',
|
||||||
contents: `
|
contents: `
|
||||||
export class NoTypeParam {}
|
export declare class NoTypeParam {}
|
||||||
export class OneTypeParam<T> {}
|
export declare class OneTypeParam<T> {}
|
||||||
export class TwoTypeParams<T, K> {}
|
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('Fesm2015ReflectionHost', () => {
|
||||||
|
|
||||||
describe('getDecoratorsOfDeclaration()', () => {
|
describe('getDecoratorsOfDeclaration()', () => {
|
||||||
|
@ -1186,12 +1209,10 @@ describe('Fesm2015ReflectionHost', () => {
|
||||||
|
|
||||||
describe('getGenericArityOfClass()', () => {
|
describe('getGenericArityOfClass()', () => {
|
||||||
it('should properly count type parameters', () => {
|
it('should properly count type parameters', () => {
|
||||||
// Mock out reading the `d.ts` file from disk
|
const dtsProgram = makeProgram(ARITY_CLASSES[1]);
|
||||||
const readFileSyncSpy = spyOn(fs, 'readFileSync').and.returnValue(ARITY_CLASSES[1].contents);
|
|
||||||
const program = makeProgram(ARITY_CLASSES[0]);
|
const program = makeProgram(ARITY_CLASSES[0]);
|
||||||
|
const host = new Esm2015ReflectionHost(
|
||||||
const dtsMapper = new DtsMapper('/src', '/typings');
|
false, program.getTypeChecker(), ARITY_CLASSES[1].name, dtsProgram);
|
||||||
const host = new Esm2015ReflectionHost(false, program.getTypeChecker(), dtsMapper);
|
|
||||||
const noTypeParamClass =
|
const noTypeParamClass =
|
||||||
getDeclaration(program, '/src/class.js', 'NoTypeParam', ts.isClassDeclaration);
|
getDeclaration(program, '/src/class.js', 'NoTypeParam', ts.isClassDeclaration);
|
||||||
expect(host.getGenericArityOfClass(noTypeParamClass)).toBe(0);
|
expect(host.getGenericArityOfClass(noTypeParamClass)).toBe(0);
|
||||||
|
@ -1217,30 +1238,95 @@ describe('Fesm2015ReflectionHost', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('findDecoratedFiles()', () => {
|
describe('findDecoratedClasses()', () => {
|
||||||
it('should return an array of objects for each file that has exported and decorated classes',
|
it('should return an array of all decorated classes in the given source file', () => {
|
||||||
() => {
|
const program = makeProgram(...DECORATED_FILES);
|
||||||
const program = makeProgram(...DECORATED_FILES);
|
const host = new Esm2015ReflectionHost(false, program.getTypeChecker());
|
||||||
const host = new Esm2015ReflectionHost(false, program.getTypeChecker());
|
const primaryFile = program.getSourceFile(DECORATED_FILES[0].name) !;
|
||||||
const primaryFile = program.getSourceFile(DECORATED_FILES[0].name) !;
|
const secondaryFile = program.getSourceFile(DECORATED_FILES[1].name) !;
|
||||||
const secondaryFile = program.getSourceFile(DECORATED_FILES[1].name) !;
|
|
||||||
const decoratedFiles = host.findDecoratedFiles(primaryFile);
|
|
||||||
|
|
||||||
expect(decoratedFiles.size).toEqual(2);
|
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 primary = decoratedFiles.get(primaryFile) !;
|
const secondaryDecoratedClasses = host.findDecoratedClasses(secondaryFile) !;
|
||||||
expect(primary.decoratedClasses.length).toEqual(1);
|
expect(secondaryDecoratedClasses.length).toEqual(1);
|
||||||
const classA = primary.decoratedClasses.find(c => c.name === 'A') !;
|
// Note that `D` is exported from `secondary.js` but not exported from `primary.js`
|
||||||
expect(classA.name).toEqual('A');
|
const classD = secondaryDecoratedClasses.find(c => c.name === 'D') !;
|
||||||
expect(ts.isClassDeclaration(classA.declaration)).toBeTruthy();
|
expect(classD.name).toEqual('D');
|
||||||
expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
|
expect(ts.isClassDeclaration(classD.declaration)).toBeTruthy();
|
||||||
|
expect(classD.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') !;
|
|
||||||
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',
|
name: '/primary.js',
|
||||||
contents: `
|
contents: `
|
||||||
import {Directive} from '@angular/core';
|
import {Directive} from '@angular/core';
|
||||||
|
import { D } from '/secondary';
|
||||||
var A = (function() {
|
var A = (function() {
|
||||||
function A() {}
|
function A() {}
|
||||||
A.decorators = [
|
A.decorators = [
|
||||||
|
@ -453,7 +454,6 @@ const DECORATED_FILES = [
|
||||||
return C;
|
return C;
|
||||||
});
|
});
|
||||||
export { A, x, C };
|
export { A, x, C };
|
||||||
export { D } from '/secondary';
|
|
||||||
`
|
`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1252,26 +1252,27 @@ describe('Esm5ReflectionHost', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('fileDecoratedFiles()', () => {
|
describe('findDecoratedClasses()', () => {
|
||||||
it('should return an array of objects for each file that has exported and decorated classes',
|
it('should return an array of all decorated classes in the given source file', () => {
|
||||||
() => {
|
const program = makeProgram(...DECORATED_FILES);
|
||||||
const program = makeProgram(...DECORATED_FILES);
|
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
||||||
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
const primary = program.getSourceFile(DECORATED_FILES[0].name) !;
|
||||||
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');
|
|
||||||
expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
|
|
||||||
|
|
||||||
const secondary = program.getSourceFile(DECORATED_FILES[1].name) !;
|
const primaryDecoratedClasses = host.findDecoratedClasses(primary);
|
||||||
const secondaryClasses = decoratedFiles.get(secondary) !.decoratedClasses;
|
expect(primaryDecoratedClasses.length).toEqual(2);
|
||||||
expect(secondaryClasses.length).toEqual(1);
|
const classA = primaryDecoratedClasses.find(c => c.name === 'A') !;
|
||||||
const classD = secondaryClasses.find(c => c.name === 'D') !;
|
expect(classA.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
|
||||||
expect(classD.name).toEqual('D');
|
// Note that `B` is not exported from `primary.js`
|
||||||
expect(classD.decorators.map(decorator => decorator.name)).toEqual(['Directive']);
|
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 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 {makeProgram} from '../helpers/utils';
|
||||||
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
import {DecorationAnalyzer} from '../../src/analysis/decoration_analyzer';
|
||||||
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
|
import {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
|
||||||
import {DtsMapper} from '../../src/host/dts_mapper';
|
|
||||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||||
import {EsmRenderer} from '../../src/rendering/esm_renderer';
|
import {EsmRenderer} from '../../src/rendering/esm_renderer';
|
||||||
|
|
||||||
|
@ -24,7 +23,7 @@ function setup(file: {name: string, contents: string}, transformDts: boolean = f
|
||||||
const decorationAnalyses =
|
const decorationAnalyses =
|
||||||
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
|
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
|
||||||
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).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};
|
return {host, program, sourceFile, renderer, decorationAnalyses, switchMarkerAnalyses};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,8 +160,9 @@ export class A {}`);
|
||||||
it('should insert the definitions directly after the class declaration', () => {
|
it('should insert the definitions directly after the class declaration', () => {
|
||||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||||
const output = new MagicString(PROGRAM.contents);
|
const output = new MagicString(PROGRAM.contents);
|
||||||
const analyzedClass = decorationAnalyses.get(sourceFile) !.analyzedClasses[0];
|
const compiledClass =
|
||||||
renderer.addDefinitions(output, analyzedClass, 'SOME DEFINITION TEXT');
|
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||||
|
renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT');
|
||||||
expect(output.toString()).toContain(`
|
expect(output.toString()).toContain(`
|
||||||
export class A {}
|
export class A {}
|
||||||
SOME DEFINITION TEXT
|
SOME DEFINITION TEXT
|
||||||
|
@ -179,9 +179,9 @@ A.decorators = [
|
||||||
() => {
|
() => {
|
||||||
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
||||||
const output = new MagicString(PROGRAM.contents);
|
const output = new MagicString(PROGRAM.contents);
|
||||||
const analyzedClass =
|
const compiledClass =
|
||||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
|
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||||
const decorator = analyzedClass.decorators[0];
|
const decorator = compiledClass.decorators[0];
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
renderer.removeDecorators(output, decoratorsToRemove);
|
renderer.removeDecorators(output, decoratorsToRemove);
|
||||||
|
@ -198,9 +198,9 @@ A.decorators = [
|
||||||
() => {
|
() => {
|
||||||
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
||||||
const output = new MagicString(PROGRAM.contents);
|
const output = new MagicString(PROGRAM.contents);
|
||||||
const analyzedClass =
|
const compiledClass =
|
||||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
|
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
|
||||||
const decorator = analyzedClass.decorators[0];
|
const decorator = compiledClass.decorators[0];
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
renderer.removeDecorators(output, decoratorsToRemove);
|
renderer.removeDecorators(output, decoratorsToRemove);
|
||||||
|
@ -217,9 +217,9 @@ A.decorators = [
|
||||||
() => {
|
() => {
|
||||||
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
const {decorationAnalyses, sourceFile, renderer} = setup(PROGRAM);
|
||||||
const output = new MagicString(PROGRAM.contents);
|
const output = new MagicString(PROGRAM.contents);
|
||||||
const analyzedClass =
|
const compiledClass =
|
||||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
|
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
|
||||||
const decorator = analyzedClass.decorators[0];
|
const decorator = compiledClass.decorators[0];
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
renderer.removeDecorators(output, decoratorsToRemove);
|
renderer.removeDecorators(output, decoratorsToRemove);
|
||||||
|
@ -238,9 +238,9 @@ A.decorators = [
|
||||||
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
|
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
|
||||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||||
const analyzedClass =
|
const compiledClass =
|
||||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
|
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
renderer.removeDecorators(output, decoratorsToRemove);
|
renderer.removeDecorators(output, decoratorsToRemove);
|
||||||
|
@ -255,9 +255,9 @@ A.decorators = [
|
||||||
() => {
|
() => {
|
||||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||||
const analyzedClass =
|
const compiledClass =
|
||||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
|
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
|
||||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
renderer.removeDecorators(output, decoratorsToRemove);
|
renderer.removeDecorators(output, decoratorsToRemove);
|
||||||
|
@ -273,9 +273,9 @@ A.decorators = [
|
||||||
() => {
|
() => {
|
||||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||||
const analyzedClass =
|
const compiledClass =
|
||||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
|
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
|
||||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
renderer.removeDecorators(output, decoratorsToRemove);
|
renderer.removeDecorators(output, decoratorsToRemove);
|
||||||
|
|
|
@ -20,7 +20,7 @@ function setup(file: {name: string, contents: string}) {
|
||||||
const decorationAnalyses =
|
const decorationAnalyses =
|
||||||
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
|
new DecorationAnalyzer(program.getTypeChecker(), host, [''], false).analyzeProgram(program);
|
||||||
const switchMarkerAnalyses = new SwitchMarkerAnalyzer(host).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};
|
return {host, program, sourceFile, renderer, decorationAnalyses, switchMarkerAnalyses};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,8 +182,9 @@ var A = (function() {`);
|
||||||
it('should insert the definitions directly after the class declaration', () => {
|
it('should insert the definitions directly after the class declaration', () => {
|
||||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||||
const output = new MagicString(PROGRAM.contents);
|
const output = new MagicString(PROGRAM.contents);
|
||||||
const analyzedClass = decorationAnalyses.get(sourceFile) !.analyzedClasses[0];
|
const compiledClass =
|
||||||
renderer.addDefinitions(output, analyzedClass, 'SOME DEFINITION TEXT');
|
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||||
|
renderer.addDefinitions(output, compiledClass, 'SOME DEFINITION TEXT');
|
||||||
expect(output.toString()).toContain(`
|
expect(output.toString()).toContain(`
|
||||||
function A() {}
|
function A() {}
|
||||||
SOME DEFINITION TEXT
|
SOME DEFINITION TEXT
|
||||||
|
@ -199,9 +200,9 @@ SOME DEFINITION TEXT
|
||||||
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
|
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
|
||||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||||
const output = new MagicString(PROGRAM.contents);
|
const output = new MagicString(PROGRAM.contents);
|
||||||
const analyzedClass =
|
const compiledClass =
|
||||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
|
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||||
const decorator = analyzedClass.decorators[0];
|
const decorator = compiledClass.decorators[0];
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
renderer.removeDecorators(output, decoratorsToRemove);
|
renderer.removeDecorators(output, decoratorsToRemove);
|
||||||
|
@ -217,9 +218,9 @@ SOME DEFINITION TEXT
|
||||||
() => {
|
() => {
|
||||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||||
const output = new MagicString(PROGRAM.contents);
|
const output = new MagicString(PROGRAM.contents);
|
||||||
const analyzedClass =
|
const compiledClass =
|
||||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
|
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
|
||||||
const decorator = analyzedClass.decorators[0];
|
const decorator = compiledClass.decorators[0];
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
renderer.removeDecorators(output, decoratorsToRemove);
|
renderer.removeDecorators(output, decoratorsToRemove);
|
||||||
|
@ -236,9 +237,9 @@ SOME DEFINITION TEXT
|
||||||
() => {
|
() => {
|
||||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM);
|
||||||
const output = new MagicString(PROGRAM.contents);
|
const output = new MagicString(PROGRAM.contents);
|
||||||
const analyzedClass =
|
const compiledClass =
|
||||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
|
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
|
||||||
const decorator = analyzedClass.decorators[0];
|
const decorator = compiledClass.decorators[0];
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
renderer.removeDecorators(output, decoratorsToRemove);
|
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', () => {
|
it('should delete the decorator (and following comma) that was matched in the analysis', () => {
|
||||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||||
const analyzedClass =
|
const compiledClass =
|
||||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'A') !;
|
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'A') !;
|
||||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
renderer.removeDecorators(output, decoratorsToRemove);
|
renderer.removeDecorators(output, decoratorsToRemove);
|
||||||
|
@ -274,9 +275,9 @@ SOME DEFINITION TEXT
|
||||||
() => {
|
() => {
|
||||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||||
const analyzedClass =
|
const compiledClass =
|
||||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'B') !;
|
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'B') !;
|
||||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
renderer.removeDecorators(output, decoratorsToRemove);
|
renderer.removeDecorators(output, decoratorsToRemove);
|
||||||
|
@ -292,9 +293,9 @@ SOME DEFINITION TEXT
|
||||||
() => {
|
() => {
|
||||||
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
const {renderer, decorationAnalyses, sourceFile} = setup(PROGRAM_DECORATE_HELPER);
|
||||||
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
const output = new MagicString(PROGRAM_DECORATE_HELPER.contents);
|
||||||
const analyzedClass =
|
const compiledClass =
|
||||||
decorationAnalyses.get(sourceFile) !.analyzedClasses.find(c => c.name === 'C') !;
|
decorationAnalyses.get(sourceFile) !.compiledClasses.find(c => c.name === 'C') !;
|
||||||
const decorator = analyzedClass.decorators.find(d => d.name === 'Directive') !;
|
const decorator = compiledClass.decorators.find(d => d.name === 'Directive') !;
|
||||||
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
const decoratorsToRemove = new Map<ts.Node, ts.Node[]>();
|
||||||
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
decoratorsToRemove.set(decorator.node.parent !, [decorator.node]);
|
||||||
renderer.removeDecorators(output, decoratorsToRemove);
|
renderer.removeDecorators(output, decoratorsToRemove);
|
||||||
|
|
|
@ -11,20 +11,20 @@ import * as ts from 'typescript';
|
||||||
import MagicString from 'magic-string';
|
import MagicString from 'magic-string';
|
||||||
import {fromObject, generateMapFileComment} from 'convert-source-map';
|
import {fromObject, generateMapFileComment} from 'convert-source-map';
|
||||||
import {makeProgram} from '../helpers/utils';
|
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 {SwitchMarkerAnalyzer} from '../../src/analysis/switch_marker_analyzer';
|
||||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||||
import {Renderer} from '../../src/rendering/renderer';
|
import {Renderer} from '../../src/rendering/renderer';
|
||||||
|
|
||||||
class TestRenderer extends 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}[]) {
|
addImports(output: MagicString, imports: {name: string, as: string}[]) {
|
||||||
output.prepend('\n// ADD IMPORTS\n');
|
output.prepend('\n// ADD IMPORTS\n');
|
||||||
}
|
}
|
||||||
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
||||||
output.prepend('\n// ADD CONSTANTS\n');
|
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');
|
output.prepend('\n// ADD DEFINITIONS\n');
|
||||||
}
|
}
|
||||||
removeDecorators(output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>) {
|
removeDecorators(output: MagicString, decoratorsToRemove: Map<ts.Node, ts.Node[]>) {
|
||||||
|
|
|
@ -12,12 +12,13 @@ import * as path from 'path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
export function makeProgram(
|
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(),
|
host: ts.CompilerHost = new InMemoryHost(),
|
||||||
checkForErrors: boolean = true): {program: ts.Program, host: ts.CompilerHost} {
|
checkForErrors: boolean = true): {program: ts.Program, host: ts.CompilerHost} {
|
||||||
files.forEach(file => host.writeFile(file.name, file.contents, false, undefined, []));
|
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(
|
const program = ts.createProgram(
|
||||||
rootNames, {
|
rootNames, {
|
||||||
noLib: true,
|
noLib: true,
|
||||||
|
|
Loading…
Reference in New Issue