jsx-no-literals.js 5.3 KB

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