fix(ivy): ngcc - teach Esm5ReflectionHost about aliased variables (#29092)
Sometimes, in ESM5 code, aliases to exported variables are used internally to refer to the exported value. This prevented some analysis from being able to match up a reference to an export to the actual export itself. For example in the following code: ``` var HttpClientXsrfModule = /** @class */ (function () { function HttpClientXsrfModule() { } HttpClientXsrfModule_1 = HttpClientXsrfModule; HttpClientXsrfModule.withOptions = function (options) { if (options === void 0) { options = {}; } return { ngModule: HttpClientXsrfModule_1, providers: [], }; }; var HttpClientXsrfModule_1; HttpClientXsrfModule = HttpClientXsrfModule_1 = tslib_1.__decorate([ NgModule({ providers: [], }) ], HttpClientXsrfModule); return HttpClientXsrfModule; }()); ``` We were not able to tell that the `ngModule: HttpClientXsrfModule_1` property assignment was actually meant to refer to the `function HttpClientXrsfModule()` declaration. This caused the `ModuleWithProviders` processing to fail. This commit ensures that we can compile typings files using the ESM5 format, so we can now update the examples boilerplate tool so that it does not need to compile the ESM2015 format at all. PR Close #29092
This commit is contained in:
parent
b48d6e1b13
commit
bdcbd9ed4b
|
@ -68,11 +68,8 @@ class ExampleBoilerPlate {
|
|||
|
||||
if (ivy) {
|
||||
// We only need the "fesm5" bundles as the CLI webpack build does not need
|
||||
// any other formats for building and serving. Ngcc currently only updates
|
||||
// the module typings if we specified an "es2015" format. This means that
|
||||
// we also need to build with "fesm2015" in order to get updated typings
|
||||
// which are needed for compilation.
|
||||
shelljs.exec(`yarn --cwd ${SHARED_PATH} ivy-ngcc --properties module es2015`);
|
||||
// any other formats for building and serving.
|
||||
shelljs.exec(`yarn --cwd ${SHARED_PATH} ivy-ngcc --properties module`);
|
||||
}
|
||||
|
||||
exampleFolders.forEach(exampleFolder => {
|
||||
|
|
|
@ -14,6 +14,7 @@ import {getNameText, hasNameIdentifier} from '../utils';
|
|||
import {Esm2015ReflectionHost, ParamInfo, getPropertyValueFromSymbol, isAssignmentStatement} from './esm2015_host';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* ESM5 packages contain ECMAScript IIFE functions that act like classes. For example:
|
||||
*
|
||||
|
@ -117,8 +118,30 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
|||
id = outerClassNode.name;
|
||||
}
|
||||
|
||||
// Resolve the identifier to a Symbol, and return the declaration of that.
|
||||
return super.getDeclarationOfIdentifier(id);
|
||||
const declaration = super.getDeclarationOfIdentifier(id);
|
||||
|
||||
if (!declaration || !ts.isVariableDeclaration(declaration.node) ||
|
||||
declaration.node.initializer !== undefined ||
|
||||
// VariableDeclaration => VariableDeclarationList => VariableStatement => IIFE Block
|
||||
!ts.isBlock(declaration.node.parent.parent.parent)) {
|
||||
return declaration;
|
||||
}
|
||||
|
||||
// We might have an alias to another variable declaration.
|
||||
// Search the containing iife body for it.
|
||||
const block = declaration.node.parent.parent.parent;
|
||||
const aliasSymbol = this.checker.getSymbolAtLocation(declaration.node.name);
|
||||
for (let i = 0; i < block.statements.length; i++) {
|
||||
const statement = block.statements[i];
|
||||
// Looking for statement that looks like: `AliasedVariable = OriginalVariable;`
|
||||
if (isAssignmentStatement(statement) && ts.isIdentifier(statement.expression.left) &&
|
||||
ts.isIdentifier(statement.expression.right) &&
|
||||
this.checker.getSymbolAtLocation(statement.expression.left) === aliasSymbol) {
|
||||
return this.getDeclarationOfIdentifier(statement.expression.right);
|
||||
}
|
||||
}
|
||||
|
||||
return declaration;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -371,6 +371,18 @@ describe('Esm5ReflectionHost [import helper style]', () => {
|
|||
expect(actualDeclaration !.node).toBe(expectedDeclarationNode);
|
||||
expect(actualDeclaration !.viaModule).toBe('@angular/core');
|
||||
});
|
||||
|
||||
it('should find the "actual" declaration of an aliased variable identifier', () => {
|
||||
const program = makeTestProgram(fileSystem.files[2]);
|
||||
const host = new Esm5ReflectionHost(false, program.getTypeChecker());
|
||||
const ngModuleRef = findIdentifier(
|
||||
program.getSourceFile(fileSystem.files[2].name) !, 'HttpClientXsrfModule_1',
|
||||
isNgModulePropertyAssignment);
|
||||
|
||||
const declaration = host.getDeclarationOfIdentifier(ngModuleRef !);
|
||||
expect(declaration).not.toBe(null);
|
||||
expect(declaration !.node.getText()).toContain('function HttpClientXsrfModule()');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -422,3 +434,20 @@ describe('Esm5ReflectionHost [import helper style]', () => {
|
|||
return node.forEachChild(node => findVariableDeclaration(node, variableName));
|
||||
}
|
||||
});
|
||||
|
||||
function findIdentifier(
|
||||
node: ts.Node | undefined, identifierName: string,
|
||||
requireFn: (node: ts.Identifier) => boolean): ts.Identifier|undefined {
|
||||
if (!node) {
|
||||
return undefined;
|
||||
}
|
||||
if (ts.isIdentifier(node) && node.text === identifierName && requireFn(node)) {
|
||||
return node;
|
||||
}
|
||||
return node.forEachChild(node => findIdentifier(node, identifierName, requireFn));
|
||||
}
|
||||
|
||||
function isNgModulePropertyAssignment(identifier: ts.Identifier): boolean {
|
||||
return ts.isPropertyAssignment(identifier.parent) &&
|
||||
identifier.parent.name.getText() === 'ngModule';
|
||||
}
|
Loading…
Reference in New Issue