Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

sort-imports.js 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. /**
  2. * @fileoverview Rule to require sorting of import declarations
  3. * @author Christian Schuller
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. module.exports = {
  10. meta: {
  11. type: "suggestion",
  12. docs: {
  13. description: "enforce sorted import declarations within modules",
  14. category: "ECMAScript 6",
  15. recommended: false,
  16. url: "https://eslint.org/docs/rules/sort-imports"
  17. },
  18. schema: [
  19. {
  20. type: "object",
  21. properties: {
  22. ignoreCase: {
  23. type: "boolean"
  24. },
  25. memberSyntaxSortOrder: {
  26. type: "array",
  27. items: {
  28. enum: ["none", "all", "multiple", "single"]
  29. },
  30. uniqueItems: true,
  31. minItems: 4,
  32. maxItems: 4
  33. },
  34. ignoreMemberSort: {
  35. type: "boolean"
  36. }
  37. },
  38. additionalProperties: false
  39. }
  40. ],
  41. fixable: "code"
  42. },
  43. create(context) {
  44. const configuration = context.options[0] || {},
  45. ignoreCase = configuration.ignoreCase || false,
  46. ignoreMemberSort = configuration.ignoreMemberSort || false,
  47. memberSyntaxSortOrder = configuration.memberSyntaxSortOrder || ["none", "all", "multiple", "single"],
  48. sourceCode = context.getSourceCode();
  49. let previousDeclaration = null;
  50. /**
  51. * Gets the used member syntax style.
  52. *
  53. * import "my-module.js" --> none
  54. * import * as myModule from "my-module.js" --> all
  55. * import {myMember} from "my-module.js" --> single
  56. * import {foo, bar} from "my-module.js" --> multiple
  57. *
  58. * @param {ASTNode} node - the ImportDeclaration node.
  59. * @returns {string} used member parameter style, ["all", "multiple", "single"]
  60. */
  61. function usedMemberSyntax(node) {
  62. if (node.specifiers.length === 0) {
  63. return "none";
  64. }
  65. if (node.specifiers[0].type === "ImportNamespaceSpecifier") {
  66. return "all";
  67. }
  68. if (node.specifiers.length === 1) {
  69. return "single";
  70. }
  71. return "multiple";
  72. }
  73. /**
  74. * Gets the group by member parameter index for given declaration.
  75. * @param {ASTNode} node - the ImportDeclaration node.
  76. * @returns {number} the declaration group by member index.
  77. */
  78. function getMemberParameterGroupIndex(node) {
  79. return memberSyntaxSortOrder.indexOf(usedMemberSyntax(node));
  80. }
  81. /**
  82. * Gets the local name of the first imported module.
  83. * @param {ASTNode} node - the ImportDeclaration node.
  84. * @returns {?string} the local name of the first imported module.
  85. */
  86. function getFirstLocalMemberName(node) {
  87. if (node.specifiers[0]) {
  88. return node.specifiers[0].local.name;
  89. }
  90. return null;
  91. }
  92. return {
  93. ImportDeclaration(node) {
  94. if (previousDeclaration) {
  95. const currentMemberSyntaxGroupIndex = getMemberParameterGroupIndex(node),
  96. previousMemberSyntaxGroupIndex = getMemberParameterGroupIndex(previousDeclaration);
  97. let currentLocalMemberName = getFirstLocalMemberName(node),
  98. previousLocalMemberName = getFirstLocalMemberName(previousDeclaration);
  99. if (ignoreCase) {
  100. previousLocalMemberName = previousLocalMemberName && previousLocalMemberName.toLowerCase();
  101. currentLocalMemberName = currentLocalMemberName && currentLocalMemberName.toLowerCase();
  102. }
  103. /*
  104. * When the current declaration uses a different member syntax,
  105. * then check if the ordering is correct.
  106. * Otherwise, make a default string compare (like rule sort-vars to be consistent) of the first used local member name.
  107. */
  108. if (currentMemberSyntaxGroupIndex !== previousMemberSyntaxGroupIndex) {
  109. if (currentMemberSyntaxGroupIndex < previousMemberSyntaxGroupIndex) {
  110. context.report({
  111. node,
  112. message: "Expected '{{syntaxA}}' syntax before '{{syntaxB}}' syntax.",
  113. data: {
  114. syntaxA: memberSyntaxSortOrder[currentMemberSyntaxGroupIndex],
  115. syntaxB: memberSyntaxSortOrder[previousMemberSyntaxGroupIndex]
  116. }
  117. });
  118. }
  119. } else {
  120. if (previousLocalMemberName &&
  121. currentLocalMemberName &&
  122. currentLocalMemberName < previousLocalMemberName
  123. ) {
  124. context.report({
  125. node,
  126. message: "Imports should be sorted alphabetically."
  127. });
  128. }
  129. }
  130. }
  131. if (!ignoreMemberSort) {
  132. const importSpecifiers = node.specifiers.filter(specifier => specifier.type === "ImportSpecifier");
  133. const getSortableName = ignoreCase ? specifier => specifier.local.name.toLowerCase() : specifier => specifier.local.name;
  134. const firstUnsortedIndex = importSpecifiers.map(getSortableName).findIndex((name, index, array) => array[index - 1] > name);
  135. if (firstUnsortedIndex !== -1) {
  136. context.report({
  137. node: importSpecifiers[firstUnsortedIndex],
  138. message: "Member '{{memberName}}' of the import declaration should be sorted alphabetically.",
  139. data: { memberName: importSpecifiers[firstUnsortedIndex].local.name },
  140. fix(fixer) {
  141. if (importSpecifiers.some(specifier =>
  142. sourceCode.getCommentsBefore(specifier).length || sourceCode.getCommentsAfter(specifier).length)) {
  143. // If there are comments in the ImportSpecifier list, don't rearrange the specifiers.
  144. return null;
  145. }
  146. return fixer.replaceTextRange(
  147. [importSpecifiers[0].range[0], importSpecifiers[importSpecifiers.length - 1].range[1]],
  148. importSpecifiers
  149. // Clone the importSpecifiers array to avoid mutating it
  150. .slice()
  151. // Sort the array into the desired order
  152. .sort((specifierA, specifierB) => {
  153. const aName = getSortableName(specifierA);
  154. const bName = getSortableName(specifierB);
  155. return aName > bName ? 1 : -1;
  156. })
  157. // Build a string out of the sorted list of import specifiers and the text between the originals
  158. .reduce((sourceText, specifier, index) => {
  159. const textAfterSpecifier = index === importSpecifiers.length - 1
  160. ? ""
  161. : sourceCode.getText().slice(importSpecifiers[index].range[1], importSpecifiers[index + 1].range[0]);
  162. return sourceText + sourceCode.getText(specifier) + textAfterSpecifier;
  163. }, "")
  164. );
  165. }
  166. });
  167. }
  168. }
  169. previousDeclaration = node;
  170. }
  171. };
  172. }
  173. };