/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import * as ts from 'typescript/lib/tsserverlibrary'; /** * Return the node that most tightly encompass the specified `position`. * @param node * @param position */ export function findTightestNode(node: ts.Node, position: number): ts.Node|undefined { if (node.getStart() <= position && position < node.getEnd()) { return node.forEachChild(c => findTightestNode(c, position)) || node; } } /** * Returns a property assignment from the assignment value if the property name * matches the specified `key`, or `undefined` if there is no match. */ export function getPropertyAssignmentFromValue(value: ts.Node, key: string): ts.PropertyAssignment| undefined { const propAssignment = value.parent; if (!propAssignment || !ts.isPropertyAssignment(propAssignment) || propAssignment.name.getText() !== key) { return; } return propAssignment; } /** * Given a decorator property assignment, return the ClassDeclaration node that corresponds to the * directive class the property applies to. * If the property assignment is not on a class decorator, no declaration is returned. * * For example, * * @Component({ * template: '
' * ^^^^^^^^^^^^^^^^^^^^^^^---- property assignment * }) * class AppComponent {} * ^---- class declaration node * * @param propAsgn property assignment */ export function getClassDeclFromDecoratorProp(propAsgnNode: ts.PropertyAssignment): ts.ClassDeclaration|undefined { if (!propAsgnNode.parent || !ts.isObjectLiteralExpression(propAsgnNode.parent)) { return; } const objLitExprNode = propAsgnNode.parent; if (!objLitExprNode.parent || !ts.isCallExpression(objLitExprNode.parent)) { return; } const callExprNode = objLitExprNode.parent; if (!callExprNode.parent || !ts.isDecorator(callExprNode.parent)) { return; } const decorator = callExprNode.parent; if (!decorator.parent || !ts.isClassDeclaration(decorator.parent)) { return; } const classDeclNode = decorator.parent; return classDeclNode; } interface DirectiveClassLike { decoratorId: ts.Identifier; // decorator identifier, like @Component classId: ts.Identifier; } /** * Return metadata about `node` if it looks like an Angular directive class. * In this case, potential matches are `@NgModule`, `@Component`, `@Directive`, * `@Pipe`, etc. * These class declarations all share some common attributes, namely their * decorator takes exactly one parameter and the parameter must be an object * literal. * * For example, * v---------- `decoratorId` * @NgModule({ < * declarations: [], < classDecl * }) < * class AppModule {} < * ^----- `classId` * * @param node Potential node that represents an Angular directive. */ export function getDirectiveClassLike(node: ts.Node): DirectiveClassLike|undefined { if (!ts.isClassDeclaration(node) || !node.name || !node.decorators) { return; } for (const d of node.decorators) { const expr = d.expression; if (!ts.isCallExpression(expr) || expr.arguments.length !== 1 || !ts.isIdentifier(expr.expression)) { continue; } const arg = expr.arguments[0]; if (ts.isObjectLiteralExpression(arg)) { return { decoratorId: expr.expression, classId: node.name, }; } } } /** * Finds the value of a property assignment that is nested in a TypeScript node and is of a certain * type T. * * @param startNode node to start searching for nested property assignment from * @param propName property assignment name * @param predicate function to verify that a node is of type T. * @return node property assignment value of type T, or undefined if none is found */ export function findPropertyValueOfType( startNode: ts.Node, propName: string, predicate: (node: ts.Node) => node is T): T|undefined { if (ts.isPropertyAssignment(startNode) && startNode.name.getText() === propName) { const {initializer} = startNode; if (predicate(initializer)) return initializer; } return startNode.forEachChild(c => findPropertyValueOfType(c, propName, predicate)); }