fix(ngcc): detect synthesized delegate constructors for downleveled ES2015 classes (#38463)
Similarly to the change we landed in the `@angular/core` reflection capabilities, we need to make sure that ngcc can detect pass-through delegate constructors for classes using downleveled ES2015 output. More details can be found in the preceding commit, and in the issue outlining the problem: #38453. Fixes #38453. PR Close #38463
This commit is contained in:
parent
ca07da4563
commit
3b9c802dee
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {ClassDeclaration, ClassMember, ClassMemberKind, Declaration, Decorator, FunctionDefinition, Parameter, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
import {ClassDeclaration, ClassMember, ClassMemberKind, Declaration, Decorator, FunctionDefinition, KnownDeclaration, Parameter, reflectObjectLiteral} from '../../../src/ngtsc/reflection';
|
||||||
import {getTsHelperFnFromDeclaration, getTsHelperFnFromIdentifier, hasNameIdentifier} from '../utils';
|
import {getTsHelperFnFromDeclaration, getTsHelperFnFromIdentifier, hasNameIdentifier} from '../utils';
|
||||||
|
|
||||||
import {Esm2015ReflectionHost, getClassDeclarationFromInnerDeclaration, getPropertyValueFromSymbol, isAssignmentStatement, ParamInfo} from './esm2015_host';
|
import {Esm2015ReflectionHost, getClassDeclarationFromInnerDeclaration, getPropertyValueFromSymbol, isAssignmentStatement, ParamInfo} from './esm2015_host';
|
||||||
|
@ -219,7 +219,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||||
return Array.from(constructor.parameters);
|
return Array.from(constructor.parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSynthesizedConstructor(constructor)) {
|
if (this.isSynthesizedConstructor(constructor)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,6 +352,219 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||||
const classDeclarationParent = classSymbol.implementation.valueDeclaration.parent;
|
const classDeclarationParent = classSymbol.implementation.valueDeclaration.parent;
|
||||||
return ts.isBlock(classDeclarationParent) ? Array.from(classDeclarationParent.statements) : [];
|
return ts.isBlock(classDeclarationParent) ? Array.from(classDeclarationParent.statements) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////// Host Private Helpers /////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* Those initializers need to be emitted into a constructor in JavaScript, so the TypeScript
|
||||||
|
* compiler generates a synthetic constructor.
|
||||||
|
*
|
||||||
|
* We need to identify such constructors as ngcc needs to be able to tell if a class did
|
||||||
|
* originally have a constructor in the TypeScript source. For ES5, we can not tell an
|
||||||
|
* empty constructor apart from a synthesized constructor, but fortunately that does not
|
||||||
|
* matter for the code generated by ngtsc.
|
||||||
|
*
|
||||||
|
* When a class has a superclass however, a synthesized constructor must not be considered
|
||||||
|
* as a user-defined constructor as that prevents a base factory call from being created by
|
||||||
|
* ngtsc, resulting in a factory function that does not inject the dependencies of the
|
||||||
|
* superclass. Hence, we identify a default synthesized super call in the constructor body,
|
||||||
|
* according to the structure that TypeScript's ES2015 to ES5 transformer generates in
|
||||||
|
* https://github.com/Microsoft/TypeScript/blob/v3.2.2/src/compiler/transformers/es2015.ts#L1082-L1098
|
||||||
|
*
|
||||||
|
* Additionally, we handle synthetic delegate constructors that are emitted when TypeScript
|
||||||
|
* downlevel's ES2015 synthetically generated to ES5. These vary slightly from the default
|
||||||
|
* structure mentioned above because the ES2015 output uses a spread operator, for delegating
|
||||||
|
* to the parent constructor, that is preserved through a TypeScript helper in ES5. e.g.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* return _super.apply(this, tslib.__spread(arguments)) || this;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Such constructs can be still considered as synthetic delegate constructors as they are
|
||||||
|
* the product of a common TypeScript to ES5 synthetic constructor, just being downleveled
|
||||||
|
* to ES5 using `tsc`. See: https://github.com/angular/angular/issues/38453.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param constructor a constructor function to test
|
||||||
|
* @returns true if the constructor appears to have been synthesized
|
||||||
|
*/
|
||||||
|
private isSynthesizedConstructor(constructor: ts.FunctionDeclaration): boolean {
|
||||||
|
if (!constructor.body) return false;
|
||||||
|
|
||||||
|
const firstStatement = constructor.body.statements[0];
|
||||||
|
if (!firstStatement) return false;
|
||||||
|
|
||||||
|
return this.isSynthesizedSuperThisAssignment(firstStatement) ||
|
||||||
|
this.isSynthesizedSuperReturnStatement(firstStatement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies synthesized super calls which pass-through function arguments directly and are
|
||||||
|
* being assigned to a common `_this` variable. The following patterns we intend to match:
|
||||||
|
*
|
||||||
|
* 1. Delegate call emitted by TypeScript when it emits ES5 directly.
|
||||||
|
* ```
|
||||||
|
* var _this = _super !== null && _super.apply(this, arguments) || this;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 2. Delegate call emitted by TypeScript when it downlevel's ES2015 to ES5.
|
||||||
|
* ```
|
||||||
|
* var _this = _super.apply(this, tslib.__spread(arguments)) || this;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param statement a statement that may be a synthesized super call
|
||||||
|
* @returns true if the statement looks like a synthesized super call
|
||||||
|
*/
|
||||||
|
private isSynthesizedSuperThisAssignment(statement: ts.Statement): boolean {
|
||||||
|
if (!ts.isVariableStatement(statement)) return false;
|
||||||
|
|
||||||
|
const variableDeclarations = statement.declarationList.declarations;
|
||||||
|
if (variableDeclarations.length !== 1) return false;
|
||||||
|
|
||||||
|
const variableDeclaration = variableDeclarations[0];
|
||||||
|
if (!ts.isIdentifier(variableDeclaration.name) ||
|
||||||
|
!variableDeclaration.name.text.startsWith('_this'))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const initializer = variableDeclaration.initializer;
|
||||||
|
if (!initializer) return false;
|
||||||
|
|
||||||
|
return this.isSynthesizedDefaultSuperCall(initializer);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Identifies synthesized super calls which pass-through function arguments directly and
|
||||||
|
* are being returned. The following patterns correspond to synthetic super return calls:
|
||||||
|
*
|
||||||
|
* 1. Delegate call emitted by TypeScript when it emits ES5 directly.
|
||||||
|
* ```
|
||||||
|
* return _super !== null && _super.apply(this, arguments) || this;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 2. Delegate call emitted by TypeScript when it downlevel's ES2015 to ES5.
|
||||||
|
* ```
|
||||||
|
* return _super.apply(this, tslib.__spread(arguments)) || this;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param statement a statement that may be a synthesized super call
|
||||||
|
* @returns true if the statement looks like a synthesized super call
|
||||||
|
*/
|
||||||
|
private isSynthesizedSuperReturnStatement(statement: ts.Statement): boolean {
|
||||||
|
if (!ts.isReturnStatement(statement)) return false;
|
||||||
|
|
||||||
|
const expression = statement.expression;
|
||||||
|
if (!expression) return false;
|
||||||
|
|
||||||
|
return this.isSynthesizedDefaultSuperCall(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies synthesized super calls which pass-through function arguments directly. The
|
||||||
|
* synthetic delegate super call match the following patterns we intend to match:
|
||||||
|
*
|
||||||
|
* 1. Delegate call emitted by TypeScript when it emits ES5 directly.
|
||||||
|
* ```
|
||||||
|
* _super !== null && _super.apply(this, arguments) || this;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* 2. Delegate call emitted by TypeScript when it downlevel's ES2015 to ES5.
|
||||||
|
* ```
|
||||||
|
* _super.apply(this, tslib.__spread(arguments)) || this;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param expression an expression that may represent a default super call
|
||||||
|
* @returns true if the expression corresponds with the above form
|
||||||
|
*/
|
||||||
|
private isSynthesizedDefaultSuperCall(expression: ts.Expression): boolean {
|
||||||
|
if (!isBinaryExpr(expression, ts.SyntaxKind.BarBarToken)) return false;
|
||||||
|
if (expression.right.kind !== ts.SyntaxKind.ThisKeyword) return false;
|
||||||
|
|
||||||
|
const left = expression.left;
|
||||||
|
if (isBinaryExpr(left, ts.SyntaxKind.AmpersandAmpersandToken)) {
|
||||||
|
return isSuperNotNull(left.left) && this.isSuperApplyCall(left.right);
|
||||||
|
} else {
|
||||||
|
return this.isSuperApplyCall(left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether the expression corresponds to a `super` call passing through
|
||||||
|
* function arguments without any modification. e.g.
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* _super !== null && _super.apply(this, arguments) || this;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This structure is generated by TypeScript when transforming ES2015 to ES5, see
|
||||||
|
* https://github.com/Microsoft/TypeScript/blob/v3.2.2/src/compiler/transformers/es2015.ts#L1148-L1163
|
||||||
|
*
|
||||||
|
* Additionally, we also handle cases where `arguments` are wrapped by a TypeScript spread helper.
|
||||||
|
* This can happen if ES2015 class output contain auto-generated constructors due to class
|
||||||
|
* members. The ES2015 output will be using `super(...arguments)` to delegate to the superclass,
|
||||||
|
* but once downleveled to ES5, the spread operator will be persisted through a TypeScript spread
|
||||||
|
* helper. For example:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* _super.apply(this, __spread(arguments)) || this;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* More details can be found in: https://github.com/angular/angular/issues/38453.
|
||||||
|
*
|
||||||
|
* @param expression an expression that may represent a default super call
|
||||||
|
* @returns true if the expression corresponds with the above form
|
||||||
|
*/
|
||||||
|
private isSuperApplyCall(expression: ts.Expression): boolean {
|
||||||
|
if (!ts.isCallExpression(expression) || expression.arguments.length !== 2) return false;
|
||||||
|
|
||||||
|
const targetFn = expression.expression;
|
||||||
|
if (!ts.isPropertyAccessExpression(targetFn)) return false;
|
||||||
|
if (!isSuperIdentifier(targetFn.expression)) return false;
|
||||||
|
if (targetFn.name.text !== 'apply') return false;
|
||||||
|
|
||||||
|
const thisArgument = expression.arguments[0];
|
||||||
|
if (thisArgument.kind !== ts.SyntaxKind.ThisKeyword) return false;
|
||||||
|
|
||||||
|
const argumentsExpr = expression.arguments[1];
|
||||||
|
|
||||||
|
// If the super is directly invoked with `arguments`, return `true`. This represents the
|
||||||
|
// common TypeScript output where the delegate constructor super call matches the following
|
||||||
|
// pattern: `super.apply(this, arguments)`.
|
||||||
|
if (isArgumentsIdentifier(argumentsExpr)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The other scenario we intend to detect: The `arguments` variable might be wrapped with the
|
||||||
|
// TypeScript spread helper (either through tslib or inlined). This can happen if an explicit
|
||||||
|
// delegate constructor uses `super(...arguments)` in ES2015 and is downleveled to ES5 using
|
||||||
|
// `--downlevelIteration`. The output in such cases would not directly pass the function
|
||||||
|
// `arguments` to the `super` call, but wrap it in a TS spread helper. The output would match
|
||||||
|
// the following pattern: `super.apply(this, tslib.__spread(arguments))`. We check for such
|
||||||
|
// constructs below, but perform the detection of the call expression definition as last as
|
||||||
|
// that is the most expensive operation here.
|
||||||
|
if (!ts.isCallExpression(argumentsExpr) || argumentsExpr.arguments.length !== 1 ||
|
||||||
|
!isArgumentsIdentifier(argumentsExpr.arguments[0])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const argumentsCallExpr = argumentsExpr.expression;
|
||||||
|
let argumentsCallDeclaration: Declaration|null = null;
|
||||||
|
|
||||||
|
// The `__spread` helper could be globally available, or accessed through a namespaced
|
||||||
|
// import. Hence we support a property access here as long as it resolves to the actual
|
||||||
|
// known TypeScript spread helper.
|
||||||
|
if (ts.isIdentifier(argumentsCallExpr)) {
|
||||||
|
argumentsCallDeclaration = this.getDeclarationOfIdentifier(argumentsCallExpr);
|
||||||
|
} else if (
|
||||||
|
ts.isPropertyAccessExpression(argumentsCallExpr) &&
|
||||||
|
ts.isIdentifier(argumentsCallExpr.name)) {
|
||||||
|
argumentsCallDeclaration = this.getDeclarationOfIdentifier(argumentsCallExpr.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return argumentsCallDeclaration !== null &&
|
||||||
|
argumentsCallDeclaration.known === KnownDeclaration.TsHelperSpread;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////// Internal Helpers /////////////
|
///////////// Internal Helpers /////////////
|
||||||
|
@ -422,103 +635,8 @@ function reflectArrayElement(element: ts.Expression) {
|
||||||
return ts.isObjectLiteralExpression(element) ? reflectObjectLiteral(element) : null;
|
return ts.isObjectLiteralExpression(element) ? reflectObjectLiteral(element) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function isArgumentsIdentifier(expression: ts.Expression): boolean {
|
||||||
* A constructor function may have been "synthesized" by TypeScript during JavaScript emit,
|
return ts.isIdentifier(expression) && expression.text === 'arguments';
|
||||||
* in the case no user-defined constructor exists and e.g. property initializers are used.
|
|
||||||
* Those initializers need to be emitted into a constructor in JavaScript, so the TypeScript
|
|
||||||
* compiler generates a synthetic constructor.
|
|
||||||
*
|
|
||||||
* We need to identify such constructors as ngcc needs to be able to tell if a class did
|
|
||||||
* originally have a constructor in the TypeScript source. For ES5, we can not tell an
|
|
||||||
* empty constructor apart from a synthesized constructor, but fortunately that does not
|
|
||||||
* matter for the code generated by ngtsc.
|
|
||||||
*
|
|
||||||
* When a class has a superclass however, a synthesized constructor must not be considered
|
|
||||||
* as a user-defined constructor as that prevents a base factory call from being created by
|
|
||||||
* ngtsc, resulting in a factory function that does not inject the dependencies of the
|
|
||||||
* superclass. Hence, we identify a default synthesized super call in the constructor body,
|
|
||||||
* according to the structure that TypeScript's ES2015 to ES5 transformer generates in
|
|
||||||
* https://github.com/Microsoft/TypeScript/blob/v3.2.2/src/compiler/transformers/es2015.ts#L1082-L1098
|
|
||||||
*
|
|
||||||
* @param constructor a constructor function to test
|
|
||||||
* @returns true if the constructor appears to have been synthesized
|
|
||||||
*/
|
|
||||||
function isSynthesizedConstructor(constructor: ts.FunctionDeclaration): boolean {
|
|
||||||
if (!constructor.body) return false;
|
|
||||||
|
|
||||||
const firstStatement = constructor.body.statements[0];
|
|
||||||
if (!firstStatement) return false;
|
|
||||||
|
|
||||||
return isSynthesizedSuperThisAssignment(firstStatement) ||
|
|
||||||
isSynthesizedSuperReturnStatement(firstStatement);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Identifies a synthesized super call of the form:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* var _this = _super !== null && _super.apply(this, arguments) || this;
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param statement a statement that may be a synthesized super call
|
|
||||||
* @returns true if the statement looks like a synthesized super call
|
|
||||||
*/
|
|
||||||
function isSynthesizedSuperThisAssignment(statement: ts.Statement): boolean {
|
|
||||||
if (!ts.isVariableStatement(statement)) return false;
|
|
||||||
|
|
||||||
const variableDeclarations = statement.declarationList.declarations;
|
|
||||||
if (variableDeclarations.length !== 1) return false;
|
|
||||||
|
|
||||||
const variableDeclaration = variableDeclarations[0];
|
|
||||||
if (!ts.isIdentifier(variableDeclaration.name) ||
|
|
||||||
!variableDeclaration.name.text.startsWith('_this'))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const initializer = variableDeclaration.initializer;
|
|
||||||
if (!initializer) return false;
|
|
||||||
|
|
||||||
return isSynthesizedDefaultSuperCall(initializer);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Identifies a synthesized super call of the form:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* return _super !== null && _super.apply(this, arguments) || this;
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param statement a statement that may be a synthesized super call
|
|
||||||
* @returns true if the statement looks like a synthesized super call
|
|
||||||
*/
|
|
||||||
function isSynthesizedSuperReturnStatement(statement: ts.Statement): boolean {
|
|
||||||
if (!ts.isReturnStatement(statement)) return false;
|
|
||||||
|
|
||||||
const expression = statement.expression;
|
|
||||||
if (!expression) return false;
|
|
||||||
|
|
||||||
return isSynthesizedDefaultSuperCall(expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests whether the expression is of the form:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* _super !== null && _super.apply(this, arguments) || this;
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* This structure is generated by TypeScript when transforming ES2015 to ES5, see
|
|
||||||
* https://github.com/Microsoft/TypeScript/blob/v3.2.2/src/compiler/transformers/es2015.ts#L1148-L1163
|
|
||||||
*
|
|
||||||
* @param expression an expression that may represent a default super call
|
|
||||||
* @returns true if the expression corresponds with the above form
|
|
||||||
*/
|
|
||||||
function isSynthesizedDefaultSuperCall(expression: ts.Expression): boolean {
|
|
||||||
if (!isBinaryExpr(expression, ts.SyntaxKind.BarBarToken)) return false;
|
|
||||||
if (expression.right.kind !== ts.SyntaxKind.ThisKeyword) return false;
|
|
||||||
|
|
||||||
const left = expression.left;
|
|
||||||
if (!isBinaryExpr(left, ts.SyntaxKind.AmpersandAmpersandToken)) return false;
|
|
||||||
|
|
||||||
return isSuperNotNull(left.left) && isSuperApplyCall(left.right);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSuperNotNull(expression: ts.Expression): boolean {
|
function isSuperNotNull(expression: ts.Expression): boolean {
|
||||||
|
@ -526,31 +644,6 @@ function isSuperNotNull(expression: ts.Expression): boolean {
|
||||||
isSuperIdentifier(expression.left);
|
isSuperIdentifier(expression.left);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests whether the expression is of the form
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* _super.apply(this, arguments)
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @param expression an expression that may represent a default super call
|
|
||||||
* @returns true if the expression corresponds with the above form
|
|
||||||
*/
|
|
||||||
function isSuperApplyCall(expression: ts.Expression): boolean {
|
|
||||||
if (!ts.isCallExpression(expression) || expression.arguments.length !== 2) return false;
|
|
||||||
|
|
||||||
const targetFn = expression.expression;
|
|
||||||
if (!ts.isPropertyAccessExpression(targetFn)) return false;
|
|
||||||
if (!isSuperIdentifier(targetFn.expression)) return false;
|
|
||||||
if (targetFn.name.text !== 'apply') return false;
|
|
||||||
|
|
||||||
const thisArgument = expression.arguments[0];
|
|
||||||
if (thisArgument.kind !== ts.SyntaxKind.ThisKeyword) return false;
|
|
||||||
|
|
||||||
const argumentsArgument = expression.arguments[1];
|
|
||||||
return ts.isIdentifier(argumentsArgument) && argumentsArgument.text === 'arguments';
|
|
||||||
}
|
|
||||||
|
|
||||||
function isBinaryExpr(
|
function isBinaryExpr(
|
||||||
expression: ts.Expression, operator: ts.BinaryOperator): expression is ts.BinaryExpression {
|
expression: ts.Expression, operator: ts.BinaryOperator): expression is ts.BinaryExpression {
|
||||||
return ts.isBinaryExpression(expression) && expression.operatorToken.kind === operator;
|
return ts.isBinaryExpression(expression) && expression.operatorToken.kind === operator;
|
||||||
|
|
|
@ -1456,6 +1456,210 @@ exports.MissingClass2 = MissingClass2;
|
||||||
expect(decorators[0].args).toEqual([]);
|
expect(decorators[0].args).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getConstructorParameters(
|
||||||
|
constructor: string, mode?: 'inlined'|'inlined_with_suffix'|'imported') {
|
||||||
|
let fileHeader = '';
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case 'imported':
|
||||||
|
fileHeader = `const tslib = require('tslib');`;
|
||||||
|
break;
|
||||||
|
case 'inlined':
|
||||||
|
fileHeader =
|
||||||
|
`var __spread = (this && this.__spread) || function (...args) { /* ... */ }`;
|
||||||
|
break;
|
||||||
|
case 'inlined_with_suffix':
|
||||||
|
fileHeader =
|
||||||
|
`var __spread$1 = (this && this.__spread$1) || function (...args) { /* ... */ }`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const file = {
|
||||||
|
name: _('/synthesized_constructors.js'),
|
||||||
|
contents: `
|
||||||
|
${fileHeader}
|
||||||
|
|
||||||
|
var TestClass = /** @class */ (function (_super) {
|
||||||
|
__extends(TestClass, _super);
|
||||||
|
${constructor}
|
||||||
|
return TestClass;
|
||||||
|
}(null));
|
||||||
|
|
||||||
|
exports.TestClass = TestClass;`,
|
||||||
|
};
|
||||||
|
|
||||||
|
loadTestFiles([file]);
|
||||||
|
const bundle = makeTestBundleProgram(file.name);
|
||||||
|
const host =
|
||||||
|
createHost(bundle, new CommonJsReflectionHost(new MockLogger(), false, bundle));
|
||||||
|
const classNode =
|
||||||
|
getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration);
|
||||||
|
return host.getConstructorParameters(classNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('TS -> ES5: synthesized constructors', () => {
|
||||||
|
it('recognizes _this assignment from super call', () => {
|
||||||
|
const parameters = getConstructorParameters(`
|
||||||
|
function TestClass() {
|
||||||
|
var _this = _super !== null && _super.apply(this, arguments) || this;
|
||||||
|
_this.synthesizedProperty = null;
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes super call as return statement', () => {
|
||||||
|
const parameters = getConstructorParameters(`
|
||||||
|
function TestClass() {
|
||||||
|
return _super !== null && _super.apply(this, arguments) || this;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles the case where a unique name was generated for _super or _this', () => {
|
||||||
|
const parameters = getConstructorParameters(`
|
||||||
|
function TestClass() {
|
||||||
|
var _this_1 = _super_1 !== null && _super_1.apply(this, arguments) || this;
|
||||||
|
_this_1._this = null;
|
||||||
|
_this_1._super = null;
|
||||||
|
return _this_1;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not consider constructors with parameters as synthesized', () => {
|
||||||
|
const parameters = getConstructorParameters(`
|
||||||
|
function TestClass(arg) {
|
||||||
|
return _super !== null && _super.apply(this, arguments) || this;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(parameters!.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not consider manual super calls as synthesized', () => {
|
||||||
|
const parameters = getConstructorParameters(`
|
||||||
|
function TestClass() {
|
||||||
|
return _super.call(this) || this;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(parameters!.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not consider empty constructors as synthesized', () => {
|
||||||
|
const parameters = getConstructorParameters(`function TestClass() {}`);
|
||||||
|
expect(parameters!.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/angular/angular/issues/38453.
|
||||||
|
describe('ES2015 -> ES5: synthesized constructors through TSC downleveling', () => {
|
||||||
|
it('recognizes delegate super call using inline spread helper', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
return _super.apply(this, __spread(arguments)) || this;
|
||||||
|
}`,
|
||||||
|
'inlined');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes delegate super call using inline spread helper with suffix', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
return _super.apply(this, __spread$1(arguments)) || this;
|
||||||
|
}`,
|
||||||
|
'inlined_with_suffix');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes delegate super call using imported spread helper', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
return _super.apply(this, tslib.__spread(arguments)) || this;
|
||||||
|
}`,
|
||||||
|
'imported');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with class member assignment', () => {
|
||||||
|
it('recognizes delegate super call using inline spread helper', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
var _this = _super.apply(this, __spread(arguments)) || this;
|
||||||
|
_this.synthesizedProperty = null;
|
||||||
|
return _this;
|
||||||
|
}`,
|
||||||
|
'inlined');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes delegate super call using inline spread helper with suffix', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
var _this = _super.apply(this, __spread$1(arguments)) || this;
|
||||||
|
_this.synthesizedProperty = null;
|
||||||
|
return _this;
|
||||||
|
}`,
|
||||||
|
'inlined_with_suffix');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes delegate super call using imported spread helper', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
var _this = _super.apply(this, tslib.__spread(arguments)) || this;
|
||||||
|
_this.synthesizedProperty = null;
|
||||||
|
return _this;
|
||||||
|
}`,
|
||||||
|
'imported');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles the case where a unique name was generated for _super or _this', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
var _this_1 = _super_1.apply(this, __spread(arguments)) || this;
|
||||||
|
_this_1._this = null;
|
||||||
|
_this_1._super = null;
|
||||||
|
return _this_1;
|
||||||
|
}`,
|
||||||
|
'inlined');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not consider constructors with parameters as synthesized', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass(arg) {
|
||||||
|
return _super.apply(this, __spread(arguments)) || this;
|
||||||
|
}`,
|
||||||
|
'inlined');
|
||||||
|
|
||||||
|
expect(parameters!.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getDefinitionOfFunction()', () => {
|
describe('getDefinitionOfFunction()', () => {
|
||||||
|
|
|
@ -1417,86 +1417,236 @@ runInEachFileSystem(() => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('synthesized constructors', () => {
|
function getConstructorParameters(
|
||||||
function getConstructorParameters(constructor: string) {
|
constructor: string,
|
||||||
const file = {
|
mode?: 'inlined'|'inlined_with_suffix'|'imported'|'imported_namespace') {
|
||||||
name: _('/synthesized_constructors.js'),
|
let fileHeader = '';
|
||||||
contents: `
|
|
||||||
|
switch (mode) {
|
||||||
|
case 'imported':
|
||||||
|
fileHeader = `import {__spread} from 'tslib';`;
|
||||||
|
break;
|
||||||
|
case 'imported_namespace':
|
||||||
|
fileHeader = `import * as tslib from 'tslib';`;
|
||||||
|
break;
|
||||||
|
case 'inlined':
|
||||||
|
fileHeader =
|
||||||
|
`var __spread = (this && this.__spread) || function (...args) { /* ... */ }`;
|
||||||
|
break;
|
||||||
|
case 'inlined_with_suffix':
|
||||||
|
fileHeader =
|
||||||
|
`var __spread$1 = (this && this.__spread$1) || function (...args) { /* ... */ }`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = {
|
||||||
|
name: _('/synthesized_constructors.js'),
|
||||||
|
contents: `
|
||||||
|
${fileHeader}
|
||||||
var TestClass = /** @class */ (function (_super) {
|
var TestClass = /** @class */ (function (_super) {
|
||||||
__extends(TestClass, _super);
|
__extends(TestClass, _super);
|
||||||
${constructor}
|
${constructor}
|
||||||
return TestClass;
|
return TestClass;
|
||||||
}(null));
|
}(null));
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
loadTestFiles([file]);
|
loadTestFiles([file]);
|
||||||
const bundle = makeTestBundleProgram(file.name);
|
const bundle = makeTestBundleProgram(file.name);
|
||||||
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
|
const host = createHost(bundle, new Esm5ReflectionHost(new MockLogger(), false, bundle));
|
||||||
const classNode =
|
const classNode =
|
||||||
getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration);
|
getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration);
|
||||||
return host.getConstructorParameters(classNode);
|
return host.getConstructorParameters(classNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe('TS -> ES5: synthesized constructors', () => {
|
||||||
it('recognizes _this assignment from super call', () => {
|
it('recognizes _this assignment from super call', () => {
|
||||||
const parameters = getConstructorParameters(`
|
const parameters = getConstructorParameters(`
|
||||||
function TestClass() {
|
function TestClass() {
|
||||||
var _this = _super !== null && _super.apply(this, arguments) || this;
|
var _this = _super !== null && _super.apply(this, arguments) || this;
|
||||||
_this.synthesizedProperty = null;
|
_this.synthesizedProperty = null;
|
||||||
return _this;
|
return _this;
|
||||||
}`);
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
expect(parameters).toBeNull();
|
expect(parameters).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('recognizes super call as return statement', () => {
|
it('recognizes super call as return statement', () => {
|
||||||
const parameters = getConstructorParameters(`
|
const parameters = getConstructorParameters(`
|
||||||
function TestClass() {
|
function TestClass() {
|
||||||
return _super !== null && _super.apply(this, arguments) || this;
|
return _super !== null && _super.apply(this, arguments) || this;
|
||||||
}`);
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
expect(parameters).toBeNull();
|
expect(parameters).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles the case where a unique name was generated for _super or _this', () => {
|
it('handles the case where a unique name was generated for _super or _this', () => {
|
||||||
const parameters = getConstructorParameters(`
|
const parameters = getConstructorParameters(`
|
||||||
function TestClass() {
|
function TestClass() {
|
||||||
var _this_1 = _super_1 !== null && _super_1.apply(this, arguments) || this;
|
var _this_1 = _super_1 !== null && _super_1.apply(this, arguments) || this;
|
||||||
_this_1._this = null;
|
_this_1._this = null;
|
||||||
_this_1._super = null;
|
_this_1._super = null;
|
||||||
return _this_1;
|
return _this_1;
|
||||||
}`);
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
expect(parameters).toBeNull();
|
expect(parameters).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not consider constructors with parameters as synthesized', () => {
|
it('does not consider constructors with parameters as synthesized', () => {
|
||||||
const parameters = getConstructorParameters(`
|
const parameters = getConstructorParameters(`
|
||||||
function TestClass(arg) {
|
function TestClass(arg) {
|
||||||
return _super !== null && _super.apply(this, arguments) || this;
|
return _super !== null && _super.apply(this, arguments) || this;
|
||||||
}`);
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
expect(parameters!.length).toBe(1);
|
expect(parameters!.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not consider manual super calls as synthesized', () => {
|
it('does not consider manual super calls as synthesized', () => {
|
||||||
const parameters = getConstructorParameters(`
|
const parameters = getConstructorParameters(`
|
||||||
function TestClass() {
|
function TestClass() {
|
||||||
return _super.call(this) || this;
|
return _super.call(this) || this;
|
||||||
}`);
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
expect(parameters!.length).toBe(0);
|
expect(parameters!.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not consider empty constructors as synthesized', () => {
|
it('does not consider empty constructors as synthesized', () => {
|
||||||
const parameters = getConstructorParameters(`
|
const parameters = getConstructorParameters(`function TestClass() {}`);
|
||||||
function TestClass() {
|
|
||||||
}`);
|
|
||||||
|
|
||||||
expect(parameters!.length).toBe(0);
|
expect(parameters!.length).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/angular/angular/issues/38453.
|
||||||
|
describe('ES2015 -> ES5: synthesized constructors through TSC downleveling', () => {
|
||||||
|
it('recognizes delegate super call using inline spread helper', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
return _super.apply(this, __spread(arguments)) || this;
|
||||||
|
}`,
|
||||||
|
'inlined');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes delegate super call using inline spread helper with suffix', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
return _super.apply(this, __spread$1(arguments)) || this;
|
||||||
|
}`,
|
||||||
|
'inlined_with_suffix');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes delegate super call using imported spread helper', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
return _super.apply(this, __spread(arguments)) || this;
|
||||||
|
}`,
|
||||||
|
'imported');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes delegate super call using namespace imported spread helper', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
return _super.apply(this, tslib.__spread(arguments)) || this;
|
||||||
|
}`,
|
||||||
|
'imported_namespace');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with class member assignment', () => {
|
||||||
|
it('recognizes delegate super call using inline spread helper', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
var _this = _super.apply(this, __spread(arguments)) || this;
|
||||||
|
_this.synthesizedProperty = null;
|
||||||
|
return _this;
|
||||||
|
}`,
|
||||||
|
'inlined');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes delegate super call using inline spread helper with suffix', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
var _this = _super.apply(this, __spread$1(arguments)) || this;
|
||||||
|
_this.synthesizedProperty = null;
|
||||||
|
return _this;
|
||||||
|
}`,
|
||||||
|
'inlined_with_suffix');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes delegate super call using imported spread helper', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
var _this = _super.apply(this, __spread(arguments)) || this;
|
||||||
|
_this.synthesizedProperty = null;
|
||||||
|
return _this;
|
||||||
|
}`,
|
||||||
|
'imported');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes delegate super call using namespace imported spread helper', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
var _this = _super.apply(this, tslib.__spread(arguments)) || this;
|
||||||
|
_this.synthesizedProperty = null;
|
||||||
|
return _this;
|
||||||
|
}`,
|
||||||
|
'imported_namespace');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles the case where a unique name was generated for _super or _this', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
var _this_1 = _super_1.apply(this, __spread(arguments)) || this;
|
||||||
|
_this_1._this = null;
|
||||||
|
_this_1._super = null;
|
||||||
|
return _this_1;
|
||||||
|
}`,
|
||||||
|
'inlined');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not consider constructors with parameters as synthesized', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass(arg) {
|
||||||
|
return _super.apply(this, __spread(arguments)) || this;
|
||||||
|
}`,
|
||||||
|
'inlined');
|
||||||
|
|
||||||
|
expect(parameters!.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('(returned parameters `decorators.args`)', () => {
|
describe('(returned parameters `decorators.args`)', () => {
|
||||||
it('should be an empty array if param decorator has no `args` property', () => {
|
it('should be an empty array if param decorator has no `args` property', () => {
|
||||||
loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]);
|
loadTestFiles([INVALID_CTOR_DECORATOR_ARGS_FILE]);
|
||||||
|
|
|
@ -1564,6 +1564,231 @@ runInEachFileSystem(() => {
|
||||||
expect(decorators[0].args).toEqual([]);
|
expect(decorators[0].args).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getConstructorParameters(
|
||||||
|
constructor: string, mode: 'inlined'|'inlined_with_suffix'|'imported' = 'imported') {
|
||||||
|
let fileHeaderWithUmd = '';
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case 'imported':
|
||||||
|
fileHeaderWithUmd = `
|
||||||
|
(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) { 'use strict';
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
case 'inlined':
|
||||||
|
fileHeaderWithUmd = `
|
||||||
|
(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 __spread = (this && this.__spread) || function (...args) { /* ... */ }
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
case 'inlined_with_suffix':
|
||||||
|
fileHeaderWithUmd = `
|
||||||
|
(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 __spread$1 = (this && this.__spread$1) || function (...args) { /* ... */ }
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = {
|
||||||
|
name: _('/synthesized_constructors.js'),
|
||||||
|
contents: `
|
||||||
|
${fileHeaderWithUmd}
|
||||||
|
var TestClass = /** @class */ (function (_super) {
|
||||||
|
__extends(TestClass, _super);
|
||||||
|
${constructor}
|
||||||
|
return TestClass;
|
||||||
|
}(null));
|
||||||
|
|
||||||
|
exports.TestClass = TestClass;
|
||||||
|
})));
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
loadTestFiles([file]);
|
||||||
|
const bundle = makeTestBundleProgram(file.name);
|
||||||
|
const host = createHost(bundle, new UmdReflectionHost(new MockLogger(), false, bundle));
|
||||||
|
const classNode =
|
||||||
|
getDeclaration(bundle.program, file.name, 'TestClass', isNamedVariableDeclaration);
|
||||||
|
return host.getConstructorParameters(classNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('TS -> ES5: synthesized constructors', () => {
|
||||||
|
it('recognizes _this assignment from super call', () => {
|
||||||
|
const parameters = getConstructorParameters(`
|
||||||
|
function TestClass() {
|
||||||
|
var _this = _super !== null && _super.apply(this, arguments) || this;
|
||||||
|
_this.synthesizedProperty = null;
|
||||||
|
return _this;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes super call as return statement', () => {
|
||||||
|
const parameters = getConstructorParameters(`
|
||||||
|
function TestClass() {
|
||||||
|
return _super !== null && _super.apply(this, arguments) || this;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles the case where a unique name was generated for _super or _this', () => {
|
||||||
|
const parameters = getConstructorParameters(`
|
||||||
|
function TestClass() {
|
||||||
|
var _this_1 = _super_1 !== null && _super_1.apply(this, arguments) || this;
|
||||||
|
_this_1._this = null;
|
||||||
|
_this_1._super = null;
|
||||||
|
return _this_1;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not consider constructors with parameters as synthesized', () => {
|
||||||
|
const parameters = getConstructorParameters(`
|
||||||
|
function TestClass(arg) {
|
||||||
|
return _super !== null && _super.apply(this, arguments) || this;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(parameters!.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not consider manual super calls as synthesized', () => {
|
||||||
|
const parameters = getConstructorParameters(`
|
||||||
|
function TestClass() {
|
||||||
|
return _super.call(this) || this;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(parameters!.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not consider empty constructors as synthesized', () => {
|
||||||
|
const parameters = getConstructorParameters(`function TestClass() {}`);
|
||||||
|
expect(parameters!.length).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// See: https://github.com/angular/angular/issues/38453.
|
||||||
|
describe('ES2015 -> ES5: synthesized constructors through TSC downleveling', () => {
|
||||||
|
it('recognizes delegate super call using inline spread helper', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
return _super.apply(this, __spread(arguments)) || this;
|
||||||
|
}`,
|
||||||
|
'inlined');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes delegate super call using inline spread helper with suffix', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
return _super.apply(this, __spread$1(arguments)) || this;
|
||||||
|
}`,
|
||||||
|
'inlined_with_suffix');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes delegate super call using imported spread helper', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
return _super.apply(this, tslib_1.__spread(arguments)) || this;
|
||||||
|
}`,
|
||||||
|
'imported');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with class member assignment', () => {
|
||||||
|
it('recognizes delegate super call using inline spread helper', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
var _this = _super.apply(this, __spread(arguments)) || this;
|
||||||
|
_this.synthesizedProperty = null;
|
||||||
|
return _this;
|
||||||
|
}`,
|
||||||
|
'inlined');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes delegate super call using inline spread helper with suffix', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
var _this = _super.apply(this, __spread$1(arguments)) || this;
|
||||||
|
_this.synthesizedProperty = null;
|
||||||
|
return _this;
|
||||||
|
}`,
|
||||||
|
'inlined_with_suffix');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognizes delegate super call using imported spread helper', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
var _this = _super.apply(this, tslib_1.__spread(arguments)) || this;
|
||||||
|
_this.synthesizedProperty = null;
|
||||||
|
return _this;
|
||||||
|
}`,
|
||||||
|
'imported');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles the case where a unique name was generated for _super or _this', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass() {
|
||||||
|
var _this_1 = _super_1.apply(this, __spread(arguments)) || this;
|
||||||
|
_this_1._this = null;
|
||||||
|
_this_1._super = null;
|
||||||
|
return _this_1;
|
||||||
|
}`,
|
||||||
|
'inlined');
|
||||||
|
|
||||||
|
expect(parameters).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not consider constructors with parameters as synthesized', () => {
|
||||||
|
const parameters = getConstructorParameters(
|
||||||
|
`
|
||||||
|
function TestClass(arg) {
|
||||||
|
return _super.apply(this, __spread(arguments)) || this;
|
||||||
|
}`,
|
||||||
|
'inlined');
|
||||||
|
|
||||||
|
expect(parameters!.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getDefinitionOfFunction()', () => {
|
describe('getDefinitionOfFunction()', () => {
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {GetterFn, MethodFn, SetterFn} from './types';
|
||||||
* it intends to capture the pattern where existing constructors have been downleveled from
|
* it intends to capture the pattern where existing constructors have been downleveled from
|
||||||
* ES2015 to ES5 using TypeScript w/ downlevel iteration. e.g.
|
* ES2015 to ES5 using TypeScript w/ downlevel iteration. e.g.
|
||||||
*
|
*
|
||||||
* * ```
|
* ```
|
||||||
* function MyClass() {
|
* function MyClass() {
|
||||||
* var _this = _super.apply(this, arguments) || this;
|
* var _this = _super.apply(this, arguments) || this;
|
||||||
* ```
|
* ```
|
||||||
|
|
Loading…
Reference in New Issue