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:
parent
c0386757b1
commit
9d9c9e43e5
|
@ -1329,7 +1329,11 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
return null;
|
||||
}
|
||||
const declaration = implementation;
|
||||
const body = this.getDefinitionOfFunction(declaration).body;
|
||||
const definition = this.getDefinitionOfFunction(declaration);
|
||||
if (definition === null) {
|
||||
return null;
|
||||
}
|
||||
const body = definition.body;
|
||||
const lastStatement = body && body[body.length - 1];
|
||||
const returnExpression =
|
||||
lastStatement && ts.isReturnStatement(lastStatement) && lastStatement.expression || null;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, FunctionDefinition, Parameter, isNamedVariableDeclaration, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, ClassSymbol, CtorParameter, Declaration, Decorator, FunctionDefinition, Parameter, TsHelperFn, isNamedVariableDeclaration, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||
import {isFromDtsFile} from '../../../src/ngtsc/util/src/typescript';
|
||||
import {getNameText, hasNameIdentifier} from '../utils';
|
||||
|
||||
|
@ -148,8 +148,28 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
|||
* @param node the function declaration to parse.
|
||||
* @returns an object containing the node, statements and parameters of the function.
|
||||
*/
|
||||
getDefinitionOfFunction<T extends ts.FunctionDeclaration|ts.MethodDeclaration|
|
||||
ts.FunctionExpression>(node: T): FunctionDefinition<T> {
|
||||
getDefinitionOfFunction(node: ts.Node): FunctionDefinition|null {
|
||||
if (!ts.isFunctionDeclaration(node) && !ts.isMethodDeclaration(node) &&
|
||||
!ts.isFunctionExpression(node) && !ts.isVariableDeclaration(node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tsHelperFn = getTsHelperFn(node);
|
||||
if (tsHelperFn !== null) {
|
||||
return {
|
||||
node,
|
||||
body: null,
|
||||
helper: tsHelperFn,
|
||||
parameters: [],
|
||||
};
|
||||
}
|
||||
|
||||
// If the node was not identified to be a TypeScript helper, a variable declaration at this
|
||||
// point cannot be resolved as a function.
|
||||
if (ts.isVariableDeclaration(node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parameters =
|
||||
node.parameters.map(p => ({name: getNameText(p.name), node: p, initializer: null}));
|
||||
let lookingForParamInitializers = true;
|
||||
|
@ -161,7 +181,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
|||
return !lookingForParamInitializers;
|
||||
});
|
||||
|
||||
return {node, body: statements || null, parameters};
|
||||
return {node, body: statements || null, helper: null, parameters};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -565,6 +585,21 @@ function reflectArrayElement(element: ts.Expression) {
|
|||
return ts.isObjectLiteralExpression(element) ? reflectObjectLiteral(element) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects a function declaration to determine if it corresponds with a TypeScript helper function,
|
||||
* returning its kind if so or null if the declaration does not seem to correspond with such a
|
||||
* helper.
|
||||
*/
|
||||
function getTsHelperFn(node: ts.NamedDeclaration): TsHelperFn|null {
|
||||
const name = node.name !== undefined && ts.isIdentifier(node.name) && node.name.text;
|
||||
|
||||
if (name === '__spread') {
|
||||
return TsHelperFn.Spread;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A constructor function may have been "synthesized" by TypeScript during JavaScript emit,
|
||||
* in the case no user-defined constructor exists and e.g. property initializers are used.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -17,6 +17,7 @@ import {ArrayConcatBuiltinFn, ArraySliceBuiltinFn} from './builtin';
|
|||
import {DynamicValue} from './dynamic';
|
||||
import {DependencyTracker, ForeignFunctionResolver} from './interface';
|
||||
import {BuiltinFn, EnumValue, ResolvedValue, ResolvedValueArray, ResolvedValueMap} from './result';
|
||||
import {evaluateTsHelperInline} from './ts_helpers';
|
||||
|
||||
|
||||
/**
|
||||
|
@ -386,11 +387,22 @@ export class StaticInterpreter {
|
|||
|
||||
if (!(lhs instanceof Reference)) {
|
||||
return DynamicValue.fromInvalidExpressionType(node.expression, lhs);
|
||||
} else if (!isFunctionOrMethodReference(lhs)) {
|
||||
return DynamicValue.fromInvalidExpressionType(node.expression, lhs);
|
||||
}
|
||||
|
||||
const fn = this.host.getDefinitionOfFunction(lhs.node);
|
||||
if (fn === null) {
|
||||
return DynamicValue.fromInvalidExpressionType(node.expression, lhs);
|
||||
}
|
||||
|
||||
// If the function corresponds with a tslib helper function, evaluate it with custom logic.
|
||||
if (fn.helper !== null) {
|
||||
const args = this.evaluateFunctionArguments(node, context);
|
||||
return evaluateTsHelperInline(fn.helper, node, args);
|
||||
}
|
||||
|
||||
if (!isFunctionOrMethodReference(lhs)) {
|
||||
return DynamicValue.fromInvalidExpressionType(node.expression, lhs);
|
||||
}
|
||||
|
||||
// If the function is foreign (declared through a d.ts file), attempt to resolve it with the
|
||||
// foreignFunctionResolver, if one is specified.
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {TsHelperFn} from '../../reflection';
|
||||
|
||||
import {DynamicValue} from './dynamic';
|
||||
import {ResolvedValue, ResolvedValueArray} from './result';
|
||||
|
||||
export function evaluateTsHelperInline(
|
||||
helper: TsHelperFn, node: ts.Node, args: ResolvedValueArray): ResolvedValue {
|
||||
if (helper === TsHelperFn.Spread) {
|
||||
return evaluateTsSpreadHelper(node, args);
|
||||
} else {
|
||||
throw new Error(`Cannot evaluate unknown helper ${helper} inline`);
|
||||
}
|
||||
}
|
||||
|
||||
function evaluateTsSpreadHelper(node: ts.Node, args: ResolvedValueArray): ResolvedValueArray {
|
||||
const result: ResolvedValueArray = [];
|
||||
for (const arg of args) {
|
||||
if (arg instanceof DynamicValue) {
|
||||
result.push(DynamicValue.fromDynamicInput(node, arg));
|
||||
} else if (Array.isArray(arg)) {
|
||||
result.push(...arg);
|
||||
} else {
|
||||
result.push(arg);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -9,8 +9,10 @@
|
|||
import * as ts from 'typescript';
|
||||
|
||||
import {Reference} from '../../imports';
|
||||
import {FunctionDefinition, TsHelperFn, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {getDeclaration, makeProgram} from '../../testing/in_memory_typescript';
|
||||
import {DynamicValue} from '../src/dynamic';
|
||||
import {PartialEvaluator} from '../src/interface';
|
||||
import {EnumValue} from '../src/result';
|
||||
|
||||
import {evaluate, firstArgFfr, makeEvaluator, makeExpression, owningModuleOf} from './utils';
|
||||
|
@ -343,6 +345,27 @@ describe('ngtsc metadata', () => {
|
|||
expect((value.node as ts.CallExpression).expression.getText()).toBe('foo');
|
||||
});
|
||||
|
||||
it('should evaluate TypeScript __spread helper', () => {
|
||||
const {checker, expression} = makeExpression(
|
||||
`
|
||||
import * as tslib from 'tslib';
|
||||
const a = [1];
|
||||
const b = [2, 3];
|
||||
`,
|
||||
'tslib.__spread(a, b)', [
|
||||
{
|
||||
name: 'node_modules/tslib/index.d.ts',
|
||||
contents: `
|
||||
export declare function __spread(...args: any[]): any[];
|
||||
`
|
||||
},
|
||||
]);
|
||||
const reflectionHost = new TsLibAwareReflectionHost(checker);
|
||||
const evaluator = new PartialEvaluator(reflectionHost, checker);
|
||||
const value = evaluator.evaluate(expression);
|
||||
expect(value).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
describe('(visited file tracking)', () => {
|
||||
it('should track each time a source file is visited', () => {
|
||||
const trackFileDependency = jasmine.createSpy('DependencyTracker');
|
||||
|
@ -393,3 +416,34 @@ describe('ngtsc metadata', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Customizes the resolution of functions to recognize functions from tslib. Such functions are not
|
||||
* handled specially in the default TypeScript host, as only ngcc's ES5 host will have special
|
||||
* powers to recognize functions from tslib.
|
||||
*/
|
||||
class TsLibAwareReflectionHost extends TypeScriptReflectionHost {
|
||||
getDefinitionOfFunction(node: ts.Node): FunctionDefinition|null {
|
||||
if (ts.isFunctionDeclaration(node)) {
|
||||
const helper = getTsHelperFn(node);
|
||||
if (helper !== null) {
|
||||
return {
|
||||
node,
|
||||
body: null, helper,
|
||||
parameters: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
return super.getDefinitionOfFunction(node);
|
||||
}
|
||||
}
|
||||
|
||||
function getTsHelperFn(node: ts.FunctionDeclaration): TsHelperFn|null {
|
||||
const name = node.name !== undefined && ts.isIdentifier(node.name) && node.name.text;
|
||||
|
||||
if (name === '__spread') {
|
||||
return TsHelperFn.Spread;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -254,27 +254,44 @@ export interface CtorParameter {
|
|||
* itself. In ES5 code this can be more complicated, as the default values for parameters may
|
||||
* be extracted from certain body statements.
|
||||
*/
|
||||
export interface FunctionDefinition<T extends ts.MethodDeclaration|ts.FunctionDeclaration|
|
||||
ts.FunctionExpression> {
|
||||
export interface FunctionDefinition {
|
||||
/**
|
||||
* A reference to the node which declares the function.
|
||||
*/
|
||||
node: T;
|
||||
node: ts.MethodDeclaration|ts.FunctionDeclaration|ts.FunctionExpression|ts.VariableDeclaration;
|
||||
|
||||
/**
|
||||
* Statements of the function body, if a body is present, or null if no body is present.
|
||||
* Statements of the function body, if a body is present, or null if no body is present or the
|
||||
* function is identified to represent a tslib helper function, in which case `helper` will
|
||||
* indicate which helper this function represents.
|
||||
*
|
||||
* This list may have been filtered to exclude statements which perform parameter default value
|
||||
* initialization.
|
||||
*/
|
||||
body: ts.Statement[]|null;
|
||||
|
||||
/**
|
||||
* The type of tslib helper function, if the function is determined to represent a tslib helper
|
||||
* function. Otherwise, this will be null.
|
||||
*/
|
||||
helper: TsHelperFn|null;
|
||||
|
||||
/**
|
||||
* Metadata regarding the function's parameters, including possible default value expressions.
|
||||
*/
|
||||
parameters: Parameter[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Possible functions from TypeScript's helper library.
|
||||
*/
|
||||
export enum TsHelperFn {
|
||||
/**
|
||||
* Indicates the `__spread` function.
|
||||
*/
|
||||
Spread,
|
||||
}
|
||||
|
||||
/**
|
||||
* A parameter to a function or method.
|
||||
*/
|
||||
|
@ -404,8 +421,7 @@ export interface ReflectionHost {
|
|||
*
|
||||
* @returns a `FunctionDefinition` giving metadata about the function definition.
|
||||
*/
|
||||
getDefinitionOfFunction<T extends ts.MethodDeclaration|ts.FunctionDeclaration|
|
||||
ts.FunctionExpression>(fn: T): FunctionDefinition<T>;
|
||||
getDefinitionOfFunction(fn: ts.Node): FunctionDefinition|null;
|
||||
|
||||
/**
|
||||
* Determine if an identifier was imported from another module and return `Import` metadata
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, CtorParameter, Declaration, Decorator, FunctionDefinition, Import, ReflectionHost} from './host';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, CtorParameter, Declaration, Decorator, FunctionDefinition, Import, ReflectionHost, TsHelperFn} from './host';
|
||||
import {typeToValue} from './type_to_value';
|
||||
|
||||
/**
|
||||
|
@ -125,11 +125,15 @@ export class TypeScriptReflectionHost implements ReflectionHost {
|
|||
return this.getDeclarationOfSymbol(symbol);
|
||||
}
|
||||
|
||||
getDefinitionOfFunction<T extends ts.FunctionDeclaration|ts.MethodDeclaration|
|
||||
ts.FunctionExpression>(node: T): FunctionDefinition<T> {
|
||||
getDefinitionOfFunction(node: ts.Node): FunctionDefinition|null {
|
||||
if (!ts.isFunctionDeclaration(node) && !ts.isMethodDeclaration(node) &&
|
||||
!ts.isFunctionExpression(node)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
node,
|
||||
body: node.body !== undefined ? Array.from(node.body.statements) : null,
|
||||
helper: null,
|
||||
parameters: node.parameters.map(param => {
|
||||
const name = parameterName(param.name);
|
||||
const initializer = param.initializer || null;
|
||||
|
|
Loading…
Reference in New Issue