forbid-component-props.js 3.2 KB

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