fix(ngcc): identifier ModuleWithProviders functions in IIFE wrapped classes (#37206)

In ES2015 IIFE wrapped classes, the identifier that would reference the class
of the NgModule may be an alias variable. Previously the `Esm2015ReflectionHost`
was not able to match this alias to the original class declaration. This resulted
in failing to identify some `ModuleWithProviders` functions in such case.

These IIFE wrapped classes were introduced in TypeScript 3.9, which is why
this issue is only recently appearing. Since 9.1.x does not support TS 3.9
there is no reason to backport this commit to that branch.

Fixes #37189

PR Close #37206
This commit is contained in:
Pete Bacon Darwin
2020-05-19 20:42:54 +01:00
committed by Kara Erickson
parent d42a912343
commit 97e13991c5
3 changed files with 147 additions and 31 deletions

View File

@ -132,6 +132,13 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
return symbol;
}
if (declaration.parent !== undefined && isNamedVariableDeclaration(declaration.parent)) {
const variableValue = this.getVariableValue(declaration.parent);
if (variableValue !== null) {
declaration = variableValue;
}
}
return this.getClassSymbolFromInnerDeclaration(declaration);
}
@ -294,8 +301,15 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
if (superDeclaration.known !== null || superDeclaration.identity !== null) {
return superDeclaration;
}
let declarationNode: ts.Node = superDeclaration.node;
if (isNamedVariableDeclaration(superDeclaration.node) && !isTopLevel(superDeclaration.node)) {
const variableValue = this.getVariableValue(superDeclaration.node);
if (variableValue !== null && ts.isClassExpression(variableValue)) {
declarationNode = getContainingStatement(variableValue);
}
}
const outerClassNode = getClassDeclarationFromInnerDeclaration(superDeclaration.node);
const outerClassNode = getClassDeclarationFromInnerDeclaration(declarationNode);
const declaration = outerClassNode !== null ?
this.getDeclarationOfIdentifier(outerClassNode.name) :
superDeclaration;
@ -2525,19 +2539,20 @@ function isTopLevel(node: ts.Node): boolean {
/**
* Get the actual (outer) declaration of a class.
*
* In ES5, the implementation of a class is a function expression that is hidden inside an IIFE and
* Sometimes, the implementation of a class is an expression that is hidden inside an IIFE and
* returned to be assigned to a variable outside the IIFE, which is what the rest of the program
* interacts with.
*
* Given the inner function declaration, we want to get to the declaration of the outer variable
* that represents the class.
* Given the inner declaration, we want to get to the declaration of the outer variable that
* represents the class.
*
* @param node a node that could be the function expression inside an ES5 class IIFE.
* @returns the outer variable declaration or `undefined` if it is not a "class".
* @param node a node that could be the inner declaration inside an IIFE.
* @returns the outer variable declaration or `null` if it is not a "class".
*/
export function getClassDeclarationFromInnerDeclaration(node: ts.Node):
ClassDeclaration<ts.VariableDeclaration>|null {
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) {
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) ||
ts.isVariableStatement(node)) {
// It might be the function expression inside the IIFE. We need to go 5 levels up...
// - IIFE body.