diff --git a/packages/compiler-cli/ngcc/src/host/esm5_host.ts b/packages/compiler-cli/ngcc/src/host/esm5_host.ts index 67c7abb291..17fd2134c8 100644 --- a/packages/compiler-cli/ngcc/src/host/esm5_host.ts +++ b/packages/compiler-cli/ngcc/src/host/esm5_host.ts @@ -9,7 +9,7 @@ import * as ts from 'typescript'; import {ClassDeclaration, ClassMember, ClassMemberKind, Declaration, Decorator, FunctionDefinition, isNamedVariableDeclaration, Parameter, reflectObjectLiteral} from '../../../src/ngtsc/reflection'; -import {getNameText, getTsHelperFnFromDeclaration, hasNameIdentifier} from '../utils'; +import {getNameText, getTsHelperFnFromDeclaration, getTsHelperFnFromIdentifier, hasNameIdentifier} from '../utils'; import {Esm2015ReflectionHost, getPropertyValueFromSymbol, isAssignment, isAssignmentStatement, ParamInfo} from './esm2015_host'; import {NgccClassSymbol} from './ngcc_host'; @@ -192,6 +192,22 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost { getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null { const superDeclaration = super.getDeclarationOfIdentifier(id); + if (superDeclaration === null) { + const nonEmittedNorImportedTsHelperDeclaration = getTsHelperFnFromIdentifier(id); + if (nonEmittedNorImportedTsHelperDeclaration !== null) { + // No declaration could be found for this identifier and its name matches a known TS helper + // function. This can happen if a package is compiled with `noEmitHelpers: true` and + // `importHelpers: false` (the default). This is, for example, the case with + // `@nativescript/angular@9.0.0-next-2019-11-12-155500-01`. + return { + expression: id, + known: nonEmittedNorImportedTsHelperDeclaration, + node: null, + viaModule: null, + }; + } + } + if (superDeclaration === null || superDeclaration.node === null || superDeclaration.known !== null) { return superDeclaration; diff --git a/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts b/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts index 421bab1b0d..b4a4231371 100644 --- a/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts @@ -2000,6 +2000,74 @@ exports.ExternalModule = ExternalModule; testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib'); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib'); }); + + it('should recognize undeclared, unimported TypeScript helpers (by name)', () => { + const file: TestFile = { + name: _('/test.js'), + contents: ` + var a = __assign({foo: 'bar'}, {baz: 'qux'}); + var b = __spread(['foo', 'bar'], ['baz', 'qux']); + var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); + `, + }; + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = + createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); + + const testForHelper = + (varName: string, helperName: string, knownAs: KnownDeclaration) => { + const node = + getDeclaration(bundle.program, file.name, varName, ts.isVariableDeclaration); + const helperIdentifier = getIdentifierFromCallExpression(node); + const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); + + expect(helperDeclaration).toEqual({ + known: knownAs, + expression: helperIdentifier, + node: null, + viaModule: null, + }); + }; + + testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); + testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); + testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + }); + + it('should recognize suffixed, undeclared, unimported TypeScript helpers (by name)', () => { + const file: TestFile = { + name: _('/test.js'), + contents: ` + var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); + var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); + var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); + `, + }; + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = + createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle)); + + const testForHelper = + (varName: string, helperName: string, knownAs: KnownDeclaration) => { + const node = + getDeclaration(bundle.program, file.name, varName, ts.isVariableDeclaration); + const helperIdentifier = getIdentifierFromCallExpression(node); + const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); + + expect(helperDeclaration).toEqual({ + known: knownAs, + expression: helperIdentifier, + node: null, + viaModule: null, + }); + }; + + testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); + testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); + testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + }); }); describe('getExportsOfModule()', () => { diff --git a/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts b/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts index 8182a8807d..0f5190a3c2 100644 --- a/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts @@ -2124,6 +2124,68 @@ runInEachFileSystem(() => { testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib'); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib'); }); + + it('should recognize undeclared, unimported TypeScript helpers (by name)', () => { + const file: TestFile = { + name: _('/test.js'), + contents: ` + var a = __assign({foo: 'bar'}, {baz: 'qux'}); + var b = __spread(['foo', 'bar'], ['baz', 'qux']); + var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); + `, + }; + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); + + const testForHelper = (varName: string, helperName: string, knownAs: KnownDeclaration) => { + const node = getDeclaration(bundle.program, file.name, varName, ts.isVariableDeclaration); + const helperIdentifier = getIdentifierFromCallExpression(node); + const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); + + expect(helperDeclaration).toEqual({ + known: knownAs, + expression: helperIdentifier, + node: null, + viaModule: null, + }); + }; + + testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); + testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); + testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + }); + + it('should recognize suffixed, undeclared, unimported TypeScript helpers (by name)', () => { + const file: TestFile = { + name: _('/test.js'), + contents: ` + var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); + var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); + var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); + `, + }; + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle)); + + const testForHelper = (varName: string, helperName: string, knownAs: KnownDeclaration) => { + const node = getDeclaration(bundle.program, file.name, varName, ts.isVariableDeclaration); + const helperIdentifier = getIdentifierFromCallExpression(node); + const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); + + expect(helperDeclaration).toEqual({ + known: knownAs, + expression: helperIdentifier, + node: null, + viaModule: null, + }); + }; + + testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); + testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); + testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + }); }); describe('getExportsOfModule()', () => { diff --git a/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts b/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts index 89319ab05f..5aea2a1f13 100644 --- a/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts @@ -2101,6 +2101,84 @@ runInEachFileSystem(() => { testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib'); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib'); }); + + it('should recognize undeclared, unimported TypeScript helpers (by name)', () => { + const file: TestFile = { + name: _('/test.js'), + contents: ` + (function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : + (factory(global.test)); + }(this, (function (exports) { 'use strict'; + var a = __assign({foo: 'bar'}, {baz: 'qux'}); + var b = __spread(['foo', 'bar'], ['baz', 'qux']); + var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); + }))); + `, + }; + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const {factoryFn} = parseStatementForUmdModule( + getSourceFileOrError(bundle.program, file.name).statements[0])!; + + const testForHelper = (varName: string, helperName: string, knownAs: KnownDeclaration) => { + const node = getVariableDeclaration(factoryFn, varName); + const helperIdentifier = getIdentifierFromCallExpression(node); + const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); + + expect(helperDeclaration).toEqual({ + known: knownAs, + expression: helperIdentifier, + node: null, + viaModule: null, + }); + }; + + testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); + testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); + testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + }); + + it('should recognize suffixed, undeclared, unimported TypeScript helpers (by name)', () => { + const file: TestFile = { + name: _('/test.js'), + contents: ` + (function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define('test', ['exports'], factory) : + (factory(global.test)); + }(this, (function (exports) { 'use strict'; + var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); + var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); + var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); + }))); + `, + }; + loadTestFiles([file]); + const bundle = makeTestBundleProgram(file.name); + const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle)); + const {factoryFn} = parseStatementForUmdModule( + getSourceFileOrError(bundle.program, file.name).statements[0])!; + + const testForHelper = (varName: string, helperName: string, knownAs: KnownDeclaration) => { + const node = getVariableDeclaration(factoryFn, varName); + const helperIdentifier = getIdentifierFromCallExpression(node); + const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier); + + expect(helperDeclaration).toEqual({ + known: knownAs, + expression: helperIdentifier, + node: null, + viaModule: null, + }); + }; + + testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); + testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); + testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + }); }); describe('getExportsOfModule()', () => {