diff --git a/packages/compiler-cli/src/ngcc/src/analysis/decoration_analyzer.ts b/packages/compiler-cli/src/ngcc/src/analysis/decoration_analyzer.ts index bbc4cfb50c..5d50f1d025 100644 --- a/packages/compiler-cli/src/ngcc/src/analysis/decoration_analyzer.ts +++ b/packages/compiler-cli/src/ngcc/src/analysis/decoration_analyzer.ts @@ -81,7 +81,7 @@ export class DecorationAnalyzer { importGraph = new ImportGraph(this.moduleResolver); cycleAnalyzer = new CycleAnalyzer(this.importGraph); handlers: DecoratorHandler[] = [ - new BaseDefDecoratorHandler(this.reflectionHost, this.evaluator), + new BaseDefDecoratorHandler(this.reflectionHost, this.evaluator, this.isCore), new ComponentDecoratorHandler( this.reflectionHost, this.evaluator, this.scopeRegistry, this.isCore, this.resourceManager, this.rootDirs, /* defaultPreserveWhitespaces */ false, /* i18nUseExternalIds */ true, diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/base_def.ts b/packages/compiler-cli/src/ngtsc/annotations/src/base_def.ts index 68941ae221..fd79d60cba 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/base_def.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/base_def.ts @@ -12,27 +12,30 @@ import * as ts from 'typescript'; import {PartialEvaluator} from '../../partial_evaluator'; import {ClassMember, Decorator, ReflectionHost} from '../../reflection'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform'; -import {isAngularCore} from './util'; -function containsNgTopLevelDecorator(decorators: Decorator[] | null): boolean { +import {isAngularDecorator} from './util'; + +function containsNgTopLevelDecorator(decorators: Decorator[] | null, isCore: boolean): boolean { if (!decorators) { return false; } - return decorators.find( - decorator => (decorator.name === 'Component' || decorator.name === 'Directive' || - decorator.name === 'NgModule') && - isAngularCore(decorator)) !== undefined; + return decorators.some( + decorator => isAngularDecorator(decorator, 'Component', isCore) || + isAngularDecorator(decorator, 'Directive', isCore) || + isAngularDecorator(decorator, 'NgModule', isCore)); } export class BaseDefDecoratorHandler implements DecoratorHandler { - constructor(private reflector: ReflectionHost, private evaluator: PartialEvaluator) {} + constructor( + private reflector: ReflectionHost, private evaluator: PartialEvaluator, + private isCore: boolean) {} readonly precedence = HandlerPrecedence.WEAK; detect(node: ts.ClassDeclaration, decorators: Decorator[]|null): DetectResult|undefined { - if (containsNgTopLevelDecorator(decorators)) { + if (containsNgTopLevelDecorator(decorators, this.isCore)) { // If the class is already decorated by @Component or @Directive let that // DecoratorHandler handle this. BaseDef is unnecessary. return undefined; @@ -44,12 +47,11 @@ export class BaseDefDecoratorHandler implements const {decorators} = property; if (decorators) { for (const decorator of decorators) { - const decoratorName = decorator.name; - if (decoratorName === 'Input' && isAngularCore(decorator)) { + if (isAngularDecorator(decorator, 'Input', this.isCore)) { result = result || {}; const inputs = result.inputs = result.inputs || []; inputs.push({decorator, property}); - } else if (decoratorName === 'Output' && isAngularCore(decorator)) { + } else if (isAngularDecorator(decorator, 'Output', this.isCore)) { result = result || {}; const outputs = result.outputs = result.outputs || []; outputs.push({decorator, property}); diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts index 10843c0544..9156e7890e 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts @@ -23,7 +23,7 @@ import {tsSourceMapBug29300Fixed} from '../../util/src/ts_source_map_bug_29300'; import {ResourceLoader} from './api'; import {extractDirectiveMetadata, extractQueriesFromDecorator, parseFieldArrayValue, queriesFromFields} from './directive'; import {generateSetClassMetadataCall} from './metadata'; -import {isAngularCore, isAngularCoreReference, unwrapExpression} from './util'; +import {findAngularDecorator, isAngularCoreReference, unwrapExpression} from './util'; const EMPTY_MAP = new Map(); const EMPTY_ARRAY: any[] = []; @@ -64,8 +64,7 @@ export class ComponentDecoratorHandler implements if (!decorators) { return undefined; } - const decorator = decorators.find( - decorator => decorator.name === 'Component' && (this.isCore || isAngularCore(decorator))); + const decorator = findAngularDecorator(decorators, 'Component', this.isCore); if (decorator !== undefined) { return { trigger: decorator.node, diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts index 4c36a13521..71099dfba7 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts @@ -7,7 +7,6 @@ */ import {ConstantPool, Expression, ParseError, ParsedHostBindings, R3DirectiveMetadata, R3QueryMetadata, Statement, WrappedNodeExpr, compileDirectiveFromMetadata, makeBindingParser, parseHostBindings, verifyHostBindings} from '@angular/compiler'; - import * as ts from 'typescript'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; @@ -19,7 +18,7 @@ import {extractDirectiveGuards} from '../../scope/src/util'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform'; import {generateSetClassMetadataCall} from './metadata'; -import {getValidConstructorDependencies, isAngularCore, unwrapExpression, unwrapForwardRef} from './util'; +import {findAngularDecorator, getValidConstructorDependencies, unwrapExpression, unwrapForwardRef} from './util'; const EMPTY_OBJECT: {[key: string]: string} = {}; @@ -39,8 +38,7 @@ export class DirectiveDecoratorHandler implements if (!decorators) { return undefined; } - const decorator = decorators.find( - decorator => decorator.name === 'Directive' && (this.isCore || isAngularCore(decorator))); + const decorator = findAngularDecorator(decorators, 'Directive', this.isCore); if (decorator !== undefined) { return { trigger: decorator.node, diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts index 6bd05da041..cc90389f81 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts @@ -14,7 +14,7 @@ import {Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection' import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform'; import {generateSetClassMetadataCall} from './metadata'; -import {getConstructorDependencies, getValidConstructorDependencies, isAngularCore, validateConstructorDependencies} from './util'; +import {findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, validateConstructorDependencies} from './util'; export interface InjectableHandlerData { meta: R3InjectableMetadata; @@ -36,8 +36,7 @@ export class InjectableDecoratorHandler implements if (!decorators) { return undefined; } - const decorator = decorators.find( - decorator => decorator.name === 'Injectable' && (this.isCore || isAngularCore(decorator))); + const decorator = findAngularDecorator(decorators, 'Injectable', this.isCore); if (decorator !== undefined) { return { trigger: decorator.node, diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts index 828c3ee344..78e3b66118 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts @@ -20,7 +20,7 @@ import {getSourceFile} from '../../util/src/typescript'; import {generateSetClassMetadataCall} from './metadata'; import {ReferencesRegistry} from './references_registry'; -import {combineResolvers, forwardRefResolver, getValidConstructorDependencies, isAngularCore, toR3Reference, unwrapExpression} from './util'; +import {combineResolvers, findAngularDecorator, forwardRefResolver, getValidConstructorDependencies, toR3Reference, unwrapExpression} from './util'; export interface NgModuleAnalysis { ngModuleDef: R3NgModuleMetadata; @@ -47,8 +47,7 @@ export class NgModuleDecoratorHandler implements DecoratorHandler decorator.name === 'NgModule' && (this.isCore || isAngularCore(decorator))); + const decorator = findAngularDecorator(decorators, 'NgModule', this.isCore); if (decorator !== undefined) { return { trigger: decorator.node, diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts b/packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts index e00c3c3c4f..ec7064d1ad 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts @@ -17,7 +17,7 @@ import {LocalModuleScopeRegistry} from '../../scope/src/local'; import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform'; import {generateSetClassMetadataCall} from './metadata'; -import {getValidConstructorDependencies, isAngularCore, unwrapExpression} from './util'; +import {findAngularDecorator, getValidConstructorDependencies, unwrapExpression} from './util'; export interface PipeHandlerData { meta: R3PipeMetadata; @@ -35,8 +35,7 @@ export class PipeDecoratorHandler implements DecoratorHandler decorator.name === 'Pipe' && (this.isCore || isAngularCore(decorator))); + const decorator = findAngularDecorator(decorators, 'Pipe', this.isCore); if (decorator !== undefined) { return { trigger: decorator.node, diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts index 65feb61606..c27253d8ad 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/util.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/util.ts @@ -12,7 +12,7 @@ import * as ts from 'typescript'; import {ErrorCode, FatalDiagnosticError} from '../../diagnostics'; import {ImportMode, Reference, ReferenceEmitter} from '../../imports'; import {ForeignFunctionResolver} from '../../partial_evaluator'; -import {ClassMemberKind, CtorParameter, Decorator, ReflectionHost, TypeValueReference} from '../../reflection'; +import {ClassMemberKind, CtorParameter, Decorator, Import, ReflectionHost, TypeValueReference} from '../../reflection'; export enum ConstructorDepErrorKind { NO_SUITABLE_TOKEN, @@ -149,14 +149,28 @@ export function toR3Reference( return {value, type}; } -export function isAngularCore(decorator: Decorator): boolean { +export function isAngularCore(decorator: Decorator): decorator is Decorator&{import: Import} { return decorator.import !== null && decorator.import.from === '@angular/core'; } -export function isAngularCoreReference(reference: Reference, symbolName: string) { +export function isAngularCoreReference(reference: Reference, symbolName: string): boolean { return reference.ownedByModuleGuess === '@angular/core' && reference.debugName === symbolName; } +export function findAngularDecorator( + decorators: Decorator[], name: string, isCore: boolean): Decorator|undefined { + return decorators.find(decorator => isAngularDecorator(decorator, name, isCore)); +} + +export function isAngularDecorator(decorator: Decorator, name: string, isCore: boolean): boolean { + if (isCore) { + return decorator.name === name; + } else if (isAngularCore(decorator)) { + return decorator.import.name === name; + } + return false; +} + /** * Unwrap a `ts.Expression`, removing outer type-casts or parentheses until the expression is in its * lowest level form. diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index 50d1b9eb7f..403e48f9c4 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -381,7 +381,7 @@ export class NgtscProgram implements api.Program { // Set up the IvyCompilation, which manages state for the Ivy transformer. const handlers = [ - new BaseDefDecoratorHandler(this.reflector, evaluator), + new BaseDefDecoratorHandler(this.reflector, evaluator, this.isCore), new ComponentDecoratorHandler( this.reflector, evaluator, scopeRegistry, this.isCore, this.resourceManager, this.rootDirs, this.options.preserveWhitespaces || false, diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index d9247ca57c..9e8be5578e 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -293,6 +293,75 @@ describe('ngtsc behavioral tests', () => { expect(jsContents).toContain('/** @nocollapse */ TestCmp.ngComponentDef'); }); + it('should recognize aliased decorators', () => { + env.tsconfig({}); + env.write('test.ts', ` + import { + Component as AngularComponent, + Directive as AngularDirective, + Pipe as AngularPipe, + Injectable as AngularInjectable, + NgModule as AngularNgModule, + Input as AngularInput, + Output as AngularOutput + } from '@angular/core'; + + export class TestBase { + @AngularInput() input: any; + @AngularOutput() output: any; + } + + @AngularComponent({ + selector: 'test-component', + template: '...' + }) + export class TestComponent { + @AngularInput() input: any; + @AngularOutput() output: any; + } + + @AngularDirective({ + selector: 'test-directive' + }) + export class TestDirective {} + + @AngularPipe({ + name: 'test-pipe' + }) + export class TestPipe {} + + @AngularInjectable({}) + export class TestInjectable {} + + @AngularNgModule({ + declarations: [ + TestComponent, + TestDirective, + TestPipe + ], + exports: [ + TestComponent, + TestDirective, + TestPipe + ] + }) + class MyModule {} + `); + + env.driveMain(); + + const jsContents = env.getContents('test.js'); + expect(jsContents).toContain('TestBase.ngBaseDef = i0.ɵdefineBase'); + expect(jsContents).toContain('TestComponent.ngComponentDef = i0.ɵdefineComponent'); + expect(jsContents).toContain('TestDirective.ngDirectiveDef = i0.ɵdefineDirective'); + expect(jsContents).toContain('TestPipe.ngPipeDef = i0.ɵdefinePipe'); + expect(jsContents).toContain('TestInjectable.ngInjectableDef = i0.defineInjectable'); + expect(jsContents).toContain('MyModule.ngModuleDef = i0.ɵdefineNgModule'); + expect(jsContents).toContain('MyModule.ngInjectorDef = i0.defineInjector'); + expect(jsContents).toContain('inputs: { input: "input" }'); + expect(jsContents).toContain('outputs: { output: "output" }'); + }); + it('should compile Components with a templateUrl in a different rootDir', () => { env.tsconfig({}, ['./extraRootDir']); env.write('extraRootDir/test.html', '

Hello World

');