.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / sort-imports.js
1 /**
2  * @fileoverview Rule to require sorting of import declarations
3  * @author Christian Schuller
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Rule Definition
10 //------------------------------------------------------------------------------
11
12 module.exports = {
13     meta: {
14         type: "suggestion",
15
16         docs: {
17             description: "enforce sorted import declarations within modules",
18             category: "ECMAScript 6",
19             recommended: false,
20             url: "https://eslint.org/docs/rules/sort-imports"
21         },
22
23         schema: [
24             {
25                 type: "object",
26                 properties: {
27                     ignoreCase: {
28                         type: "boolean",
29                         default: false
30                     },
31                     memberSyntaxSortOrder: {
32                         type: "array",
33                         items: {
34                             enum: ["none", "all", "multiple", "single"]
35                         },
36                         uniqueItems: true,
37                         minItems: 4,
38                         maxItems: 4
39                     },
40                     ignoreDeclarationSort: {
41                         type: "boolean",
42                         default: false
43                     },
44                     ignoreMemberSort: {
45                         type: "boolean",
46                         default: false
47                     },
48                     allowSeparatedGroups: {
49                         type: "boolean",
50                         default: false
51                     }
52                 },
53                 additionalProperties: false
54             }
55         ],
56
57         fixable: "code",
58
59         messages: {
60             sortImportsAlphabetically: "Imports should be sorted alphabetically.",
61             sortMembersAlphabetically: "Member '{{memberName}}' of the import declaration should be sorted alphabetically.",
62             unexpectedSyntaxOrder: "Expected '{{syntaxA}}' syntax before '{{syntaxB}}' syntax."
63         }
64     },
65
66     create(context) {
67
68         const configuration = context.options[0] || {},
69             ignoreCase = configuration.ignoreCase || false,
70             ignoreDeclarationSort = configuration.ignoreDeclarationSort || false,
71             ignoreMemberSort = configuration.ignoreMemberSort || false,
72             memberSyntaxSortOrder = configuration.memberSyntaxSortOrder || ["none", "all", "multiple", "single"],
73             allowSeparatedGroups = configuration.allowSeparatedGroups || false,
74             sourceCode = context.getSourceCode();
75         let previousDeclaration = null;
76
77         /**
78          * Gets the used member syntax style.
79          *
80          * import "my-module.js" --> none
81          * import * as myModule from "my-module.js" --> all
82          * import {myMember} from "my-module.js" --> single
83          * import {foo, bar} from  "my-module.js" --> multiple
84          * @param {ASTNode} node the ImportDeclaration node.
85          * @returns {string} used member parameter style, ["all", "multiple", "single"]
86          */
87         function usedMemberSyntax(node) {
88             if (node.specifiers.length === 0) {
89                 return "none";
90             }
91             if (node.specifiers[0].type === "ImportNamespaceSpecifier") {
92                 return "all";
93             }
94             if (node.specifiers.length === 1) {
95                 return "single";
96             }
97             return "multiple";
98
99         }
100
101         /**
102          * Gets the group by member parameter index for given declaration.
103          * @param {ASTNode} node the ImportDeclaration node.
104          * @returns {number} the declaration group by member index.
105          */
106         function getMemberParameterGroupIndex(node) {
107             return memberSyntaxSortOrder.indexOf(usedMemberSyntax(node));
108         }
109
110         /**
111          * Gets the local name of the first imported module.
112          * @param {ASTNode} node the ImportDeclaration node.
113          * @returns {?string} the local name of the first imported module.
114          */
115         function getFirstLocalMemberName(node) {
116             if (node.specifiers[0]) {
117                 return node.specifiers[0].local.name;
118             }
119             return null;
120
121         }
122
123         /**
124          * Calculates number of lines between two nodes. It is assumed that the given `left` node appears before
125          * the given `right` node in the source code. Lines are counted from the end of the `left` node till the
126          * start of the `right` node. If the given nodes are on the same line, it returns `0`, same as if they were
127          * on two consecutive lines.
128          * @param {ASTNode} left node that appears before the given `right` node.
129          * @param {ASTNode} right node that appears after the given `left` node.
130          * @returns {number} number of lines between nodes.
131          */
132         function getNumberOfLinesBetween(left, right) {
133             return Math.max(right.loc.start.line - left.loc.end.line - 1, 0);
134         }
135
136         return {
137             ImportDeclaration(node) {
138                 if (!ignoreDeclarationSort) {
139                     if (
140                         previousDeclaration &&
141                         allowSeparatedGroups &&
142                         getNumberOfLinesBetween(previousDeclaration, node) > 0
143                     ) {
144
145                         // reset declaration sort
146                         previousDeclaration = null;
147                     }
148
149                     if (previousDeclaration) {
150                         const currentMemberSyntaxGroupIndex = getMemberParameterGroupIndex(node),
151                             previousMemberSyntaxGroupIndex = getMemberParameterGroupIndex(previousDeclaration);
152                         let currentLocalMemberName = getFirstLocalMemberName(node),
153                             previousLocalMemberName = getFirstLocalMemberName(previousDeclaration);
154
155                         if (ignoreCase) {
156                             previousLocalMemberName = previousLocalMemberName && previousLocalMemberName.toLowerCase();
157                             currentLocalMemberName = currentLocalMemberName && currentLocalMemberName.toLowerCase();
158                         }
159
160                         /*
161                          * When the current declaration uses a different member syntax,
162                          * then check if the ordering is correct.
163                          * Otherwise, make a default string compare (like rule sort-vars to be consistent) of the first used local member name.
164                          */
165                         if (currentMemberSyntaxGroupIndex !== previousMemberSyntaxGroupIndex) {
166                             if (currentMemberSyntaxGroupIndex < previousMemberSyntaxGroupIndex) {
167                                 context.report({
168                                     node,
169                                     messageId: "unexpectedSyntaxOrder",
170                                     data: {
171                                         syntaxA: memberSyntaxSortOrder[currentMemberSyntaxGroupIndex],
172                                         syntaxB: memberSyntaxSortOrder[previousMemberSyntaxGroupIndex]
173                                     }
174                                 });
175                             }
176                         } else {
177                             if (previousLocalMemberName &&
178                                 currentLocalMemberName &&
179                                 currentLocalMemberName < previousLocalMemberName
180                             ) {
181                                 context.report({
182                                     node,
183                                     messageId: "sortImportsAlphabetically"
184                                 });
185                             }
186                         }
187                     }
188
189                     previousDeclaration = node;
190                 }
191
192                 if (!ignoreMemberSort) {
193                     const importSpecifiers = node.specifiers.filter(specifier => specifier.type === "ImportSpecifier");
194                     const getSortableName = ignoreCase ? specifier => specifier.local.name.toLowerCase() : specifier => specifier.local.name;
195                     const firstUnsortedIndex = importSpecifiers.map(getSortableName).findIndex((name, index, array) => array[index - 1] > name);
196
197                     if (firstUnsortedIndex !== -1) {
198                         context.report({
199                             node: importSpecifiers[firstUnsortedIndex],
200                             messageId: "sortMembersAlphabetically",
201                             data: { memberName: importSpecifiers[firstUnsortedIndex].local.name },
202                             fix(fixer) {
203                                 if (importSpecifiers.some(specifier =>
204                                     sourceCode.getCommentsBefore(specifier).length || sourceCode.getCommentsAfter(specifier).length)) {
205
206                                     // If there are comments in the ImportSpecifier list, don't rearrange the specifiers.
207                                     return null;
208                                 }
209
210                                 return fixer.replaceTextRange(
211                                     [importSpecifiers[0].range[0], importSpecifiers[importSpecifiers.length - 1].range[1]],
212                                     importSpecifiers
213
214                                         // Clone the importSpecifiers array to avoid mutating it
215                                         .slice()
216
217                                         // Sort the array into the desired order
218                                         .sort((specifierA, specifierB) => {
219                                             const aName = getSortableName(specifierA);
220                                             const bName = getSortableName(specifierB);
221
222                                             return aName > bName ? 1 : -1;
223                                         })
224
225                                         // Build a string out of the sorted list of import specifiers and the text between the originals
226                                         .reduce((sourceText, specifier, index) => {
227                                             const textAfterSpecifier = index === importSpecifiers.length - 1
228                                                 ? ""
229                                                 : sourceCode.getText().slice(importSpecifiers[index].range[1], importSpecifiers[index + 1].range[0]);
230
231                                             return sourceText + sourceCode.getText(specifier) + textAfterSpecifier;
232                                         }, "")
233                                 );
234                             }
235                         });
236                     }
237                 }
238             }
239         };
240     }
241 };