jsx-props-no-spreading.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. /**
  2. * @fileoverview Prevent JSX prop spreading
  3. * @author Ashish Gambhir
  4. */
  5. 'use strict';
  6. const docsUrl = require('../util/docsUrl');
  7. const report = require('../util/report');
  8. // ------------------------------------------------------------------------------
  9. // Constants
  10. // ------------------------------------------------------------------------------
  11. const OPTIONS = { ignore: 'ignore', enforce: 'enforce' };
  12. const DEFAULTS = {
  13. html: OPTIONS.enforce,
  14. custom: OPTIONS.enforce,
  15. explicitSpread: OPTIONS.enforce,
  16. exceptions: [],
  17. };
  18. const isException = (tag, allExceptions) => allExceptions.indexOf(tag) !== -1;
  19. const isProperty = (property) => property.type === 'Property';
  20. const getTagNameFromMemberExpression = (node) => {
  21. if (node.property.parent) {
  22. return `${node.property.parent.object.name}.${node.property.name}`;
  23. }
  24. // for eslint 3
  25. return `${node.object.name}.${node.property.name}`;
  26. };
  27. // ------------------------------------------------------------------------------
  28. // Rule Definition
  29. // ------------------------------------------------------------------------------
  30. const messages = {
  31. noSpreading: 'Prop spreading is forbidden',
  32. };
  33. /** @type {import('eslint').Rule.RuleModule} */
  34. module.exports = {
  35. meta: {
  36. docs: {
  37. description: 'Disallow JSX prop spreading',
  38. category: 'Best Practices',
  39. recommended: false,
  40. url: docsUrl('jsx-props-no-spreading'),
  41. },
  42. messages,
  43. schema: [{
  44. allOf: [{
  45. type: 'object',
  46. properties: {
  47. html: {
  48. enum: [OPTIONS.enforce, OPTIONS.ignore],
  49. },
  50. custom: {
  51. enum: [OPTIONS.enforce, OPTIONS.ignore],
  52. },
  53. explicitSpread: {
  54. enum: [OPTIONS.enforce, OPTIONS.ignore],
  55. },
  56. exceptions: {
  57. type: 'array',
  58. items: {
  59. type: 'string',
  60. uniqueItems: true,
  61. },
  62. },
  63. },
  64. }, {
  65. not: {
  66. type: 'object',
  67. required: ['html', 'custom'],
  68. properties: {
  69. html: {
  70. enum: [OPTIONS.ignore],
  71. },
  72. custom: {
  73. enum: [OPTIONS.ignore],
  74. },
  75. exceptions: {
  76. type: 'array',
  77. minItems: 0,
  78. maxItems: 0,
  79. },
  80. },
  81. },
  82. }],
  83. }],
  84. },
  85. create(context) {
  86. const configuration = context.options[0] || {};
  87. const ignoreHtmlTags = (configuration.html || DEFAULTS.html) === OPTIONS.ignore;
  88. const ignoreCustomTags = (configuration.custom || DEFAULTS.custom) === OPTIONS.ignore;
  89. const ignoreExplicitSpread = (configuration.explicitSpread || DEFAULTS.explicitSpread) === OPTIONS.ignore;
  90. const exceptions = configuration.exceptions || DEFAULTS.exceptions;
  91. return {
  92. JSXSpreadAttribute(node) {
  93. const jsxOpeningElement = node.parent.name;
  94. const type = jsxOpeningElement.type;
  95. let tagName;
  96. if (type === 'JSXIdentifier') {
  97. tagName = jsxOpeningElement.name;
  98. } else if (type === 'JSXMemberExpression') {
  99. tagName = getTagNameFromMemberExpression(jsxOpeningElement);
  100. } else {
  101. tagName = undefined;
  102. }
  103. const isHTMLTag = tagName && tagName[0] !== tagName[0].toUpperCase();
  104. const isCustomTag = tagName && (tagName[0] === tagName[0].toUpperCase() || tagName.includes('.'));
  105. if (
  106. isHTMLTag
  107. && ((ignoreHtmlTags && !isException(tagName, exceptions))
  108. || (!ignoreHtmlTags && isException(tagName, exceptions)))
  109. ) {
  110. return;
  111. }
  112. if (
  113. isCustomTag
  114. && ((ignoreCustomTags && !isException(tagName, exceptions))
  115. || (!ignoreCustomTags && isException(tagName, exceptions)))
  116. ) {
  117. return;
  118. }
  119. if (
  120. ignoreExplicitSpread
  121. && node.argument.type === 'ObjectExpression'
  122. && node.argument.properties.every(isProperty)
  123. ) {
  124. return;
  125. }
  126. report(context, messages.noSpreading, 'noSpreading', {
  127. node,
  128. });
  129. },
  130. };
  131. },
  132. };