jsx.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. /**
  2. * @fileoverview Utility functions for JSX
  3. */
  4. 'use strict';
  5. const elementType = require('jsx-ast-utils/elementType');
  6. const astUtil = require('./ast');
  7. const isCreateElement = require('./isCreateElement');
  8. const variableUtil = require('./variable');
  9. // See https://github.com/babel/babel/blob/ce420ba51c68591e057696ef43e028f41c6e04cd/packages/babel-types/src/validators/react/isCompatTag.js
  10. // for why we only test for the first character
  11. const COMPAT_TAG_REGEX = /^[a-z]/;
  12. /**
  13. * Checks if a node represents a DOM element according to React.
  14. * @param {object} node - JSXOpeningElement to check.
  15. * @returns {boolean} Whether or not the node corresponds to a DOM element.
  16. */
  17. function isDOMComponent(node) {
  18. const name = elementType(node);
  19. return COMPAT_TAG_REGEX.test(name);
  20. }
  21. /**
  22. * Test whether a JSXElement is a fragment
  23. * @param {JSXElement} node
  24. * @param {string} reactPragma
  25. * @param {string} fragmentPragma
  26. * @returns {boolean}
  27. */
  28. function isFragment(node, reactPragma, fragmentPragma) {
  29. const name = node.openingElement.name;
  30. // <Fragment>
  31. if (name.type === 'JSXIdentifier' && name.name === fragmentPragma) {
  32. return true;
  33. }
  34. // <React.Fragment>
  35. if (
  36. name.type === 'JSXMemberExpression'
  37. && name.object.type === 'JSXIdentifier'
  38. && name.object.name === reactPragma
  39. && name.property.type === 'JSXIdentifier'
  40. && name.property.name === fragmentPragma
  41. ) {
  42. return true;
  43. }
  44. return false;
  45. }
  46. /**
  47. * Checks if a node represents a JSX element or fragment.
  48. * @param {object} node - node to check.
  49. * @returns {boolean} Whether or not the node if a JSX element or fragment.
  50. */
  51. function isJSX(node) {
  52. return node && ['JSXElement', 'JSXFragment'].indexOf(node.type) >= 0;
  53. }
  54. /**
  55. * Check if node is like `key={...}` as in `<Foo key={...} />`
  56. * @param {ASTNode} node
  57. * @returns {boolean}
  58. */
  59. function isJSXAttributeKey(node) {
  60. return node.type === 'JSXAttribute'
  61. && node.name
  62. && node.name.type === 'JSXIdentifier'
  63. && node.name.name === 'key';
  64. }
  65. /**
  66. * Check if value has only whitespaces
  67. * @param {string} value
  68. * @returns {boolean}
  69. */
  70. function isWhiteSpaces(value) {
  71. return typeof value === 'string' ? /^\s*$/.test(value) : false;
  72. }
  73. /**
  74. * Check if the node is returning JSX or null
  75. *
  76. * @param {ASTNode} ASTnode The AST node being checked
  77. * @param {Context} context The context of `ASTNode`.
  78. * @param {Boolean} [strict] If true, in a ternary condition the node must return JSX in both cases
  79. * @param {Boolean} [ignoreNull] If true, null return values will be ignored
  80. * @returns {Boolean} True if the node is returning JSX or null, false if not
  81. */
  82. function isReturningJSX(ASTnode, context, strict, ignoreNull) {
  83. const isJSXValue = (node) => {
  84. if (!node) {
  85. return false;
  86. }
  87. switch (node.type) {
  88. case 'ConditionalExpression':
  89. if (strict) {
  90. return isJSXValue(node.consequent) && isJSXValue(node.alternate);
  91. }
  92. return isJSXValue(node.consequent) || isJSXValue(node.alternate);
  93. case 'LogicalExpression':
  94. if (strict) {
  95. return isJSXValue(node.left) && isJSXValue(node.right);
  96. }
  97. return isJSXValue(node.left) || isJSXValue(node.right);
  98. case 'SequenceExpression':
  99. return isJSXValue(node.expressions[node.expressions.length - 1]);
  100. case 'JSXElement':
  101. case 'JSXFragment':
  102. return true;
  103. case 'CallExpression':
  104. return isCreateElement(node, context);
  105. case 'Literal':
  106. if (!ignoreNull && node.value === null) {
  107. return true;
  108. }
  109. return false;
  110. case 'Identifier': {
  111. const variable = variableUtil.findVariableByName(context, node.name);
  112. return isJSX(variable);
  113. }
  114. default:
  115. return false;
  116. }
  117. };
  118. let found = false;
  119. astUtil.traverseReturns(ASTnode, context, (node, breakTraverse) => {
  120. if (isJSXValue(node)) {
  121. found = true;
  122. breakTraverse();
  123. }
  124. });
  125. return found;
  126. }
  127. /**
  128. * Check if the node is returning only null values
  129. *
  130. * @param {ASTNode} ASTnode The AST node being checked
  131. * @param {Context} context The context of `ASTNode`.
  132. * @returns {Boolean} True if the node is returning only null values
  133. */
  134. function isReturningOnlyNull(ASTnode, context) {
  135. let found = false;
  136. let foundSomethingElse = false;
  137. astUtil.traverseReturns(ASTnode, context, (node) => {
  138. // Traverse return statement
  139. astUtil.traverse(node, {
  140. enter(childNode) {
  141. const setFound = () => {
  142. found = true;
  143. this.skip();
  144. };
  145. const setFoundSomethingElse = () => {
  146. foundSomethingElse = true;
  147. this.skip();
  148. };
  149. switch (childNode.type) {
  150. case 'ReturnStatement':
  151. break;
  152. case 'ConditionalExpression':
  153. if (childNode.consequent.value === null && childNode.alternate.value === null) {
  154. setFound();
  155. }
  156. break;
  157. case 'Literal':
  158. if (childNode.value === null) {
  159. setFound();
  160. }
  161. break;
  162. default:
  163. setFoundSomethingElse();
  164. }
  165. },
  166. });
  167. });
  168. return found && !foundSomethingElse;
  169. }
  170. module.exports = {
  171. isDOMComponent,
  172. isFragment,
  173. isJSX,
  174. isJSXAttributeKey,
  175. isWhiteSpaces,
  176. isReturningJSX,
  177. isReturningOnlyNull,
  178. };