prefer-exact-props.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. /**
  2. * @fileoverview Prefer exact proptype definitions
  3. */
  4. 'use strict';
  5. const Components = require('../util/Components');
  6. const docsUrl = require('../util/docsUrl');
  7. const propsUtil = require('../util/props');
  8. const propWrapperUtil = require('../util/propWrapper');
  9. const variableUtil = require('../util/variable');
  10. const report = require('../util/report');
  11. // -----------------------------------------------------------------------------
  12. // Rule Definition
  13. // -----------------------------------------------------------------------------
  14. const messages = {
  15. propTypes: 'Component propTypes should be exact by using {{exactPropWrappers}}.',
  16. flow: 'Component flow props should be set with exact objects.',
  17. };
  18. module.exports = {
  19. meta: {
  20. docs: {
  21. description: 'Prefer exact proptype definitions',
  22. category: 'Possible Errors',
  23. recommended: false,
  24. url: docsUrl('prefer-exact-props'),
  25. },
  26. messages,
  27. schema: [],
  28. },
  29. create: Components.detect((context, components, utils) => {
  30. const typeAliases = {};
  31. const exactWrappers = propWrapperUtil.getExactPropWrapperFunctions(context);
  32. const sourceCode = context.getSourceCode();
  33. function getPropTypesErrorMessage() {
  34. const formattedWrappers = propWrapperUtil.formatPropWrapperFunctions(exactWrappers);
  35. const message = exactWrappers.size > 1 ? `one of ${formattedWrappers}` : formattedWrappers;
  36. return { exactPropWrappers: message };
  37. }
  38. function isNonExactObjectTypeAnnotation(node) {
  39. return (
  40. node
  41. && node.type === 'ObjectTypeAnnotation'
  42. && node.properties.length > 0
  43. && !node.exact
  44. );
  45. }
  46. function hasNonExactObjectTypeAnnotation(node) {
  47. const typeAnnotation = node.typeAnnotation;
  48. return (
  49. typeAnnotation
  50. && typeAnnotation.typeAnnotation
  51. && isNonExactObjectTypeAnnotation(typeAnnotation.typeAnnotation)
  52. );
  53. }
  54. function hasGenericTypeAnnotation(node) {
  55. const typeAnnotation = node.typeAnnotation;
  56. return (
  57. typeAnnotation
  58. && typeAnnotation.typeAnnotation
  59. && typeAnnotation.typeAnnotation.type === 'GenericTypeAnnotation'
  60. );
  61. }
  62. function isNonEmptyObjectExpression(node) {
  63. return (
  64. node
  65. && node.type === 'ObjectExpression'
  66. && node.properties.length > 0
  67. );
  68. }
  69. function isNonExactPropWrapperFunction(node) {
  70. return (
  71. node
  72. && node.type === 'CallExpression'
  73. && !propWrapperUtil.isExactPropWrapperFunction(context, sourceCode.getText(node.callee))
  74. );
  75. }
  76. function reportPropTypesError(node) {
  77. report(context, messages.propTypes, 'propTypes', {
  78. node,
  79. data: getPropTypesErrorMessage(),
  80. });
  81. }
  82. function reportFlowError(node) {
  83. report(context, messages.flow, 'flow', {
  84. node,
  85. });
  86. }
  87. return {
  88. TypeAlias(node) {
  89. // working around an issue with eslint@3 and babel-eslint not finding the TypeAlias in scope
  90. typeAliases[node.id.name] = node;
  91. },
  92. 'ClassProperty, PropertyDefinition'(node) {
  93. if (!propsUtil.isPropTypesDeclaration(node)) {
  94. return;
  95. }
  96. if (hasNonExactObjectTypeAnnotation(node)) {
  97. reportFlowError(node);
  98. } else if (exactWrappers.size > 0 && isNonEmptyObjectExpression(node.value)) {
  99. reportPropTypesError(node);
  100. } else if (exactWrappers.size > 0 && isNonExactPropWrapperFunction(node.value)) {
  101. reportPropTypesError(node);
  102. }
  103. },
  104. Identifier(node) {
  105. if (!utils.getStatelessComponent(node.parent)) {
  106. return;
  107. }
  108. if (hasNonExactObjectTypeAnnotation(node)) {
  109. reportFlowError(node);
  110. } else if (hasGenericTypeAnnotation(node)) {
  111. const identifier = node.typeAnnotation.typeAnnotation.id.name;
  112. const typeAlias = typeAliases[identifier];
  113. const propsDefinition = typeAlias ? typeAlias.right : null;
  114. if (isNonExactObjectTypeAnnotation(propsDefinition)) {
  115. reportFlowError(node);
  116. }
  117. }
  118. },
  119. MemberExpression(node) {
  120. if (!propsUtil.isPropTypesDeclaration(node) || exactWrappers.size === 0) {
  121. return;
  122. }
  123. const right = node.parent.right;
  124. if (isNonEmptyObjectExpression(right)) {
  125. reportPropTypesError(node);
  126. } else if (isNonExactPropWrapperFunction(right)) {
  127. reportPropTypesError(node);
  128. } else if (right.type === 'Identifier') {
  129. const identifier = right.name;
  130. const propsDefinition = variableUtil.findVariableByName(context, identifier);
  131. if (isNonEmptyObjectExpression(propsDefinition)) {
  132. reportPropTypesError(node);
  133. } else if (isNonExactPropWrapperFunction(propsDefinition)) {
  134. reportPropTypesError(node);
  135. }
  136. }
  137. },
  138. };
  139. }),
  140. };