fix(ngcc): map exports to the current module in UMD files (#38959)

UMD files export values by assigning them to an `exports` variable.
When evaluating expressions ngcc was failing to cope with expressions
like `exports.MyComponent`.

This commit fixes the `UmdReflectionHost.getDeclarationOfIdentifier()`
method to map the `exports` variable to the current source file.

PR Close #38959
This commit is contained in:
Pete Bacon Darwin
2020-09-23 19:39:21 +01:00
committed by atscott
parent acfce0ba1b
commit 11485d96fb
2 changed files with 171 additions and 2 deletions

View File

@ -14,6 +14,7 @@ import {MockLogger} from '../../../src/ngtsc/logging/testing';
import {ClassMemberKind, ConcreteDeclaration, CtorParameter, DownleveledEnum, Import, InlineDeclaration, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, TypeScriptReflectionHost, TypeValueReferenceKind} from '../../../src/ngtsc/reflection';
import {getDeclaration} from '../../../src/ngtsc/testing';
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
import {isExportsStatement} from '../../src/host/commonjs_umd_utils';
import {DelegatingReflectionHost} from '../../src/host/delegating_host';
import {getIifeBody} from '../../src/host/esm2015_host';
import {NgccReflectionHost} from '../../src/host/ngcc_host';
@ -34,6 +35,7 @@ runInEachFileSystem(() => {
let SIMPLE_CLASS_FILE: TestFile;
let FOO_FUNCTION_FILE: TestFile;
let INLINE_EXPORT_FILE: TestFile;
let EXPORTS_IDENTIFIERS_FILE: TestFile;
let INVALID_DECORATORS_FILE: TestFile;
let INVALID_DECORATOR_ARGS_FILE: TestFile;
let INVALID_PROP_DECORATORS_FILE: TestFile;
@ -238,6 +240,33 @@ runInEachFileSystem(() => {
`,
};
EXPORTS_IDENTIFIERS_FILE = {
name: _('/exports_identifiers.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core')) :
typeof define === 'function' && define.amd ? define('foo_function', ['exports', '@angular/core'], factory) :
(factory(global.inline_export,global.ng.core));
}(this, (function (exports,core) { 'use strict';
var x = exports;
exports.foo = 42;
function simpleFn() {
exports.foo = 42;
}
function localVar() {
var exports = {};
exports.foo = 42;
}
function exportsArg(exports) {
exports.foo = 42;
}
})));
`,
};
INVALID_DECORATORS_FILE = {
name: _('/invalid_decorators.js'),
contents: `
@ -2078,6 +2107,111 @@ runInEachFileSystem(() => {
expect(actualDeclaration!.viaModule).toBe('@angular/core');
});
it('should return the source file as the declaration of a standalone "exports" identifier',
() => {
loadTestFiles([EXPORTS_IDENTIFIERS_FILE]);
const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name);
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
const xVar = getDeclaration(
bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'x', ts.isVariableDeclaration);
const exportsIdentifier = xVar.initializer;
if (exportsIdentifier === undefined || !ts.isIdentifier(exportsIdentifier)) {
throw new Error('Bad test - unable to find `var x = exports;` statement');
}
const exportsDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier);
expect(exportsDeclaration).not.toBeNull();
expect(exportsDeclaration!.node).toBe(bundle.file);
});
it('should return the source file as the declaration of "exports" in the LHS of a property access',
() => {
loadTestFiles([EXPORTS_IDENTIFIERS_FILE]);
const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name);
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
const exportsStatement = walkForNode(bundle.file, isExportsStatement);
if (exportsStatement === undefined) {
throw new Error('Bad test - unable to find `exports.foo = 42;` statement');
}
const exportsIdentifier = exportsStatement.expression.left.expression;
const exportDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier);
expect(exportDeclaration).not.toBeNull();
expect(exportDeclaration!.node).toBe(bundle.file);
});
it('should return the source file as the declaration of "exports" in the LHS of a property access inside a local function',
() => {
loadTestFiles([EXPORTS_IDENTIFIERS_FILE]);
const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name);
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
const simpleFn = getDeclaration(
bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'simpleFn', ts.isFunctionDeclaration);
const exportsStatement = walkForNode(simpleFn, isExportsStatement);
if (exportsStatement === undefined) {
throw new Error('Bad test - unable to find `exports.foo = 42;` statement');
}
const exportsIdentifier = exportsStatement.expression.left.expression;
const exportDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier);
expect(exportDeclaration).not.toBeNull();
expect(exportDeclaration!.node).toBe(bundle.file);
});
it('should return the variable declaration if a standalone "exports" is declared locally',
() => {
loadTestFiles([EXPORTS_IDENTIFIERS_FILE]);
const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name);
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
const localVarFn = getDeclaration(
bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'localVar', ts.isFunctionDeclaration);
const exportsVar = walkForNode(localVarFn, ts.isVariableDeclaration);
if (exportsVar === undefined) {
throw new Error('Bad test - unable to find `var exports = {}` statement');
}
const exportsIdentifier = exportsVar.name;
if (exportsIdentifier === undefined || !ts.isIdentifier(exportsIdentifier)) {
throw new Error('Bad test - unable to find `var exports = {};` statement');
}
const exportsDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier);
expect(exportsDeclaration).not.toBeNull();
expect(exportsDeclaration!.node).toBe(exportsVar);
});
it('should return the variable declaration of "exports" in the LHS of a property access, if it is declared locally',
() => {
loadTestFiles([EXPORTS_IDENTIFIERS_FILE]);
const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name);
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
const localVarFn = getDeclaration(
bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'localVar', ts.isFunctionDeclaration);
const exportsVar = walkForNode(localVarFn, ts.isVariableDeclaration);
const exportsStatement = walkForNode(localVarFn, isExportsStatement);
if (exportsStatement === undefined) {
throw new Error('Bad test - unable to find `exports.foo = 42;` statement');
}
const exportsIdentifier = exportsStatement.expression.left.expression;
const exportDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier);
expect(exportDeclaration).not.toBeNull();
expect(exportDeclaration?.node).toBe(exportsVar);
});
it('should return the variable declaration of "exports" in the LHS of a property access, if it is a local function parameter',
() => {
loadTestFiles([EXPORTS_IDENTIFIERS_FILE]);
const bundle = makeTestBundleProgram(EXPORTS_IDENTIFIERS_FILE.name);
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
const exportsArgFn = getDeclaration(
bundle.program, EXPORTS_IDENTIFIERS_FILE.name, 'exportsArg',
ts.isFunctionDeclaration);
const exportsVar = exportsArgFn.parameters[0];
const exportsStatement = walkForNode(exportsArgFn, isExportsStatement);
if (exportsStatement === undefined) {
throw new Error('Bad test - unable to find `exports.foo = 42;` statement');
}
const exportsIdentifier = exportsStatement.expression.left.expression;
const exportDeclaration = host.getDeclarationOfIdentifier(exportsIdentifier);
expect(exportDeclaration).not.toBeNull();
expect(exportDeclaration?.node).toBe(exportsVar);
});
it('should recognize TypeScript helpers (as function declarations)', () => {
const file: TestFile = {
name: _('/test.js'),
@ -3232,3 +3366,8 @@ runInEachFileSystem(() => {
});
});
});
type WalkerPredicate<T extends ts.Node> = (node: ts.Node) => node is T;
function walkForNode<T extends ts.Node>(node: ts.Node, predicate: WalkerPredicate<T>): T|undefined {
return node.forEachChild(child => predicate(child) ? child : walkForNode(child, predicate));
}