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

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