forbid-component-props.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /**
  2. * @fileoverview Forbid certain props on components
  3. * @author Joe Lencioni
  4. */
  5. 'use strict';
  6. const docsUrl = require('../util/docsUrl');
  7. const report = require('../util/report');
  8. // ------------------------------------------------------------------------------
  9. // Constants
  10. // ------------------------------------------------------------------------------
  11. const DEFAULTS = ['className', 'style'];
  12. // ------------------------------------------------------------------------------
  13. // Rule Definition
  14. // ------------------------------------------------------------------------------
  15. const messages = {
  16. propIsForbidden: 'Prop "{{prop}}" is forbidden on Components',
  17. };
  18. module.exports = {
  19. meta: {
  20. docs: {
  21. description: 'Disallow certain props on components',
  22. category: 'Best Practices',
  23. recommended: false,
  24. url: docsUrl('forbid-component-props'),
  25. },
  26. messages,
  27. schema: [{
  28. type: 'object',
  29. properties: {
  30. forbid: {
  31. type: 'array',
  32. items: {
  33. anyOf: [
  34. { type: 'string' },
  35. {
  36. type: 'object',
  37. properties: {
  38. propName: { type: 'string' },
  39. allowedFor: {
  40. type: 'array',
  41. uniqueItems: true,
  42. items: { type: 'string' },
  43. },
  44. message: { type: 'string' },
  45. },
  46. additionalProperties: false,
  47. },
  48. {
  49. type: 'object',
  50. properties: {
  51. propName: { type: 'string' },
  52. disallowedFor: {
  53. type: 'array',
  54. uniqueItems: true,
  55. minItems: 1,
  56. items: { type: 'string' },
  57. },
  58. message: { type: 'string' },
  59. },
  60. required: ['disallowedFor'],
  61. additionalProperties: false,
  62. },
  63. ],
  64. },
  65. },
  66. },
  67. }],
  68. },
  69. create(context) {
  70. const configuration = context.options[0] || {};
  71. const forbid = new Map((configuration.forbid || DEFAULTS).map((value) => {
  72. const propName = typeof value === 'string' ? value : value.propName;
  73. const options = {
  74. allowList: typeof value === 'string' ? [] : (value.allowedFor || []),
  75. disallowList: typeof value === 'string' ? [] : (value.disallowedFor || []),
  76. message: typeof value === 'string' ? null : value.message,
  77. };
  78. return [propName, options];
  79. }));
  80. function isForbidden(prop, tagName) {
  81. const options = forbid.get(prop);
  82. if (!options) {
  83. return false;
  84. }
  85. // disallowList should have a least one item (schema configuration)
  86. const isTagForbidden = options.disallowList.length > 0
  87. ? options.disallowList.indexOf(tagName) !== -1
  88. : options.allowList.indexOf(tagName) === -1;
  89. // if the tagName is undefined (`<this.something>`), we assume it's a forbidden element
  90. return typeof tagName === 'undefined' || isTagForbidden;
  91. }
  92. return {
  93. JSXAttribute(node) {
  94. const parentName = node.parent.name;
  95. // Extract a component name when using a "namespace", e.g. `<AntdLayout.Content />`.
  96. const tag = parentName.name || `${parentName.object.name}.${parentName.property.name}`;
  97. const componentName = parentName.name || parentName.property.name;
  98. if (componentName && typeof componentName[0] === 'string' && componentName[0] !== componentName[0].toUpperCase()) {
  99. // This is a DOM node, not a Component, so exit.
  100. return;
  101. }
  102. const prop = node.name.name;
  103. if (!isForbidden(prop, tag)) {
  104. return;
  105. }
  106. const customMessage = forbid.get(prop).message;
  107. report(context, customMessage || messages.propIsForbidden, !customMessage && 'propIsForbidden', {
  108. node,
  109. data: {
  110. prop,
  111. },
  112. });
  113. },
  114. };
  115. },
  116. };