fix(ivy): ngtsc should pay attention to declaration order (#25392)
When generating the 'directives:' property of ngComponentDef, ngtsc needs to be conscious of declaration order. If a directive being written into the array is declarated after the component currently being compiled, then the entire directives array needs to be wrapped in a closure. This commit fixes ngtsc to pay attention to such ordering issues within directives arrays. PR Close #25392
This commit is contained in:

committed by
Ben Lesh

parent
6f085f8610
commit
2befc65777
@ -142,6 +142,7 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
||||
// analyzed and the full compilation scope for the component can be realized.
|
||||
pipes: EMPTY_MAP,
|
||||
directives: EMPTY_MAP,
|
||||
wrapDirectivesInClosure: false,
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -155,7 +156,9 @@ export class ComponentDecoratorHandler implements DecoratorHandler<R3ComponentMe
|
||||
// Replace the empty components and directives from the analyze() step with a fully expanded
|
||||
// scope. This is possible now because during compile() the whole compilation unit has been
|
||||
// fully analyzed.
|
||||
analysis = {...analysis, ...scope};
|
||||
const {directives, pipes, containsForwardDecls} = scope;
|
||||
const wrapDirectivesInClosure: boolean = !!containsForwardDecls;
|
||||
analysis = {...analysis, directives, pipes, wrapDirectivesInClosure};
|
||||
}
|
||||
|
||||
const res = compileComponentFromMetadata(analysis, pool, makeBindingParser());
|
||||
|
@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Expression, ExternalExpr, ExternalReference} from '@angular/compiler';
|
||||
import {Expression, ExternalExpr, ExternalReference, WrappedNodeExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ReflectionHost} from '../../host';
|
||||
@ -33,6 +33,7 @@ export interface ModuleData {
|
||||
export interface CompilationScope<T> {
|
||||
directives: Map<string, T>;
|
||||
pipes: Map<string, T>;
|
||||
containsForwardDecls?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -146,7 +147,7 @@ export class SelectorScopeRegistry {
|
||||
|
||||
// The scope as cached is in terms of References, not Expressions. Converting between them
|
||||
// requires knowledge of the context file (in this case, the component node's source file).
|
||||
return convertScopeToExpressions(scope, node.getSourceFile());
|
||||
return convertScopeToExpressions(scope, node);
|
||||
}
|
||||
|
||||
// This is the first time the scope for this module is being computed.
|
||||
@ -179,7 +180,7 @@ export class SelectorScopeRegistry {
|
||||
this._compilationScopeCache.set(node, scope);
|
||||
|
||||
// Convert References to Expressions in the context of the component's source file.
|
||||
return convertScopeToExpressions(scope, node.getSourceFile());
|
||||
return convertScopeToExpressions(scope, node);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -390,8 +391,40 @@ function convertReferenceMap(
|
||||
}
|
||||
|
||||
function convertScopeToExpressions(
|
||||
scope: CompilationScope<Reference>, context: ts.SourceFile): CompilationScope<Expression> {
|
||||
const directives = convertReferenceMap(scope.directives, context);
|
||||
const pipes = convertReferenceMap(scope.pipes, context);
|
||||
return {directives, pipes};
|
||||
scope: CompilationScope<Reference>, context: ts.Declaration): CompilationScope<Expression> {
|
||||
const sourceContext = ts.getOriginalNode(context).getSourceFile();
|
||||
const directives = convertReferenceMap(scope.directives, sourceContext);
|
||||
const pipes = convertReferenceMap(scope.pipes, sourceContext);
|
||||
const declPointer = maybeUnwrapNameOfDeclaration(context);
|
||||
let containsForwardDecls = false;
|
||||
directives.forEach(expr => {
|
||||
containsForwardDecls =
|
||||
containsForwardDecls || isExpressionForwardReference(expr, declPointer, sourceContext);
|
||||
});
|
||||
!containsForwardDecls && pipes.forEach(expr => {
|
||||
containsForwardDecls =
|
||||
containsForwardDecls || isExpressionForwardReference(expr, declPointer, sourceContext);
|
||||
});
|
||||
return {directives, pipes, containsForwardDecls};
|
||||
}
|
||||
|
||||
function isExpressionForwardReference(
|
||||
expr: Expression, context: ts.Node, contextSource: ts.SourceFile): boolean {
|
||||
if (isWrappedTsNodeExpr(expr)) {
|
||||
const node = ts.getOriginalNode(expr.node);
|
||||
return node.getSourceFile() === contextSource && context.pos < node.pos;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isWrappedTsNodeExpr(expr: Expression): expr is WrappedNodeExpr<ts.Node> {
|
||||
return expr instanceof WrappedNodeExpr;
|
||||
}
|
||||
|
||||
function maybeUnwrapNameOfDeclaration(decl: ts.Declaration): ts.Declaration|ts.Identifier {
|
||||
if ((ts.isClassDeclaration(decl) || ts.isVariableDeclaration(decl)) && decl.name !== undefined &&
|
||||
ts.isIdentifier(decl.name)) {
|
||||
return decl.name;
|
||||
}
|
||||
return decl;
|
||||
}
|
Reference in New Issue
Block a user