From 9d9c9e43e544ac2ecf74e20d31ba3bd1064db6c9 Mon Sep 17 00:00:00 2001 From: JoostK Date: Wed, 15 May 2019 21:10:47 +0200 Subject: [PATCH] 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 --- .../ngcc/src/host/esm2015_host.ts | 6 ++- .../compiler-cli/ngcc/src/host/esm5_host.ts | 43 +++++++++++++-- .../ngcc/test/host/esm2015_host_spec.ts | 12 ++--- .../ngcc/test/host/esm5_host_spec.ts | 48 +++++++++++++++-- .../ngcc/test/host/umd_host_spec.ts | 8 +-- .../partial_evaluator/src/interpreter.ts | 16 +++++- .../ngtsc/partial_evaluator/src/ts_helpers.ts | 37 +++++++++++++ .../partial_evaluator/test/evaluator_spec.ts | 54 +++++++++++++++++++ .../src/ngtsc/reflection/src/host.ts | 28 +++++++--- .../src/ngtsc/reflection/src/typescript.ts | 10 ++-- 10 files changed, 231 insertions(+), 31 deletions(-) create mode 100644 packages/compiler-cli/src/ngtsc/partial_evaluator/src/ts_helpers.ts diff --git a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts index b30074c49e..044b523d61 100644 --- a/packages/compiler-cli/ngcc/src/host/esm2015_host.ts +++ b/packages/compiler-cli/ngcc/src/host/esm2015_host.ts @@ -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; diff --git a/packages/compiler-cli/ngcc/src/host/esm5_host.ts b/packages/compiler-cli/ngcc/src/host/esm5_host.ts index 6fcfb09baa..8efa7d8797 100644 --- a/packages/compiler-cli/ngcc/src/host/esm5_host.ts +++ b/packages/compiler-cli/ngcc/src/host/esm5_host.ts @@ -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(node: T): FunctionDefinition { + 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. diff --git a/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts b/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts index 75defb0600..33c4c8e533 100644 --- a/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/esm2015_host_spec.ts @@ -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([]); 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 76cc48b156..a4dd0a2c08 100644 --- a/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/esm5_host_spec.ts @@ -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()', () => { 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 bf76f5a5d9..444e281c93 100644 --- a/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/host/umd_host_spec.ts @@ -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); diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts index 56b9ac70c0..a74c84770e 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts @@ -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. 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 new file mode 100644 index 0000000000..82e4ec805c --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/ts_helpers.ts @@ -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; +} 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 4cf93ea800..f6ff0a83c0 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 @@ -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; + } +} diff --git a/packages/compiler-cli/src/ngtsc/reflection/src/host.ts b/packages/compiler-cli/src/ngtsc/reflection/src/host.ts index 4e87a1409e..434de99070 100644 --- a/packages/compiler-cli/src/ngtsc/reflection/src/host.ts +++ b/packages/compiler-cli/src/ngtsc/reflection/src/host.ts @@ -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 { +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(fn: T): FunctionDefinition; + getDefinitionOfFunction(fn: ts.Node): FunctionDefinition|null; /** * Determine if an identifier was imported from another module and return `Import` metadata diff --git a/packages/compiler-cli/src/ngtsc/reflection/src/typescript.ts b/packages/compiler-cli/src/ngtsc/reflection/src/typescript.ts index 270083c2f9..1bd26adb24 100644 --- a/packages/compiler-cli/src/ngtsc/reflection/src/typescript.ts +++ b/packages/compiler-cli/src/ngtsc/reflection/src/typescript.ts @@ -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(node: T): FunctionDefinition { + 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;