| /** |
| * @fileoverview Utility functions for AST |
| */ |
| |
| 'use strict'; |
| |
| /** |
| * Find a return statment in the current node |
| * |
| * @param {ASTNode} node The AST node being checked |
| */ |
| function findReturnStatement(node) { |
| if ( |
| (!node.value || !node.value.body || !node.value.body.body) && |
| (!node.body || !node.body.body) |
| ) { |
| return false; |
| } |
| |
| const bodyNodes = (node.value ? node.value.body.body : node.body.body); |
| |
| return (function loopNodes(nodes) { |
| let i = nodes.length - 1; |
| for (; i >= 0; i--) { |
| if (nodes[i].type === 'ReturnStatement') { |
| return nodes[i]; |
| } |
| if (nodes[i].type === 'SwitchStatement') { |
| let j = nodes[i].cases.length - 1; |
| for (; j >= 0; j--) { |
| return loopNodes(nodes[i].cases[j].consequent); |
| } |
| } |
| } |
| return false; |
| }(bodyNodes)); |
| } |
| |
| /** |
| * Get node with property's name |
| * @param {Object} node - Property. |
| * @returns {Object} Property name node. |
| */ |
| function getPropertyNameNode(node) { |
| if (node.key || ['MethodDefinition', 'Property'].indexOf(node.type) !== -1) { |
| return node.key; |
| } |
| if (node.type === 'MemberExpression') { |
| return node.property; |
| } |
| return null; |
| } |
| |
| /** |
| * Get properties name |
| * @param {Object} node - Property. |
| * @returns {String} Property name. |
| */ |
| function getPropertyName(node) { |
| const nameNode = getPropertyNameNode(node); |
| return nameNode ? nameNode.name : ''; |
| } |
| |
| /** |
| * Get properties for a given AST node |
| * @param {ASTNode} node The AST node being checked. |
| * @returns {Array} Properties array. |
| */ |
| function getComponentProperties(node) { |
| switch (node.type) { |
| case 'ClassDeclaration': |
| case 'ClassExpression': |
| return node.body.body; |
| case 'ObjectExpression': |
| return node.properties; |
| default: |
| return []; |
| } |
| } |
| |
| |
| /** |
| * Gets the first node in a line from the initial node, excluding whitespace. |
| * @param {Object} context The node to check |
| * @param {ASTNode} node The node to check |
| * @return {ASTNode} the first node in the line |
| */ |
| function getFirstNodeInLine(context, node) { |
| const sourceCode = context.getSourceCode(); |
| let token = node; |
| let lines; |
| do { |
| token = sourceCode.getTokenBefore(token); |
| lines = token.type === 'JSXText' ? |
| token.value.split('\n') : |
| null; |
| } while ( |
| token.type === 'JSXText' && |
| /^\s*$/.test(lines[lines.length - 1]) |
| ); |
| return token; |
| } |
| |
| /** |
| * Checks if the node is the first in its line, excluding whitespace. |
| * @param {Object} context The node to check |
| * @param {ASTNode} node The node to check |
| * @return {Boolean} true if it's the first node in its line |
| */ |
| function isNodeFirstInLine(context, node) { |
| const token = getFirstNodeInLine(context, node); |
| const startLine = node.loc.start.line; |
| const endLine = token ? token.loc.end.line : -1; |
| return startLine !== endLine; |
| } |
| |
| /** |
| * Checks if the node is a function or arrow function expression. |
| * @param {ASTNode} node The node to check |
| * @return {Boolean} true if it's a function-like expression |
| */ |
| function isFunctionLikeExpression(node) { |
| return node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression'; |
| } |
| |
| /** |
| * Checks if the node is a function. |
| * @param {ASTNode} node The node to check |
| * @return {Boolean} true if it's a function |
| */ |
| function isFunction(node) { |
| return node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration'; |
| } |
| |
| /** |
| * Checks if the node is an arrow function. |
| * @param {ASTNode} node The node to check |
| * @return {Boolean} true if it's an arrow function |
| */ |
| function isArrowFunction(node) { |
| return node.type === 'ArrowFunctionExpression'; |
| } |
| |
| /** |
| * Checks if the node is a class. |
| * @param {ASTNode} node The node to check |
| * @return {Boolean} true if it's a class |
| */ |
| function isClass(node) { |
| return node.type === 'ClassDeclaration' || node.type === 'ClassExpression'; |
| } |
| |
| /** |
| * Removes quotes from around an identifier. |
| * @param {string} string the identifier to strip |
| */ |
| function stripQuotes(string) { |
| return string.replace(/^'|'$/g, ''); |
| } |
| |
| /** |
| * Retrieve the name of a key node |
| * @param {Context} context The AST node with the key. |
| * @param {ASTNode} node The AST node with the key. |
| * @return {string} the name of the key |
| */ |
| function getKeyValue(context, node) { |
| if (node.type === 'ObjectTypeProperty') { |
| const tokens = context.getFirstTokens(node, 2); |
| return (tokens[0].value === '+' || tokens[0].value === '-' ? |
| tokens[1].value : |
| stripQuotes(tokens[0].value) |
| ); |
| } |
| const key = node.key || node.argument; |
| return key.type === 'Identifier' ? key.name : key.value; |
| } |
| |
| /** |
| * Checks if a node is being assigned a value: props.bar = 'bar' |
| * @param {ASTNode} node The AST node being checked. |
| * @returns {Boolean} |
| */ |
| function isAssignmentLHS(node) { |
| return ( |
| node.parent && |
| node.parent.type === 'AssignmentExpression' && |
| node.parent.left === node |
| ); |
| } |
| |
| module.exports = { |
| findReturnStatement, |
| getFirstNodeInLine, |
| getPropertyName, |
| getPropertyNameNode, |
| getComponentProperties, |
| getKeyValue, |
| isArrowFunction, |
| isAssignmentLHS, |
| isClass, |
| isFunction, |
| isFunctionLikeExpression, |
| isNodeFirstInLine |
| }; |