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

@ -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);