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