From 7b1214eca2dd2f09e723a46bed857fcb7d40bc0b Mon Sep 17 00:00:00 2001 From: JoostK Date: Sun, 14 Mar 2021 00:36:34 +0100 Subject: [PATCH] feat(ngcc): support `__spreadArray` helper as used by TypeScript 4.2 (#41201) In TypeScript 4.2 the `__spread` and `__spreadArrays` helpers were both replaced by the new helper function `__spreadArray` in microsoft/TypeScript#41523. These helpers may be used in downleveled JavaScript bundles that ngcc has to process, so ngcc has the ability to statically detect these helpers and provide evaluation logic for them. Because Angular is adopting support for TypeScript 4.2 it becomes possible for libraries to be compiled by TypeScript 4.2 and thus ngcc has to add support for the `__spreadArray` helper. The deprecated `__spread` and `__spreadArrays` helpers are not affected by this change. Closes #40394 PR Close #41201 --- packages/compiler-cli/ngcc/src/utils.ts | 2 + .../ngcc/test/host/commonjs_host_spec.ts | 21 ++++++++ .../ngcc/test/host/esm5_host_spec.ts | 28 +++++++++- .../ngcc/test/host/umd_host_spec.ts | 21 ++++++++ packages/compiler-cli/ngcc/test/utils_spec.ts | 24 +++++++++ .../src/known_declaration.ts | 7 ++- .../ngtsc/partial_evaluator/src/ts_helpers.ts | 26 +++++++++- .../partial_evaluator/test/evaluator_spec.ts | 51 +++++++++++++++++++ .../src/ngtsc/reflection/src/host.ts | 5 ++ 9 files changed, 181 insertions(+), 4 deletions(-) diff --git a/packages/compiler-cli/ngcc/src/utils.ts b/packages/compiler-cli/ngcc/src/utils.ts index 91cc0f9538..d36ce1d9d2 100644 --- a/packages/compiler-cli/ngcc/src/utils.ts +++ b/packages/compiler-cli/ngcc/src/utils.ts @@ -161,6 +161,8 @@ export function getTsHelperFnFromIdentifier(id: ts.Identifier): KnownDeclaration return KnownDeclaration.TsHelperSpread; case '__spreadArrays': return KnownDeclaration.TsHelperSpreadArrays; + case '__spreadArray': + return KnownDeclaration.TsHelperSpreadArray; default: return null; } 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 fb8315506f..883d69f894 100644 --- a/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/commonjs_host_spec.ts @@ -1999,10 +1999,12 @@ exports.MissingClass2 = MissingClass2; function __assign(t, ...sources) { /* ... */ } function __spread(...args) { /* ... */ } function __spreadArrays(...args) { /* ... */ } + function __spreadArray(to, from) { /* ... */ } var a = __assign({foo: 'bar'}, {baz: 'qux'}); var b = __spread(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); `, }; loadTestFiles([file]); @@ -2018,6 +2020,7 @@ exports.MissingClass2 = MissingClass2; testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize suffixed TypeScript helpers (as function declarations)', () => { @@ -2027,10 +2030,12 @@ exports.MissingClass2 = MissingClass2; function __assign$1(t, ...sources) { /* ... */ } function __spread$2(...args) { /* ... */ } function __spreadArrays$3(...args) { /* ... */ } + function __spreadArray$3(to, from) { /* ... */ } var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); `, }; loadTestFiles([file]); @@ -2046,6 +2051,7 @@ exports.MissingClass2 = MissingClass2; testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize TypeScript helpers (as variable declarations)', () => { @@ -2055,10 +2061,12 @@ exports.MissingClass2 = MissingClass2; var __assign = (this && this.__assign) || function (t, ...sources) { /* ... */ } var __spread = (this && this.__spread) || function (...args) { /* ... */ } var __spreadArrays = (this && this.__spreadArrays) || function (...args) { /* ... */ } + var __spreadArray = (this && this.__spreadArray) || function (to, from) { /* ... */ } var a = __assign({foo: 'bar'}, {baz: 'qux'}); var b = __spread(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); `, }; loadTestFiles([file]); @@ -2074,6 +2082,7 @@ exports.MissingClass2 = MissingClass2; testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize suffixed TypeScript helpers (as variable declarations)', () => { @@ -2083,10 +2092,12 @@ exports.MissingClass2 = MissingClass2; var __assign$1 = (this && this.__assign$1) || function (t, ...sources) { /* ... */ } var __spread$2 = (this && this.__spread$2) || function (...args) { /* ... */ } var __spreadArrays$3 = (this && this.__spreadArrays$3) || function (...args) { /* ... */ } + var __spreadArray$3 = (this && this.__spreadArray$3) || function (to, from) { /* ... */ } var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); `, }; loadTestFiles([file]); @@ -2102,6 +2113,7 @@ exports.MissingClass2 = MissingClass2; testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize imported TypeScript helpers', () => { @@ -2114,6 +2126,7 @@ exports.MissingClass2 = MissingClass2; var a = tslib_1.__assign({foo: 'bar'}, {baz: 'qux'}); var b = tslib_1.__spread(['foo', 'bar'], ['baz', 'qux']); var c = tslib_1.__spreadArrays(['foo', 'bar'], ['baz', 'qux']); + var d = tslib_1.__spreadArray(['foo', 'bar'], ['baz', 'qux']); `, }, { @@ -2122,6 +2135,7 @@ exports.MissingClass2 = MissingClass2; export declare function __assign(t: any, ...sources: any[]): any; export declare function __spread(...args: any[][]): any[]; export declare function __spreadArrays(...args: any[][]): any[]; + export declare function __spreadArray(to: any[], from: any[]): any[]; `, }, ]; @@ -2140,6 +2154,7 @@ exports.MissingClass2 = MissingClass2; testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib'); testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib'); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib'); + testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray, 'tslib'); }); it('should recognize undeclared, unimported TypeScript helpers (by name)', () => { @@ -2149,6 +2164,7 @@ exports.MissingClass2 = MissingClass2; var a = __assign({foo: 'bar'}, {baz: 'qux'}); var b = __spread(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); `, }; loadTestFiles([file]); @@ -2174,6 +2190,7 @@ exports.MissingClass2 = MissingClass2; testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize suffixed, undeclared, unimported TypeScript helpers (by name)', () => { @@ -2183,6 +2200,7 @@ exports.MissingClass2 = MissingClass2; var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); `, }; loadTestFiles([file]); @@ -2208,6 +2226,7 @@ exports.MissingClass2 = MissingClass2; testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize enum declarations with string values', () => { @@ -2441,6 +2460,7 @@ exports.MissingClass2 = MissingClass2; export declare function __assign(t: any, ...sources: any[]): any; export declare function __spread(...args: any[][]): any[]; export declare function __spreadArrays(...args: any[][]): any[]; + export declare function __spreadArray(to: any[], from: any[]): any[]; export declare function __unknownHelper(...args: any[]): any; `, }; @@ -2456,6 +2476,7 @@ exports.MissingClass2 = MissingClass2; ['__assign', KnownDeclaration.TsHelperAssign], ['__spread', KnownDeclaration.TsHelperSpread], ['__spreadArrays', KnownDeclaration.TsHelperSpreadArrays], + ['__spreadArray', KnownDeclaration.TsHelperSpreadArray], ['__unknownHelper', null], ]); }); 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 d258f63c4f..4e112a86f8 100644 --- a/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts @@ -2059,10 +2059,12 @@ runInEachFileSystem(() => { function __assign(t, ...sources) { /* ... */ } function __spread(...args) { /* ... */ } function __spreadArrays(...args) { /* ... */ } + function __spreadArray(to, from) { /* ... */ } var a = __assign({foo: 'bar'}, {baz: 'qux'}); var b = __spread(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); `, }; loadTestFiles([file]); @@ -2077,6 +2079,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize suffixed TypeScript helpers (as function declarations)', () => { @@ -2086,10 +2089,12 @@ runInEachFileSystem(() => { function __assign$1(t, ...sources) { /* ... */ } function __spread$2(...args) { /* ... */ } function __spreadArrays$3(...args) { /* ... */ } + function __spreadArray$3(to, from) { /* ... */ } var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); `, }; loadTestFiles([file]); @@ -2104,6 +2109,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize TypeScript helpers (as variable declarations)', () => { @@ -2113,11 +2119,13 @@ runInEachFileSystem(() => { var __assign = (this && this.__assign) || function (t, ...sources) { /* ... */ } var __spread = (this && this.__spread) || function (...args) { /* ... */ } var __spreadArrays = (this && this.__spreadArrays) || function (...args) { /* ... */ } + var __spreadArray = (this && this.__spreadArray) || function (to, from) { /* ... */ } var a = __assign({foo: 'bar'}, {baz: 'qux'}); var b = __spread(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); - `, + var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); + `, }; loadTestFiles([file]); const bundle = makeTestBundleProgram(file.name); @@ -2131,6 +2139,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize suffixed TypeScript helpers (as variable declarations)', () => { @@ -2140,10 +2149,12 @@ runInEachFileSystem(() => { var __assign$1 = (this && this.__assign$1) || function (t, ...sources) { /* ... */ } var __spread$2 = (this && this.__spread$2) || function (...args) { /* ... */ } var __spreadArrays$3 = (this && this.__spreadArrays$3) || function (...args) { /* ... */ } + var __spreadArray$3 = (this && this.__spreadArray$3) || function (to, from) { /* ... */ } var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); `, }; loadTestFiles([file]); @@ -2158,6 +2169,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize imported TypeScript helpers (named imports)', () => { @@ -2165,11 +2177,12 @@ runInEachFileSystem(() => { { name: _('/test.js'), contents: ` - import {__assign, __spread, __spreadArrays} from 'tslib'; + import {__assign, __spread, __spreadArrays, __spreadArray} from 'tslib'; var a = __assign({foo: 'bar'}, {baz: 'qux'}); var b = __spread(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); `, }, { @@ -2178,6 +2191,7 @@ runInEachFileSystem(() => { export declare function __assign(t: any, ...sources: any[]): any; export declare function __spread(...args: any[][]): any[]; export declare function __spreadArrays(...args: any[][]): any[]; + export declare function __spreadArray(to: any[], from: any[]): any[]; `, }, ]; @@ -2195,6 +2209,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib'); testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib'); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib'); + testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray, 'tslib'); }); it('should recognize imported TypeScript helpers (star import)', () => { @@ -2207,6 +2222,7 @@ runInEachFileSystem(() => { var a = tslib_1.__assign({foo: 'bar'}, {baz: 'qux'}); var b = tslib_1.__spread(['foo', 'bar'], ['baz', 'qux']); var c = tslib_1.__spreadArrays(['foo', 'bar'], ['baz', 'qux']); + var d = tslib_1.__spreadArray(['foo', 'bar'], ['baz', 'qux']); `, }, { @@ -2215,6 +2231,7 @@ runInEachFileSystem(() => { export declare function __assign(t: any, ...sources: any[]): any; export declare function __spread(...args: any[][]): any[]; export declare function __spreadArrays(...args: any[][]): any[]; + export declare function __spreadArray(to: any[], from: any[]): any[]; `, }, ]; @@ -2232,6 +2249,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib'); testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib'); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib'); + testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray, 'tslib'); }); it('should recognize undeclared, unimported TypeScript helpers (by name)', () => { @@ -2241,6 +2259,7 @@ runInEachFileSystem(() => { var a = __assign({foo: 'bar'}, {baz: 'qux'}); var b = __spread(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); `, }; loadTestFiles([file]); @@ -2263,6 +2282,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize suffixed, undeclared, unimported TypeScript helpers (by name)', () => { @@ -2272,6 +2292,7 @@ runInEachFileSystem(() => { var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); `, }; loadTestFiles([file]); @@ -2294,6 +2315,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize enum declarations with string values', () => { @@ -2456,6 +2478,7 @@ runInEachFileSystem(() => { export declare function __assign(t: any, ...sources: any[]): any; export declare function __spread(...args: any[][]): any[]; export declare function __spreadArrays(...args: any[][]): any[]; + export declare function __spreadArray(to: any[], from: any[]): any[]; export declare function __unknownHelper(...args: any[]): any; `, }; @@ -2470,6 +2493,7 @@ runInEachFileSystem(() => { ['__assign', KnownDeclaration.TsHelperAssign], ['__spread', KnownDeclaration.TsHelperSpread], ['__spreadArrays', KnownDeclaration.TsHelperSpreadArrays], + ['__spreadArray', KnownDeclaration.TsHelperSpreadArray], ['__unknownHelper', null], ]); }); 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 870d49edae..5f42d2a7bc 100644 --- a/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts @@ -2331,10 +2331,12 @@ runInEachFileSystem(() => { function __assign(t, ...sources) { /* ... */ } function __spread(...args) { /* ... */ } function __spreadArrays(...args) { /* ... */ } + function __spreadArray(to, from) { /* ... */ } var a = __assign({foo: 'bar'}, {baz: 'qux'}); var b = __spread(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); }))); `, }; @@ -2349,6 +2351,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize suffixed TypeScript helpers (as function declarations)', () => { @@ -2363,10 +2366,12 @@ runInEachFileSystem(() => { function __assign$1(t, ...sources) { /* ... */ } function __spread$2(...args) { /* ... */ } function __spreadArrays$3(...args) { /* ... */ } + function __spreadArray$3(to, from) { /* ... */ } var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); }))); `, }; @@ -2381,6 +2386,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize TypeScript helpers (as variable declarations)', () => { @@ -2395,10 +2401,12 @@ runInEachFileSystem(() => { var __assign = (this && this.__assign) || function (t, ...sources) { /* ... */ } var __spread = (this && this.__spread) || function (...args) { /* ... */ } var __spreadArrays = (this && this.__spreadArrays) || function (...args) { /* ... */ } + var __spreadArray = (this && this.__spreadArray) || function (to, from) { /* ... */ } var a = __assign({foo: 'bar'}, {baz: 'qux'}); var b = __spread(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); }))); `, }; @@ -2413,6 +2421,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize suffixed TypeScript helpers (as variable declarations)', () => { @@ -2427,10 +2436,12 @@ runInEachFileSystem(() => { var __assign$1 = (this && this.__assign$1) || function (t, ...sources) { /* ... */ } var __spread$2 = (this && this.__spread$2) || function (...args) { /* ... */ } var __spreadArrays$3 = (this && this.__spreadArrays$3) || function (...args) { /* ... */ } + var __spreadArray$3 = (this && this.__spreadArray$3) || function (to, from) { /* ... */ } var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); }))); `, }; @@ -2445,6 +2456,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize imported TypeScript helpers', () => { @@ -2460,6 +2472,7 @@ runInEachFileSystem(() => { var a = tslib_1.__assign({foo: 'bar'}, {baz: 'qux'}); var b = tslib_1.__spread(['foo', 'bar'], ['baz', 'qux']); var c = tslib_1.__spreadArrays(['foo', 'bar'], ['baz', 'qux']); + var d = tslib_1.__spreadArray(['foo', 'bar'], ['baz', 'qux']); }))); `, }, @@ -2469,6 +2482,7 @@ runInEachFileSystem(() => { export declare function __assign(t: any, ...sources: any[]): any; export declare function __spread(...args: any[][]): any[]; export declare function __spreadArrays(...args: any[][]): any[]; + export declare function __spreadArray(to: any[], from: any[]): any[]; `, }, ]; @@ -2487,6 +2501,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib'); testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib'); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib'); + testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray, 'tslib'); }); it('should recognize undeclared, unimported TypeScript helpers (by name)', () => { @@ -2501,6 +2516,7 @@ runInEachFileSystem(() => { var a = __assign({foo: 'bar'}, {baz: 'qux'}); var b = __spread(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray(['foo', 'bar'], ['baz', 'qux']); }))); `, }; @@ -2527,6 +2543,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize suffixed, undeclared, unimported TypeScript helpers (by name)', () => { @@ -2541,6 +2558,7 @@ runInEachFileSystem(() => { var a = __assign$1({foo: 'bar'}, {baz: 'qux'}); var b = __spread$2(['foo', 'bar'], ['baz', 'qux']); var c = __spreadArrays$3(['foo', 'bar'], ['baz', 'qux']); + var d = __spreadArray$3(['foo', 'bar'], ['baz', 'qux']); }))); `, }; @@ -2567,6 +2585,7 @@ runInEachFileSystem(() => { testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign); testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread); testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays); + testForHelper('d', '__spreadArray$3', KnownDeclaration.TsHelperSpreadArray); }); it('should recognize enum declarations with string values', () => { @@ -2841,6 +2860,7 @@ runInEachFileSystem(() => { export declare function __assign(t: any, ...sources: any[]): any; export declare function __spread(...args: any[][]): any[]; export declare function __spreadArrays(...args: any[][]): any[]; + export declare function __spreadArray(to: any[], from: any[]): any[]; export declare function __unknownHelper(...args: any[]): any; `, }; @@ -2855,6 +2875,7 @@ runInEachFileSystem(() => { ['__assign', KnownDeclaration.TsHelperAssign], ['__spread', KnownDeclaration.TsHelperSpread], ['__spreadArrays', KnownDeclaration.TsHelperSpreadArrays], + ['__spreadArray', KnownDeclaration.TsHelperSpreadArray], ['__unknownHelper', null], ]); }); diff --git a/packages/compiler-cli/ngcc/test/utils_spec.ts b/packages/compiler-cli/ngcc/test/utils_spec.ts index 14aa5fbabe..6d288c7497 100644 --- a/packages/compiler-cli/ngcc/test/utils_spec.ts +++ b/packages/compiler-cli/ngcc/test/utils_spec.ts @@ -108,6 +108,22 @@ describe('getTsHelperFnFromDeclaration()', () => { expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperSpreadArrays); }); + it('should recognize the `__spreadArray` helper as function declaration', () => { + const decl1 = createFunctionDeclaration('__spreadArray'); + const decl2 = createFunctionDeclaration('__spreadArray$42'); + + expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperSpreadArray); + expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperSpreadArray); + }); + + it('should recognize the `__spreadArray` helper as variable declaration', () => { + const decl1 = createVariableDeclaration('__spreadArray'); + const decl2 = createVariableDeclaration('__spreadArray$42'); + + expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperSpreadArray); + expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperSpreadArray); + }); + it('should return null for unrecognized helpers', () => { const decl1 = createFunctionDeclaration('__foo'); const decl2 = createVariableDeclaration('spread'); @@ -158,6 +174,14 @@ describe('getTsHelperFnFromIdentifier()', () => { expect(getTsHelperFnFromIdentifier(id2)).toBe(KnownDeclaration.TsHelperSpreadArrays); }); + it('should recognize the `__spreadArray` helper', () => { + const id1 = ts.createIdentifier('__spreadArray'); + const id2 = ts.createIdentifier('__spreadArray$42'); + + expect(getTsHelperFnFromIdentifier(id1)).toBe(KnownDeclaration.TsHelperSpreadArray); + expect(getTsHelperFnFromIdentifier(id2)).toBe(KnownDeclaration.TsHelperSpreadArray); + }); + it('should return null for unrecognized helpers', () => { const id1 = ts.createIdentifier('__foo'); const id2 = ts.createIdentifier('spread'); diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/known_declaration.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/known_declaration.ts index afc2e4d62b..3712eae54b 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/known_declaration.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/known_declaration.ts @@ -10,7 +10,7 @@ import {KnownDeclaration} from '../../reflection/src/host'; import {ObjectAssignBuiltinFn} from './builtin'; import {ResolvedValue} from './result'; -import {AssignHelperFn, SpreadHelperFn} from './ts_helpers'; +import {AssignHelperFn, SpreadArrayHelperFn, SpreadHelperFn} from './ts_helpers'; /** Resolved value for the JavaScript global `Object` declaration. */ export const jsGlobalObjectValue = new Map([['assign', new ObjectAssignBuiltinFn()]]); @@ -21,6 +21,9 @@ const assignTsHelperFn = new AssignHelperFn(); /** Resolved value for the `__spread()` and `__spreadArrays()` TypeScript helper declarations. */ const spreadTsHelperFn = new SpreadHelperFn(); +/** Resolved value for the `__spreadArray()` TypeScript helper declarations. */ +const spreadArrayTsHelperFn = new SpreadArrayHelperFn(); + /** * Resolves the specified known declaration to a resolved value. For example, * the known JavaScript global `Object` will resolve to a `Map` that provides the @@ -35,6 +38,8 @@ export function resolveKnownDeclaration(decl: KnownDeclaration): ResolvedValue { case KnownDeclaration.TsHelperSpread: case KnownDeclaration.TsHelperSpreadArrays: return spreadTsHelperFn; + case KnownDeclaration.TsHelperSpreadArray: + return spreadArrayTsHelperFn; default: throw new Error(`Cannot resolve known declaration. Received: ${KnownDeclaration[decl]}.`); } diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/ts_helpers.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/ts_helpers.ts index 2466eee6d2..27080f0915 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/ts_helpers.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/ts_helpers.ts @@ -10,7 +10,7 @@ import * as ts from 'typescript'; import {ObjectAssignBuiltinFn} from './builtin'; import {DynamicValue} from './dynamic'; -import {KnownFn, ResolvedValueArray} from './result'; +import {KnownFn, ResolvedValue, ResolvedValueArray} from './result'; // Use the same implementation we use for `Object.assign()`. Semantically these functions are the @@ -35,3 +35,27 @@ export class SpreadHelperFn extends KnownFn { return result; } } + +// Used for `__spreadArray` TypeScript helper function. +export class SpreadArrayHelperFn extends KnownFn { + evaluate(node: ts.Node, args: ResolvedValueArray): ResolvedValue { + if (args.length !== 2) { + return DynamicValue.fromUnknown(node); + } + + const [to, from] = args; + if (to instanceof DynamicValue) { + return DynamicValue.fromDynamicInput(node, to); + } else if (from instanceof DynamicValue) { + return DynamicValue.fromDynamicInput(node, from); + } + + if (!Array.isArray(to)) { + return DynamicValue.fromInvalidExpressionType(node, to); + } else if (!Array.isArray(from)) { + return DynamicValue.fromInvalidExpressionType(node, from); + } + + return to.concat(from); + } +} diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts index 37d1c56d00..e4ead4720e 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts @@ -646,6 +646,7 @@ runInEachFileSystem(() => { export declare function __assign(t: any, ...sources: any[]): any; export declare function __spread(...args: any[][]): any[]; export declare function __spreadArrays(...args: any[][]): any[]; + export declare function __spreadArray(to: any[], from: any[]): any[]; `, }, ]); @@ -733,6 +734,30 @@ runInEachFileSystem(() => { expect(arr).toEqual([4, 5, 6]); }); + + it('should evaluate `__spreadArray()` (named import)', () => { + const arr: number[] = evaluateExpression( + ` + import {__spreadArray} from 'tslib'; + const a = [4]; + const b = [5, 6]; + `, + '__spreadArray(a, b)'); + + expect(arr).toEqual([4, 5, 6]); + }); + + it('should evaluate `__spreadArray()` (star import)', () => { + const arr: number[] = evaluateExpression( + ` + import * as tslib from 'tslib'; + const a = [4]; + const b = [5, 6]; + `, + 'tslib.__spreadArray(a, b)'); + + expect(arr).toEqual([4, 5, 6]); + }); }); describe('(with emitted TypeScript helpers as functions)', () => { @@ -742,6 +767,7 @@ runInEachFileSystem(() => { function __assign(t, ...sources) { /* ... */ } function __spread(...args) { /* ... */ } function __spreadArrays(...args) { /* ... */ } + function __spreadArray(to, from) { /* ... */ } `; const {checker, expression} = makeExpression(helpers + code, expr); @@ -786,6 +812,17 @@ runInEachFileSystem(() => { expect(arr).toEqual([4, 5, 6]); }); + + it('should evaluate `__spreadArray()`', () => { + const arr: number[] = evaluateExpression( + ` + const a = [4]; + const b = [5, 6]; + `, + '__spreadArray(a, b)'); + + expect(arr).toEqual([4, 5, 6]); + }); }); describe('(with emitted TypeScript helpers as variables)', () => { @@ -795,6 +832,7 @@ runInEachFileSystem(() => { var __assign = (this && this.__assign) || function (t, ...sources) { /* ... */ } var __spread = (this && this.__spread) || function (...args) { /* ... */ } var __spreadArrays = (this && this.__spreadArrays) || function (...args) { /* ... */ } + var __spreadArray = (this && this.__spreadArray) || function (to, from) { /* ... */ } `; const {checker, expression} = makeExpression(helpers + code, expr); @@ -839,6 +877,17 @@ runInEachFileSystem(() => { expect(arr).toEqual([4, 5, 6]); }); + + it('should evaluate `__spreadArray()`', () => { + const arr: number[] = evaluateExpression( + ` + const a = [4]; + const b = [5, 6]; + `, + '__spreadArray(a, b)'); + + expect(arr).toEqual([4, 5, 6]); + }); }); describe('(visited file tracking)', () => { @@ -980,6 +1029,8 @@ runInEachFileSystem(() => { return KnownDeclaration.TsHelperSpread; case '__spreadArrays': return KnownDeclaration.TsHelperSpreadArrays; + case '__spreadArray': + return KnownDeclaration.TsHelperSpreadArray; default: return null; } diff --git a/packages/compiler-cli/src/ngtsc/reflection/src/host.ts b/packages/compiler-cli/src/ngtsc/reflection/src/host.ts index cbf28be5b8..c3dbcf07ca 100644 --- a/packages/compiler-cli/src/ngtsc/reflection/src/host.ts +++ b/packages/compiler-cli/src/ngtsc/reflection/src/host.ts @@ -474,6 +474,11 @@ export enum KnownDeclaration { * Indicates the `__spreadArrays` TypeScript helper function. */ TsHelperSpreadArrays, + + /** + * Indicates the `__spreadArray` TypeScript helper function. + */ + TsHelperSpreadArray, } /**