You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
205 lines
5.1 KiB
205 lines
5.1 KiB
/** |
|
* @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 |
|
};
|
|
|