massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / no-duplicate-imports.js
1 /**
2  * @fileoverview Restrict usage of duplicate imports.
3  * @author Simen Bekkhus
4  */
5 "use strict";
6
7 //------------------------------------------------------------------------------
8 // Helpers
9 //------------------------------------------------------------------------------
10
11 const NAMED_TYPES = ["ImportSpecifier", "ExportSpecifier"];
12 const NAMESPACE_TYPES = [
13     "ImportNamespaceSpecifier",
14     "ExportNamespaceSpecifier"
15 ];
16
17 //------------------------------------------------------------------------------
18 // Rule Definition
19 //------------------------------------------------------------------------------
20
21 /**
22  * Check if an import/export type belongs to (ImportSpecifier|ExportSpecifier) or (ImportNamespaceSpecifier|ExportNamespaceSpecifier).
23  * @param {string} importExportType An import/export type to check.
24  * @param {string} type Can be "named" or "namespace"
25  * @returns {boolean} True if import/export type belongs to (ImportSpecifier|ExportSpecifier) or (ImportNamespaceSpecifier|ExportNamespaceSpecifier) and false if it doesn't.
26  */
27 function isImportExportSpecifier(importExportType, type) {
28     const arrayToCheck = type === "named" ? NAMED_TYPES : NAMESPACE_TYPES;
29
30     return arrayToCheck.includes(importExportType);
31 }
32
33 /**
34  * Return the type of (import|export).
35  * @param {ASTNode} node A node to get.
36  * @returns {string} The type of the (import|export).
37  */
38 function getImportExportType(node) {
39     if (node.specifiers && node.specifiers.length > 0) {
40         const nodeSpecifiers = node.specifiers;
41         const index = nodeSpecifiers.findIndex(
42             ({ type }) =>
43                 isImportExportSpecifier(type, "named") ||
44                 isImportExportSpecifier(type, "namespace")
45         );
46         const i = index > -1 ? index : 0;
47
48         return nodeSpecifiers[i].type;
49     }
50     if (node.type === "ExportAllDeclaration") {
51         if (node.exported) {
52             return "ExportNamespaceSpecifier";
53         }
54         return "ExportAll";
55     }
56     return "SideEffectImport";
57 }
58
59 /**
60  * Returns a boolean indicates if two (import|export) can be merged
61  * @param {ASTNode} node1 A node to check.
62  * @param {ASTNode} node2 A node to check.
63  * @returns {boolean} True if two (import|export) can be merged, false if they can't.
64  */
65 function isImportExportCanBeMerged(node1, node2) {
66     const importExportType1 = getImportExportType(node1);
67     const importExportType2 = getImportExportType(node2);
68
69     if (
70         (importExportType1 === "ExportAll" &&
71             importExportType2 !== "ExportAll" &&
72             importExportType2 !== "SideEffectImport") ||
73         (importExportType1 !== "ExportAll" &&
74             importExportType1 !== "SideEffectImport" &&
75             importExportType2 === "ExportAll")
76     ) {
77         return false;
78     }
79     if (
80         (isImportExportSpecifier(importExportType1, "namespace") &&
81             isImportExportSpecifier(importExportType2, "named")) ||
82         (isImportExportSpecifier(importExportType2, "namespace") &&
83             isImportExportSpecifier(importExportType1, "named"))
84     ) {
85         return false;
86     }
87     return true;
88 }
89
90 /**
91  * Returns a boolean if we should report (import|export).
92  * @param {ASTNode} node A node to be reported or not.
93  * @param {[ASTNode]} previousNodes An array contains previous nodes of the module imported or exported.
94  * @returns {boolean} True if the (import|export) should be reported.
95  */
96 function shouldReportImportExport(node, previousNodes) {
97     let i = 0;
98
99     while (i < previousNodes.length) {
100         if (isImportExportCanBeMerged(node, previousNodes[i])) {
101             return true;
102         }
103         i++;
104     }
105     return false;
106 }
107
108 /**
109  * Returns array contains only nodes with declarations types equal to type.
110  * @param {[{node: ASTNode, declarationType: string}]} nodes An array contains objects, each object contains a node and a declaration type.
111  * @param {string} type Declaration type.
112  * @returns {[ASTNode]} An array contains only nodes with declarations types equal to type.
113  */
114 function getNodesByDeclarationType(nodes, type) {
115     return nodes
116         .filter(({ declarationType }) => declarationType === type)
117         .map(({ node }) => node);
118 }
119
120 /**
121  * Returns the name of the module imported or re-exported.
122  * @param {ASTNode} node A node to get.
123  * @returns {string} The name of the module, or empty string if no name.
124  */
125 function getModule(node) {
126     if (node && node.source && node.source.value) {
127         return node.source.value.trim();
128     }
129     return "";
130 }
131
132 /**
133  * Checks if the (import|export) can be merged with at least one import or one export, and reports if so.
134  * @param {RuleContext} context The ESLint rule context object.
135  * @param {ASTNode} node A node to get.
136  * @param {Map} modules A Map object contains as a key a module name and as value an array contains objects, each object contains a node and a declaration type.
137  * @param {string} declarationType A declaration type can be an import or export.
138  * @param {boolean} includeExports Whether or not to check for exports in addition to imports.
139  * @returns {void} No return value.
140  */
141 function checkAndReport(
142     context,
143     node,
144     modules,
145     declarationType,
146     includeExports
147 ) {
148     const module = getModule(node);
149
150     if (modules.has(module)) {
151         const previousNodes = modules.get(module);
152         const messagesIds = [];
153         const importNodes = getNodesByDeclarationType(previousNodes, "import");
154         let exportNodes;
155
156         if (includeExports) {
157             exportNodes = getNodesByDeclarationType(previousNodes, "export");
158         }
159         if (declarationType === "import") {
160             if (shouldReportImportExport(node, importNodes)) {
161                 messagesIds.push("import");
162             }
163             if (includeExports) {
164                 if (shouldReportImportExport(node, exportNodes)) {
165                     messagesIds.push("importAs");
166                 }
167             }
168         } else if (declarationType === "export") {
169             if (shouldReportImportExport(node, exportNodes)) {
170                 messagesIds.push("export");
171             }
172             if (shouldReportImportExport(node, importNodes)) {
173                 messagesIds.push("exportAs");
174             }
175         }
176         messagesIds.forEach(messageId =>
177             context.report({
178                 node,
179                 messageId,
180                 data: {
181                     module
182                 }
183             }));
184     }
185 }
186
187 /**
188  * @callback nodeCallback
189  * @param {ASTNode} node A node to handle.
190  */
191
192 /**
193  * Returns a function handling the (imports|exports) of a given file
194  * @param {RuleContext} context The ESLint rule context object.
195  * @param {Map} modules A Map object contains as a key a module name and as value an array contains objects, each object contains a node and a declaration type.
196  * @param {string} declarationType A declaration type can be an import or export.
197  * @param {boolean} includeExports Whether or not to check for exports in addition to imports.
198  * @returns {nodeCallback} A function passed to ESLint to handle the statement.
199  */
200 function handleImportsExports(
201     context,
202     modules,
203     declarationType,
204     includeExports
205 ) {
206     return function(node) {
207         const module = getModule(node);
208
209         if (module) {
210             checkAndReport(
211                 context,
212                 node,
213                 modules,
214                 declarationType,
215                 includeExports
216             );
217             const currentNode = { node, declarationType };
218             let nodes = [currentNode];
219
220             if (modules.has(module)) {
221                 const previousNodes = modules.get(module);
222
223                 nodes = [...previousNodes, currentNode];
224             }
225             modules.set(module, nodes);
226         }
227     };
228 }
229
230 module.exports = {
231     meta: {
232         type: "problem",
233
234         docs: {
235             description: "disallow duplicate module imports",
236             category: "ECMAScript 6",
237             recommended: false,
238             url: "https://eslint.org/docs/rules/no-duplicate-imports"
239         },
240
241         schema: [
242             {
243                 type: "object",
244                 properties: {
245                     includeExports: {
246                         type: "boolean",
247                         default: false
248                     }
249                 },
250                 additionalProperties: false
251             }
252         ],
253
254         messages: {
255             import: "'{{module}}' import is duplicated.",
256             importAs: "'{{module}}' import is duplicated as export.",
257             export: "'{{module}}' export is duplicated.",
258             exportAs: "'{{module}}' export is duplicated as import."
259         }
260     },
261
262     create(context) {
263         const includeExports = (context.options[0] || {}).includeExports,
264             modules = new Map();
265         const handlers = {
266             ImportDeclaration: handleImportsExports(
267                 context,
268                 modules,
269                 "import",
270                 includeExports
271             )
272         };
273
274         if (includeExports) {
275             handlers.ExportNamedDeclaration = handleImportsExports(
276                 context,
277                 modules,
278                 "export",
279                 includeExports
280             );
281             handlers.ExportAllDeclaration = handleImportsExports(
282                 context,
283                 modules,
284                 "export",
285                 includeExports
286             );
287         }
288         return handlers;
289     }
290 };