fix(ngcc): correctly detect emitted TS helpers in ES5 (#35191)

In ES5 code, TypeScript requires certain helpers (such as
`__spreadArrays()`) to be able to support ES2015+ features. These
helpers can be either imported from `tslib` (by setting the
`importHelpers` TS compiler option to `true`) or emitted inline (by
setting the `importHelpers` and `noEmitHelpers` TS compiler options to
`false`, which is the default value for both).

Ngtsc's `StaticInterpreter` (which is also used during ngcc processing)
is able to statically evaluate some of these helpers (currently
`__assign()`, `__spread()` and `__spreadArrays()`), as long as
`ReflectionHost#getDefinitionOfFunction()` correctly detects the
declaration of the helper. For this to happen, the left-hand side of the
corresponding call expression (i.e. `__spread(...)` or
`tslib.__spread(...)`) must be evaluated as a function declaration for
`getDefinitionOfFunction()` to be called with.

In the case of imported helpers, the `tslib.__someHelper` expression was
resolved to a function declaration of the form
`export declare function __someHelper(...args: any[][]): any[];`, which
allows `getDefinitionOfFunction()` to correctly map it to a TS helper.

In contrast, in the case of emitted helpers (and regardless of the
module format: `CommonJS`, `ESNext`, `UMD`, etc.)), the `__someHelper`
identifier was resolved to a variable declaration of the form
`var __someHelper = (this && this.__someHelper) || function () { ... }`,
which upon further evaluation was categorized as a `DynamicValue`
(prohibiting further evaluation by the `getDefinitionOfFunction()`).

As a result of the above, emitted TypeScript helpers were not evaluated
in ES5 code.

---
This commit changes the detection of TS helpers to leverage the existing
`KnownFn` feature (previously only used for built-in functions).
`Esm5ReflectionHost` is changed to always return `KnownDeclaration`s for
TS helpers, both imported (`getExportsOfModule()`) as well as emitted
(`getDeclarationOfIdentifier()`).

Similar changes are made to `CommonJsReflectionHost` and
`UmdReflectionHost`.

The `KnownDeclaration`s are then mapped to `KnownFn`s in
`StaticInterpreter`, allowing it to statically evaluate call expressions
involving any kind of TS helpers.

Jira issue: https://angular-team.atlassian.net/browse/FW-1689

PR Close #35191
This commit is contained in:
George Kalpakas 2020-02-06 18:44:49 +02:00 committed by Miško Hevery
parent 14744f27c5
commit bd6a39c364
15 changed files with 1194 additions and 453 deletions

View File

@ -11,7 +11,7 @@ import {absoluteFrom} from '../../../src/ngtsc/file_system';
import {Declaration, Import} from '../../../src/ngtsc/reflection';
import {Logger} from '../logging/logger';
import {BundleProgram} from '../packages/bundle_program';
import {FactoryMap, isDefined, stripExtension} from '../utils';
import {FactoryMap, getTsHelperFnFromIdentifier, isDefined, stripExtension} from '../utils';
import {ExportDeclaration, ExportStatement, ReexportStatement, RequireCall, findNamespaceOfIdentifier, findRequireCallReference, isExportStatement, isReexportStatement, isRequireCall} from './commonjs_umd_utils';
import {Esm5ReflectionHost} from './esm5_host';
@ -189,7 +189,7 @@ export class CommonJsReflectionHost extends Esm5ReflectionHost {
}
const viaModule = !importInfo.from.startsWith('.') ? importInfo.from : null;
return {node: importedFile, known: null, viaModule};
return {node: importedFile, known: getTsHelperFnFromIdentifier(id), viaModule};
}
private resolveModuleName(moduleName: string, containingFile: ts.SourceFile): ts.SourceFile

View File

