sort-default-props.js 5.1 KB

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