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 * 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 {TypeScriptReflectionHost, reflectObjectLiteral} from '../../../ngtsc/metadata';
|
||||||
import {getNameText} from '../utils';
|
import {getNameText} from '../utils';
|
||||||
|
|
||||||
import {NgccReflectionHost} from './ngcc_host';
|
import {NgccReflectionHost} from './ngcc_host';
|
||||||
|
|
||||||
export const DECORATORS = 'decorators' as ts.__String;
|
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.
|
* @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);
|
const classSymbol = this.getClassSymbol(clazz);
|
||||||
if (!classSymbol) {
|
if (!classSymbol) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -170,7 +171,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
|||||||
}
|
}
|
||||||
const parameterNodes = this.getConstructorParameterDeclarations(classSymbol);
|
const parameterNodes = this.getConstructorParameterDeclarations(classSymbol);
|
||||||
if (parameterNodes) {
|
if (parameterNodes) {
|
||||||
const parameters: Parameter[] = [];
|
const parameters: CtorParameter[] = [];
|
||||||
const decoratorInfo = this.getConstructorDecorators(classSymbol);
|
const decoratorInfo = this.getConstructorDecorators(classSymbol);
|
||||||
parameterNodes.forEach((node, index) => {
|
parameterNodes.forEach((node, index) => {
|
||||||
const info = decoratorInfo[index];
|
const info = decoratorInfo[index];
|
||||||
|
@ -7,8 +7,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
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 {reflectObjectLiteral} from '../../../ngtsc/metadata';
|
||||||
|
import {getNameText} from '../utils';
|
||||||
import {CONSTRUCTOR_PARAMS, Esm2015ReflectionHost, getPropertyValueFromSymbol} from './esm2015_host';
|
import {CONSTRUCTOR_PARAMS, Esm2015ReflectionHost, getPropertyValueFromSymbol} from './esm2015_host';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,6 +55,29 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
|||||||
return undefined;
|
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.
|
* 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
|
* 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) {
|
function reflectArrayElement(element: ts.Expression) {
|
||||||
return ts.isObjectLiteralExpression(element) ? reflectObjectLiteral(element) : null;
|
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('Esm2015ReflectionHost', () => {
|
||||||
|
|
||||||
describe('getDecoratorsOfDeclaration()', () => {
|
describe('getDecoratorsOfDeclaration()', () => {
|
||||||
@ -701,7 +733,7 @@ describe('Esm2015ReflectionHost', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getConstructorParameters', () => {
|
describe('getConstructorParameters()', () => {
|
||||||
it('should find the decorated constructor parameters', () => {
|
it('should find the decorated constructor parameters', () => {
|
||||||
const program = makeProgram(SOME_DIRECTIVE_FILE);
|
const program = makeProgram(SOME_DIRECTIVE_FILE);
|
||||||
const host = new Esm2015ReflectionHost(program.getTypeChecker());
|
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', () => {
|
it('should find the import of an identifier', () => {
|
||||||
const program = makeProgram(...IMPORTS_FILES);
|
const program = makeProgram(...IMPORTS_FILES);
|
||||||
const host = new Esm2015ReflectionHost(program.getTypeChecker());
|
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', () => {
|
it('should return the declaration of a locally defined identifier', () => {
|
||||||
const program = makeProgram(SOME_DIRECTIVE_FILE);
|
const program = makeProgram(SOME_DIRECTIVE_FILE);
|
||||||
const host = new Esm2015ReflectionHost(program.getTypeChecker());
|
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('Esm5ReflectionHost', () => {
|
||||||
|
|
||||||
describe('getDecoratorsOfDeclaration()', () => {
|
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', () => {
|
describe('getImportOfIdentifier', () => {
|
||||||
it('should find the import of an identifier', () => {
|
it('should find the import of an identifier', () => {
|
||||||
const program = makeProgram(...IMPORTS_FILES);
|
const program = makeProgram(...IMPORTS_FILES);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user