feat(ivy): static evaluation of TypeScript's __spread
helper (#30492)
The usage of array spread syntax in source code may be downleveled to a call to TypeScript's `__spread` helper function from `tslib`, depending on the options `downlevelIteration` and `emitHelpers`. This proves problematic for ngcc when it is processing ES5 formats, as the static evaluator won't be able to interpret those calls. A custom foreign function resolver is not sufficient in this case, as `tslib` may be emitted into the library code itself. In that case, a helper function can be resolved to an actual function with body, such that it won't be considered as foreign function. Instead, a reflection host can now indicate that the definition of a function corresponds with a certain TypeScript helper, such that it becomes statically evaluable in ngtsc. Resolves #30299 PR Close #30492
This commit is contained in:
@ -1240,7 +1240,7 @@ describe('Esm2015ReflectionHost', () => {
|
||||
|
||||
const fooNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'foo', isNamedFunctionDeclaration) !;
|
||||
const fooDef = host.getDefinitionOfFunction(fooNode);
|
||||
const fooDef = host.getDefinitionOfFunction(fooNode) !;
|
||||
expect(fooDef.node).toBe(fooNode);
|
||||
expect(fooDef.body !.length).toEqual(1);
|
||||
expect(fooDef.body ![0].getText()).toEqual(`return x;`);
|
||||
@ -1250,7 +1250,7 @@ describe('Esm2015ReflectionHost', () => {
|
||||
|
||||
const barNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'bar', isNamedFunctionDeclaration) !;
|
||||
const barDef = host.getDefinitionOfFunction(barNode);
|
||||
const barDef = host.getDefinitionOfFunction(barNode) !;
|
||||
expect(barDef.node).toBe(barNode);
|
||||
expect(barDef.body !.length).toEqual(1);
|
||||
expect(ts.isReturnStatement(barDef.body ![0])).toBeTruthy();
|
||||
@ -1263,7 +1263,7 @@ describe('Esm2015ReflectionHost', () => {
|
||||
|
||||
const bazNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'baz', isNamedFunctionDeclaration) !;
|
||||
const bazDef = host.getDefinitionOfFunction(bazNode);
|
||||
const bazDef = host.getDefinitionOfFunction(bazNode) !;
|
||||
expect(bazDef.node).toBe(bazNode);
|
||||
expect(bazDef.body !.length).toEqual(3);
|
||||
expect(bazDef.parameters.length).toEqual(1);
|
||||
@ -1272,7 +1272,7 @@ describe('Esm2015ReflectionHost', () => {
|
||||
|
||||
const quxNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'qux', isNamedFunctionDeclaration) !;
|
||||
const quxDef = host.getDefinitionOfFunction(quxNode);
|
||||
const quxDef = host.getDefinitionOfFunction(quxNode) !;
|
||||
expect(quxDef.node).toBe(quxNode);
|
||||
expect(quxDef.body !.length).toEqual(2);
|
||||
expect(quxDef.parameters.length).toEqual(1);
|
||||
@ -1281,14 +1281,14 @@ describe('Esm2015ReflectionHost', () => {
|
||||
|
||||
const mooNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'moo', isNamedFunctionDeclaration) !;
|
||||
const mooDef = host.getDefinitionOfFunction(mooNode);
|
||||
const mooDef = host.getDefinitionOfFunction(mooNode) !;
|
||||
expect(mooDef.node).toBe(mooNode);
|
||||
expect(mooDef.body !.length).toEqual(3);
|
||||
expect(mooDef.parameters).toEqual([]);
|
||||
|
||||
const juuNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'juu', isNamedFunctionDeclaration) !;
|
||||
const juuDef = host.getDefinitionOfFunction(juuNode);
|
||||
const juuDef = host.getDefinitionOfFunction(juuNode) !;
|
||||
expect(juuDef.node).toBe(juuNode);
|
||||
expect(juuDef.body !.length).toEqual(2);
|
||||
expect(juuDef.parameters).toEqual([]);
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassMemberKind, CtorParameter, Decorator, Import, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
|
||||
import {ClassMemberKind, CtorParameter, Decorator, Import, TsHelperFn, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
|
||||
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
|
||||
import {Esm5ReflectionHost, getIifeBody} from '../../src/host/esm5_host';
|
||||
import {MockLogger} from '../helpers/mock_logger';
|
||||
@ -1409,7 +1409,7 @@ describe('Esm5ReflectionHost', () => {
|
||||
|
||||
const fooNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'foo', isNamedFunctionDeclaration) !;
|
||||
const fooDef = host.getDefinitionOfFunction(fooNode);
|
||||
const fooDef = host.getDefinitionOfFunction(fooNode) !;
|
||||
expect(fooDef.node).toBe(fooNode);
|
||||
expect(fooDef.body !.length).toEqual(1);
|
||||
expect(fooDef.body ![0].getText()).toEqual(`return x;`);
|
||||
@ -1419,7 +1419,7 @@ describe('Esm5ReflectionHost', () => {
|
||||
|
||||
const barNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'bar', isNamedFunctionDeclaration) !;
|
||||
const barDef = host.getDefinitionOfFunction(barNode);
|
||||
const barDef = host.getDefinitionOfFunction(barNode) !;
|
||||
expect(barDef.node).toBe(barNode);
|
||||
expect(barDef.body !.length).toEqual(1);
|
||||
expect(ts.isReturnStatement(barDef.body ![0])).toBeTruthy();
|
||||
@ -1432,7 +1432,7 @@ describe('Esm5ReflectionHost', () => {
|
||||
|
||||
const bazNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'baz', isNamedFunctionDeclaration) !;
|
||||
const bazDef = host.getDefinitionOfFunction(bazNode);
|
||||
const bazDef = host.getDefinitionOfFunction(bazNode) !;
|
||||
expect(bazDef.node).toBe(bazNode);
|
||||
expect(bazDef.body !.length).toEqual(3);
|
||||
expect(bazDef.parameters.length).toEqual(1);
|
||||
@ -1441,13 +1441,51 @@ describe('Esm5ReflectionHost', () => {
|
||||
|
||||
const quxNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'qux', isNamedFunctionDeclaration) !;
|
||||
const quxDef = host.getDefinitionOfFunction(quxNode);
|
||||
const quxDef = host.getDefinitionOfFunction(quxNode) !;
|
||||
expect(quxDef.node).toBe(quxNode);
|
||||
expect(quxDef.body !.length).toEqual(2);
|
||||
expect(quxDef.parameters.length).toEqual(1);
|
||||
expect(quxDef.parameters[0].name).toEqual('x');
|
||||
expect(quxDef.parameters[0].initializer).toBe(null);
|
||||
});
|
||||
|
||||
it('should recognize TypeScript __spread helper function declaration', () => {
|
||||
const file = {
|
||||
name: 'declaration.d.ts',
|
||||
contents: `export declare function __spread(...args: any[]): any[];`,
|
||||
};
|
||||
const program = makeTestProgram(file);
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
|
||||
const node = getDeclaration(program, file.name, '__spread', isNamedFunctionDeclaration) !;
|
||||
|
||||
const definition = host.getDefinitionOfFunction(node) !;
|
||||
expect(definition.node).toBe(node);
|
||||
expect(definition.body).toBeNull();
|
||||
expect(definition.helper).toBe(TsHelperFn.Spread);
|
||||
expect(definition.parameters.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should recognize TypeScript __spread helper function implementation', () => {
|
||||
const file = {
|
||||
name: 'implementation.js',
|
||||
contents: `
|
||||
var __spread = (this && this.__spread) || function () {
|
||||
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
|
||||
return ar;
|
||||
};`,
|
||||
};
|
||||
const program = makeTestProgram(file);
|
||||
const host = new Esm5ReflectionHost(new MockLogger(), false, program.getTypeChecker());
|
||||
|
||||
const node = getDeclaration(program, file.name, '__spread', ts.isVariableDeclaration) !;
|
||||
|
||||
const definition = host.getDefinitionOfFunction(node) !;
|
||||
expect(definition.node).toBe(node);
|
||||
expect(definition.body).toBeNull();
|
||||
expect(definition.helper).toBe(TsHelperFn.Spread);
|
||||
expect(definition.parameters.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getImportOfIdentifier()', () => {
|
||||
|
@ -1380,7 +1380,7 @@ describe('UmdReflectionHost', () => {
|
||||
|
||||
const fooNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'foo', isNamedFunctionDeclaration) !;
|
||||
const fooDef = host.getDefinitionOfFunction(fooNode);
|
||||
const fooDef = host.getDefinitionOfFunction(fooNode) !;
|
||||
expect(fooDef.node).toBe(fooNode);
|
||||
expect(fooDef.body !.length).toEqual(1);
|
||||
expect(fooDef.body ![0].getText()).toEqual(`return x;`);
|
||||
@ -1390,7 +1390,7 @@ describe('UmdReflectionHost', () => {
|
||||
|
||||
const barNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'bar', isNamedFunctionDeclaration) !;
|
||||
const barDef = host.getDefinitionOfFunction(barNode);
|
||||
const barDef = host.getDefinitionOfFunction(barNode) !;
|
||||
expect(barDef.node).toBe(barNode);
|
||||
expect(barDef.body !.length).toEqual(1);
|
||||
expect(ts.isReturnStatement(barDef.body ![0])).toBeTruthy();
|
||||
@ -1403,7 +1403,7 @@ describe('UmdReflectionHost', () => {
|
||||
|
||||
const bazNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'baz', isNamedFunctionDeclaration) !;
|
||||
const bazDef = host.getDefinitionOfFunction(bazNode);
|
||||
const bazDef = host.getDefinitionOfFunction(bazNode) !;
|
||||
expect(bazDef.node).toBe(bazNode);
|
||||
expect(bazDef.body !.length).toEqual(3);
|
||||
expect(bazDef.parameters.length).toEqual(1);
|
||||
@ -1412,7 +1412,7 @@ describe('UmdReflectionHost', () => {
|
||||
|
||||
const quxNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'qux', isNamedFunctionDeclaration) !;
|
||||
const quxDef = host.getDefinitionOfFunction(quxNode);
|
||||
const quxDef = host.getDefinitionOfFunction(quxNode) !;
|
||||
expect(quxDef.node).toBe(quxNode);
|
||||
expect(quxDef.body !.length).toEqual(2);
|
||||
expect(quxDef.parameters.length).toEqual(1);
|
||||
|
Reference in New Issue
Block a user