UNPKG

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