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.
196 lines
5.2 KiB
196 lines
5.2 KiB
/** |
|
* @fileoverview Utility functions for JSX |
|
*/ |
|
|
|
'use strict'; |
|
|
|
const elementType = require('jsx-ast-utils/elementType'); |
|
|
|
const astUtil = require('./ast'); |
|
const isCreateElement = require('./isCreateElement'); |
|
const variableUtil = require('./variable'); |
|
|
|
// See https://github.com/babel/babel/blob/ce420ba51c68591e057696ef43e028f41c6e04cd/packages/babel-types/src/validators/react/isCompatTag.js |
|
// for why we only test for the first character |
|
const COMPAT_TAG_REGEX = /^[a-z]/; |
|
|
|
/** |
|
* Checks if a node represents a DOM element according to React. |
|
* @param {object} node - JSXOpeningElement to check. |
|
* @returns {boolean} Whether or not the node corresponds to a DOM element. |
|
*/ |
|
function isDOMComponent(node) { |
|
const name = elementType(node); |
|
return COMPAT_TAG_REGEX.test(name); |
|
} |
|
|
|
/** |
|
* Test whether a JSXElement is a fragment |
|
* @param {JSXElement} node |
|
* @param {string} reactPragma |
|
* @param {string} fragmentPragma |
|
* @returns {boolean} |
|
*/ |
|
function isFragment(node, reactPragma, fragmentPragma) { |
|
const name = node.openingElement.name; |
|
|
|
// <Fragment> |
|
if (name.type === 'JSXIdentifier' && name.name === fragmentPragma) { |
|
return true; |
|
} |
|
|
|
// <React.Fragment> |
|
if ( |
|
name.type === 'JSXMemberExpression' |
|
&& name.object.type === 'JSXIdentifier' |
|
&& name.object.name === reactPragma |
|
&& name.property.type === 'JSXIdentifier' |
|
&& name.property.name === fragmentPragma |
|
) { |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/** |
|
* Checks if a node represents a JSX element or fragment. |
|
* @param {object} node - node to check. |
|
* @returns {boolean} Whether or not the node if a JSX element or fragment. |
|
*/ |
|
function isJSX(node) { |
|
return node && ['JSXElement', 'JSXFragment'].indexOf(node.type) >= 0; |
|
} |
|
|
|
/** |
|
* Check if node is like `key={...}` as in `<Foo key={...} />` |
|
* @param {ASTNode} node |
|
* @returns {boolean} |
|
*/ |
|
function isJSXAttributeKey(node) { |
|
return node.type === 'JSXAttribute' |
|
&& node.name |
|
&& node.name.type === 'JSXIdentifier' |
|
&& node.name.name === 'key'; |
|
} |
|
|
|
/** |
|
* Check if value has only whitespaces |
|
* @param {string} value |
|
* @returns {boolean} |
|
*/ |
|
function isWhiteSpaces(value) { |
|
return typeof value === 'string' ? /^\s*$/.test(value) : false; |
|
} |
|
|
|
/** |
|
* Check if the node is returning JSX or null |
|
* |
|
* @param {ASTNode} ASTnode The AST node being checked |
|
* @param {Context} context The context of `ASTNode`. |
|
* @param {Boolean} [strict] If true, in a ternary condition the node must return JSX in both cases |
|
* @param {Boolean} [ignoreNull] If true, null return values will be ignored |
|
* @returns {Boolean} True if the node is returning JSX or null, false if not |
|
*/ |
|
function isReturningJSX(ASTnode, context, strict, ignoreNull) { |
|
const isJSXValue = (node) => { |
|
if (!node) { |
|
return false; |
|
} |
|
switch (node.type) { |
|
case 'ConditionalExpression': |
|
if (strict) { |
|
return isJSXValue(node.consequent) && isJSXValue(node.alternate); |
|
} |
|
return isJSXValue(node.consequent) || isJSXValue(node.alternate); |
|
case 'LogicalExpression': |
|
if (strict) { |
|
return isJSXValue(node.left) && isJSXValue(node.right); |
|
} |
|
return isJSXValue(node.left) || isJSXValue(node.right); |
|
case 'SequenceExpression': |
|
return isJSXValue(node.expressions[node.expressions.length - 1]); |
|
case 'JSXElement': |
|
case 'JSXFragment': |
|
return true; |
|
case 'CallExpression': |
|
return isCreateElement(node, context); |
|
case 'Literal': |
|
if (!ignoreNull && node.value === null) { |
|
return true; |
|
} |
|
return false; |
|
case 'Identifier': { |
|
const variable = variableUtil.findVariableByName(context, node.name); |
|
return isJSX(variable); |
|
} |
|
default: |
|
return false; |
|
} |
|
}; |
|
|
|
let found = false; |
|
astUtil.traverseReturns(ASTnode, context, (node, breakTraverse) => { |
|
if (isJSXValue(node)) { |
|
found = true; |
|
breakTraverse(); |
|
} |
|
}); |
|
|
|
return found; |
|
} |
|
|
|
/** |
|
* Check if the node is returning only null values |
|
* |
|
* @param {ASTNode} ASTnode The AST node being checked |
|
* @param {Context} context The context of `ASTNode`. |
|
* @returns {Boolean} True if the node is returning only null values |
|
*/ |
|
function isReturningOnlyNull(ASTnode, context) { |
|
let found = false; |
|
let foundSomethingElse = false; |
|
astUtil.traverseReturns(ASTnode, context, (node) => { |
|
// Traverse return statement |
|
astUtil.traverse(node, { |
|
enter(childNode) { |
|
const setFound = () => { |
|
found = true; |
|
this.skip(); |
|
}; |
|
const setFoundSomethingElse = () => { |
|
foundSomethingElse = true; |
|
this.skip(); |
|
}; |
|
switch (childNode.type) { |
|
case 'ReturnStatement': |
|
break; |
|
case 'ConditionalExpression': |
|
if (childNode.consequent.value === null && childNode.alternate.value === null) { |
|
setFound(); |
|
} |
|
break; |
|
case 'Literal': |
|
if (childNode.value === null) { |
|
setFound(); |
|
} |
|
break; |
|
default: |
|
setFoundSomethingElse(); |
|
} |
|
}, |
|
}); |
|
}); |
|
|
|
return found && !foundSomethingElse; |
|
} |
|
|
|
module.exports = { |
|
isDOMComponent, |
|
isFragment, |
|
isJSX, |
|
isJSXAttributeKey, |
|
isWhiteSpaces, |
|
isReturningJSX, |
|
isReturningOnlyNull, |
|
};
|
|
|