@ -8,8 +8,8 @@
import * as ts from 'typescript';
import {ClassDeclaration, ClassMember, ClassMemberKind, Declaration, Decorator, FunctionDefinition, Parameter, TsHelperFn, isNamedVariableDeclaration, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
import {getNameText, hasNameIdentifier, stripDollarSuffix} from '../utils';
import {ClassDeclaration, ClassMember, ClassMemberKind, Declaration, Decorator, FunctionDefinition, Parameter, isNamedVariableDeclaration, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
import {getNameText, getTsHelperFnFromDeclaration, hasNameIdentifier} from '../utils';
import {Esm2015ReflectionHost, ParamInfo, getPropertyValueFromSymbol, isAssignment, isAssignmentStatement} from './esm2015_host';
import {NgccClassSymbol} from './ngcc_host';
@ -118,6 +118,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
// Return the statement before the IIFE return statement
return iifeBody.statements[returnStatementIndex - 1];
}
/**
* In ES5, the implementation of a class is a function expression that is hidden inside an IIFE,
* whose value is assigned to a variable (which represents the class to the rest of the program).
@ -245,23 +246,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
*/
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)) {
!ts.isFunctionExpression(node)) {
return null;
}
@ -276,11 +261,26 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
return !lookingForParamInitializers;
});
return {node, body: statements || null, helper: null, parameters};
return {node, body: statements || null, parameters};
}
///////////// Protected Helpers /////////////
/**
* Resolve a `ts.Symbol` to its declaration and detect whether it corresponds with a known
* TypeScript helper function.
*/
protected getDeclarationOfSymbol(symbol: ts.Symbol, originalId: ts.Identifier|null): Declaration
|null {
const superDeclaration = super.getDeclarationOfSymbol(symbol, originalId);
if (superDeclaration !== null && superDeclaration.node !== null &&
superDeclaration.known === null) {
superDeclaration.known = getTsHelperFnFromDeclaration(superDeclaration.node);
}
return superDeclaration;
}
/**
* Get the inner function declaration of an ES5-style class.
@ -651,28 +651,6 @@ 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) ?
stripDollarSuffix(node.name.text) :
null;
switch (name) {
case '__assign':
return TsHelperFn.Assign;
case '__spread':
return TsHelperFn.Spread;
case '__spreadArrays':
return TsHelperFn.SpreadArrays;
default:
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.

View File

@ -12,7 +12,7 @@ import {absoluteFrom} from '../../../src/ngtsc/file_system';
import {Declaration, Import} from '../../../src/ngtsc/reflection';
import {Logger} from '../logging/logger';
import {BundleProgram} from '../packages/bundle_program';
import {FactoryMap, stripExtension} from '../utils';
import {FactoryMap, getTsHelperFnFromIdentifier, stripExtension} from '../utils';
import {ExportDeclaration, ExportStatement, ReexportStatement, findNamespaceOfIdentifier, findRequireCallReference, isExportStatement, isReexportStatement, isRequireCall} from './commonjs_umd_utils';
import {Esm5ReflectionHost, stripParentheses} from './esm5_host';
@ -215,7 +215,7 @@ export class UmdReflectionHost extends Esm5ReflectionHost {
// We need to add the `viaModule` because the `getExportsOfModule()` call
// did not know that we were importing the declaration.
return {node: importedFile, known: null, viaModule: importInfo.from};
return {node: importedFile, known: getTsHelperFnFromIdentifier(id), viaModule: importInfo.from};
}
private resolveModuleName(moduleName: string, containingFile: ts.SourceFile): ts.SourceFile

View File

@ -7,6 +7,7 @@
*/
import * as ts from 'typescript';
import {AbsoluteFsPath, FileSystem, absoluteFrom} from '../../src/ngtsc/file_system';
import {KnownDeclaration} from '../../src/ngtsc/reflection';
/**
* A list (`Array`) of partially ordered `T` items.
@ -132,6 +133,40 @@ export function resolveFileWithPostfixes(
return null;
}
/**
* Determine whether a function declaration 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.
*/
export function getTsHelperFnFromDeclaration(decl: ts.Declaration): KnownDeclaration|null {
if (!ts.isFunctionDeclaration(decl) && !ts.isVariableDeclaration(decl)) {
return null;
}
if (decl.name === undefined || !ts.isIdentifier(decl.name)) {
return null;
}
return getTsHelperFnFromIdentifier(decl.name);
}
/**
* Determine whether an identifier corresponds with a TypeScript helper function (based on its
* name), returning its kind if so or null if the identifier does not seem to correspond with such a
* helper.
*/
export function getTsHelperFnFromIdentifier(id: ts.Identifier): KnownDeclaration|null {
switch (stripDollarSuffix(id.text)) {
case '__assign':
return KnownDeclaration.TsHelperAssign;
case '__spread':
return KnownDeclaration.TsHelperSpread;
case '__spreadArrays':
return KnownDeclaration.TsHelperSpreadArrays;
default:
return null;
}
}
/**
* An identifier may become repeated when bundling multiple source files into a single bundle, so
* bundlers have a strategy of suffixing non-unique identifiers with a suffix like $2. This function

View File

@ -9,7 +9,7 @@ import * as ts from 'typescript';
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system';
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {ClassMemberKind, CtorParameter, InlineDeclaration, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
import {ClassMemberKind, CtorParameter, InlineDeclaration, KnownDeclaration, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
import {getDeclaration} from '../../../src/ngtsc/testing';
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
import {CommonJsReflectionHost} from '../../src/host/commonjs_host';
@ -1660,6 +1660,32 @@ exports.ExternalModule = ExternalModule;
});
describe('getDeclarationOfIdentifier', () => {
// Helpers
const createTestForTsHelper =
(program: ts.Program, host: CommonJsReflectionHost, srcFile: TestFile,
getHelperDeclaration: (name: string) => ts.Declaration) =>
(varName: string, helperName: string, knownAs: KnownDeclaration,
viaModule: string | null = null) => {
const node =
getDeclaration(program, srcFile.name, varName, ts.isVariableDeclaration);
const helperIdentifier = getIdentifierFromCallExpression(node);
const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier);
expect(helperDeclaration).toEqual({
known: knownAs,
node: getHelperDeclaration(helperName), viaModule,
});
};
const getIdentifierFromCallExpression = (decl: ts.VariableDeclaration) => {
if (decl.initializer !== undefined && ts.isCallExpression(decl.initializer)) {
const expr = decl.initializer.expression;
if (ts.isIdentifier(expr)) return expr;
if (ts.isPropertyAccessExpression(expr)) return expr.name;
}
throw new Error(`Unable to extract identifier from declaration '${decl.getText()}'.`);
};
it('should return the declaration of a locally defined identifier', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
@ -1819,6 +1845,150 @@ exports.ExternalModule = ExternalModule;
expect(decl.viaModule).toEqual('sub_module');
expect(decl.node).toBe(expectedDeclaration);
});
it('should recognize TypeScript helpers (as function declarations)', () => {
const file: TestFile = {
name: _('/test.js'),
contents: `
function __assign(t, ...sources) { /* ... */ }
function __spread(...args) { /* ... */ }
function __spreadArrays(...args) { /* ... */ }
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 = new CommonJsReflectionHost(new MockLogger(), false, bundle);
const testForHelper = createTestForTsHelper(
bundle.program, host, file,
helperName =>
getDeclaration(bundle.program, file.name, helperName, ts.isFunctionDeclaration));
testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign);
testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread);
testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize suffixed TypeScript helpers (as function declarations)', () => {
const file: TestFile = {
name: _('/test.js'),
contents: `
function __assign$1(t, ...sources) { /* ... */ }
function __spread$2(...args) { /* ... */ }
function __spreadArrays$3(...args) { /* ... */ }
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 = new CommonJsReflectionHost(new MockLogger(), false, bundle);
const testForHelper = createTestForTsHelper(
bundle.program, host, file,
helperName =>
getDeclaration(bundle.program, file.name, helperName, ts.isFunctionDeclaration));
testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign);
testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread);
testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize TypeScript helpers (as variable declarations)', () => {
const file: TestFile = {
name: _('/test.js'),
contents: `
var __assign = (this && this.__assign) || function (t, ...sources) { /* ... */ }
var __spread = (this && this.__spread) || function (...args) { /* ... */ }
var __spreadArrays = (this && this.__spreadArrays) || function (...args) { /* ... */ }
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 = new CommonJsReflectionHost(new MockLogger(), false, bundle);
const testForHelper = createTestForTsHelper(
bundle.program, host, file,
helperName =>
getDeclaration(bundle.program, file.name, helperName, ts.isVariableDeclaration));
testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign);
testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread);
testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize suffixed TypeScript helpers (as variable declarations)', () => {
const file: TestFile = {
name: _('/test.js'),
contents: `
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 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 = new CommonJsReflectionHost(new MockLogger(), false, bundle);
const testForHelper = createTestForTsHelper(
bundle.program, host, file,
helperName =>
getDeclaration(bundle.program, file.name, helperName, ts.isVariableDeclaration));
testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign);
testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread);
testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize imported TypeScript helpers', () => {
const files: TestFile[] = [
{
name: _('/test.js'),
contents: `
var tslib_1 = require('tslib');
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']);
`,
},
{
name: _('/node_modules/tslib/index.d.ts'),
contents: `
export declare function __assign(t: any, ...sources: any[]): any;
export declare function __spread(...args: any[][]): any[];
export declare function __spreadArrays(...args: any[][]): any[];
`,
},
];
loadTestFiles(files);
const [testFile, tslibFile] = files;
const bundle = makeTestBundleProgram(testFile.name);
const host = new CommonJsReflectionHost(new MockLogger(), false, bundle);
const tslibSourceFile = getSourceFileOrError(bundle.program, tslibFile.name);
const testForHelper =
createTestForTsHelper(bundle.program, host, testFile, () => tslibSourceFile);
testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib');
testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib');
testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib');
});
});
describe('getExportsOfModule()', () => {
@ -1918,6 +2088,31 @@ exports.ExternalModule = ExternalModule;
expect(decl.node).toBeNull();
expect(decl.expression).toBeDefined();
});
it('should recognize declarations of known TypeScript helpers', () => {
const tslib = {
name: _('/tslib.d.ts'),
contents: `
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 __unknownHelper(...args: any[]): any;
`,
};
loadTestFiles([tslib]);
const bundle = makeTestBundleProgram(tslib.name);
const host = new CommonJsReflectionHost(new MockLogger(), false, bundle);
const sf = getSourceFileOrError(bundle.program, tslib.name);
const exportDeclarations = host.getExportsOfModule(sf) !;
expect([...exportDeclarations].map(([exportName, {known}]) => [exportName, known]))
.toEqual([
['__assign', KnownDeclaration.TsHelperAssign],
['__spread', KnownDeclaration.TsHelperSpread],
['__spreadArrays', KnownDeclaration.TsHelperSpreadArrays],
['__unknownHelper', null],
]);
});
});
describe('getClassSymbol()', () => {

View File

@ -10,7 +10,7 @@ import * as ts from 'typescript';
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system';
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {ClassMemberKind, CtorParameter, Decorator, Import, TsHelperFn, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
import {ClassMemberKind, CtorParameter, Decorator, KnownDeclaration, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
import {getDeclaration} from '../../../src/ngtsc/testing';
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
import {Esm2015ReflectionHost} from '../../src/host/esm2015_host';
@ -1689,224 +1689,6 @@ runInEachFileSystem(() => {
expect(quxDef.parameters[0].name).toEqual('x');
expect(quxDef.parameters[0].initializer).toBe(null);
});
it('should recognize TypeScript __spread helper function declaration', () => {
const file: TestFile = {
name: _('/declaration.d.ts'),
contents: `export declare function __spread(...args: any[][]): any[];`,
};
loadTestFiles([file]);
const bundle = makeTestBundleProgram(file.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const node =
getDeclaration(bundle.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: TestFile = {
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;
};`,
};
loadTestFiles([file]);
const bundle = makeTestBundleProgram(file.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const node =
getDeclaration(bundle.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);
});
it('should recognize TypeScript __spread helper function implementation when suffixed',
() => {
const file: TestFile = {
name: _('/implementation.js'),
contents: `
var __spread$2 = (this && this.__spread$2) || function () {
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
return ar;
};`,
};
loadTestFiles([file]);
const bundle = makeTestBundleProgram(file.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const node =
getDeclaration(bundle.program, file.name, '__spread$2', 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);
});
it('should recognize TypeScript __spreadArrays helper function declaration', () => {
const file: TestFile = {
name: _('/declaration.d.ts'),
contents: `export declare function __spreadArrays(...args: any[][]): any[];`,
};
loadTestFiles([file]);
const bundle = makeTestBundleProgram(file.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const node = getDeclaration(
bundle.program, file.name, '__spreadArrays', isNamedFunctionDeclaration) !;
const definition = host.getDefinitionOfFunction(node) !;
expect(definition.node).toBe(node);
expect(definition.body).toBeNull();
expect(definition.helper).toBe(TsHelperFn.SpreadArrays);
expect(definition.parameters.length).toEqual(0);
});
it('should recognize TypeScript __spreadArrays helper function implementation', () => {
const file: TestFile = {
name: _('/implementation.js'),
contents: `
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};`,
};
loadTestFiles([file]);
const bundle = makeTestBundleProgram(file.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const node =
getDeclaration(bundle.program, file.name, '__spreadArrays', ts.isVariableDeclaration) !;
const definition = host.getDefinitionOfFunction(node) !;
expect(definition.node).toBe(node);
expect(definition.body).toBeNull();
expect(definition.helper).toBe(TsHelperFn.SpreadArrays);
expect(definition.parameters.length).toEqual(0);
});
it('should recognize TypeScript __spreadArrays helper function implementation when suffixed',
() => {
const file: TestFile = {
name: _('/implementation.js'),
contents: `
var __spreadArrays$2 = (this && this.__spreadArrays$2) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};`,
};
loadTestFiles([file]);
const bundle = makeTestBundleProgram(file.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const node = getDeclaration(
bundle.program, file.name, '__spreadArrays$2', ts.isVariableDeclaration) !;
const definition = host.getDefinitionOfFunction(node) !;
expect(definition.node).toBe(node);
expect(definition.body).toBeNull();
expect(definition.helper).toBe(TsHelperFn.SpreadArrays);
expect(definition.parameters.length).toEqual(0);
});
it('should recognize TypeScript __assign helper function declaration', () => {
const file: TestFile = {
name: _('/declaration.d.ts'),
contents: `export declare function __assign(...args: object[]): object;`,
};
loadTestFiles([file]);
const bundle = makeTestBundleProgram(file.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const node =
getDeclaration(bundle.program, file.name, '__assign', isNamedFunctionDeclaration) !;
const definition = host.getDefinitionOfFunction(node) !;
expect(definition.node).toBe(node);
expect(definition.body).toBeNull();
expect(definition.helper).toBe(TsHelperFn.Assign);
expect(definition.parameters.length).toEqual(0);
});
it('should recognize TypeScript __assign helper function implementation', () => {
const file: TestFile = {
name: _('/implementation.js'),
contents: `
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};`,
};
loadTestFiles([file]);
const bundle = makeTestBundleProgram(file.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const node =
getDeclaration(bundle.program, file.name, '__assign', ts.isVariableDeclaration) !;
const definition = host.getDefinitionOfFunction(node) !;
expect(definition.node).toBe(node);
expect(definition.body).toBeNull();
expect(definition.helper).toBe(TsHelperFn.Assign);
expect(definition.parameters.length).toEqual(0);
});
it('should recognize TypeScript __assign helper function implementation when suffixed',
() => {
const file: TestFile = {
name: _('/implementation.js'),
contents: `
var __assign$2 = (this && this.__assign$2) || function () {
__assign$2 = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign$2.apply(this, arguments);
};`,
};
loadTestFiles([file]);
const bundle = makeTestBundleProgram(file.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const node =
getDeclaration(bundle.program, file.name, '__assign$2', ts.isVariableDeclaration) !;
const definition = host.getDefinitionOfFunction(node) !;
expect(definition.node).toBe(node);
expect(definition.body).toBeNull();
expect(definition.helper).toBe(TsHelperFn.Assign);
expect(definition.parameters.length).toEqual(0);
});
});
describe('getImportOfIdentifier()', () => {
@ -1945,6 +1727,32 @@ runInEachFileSystem(() => {
});
describe('getDeclarationOfIdentifier()', () => {
// Helpers
const createTestForTsHelper =
(program: ts.Program, host: Esm5ReflectionHost, srcFile: TestFile,
getHelperDeclaration: (name: string) => ts.Declaration) =>
(varName: string, helperName: string, knownAs: KnownDeclaration,
viaModule: string | null = null) => {
const node =
getDeclaration(program, srcFile.name, varName, ts.isVariableDeclaration);
const helperIdentifier = getIdentifierFromCallExpression(node);
const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier);
expect(helperDeclaration).toEqual({
known: knownAs,
node: getHelperDeclaration(helperName), viaModule,
});
};
const getIdentifierFromCallExpression = (decl: ts.VariableDeclaration) => {
if (decl.initializer !== undefined && ts.isCallExpression(decl.initializer)) {
const expr = decl.initializer.expression;
if (ts.isIdentifier(expr)) return expr;
if (ts.isPropertyAccessExpression(expr)) return expr.name;
}
throw new Error(`Unable to extract identifier from declaration '${decl.getText()}'.`);
};
it('should return the declaration of a locally defined identifier', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
@ -2117,6 +1925,188 @@ runInEachFileSystem(() => {
const actualDeclaration = host.getDeclarationOfIdentifier(identifier) !;
expect(actualDeclaration.node !.getText()).toBe(expectedDeclaration.getText());
});
it('should recognize TypeScript helpers (as function declarations)', () => {
const file: TestFile = {
name: _('/test.js'),
contents: `
function __assign(t, ...sources) { /* ... */ }
function __spread(...args) { /* ... */ }
function __spreadArrays(...args) { /* ... */ }
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 = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const testForHelper = createTestForTsHelper(
bundle.program, host, file,
helperName =>
getDeclaration(bundle.program, file.name, helperName, ts.isFunctionDeclaration));
testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign);
testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread);
testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize suffixed TypeScript helpers (as function declarations)', () => {
const file: TestFile = {
name: _('/test.js'),
contents: `
function __assign$1(t, ...sources) { /* ... */ }
function __spread$2(...args) { /* ... */ }
function __spreadArrays$3(...args) { /* ... */ }
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 = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const testForHelper = createTestForTsHelper(
bundle.program, host, file,
helperName =>
getDeclaration(bundle.program, file.name, helperName, ts.isFunctionDeclaration));
testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign);
testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread);
testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize TypeScript helpers (as variable declarations)', () => {
const file: TestFile = {
name: _('/test.js'),
contents: `
var __assign = (this && this.__assign) || function (t, ...sources) { /* ... */ }
var __spread = (this && this.__spread) || function (...args) { /* ... */ }
var __spreadArrays = (this && this.__spreadArrays) || function (...args) { /* ... */ }
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 = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const testForHelper = createTestForTsHelper(
bundle.program, host, file,
helperName =>
getDeclaration(bundle.program, file.name, helperName, ts.isVariableDeclaration));
testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign);
testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread);
testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize suffixed TypeScript helpers (as variable declarations)', () => {
const file: TestFile = {
name: _('/test.js'),
contents: `
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 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 = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const testForHelper = createTestForTsHelper(
bundle.program, host, file,
helperName =>
getDeclaration(bundle.program, file.name, helperName, ts.isVariableDeclaration));
testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign);
testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread);
testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize imported TypeScript helpers (named imports)', () => {
const files: TestFile[] = [
{
name: _('/test.js'),
contents: `
import {__assign, __spread, __spreadArrays} from 'tslib';
var a = __assign({foo: 'bar'}, {baz: 'qux'});
var b = __spread(['foo', 'bar'], ['baz', 'qux']);
var c = __spreadArrays(['foo', 'bar'], ['baz', 'qux']);
`,
},
{
name: _('/node_modules/tslib/index.d.ts'),
contents: `
export declare function __assign(t: any, ...sources: any[]): any;
export declare function __spread(...args: any[][]): any[];
export declare function __spreadArrays(...args: any[][]): any[];
`,
},
];
loadTestFiles(files);
const [testFile, tslibFile] = files;
const bundle = makeTestBundleProgram(testFile.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const testForHelper = createTestForTsHelper(
bundle.program, host, testFile,
helperName => getDeclaration(
bundle.program, tslibFile.name, helperName, ts.isFunctionDeclaration));
testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib');
testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib');
testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib');
});
it('should recognize imported TypeScript helpers (star import)', () => {
const files: TestFile[] = [
{
name: _('/test.js'),
contents: `
import * as tslib_1 from 'tslib';
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']);
`,
},
{
name: _('/node_modules/tslib/index.d.ts'),
contents: `
export declare function __assign(t: any, ...sources: any[]): any;
export declare function __spread(...args: any[][]): any[];
export declare function __spreadArrays(...args: any[][]): any[];
`,
},
];
loadTestFiles(files);
const [testFile, tslibFile] = files;
const bundle = makeTestBundleProgram(testFile.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const testForHelper = createTestForTsHelper(
bundle.program, host, testFile,
helperName => getDeclaration(
bundle.program, tslibFile.name, helperName, ts.isFunctionDeclaration));
testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib');
testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib');
testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib');
});
});
describe('getExportsOfModule()', () => {
@ -2159,6 +2149,31 @@ runInEachFileSystem(() => {
],
]);
});
it('should recognize declarations of known TypeScript helpers', () => {
const tslib = {
name: _('/tslib.d.ts'),
contents: `
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 __unknownHelper(...args: any[]): any;
`,
};
loadTestFiles([tslib]);
const bundle = makeTestBundleProgram(tslib.name);
const host = new Esm5ReflectionHost(new MockLogger(), false, bundle);
const sf = getSourceFileOrError(bundle.program, tslib.name);
const exportDeclarations = host.getExportsOfModule(sf) !;
expect([...exportDeclarations].map(([exportName, {known}]) => [exportName, known]))
.toEqual([
['__assign', KnownDeclaration.TsHelperAssign],
['__spread', KnownDeclaration.TsHelperSpread],
['__spreadArrays', KnownDeclaration.TsHelperSpreadArrays],
['__unknownHelper', null],
]);
});
});
describe('getClassSymbol()', () => {

View File

@ -10,11 +10,11 @@ import * as ts from 'typescript';
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../../src/ngtsc/file_system';
import {TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
import {ClassMemberKind, CtorParameter, Import, InlineDeclaration, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
import {ClassMemberKind, CtorParameter, Import, InlineDeclaration, KnownDeclaration, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration} from '../../../src/ngtsc/reflection';
import {getDeclaration} from '../../../src/ngtsc/testing';
import {loadFakeCore, loadTestFiles} from '../../../test/helpers';
import {getIifeBody} from '../../src/host/esm5_host';
import {UmdReflectionHost} from '../../src/host/umd_host';
import {UmdReflectionHost, parseStatementForUmdModule} from '../../src/host/umd_host';
import {MockLogger} from '../helpers/mock_logger';
import {getRootFiles, makeTestBundleProgram} from '../helpers/utils';
@ -1827,6 +1827,41 @@ runInEachFileSystem(() => {
});
describe('getDeclarationOfIdentifier', () => {
// Helpers
const createTestForTsHelper =
(host: UmdReflectionHost, factoryFn: ts.FunctionExpression,
getHelperDeclaration: (factoryFn: ts.FunctionExpression, name: string) =>
ts.Declaration) =>
(varName: string, helperName: string, knownAs: KnownDeclaration,
viaModule: string | null = null) => {
const node = getVariableDeclaration(factoryFn, varName);
const helperIdentifier = getIdentifierFromCallExpression(node);
const helperDeclaration = host.getDeclarationOfIdentifier(helperIdentifier);
expect(helperDeclaration).toEqual({
known: knownAs,
node: getHelperDeclaration(factoryFn, helperName), viaModule,
});
};
const getFunctionDeclaration = (factoryFn: ts.FunctionExpression, name: string) =>
factoryFn.body.statements.filter(ts.isFunctionDeclaration)
.find(decl => (decl.name !== undefined) && (decl.name.text === name)) !;
const getIdentifierFromCallExpression = (decl: ts.VariableDeclaration) => {
if (decl.initializer !== undefined && ts.isCallExpression(decl.initializer)) {
const expr = decl.initializer.expression;
if (ts.isIdentifier(expr)) return expr;
if (ts.isPropertyAccessExpression(expr)) return expr.name;
}
throw new Error(`Unable to extract identifier from declaration '${decl.getText()}'.`);
};
const getVariableDeclaration = (factoryFn: ts.FunctionExpression, name: string) =>
factoryFn.body.statements.filter(ts.isVariableStatement)
.map(stmt => stmt.declarationList.declarations[0])
.find(decl => ts.isIdentifier(decl.name) && (decl.name.text === name)) !;
it('should return the declaration of a locally defined identifier', () => {
loadTestFiles([SOME_DIRECTIVE_FILE]);
const bundle = makeTestBundleProgram(SOME_DIRECTIVE_FILE.name);
@ -1937,6 +1972,175 @@ runInEachFileSystem(() => {
expect(decl.viaModule).toEqual('sub_module');
expect(decl.node).toBe(expectedDeclaration);
});
it('should recognize TypeScript helpers (as function declarations)', () => {
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';
function __assign(t, ...sources) { /* ... */ }
function __spread(...args) { /* ... */ }
function __spreadArrays(...args) { /* ... */ }
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 = new UmdReflectionHost(new MockLogger(), false, bundle);
const {factoryFn} = parseStatementForUmdModule(
getSourceFileOrError(bundle.program, file.name).statements[0]) !;
const testForHelper = createTestForTsHelper(host, factoryFn, getFunctionDeclaration);
testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign);
testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread);
testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize suffixed TypeScript helpers (as function declarations)', () => {
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';
function __assign$1(t, ...sources) { /* ... */ }
function __spread$2(...args) { /* ... */ }
function __spreadArrays$3(...args) { /* ... */ }
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 = new UmdReflectionHost(new MockLogger(), false, bundle);
const {factoryFn} = parseStatementForUmdModule(
getSourceFileOrError(bundle.program, file.name).statements[0]) !;
const testForHelper = createTestForTsHelper(host, factoryFn, getFunctionDeclaration);
testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign);
testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread);
testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize TypeScript helpers (as variable declarations)', () => {
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 __assign = (this && this.__assign) || function (t, ...sources) { /* ... */ }
var __spread = (this && this.__spread) || function (...args) { /* ... */ }
var __spreadArrays = (this && this.__spreadArrays) || function (...args) { /* ... */ }
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 = new UmdReflectionHost(new MockLogger(), false, bundle);
const {factoryFn} = parseStatementForUmdModule(
getSourceFileOrError(bundle.program, file.name).statements[0]) !;
const testForHelper = createTestForTsHelper(host, factoryFn, getVariableDeclaration);
testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign);
testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread);
testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize suffixed TypeScript helpers (as variable declarations)', () => {
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 __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 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 = new UmdReflectionHost(new MockLogger(), false, bundle);
const {factoryFn} = parseStatementForUmdModule(
getSourceFileOrError(bundle.program, file.name).statements[0]) !;
const testForHelper = createTestForTsHelper(host, factoryFn, getVariableDeclaration);
testForHelper('a', '__assign$1', KnownDeclaration.TsHelperAssign);
testForHelper('b', '__spread$2', KnownDeclaration.TsHelperSpread);
testForHelper('c', '__spreadArrays$3', KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize imported TypeScript helpers', () => {
const files: TestFile[] = [
{
name: _('/test.js'),
contents: `
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tslib')) :
typeof define === 'function' && define.amd ? define('test', ['exports', 'tslib'], factory) :
(factory(global.test, global.tslib));
}(this, (function (exports, tslib_1) { 'use strict';
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']);
})));
`,
},
{
name: _('/node_modules/tslib/index.d.ts'),
contents: `
export declare function __assign(t: any, ...sources: any[]): any;
export declare function __spread(...args: any[][]): any[];
export declare function __spreadArrays(...args: any[][]): any[];
`,
},
];
loadTestFiles(files);
const [testFile, tslibFile] = files;
const bundle = makeTestBundleProgram(testFile.name);
const host = new UmdReflectionHost(new MockLogger(), false, bundle);
const {factoryFn} = parseStatementForUmdModule(
getSourceFileOrError(bundle.program, testFile.name).statements[0]) !;
const tslibSourceFile = getSourceFileOrError(bundle.program, tslibFile.name);
const testForHelper = createTestForTsHelper(host, factoryFn, () => tslibSourceFile);
testForHelper('a', '__assign', KnownDeclaration.TsHelperAssign, 'tslib');
testForHelper('b', '__spread', KnownDeclaration.TsHelperSpread, 'tslib');
testForHelper('c', '__spreadArrays', KnownDeclaration.TsHelperSpreadArrays, 'tslib');
});
});
describe('getExportsOfModule()', () => {
@ -2038,6 +2242,31 @@ runInEachFileSystem(() => {
expect(decl.node).toBeNull();
expect(decl.expression).toBeDefined();
});
it('should recognize declarations of known TypeScript helpers', () => {
const tslib = {
name: _('/tslib.d.ts'),
contents: `
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 __unknownHelper(...args: any[]): any;
`,
};
loadTestFiles([tslib]);
const bundle = makeTestBundleProgram(tslib.name);
const host = new UmdReflectionHost(new MockLogger(), false, bundle);
const sf = getSourceFileOrError(bundle.program, tslib.name);
const exportDeclarations = host.getExportsOfModule(sf) !;
expect([...exportDeclarations].map(([exportName, {known}]) => [exportName, known]))
.toEqual([
['__assign', KnownDeclaration.TsHelperAssign],
['__spread', KnownDeclaration.TsHelperSpread],
['__spreadArrays', KnownDeclaration.TsHelperSpreadArrays],
['__unknownHelper', null],
]);
});
});
describe('getClassSymbol()', () => {

View File

@ -147,46 +147,79 @@ runInEachFileSystem(() => {
});
['esm5', 'esm2015'].forEach(target => {
it(`should be able to process spread operator inside objects for ${target} format`, () => {
compileIntoApf(
'test-package', {
'/index.ts': `
import {Directive, Input, NgModule} from '@angular/core';
it(`should be able to process spread operator inside objects for ${target} format (imported helpers)`,
() => {
compileIntoApf(
'test-package', {
'/index.ts': `
import {Directive, Input, NgModule} from '@angular/core';
const a = { '[class.a]': 'true' };
const b = { '[class.b]': 'true' };
const a = { '[class.a]': 'true' };
const b = { '[class.b]': 'true' };
@Directive({
selector: '[foo]',
host: {...a, ...b, '[class.c]': 'false'}
})
export class FooDirective {}
@Directive({
selector: '[foo]',
host: {...a, ...b, '[class.c]': 'false'}
})
export class FooDirective {}
@NgModule({
declarations: [FooDirective],
})
export class FooModule {}
`,
},
{importHelpers: true});
@NgModule({
declarations: [FooDirective],
})
export class FooModule {}
`,
},
{importHelpers: true, noEmitHelpers: true});
// TODO: add test with import helpers disabled. This currently won't work because
// inlined TS helper functions are not detected. For more details, see PR:
// https://github.com/angular/angular/pull/34169
fs.writeFile(
_('/node_modules/tslib/index.d.ts'),
`export declare function __assign(...args: object[]): object;`);
fs.writeFile(
_('/node_modules/tslib/index.d.ts'),
`export declare function __assign(...args: object[]): object;`);
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'test-package',
propertiesToConsider: [target],
});
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'test-package',
propertiesToConsider: [target],
});
const jsContents = fs.readFile(_(`/node_modules/test-package/${target}/src/index.js`))
.replace(/\s+/g, ' ');
expect(jsContents).toContain('ngcc0.ɵɵclassProp("a", true)("b", true)("c", false)');
});
const jsContents = fs.readFile(_(`/node_modules/test-package/${target}/src/index.js`))
.replace(/\s+/g, ' ');
expect(jsContents).toContain('ngcc0.ɵɵclassProp("a", true)("b", true)("c", false)');
});
it(`should be able to process emitted spread operator inside objects for ${target} format (emitted helpers)`,
() => {
compileIntoApf(
'test-package', {
'/index.ts': `
import {Directive, Input, NgModule} from '@angular/core';
const a = { '[class.a]': 'true' };
const b = { '[class.b]': 'true' };
@Directive({
selector: '[foo]',
host: {...a, ...b, '[class.c]': 'false'}
})
export class FooDirective {}
@NgModule({
declarations: [FooDirective],
})
export class FooModule {}
`,
},
{importHelpers: false, noEmitHelpers: false});
mainNgcc({
basePath: '/node_modules',
targetEntryPointPath: 'test-package',
propertiesToConsider: [target],
});
const jsContents = fs.readFile(_(`/node_modules/test-package/${target}/src/index.js`))
.replace(/\s+/g, ' ');
expect(jsContents).toContain('ngcc0.ɵɵclassProp("a", true)("b", true)("c", false)');
});
});
it('should not add `const` in ES5 generated code', () => {

View File

@ -6,7 +6,9 @@
* found in the LICENSE file at https://angular.io/license
*/
import {FactoryMap, isRelativePath, stripExtension} from '../src/utils';
import * as ts from 'typescript';
import {KnownDeclaration} from '../../src/ngtsc/reflection';
import {FactoryMap, getTsHelperFnFromDeclaration, getTsHelperFnFromIdentifier, isRelativePath, stripExtension} from '../src/utils';
describe('FactoryMap', () => {
it('should return an existing value', () => {
@ -50,6 +52,121 @@ describe('FactoryMap', () => {
});
});
describe('getTsHelperFnFromDeclaration()', () => {
const createFunctionDeclaration = (fnName?: string) => ts.createFunctionDeclaration(
undefined, undefined, undefined, fnName, undefined, [], undefined, undefined);
const createVariableDeclaration = (varName: string) =>
ts.createVariableDeclaration(varName, undefined, undefined);
it('should recognize the `__assign` helper as function declaration', () => {
const decl1 = createFunctionDeclaration('__assign');
const decl2 = createFunctionDeclaration('__assign$42');
expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperAssign);
expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperAssign);
});
it('should recognize the `__assign` helper as variable declaration', () => {
const decl1 = createVariableDeclaration('__assign');
const decl2 = createVariableDeclaration('__assign$42');
expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperAssign);
expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperAssign);
});
it('should recognize the `__spread` helper as function declaration', () => {
const decl1 = createFunctionDeclaration('__spread');
const decl2 = createFunctionDeclaration('__spread$42');
expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperSpread);
expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperSpread);
});
it('should recognize the `__spread` helper as variable declaration', () => {
const decl1 = createVariableDeclaration('__spread');
const decl2 = createVariableDeclaration('__spread$42');
expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperSpread);
expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperSpread);
});
it('should recognize the `__spreadArrays` helper as function declaration', () => {
const decl1 = createFunctionDeclaration('__spreadArrays');
const decl2 = createFunctionDeclaration('__spreadArrays$42');
expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperSpreadArrays);
expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperSpreadArrays);
});
it('should recognize the `__spreadArrays` helper as variable declaration', () => {
const decl1 = createVariableDeclaration('__spreadArrays');
const decl2 = createVariableDeclaration('__spreadArrays$42');
expect(getTsHelperFnFromDeclaration(decl1)).toBe(KnownDeclaration.TsHelperSpreadArrays);
expect(getTsHelperFnFromDeclaration(decl2)).toBe(KnownDeclaration.TsHelperSpreadArrays);
});
it('should return null for unrecognized helpers', () => {
const decl1 = createFunctionDeclaration('__foo');
const decl2 = createVariableDeclaration('spread');
const decl3 = createFunctionDeclaration('spread$42');
expect(getTsHelperFnFromDeclaration(decl1)).toBe(null);
expect(getTsHelperFnFromDeclaration(decl2)).toBe(null);
expect(getTsHelperFnFromDeclaration(decl3)).toBe(null);
});
it('should return null for unnamed declarations', () => {
const unnamledDecl = createFunctionDeclaration(undefined);
expect(getTsHelperFnFromDeclaration(unnamledDecl)).toBe(null);
});
it('should return null for non-function/variable declarations', () => {
const classDecl =
ts.createClassDeclaration(undefined, undefined, '__assign', undefined, undefined, []);
expect(classDecl.name !.text).toBe('__assign');
expect(getTsHelperFnFromDeclaration(classDecl)).toBe(null);
});
});
describe('getTsHelperFnFromIdentifier()', () => {
it('should recognize the `__assign` helper', () => {
const id1 = ts.createIdentifier('__assign');
const id2 = ts.createIdentifier('__assign$42');
expect(getTsHelperFnFromIdentifier(id1)).toBe(KnownDeclaration.TsHelperAssign);
expect(getTsHelperFnFromIdentifier(id2)).toBe(KnownDeclaration.TsHelperAssign);
});
it('should recognize the `__spread` helper', () => {
const id1 = ts.createIdentifier('__spread');
const id2 = ts.createIdentifier('__spread$42');
expect(getTsHelperFnFromIdentifier(id1)).toBe(KnownDeclaration.TsHelperSpread);
expect(getTsHelperFnFromIdentifier(id2)).toBe(KnownDeclaration.TsHelperSpread);
});
it('should recognize the `__spreadArrays` helper', () => {
const id1 = ts.createIdentifier('__spreadArrays');
const id2 = ts.createIdentifier('__spreadArrays$42');
expect(getTsHelperFnFromIdentifier(id1)).toBe(KnownDeclaration.TsHelperSpreadArrays);
expect(getTsHelperFnFromIdentifier(id2)).toBe(KnownDeclaration.TsHelperSpreadArrays);
});
it('should return null for unrecognized helpers', () => {
const id1 = ts.createIdentifier('__foo');
const id2 = ts.createIdentifier('spread');
const id3 = ts.createIdentifier('spread$42');
expect(getTsHelperFnFromIdentifier(id1)).toBe(null);
expect(getTsHelperFnFromIdentifier(id2)).toBe(null);
expect(getTsHelperFnFromIdentifier(id3)).toBe(null);
});
});
describe('isRelativePath()', () => {
it('should return true for relative paths', () => {
expect(isRelativePath('.')).toBe(true);

View File

@ -19,7 +19,6 @@ import {DynamicValue} from './dynamic';
import {ForeignFunctionResolver} from './interface';
import {resolveKnownDeclaration} from './known_declaration';
import {EnumValue, KnownFn, ResolvedModule, ResolvedValue, ResolvedValueArray, ResolvedValueMap} from './result';
import {evaluateTsHelperInline} from './ts_helpers';
@ -333,6 +332,10 @@ export class StaticInterpreter {
}
return new ResolvedModule(declarations, decl => {
if (decl.known !== null) {
return resolveKnownDeclaration(decl.known);
}
const declContext = {
...context, ...joinModuleContext(context, node, decl),
};
@ -417,12 +420,6 @@ export class StaticInterpreter {
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);
}

View File

@ -10,19 +10,31 @@ import {KnownDeclaration} from '../../reflection/src/host';
import {ObjectAssignBuiltinFn} from './builtin';
import {ResolvedValue} from './result';
import {AssignHelperFn, SpreadHelperFn} from './ts_helpers';
/** Resolved value for the JavaScript global `Object` declaration .*/
/** Resolved value for the JavaScript global `Object` declaration. */
export const jsGlobalObjectValue = new Map([['assign', new ObjectAssignBuiltinFn()]]);
/** Resolved value for the `__assign()` TypeScript helper declaration. */
const assignTsHelperFn = new AssignHelperFn();
/** Resolved value for the `__spread()` and `__spreadArrays()` TypeScript helper declarations. */
const spreadTsHelperFn = new SpreadHelperFn();
/**
* Resolves the specified known declaration to a resolved value. For example,
* the known JavaScript global `Object` will resolve to a `Map` that provides the
* `assign` method with a builtin function. This enables evaluation of `Object.assign`.
* `assign` method with a built-in function. This enables evaluation of `Object.assign`.
*/
export function resolveKnownDeclaration(decl: KnownDeclaration): ResolvedValue {
switch (decl) {
case KnownDeclaration.JsGlobalObject:
return jsGlobalObjectValue;
case KnownDeclaration.TsHelperAssign:
return assignTsHelperFn;
case KnownDeclaration.TsHelperSpread:
case KnownDeclaration.TsHelperSpreadArrays:
return spreadTsHelperFn;
default:
throw new Error(`Cannot resolve known declaration. Received: ${KnownDeclaration[decl]}.`);
}

View File

@ -8,44 +8,30 @@
import * as ts from 'typescript';
import {TsHelperFn} from '../../reflection';
import {ObjectAssignBuiltinFn} from './builtin';
import {DynamicValue} from './dynamic';
import {ResolvedValue, ResolvedValueArray} from './result';
import {KnownFn, ResolvedValueArray} from './result';
/**
* Instance of the known `Object.assign` built-in function. Used for evaluating
* the `__assign` TypeScript helper.
*/
const objectAssignBuiltinFn = new ObjectAssignBuiltinFn();
// Use the same implementation we use for `Object.assign()`. Semantically these functions are the
// same, so they can also share the same evaluation code.
export class AssignHelperFn extends ObjectAssignBuiltinFn {}
export function evaluateTsHelperInline(
helper: TsHelperFn, node: ts.CallExpression, args: ResolvedValueArray): ResolvedValue {
switch (helper) {
case TsHelperFn.Assign:
// Use the same implementation we use for `Object.assign`. Semantically these
// functions are the same, so they can also share the same evaluation code.
return objectAssignBuiltinFn.evaluate(node, args);
case TsHelperFn.Spread:
case TsHelperFn.SpreadArrays:
return evaluateTsSpreadHelper(node, args);
default:
throw new Error(`Cannot evaluate TypeScript helper function: ${TsHelperFn[helper]}`);
}
}
// Used for both `__spread()` and `__spreadArrays()` TypeScript helper functions.
export class SpreadHelperFn extends KnownFn {
evaluate(node: ts.Node, args: ResolvedValueArray): ResolvedValueArray {
const result: ResolvedValueArray = [];
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);
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;
}
return result;
}

View File

@ -11,11 +11,11 @@ import {absoluteFrom, getSourceFileOrError} from '../../file_system';
import {runInEachFileSystem} from '../../file_system/testing';
import {Reference} from '../../imports';
import {DependencyTracker} from '../../incremental/api';
import {FunctionDefinition, TsHelperFn, TypeScriptReflectionHost} from '../../reflection';
import {Declaration, KnownDeclaration, TypeScriptReflectionHost} from '../../reflection';
import {getDeclaration, makeProgram} from '../../testing';
import {DynamicValue} from '../src/dynamic';
import {PartialEvaluator} from '../src/interface';
import {EnumValue} from '../src/result';
import {EnumValue, ResolvedValue} from '../src/result';
import {evaluate, firstArgFfr, makeEvaluator, makeExpression, owningModuleOf} from './utils';
@ -538,69 +538,209 @@ runInEachFileSystem(() => {
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, null);
const value = evaluator.evaluate(expression);
expect(value).toEqual([1, 2, 3]);
describe('(with imported TypeScript helpers)', () => {
// Helpers
const evaluateExpression = <T extends ResolvedValue>(code: string, expr: string) => {
const {checker, expression} = makeExpression(code, expr, [
{
name: _('/node_modules/tslib/index.d.ts'),
contents: `
export declare function __assign(t: any, ...sources: any[]): any;
export declare function __spread(...args: any[][]): any[];
export declare function __spreadArrays(...args: any[][]): any[];
`,
},
]);
const reflectionHost = new TsLibAwareReflectionHost(checker);
const evaluator = new PartialEvaluator(reflectionHost, checker, null);
return evaluator.evaluate(expression) as T;
};
it('should evaluate `__assign()` (named import)', () => {
const map: Map<string, boolean> = evaluateExpression(
`
import {__assign} from 'tslib';
const a = {a: true};
const b = {b: true};
`,
'__assign(a, b)');
expect([...map]).toEqual([
['a', true],
['b', true],
]);
});
it('should evaluate `__assign()` (star import)', () => {
const map: Map<string, boolean> = evaluateExpression(
`
import * as tslib from 'tslib';
const a = {a: true};
const b = {b: true};
`,
'tslib.__assign(a, b)');
expect([...map]).toEqual([
['a', true],
['b', true],
]);
});
it('should evaluate `__spread()` (named import)', () => {
const arr: number[] = evaluateExpression(
`
import {__spread} from 'tslib';
const a = [1];
const b = [2, 3];
`,
'__spread(a, b)');
expect(arr).toEqual([1, 2, 3]);
});
it('should evaluate `__spread()` (star import)', () => {
const arr: number[] = evaluateExpression(
`
import * as tslib from 'tslib';
const a = [1];
const b = [2, 3];
`,
'tslib.__spread(a, b)');
expect(arr).toEqual([1, 2, 3]);
});
it('should evaluate `__spreadArrays()` (named import)', () => {
const arr: number[] = evaluateExpression(
`
import {__spreadArrays} from 'tslib';
const a = [4];
const b = [5, 6];
`,
'__spreadArrays(a, b)');
expect(arr).toEqual([4, 5, 6]);
});
it('should evaluate `__spreadArrays()` (star import)', () => {
const arr: number[] = evaluateExpression(
`
import * as tslib from 'tslib';
const a = [4];
const b = [5, 6];
`,
'tslib.__spreadArrays(a, b)');
expect(arr).toEqual([4, 5, 6]);
});
});
it('should evaluate TypeScript __spreadArrays helper', () => {
const {checker, expression} = makeExpression(
`
import * as tslib from 'tslib';
const a = [1];
const b = [2, 3];
`,
'tslib.__spreadArrays(a, b)', [
{
name: _('/node_modules/tslib/index.d.ts'),
contents: `
export declare function __spreadArrays(...args: any[][]): any[];
`
},
]);
const reflectionHost = new TsLibAwareReflectionHost(checker);
const evaluator = new PartialEvaluator(reflectionHost, checker, null);
const value = evaluator.evaluate(expression);
expect(value).toEqual([1, 2, 3]);
describe('(with emitted TypeScript helpers as functions)', () => {
// Helpers
const evaluateExpression = <T extends ResolvedValue>(code: string, expr: string) => {
const helpers = `
function __assign(t, ...sources) { /* ... */ }
function __spread(...args) { /* ... */ }
function __spreadArrays(...args) { /* ... */ }
`;
const {checker, expression} = makeExpression(helpers + code, expr);
const reflectionHost = new TsLibAwareReflectionHost(checker);
const evaluator = new PartialEvaluator(reflectionHost, checker, null);
return evaluator.evaluate(expression) as T;
};
it('should evaluate `__assign()`', () => {
const map: Map<string, boolean> = evaluateExpression(
`
const a = {a: true};
const b = {b: true};
`,
'__assign(a, b)');
expect([...map]).toEqual([
['a', true],
['b', true],
]);
});
it('should evaluate `__spread()`', () => {
const arr: number[] = evaluateExpression(
`
const a = [1];
const b = [2, 3];
`,
'__spread(a, b)');
expect(arr).toEqual([1, 2, 3]);
});
it('should evaluate `__spreadArrays()`', () => {
const arr: number[] = evaluateExpression(
`
const a = [4];
const b = [5, 6];
`,
'__spreadArrays(a, b)');
expect(arr).toEqual([4, 5, 6]);
});
});
it('should evaluate TypeScript __assign helper', () => {
const {checker, expression} = makeExpression(
`
import * as tslib from 'tslib';
const a = {a: true};
const b = {b: true};
`,
'tslib.__assign(a, b)', [
{
name: _('/node_modules/tslib/index.d.ts'),
contents: `
export declare function __assign(...args: object[]): object;
`
},
]);
const reflectionHost = new TsLibAwareReflectionHost(checker);
const evaluator = new PartialEvaluator(reflectionHost, checker, null);
const map = evaluator.evaluate(expression) as Map<string, boolean>;
const obj: {[key: string]: boolean} = {};
map.forEach((value, key) => obj[key] = value);
expect(obj).toEqual({a: true, b: true});
describe('(with emitted TypeScript helpers as variables)', () => {
// Helpers
const evaluateExpression = <T extends ResolvedValue>(code: string, expr: string) => {
const helpers = `
var __assign = (this && this.__assign) || function (t, ...sources) { /* ... */ }
var __spread = (this && this.__spread) || function (...args) { /* ... */ }
var __spreadArrays = (this && this.__spreadArrays) || function (...args) { /* ... */ }
`;
const {checker, expression} = makeExpression(helpers + code, expr);
const reflectionHost = new TsLibAwareReflectionHost(checker);
const evaluator = new PartialEvaluator(reflectionHost, checker, null);
return evaluator.evaluate(expression) as T;
};
it('should evaluate `__assign()`', () => {
const map: Map<string, boolean> = evaluateExpression(
`
const a = {a: true};
const b = {b: true};
`,
'__assign(a, b)');
expect([...map]).toEqual([
['a', true],
['b', true],
]);
});
it('should evaluate `__spread()`', () => {
const arr: number[] = evaluateExpression(
`
const a = [1];
const b = [2, 3];
`,
'__spread(a, b)');
expect(arr).toEqual([1, 2, 3]);
});
it('should evaluate `__spreadArrays()`', () => {
const arr: number[] = evaluateExpression(
`
const a = [4];
const b = [5, 6];
`,
'__spreadArrays(a, b)');
expect(arr).toEqual([4, 5, 6]);
});
});
describe('(visited file tracking)', () => {
@ -665,36 +805,52 @@ runInEachFileSystem(() => {
});
/**
* 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.
* Customizes the resolution of module exports and identifier declarations to recognize known
* helper functions from `tslib`. Such functions are not handled specially in the default
* TypeScript host, as only ngcc's ES5 hosts will have special powers to recognize such functions.
*/
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: [],
};
}
getExportsOfModule(node: ts.Node): Map<string, Declaration>|null {
const map = super.getExportsOfModule(node);
if (map !== null) {
map.forEach(decl => decl.known = decl.known || (decl.node && getTsHelperFn(decl.node)));
}
return super.getDefinitionOfFunction(node);
return map;
}
getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
const superDeclaration = super.getDeclarationOfIdentifier(id);
if (superDeclaration === null || superDeclaration.node === null) {
return superDeclaration;
}
const tsHelperFn = getTsHelperFn(superDeclaration.node);
if (tsHelperFn !== null) {
return {
known: tsHelperFn,
node: id,
viaModule: null,
};
}
return superDeclaration;
}
}
function getTsHelperFn(node: ts.FunctionDeclaration): TsHelperFn|null {
const name = node.name !== undefined && ts.isIdentifier(node.name) && node.name.text;
function getTsHelperFn(node: ts.Declaration): KnownDeclaration|null {
const id = (node as ts.Declaration & {name?: ts.Identifier}).name || null;
const name = id && id.text;
switch (name) {
case '__assign':
return TsHelperFn.Assign;
return KnownDeclaration.TsHelperAssign;
case '__spread':
return TsHelperFn.Spread;
return KnownDeclaration.TsHelperSpread;
case '__spreadArrays':
return TsHelperFn.SpreadArrays;
return KnownDeclaration.TsHelperSpreadArrays;
default:
return null;
}

View File

@ -316,12 +316,6 @@ export interface FunctionDefinition {
*/
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.
*/
@ -329,31 +323,28 @@ export interface FunctionDefinition {
}
/**
* Possible functions from TypeScript's helper library.
*/
export enum TsHelperFn {
/**
* Indicates the `__assign` function.
*/
Assign,
/**
* Indicates the `__spread` function.
*/
Spread,
/**
* Indicates the `__spreadArrays` function.
*/
SpreadArrays,
}
/**
* Possible declarations which are known.
* Possible declarations of known values, such as built-in objects/functions or TypeScript helpers.
*/
export enum KnownDeclaration {
/**
* Indicates the JavaScript global `Object` class.
*/
JsGlobalObject,
/**
* Indicates the `__assign` TypeScript helper function.
*/
TsHelperAssign,
/**
* Indicates the `__spread` TypeScript helper function.
*/
TsHelperSpread,
/**
* Indicates the `__spreadArrays` TypeScript helper function.
*/
TsHelperSpreadArrays,
}
/**

View File

@ -164,7 +164,6 @@ export class TypeScriptReflectionHost implements ReflectionHost {
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;
@ -266,10 +265,8 @@ export class TypeScriptReflectionHost implements ReflectionHost {
/**
* Resolve a `ts.Symbol` to its declaration, keeping track of the `viaModule` along the way.
*
* @internal
*/
private getDeclarationOfSymbol(symbol: ts.Symbol, originalId: ts.Identifier|null): Declaration
protected getDeclarationOfSymbol(symbol: ts.Symbol, originalId: ts.Identifier|null): Declaration
|null {
// If the symbol points to a ShorthandPropertyAssignment, resolve it.
let valueDeclaration: ts.Declaration|undefined = undefined;