refactor(ngcc): simplify and rename `getClassDeclarationFromInnerDeclaration()` (#38959)

The new function does not try to restrict the kind of AST node that it
finds, leaving that to the caller. This will make it more resuable in the
UMD reflection host.

PR Close #38959
This commit is contained in:
Pete Bacon Darwin 2020-09-27 11:42:43 +01:00 committed by atscott
parent 5038e5741b
commit 1d6e67478e
3 changed files with 54 additions and 51 deletions

View File

@ -302,6 +302,7 @@ 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);
@ -310,9 +311,9 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
}
}
const outerClassNode = getClassDeclarationFromInnerDeclaration(declarationNode);
const declaration = outerClassNode !== null ?
this.getDeclarationOfIdentifier(outerClassNode.name) :
const outerNode = getOuterNodeFromInnerDeclaration(declarationNode);
const declaration = outerNode !== null && isNamedVariableDeclaration(outerNode) ?
this.getDeclarationOfIdentifier(outerNode.name) :
superDeclaration;
if (declaration === null || declaration.node === null || declaration.known !== null) {
return declaration;
@ -2569,51 +2570,53 @@ function isTopLevel(node: ts.Node): boolean {
}
/**
* Get the actual (outer) declaration of a class.
* Get a node that represents the actual (outer) declaration of a class from its implementation.
*
* 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.
* assigned to a variable outside the IIFE, which is what the rest of the program interacts with.
* For example,
*
* Given the inner declaration, we want to get to the declaration of the outer variable that
* represents the class.
* ```
* OuterNode = Alias = (function() { function InnerNode() {} return InnerNode; })();
* ```
*
* @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".
* @param node a node that could be the implementation inside an IIFE.
* @returns a node that represents the outer declaration, or `null` if it is does not match the IIFE
* format shown above.
*/
export function getClassDeclarationFromInnerDeclaration(node: ts.Node):
ClassDeclaration<ts.VariableDeclaration>|null {
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.
let outerNode = node.parent;
if (!outerNode || !ts.isBlock(outerNode)) return null;
// - IIFE function expression.
outerNode = outerNode.parent;
if (!outerNode || (!ts.isFunctionExpression(outerNode) && !ts.isArrowFunction(outerNode))) {
return null;
}
outerNode = outerNode.parent;
// - Parenthesis inside IIFE.
if (outerNode && ts.isParenthesizedExpression(outerNode)) outerNode = outerNode.parent;
// - IIFE call expression.
if (!outerNode || !ts.isCallExpression(outerNode)) return null;
outerNode = outerNode.parent;
// - Parenthesis around IIFE.
if (outerNode && ts.isParenthesizedExpression(outerNode)) outerNode = outerNode.parent;
// - Outer variable declaration.
if (!outerNode || !ts.isVariableDeclaration(outerNode)) return null;
// Finally, ensure that the variable declaration has a `name` identifier.
return hasNameIdentifier(outerNode) ? outerNode : null;
export function getOuterNodeFromInnerDeclaration(node: ts.Node): ts.Node|null {
if (!ts.isFunctionDeclaration(node) && !ts.isClassDeclaration(node) &&
!ts.isVariableStatement(node)) {
return null;
}
return null;
// It might be the function expression inside the IIFE. We need to go 5 levels up...
// - IIFE body.
let outerNode = node.parent;
if (!outerNode || !ts.isBlock(outerNode)) return null;
// - IIFE function expression.
outerNode = outerNode.parent;
if (!outerNode || (!ts.isFunctionExpression(outerNode) && !ts.isArrowFunction(outerNode))) {
return null;
}
outerNode = outerNode.parent;
// - Parenthesis inside IIFE.
if (outerNode && ts.isParenthesizedExpression(outerNode)) outerNode = outerNode.parent;
// - IIFE call expression.
if (!outerNode || !ts.isCallExpression(outerNode)) return null;
outerNode = outerNode.parent;
// - Parenthesis around IIFE.
if (outerNode && ts.isParenthesizedExpression(outerNode)) outerNode = outerNode.parent;
// - Skip any aliases between the IIFE and the far left hand side of any assignments.
while (isAssignment(outerNode.parent)) {
outerNode = outerNode.parent;
}
return outerNode;
}

View File

@ -8,10 +8,10 @@
import * as ts from 'typescript';
import {ClassDeclaration, ClassMember, ClassMemberKind, Declaration, Decorator, FunctionDefinition, KnownDeclaration, Parameter, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
import {ClassDeclaration, ClassMember, ClassMemberKind, Declaration, Decorator, FunctionDefinition, isNamedFunctionDeclaration, KnownDeclaration, Parameter, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
import {getTsHelperFnFromDeclaration, getTsHelperFnFromIdentifier, hasNameIdentifier} from '../utils';
import {Esm2015ReflectionHost, getClassDeclarationFromInnerDeclaration, getPropertyValueFromSymbol, isAssignmentStatement, ParamInfo} from './esm2015_host';
import {Esm2015ReflectionHost, getOuterNodeFromInnerDeclaration, getPropertyValueFromSymbol, isAssignmentStatement, ParamInfo} from './esm2015_host';
import {NgccClassSymbol} from './ngcc_host';
@ -186,16 +186,16 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
return classSymbol;
}
if (!ts.isFunctionDeclaration(declaration) || !hasNameIdentifier(declaration)) {
if (!isNamedFunctionDeclaration(declaration)) {
return undefined;
}
const outerDeclaration = getClassDeclarationFromInnerDeclaration(declaration);
if (outerDeclaration === null || !hasNameIdentifier(outerDeclaration)) {
const outerNode = getOuterNodeFromInnerDeclaration(declaration);
if (outerNode === null || !hasNameIdentifier(outerNode)) {
return undefined;
}
return this.createClassSymbol(outerDeclaration, declaration);
return this.createClassSymbol(outerNode.name, declaration);
}
/**

View File

@ -71,9 +71,9 @@ export function findAll<T>(node: ts.Node, test: (node: ts.Node) => node is ts.No
* @param declaration The declaration to test.
* @returns true if the declaration has an identifier for a name.
*/
export function hasNameIdentifier(declaration: ts.Declaration): declaration is ts.Declaration&
export function hasNameIdentifier(declaration: ts.Node): declaration is ts.Declaration&
{name: ts.Identifier} {
const namedDeclaration: ts.Declaration&{name?: ts.Node} = declaration;
const namedDeclaration: ts.Node&{name?: ts.Node} = declaration;
return namedDeclaration.name !== undefined && ts.isIdentifier(namedDeclaration.name);
}