jsx-sort-default-props.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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 propWrapperUtil = require('../util/propWrapper');
  9. // const propTypesSortUtil = require('../util/propTypesSort');
  10. // ------------------------------------------------------------------------------
  11. // Rule Definition
  12. // ------------------------------------------------------------------------------
  13. module.exports = {
  14. meta: {
  15. docs: {
  16. description: 'Enforce default props alphabetical sorting',
  17. category: 'Stylistic Issues',
  18. recommended: false,
  19. url: docsUrl('jsx-sort-default-props')
  20. },
  21. // fixable: 'code',
  22. messages: {
  23. propsNotSorted: 'Default prop types declarations should be sorted alphabetically'
  24. },
  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.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. context.report({
  106. node: curr,
  107. messageId: 'propsNotSorted'
  108. // fix
  109. });
  110. return prev;
  111. }
  112. return curr;
  113. }, declarations[0]);
  114. }
  115. function checkNode(node) {
  116. switch (node && node.type) {
  117. case 'ObjectExpression':
  118. checkSorted(node.properties);
  119. break;
  120. case 'Identifier': {
  121. const propTypesObject = findVariableByName(node.name);
  122. if (propTypesObject && propTypesObject.properties) {
  123. checkSorted(propTypesObject.properties);
  124. }
  125. break;
  126. }
  127. case 'CallExpression': {
  128. const innerNode = node.arguments && node.arguments[0];
  129. if (propWrapperUtil.isPropWrapperFunction(context, node.callee.name) && innerNode) {
  130. checkNode(innerNode);
  131. }
  132. break;
  133. }
  134. default:
  135. break;
  136. }
  137. }
  138. // --------------------------------------------------------------------------
  139. // Public API
  140. // --------------------------------------------------------------------------
  141. return {
  142. ClassProperty(node) {
  143. if (!isDefaultPropsDeclaration(node)) {
  144. return;
  145. }
  146. checkNode(node.value);
  147. },
  148. MemberExpression(node) {
  149. if (!isDefaultPropsDeclaration(node)) {
  150. return;
  151. }
  152. checkNode(node.parent.right);
  153. }
  154. };
  155. }
  156. };