fix(ivy): support late-initialized variables in ngcc/ngtsc (#26236)
In some formats variables are declared as `var` or `let` and only assigned a value later in the code. The ngtsc resolver still needs to be able to resolve this value, so the host now provides a `host.getVariableValue(declaration)` method that can do this resolution based on the format. The hosts make some assumptions about the layout of the code, so they may only work in the constrained scenarios that ngcc expects. PR Close #26236
This commit is contained in:
parent
9320ec0f43
commit
13cdd13511
|
@ -211,6 +211,89 @@ export class Fesm2015ReflectionHost extends TypeScriptReflectionHost implements
|
||||||
[];
|
[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getVariableValue(declaration: ts.VariableDeclaration): ts.Expression|null {
|
||||||
|
const value = super.getVariableValue(declaration);
|
||||||
|
if (value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have a variable declaration that has no initializer. For example:
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// var HttpClientXsrfModule_1;
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// So look for the special scenario where the variable is being assigned in
|
||||||
|
// a nearby statement to the return value of a call to `__decorate`.
|
||||||
|
// Then find the 2nd argument of that call, the "target", which will be the
|
||||||
|
// actual class identifier. For example:
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// HttpClientXsrfModule = HttpClientXsrfModule_1 = tslib_1.__decorate([
|
||||||
|
// NgModule({
|
||||||
|
// providers: [],
|
||||||
|
// })
|
||||||
|
// ], HttpClientXsrfModule);
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// And finally, find the declaration of the identifier in that argument.
|
||||||
|
// Note also that the assignment can occur within another assignment.
|
||||||
|
//
|
||||||
|
const block = declaration.parent.parent.parent;
|
||||||
|
const symbol = this.checker.getSymbolAtLocation(declaration.name);
|
||||||
|
if (symbol && (ts.isBlock(block) || ts.isSourceFile(block))) {
|
||||||
|
const decorateCall = this.findDecoratedVariableValue(block, symbol);
|
||||||
|
const target = decorateCall && decorateCall.arguments[1];
|
||||||
|
if (target && ts.isIdentifier(target)) {
|
||||||
|
const targetSymbol = this.checker.getSymbolAtLocation(target);
|
||||||
|
const targetDeclaration = targetSymbol && targetSymbol.valueDeclaration;
|
||||||
|
if (targetDeclaration) {
|
||||||
|
if (ts.isClassDeclaration(targetDeclaration) ||
|
||||||
|
ts.isFunctionDeclaration(targetDeclaration)) {
|
||||||
|
// The target is just a function or class declaration
|
||||||
|
// so return its identifier as the variable value.
|
||||||
|
return targetDeclaration.name || null;
|
||||||
|
} else if (ts.isVariableDeclaration(targetDeclaration)) {
|
||||||
|
// The target is a variable declaration, so find the far right expression,
|
||||||
|
// in the case of multiple assignments (e.g. `var1 = var2 = value`).
|
||||||
|
let targetValue = targetDeclaration.initializer;
|
||||||
|
while (targetValue && isAssignment(targetValue)) {
|
||||||
|
targetValue = targetValue.right;
|
||||||
|
}
|
||||||
|
if (targetValue) {
|
||||||
|
return targetValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////// Protected Helpers /////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walk the AST looking for an assignment to the specified symbol.
|
||||||
|
* @param node The current node we are searching.
|
||||||
|
* @returns an expression that represents the value of the variable, or undefined if none can be
|
||||||
|
* found.
|
||||||
|
*/
|
||||||
|
protected findDecoratedVariableValue(node: ts.Node|undefined, symbol: ts.Symbol):
|
||||||
|
ts.CallExpression|null {
|
||||||
|
if (!node) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
||||||
|
const left = node.left;
|
||||||
|
const right = node.right;
|
||||||
|
if (ts.isIdentifier(left) && this.checker.getSymbolAtLocation(left) === symbol) {
|
||||||
|
return (ts.isCallExpression(right) && getCalleeName(right) === '__decorate') ? right : null;
|
||||||
|
}
|
||||||
|
return this.findDecoratedVariableValue(right, symbol);
|
||||||
|
}
|
||||||
|
return node.forEachChild(node => this.findDecoratedVariableValue(node, symbol)) || null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Member decorators are declared as static properties of the class in ES2015:
|
* Member decorators are declared as static properties of the class in ES2015:
|
||||||
*
|
*
|
||||||
|
|
|
@ -50,3 +50,15 @@ export function getFakeCore() {
|
||||||
`
|
`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function convertToDirectTsLibImport(filesystem: {name: string, contents: string}[]) {
|
||||||
|
return filesystem.map(file => {
|
||||||
|
const contents =
|
||||||
|
file.contents
|
||||||
|
.replace(
|
||||||
|
`import * as tslib_1 from 'tslib';`,
|
||||||
|
`import { __decorate, __metadata, __read, __values, __param, __extends, __assign } from 'tslib';`)
|
||||||
|
.replace('tslib_1.', '');
|
||||||
|
return {...file, contents};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -419,4 +419,16 @@ export interface ReflectionHost {
|
||||||
* is not a class or has an unknown number of type parameters.
|
* is not a class or has an unknown number of type parameters.
|
||||||
*/
|
*/
|
||||||
getGenericArityOfClass(clazz: ts.Declaration): number|null;
|
getGenericArityOfClass(clazz: ts.Declaration): number|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the assigned value of a variable declaration.
|
||||||
|
*
|
||||||
|
* Normally this will be the initializer of the declaration, but where the variable is
|
||||||
|
* not a `const` we may need to look elsewhere for the variable's value.
|
||||||
|
*
|
||||||
|
* @param declaration a TypeScript variable declaration, whose value we want.
|
||||||
|
* @returns the value of the variable, as a TypeScript expression node, or `undefined`
|
||||||
|
* if the value cannot be computed.
|
||||||
|
*/
|
||||||
|
getVariableValue(declaration: ts.VariableDeclaration): ts.Expression|null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,6 +166,10 @@ export class TypeScriptReflectionHost implements ReflectionHost {
|
||||||
return clazz.typeParameters !== undefined ? clazz.typeParameters.length : 0;
|
return clazz.typeParameters !== undefined ? clazz.typeParameters.length : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getVariableValue(declaration: ts.VariableDeclaration): ts.Expression|null {
|
||||||
|
return declaration.initializer || null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve a `ts.Symbol` to its declaration, keeping track of the `viaModule` along the way.
|
* Resolve a `ts.Symbol` to its declaration, keeping track of the `viaModule` along the way.
|
||||||
*
|
*
|
||||||
|
|
|
@ -454,8 +454,9 @@ class StaticInterpreter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private visitVariableDeclaration(node: ts.VariableDeclaration, context: Context): ResolvedValue {
|
private visitVariableDeclaration(node: ts.VariableDeclaration, context: Context): ResolvedValue {
|
||||||
if (node.initializer !== undefined) {
|
const value = this.host.getVariableValue(node);
|
||||||
return this.visitExpression(node.initializer, context);
|
if (value !== null) {
|
||||||
|
return this.visitExpression(value, context);
|
||||||
} else if (isVariableDeclarationDeclared(node)) {
|
} else if (isVariableDeclarationDeclared(node)) {
|
||||||
return this.getReference(node, context);
|
return this.getReference(node, context);
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue