jsx-sort-default-props.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. /**
  2. * @fileoverview Enforce default props alphabetical sorting
  3. * @author Vladimir Kattsov
  4. */
  5. 'use strict';
  6. const variableUtil = require('../util/variable');
  7. const docsUrl = require('../util/docsUrl');
  8. const report = require('../util/report');
  9. // ------------------------------------------------------------------------------
  10. // Rule Definition
  11. // ------------------------------------------------------------------------------
  12. const messages = {
  13. propsNotSorted: 'Default prop types declarations should be sorted alphabetically',
  14. };
  15. module.exports = {
  16. meta: {
  17. docs: {
  18. description: 'Enforce defaultProps declarations alphabetical sorting',
  19. category: 'Stylistic Issues',
  20. recommended: false,
  21. url: docsUrl('jsx-sort-default-props'),
  22. },
  23. // fixable: 'code',
  24. messages,
  25. schema: [{
  26. type: 'object',
  27. properties: {
  28. ignoreCase: {
  29. type: 'boolean',
  30. },
  31. },
  32. additionalProperties: false,
  33. }],
  34. },
  35. create(context) {
  36. const configuration = context.options[0] || {};
  37. const ignoreCase = configuration.ignoreCase || false;
  38. /**
  39. * Get properties name
  40. * @param {Object} node - Property.
  41. * @returns {String} Property name.
  42. */
  43. function getPropertyName(node) {
  44. if (node.key || ['MethodDefinition', 'Property'].indexOf(node.type) !== -1) {
  45. return node.key.name;
  46. }
  47. if (node.type === 'MemberExpression') {
  48. return node.property.name;
  49. // Special case for class properties
  50. // (babel-eslint@5 does not expose property name so we have to rely on tokens)
  51. }
  52. if (node.type === 'ClassProperty') {
  53. const tokens = context.getSourceCode().getFirstTokens(node, 2);
  54. return tokens[1] && tokens[1].type === 'Identifier' ? tokens[1].value : tokens[0].value;
  55. }
  56. return '';
  57. }
  58. /**
  59. * Checks if the Identifier node passed in looks like a defaultProps declaration.
  60. * @param {ASTNode} node The node to check. Must be an Identifier node.
  61. * @returns {Boolean} `true` if the node is a defaultProps declaration, `false` if not
  62. */
  63. function isDefaultPropsDeclaration(node) {
  64. const propName = getPropertyName(node);
  65. return (propName === 'defaultProps' || propName === 'getDefaultProps');
  66. }
  67. function getKey(node) {
  68. return context.getSourceCode().getText(node.key || node.argument);
  69. }
  70. /**
  71. * Find a variable by name in the current scope.
  72. * @param {string} name Name of the variable to look for.
  73. * @returns {ASTNode|null} Return null if the variable could not be found, ASTNode otherwise.
  74. */
  75. function findVariableByName(name) {
  76. const variable = variableUtil.variablesInScope(context).find((item) => item.name === name);
  77. if (!variable || !variable.defs[0] || !variable.defs[0].node) {
  78. return null;
  79. }
  80. if (variable.defs[0].node.type === 'TypeAlias') {
  81. return variable.defs[0].node.right;
  82. }
  83. return variable.defs[0].node.init;
  84. }
  85. /**
  86. * Checks if defaultProps declarations are sorted
  87. * @param {Array} declarations The array of AST nodes being checked.
  88. * @returns {void}
  89. */
  90. function checkSorted(declarations) {
  91. // function fix(fixer) {
  92. // return propTypesSortUtil.fixPropTypesSort(fixer, context, declarations, ignoreCase);
  93. // }
  94. declarations.reduce((prev, curr, idx, decls) => {
  95. if (/Spread(?:Property|Element)$/.test(curr.type)) {
  96. return decls[idx + 1];
  97. }
  98. let prevPropName = getKey(prev);
  99. let currentPropName = getKey(curr);
  100. if (ignoreCase) {
  101. prevPropName = prevPropName.toLowerCase();
  102. currentPropName = currentPropName.toLowerCase();
  103. }
  104. if (currentPropName < prevPropName) {
  105. report(context, messages.propsNotSorted, 'propsNotSorted', {
  106. node: curr,
  107. // fix
  108. });
  109. return prev;
  110. }
  111. return curr;
  112. }, declarations[0]);
  113. }
  114. function checkNode(node) {
  115. if (!node) {
  116. return;
  117. }
  118. if (node.type === 'ObjectExpression') {
  119. checkSorted(node.properties);
  120. } else if (node.type === 'Identifier') {
  121. const propTypesObject = findVariableByName(node.name);
  122. if (propTypesObject && propTypesObject.properties) {
  123. checkSorted(propTypesObject.properties);
  124. }
  125. }
  126. }
  127. // --------------------------------------------------------------------------
  128. // Public API
  129. // --------------------------------------------------------------------------
  130. return {
  131. 'ClassProperty, PropertyDefinition'(node) {
  132. if (!isDefaultPropsDeclaration(node)) {
  133. return;
  134. }
  135. checkNode(node.value);
  136. },
  137. MemberExpression(node) {
  138. if (!isDefaultPropsDeclaration(node)) {
  139. return;
  140. }
  141. checkNode(node.parent.right);
  142. },
  143. };
  144. },
  145. };