UNPKG

10.5 kBJavaScriptView Raw
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
13const arrayOfStrings = {
14 type: "array",
15 items: { type: "string" },
16 uniqueItems: true
17};
18
19const arrayOfStringsOrObjects = {
20 type: "array",
21 items: {
22 anyOf: [
23 { type: "string" },
24 {
25 type: "object",
26 properties: {
27 name: { type: "string" },
28 message: {
29 type: "string",
30 minLength: 1
31 },
32 importNames: {
33 type: "array",
34 items: {
35 type: "string"
36 }
37 }
38 },
39 additionalProperties: false,
40 required: ["name"]
41 }
42 ]
43 },
44 uniqueItems: true
45};
46
47module.exports = {
48 meta: {
49 type: "suggestion",
50
51 docs: {
52 description: "disallow specified modules when loaded by `import`",
53 category: "ECMAScript 6",
54 recommended: false,
55 url: "https://eslint.org/docs/rules/no-restricted-imports"
56 },
57
58 messages: {
59 path: "'{{importSource}}' import is restricted from being used.",
60 // eslint-disable-next-line eslint-plugin/report-message-format
61 pathWithCustomMessage: "'{{importSource}}' import is restricted from being used. {{customMessage}}",
62
63 patterns: "'{{importSource}}' import is restricted from being used by a pattern.",
64
65 everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.",
66 // eslint-disable-next-line eslint-plugin/report-message-format
67 everythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted. {{customMessage}}"
68 },
69
70 schema: {
71 anyOf: [
72 arrayOfStringsOrObjects,
73 {
74 type: "array",
75 items: {
76 type: "object",
77 properties: {
78 paths: arrayOfStringsOrObjects,
79 patterns: arrayOfStrings
80 },
81 additionalProperties: false
82 },
83 additionalItems: false
84 }
85 ]
86 }
87 },
88
89 create(context) {
90 const options = Array.isArray(context.options) ? context.options : [];
91 const isPathAndPatternsObject =
92 typeof options[0] === "object" &&
93 (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns"));
94
95 const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
96 const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
97
98 const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
99 if (typeof importSource === "string") {
100 memo[importSource] = { message: null };
101 } else {
102 memo[importSource.name] = {
103 message: importSource.message,
104 importNames: importSource.importNames
105 };
106 }
107 return memo;
108 }, {});
109
110 // if no imports are restricted we don"t need to check
111 if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
112 return {};
113 }
114
115 const restrictedPatternsMatcher = ignore().add(restrictedPatterns);
116
117 /**
118 * Checks to see if "*" is being used to import everything.
119 * @param {Set.<string>} importNames - Set of import names that are being imported
120 * @returns {boolean} whether everything is imported or not
121 */
122 function isEverythingImported(importNames) {
123 return importNames.has("*");
124 }
125
126 /**
127 * Report a restricted path.
128 * @param {node} node representing the restricted path reference
129 * @returns {void}
130 * @private
131 */
132 function reportPath(node) {
133 const importSource = node.source.value.trim();
134 const customMessage = restrictedPathMessages[importSource] && restrictedPathMessages[importSource].message;
135
136 context.report({
137 node,
138 messageId: customMessage ? "pathWithCustomMessage" : "path",
139 data: {
140 importSource,
141 customMessage
142 }
143 });
144 }
145
146 /**
147 * Report a restricted path specifically for patterns.
148 * @param {node} node - representing the restricted path reference
149 * @returns {void}
150 * @private
151 */
152 function reportPathForPatterns(node) {
153 const importSource = node.source.value.trim();
154
155 context.report({
156 node,
157 messageId: "patterns",
158 data: {
159 importSource
160 }
161 });
162 }
163
164 /**
165 * Report a restricted path specifically when using the '*' import.
166 * @param {string} importSource - path of the import
167 * @param {node} node - representing the restricted path reference
168 * @returns {void}
169 * @private
170 */
171 function reportPathForEverythingImported(importSource, node) {
172 const importNames = restrictedPathMessages[importSource].importNames;
173 const customMessage = restrictedPathMessages[importSource] && restrictedPathMessages[importSource].message;
174
175 context.report({
176 node,
177 messageId: customMessage ? "everythingWithCustomMessage" : "everything",
178 data: {
179 importSource,
180 importNames,
181 customMessage
182 }
183 });
184 }
185
186 /**
187 * Check if the given importSource is restricted because '*' is being imported.
188 * @param {string} importSource - path of the import
189 * @param {Set.<string>} importNames - Set of import names that are being imported
190 * @returns {boolean} whether the path is restricted
191 * @private
192 */
193 function isRestrictedForEverythingImported(importSource, importNames) {
194 return Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource) &&
195 restrictedPathMessages[importSource].importNames &&
196 isEverythingImported(importNames);
197 }
198
199 /**
200 * Check if the given importNames are restricted given a list of restrictedImportNames.
201 * @param {Set.<string>} importNames - Set of import names that are being imported
202 * @param {string[]} restrictedImportNames - array of import names that are restricted for this import
203 * @returns {boolean} whether the objectName is restricted
204 * @private
205 */
206 function isRestrictedObject(importNames, restrictedImportNames) {
207 return restrictedImportNames.some(restrictedObjectName => (
208 importNames.has(restrictedObjectName)
209 ));
210 }
211
212 /**
213 * Check if the given importSource is a restricted path.
214 * @param {string} importSource - path of the import
215 * @param {Set.<string>} importNames - Set of import names that are being imported
216 * @returns {boolean} whether the variable is a restricted path or not
217 * @private
218 */
219 function isRestrictedPath(importSource, importNames) {
220 let isRestricted = false;
221
222 if (Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) {
223 if (restrictedPathMessages[importSource].importNames) {
224 isRestricted = isRestrictedObject(importNames, restrictedPathMessages[importSource].importNames);
225 } else {
226 isRestricted = true;
227 }
228 }
229
230 return isRestricted;
231 }
232
233 /**
234 * Check if the given importSource is restricted by a pattern.
235 * @param {string} importSource - path of the import
236 * @returns {boolean} whether the variable is a restricted pattern or not
237 * @private
238 */
239 function isRestrictedPattern(importSource) {
240 return restrictedPatterns.length > 0 && restrictedPatternsMatcher.ignores(importSource);
241 }
242
243 /**
244 * Checks a node to see if any problems should be reported.
245 * @param {ASTNode} node The node to check.
246 * @returns {void}
247 * @private
248 */
249 function checkNode(node) {
250 const importSource = node.source.value.trim();
251 const importNames = node.specifiers ? node.specifiers.reduce((set, specifier) => {
252 if (specifier.type === "ImportDefaultSpecifier") {
253 set.add("default");
254 } else if (specifier.type === "ImportNamespaceSpecifier") {
255 set.add("*");
256 } else if (specifier.imported) {
257 set.add(specifier.imported.name);
258 } else if (specifier.local) {
259 set.add(specifier.local.name);
260 }
261 return set;
262 }, new Set()) : new Set();
263
264 if (isRestrictedForEverythingImported(importSource, importNames)) {
265 reportPathForEverythingImported(importSource, node);
266 }
267
268 if (isRestrictedPath(importSource, importNames)) {
269 reportPath(node);
270 }
271 if (isRestrictedPattern(importSource)) {
272 reportPathForPatterns(node);
273 }
274 }
275
276 return {
277 ImportDeclaration: checkNode,
278 ExportNamedDeclaration(node) {
279 if (node.source) {
280 checkNode(node);
281 }
282 },
283 ExportAllDeclaration: checkNode
284 };
285 }
286};