diff --git a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts index 7ac8a2bf91..41bc67ba50 100644 --- a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts +++ b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts @@ -9,7 +9,7 @@ import * as ts from 'typescript'; import {AbsoluteFsPath} from '../../../src/ngtsc/file_system'; -import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, Import, TypeScriptReflectionHost, isDecoratorIdentifier, reflectObjectLiteral} from '../../../src/ngtsc/reflection'; +import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, TypeScriptReflectionHost, isDecoratorIdentifier, reflectObjectLiteral} from '../../../src/ngtsc/reflection'; import {Logger} from '../logging/logger'; import {BundleProgram} from '../packages/bundle_program'; import {findAll, getNameText, hasNameIdentifier, isDefined} from '../utils'; @@ -73,6 +73,14 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N */ protected aliasedClassDeclarations = new Map(); + /** + * Caches the information of the decorators on a class, as the work involved with extracting + * decorators is complex and frequently used. + * + * This map is lazily populated during the first call to `acquireDecoratorInfo` for a given class. + */ + protected decoratorCache = new Map(); + constructor( protected logger: Logger, protected isCore: boolean, checker: ts.TypeChecker, dts?: BundleProgram|null) { @@ -247,12 +255,8 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N /** Gets all decorators of the given class symbol. */ getDecoratorsOfSymbol(symbol: ClassSymbol): Decorator[]|null { - const decoratorsProperty = this.getStaticProperty(symbol, DECORATORS); - if (decoratorsProperty) { - return this.getClassDecoratorsFromStaticProperty(decoratorsProperty); - } else { - return this.getClassDecoratorsFromHelperCall(symbol); - } + const {classDecorators} = this.acquireDecoratorInfo(symbol); + return classDecorators; } /** @@ -542,6 +546,72 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N return symbol.exports && symbol.exports.get(propertyName); } + /** + * This is the main entry-point for obtaining information on the decorators of a given class. This + * information is computed either from static properties if present, or using `tslib.__decorate` + * helper calls otherwise. The computed result is cached per class. + * + * @param classSymbol the class for which decorators should be acquired. + * @returns all information of the decorators on the class. + */ + protected acquireDecoratorInfo(classSymbol: ClassSymbol): DecoratorInfo { + if (this.decoratorCache.has(classSymbol.valueDeclaration)) { + return this.decoratorCache.get(classSymbol.valueDeclaration) !; + } + + // First attempt extracting decorators from static properties. + let decoratorInfo = this.computeDecoratorInfoFromStaticProperties(classSymbol); + if (decoratorInfo === null) { + // If none were present, use the `__decorate` helper calls instead. + decoratorInfo = this.computeDecoratorInfoFromHelperCalls(classSymbol); + } + + this.decoratorCache.set(classSymbol.valueDeclaration, decoratorInfo); + return decoratorInfo; + } + + /** + * Attempts to compute decorator information from static properties "decorators", "propDecorators" + * and "ctorParameters" on the class. If neither of these static properties is present the + * library is likely not compiled using tsickle for usage with Closure compiler, in which case + * `null` is returned. + * + * @param classSymbol The class symbol to compute the decorators information for. + * @returns All information on the decorators as extracted from static properties, or `null` if + * none of the static properties exist. + */ + protected computeDecoratorInfoFromStaticProperties(classSymbol: ClassSymbol): DecoratorInfo|null { + let classDecorators: Decorator[]|null = null; + let memberDecorators: Map|null = null; + let constructorParamInfo: ParamInfo[]|null = null; + + const decoratorsProperty = this.getStaticProperty(classSymbol, DECORATORS); + if (decoratorsProperty !== undefined) { + classDecorators = this.getClassDecoratorsFromStaticProperty(decoratorsProperty); + } + + const propDecoratorsProperty = this.getStaticProperty(classSymbol, PROP_DECORATORS); + if (propDecoratorsProperty !== undefined) { + memberDecorators = this.getMemberDecoratorsFromStaticProperty(propDecoratorsProperty); + } + + const constructorParamsProperty = this.getStaticProperty(classSymbol, CONSTRUCTOR_PARAMS); + if (constructorParamsProperty !== undefined) { + constructorParamInfo = this.getParamInfoFromStaticProperty(constructorParamsProperty); + } + + // If none of the static properties were present, no decorator info could be computed. + if (classDecorators === null && memberDecorators === null && constructorParamInfo === null) { + return null; + } + + return { + classDecorators, + memberDecorators: memberDecorators || new Map(), + constructorParamInfo: constructorParamInfo || [], + }; + } + /** * Get all class decorators for the given class, where the decorators are declared * via a static property. For example: @@ -570,32 +640,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N return null; } - /** - * Get all class decorators for the given class, where the decorators are declared - * via the `__decorate` helper method. For example: - * - * ``` - * let SomeDirective = class SomeDirective {} - * SomeDirective = __decorate([ - * Directive({ selector: '[someDirective]' }), - * ], SomeDirective); - * ``` - * - * @param symbol the class whose decorators we want to get. - * @returns an array of decorators or null if none where found. - */ - protected getClassDecoratorsFromHelperCall(symbol: ClassSymbol): Decorator[]|null { - const decorators: Decorator[] = []; - const helperCalls = this.getHelperCallsForClass(symbol, '__decorate'); - helperCalls.forEach(helperCall => { - const {classDecorators} = - this.reflectDecoratorsFromHelperCall(helperCall, makeClassTargetFilter(symbol.name)); - classDecorators.filter(decorator => this.isFromCore(decorator)) - .forEach(decorator => decorators.push(decorator)); - }); - return decorators.length ? decorators : null; - } - /** * Examine a symbol which should be of a class, and return metadata about its members. * @@ -606,7 +650,11 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N const members: ClassMember[] = []; // The decorators map contains all the properties that are decorated - const decoratorsMap = this.getMemberDecorators(symbol); + const {memberDecorators} = this.acquireDecoratorInfo(symbol); + + // Make a copy of the decorators as successfully reflected members delete themselves from the + // map, so that any leftovers can be easily dealt with. + const decoratorsMap = new Map(memberDecorators); // The member map contains all the method (instance and static); and any instance properties // that are initialized in the class. @@ -675,21 +723,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N return members; } - /** - * Get all the member decorators for the given class. - * @param classSymbol the class whose member decorators we are interested in. - * @returns a map whose keys are the name of the members and whose values are collections of - * decorators for the given member. - */ - protected getMemberDecorators(classSymbol: ClassSymbol): Map { - const decoratorsProperty = this.getStaticProperty(classSymbol, PROP_DECORATORS); - if (decoratorsProperty) { - return this.getMemberDecoratorsFromStaticProperty(decoratorsProperty); - } else { - return this.getMemberDecoratorsFromHelperCalls(classSymbol); - } - } - /** * Member decorators may be declared as static properties of the class: * @@ -724,7 +757,21 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N } /** - * Member decorators may be declared via helper call statements. + * For a given class symbol, collects all decorator information from tslib helper methods, as + * generated by TypeScript into emitted JavaScript files. + * + * Class decorators are extracted from calls to `tslib.__decorate` that look as follows: + * + * ``` + * let SomeDirective = class SomeDirective {} + * SomeDirective = __decorate([ + * Directive({ selector: '[someDirective]' }), + * ], SomeDirective); + * ``` + * + * The extraction of member decorators is similar, with the distinction that its 2nd and 3rd + * argument correspond with a "prototype" target and the name of the member to which the + * decorators apply. * * ``` * __decorate([ @@ -733,102 +780,194 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N * ], SomeDirective.prototype, "input1", void 0); * ``` * - * @param classSymbol the class whose member decorators we are interested in. - * @returns a map whose keys are the name of the members and whose values are collections of - * decorators for the given member. + * @param classSymbol The class symbol for which decorators should be extracted. + * @returns All information on the decorators of the class. */ - protected getMemberDecoratorsFromHelperCalls(classSymbol: ClassSymbol): Map { - const memberDecoratorMap = new Map(); - const helperCalls = this.getHelperCallsForClass(classSymbol, '__decorate'); - helperCalls.forEach(helperCall => { - const {memberDecorators} = this.reflectDecoratorsFromHelperCall( - helperCall, makeMemberTargetFilter(classSymbol.name)); - memberDecorators.forEach((decorators, memberName) => { - if (memberName) { - const memberDecorators = - memberDecoratorMap.has(memberName) ? memberDecoratorMap.get(memberName) ! : []; - const coreDecorators = decorators.filter(decorator => this.isFromCore(decorator)); - memberDecoratorMap.set(memberName, memberDecorators.concat(coreDecorators)); - } - }); - }); - return memberDecoratorMap; - } - - /** - * Extract decorator info from `__decorate` helper function calls. - * @param helperCall the call to a helper that may contain decorator calls - * @param targetFilter a function to filter out targets that we are not interested in. - * @returns a mapping from member name to decorators, where the key is either the name of the - * member or `undefined` if it refers to decorators on the class as a whole. - */ - protected reflectDecoratorsFromHelperCall( - helperCall: ts.CallExpression, targetFilter: TargetFilter): - {classDecorators: Decorator[], memberDecorators: Map} { - const classDecorators: Decorator[] = []; + protected computeDecoratorInfoFromHelperCalls(classSymbol: ClassSymbol): DecoratorInfo { + let classDecorators: Decorator[]|null = null; const memberDecorators = new Map(); + const constructorParamInfo: ParamInfo[] = []; - // First check that the `target` argument is correct - if (targetFilter(helperCall.arguments[1])) { - // Grab the `decorators` argument which should be an array of calls - const decoratorCalls = helperCall.arguments[0]; - if (decoratorCalls && ts.isArrayLiteralExpression(decoratorCalls)) { - decoratorCalls.elements.forEach(element => { - // We only care about those elements that are actual calls - if (ts.isCallExpression(element)) { - const decorator = this.reflectDecoratorCall(element); - if (decorator) { - const keyArg = helperCall.arguments[2]; - const keyName = keyArg && ts.isStringLiteral(keyArg) ? keyArg.text : undefined; - if (keyName === undefined) { - classDecorators.push(decorator); - } else { - const decorators = - memberDecorators.has(keyName) ? memberDecorators.get(keyName) ! : []; - decorators.push(decorator); - memberDecorators.set(keyName, decorators); - } - } + const getConstructorParamInfo = (index: number) => { + let param = constructorParamInfo[index]; + if (param === undefined) { + param = constructorParamInfo[index] = {decorators: null, typeExpression: null}; + } + return param; + }; + + // All relevant information can be extracted from calls to `__decorate`, obtain these first. + // Note that although the helper calls are retrieved using the class symbol, the result may + // contain helper calls corresponding with unrelated classes. Therefore, each helper call still + // has to be checked to actually correspond with the class symbol. + const helperCalls = this.getHelperCallsForClass(classSymbol, '__decorate'); + + for (const helperCall of helperCalls) { + if (isClassDecorateCall(helperCall, classSymbol.name)) { + // This `__decorate` call is targeting the class itself. + const helperArgs = helperCall.arguments[0]; + + for (const element of helperArgs.elements) { + const entry = this.reflectDecorateHelperEntry(element); + if (entry === null) { + continue; } - }); + + if (entry.type === 'decorator') { + // The helper arg was reflected to represent an actual decorator + if (this.isFromCore(entry.decorator)) { + (classDecorators || (classDecorators = [])).push(entry.decorator); + } + } else if (entry.type === 'param:decorators') { + // The helper arg represents a decorator for a parameter. Since it's applied to the + // class, it corresponds with a constructor parameter of the class. + const param = getConstructorParamInfo(entry.index); + (param.decorators || (param.decorators = [])).push(entry.decorator); + } else if (entry.type === 'params') { + // The helper arg represents the types of the parameters. Since it's applied to the + // class, it corresponds with the constructor parameters of the class. + entry.types.forEach( + (type, index) => getConstructorParamInfo(index).typeExpression = type); + } + } + } else if (isMemberDecorateCall(helperCall, classSymbol.name)) { + // The `__decorate` call is targeting a member of the class + const helperArgs = helperCall.arguments[0]; + const memberName = helperCall.arguments[2].text; + + for (const element of helperArgs.elements) { + const entry = this.reflectDecorateHelperEntry(element); + if (entry === null) { + continue; + } + + if (entry.type === 'decorator') { + // The helper arg was reflected to represent an actual decorator. + if (this.isFromCore(entry.decorator)) { + const decorators = + memberDecorators.has(memberName) ? memberDecorators.get(memberName) ! : []; + decorators.push(entry.decorator); + memberDecorators.set(memberName, decorators); + } + } else { + // Information on decorated parameters is not interesting for ngcc, so it's ignored. + } + } } } - return {classDecorators, memberDecorators}; + + return {classDecorators, memberDecorators, constructorParamInfo}; } /** - * Extract the decorator information from a call to a decorator as a function. - * This happens when the decorators has been used in a `__decorate` helper call. - * For example: + * Extract the details of an entry within a `__decorate` helper call. For example, given the + * following code: * * ``` * __decorate([ * Directive({ selector: '[someDirective]' }), + * tslib_1.__param(2, Inject(INJECTED_TOKEN)), + * tslib_1.__metadata("design:paramtypes", [ViewContainerRef, TemplateRef, String]) * ], SomeDirective); * ``` * - * Here the `Directive` decorator is decorating `SomeDirective` and the options for - * the decorator are passed as arguments to the `Directive()` call. + * it can be seen that there are calls to regular decorators (the `Directive`) and calls into + * `tslib` functions which have been inserted by TypeScript. Therefore, this function classifies + * a call to correspond with + * 1. a real decorator like `Directive` above, or + * 2. a decorated parameter, corresponding with `__param` calls from `tslib`, or + * 3. the type information of parameters, corresponding with `__metadata` call from `tslib` * - * @param call the call to the decorator. - * @returns a decorator containing the reflected information, or null if the call - * is not a valid decorator call. + * @param expression the expression that needs to be reflected into a `DecorateHelperEntry` + * @returns an object that indicates which of the three categories the call represents, together + * with the reflected information of the call, or null if the call is not a valid decorate call. */ - protected reflectDecoratorCall(call: ts.CallExpression): Decorator|null { - const decoratorExpression = call.expression; - if (isDecoratorIdentifier(decoratorExpression)) { - // We found a decorator! - const decoratorIdentifier = - ts.isIdentifier(decoratorExpression) ? decoratorExpression : decoratorExpression.name; + protected reflectDecorateHelperEntry(expression: ts.Expression): DecorateHelperEntry|null { + // We only care about those elements that are actual calls + if (!ts.isCallExpression(expression)) { + return null; + } + const call = expression; + + const helperCallFn = + ts.isPropertyAccessExpression(call.expression) ? call.expression.name : call.expression; + if (!ts.isIdentifier(helperCallFn)) { + return null; + } + + const name = helperCallFn.text; + if (name === '__metadata') { + // This is a `tslib.__metadata` call, reflect to arguments into a `ParameterTypes` object + // if the metadata key is "design:paramtypes". + const key = call.arguments[0]; + if (key === undefined || !ts.isStringLiteral(key) || key.text !== 'design:paramtypes') { + return null; + } + + const value = call.arguments[1]; + if (value === undefined || !ts.isArrayLiteralExpression(value)) { + return null; + } + return { - name: decoratorIdentifier.text, - identifier: decoratorIdentifier, - import: this.getImportOfIdentifier(decoratorIdentifier), - node: call, - args: Array.from(call.arguments) + type: 'params', + types: Array.from(value.elements), }; } - return null; + + if (name === '__param') { + // This is a `tslib.__param` call that is reflected into a `ParameterDecorators` object. + const indexArg = call.arguments[0]; + const index = indexArg && ts.isNumericLiteral(indexArg) ? parseInt(indexArg.text, 10) : NaN; + if (isNaN(index)) { + return null; + } + + const decoratorCall = call.arguments[1]; + if (decoratorCall === undefined || !ts.isCallExpression(decoratorCall)) { + return null; + } + + const decorator = this.reflectDecoratorCall(decoratorCall); + if (decorator === null) { + return null; + } + + return { + type: 'param:decorators', + index, + decorator, + }; + } + + // Otherwise attempt to reflect it as a regular decorator. + const decorator = this.reflectDecoratorCall(call); + if (decorator === null) { + return null; + } + return { + type: 'decorator', + decorator, + }; + } + + protected reflectDecoratorCall(call: ts.CallExpression): Decorator|null { + const decoratorExpression = call.expression; + if (!isDecoratorIdentifier(decoratorExpression)) { + return null; + } + + // We found a decorator! + const decoratorIdentifier = + ts.isIdentifier(decoratorExpression) ? decoratorExpression : decoratorExpression.name; + + return { + name: decoratorIdentifier.text, + identifier: decoratorIdentifier, + import: this.getImportOfIdentifier(decoratorIdentifier), + node: call, + args: Array.from(call.arguments), + }; } /** @@ -1070,14 +1209,11 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N */ protected getConstructorParamInfo( classSymbol: ClassSymbol, parameterNodes: ts.ParameterDeclaration[]): CtorParameter[] { - const paramsProperty = this.getStaticProperty(classSymbol, CONSTRUCTOR_PARAMS); - const paramInfo: ParamInfo[]|null = paramsProperty ? - this.getParamInfoFromStaticProperty(paramsProperty) : - this.getParamInfoFromHelperCall(classSymbol, parameterNodes); + const {constructorParamInfo} = this.acquireDecoratorInfo(classSymbol); return parameterNodes.map((node, index) => { - const {decorators, typeExpression} = paramInfo && paramInfo[index] ? - paramInfo[index] : + const {decorators, typeExpression} = constructorParamInfo[index] ? + constructorParamInfo[index] : {decorators: null, typeExpression: null}; const nameNode = node.name; return { @@ -1153,58 +1289,6 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N return null; } - /** - * Get the parameter type and decorators for a class where the information is stored via - * calls to `__decorate` helpers. - * - * Reflect over the helpers to find the decorators and types about each of - * the class's constructor parameters. - * - * @param classSymbol the class whose parameter info we want to get. - * @param parameterNodes the array of TypeScript parameter nodes for this class's constructor. - * @returns an array of objects containing the type and decorators for each parameter. - */ - protected getParamInfoFromHelperCall( - classSymbol: ClassSymbol, parameterNodes: ts.ParameterDeclaration[]): ParamInfo[] { - const parameters: ParamInfo[] = - parameterNodes.map(() => ({typeExpression: null, decorators: null})); - const helperCalls = this.getHelperCallsForClass(classSymbol, '__decorate'); - helperCalls.forEach(helperCall => { - const {classDecorators} = - this.reflectDecoratorsFromHelperCall(helperCall, makeClassTargetFilter(classSymbol.name)); - classDecorators.forEach(call => { - switch (call.name) { - case '__metadata': - const metadataArg = call.args && call.args[0]; - const typesArg = call.args && call.args[1]; - const isParamTypeDecorator = metadataArg && ts.isStringLiteral(metadataArg) && - metadataArg.text === 'design:paramtypes'; - const types = typesArg && ts.isArrayLiteralExpression(typesArg) && typesArg.elements; - if (isParamTypeDecorator && types) { - types.forEach((type, index) => parameters[index].typeExpression = type); - } - break; - case '__param': - const paramIndexArg = call.args && call.args[0]; - const decoratorCallArg = call.args && call.args[1]; - const paramIndex = paramIndexArg && ts.isNumericLiteral(paramIndexArg) ? - parseInt(paramIndexArg.text, 10) : - NaN; - const decorator = decoratorCallArg && ts.isCallExpression(decoratorCallArg) ? - this.reflectDecoratorCall(decoratorCallArg) : - null; - if (!isNaN(paramIndex) && decorator) { - const decorators = parameters[paramIndex].decorators = - parameters[paramIndex].decorators || []; - decorators.push(decorator); - } - break; - } - }); - }); - return parameters; - } - /** * Search statements related to the given class for calls to the specified helper. * @param classSymbol the class whose helper calls we are interested in. @@ -1377,6 +1461,72 @@ export type ParamInfo = { typeExpression: ts.Expression | null }; +/** + * Represents a call to `tslib.__metadata` as present in `tslib.__decorate` calls. This is a + * synthetic decorator inserted by TypeScript that contains reflection information about the + * target of the decorator, i.e. the class or property. + */ +export interface ParameterTypes { + type: 'params'; + types: ts.Expression[]; +} + +/** + * Represents a call to `tslib.__param` as present in `tslib.__decorate` calls. This contains + * information on any decorators were applied to a certain parameter. + */ +export interface ParameterDecorators { + type: 'param:decorators'; + index: number; + decorator: Decorator; +} + +/** + * Represents a call to a decorator as it was present in the original source code, as present in + * `tslib.__decorate` calls. + */ +export interface DecoratorCall { + type: 'decorator'; + decorator: Decorator; +} + +/** + * Represents the different kinds of decorate helpers that may be present as first argument to + * `tslib.__decorate`, as follows: + * + * ``` + * __decorate([ + * Directive({ selector: '[someDirective]' }), + * tslib_1.__param(2, Inject(INJECTED_TOKEN)), + * tslib_1.__metadata("design:paramtypes", [ViewContainerRef, TemplateRef, String]) + * ], SomeDirective); + * ``` + */ +export type DecorateHelperEntry = ParameterTypes | ParameterDecorators | DecoratorCall; + +/** + * The recorded decorator information of a single class. This information is cached in the host. + */ +interface DecoratorInfo { + /** + * All decorators that were present on the class. If no decorators were present, this is `null` + */ + classDecorators: Decorator[]|null; + + /** + * All decorators per member of the class they were present on. + */ + memberDecorators: Map; + + /** + * Represents the constructor parameter information, such as the type of a parameter and all + * decorators for a certain parameter. Indices in this array correspond with the parameter's index + * in the constructor. Note that this array may be sparse, i.e. certain constructor parameters may + * not have any info recorded. + */ + constructorParamInfo: ParamInfo[]; +} + /** * A statement node that represents an assignment. */ @@ -1397,27 +1547,55 @@ export function isAssignment(node: ts.Node): node is ts.AssignmentExpression boolean; +export function isClassDecorateCall(call: ts.CallExpression, className: string): + call is ts.CallExpression&{arguments: [ts.ArrayLiteralExpression, ts.Expression]} { + const helperArgs = call.arguments[0]; + if (helperArgs === undefined || !ts.isArrayLiteralExpression(helperArgs)) { + return false; + } -/** - * Creates a function that tests whether the given expression is a class target. - * @param className the name of the class we want to target. - */ -export function makeClassTargetFilter(className: string): TargetFilter { - return (target: ts.Expression): boolean => ts.isIdentifier(target) && target.text === className; + const target = call.arguments[1]; + return target !== undefined && ts.isIdentifier(target) && target.text === className; } /** - * Creates a function that tests whether the given expression is a class member target. - * @param className the name of the class we want to target. + * Tests whether the provided call expression targets a member of the class, by verifying its + * arguments are according to the following form: + * + * ``` + * __decorate([], SomeDirective.prototype, "member", void 0); + * ``` + * + * @param call the call expression that is tested to represent a member decorator call. + * @param className the name of the class that the call needs to correspond with. */ -export function makeMemberTargetFilter(className: string): TargetFilter { - return (target: ts.Expression): boolean => ts.isPropertyAccessExpression(target) && - ts.isIdentifier(target.expression) && target.expression.text === className && - target.name.text === 'prototype'; +export function isMemberDecorateCall(call: ts.CallExpression, className: string): + call is ts.CallExpression& + {arguments: [ts.ArrayLiteralExpression, ts.StringLiteral, ts.StringLiteral]} { + const helperArgs = call.arguments[0]; + if (helperArgs === undefined || !ts.isArrayLiteralExpression(helperArgs)) { + return false; + } + + const target = call.arguments[1]; + if (target === undefined || !ts.isPropertyAccessExpression(target) || + !ts.isIdentifier(target.expression) || target.expression.text !== className || + target.name.text !== 'prototype') { + return false; + } + + const memberName = call.arguments[2]; + return memberName !== undefined && ts.isStringLiteral(memberName); } /** diff --git a/packages/compiler-cli/ngcc/test/helpers/utils.ts b/packages/compiler-cli/ngcc/test/helpers/utils.ts index 3b6bd99dd0..bfb679ce86 100644 --- a/packages/compiler-cli/ngcc/test/helpers/utils.ts +++ b/packages/compiler-cli/ngcc/test/helpers/utils.ts @@ -78,6 +78,22 @@ export function convertToDirectTsLibImport(filesystem: TestFile[]) { }); } +export function convertToInlineTsLib(filesystem: TestFile[]) { + return filesystem.map(file => { + const contents = file.contents + .replace(`import * as tslib_1 from 'tslib';`, ` +var __decorate = null; +var __metadata = null; +var __read = null; +var __values = null; +var __param = null; +var __extends = null; +var __assign = null; +`).replace(/tslib_1\./g, ''); + return {...file, contents}; + }); +} + export function getRootFiles(testFiles: TestFile[]): AbsoluteFsPath[] { return testFiles.filter(f => f.isRoot !== false).map(f => absoluteFrom(f.name)); } diff --git a/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts b/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts index 620ca1014e..3e1a062139 100644 --- a/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts @@ -951,23 +951,6 @@ exports.ExternalModule = ExternalModule; expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { - loadTestFiles([SOME_DIRECTIVE_FILE]); - const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost); - const mockImportInfo: Import = {from: '@angular/core', name: 'Directive'}; - const spy = spyOn(host, 'getImportOfIdentifier').and.returnValue(mockImportInfo); - const classNode = getDeclaration( - program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const decorators = host.getDecoratorsOfDeclaration(classNode) !; - - expect(decorators.length).toEqual(1); - expect(decorators[0].import).toBe(mockImportInfo); - - const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier; - expect(typeIdentifier.text).toBe('Directive'); - }); - describe('(returned decorators `args`)', () => { it('should be an empty array if decorator has no `args` property', () => { loadTestFiles([INVALID_DECORATOR_ARGS_FILE]); @@ -1185,22 +1168,17 @@ exports.ExternalModule = ExternalModule; expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { + it('should have import information on decorators', () => { loadTestFiles([SOME_DIRECTIVE_FILE]); const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); const host = new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost); - const mockImportInfo = { name: 'mock', from: '@angular/core' } as Import; - const spy = spyOn(host, 'getImportOfIdentifier').and.returnValue(mockImportInfo); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); const decorators = host.getDecoratorsOfDeclaration(classNode) !; expect(decorators.length).toEqual(1); - expect(decorators[0].import).toBe(mockImportInfo); - - const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier; - expect(typeIdentifier.text).toBe('Directive'); + expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'}); }); describe('(returned prop decorators `args`)', () => { @@ -1430,24 +1408,19 @@ exports.ExternalModule = ExternalModule; expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { + it('should have import information on decorators', () => { loadTestFiles([SOME_DIRECTIVE_FILE]); const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); const host = new CommonJsReflectionHost(new MockLogger(), false, program, compilerHost); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const mockImportInfo: Import = {from: '@angular/core', name: 'Directive'}; - const spy = spyOn(CommonJsReflectionHost.prototype, 'getImportOfIdentifier') - .and.returnValue(mockImportInfo); const parameters = host.getConstructorParameters(classNode); const decorators = parameters ![2].decorators !; expect(decorators.length).toEqual(1); - expect(decorators[0].import).toBe(mockImportInfo); - - const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier; - expect(typeIdentifier.text).toBe('Inject'); + expect(decorators[0].name).toBe('Inject'); + expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'}); }); }); diff --git a/packages/compiler-cli/ngcc/test/host/esm2015_host_import_helper_spec.ts b/packages/compiler-cli/ngcc/test/host/esm2015_host_import_helper_spec.ts index b61461eab8..2eb69d28e5 100644 --- a/packages/compiler-cli/ngcc/test/host/esm2015_host_import_helper_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/esm2015_host_import_helper_spec.ts @@ -15,7 +15,7 @@ import {getDeclaration} from '../../../src/ngtsc/testing'; import {loadFakeCore, loadTestFiles, loadTsLib} from '../../../test/helpers'; import {Esm2015ReflectionHost} from '../../src/host/esm2015_host'; import {MockLogger} from '../helpers/mock_logger'; -import {convertToDirectTsLibImport, makeTestBundleProgram} from '../helpers/utils'; +import {convertToDirectTsLibImport, convertToInlineTsLib, makeTestBundleProgram} from '../helpers/utils'; import {expectTypeValueReferencesForParameters} from './util'; @@ -111,14 +111,16 @@ runInEachFileSystem(() => { ]; const DIRECT_IMPORT_FILES = convertToDirectTsLibImport(NAMESPACED_IMPORT_FILES); + const INLINE_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES); FILES = { 'namespaced': NAMESPACED_IMPORT_FILES, 'direct import': DIRECT_IMPORT_FILES, + 'inline': INLINE_FILES, }; }); - ['namespaced', 'direct import'].forEach(label => { + ['namespaced', 'direct import', 'inline'].forEach(label => { describe(`[${label}]`, () => { beforeEach(() => { const fs = getFileSystem(); @@ -147,29 +149,6 @@ runInEachFileSystem(() => { ]); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { - const spy = - spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier') - .and.callFake( - (identifier: ts.Identifier) => identifier.getText() === 'Directive' ? - {from: '@angular/core', name: 'Directive'} : - {}); - - const {program} = makeTestBundleProgram(_('/some_directive.js')); - const host = - new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker()); - const classNode = getDeclaration( - program, _('/some_directive.js'), 'SomeDirective', isNamedVariableDeclaration); - - const decorators = host.getDecoratorsOfDeclaration(classNode) !; - - expect(decorators.length).toEqual(1); - expect(decorators[0].import).toEqual({from: '@angular/core', name: 'Directive'}); - - const identifiers = spy.calls.all().map(call => (call.args[0] as ts.Identifier).text); - expect(identifiers.some(identifier => identifier === 'Directive')).toBeTruthy(); - }); - it('should support decorators being used inside @angular/core', () => { const {program} = makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js')); @@ -272,21 +251,6 @@ runInEachFileSystem(() => { expect(staticProperty.value !.getText()).toEqual(`'static'`); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { - const spy = - spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier').and.returnValue({}); - - const {program} = makeTestBundleProgram(_('/some_directive.js')); - const host = - new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker()); - const classNode = getDeclaration( - program, _('/some_directive.js'), 'SomeDirective', isNamedVariableDeclaration); - - host.getMembersOfClass(classNode); - const identifiers = spy.calls.all().map(call => (call.args[0] as ts.Identifier).text); - expect(identifiers.some(identifier => identifier === 'Input')).toBeTruthy(); - }); - it('should support decorators being used inside @angular/core', () => { const {program} = makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js')); diff --git a/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts b/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts index 2c5c507ad8..2064b944e6 100644 --- a/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts @@ -763,11 +763,7 @@ runInEachFileSystem(() => { expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { - const mockImportInfo = { from: '@angular/core' } as Import; - const spy = spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier') - .and.returnValue(mockImportInfo); - + it('should have import information on decorators', () => { loadTestFiles([SOME_DIRECTIVE_FILE]); const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker()); @@ -776,10 +772,7 @@ runInEachFileSystem(() => { const decorators = host.getDecoratorsOfDeclaration(classNode) !; expect(decorators.length).toEqual(1); - expect(decorators[0].import).toBe(mockImportInfo); - - const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier; - expect(typeIdentifier.text).toBe('Directive'); + expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'}); }); describe('(returned decorators `args`)', () => { @@ -839,11 +832,13 @@ runInEachFileSystem(() => { expect(input1.kind).toEqual(ClassMemberKind.Property); expect(input1.isStatic).toEqual(false); expect(input1.decorators !.map(d => d.name)).toEqual(['Input']); + expect(input1.decorators ![0].import).toEqual({name: 'Input', from: '@angular/core'}); const input2 = members.find(member => member.name === 'input2') !; expect(input2.kind).toEqual(ClassMemberKind.Property); expect(input2.isStatic).toEqual(false); - expect(input1.decorators !.map(d => d.name)).toEqual(['Input']); + expect(input2.decorators !.map(d => d.name)).toEqual(['Input']); + expect(input2.decorators ![0].import).toEqual({name: 'Input', from: '@angular/core'}); }); it('should find non decorated properties on a class', () => { @@ -991,35 +986,6 @@ runInEachFileSystem(() => { expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'})); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { - let callCount = 0; - const spy = - spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier').and.callFake(() => { - callCount++; - return {name: `name${callCount}`, from: '@angular/core'}; - }); - - loadTestFiles([SOME_DIRECTIVE_FILE]); - const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker()); - const classNode = getDeclaration( - program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedClassDeclaration); - const members = host.getMembersOfClass(classNode); - - expect(spy).toHaveBeenCalled(); - expect(spy.calls.allArgs().map(arg => arg[0].getText())).toEqual([ - 'Input', - 'Input', - 'HostBinding', - 'Input', - 'HostListener', - ]); - - const member = members.find(member => member.name === 'input1') !; - expect(member.decorators !.length).toBe(1); - expect(member.decorators ![0].import).toEqual({name: 'name1', from: '@angular/core'}); - }); - describe('(returned prop decorators `args`)', () => { it('should be an empty array if prop decorator has no `args` property', () => { loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); @@ -1311,11 +1277,7 @@ runInEachFileSystem(() => { expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { - const mockImportInfo: Import = {name: 'mock', from: '@angular/core'}; - const spy = spyOn(Esm2015ReflectionHost.prototype, 'getImportOfIdentifier') - .and.returnValue(mockImportInfo); - + it('should have import information on decorators', () => { loadTestFiles([SOME_DIRECTIVE_FILE]); const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); const host = new Esm2015ReflectionHost(new MockLogger(), false, program.getTypeChecker()); @@ -1325,10 +1287,7 @@ runInEachFileSystem(() => { const decorators = parameters[2].decorators !; expect(decorators.length).toEqual(1); - expect(decorators[0].import).toBe(mockImportInfo); - - const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier; - expect(typeIdentifier.text).toBe('Inject'); + expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'}); }); }); diff --git a/packages/compiler-cli/ngcc/test/host/esm5_host_import_helper_spec.ts b/packages/compiler-cli/ngcc/test/host/esm5_host_import_helper_spec.ts index 4e961f76db..be6eb5a975 100644 --- a/packages/compiler-cli/ngcc/test/host/esm5_host_import_helper_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/esm5_host_import_helper_spec.ts @@ -14,7 +14,7 @@ import {getDeclaration} from '../../../src/ngtsc/testing'; import {loadFakeCore, loadTestFiles, loadTsLib} from '../../../test/helpers'; import {Esm5ReflectionHost} from '../../src/host/esm5_host'; import {MockLogger} from '../helpers/mock_logger'; -import {convertToDirectTsLibImport, makeTestBundleProgram} from '../helpers/utils'; +import {convertToDirectTsLibImport, convertToInlineTsLib, makeTestBundleProgram} from '../helpers/utils'; import {expectTypeValueReferencesForParameters} from './util'; @@ -132,14 +132,16 @@ export { SomeDirective }; ]; const DIRECT_IMPORT_FILES = convertToDirectTsLibImport(NAMESPACED_IMPORT_FILES); + const INLINE_FILES = convertToInlineTsLib(NAMESPACED_IMPORT_FILES); FILES = { 'namespaced': NAMESPACED_IMPORT_FILES, 'direct import': DIRECT_IMPORT_FILES, + 'inline': INLINE_FILES, }; }); - ['namespaced', 'direct import'].forEach(label => { + ['namespaced', 'direct import', 'inline'].forEach(label => { describe(`[${label}]`, () => { beforeEach(() => { const fs = getFileSystem(); @@ -167,28 +169,6 @@ export { SomeDirective }; ]); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { - const spy = - spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier') - .and.callFake( - (identifier: ts.Identifier) => identifier.getText() === 'Directive' ? - {from: '@angular/core', name: 'Directive'} : - {}); - - const {program} = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker()); - const classNode = getDeclaration( - program, _('/some_directive.js'), 'SomeDirective', isNamedVariableDeclaration); - - const decorators = host.getDecoratorsOfDeclaration(classNode) !; - - expect(decorators.length).toEqual(1); - expect(decorators[0].import).toEqual({from: '@angular/core', name: 'Directive'}); - - const identifiers = spy.calls.all().map(call => (call.args[0] as ts.Identifier).text); - expect(identifiers.some(identifier => identifier === 'Directive')).toBeTruthy(); - }); - it('should support decorators being used inside @angular/core', () => { const {program} = makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js')); @@ -270,20 +250,6 @@ export { SomeDirective }; expect(staticProperty.value !.getText()).toEqual(`'static'`); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { - const spy = - spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier').and.returnValue({}); - - const {program} = makeTestBundleProgram(_('/some_directive.js')); - const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker()); - const classNode = getDeclaration( - program, _('/some_directive.js'), 'SomeDirective', isNamedVariableDeclaration); - - host.getMembersOfClass(classNode); - const identifiers = spy.calls.all().map(call => (call.args[0] as ts.Identifier).text); - expect(identifiers.some(identifier => identifier === 'Input')).toBeTruthy(); - }); - it('should support decorators being used inside @angular/core', () => { const {program} = makeTestBundleProgram(_('/node_modules/@angular/core/some_directive.js')); @@ -319,12 +285,7 @@ export { SomeDirective }; }); describe('(returned parameters `decorators`)', () => { - it('should use `getImportOfIdentifier()` to retrieve import info', () => { - const mockImportInfo = {} as Import; - const spy = spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier') - .and.returnValue(mockImportInfo); - - + it('should have import information on decorators', () => { const {program} = makeTestBundleProgram(_('/some_directive.js')); const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker()); @@ -334,10 +295,7 @@ export { SomeDirective }; const decorators = parameters ![2].decorators !; expect(decorators.length).toEqual(1); - expect(decorators[0].import).toBe(mockImportInfo); - - const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier; - expect(typeIdentifier.text).toBe('Inject'); + expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'}); }); }); }); diff --git a/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts b/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts index a898b190cb..11ff1a063e 100644 --- a/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts @@ -942,11 +942,7 @@ runInEachFileSystem(() => { expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { - const mockImportInfo = { name: 'mock', from: '@angular/core' } as Import; - const spy = spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier') - .and.returnValue(mockImportInfo); - + it('should have import information on decorators', () => { loadTestFiles([SOME_DIRECTIVE_FILE]); const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker()); @@ -955,10 +951,7 @@ runInEachFileSystem(() => { const decorators = host.getDecoratorsOfDeclaration(classNode) !; expect(decorators.length).toEqual(1); - expect(decorators[0].import).toBe(mockImportInfo); - - const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier; - expect(typeIdentifier.text).toBe('Directive'); + expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'}); }); describe('(returned decorators `args`)', () => { @@ -1019,11 +1012,13 @@ runInEachFileSystem(() => { expect(input1.kind).toEqual(ClassMemberKind.Property); expect(input1.isStatic).toEqual(false); expect(input1.decorators !.map(d => d.name)).toEqual(['Input']); + expect(input1.decorators ![0].import).toEqual({name: 'Input', from: '@angular/core'}); const input2 = members.find(member => member.name === 'input2') !; expect(input2.kind).toEqual(ClassMemberKind.Property); expect(input2.isStatic).toEqual(false); expect(input2.decorators !.map(d => d.name)).toEqual(['Input']); + expect(input2.decorators ![0].import).toEqual({name: 'Input', from: '@angular/core'}); }); it('should find decorated members on a class', () => { @@ -1232,30 +1227,6 @@ runInEachFileSystem(() => { expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Input'})); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { - let callCount = 0; - const spy = - spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier').and.callFake(() => { - callCount++; - return {name: `name${callCount}`, from: `@angular/core`}; - }); - - loadTestFiles([SOME_DIRECTIVE_FILE]); - const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); - const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker()); - const classNode = getDeclaration( - program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); - const members = host.getMembersOfClass(classNode); - - expect(spy).toHaveBeenCalled(); - spy.calls.allArgs().forEach(arg => expect(arg[0].getText()).toEqual('Input')); - - const index = members.findIndex(member => member.name === 'input1'); - expect(members[index].decorators !.length).toBe(1); - expect(members[index].decorators ![0].import) - .toEqual({name: 'name1', from: '@angular/core'}); - }); - describe('(returned prop decorators `args`)', () => { it('should be an empty array if prop decorator has no `args` property', () => { loadTestFiles([INVALID_PROP_DECORATOR_ARGS_FILE]); @@ -1466,11 +1437,7 @@ runInEachFileSystem(() => { expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Inject'})); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { - const mockImportInfo = { name: 'mock', from: '@angulare/core' } as Import; - const spy = spyOn(Esm5ReflectionHost.prototype, 'getImportOfIdentifier') - .and.returnValue(mockImportInfo); - + it('should have import information on decorators', () => { loadTestFiles([SOME_DIRECTIVE_FILE]); const {program} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker()); @@ -1480,10 +1447,7 @@ runInEachFileSystem(() => { const decorators = parameters ![2].decorators !; expect(decorators.length).toEqual(1); - expect(decorators[0].import).toBe(mockImportInfo); - - const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier; - expect(typeIdentifier.text).toBe('Inject'); + expect(decorators[0].import).toEqual({name: 'Inject', from: '@angular/core'}); }); }); diff --git a/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts b/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts index c62815f997..0791c6da37 100644 --- a/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts @@ -1057,22 +1057,17 @@ runInEachFileSystem(() => { expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { + it('should have import information on decorators', () => { loadTestFiles([SOME_DIRECTIVE_FILE]); const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost); - const mockImportInfo: Import = {from: '@angular/core', name: 'Directive'}; - const spy = spyOn(host, 'getImportOfIdentifier').and.returnValue(mockImportInfo); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); const decorators = host.getDecoratorsOfDeclaration(classNode) !; expect(decorators.length).toEqual(1); - expect(decorators[0].import).toBe(mockImportInfo); - - const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier; - expect(typeIdentifier.text).toBe('Directive'); + expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'}); }); it('should find decorated members on a class at the top level', () => { @@ -1290,22 +1285,17 @@ runInEachFileSystem(() => { expect(decorators[0]).toEqual(jasmine.objectContaining({name: 'Directive'})); }); - it('should use `getImportOfIdentifier()` to retrieve import info', () => { + it('should have import information on decorators', () => { loadTestFiles([SOME_DIRECTIVE_FILE]); const {program, host: compilerHost} = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name); const host = new UmdReflectionHost(new MockLogger(), false, program, compilerHost); - const mockImportInfo = { name: 'mock', from: '@angular/core' } as Import; - const spy = spyOn(host, 'getImportOfIdentifier').and.returnValue(mockImportInfo); const classNode = getDeclaration( program, SOME_DIRECTIVE_FILE.name, 'SomeDirective', isNamedVariableDeclaration); const decorators = host.getDecoratorsOfDeclaration(classNode) !; expect(decorators.length).toEqual(1); - expect(decorators[0].import).toBe(mockImportInfo); - - const typeIdentifier = spy.calls.mostRecent().args[0] as ts.Identifier; - expect(typeIdentifier.text).toBe('Directive'); + expect(decorators[0].import).toEqual({name: 'Directive', from: '@angular/core'}); }); describe('(returned prop decorators `args`)', () => {