jsx-no-literals.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. /**
  2. * @fileoverview Prevent using string literals in React component definition
  3. * @author Caleb Morris
  4. * @author David Buchan-Swanson
  5. */
  6. 'use strict';
  7. const docsUrl = require('../util/docsUrl');
  8. const report = require('../util/report');
  9. // ------------------------------------------------------------------------------
  10. // Rule Definition
  11. // ------------------------------------------------------------------------------
  12. function trimIfString(val) {
  13. return typeof val === 'string' ? val.trim() : val;
  14. }
  15. const messages = {
  16. invalidPropValue: 'Invalid prop value: "{{text}}"',
  17. noStringsInAttributes: 'Strings not allowed in attributes: "{{text}}"',
  18. noStringsInJSX: 'Strings not allowed in JSX files: "{{text}}"',
  19. literalNotInJSXExpression: 'Missing JSX expression container around literal string: "{{text}}"',
  20. };
  21. module.exports = {
  22. meta: {
  23. docs: {
  24. description: 'Disallow usage of string literals in JSX',
  25. category: 'Stylistic Issues',
  26. recommended: false,
  27. url: docsUrl('jsx-no-literals'),
  28. },
  29. messages,
  30. schema: [{
  31. type: 'object',
  32. properties: {
  33. noStrings: {
  34. type: 'boolean',
  35. },
  36. allowedStrings: {
  37. type: 'array',
  38. uniqueItems: true,
  39. items: {
  40. type: 'string',
  41. },
  42. },
  43. ignoreProps: {
  44. type: 'boolean',
  45. },
  46. noAttributeStrings: {
  47. type: 'boolean',
  48. },
  49. },
  50. additionalProperties: false,
  51. }],
  52. },
  53. create(context) {
  54. const defaults = {
  55. noStrings: false,
  56. allowedStrings: [],
  57. ignoreProps: false,
  58. noAttributeStrings: false,
  59. };
  60. const config = Object.assign({}, defaults, context.options[0] || {});
  61. config.allowedStrings = new Set(config.allowedStrings.map(trimIfString));
  62. function defaultMessageId() {
  63. const ancestorIsJSXElement = arguments.length >= 1 && arguments[0];
  64. if (config.noAttributeStrings && !ancestorIsJSXElement) {
  65. return 'noStringsInAttributes';
  66. }
  67. if (config.noStrings) {
  68. return 'noStringsInJSX';
  69. }
  70. return 'literalNotInJSXExpression';
  71. }
  72. function getParentIgnoringBinaryExpressions(node) {
  73. let current = node;
  74. while (current.parent.type === 'BinaryExpression') {
  75. current = current.parent;
  76. }
  77. return current.parent;
  78. }
  79. function getValidation(node) {
  80. const values = [trimIfString(node.raw), trimIfString(node.value)];
  81. if (values.some((value) => config.allowedStrings.has(value))) {
  82. return false;
  83. }
  84. const parent = getParentIgnoringBinaryExpressions(node);
  85. function isParentNodeStandard() {
  86. if (!/^[\s]+$/.test(node.value) && typeof node.value === 'string' && parent.type.includes('JSX')) {
  87. if (config.noAttributeStrings) {
  88. return parent.type === 'JSXAttribute' || parent.type === 'JSXElement';
  89. }
  90. if (!config.noAttributeStrings) {
  91. return parent.type !== 'JSXAttribute';
  92. }
  93. }
  94. return false;
  95. }
  96. const standard = isParentNodeStandard();
  97. if (config.noStrings) {
  98. return standard;
  99. }
  100. return standard && parent.type !== 'JSXExpressionContainer';
  101. }
  102. function getParentAndGrandParentType(node) {
  103. const parent = getParentIgnoringBinaryExpressions(node);
  104. const parentType = parent.type;
  105. const grandParentType = parent.parent.type;
  106. return {
  107. parent,
  108. parentType,
  109. grandParentType,
  110. grandParent: parent.parent,
  111. };
  112. }
  113. function hasJSXElementParentOrGrandParent(node) {
  114. const parents = getParentAndGrandParentType(node);
  115. const parentType = parents.parentType;
  116. const grandParentType = parents.grandParentType;
  117. return parentType === 'JSXFragment' || parentType === 'JSXElement' || grandParentType === 'JSXElement';
  118. }
  119. function reportLiteralNode(node, messageId) {
  120. const ancestorIsJSXElement = hasJSXElementParentOrGrandParent(node);
  121. messageId = messageId || defaultMessageId(ancestorIsJSXElement);
  122. report(context, messages[messageId], messageId, {
  123. node,
  124. data: {
  125. text: context.getSourceCode().getText(node).trim(),
  126. },
  127. });
  128. }
  129. // --------------------------------------------------------------------------
  130. // Public
  131. // --------------------------------------------------------------------------
  132. return {
  133. Literal(node) {
  134. if (getValidation(node) && (hasJSXElementParentOrGrandParent(node) || !config.ignoreProps)) {
  135. reportLiteralNode(node);
  136. }
  137. },
  138. JSXAttribute(node) {
  139. const isNodeValueString = node && node.value && node.value.type === 'Literal' && typeof node.value.value === 'string' && !config.allowedStrings.has(node.value.value);
  140. if (config.noStrings && !config.ignoreProps && isNodeValueString) {
  141. const messageId = 'invalidPropValue';
  142. reportLiteralNode(node, messageId);
  143. }
  144. },
  145. JSXText(node) {
  146. if (getValidation(node)) {
  147. reportLiteralNode(node);
  148. }
  149. },
  150. TemplateLiteral(node) {
  151. const parents = getParentAndGrandParentType(node);
  152. const parentType = parents.parentType;
  153. const grandParentType = parents.grandParentType;
  154. const isParentJSXExpressionCont = parentType === 'JSXExpressionContainer';
  155. const isParentJSXElement = parentType === 'JSXElement' || grandParentType === 'JSXElement';
  156. if (isParentJSXExpressionCont && config.noStrings && (isParentJSXElement || !config.ignoreProps)) {
  157. reportLiteralNode(node);
  158. }
  159. },
  160. };
  161. },
  162. };