fix(ivy): handle aliased Angular decorators (#29195)
Prior to this change the code didn't take into account the fact that decorators can be aliases while importing into a script. As a result, these decorators were not recognized by Angular and various failures happened because of that. Now we take aliases into account and resolve decorator name properly. PR Close #29195
This commit is contained in:
parent
99aa9674b2
commit
1d88c2bb81
|
@ -81,7 +81,7 @@ export class DecorationAnalyzer {
|
|||
importGraph = new ImportGraph(this.moduleResolver);
|
||||
cycleAnalyzer = new CycleAnalyzer(this.importGraph);
|
||||
handlers: DecoratorHandler<any, any>[] = [
|
||||
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,
|
||||
|
|
|
@ -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<R3BaseRefMetaData, R3BaseRefDecoratorDetection> {
|
||||
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<R3BaseRefDecoratorDetection>|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});
|
||||
|
|
|
@ -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<string, Expression>();
|
||||
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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<NgModuleAnalys
|
|||
if (!decorators) {
|
||||
return undefined;
|
||||
}
|
||||
const decorator = decorators.find(
|
||||
decorator => decorator.name === 'NgModule' && (this.isCore || isAngularCore(decorator)));
|
||||
const decorator = findAngularDecorator(decorators, 'NgModule', this.isCore);
|
||||
if (decorator !== undefined) {
|
||||
return {
|
||||
trigger: decorator.node,
|
||||
|
|
|
@ -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<PipeHandlerData, D
|
|||
if (!decorators) {
|
||||
return undefined;
|
||||
}
|
||||
const decorator = decorators.find(
|
||||
decorator => decorator.name === 'Pipe' && (this.isCore || isAngularCore(decorator)));
|
||||
const decorator = findAngularDecorator(decorators, 'Pipe', this.isCore);
|
||||
if (decorator !== undefined) {
|
||||
return {
|
||||
trigger: decorator.node,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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', '<p>Hello World</p>');
|
||||
|
|
Loading…
Reference in New Issue