feat(ivy): implement `getDefinitionOfFunction` on ES2015 and ES5 reflection hosts (#25406)
PR Close #25406
This commit is contained in:
parent
a45f2bfb8f
commit
f87b499dde
|
@ -8,9 +8,10 @@
|
|||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassMember, ClassMemberKind, Decorator, Parameter} from '../../../ngtsc/host';
|
||||
import {ClassMember, ClassMemberKind, CtorParameter, Decorator} from '../../../ngtsc/host';
|
||||
import {TypeScriptReflectionHost, reflectObjectLiteral} from '../../../ngtsc/metadata';
|
||||
import {getNameText} from '../utils';
|
||||
|
||||
import {NgccReflectionHost} from './ngcc_host';
|
||||
|
||||
export const DECORATORS = 'decorators' as ts.__String;
|
||||
|
@ -162,7 +163,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
*
|
||||
* @throws if `declaration` does not resolve to a class declaration.
|
||||
*/
|
||||
getConstructorParameters(clazz: ts.Declaration): Parameter[]|null {
|
||||
getConstructorParameters(clazz: ts.Declaration): CtorParameter[]|null {
|
||||
const classSymbol = this.getClassSymbol(clazz);
|
||||
if (!classSymbol) {
|
||||
throw new Error(
|
||||
|
@ -170,7 +171,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||
}
|
||||
const parameterNodes = this.getConstructorParameterDeclarations(classSymbol);
|
||||
if (parameterNodes) {
|
||||
const parameters: Parameter[] = [];
|
||||
const parameters: CtorParameter[] = [];
|
||||
const decoratorInfo = this.getConstructorDecorators(classSymbol);
|
||||
parameterNodes.forEach((node, index) => {
|
||||
const info = decoratorInfo[index];
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import {ClassMember, ClassMemberKind, Decorator} from '../../../ngtsc/host';
|
||||
import {ClassMember, ClassMemberKind, Decorator, FunctionDefinition, Parameter} from '../../../ngtsc/host';
|
||||
import {reflectObjectLiteral} from '../../../ngtsc/metadata';
|
||||
import {getNameText} from '../utils';
|
||||
import {CONSTRUCTOR_PARAMS, Esm2015ReflectionHost, getPropertyValueFromSymbol} from './esm2015_host';
|
||||
|
||||
/**
|
||||
|
@ -54,6 +55,29 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a function declaration to find the relevant metadata about it.
|
||||
* In ESM5 we need to do special work with optional arguments to the function, since they get
|
||||
* their own initializer statement that needs to be parsed and then not included in the "body"
|
||||
* statements of the function.
|
||||
* @param node the function declaration to parse.
|
||||
*/
|
||||
getDefinitionOfFunction<T extends ts.FunctionDeclaration|ts.MethodDeclaration|
|
||||
ts.FunctionExpression>(node: T): FunctionDefinition<T> {
|
||||
const parameters =
|
||||
node.parameters.map(p => ({name: getNameText(p.name), node: p, initializer: null}));
|
||||
let lookingForParamInitializers = true;
|
||||
|
||||
const statements = node.body && node.body.statements.filter(s => {
|
||||
lookingForParamInitializers =
|
||||
lookingForParamInitializers && reflectParamInitializer(s, parameters);
|
||||
// If we are no longer looking for parameter initializers then we include this statement
|
||||
return !lookingForParamInitializers;
|
||||
});
|
||||
|
||||
return {node, body: statements || null, parameters};
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the declarations of the constructor parameters of a class identified by its symbol.
|
||||
* In ESM5 there is no "class" so the constructor that we want is actually the declaration
|
||||
|
@ -135,3 +159,51 @@ function getReturnStatement(declaration: ts.Expression | undefined): ts.ReturnSt
|
|||
function reflectArrayElement(element: ts.Expression) {
|
||||
return ts.isObjectLiteralExpression(element) ? reflectObjectLiteral(element) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the statement to extract the ESM5 parameter initializer if there is one.
|
||||
* If one is found, add it to the appropriate parameter in the `parameters` collection.
|
||||
*
|
||||
* The form we are looking for is:
|
||||
*
|
||||
* ```
|
||||
* if (arg === void 0) { arg = initializer; }
|
||||
* ```
|
||||
*
|
||||
* @param statement A statement that may be initializing an optional parameter
|
||||
* @param parameters The collection of parameters that were found in the function definition
|
||||
* @returns true if the statement was a parameter initializer
|
||||
*/
|
||||
function reflectParamInitializer(statement: ts.Statement, parameters: Parameter[]) {
|
||||
if (ts.isIfStatement(statement) && isUndefinedComparison(statement.expression) &&
|
||||
ts.isBlock(statement.thenStatement) && statement.thenStatement.statements.length === 1) {
|
||||
const ifStatementComparison = statement.expression; // (arg === void 0)
|
||||
const thenStatement = statement.thenStatement.statements[0]; // arg = initializer;
|
||||
if (isAssignment(thenStatement)) {
|
||||
const comparisonName = ifStatementComparison.left.text;
|
||||
const assignmentName = thenStatement.expression.left.text;
|
||||
if (comparisonName === assignmentName) {
|
||||
const parameter = parameters.find(p => p.name === comparisonName);
|
||||
if (parameter) {
|
||||
parameter.initializer = thenStatement.expression.right;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isUndefinedComparison(expression: ts.Expression): expression is ts.Expression&
|
||||
{left: ts.Identifier, right: ts.Expression} {
|
||||
return ts.isBinaryExpression(expression) &&
|
||||
expression.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken &&
|
||||
ts.isVoidExpression(expression.right) && ts.isIdentifier(expression.left);
|
||||
}
|
||||
|
||||
function isAssignment(statement: ts.Statement): statement is ts.ExpressionStatement&
|
||||
{expression: {left: ts.Identifier, right: ts.Expression}} {
|
||||
return ts.isExpressionStatement(statement) && ts.isBinaryExpression(statement.expression) &&
|
||||
statement.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
||||
ts.isIdentifier(statement.expression.left);
|
||||
}
|
||||
|
|
|
@ -353,6 +353,38 @@ const EXPORTS_FILES = [
|
|||
},
|
||||
];
|
||||
|
||||
const FUNCTION_BODY_FILE = {
|
||||
name: '/function_body.js',
|
||||
contents: `
|
||||
function foo(x) {
|
||||
return x;
|
||||
}
|
||||
function bar(x, y = 42) {
|
||||
return x + y;
|
||||
}
|
||||
function baz(x) {
|
||||
let y;
|
||||
if (y === void 0) { y = 42; }
|
||||
return x;
|
||||
}
|
||||
let y;
|
||||
function qux(x) {
|
||||
if (x === void 0) { y = 42; }
|
||||
return y;
|
||||
}
|
||||
function moo() {
|
||||
let x;
|
||||
if (x === void 0) { x = 42; }
|
||||
return x;
|
||||
}
|
||||
let x;
|
||||
function juu() {
|
||||
if (x === void 0) { x = 42; }
|
||||
return x;
|
||||
}
|
||||
`
|
||||
};
|
||||
|
||||
describe('Esm2015ReflectionHost', () => {
|
||||
|
||||
describe('getDecoratorsOfDeclaration()', () => {
|
||||
|
@ -701,7 +733,7 @@ describe('Esm2015ReflectionHost', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getConstructorParameters', () => {
|
||||
describe('getConstructorParameters()', () => {
|
||||
it('should find the decorated constructor parameters', () => {
|
||||
const program = makeProgram(SOME_DIRECTIVE_FILE);
|
||||
const host = new Esm2015ReflectionHost(program.getTypeChecker());
|
||||
|
@ -897,7 +929,69 @@ describe('Esm2015ReflectionHost', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getImportOfIdentifier', () => {
|
||||
describe('getDefinitionOfFunction()', () => {
|
||||
it('should return an object describing the function declaration passed as an argument', () => {
|
||||
const program = makeProgram(FUNCTION_BODY_FILE);
|
||||
const host = new Esm2015ReflectionHost(program.getTypeChecker());
|
||||
|
||||
const fooNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'foo', ts.isFunctionDeclaration) !;
|
||||
const fooDef = host.getDefinitionOfFunction(fooNode);
|
||||
expect(fooDef.node).toBe(fooNode);
|
||||
expect(fooDef.body !.length).toEqual(1);
|
||||
expect(fooDef.body ![0].getText()).toEqual(`return x;`);
|
||||
expect(fooDef.parameters.length).toEqual(1);
|
||||
expect(fooDef.parameters[0].name).toEqual('x');
|
||||
expect(fooDef.parameters[0].initializer).toBe(null);
|
||||
|
||||
const barNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'bar', ts.isFunctionDeclaration) !;
|
||||
const barDef = host.getDefinitionOfFunction(barNode);
|
||||
expect(barDef.node).toBe(barNode);
|
||||
expect(barDef.body !.length).toEqual(1);
|
||||
expect(ts.isReturnStatement(barDef.body ![0])).toBeTruthy();
|
||||
expect(barDef.body ![0].getText()).toEqual(`return x + y;`);
|
||||
expect(barDef.parameters.length).toEqual(2);
|
||||
expect(barDef.parameters[0].name).toEqual('x');
|
||||
expect(fooDef.parameters[0].initializer).toBe(null);
|
||||
expect(barDef.parameters[1].name).toEqual('y');
|
||||
expect(barDef.parameters[1].initializer !.getText()).toEqual('42');
|
||||
|
||||
const bazNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'baz', ts.isFunctionDeclaration) !;
|
||||
const bazDef = host.getDefinitionOfFunction(bazNode);
|
||||
expect(bazDef.node).toBe(bazNode);
|
||||
expect(bazDef.body !.length).toEqual(3);
|
||||
expect(bazDef.parameters.length).toEqual(1);
|
||||
expect(bazDef.parameters[0].name).toEqual('x');
|
||||
expect(bazDef.parameters[0].initializer).toBe(null);
|
||||
|
||||
const quxNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'qux', ts.isFunctionDeclaration) !;
|
||||
const quxDef = host.getDefinitionOfFunction(quxNode);
|
||||
expect(quxDef.node).toBe(quxNode);
|
||||
expect(quxDef.body !.length).toEqual(2);
|
||||
expect(quxDef.parameters.length).toEqual(1);
|
||||
expect(quxDef.parameters[0].name).toEqual('x');
|
||||
expect(quxDef.parameters[0].initializer).toBe(null);
|
||||
|
||||
const mooNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'moo', ts.isFunctionDeclaration) !;
|
||||
const mooDef = host.getDefinitionOfFunction(mooNode);
|
||||
expect(mooDef.node).toBe(mooNode);
|
||||
expect(mooDef.body !.length).toEqual(3);
|
||||
expect(mooDef.parameters).toEqual([]);
|
||||
|
||||
const juuNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'juu', ts.isFunctionDeclaration) !;
|
||||
const juuDef = host.getDefinitionOfFunction(juuNode);
|
||||
expect(juuDef.node).toBe(juuNode);
|
||||
expect(juuDef.body !.length).toEqual(2);
|
||||
expect(juuDef.parameters).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getImportOfIdentifier()', () => {
|
||||
it('should find the import of an identifier', () => {
|
||||
const program = makeProgram(...IMPORTS_FILES);
|
||||
const host = new Esm2015ReflectionHost(program.getTypeChecker());
|
||||
|
@ -929,7 +1023,7 @@ describe('Esm2015ReflectionHost', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getDeclarationOfIdentifier', () => {
|
||||
describe('getDeclarationOfIdentifier()', () => {
|
||||
it('should return the declaration of a locally defined identifier', () => {
|
||||
const program = makeProgram(SOME_DIRECTIVE_FILE);
|
||||
const host = new Esm2015ReflectionHost(program.getTypeChecker());
|
||||
|
|
|
@ -406,6 +406,43 @@ const EXPORTS_FILES = [
|
|||
},
|
||||
];
|
||||
|
||||
const FUNCTION_BODY_FILE = {
|
||||
name: '/function_body.js',
|
||||
contents: `
|
||||
function foo(x) {
|
||||
return x;
|
||||
}
|
||||
function bar(x, y) {
|
||||
if (y === void 0) { y = 42; }
|
||||
return x + y;
|
||||
}
|
||||
function complex() {
|
||||
var x = 42;
|
||||
return 42;
|
||||
}
|
||||
function baz(x) {
|
||||
var y;
|
||||
if (x === void 0) { y = 42; }
|
||||
return y;
|
||||
}
|
||||
var y;
|
||||
function qux(x) {
|
||||
if (x === void 0) { y = 42; }
|
||||
return y;
|
||||
}
|
||||
function moo() {
|
||||
var x;
|
||||
if (x === void 0) { x = 42; }
|
||||
return x;
|
||||
}
|
||||
var x;
|
||||
function juu() {
|
||||
if (x === void 0) { x = 42; }
|
||||
return x;
|
||||
}
|
||||
`
|
||||
};
|
||||
|
||||
describe('Esm5ReflectionHost', () => {
|
||||
|
||||
describe('getDecoratorsOfDeclaration()', () => {
|
||||
|
@ -928,6 +965,54 @@ describe('Esm5ReflectionHost', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getDefinitionOfFunction()', () => {
|
||||
it('should return an object describing the function declaration passed as an argument', () => {
|
||||
const program = makeProgram(FUNCTION_BODY_FILE);
|
||||
const host = new Esm5ReflectionHost(program.getTypeChecker());
|
||||
|
||||
const fooNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'foo', ts.isFunctionDeclaration) !;
|
||||
const fooDef = host.getDefinitionOfFunction(fooNode);
|
||||
expect(fooDef.node).toBe(fooNode);
|
||||
expect(fooDef.body !.length).toEqual(1);
|
||||
expect(fooDef.body ![0].getText()).toEqual(`return x;`);
|
||||
expect(fooDef.parameters.length).toEqual(1);
|
||||
expect(fooDef.parameters[0].name).toEqual('x');
|
||||
expect(fooDef.parameters[0].initializer).toBe(null);
|
||||
|
||||
const barNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'bar', ts.isFunctionDeclaration) !;
|
||||
const barDef = host.getDefinitionOfFunction(barNode);
|
||||
expect(barDef.node).toBe(barNode);
|
||||
expect(barDef.body !.length).toEqual(1);
|
||||
expect(ts.isReturnStatement(barDef.body ![0])).toBeTruthy();
|
||||
expect(barDef.body ![0].getText()).toEqual(`return x + y;`);
|
||||
expect(barDef.parameters.length).toEqual(2);
|
||||
expect(barDef.parameters[0].name).toEqual('x');
|
||||
expect(fooDef.parameters[0].initializer).toBe(null);
|
||||
expect(barDef.parameters[1].name).toEqual('y');
|
||||
expect(barDef.parameters[1].initializer !.getText()).toEqual('42');
|
||||
|
||||
const bazNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'baz', ts.isFunctionDeclaration) !;
|
||||
const bazDef = host.getDefinitionOfFunction(bazNode);
|
||||
expect(bazDef.node).toBe(bazNode);
|
||||
expect(bazDef.body !.length).toEqual(3);
|
||||
expect(bazDef.parameters.length).toEqual(1);
|
||||
expect(bazDef.parameters[0].name).toEqual('x');
|
||||
expect(bazDef.parameters[0].initializer).toBe(null);
|
||||
|
||||
const quxNode =
|
||||
getDeclaration(program, FUNCTION_BODY_FILE.name, 'qux', ts.isFunctionDeclaration) !;
|
||||
const quxDef = host.getDefinitionOfFunction(quxNode);
|
||||
expect(quxDef.node).toBe(quxNode);
|
||||
expect(quxDef.body !.length).toEqual(2);
|
||||
expect(quxDef.parameters.length).toEqual(1);
|
||||
expect(quxDef.parameters[0].name).toEqual('x');
|
||||
expect(quxDef.parameters[0].initializer).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getImportOfIdentifier', () => {
|
||||
it('should find the import of an identifier', () => {
|
||||
const program = makeProgram(...IMPORTS_FILES);
|
||||
|
|
Loading…
Reference in New Issue