]> git.proxmox.com Git - pve-eslint.git/blame - eslint/lib/rules/no-restricted-imports.js
import 8.3.0 source
[pve-eslint.git] / eslint / lib / rules / no-restricted-imports.js
CommitLineData
eb39fafa
DC
1/**
2 * @fileoverview Restrict usage of specified node imports.
3 * @author Guy Ellis
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Rule Definition
9//------------------------------------------------------------------------------
10
11const ignore = require("ignore");
12
eb39fafa
DC
13const arrayOfStringsOrObjects = {
14 type: "array",
15 items: {
16 anyOf: [
17 { type: "string" },
18 {
19 type: "object",
20 properties: {
21 name: { type: "string" },
22 message: {
23 type: "string",
24 minLength: 1
25 },
26 importNames: {
27 type: "array",
28 items: {
29 type: "string"
30 }
31 }
32 },
33 additionalProperties: false,
34 required: ["name"]
35 }
36 ]
37 },
38 uniqueItems: true
39};
40
5422a9cc
TL
41const arrayOfStringsOrObjectPatterns = {
42 anyOf: [
43 {
44 type: "array",
45 items: {
46 type: "string"
47 },
48 uniqueItems: true
49 },
50 {
51 type: "array",
52 items: {
53 type: "object",
54 properties: {
55 group: {
56 type: "array",
57 items: {
58 type: "string"
59 },
60 minItems: 1,
61 uniqueItems: true
62 },
63 message: {
64 type: "string",
65 minLength: 1
66 }
67 },
68 additionalProperties: false,
69 required: ["group"]
70 },
71 uniqueItems: true
72 }
73 ]
74};
75
eb39fafa
DC
76module.exports = {
77 meta: {
78 type: "suggestion",
79
80 docs: {
81 description: "disallow specified modules when loaded by `import`",
eb39fafa
DC
82 recommended: false,
83 url: "https://eslint.org/docs/rules/no-restricted-imports"
84 },
85
86 messages: {
87 path: "'{{importSource}}' import is restricted from being used.",
609c276f 88 // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
eb39fafa
DC
89 pathWithCustomMessage: "'{{importSource}}' import is restricted from being used. {{customMessage}}",
90
91 patterns: "'{{importSource}}' import is restricted from being used by a pattern.",
609c276f 92 // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
5422a9cc 93 patternWithCustomMessage: "'{{importSource}}' import is restricted from being used by a pattern. {{customMessage}}",
eb39fafa
DC
94
95 everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.",
609c276f 96 // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
eb39fafa
DC
97 everythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted. {{customMessage}}",
98
99 importName: "'{{importName}}' import from '{{importSource}}' is restricted.",
609c276f 100 // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period
eb39fafa
DC
101 importNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}"
102 },
103
104 schema: {
105 anyOf: [
106 arrayOfStringsOrObjects,
107 {
108 type: "array",
109 items: [{
110 type: "object",
111 properties: {
112 paths: arrayOfStringsOrObjects,
5422a9cc 113 patterns: arrayOfStringsOrObjectPatterns
eb39fafa
DC
114 },
115 additionalProperties: false
116 }],
117 additionalItems: false
118 }
119 ]
120 }
121 },
122
123 create(context) {
124 const sourceCode = context.getSourceCode();
125 const options = Array.isArray(context.options) ? context.options : [];
126 const isPathAndPatternsObject =
127 typeof options[0] === "object" &&
128 (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns"));
129
130 const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
eb39fafa
DC
131 const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
132 if (typeof importSource === "string") {
133 memo[importSource] = { message: null };
134 } else {
135 memo[importSource.name] = {
136 message: importSource.message,
137 importNames: importSource.importNames
138 };
139 }
140 return memo;
141 }, {});
142
5422a9cc
TL
143 // Handle patterns too, either as strings or groups
144 const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
145 const restrictedPatternGroups = restrictedPatterns.length > 0 && typeof restrictedPatterns[0] === "string"
146 ? [{ matcher: ignore().add(restrictedPatterns) }]
147 : restrictedPatterns.map(({ group, message }) => ({ matcher: ignore().add(group), customMessage: message }));
148
609c276f 149 // if no imports are restricted we don't need to check
5422a9cc
TL
150 if (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) {
151 return {};
152 }
eb39fafa
DC
153
154 /**
155 * Report a restricted path.
156 * @param {string} importSource path of the import
157 * @param {Map<string,Object[]>} importNames Map of import names that are being imported
158 * @param {node} node representing the restricted path reference
159 * @returns {void}
160 * @private
161 */
162 function checkRestrictedPathAndReport(importSource, importNames, node) {
163 if (!Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) {
164 return;
165 }
166
167 const customMessage = restrictedPathMessages[importSource].message;
168 const restrictedImportNames = restrictedPathMessages[importSource].importNames;
169
170 if (restrictedImportNames) {
171 if (importNames.has("*")) {
172 const specifierData = importNames.get("*")[0];
173
174 context.report({
175 node,
176 messageId: customMessage ? "everythingWithCustomMessage" : "everything",
177 loc: specifierData.loc,
178 data: {
179 importSource,
180 importNames: restrictedImportNames,
181 customMessage
182 }
183 });
184 }
185
186 restrictedImportNames.forEach(importName => {
187 if (importNames.has(importName)) {
188 const specifiers = importNames.get(importName);
189
190 specifiers.forEach(specifier => {
191 context.report({
192 node,
193 messageId: customMessage ? "importNameWithCustomMessage" : "importName",
194 loc: specifier.loc,
195 data: {
196 importSource,
197 customMessage,
198 importName
199 }
200 });
201 });
202 }
203 });
204 } else {
205 context.report({
206 node,
207 messageId: customMessage ? "pathWithCustomMessage" : "path",
208 data: {
209 importSource,
210 customMessage
211 }
212 });
213 }
214 }
215
216 /**
217 * Report a restricted path specifically for patterns.
218 * @param {node} node representing the restricted path reference
5422a9cc 219 * @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails
eb39fafa
DC
220 * @returns {void}
221 * @private
222 */
5422a9cc 223 function reportPathForPatterns(node, group) {
eb39fafa
DC
224 const importSource = node.source.value.trim();
225
226 context.report({
227 node,
5422a9cc 228 messageId: group.customMessage ? "patternWithCustomMessage" : "patterns",
eb39fafa 229 data: {
5422a9cc
TL
230 importSource,
231 customMessage: group.customMessage
eb39fafa
DC
232 }
233 });
234 }
235
236 /**
237 * Check if the given importSource is restricted by a pattern.
238 * @param {string} importSource path of the import
5422a9cc 239 * @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails
eb39fafa
DC
240 * @returns {boolean} whether the variable is a restricted pattern or not
241 * @private
242 */
5422a9cc
TL
243 function isRestrictedPattern(importSource, group) {
244 return group.matcher.ignores(importSource);
eb39fafa
DC
245 }
246
247 /**
248 * Checks a node to see if any problems should be reported.
249 * @param {ASTNode} node The node to check.
250 * @returns {void}
251 * @private
252 */
253 function checkNode(node) {
254 const importSource = node.source.value.trim();
255 const importNames = new Map();
256
257 if (node.type === "ExportAllDeclaration") {
258 const starToken = sourceCode.getFirstToken(node, 1);
259
260 importNames.set("*", [{ loc: starToken.loc }]);
261 } else if (node.specifiers) {
262 for (const specifier of node.specifiers) {
263 let name;
264 const specifierData = { loc: specifier.loc };
265
266 if (specifier.type === "ImportDefaultSpecifier") {
267 name = "default";
268 } else if (specifier.type === "ImportNamespaceSpecifier") {
269 name = "*";
270 } else if (specifier.imported) {
271 name = specifier.imported.name;
272 } else if (specifier.local) {
273 name = specifier.local.name;
274 }
275
276 if (name) {
277 if (importNames.has(name)) {
278 importNames.get(name).push(specifierData);
279 } else {
280 importNames.set(name, [specifierData]);
281 }
282 }
283 }
284 }
285
286 checkRestrictedPathAndReport(importSource, importNames, node);
5422a9cc
TL
287 restrictedPatternGroups.forEach(group => {
288 if (isRestrictedPattern(importSource, group)) {
289 reportPathForPatterns(node, group);
290 }
291 });
eb39fafa
DC
292 }
293
294 return {
295 ImportDeclaration: checkNode,
296 ExportNamedDeclaration(node) {
297 if (node.source) {
298 checkNode(node);
299 }
300 },
301 ExportAllDeclaration: checkNode
302 };
303 }
304};