require-default-props.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. /**
  2. * @fileOverview Enforce a defaultProps definition for every prop that is not a required prop.
  3. * @author Vitor Balocco
  4. */
  5. 'use strict';
  6. const Components = require('../util/Components');
  7. const docsUrl = require('../util/docsUrl');
  8. const astUtil = require('../util/ast');
  9. // ------------------------------------------------------------------------------
  10. // Rule Definition
  11. // ------------------------------------------------------------------------------
  12. module.exports = {
  13. meta: {
  14. docs: {
  15. description: 'Enforce a defaultProps definition for every prop that is not a required prop.',
  16. category: 'Best Practices',
  17. url: docsUrl('require-default-props')
  18. },
  19. messages: {
  20. noDefaultWithRequired: 'propType "{{name}}" is required and should not have a defaultProps declaration.',
  21. shouldHaveDefault: 'propType "{{name}}" is not required, but has no corresponding defaultProps declaration.'
  22. },
  23. schema: [{
  24. type: 'object',
  25. properties: {
  26. forbidDefaultForRequired: {
  27. type: 'boolean'
  28. },
  29. ignoreFunctionalComponents: {
  30. type: 'boolean'
  31. }
  32. },
  33. additionalProperties: false
  34. }]
  35. },
  36. create: Components.detect((context, components) => {
  37. const configuration = context.options[0] || {};
  38. const forbidDefaultForRequired = configuration.forbidDefaultForRequired || false;
  39. const ignoreFunctionalComponents = configuration.ignoreFunctionalComponents || false;
  40. /**
  41. * Reports all propTypes passed in that don't have a defaultProps counterpart.
  42. * @param {Object[]} propTypes List of propTypes to check.
  43. * @param {Object} defaultProps Object of defaultProps to check. Keys are the props names.
  44. * @return {void}
  45. */
  46. function reportPropTypesWithoutDefault(propTypes, defaultProps) {
  47. // If this defaultProps is "unresolved", then we should ignore this component and not report
  48. // any errors for it, to avoid false-positives with e.g. external defaultProps declarations or spread operators.
  49. if (defaultProps === 'unresolved') {
  50. return;
  51. }
  52. Object.keys(propTypes).forEach((propName) => {
  53. const prop = propTypes[propName];
  54. if (prop.isRequired) {
  55. if (forbidDefaultForRequired && defaultProps[propName]) {
  56. context.report({
  57. node: prop.node,
  58. messageId: 'noDefaultWithRequired',
  59. data: {name: propName}
  60. });
  61. }
  62. return;
  63. }
  64. if (defaultProps[propName]) {
  65. return;
  66. }
  67. context.report({
  68. node: prop.node,
  69. messageId: 'shouldHaveDefault',
  70. data: {name: propName}
  71. });
  72. });
  73. }
  74. // --------------------------------------------------------------------------
  75. // Public API
  76. // --------------------------------------------------------------------------
  77. return {
  78. 'Program:exit'() {
  79. const list = components.list();
  80. Object.keys(list).filter((component) => {
  81. if (ignoreFunctionalComponents
  82. && (astUtil.isFunction(list[component].node) || astUtil.isFunctionLikeExpression(list[component].node))) {
  83. return false;
  84. }
  85. return list[component].declaredPropTypes;
  86. }).forEach((component) => {
  87. reportPropTypesWithoutDefault(
  88. list[component].declaredPropTypes,
  89. list[component].defaultProps || {}
  90. );
  91. });
  92. }
  93. };
  94. })
  95. };