diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts index f68244a849..4889294ac1 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts @@ -202,8 +202,7 @@ export function extractDirectiveMetadata( } // Detect if the component inherits from another class - const usesInheritance = clazz.heritageClauses !== undefined && - clazz.heritageClauses.some(hc => hc.token === ts.SyntaxKind.ExtendsKeyword); + const usesInheritance = reflector.hasBaseClass(clazz); const metadata: R3DirectiveMetadata = { name: clazz.name !.text, deps: getValidConstructorDependencies(clazz, reflector, isCore), host, diff --git a/packages/compiler-cli/src/ngtsc/annotations/test/directive_spec.ts b/packages/compiler-cli/src/ngtsc/annotations/test/directive_spec.ts new file mode 100644 index 0000000000..769ac30700 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/annotations/test/directive_spec.ts @@ -0,0 +1,80 @@ +/** + * @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 {ReferenceEmitter} from '../../imports'; +import {PartialEvaluator} from '../../partial_evaluator'; +import {TypeScriptReflectionHost} from '../../reflection'; +import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript'; +import {DirectiveDecoratorHandler} from '../src/directive'; +import {SelectorScopeRegistry} from '../src/selector_scope'; + + +describe('DirectiveDecoratorHandler', () => { + it('should use the `ReflectionHost` to detect class inheritance', () => { + const {program, options, host} = makeProgram([ + { + name: 'node_modules/@angular/core/index.d.ts', + contents: 'export const Directive: any;', + }, + { + name: 'entry.ts', + contents: ` + import {Directive} from '@angular/core'; + + @Directive({selector: 'test-dir-1'}) + export class TestDir1 {} + + @Directive({selector: 'test-dir-2'}) + export class TestDir2 {} + `, + }, + ]); + + const checker = program.getTypeChecker(); + const reflectionHost = new TestReflectionHost(checker); + const evaluator = new PartialEvaluator(reflectionHost, checker); + const handler = new DirectiveDecoratorHandler( + reflectionHost, evaluator, + new SelectorScopeRegistry(checker, reflectionHost, new ReferenceEmitter([])), false); + + const analyzeDirective = (dirName: string) => { + const DirNode = getDeclaration(program, 'entry.ts', dirName, ts.isClassDeclaration); + + const detected = handler.detect(DirNode, reflectionHost.getDecoratorsOfDeclaration(DirNode)); + if (detected === undefined) { + throw new Error(`Failed to recognize @Directive (${dirName}).`); + } + + const {analysis} = handler.analyze(DirNode, detected.metadata); + if (analysis === undefined) { + throw new Error(`Failed to analyze @Directive (${dirName}).`); + } + + return analysis; + }; + + // By default, `TestReflectionHost#hasBaseClass()` returns `false`. + const analysis1 = analyzeDirective('TestDir1'); + expect(analysis1.meta.usesInheritance).toBe(false); + + // Tweak `TestReflectionHost#hasBaseClass()` to return true. + reflectionHost.hasBaseClassReturnValue = true; + + const analysis2 = analyzeDirective('TestDir2'); + expect(analysis2.meta.usesInheritance).toBe(true); + }); +}); + +// Helpers +class TestReflectionHost extends TypeScriptReflectionHost { + hasBaseClassReturnValue = false; + + hasBaseClass(node: ts.Declaration): boolean { return this.hasBaseClassReturnValue; } +}