UNPKG

9.57 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 docs: {
57 description: "disallow specified modules when loaded by `import`",
58 category: "ECMAScript 6",
59 recommended: false,
60 url: "https://eslint.org/docs/rules/no-restricted-imports"
61 },
62
63 schema: {
64 anyOf: [
65 arrayOfStringsOrObjects,
66 {
67 type: "array",
68 items: {
69 type: "object",
70 properties: {
71 paths: arrayOfStringsOrObjects,
72 patterns: arrayOfStrings
73 },
74 additionalProperties: false
75 },
76 additionalItems: false
77 }
78 ]
79 }
80 },
81
82 create(context) {
83 const options = Array.isArray(context.options) ? context.options : [];
84 const isPathAndPatternsObject =
85 typeof options[0] === "object" &&
86 (options[0].hasOwnProperty("paths") || options[0].hasOwnProperty("patterns"));
87
88 const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
89 const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
90
91 const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
92 if (typeof importSource === "string") {
93 memo[importSource] = { message: null };
94 } else {
95 memo[importSource.name] = {
96 message: importSource.message,
97 importNames: importSource.importNames
98 };
99 }
100 return memo;
101 }, {});
102
103 // if no imports are restricted we don"t need to check
104 if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
105 return {};
106 }
107
108 const restrictedPatternsMatcher = ignore().add(restrictedPatterns);
109
110 /**
111 * Checks to see if "*" is being used to import everything.
112 * @param {Set.<string>} importNames - Set of import names that are being imported
113 * @returns {boolean} whether everything is imported or not
114 */
115 function isEverythingImported(importNames) {
116 return importNames.has("*");
117 }
118
119 /**
120 * Report a restricted path.
121 * @param {node} node representing the restricted path reference
122 * @returns {void}
123 * @private
124 */
125 function reportPath(node) {
126 const importSource = node.source.value.trim();
127 const customMessage = restrictedPathMessages[importSource] && restrictedPathMessages[importSource].message;
128 const message = customMessage
129 ? CUSTOM_MESSAGE_TEMPLATE
130 : DEFAULT_MESSAGE_TEMPLATE;
131
132 context.report({
133 node,
134 message,
135 data: {
136 importSource,
137 customMessage
138 }
139 });
140 }
141
142 /**
143 * Report a restricted path specifically for patterns.
144 * @param {node} node - representing the restricted path reference
145 * @returns {void}
146 * @private
147 */
148 function reportPathForPatterns(node) {
149 const importSource = node.source.value.trim();
150
151 context.report({
152 node,
153 message: "'{{importSource}}' import is restricted from being used by a pattern.",
154 data: {
155 importSource
156 }
157 });
158 }
159
160 /**
161 * Report a restricted path specifically when using the '*' import.
162 * @param {string} importSource - path of the import
163 * @param {node} node - representing the restricted path reference
164 * @returns {void}
165 * @private
166 */
167 function reportPathForEverythingImported(importSource, node) {
168 const importNames = restrictedPathMessages[importSource].importNames;
169
170 context.report({
171 node,
172 message: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.",
173 data: {
174 importSource,
175 importNames
176 }
177 });
178 }
179
180 /**
181 * Check if the given importSource is restricted because '*' is being imported.
182 * @param {string} importSource - path of the import
183 * @param {Set.<string>} importNames - Set of import names that are being imported
184 * @returns {boolean} whether the path is restricted
185 * @private
186 */
187 function isRestrictedForEverythingImported(importSource, importNames) {
188 return Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource) &&
189 restrictedPathMessages[importSource].importNames &&
190 isEverythingImported(importNames);
191 }
192
193 /**
194 * Check if the given importNames are restricted given a list of restrictedImportNames.
195 * @param {Set.<string>} importNames - Set of import names that are being imported
196 * @param {[string]} restrictedImportNames - array of import names that are restricted for this import
197 * @returns {boolean} whether the objectName is restricted
198 * @private
199 */
200 function isRestrictedObject(importNames, restrictedImportNames) {
201 return restrictedImportNames.some(restrictedObjectName => (
202 importNames.has(restrictedObjectName)
203 ));
204 }
205
206 /**
207 * Check if the given importSource is a restricted path.
208 * @param {string} importSource - path of the import
209 * @param {Set.<string>} importNames - Set of import names that are being imported
210 * @returns {boolean} whether the variable is a restricted path or not
211 * @private
212 */
213 function isRestrictedPath(importSource, importNames) {
214 let isRestricted = false;
215
216 if (Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) {
217 if (restrictedPathMessages[importSource].importNames) {
218 isRestricted = isRestrictedObject(importNames, restrictedPathMessages[importSource].importNames);
219 } else {
220 isRestricted = true;
221 }
222 }
223
224 return isRestricted;
225 }
226
227 /**
228 * Check if the given importSource is restricted by a pattern.
229 * @param {string} importSource - path of the import
230 * @returns {boolean} whether the variable is a restricted pattern or not
231 * @private
232 */
233 function isRestrictedPattern(importSource) {
234 return restrictedPatterns.length > 0 && restrictedPatternsMatcher.ignores(importSource);
235 }
236
237 return {
238 ImportDeclaration(node) {
239 const importSource = node.source.value.trim();
240 const importNames = node.specifiers.reduce((set, specifier) => {
241 if (specifier.type === "ImportDefaultSpecifier") {
242 set.add("default");
243 } else if (specifier.type === "ImportNamespaceSpecifier") {
244 set.add("*");
245 } else {
246 set.add(specifier.imported.name);
247 }
248 return set;
249 }, new Set());
250
251 if (isRestrictedForEverythingImported(importSource, importNames)) {
252 reportPathForEverythingImported(importSource, node);
253 }
254
255 if (isRestrictedPath(importSource, importNames)) {
256 reportPath(node);
257 }
258 if (isRestrictedPattern(importSource)) {
259 reportPathForPatterns(node);
260 }
261 }
262 };
263 }
264};