2 * @fileoverview Restrict usage of specified node imports.
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
11 const ignore = require("ignore");
13 const arrayOfStrings = {
15 items: { type: "string" },
19 const arrayOfStringsOrObjects = {
27 name: { type: "string" },
39 additionalProperties: false,
52 description: "disallow specified modules when loaded by `import`",
53 category: "ECMAScript 6",
55 url: "https://eslint.org/docs/rules/no-restricted-imports"
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}}",
63 patterns: "'{{importSource}}' import is restricted from being used by a pattern.",
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}}",
69 importName: "'{{importName}}' import from '{{importSource}}' is restricted.",
70 // eslint-disable-next-line eslint-plugin/report-message-format
71 importNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}"
76 arrayOfStringsOrObjects,
82 paths: arrayOfStringsOrObjects,
83 patterns: arrayOfStrings
85 additionalProperties: false
87 additionalItems: false
94 const sourceCode = context.getSourceCode();
95 const options = Array.isArray(context.options) ? context.options : [];
96 const isPathAndPatternsObject =
97 typeof options[0] === "object" &&
98 (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns"));
100 const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
101 const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
103 // if no imports are restricted we don"t need to check
104 if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
108 const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
109 if (typeof importSource === "string") {
110 memo[importSource] = { message: null };
112 memo[importSource.name] = {
113 message: importSource.message,
114 importNames: importSource.importNames
120 const restrictedPatternsMatcher = ignore().add(restrictedPatterns);
123 * Report a restricted path.
124 * @param {string} importSource path of the import
125 * @param {Map<string,Object[]>} importNames Map of import names that are being imported
126 * @param {node} node representing the restricted path reference
130 function checkRestrictedPathAndReport(importSource, importNames, node) {
131 if (!Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) {
135 const customMessage = restrictedPathMessages[importSource].message;
136 const restrictedImportNames = restrictedPathMessages[importSource].importNames;
138 if (restrictedImportNames) {
139 if (importNames.has("*")) {
140 const specifierData = importNames.get("*")[0];
144 messageId: customMessage ? "everythingWithCustomMessage" : "everything",
145 loc: specifierData.loc,
148 importNames: restrictedImportNames,
154 restrictedImportNames.forEach(importName => {
155 if (importNames.has(importName)) {
156 const specifiers = importNames.get(importName);
158 specifiers.forEach(specifier => {
161 messageId: customMessage ? "importNameWithCustomMessage" : "importName",
175 messageId: customMessage ? "pathWithCustomMessage" : "path",
185 * Report a restricted path specifically for patterns.
186 * @param {node} node representing the restricted path reference
190 function reportPathForPatterns(node) {
191 const importSource = node.source.value.trim();
195 messageId: "patterns",
203 * Check if the given importSource is restricted by a pattern.
204 * @param {string} importSource path of the import
205 * @returns {boolean} whether the variable is a restricted pattern or not
208 function isRestrictedPattern(importSource) {
209 return restrictedPatterns.length > 0 && restrictedPatternsMatcher.ignores(importSource);
213 * Checks a node to see if any problems should be reported.
214 * @param {ASTNode} node The node to check.
218 function checkNode(node) {
219 const importSource = node.source.value.trim();
220 const importNames = new Map();
222 if (node.type === "ExportAllDeclaration") {
223 const starToken = sourceCode.getFirstToken(node, 1);
225 importNames.set("*", [{ loc: starToken.loc }]);
226 } else if (node.specifiers) {
227 for (const specifier of node.specifiers) {
229 const specifierData = { loc: specifier.loc };
231 if (specifier.type === "ImportDefaultSpecifier") {
233 } else if (specifier.type === "ImportNamespaceSpecifier") {
235 } else if (specifier.imported) {
236 name = specifier.imported.name;
237 } else if (specifier.local) {
238 name = specifier.local.name;
242 if (importNames.has(name)) {
243 importNames.get(name).push(specifierData);
245 importNames.set(name, [specifierData]);
251 checkRestrictedPathAndReport(importSource, importNames, node);
253 if (isRestrictedPattern(importSource)) {
254 reportPathForPatterns(node);
259 ImportDeclaration: checkNode,
260 ExportNamedDeclaration(node) {
265 ExportAllDeclaration: checkNode