refactor(compiler-cli): allow visiting call expressions without an active linker (#39707)

The compilation result of components may have inserted template
functions into the constant pool, which would be inserted into the Babel
AST upon program exit. Babel will then proceed with visiting this newly
inserted subtree, but we have already cleaned up the linker instance
when exiting the program. Any call expressions within the template
functions would then fail to be processed, as a file linker would no
longer be available.

Since the inserted AST subtree is known not to contain yet more partial
declarations, it is safe to skip visiting call expressions when no
file linker is available.

PR Close #39707
This commit is contained in:
JoostK 2020-11-16 17:56:36 +01:00 committed by Andrew Kushnir
parent 8348556b77
commit e79ce386fc
2 changed files with 42 additions and 2 deletions

View File

@ -58,9 +58,14 @@ export function createEs2015LinkerPlugin(options: Partial<LinkerOptions> = {}):
* with the results of linking the declaration.
*/
CallExpression(call: NodePath<t.CallExpression>): void {
try {
assertNotNull(fileLinker);
if (fileLinker === null) {
// Any statements that are inserted upon program exit will be visited outside of an active
// linker context. These call expressions are known not to contain partial declarations,
// so it's safe to skip visiting those call expressions.
return;
}
try {
const callee = call.node.callee;
if (!t.isExpression(callee)) {
return;

View File

@ -213,6 +213,41 @@ describe('createEs2015LinkerPlugin()', () => {
`(function(){const x1=[1];return function BAR(){};})();BAR;`,
].join(''));
});
it('should not process call expressions within inserted functions', () => {
spyOn(PartialDirectiveLinkerVersion1.prototype, 'linkPartialDeclaration')
.and.callFake(((sourceUrl, code, constantPool) => {
// Insert a call expression into the constant pool. This is inserted into
// Babel's AST upon program exit, and will therefore be visited by Babel
// outside of an active linker context.
constantPool.statements.push(
o.fn(/* params */[], /* body */[], /* type */ undefined,
/* sourceSpan */ undefined, /* name */ 'inserted')
.callFn([])
.toStmt());
return o.literal('REPLACEMENT');
}) as typeof PartialDirectiveLinkerVersion1.prototype.linkPartialDeclaration);
const isPartialDeclarationSpy =
spyOn(FileLinker.prototype, 'isPartialDeclaration').and.callThrough();
const result = transformSync(
[
'import * as core from \'some-module\';',
`ɵɵngDeclareDirective({version: 1, ngImport: core})`,
].join('\n'),
{
plugins: [createEs2015LinkerPlugin()],
filename: '/test.js',
parserOpts: {sourceType: 'unambiguous'},
generatorOpts: {compact: true},
});
expect(result!.code)
.toEqual('import*as core from\'some-module\';(function inserted(){})();"REPLACEMENT";');
expect(isPartialDeclarationSpy.calls.allArgs()).toEqual([['ɵɵngDeclareDirective']]);
});
});
/**