/** * @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 };