jsx-props-no-spreading.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  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. module.exports = {
  34. meta: {
  35. docs: {
  36. description: 'Disallow JSX prop spreading',
  37. category: 'Best Practices',
  38. recommended: false,
  39. url: docsUrl('jsx-props-no-spreading'),
  40. },
  41. messages,
  42. schema: [{
  43. allOf: [{
  44. type: 'object',
  45. properties: {
  46. html: {
  47. enum: [OPTIONS.enforce, OPTIONS.ignore],
  48. },
  49. custom: {
  50. enum: [OPTIONS.enforce, OPTIONS.ignore],
  51. },
  52. exceptions: {
  53. type: 'array',
  54. items: {
  55. type: 'string',
  56. uniqueItems: true,
  57. },
  58. },
  59. },
  60. }, {
  61. not: {
  62. type: 'object',
  63. required: ['html', 'custom'],
  64. properties: {
  65. html: {
  66. enum: [OPTIONS.ignore],
  67. },
  68. custom: {
  69. enum: [OPTIONS.ignore],
  70. },
  71. exceptions: {
  72. type: 'array',
  73. minItems: 0,
  74. maxItems: 0,
  75. },
  76. },
  77. },
  78. }],
  79. }],
  80. },
  81. create(context) {
  82. const configuration = context.options[0] || {};
  83. const ignoreHtmlTags = (configuration.html || DEFAULTS.html) === OPTIONS.ignore;
  84. const ignoreCustomTags = (configuration.custom || DEFAULTS.custom) === OPTIONS.ignore;
  85. const ignoreExplicitSpread = (configuration.explicitSpread || DEFAULTS.explicitSpread) === OPTIONS.ignore;
  86. const exceptions = configuration.exceptions || DEFAULTS.exceptions;
  87. return {
  88. JSXSpreadAttribute(node) {
  89. const jsxOpeningElement = node.parent.name;
  90. const type = jsxOpeningElement.type;
  91. let tagName;
  92. if (type === 'JSXIdentifier') {
  93. tagName = jsxOpeningElement.name;
  94. } else if (type === 'JSXMemberExpression') {
  95. tagName = getTagNameFromMemberExpression(jsxOpeningElement);
  96. } else {
  97. tagName = undefined;
  98. }
  99. const isHTMLTag = tagName && tagName[0] !== tagName[0].toUpperCase();
  100. const isCustomTag = tagName && (tagName[0] === tagName[0].toUpperCase() || tagName.includes('.'));
  101. if (
  102. isHTMLTag
  103. && ((ignoreHtmlTags && !isException(tagName, exceptions))
  104. || (!ignoreHtmlTags && isException(tagName, exceptions)))
  105. ) {
  106. return;
  107. }
  108. if (
  109. isCustomTag
  110. && ((ignoreCustomTags && !isException(tagName, exceptions))
  111. || (!ignoreCustomTags && isException(tagName, exceptions)))
  112. ) {
  113. return;
  114. }
  115. if (
  116. ignoreExplicitSpread
  117. && node.argument.type === 'ObjectExpression'
  118. && node.argument.properties.every(isProperty)
  119. ) {
  120. return;
  121. }
  122. report(context, messages.noSpreading, 'noSpreading', {
  123. node,
  124. });
  125. },
  126. };
  127. },
  128. };