diff --git a/lib/rules/no-restricted-imports.js b/lib/rules/no-restricted-imports.js index ec0696f99a2..5514b979c84 100644 --- a/lib/rules/no-restricted-imports.js +++ b/lib/rules/no-restricted-imports.js @@ -64,7 +64,11 @@ module.exports = { everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.", // eslint-disable-next-line eslint-plugin/report-message-format - everythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted. {{customMessage}}" + everythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted. {{customMessage}}", + + importName: "'{{importName}}' import from '{{importSource}}' is restricted.", + // eslint-disable-next-line eslint-plugin/report-message-format + importNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}" }, schema: { @@ -95,6 +99,11 @@ module.exports = { const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || []; const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || []; + // if no imports are restricted we don"t need to check + if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) { + return {}; + } + const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => { if (typeof importSource === "string") { memo[importSource] = { message: null }; @@ -107,40 +116,68 @@ module.exports = { return memo; }, {}); - // if no imports are restricted we don"t need to check - if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) { - return {}; - } - const restrictedPatternsMatcher = ignore().add(restrictedPatterns); - /** - * Checks to see if "*" is being used to import everything. - * @param {Set.} importNames Set of import names that are being imported - * @returns {boolean} whether everything is imported or not - */ - function isEverythingImported(importNames) { - return importNames.has("*"); - } - /** * Report a restricted path. + * @param {string} importSource path of the import + * @param {Map} importNames Map of import names that are being imported * @param {node} node representing the restricted path reference * @returns {void} * @private */ - function reportPath(node) { - const importSource = node.source.value.trim(); - const customMessage = restrictedPathMessages[importSource] && restrictedPathMessages[importSource].message; + function checkRestrictedPathAndReport(importSource, importNames, node) { + if (!Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) { + return; + } - context.report({ - node, - messageId: customMessage ? "pathWithCustomMessage" : "path", - data: { - importSource, - customMessage + const customMessage = restrictedPathMessages[importSource].message; + const restrictedImportNames = restrictedPathMessages[importSource].importNames; + + if (restrictedImportNames) { + if (importNames.has("*")) { + const specifierData = importNames.get("*")[0]; + + context.report({ + node, + messageId: customMessage ? "everythingWithCustomMessage" : "everything", + loc: specifierData.loc, + data: { + importSource, + importNames: restrictedImportNames, + customMessage + } + }); } - }); + + restrictedImportNames.forEach(importName => { + if (importNames.has(importName)) { + const specifiers = importNames.get(importName); + + specifiers.forEach(specifier => { + context.report({ + node, + messageId: customMessage ? "importNameWithCustomMessage" : "importName", + loc: specifier.loc, + data: { + importSource, + customMessage, + importName + } + }); + }); + } + }); + } else { + context.report({ + node, + messageId: customMessage ? "pathWithCustomMessage" : "path", + data: { + importSource, + customMessage + } + }); + } } /** @@ -161,75 +198,6 @@ module.exports = { }); } - /** - * Report a restricted path specifically when using the '*' import. - * @param {string} importSource path of the import - * @param {node} node representing the restricted path reference - * @returns {void} - * @private - */ - function reportPathForEverythingImported(importSource, node) { - const importNames = restrictedPathMessages[importSource].importNames; - const customMessage = restrictedPathMessages[importSource] && restrictedPathMessages[importSource].message; - - context.report({ - node, - messageId: customMessage ? "everythingWithCustomMessage" : "everything", - data: { - importSource, - importNames, - customMessage - } - }); - } - - /** - * Check if the given importSource is restricted because '*' is being imported. - * @param {string} importSource path of the import - * @param {Set.} importNames Set of import names that are being imported - * @returns {boolean} whether the path is restricted - * @private - */ - function isRestrictedForEverythingImported(importSource, importNames) { - return Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource) && - restrictedPathMessages[importSource].importNames && - isEverythingImported(importNames); - } - - /** - * Check if the given importNames are restricted given a list of restrictedImportNames. - * @param {Set.} importNames Set of import names that are being imported - * @param {string[]} restrictedImportNames array of import names that are restricted for this import - * @returns {boolean} whether the objectName is restricted - * @private - */ - function isRestrictedObject(importNames, restrictedImportNames) { - return restrictedImportNames.some(restrictedObjectName => ( - importNames.has(restrictedObjectName) - )); - } - - /** - * Check if the given importSource is a restricted path. - * @param {string} importSource path of the import - * @param {Set.} importNames Set of import names that are being imported - * @returns {boolean} whether the variable is a restricted path or not - * @private - */ - function isRestrictedPath(importSource, importNames) { - let isRestricted = false; - - if (Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) { - if (restrictedPathMessages[importSource].importNames) { - isRestricted = isRestrictedObject(importNames, restrictedPathMessages[importSource].importNames); - } else { - isRestricted = true; - } - } - - return isRestricted; - } - /** * Check if the given importSource is restricted by a pattern. * @param {string} importSource path of the import @@ -248,26 +216,33 @@ module.exports = { */ function checkNode(node) { const importSource = node.source.value.trim(); - const importNames = node.specifiers ? node.specifiers.reduce((set, specifier) => { + const importNames = node.specifiers ? node.specifiers.reduce((map, specifier) => { + let name; + const specifierData = { loc: specifier.loc }; + if (specifier.type === "ImportDefaultSpecifier") { - set.add("default"); + name = "default"; } else if (specifier.type === "ImportNamespaceSpecifier") { - set.add("*"); + name = "*"; } else if (specifier.imported) { - set.add(specifier.imported.name); + name = specifier.imported.name; } else if (specifier.local) { - set.add(specifier.local.name); + name = specifier.local.name; } - return set; - }, new Set()) : new Set(); - if (isRestrictedForEverythingImported(importSource, importNames)) { - reportPathForEverythingImported(importSource, node); - } + if (name) { + if (map.has(name)) { + map.get(name).push(specifierData); + } else { + map.set(name, [specifierData]); + } + } + + return map; + }, new Map()) : new Map(); + + checkRestrictedPathAndReport(importSource, importNames, node); - if (isRestrictedPath(importSource, importNames)) { - reportPath(node); - } if (isRestrictedPattern(importSource)) { reportPathForPatterns(node); } diff --git a/tests/lib/rules/no-restricted-imports.js b/tests/lib/rules/no-restricted-imports.js index 15ee089b28b..958395f453a 100644 --- a/tests/lib/rules/no-restricted-imports.js +++ b/tests/lib/rules/no-restricted-imports.js @@ -164,44 +164,102 @@ ruleTester.run("no-restricted-imports", rule, { message: "Please import 'DisallowedObject' and 'DisallowedObjectTwo' from /bar/ instead." }] }] + }, + { + code: "import {\nAllowedObject,\nDisallowedObject, // eslint-disable-line\n} from \"foo\";", + options: [{ paths: [{ name: "foo", importNames: ["DisallowedObject"] }] }] } ], invalid: [{ code: "import \"fs\"", options: ["fs"], - errors: [{ message: "'fs' import is restricted from being used.", type: "ImportDeclaration" }] + errors: [{ + message: "'fs' import is restricted from being used.", + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 12 + }] }, { code: "import os from \"os \";", options: ["fs", "crypto ", "stream", "os"], - errors: [{ message: "'os' import is restricted from being used.", type: "ImportDeclaration" }] + errors: [{ + message: "'os' import is restricted from being used.", + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 22 + }] }, { code: "import \"foo/bar\";", options: ["foo/bar"], - errors: [{ message: "'foo/bar' import is restricted from being used.", type: "ImportDeclaration" }] + errors: [{ + message: "'foo/bar' import is restricted from being used.", + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 18 + }] }, { code: "import withPaths from \"foo/bar\";", options: [{ paths: ["foo/bar"] }], - errors: [{ message: "'foo/bar' import is restricted from being used.", type: "ImportDeclaration" }] + errors: [{ + message: "'foo/bar' import is restricted from being used.", + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 33 + }] }, { code: "import withPatterns from \"foo/bar\";", options: [{ patterns: ["foo"] }], - errors: [{ message: "'foo/bar' import is restricted from being used by a pattern.", type: "ImportDeclaration" }] + errors: [{ + message: "'foo/bar' import is restricted from being used by a pattern.", + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 36 + }] }, { code: "import withPatterns from \"foo/bar\";", options: [{ patterns: ["bar"] }], - errors: [{ message: "'foo/bar' import is restricted from being used by a pattern.", type: "ImportDeclaration" }] + errors: [{ + message: "'foo/bar' import is restricted from being used by a pattern.", + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 36 + }] }, { code: "import withGitignores from \"foo/bar\";", options: [{ patterns: ["foo/*", "!foo/baz"] }], - errors: [{ message: "'foo/bar' import is restricted from being used by a pattern.", type: "ImportDeclaration" }] + errors: [{ + message: "'foo/bar' import is restricted from being used by a pattern.", + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 38 + }] }, { code: "export * from \"fs\";", options: ["fs"], - errors: [{ message: "'fs' import is restricted from being used.", type: "ExportAllDeclaration" }] + errors: [{ + message: "'fs' import is restricted from being used.", + type: "ExportAllDeclaration", + line: 1, + column: 1, + endColumn: 20 + }] }, { code: "export {a} from \"fs\";", options: ["fs"], - errors: [{ message: "'fs' import is restricted from being used.", type: "ExportNamedDeclaration" }] + errors: [{ + message: "'fs' import is restricted from being used.", + type: "ExportNamedDeclaration", + line: 1, + column: 1, + endColumn: 22 + }] }, { code: "export {foo as b} from \"fs\";", options: [{ @@ -211,7 +269,13 @@ ruleTester.run("no-restricted-imports", rule, { message: "Don't import 'foo'." }] }], - errors: [{ message: "'fs' import is restricted from being used. Don't import 'foo'.", type: "ExportNamedDeclaration" }] + errors: [{ + message: "'foo' import from 'fs' is restricted. Don't import 'foo'.", + type: "ExportNamedDeclaration", + line: 1, + column: 9, + endColumn: 17 + }] }, { code: "import withGitignores from \"foo\";", options: [{ @@ -220,7 +284,10 @@ ruleTester.run("no-restricted-imports", rule, { }], errors: [{ message: "'foo' import is restricted from being used. Please import from 'bar' instead.", - type: "ImportDeclaration" + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 34 }] }, { code: "import withGitignores from \"bar\";", @@ -234,7 +301,10 @@ ruleTester.run("no-restricted-imports", rule, { ], errors: [{ message: "'bar' import is restricted from being used. Please import from 'baz' instead.", - type: "ImportDeclaration" + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 34 }] }, { code: "import withGitignores from \"foo\";", @@ -246,7 +316,10 @@ ruleTester.run("no-restricted-imports", rule, { }], errors: [{ message: "'foo' import is restricted from being used. Please import from 'bar' instead.", - type: "ImportDeclaration" + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 34 }] }, { @@ -259,8 +332,11 @@ ruleTester.run("no-restricted-imports", rule, { }] }], errors: [{ - message: "'foo' import is restricted from being used. Please import the default import of 'foo' from /bar/ instead.", - type: "ImportDeclaration" + message: "'default' import from 'foo' is restricted. Please import the default import of 'foo' from /bar/ instead.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 24 }] }, { @@ -274,7 +350,10 @@ ruleTester.run("no-restricted-imports", rule, { }], errors: [{ message: "* import is invalid because 'DisallowedObject' from 'foo' is restricted. Please import 'DisallowedObject' from /bar/ instead.", - type: "ImportDeclaration" + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 16 }] }, { @@ -287,8 +366,11 @@ ruleTester.run("no-restricted-imports", rule, { }] }], errors: [{ - message: "'foo' import is restricted from being used. Please import 'DisallowedObject' from /bar/ instead.", - type: "ImportDeclaration" + message: "'DisallowedObject' import from 'foo' is restricted. Please import 'DisallowedObject' from /bar/ instead.", + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 26 }] }, { @@ -301,8 +383,11 @@ ruleTester.run("no-restricted-imports", rule, { }] }], errors: [{ - message: "'foo' import is restricted from being used. Please import 'DisallowedObject' from /bar/ instead.", - type: "ImportDeclaration" + message: "'DisallowedObject' import from 'foo' is restricted. Please import 'DisallowedObject' from /bar/ instead.", + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 43 }] }, { @@ -315,8 +400,11 @@ ruleTester.run("no-restricted-imports", rule, { }] }], errors: [{ - message: "'foo' import is restricted from being used. Please import 'DisallowedObject' from /bar/ instead.", - type: "ImportDeclaration" + message: "'DisallowedObject' import from 'foo' is restricted. Please import 'DisallowedObject' from /bar/ instead.", + type: "ImportDeclaration", + line: 1, + column: 25, + endColumn: 41 }] }, { @@ -329,8 +417,11 @@ ruleTester.run("no-restricted-imports", rule, { }] }], errors: [{ - message: "'foo' import is restricted from being used. Please import 'DisallowedObject' from /bar/ instead.", - type: "ImportDeclaration" + message: "'DisallowedObject' import from 'foo' is restricted. Please import 'DisallowedObject' from /bar/ instead.", + type: "ImportDeclaration", + line: 1, + column: 25, + endColumn: 61 }] }, { @@ -343,8 +434,11 @@ ruleTester.run("no-restricted-imports", rule, { }] }], errors: [{ - message: "'foo' import is restricted from being used. Please import 'DisallowedObject' and 'DisallowedObjectTwo' from /bar/ instead.", - type: "ImportDeclaration" + message: "'DisallowedObject' import from 'foo' is restricted. Please import 'DisallowedObject' and 'DisallowedObjectTwo' from /bar/ instead.", + type: "ImportDeclaration", + line: 1, + column: 25, + endColumn: 61 }] }, { @@ -357,8 +451,11 @@ ruleTester.run("no-restricted-imports", rule, { }] }], errors: [{ - message: "'foo' import is restricted from being used. Please import 'DisallowedObject' and 'DisallowedObjectTwo' from /bar/ instead.", - type: "ImportDeclaration" + message: "'DisallowedObject' import from 'foo' is restricted. Please import 'DisallowedObject' and 'DisallowedObjectTwo' from /bar/ instead.", + type: "ImportDeclaration", + line: 1, + column: 25, + endColumn: 61 }] }, { @@ -371,8 +468,11 @@ ruleTester.run("no-restricted-imports", rule, { }] }], errors: [{ - message: "'foo' import is restricted from being used. Please import the default import of 'foo' from /bar/ instead.", - type: "ImportDeclaration" + message: "'default' import from 'foo' is restricted. Please import the default import of 'foo' from /bar/ instead.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 24 }] }, { @@ -385,8 +485,11 @@ ruleTester.run("no-restricted-imports", rule, { }] }], errors: [{ - message: "'foo' import is restricted from being used. Please import 'DisallowedObject' from /bar/ instead.", - type: "ImportDeclaration" + message: "'DisallowedObject' import from 'foo' is restricted. Please import 'DisallowedObject' from /bar/ instead.", + type: "ImportDeclaration", + line: 1, + column: 25, + endColumn: 61 }] }, { @@ -400,7 +503,10 @@ ruleTester.run("no-restricted-imports", rule, { }], errors: [{ message: "* import is invalid because 'DisallowedObject' from 'foo' is restricted. Please import 'DisallowedObject' from /bar/ instead.", - type: "ImportDeclaration" + type: "ImportDeclaration", + line: 1, + column: 23, + endColumn: 44 }] }, { @@ -414,7 +520,198 @@ ruleTester.run("no-restricted-imports", rule, { }], errors: [{ message: "* import is invalid because 'DisallowedObject,DisallowedObjectTwo' from 'foo' is restricted. Please import 'DisallowedObject' and 'DisallowedObjectTwo' from /bar/ instead.", - type: "ImportDeclaration" + type: "ImportDeclaration", + line: 1, + column: 23, + endColumn: 44 + }] + }, + { + code: "import { DisallowedObjectOne, DisallowedObjectTwo, AllowedObject } from \"foo\";", + options: [{ + paths: [{ + name: "foo", + importNames: ["DisallowedObjectOne", "DisallowedObjectTwo"] + }] + }], + errors: [{ + message: "'DisallowedObjectOne' import from 'foo' is restricted.", + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 29 + }, { + message: "'DisallowedObjectTwo' import from 'foo' is restricted.", + type: "ImportDeclaration", + line: 1, + column: 31, + endColumn: 50 + }] + }, + { + code: "import { DisallowedObjectOne, DisallowedObjectTwo, AllowedObject } from \"foo\";", + options: [{ + paths: [{ + name: "foo", + importNames: ["DisallowedObjectOne", "DisallowedObjectTwo"], + message: "Please import this module from /bar/ instead." + }] + }], + errors: [{ + message: "'DisallowedObjectOne' import from 'foo' is restricted. Please import this module from /bar/ instead.", + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 29 + }, { + message: "'DisallowedObjectTwo' import from 'foo' is restricted. Please import this module from /bar/ instead.", + type: "ImportDeclaration", + line: 1, + column: 31, + endColumn: 50 + }] + }, + { + code: "import { AllowedObject, DisallowedObject as Bar } from \"foo\";", + options: [{ + paths: [{ + name: "foo", + importNames: ["DisallowedObject"] + }] + }], + errors: [{ + message: "'DisallowedObject' import from 'foo' is restricted.", + type: "ImportDeclaration", + line: 1, + column: 25, + endColumn: 48 + }] + }, + { + code: "import foo, { bar } from 'mod';", + options: [{ + paths: [{ + name: "mod", + importNames: ["bar"] + }] + }], + errors: [{ + message: "'bar' import from 'mod' is restricted.", + type: "ImportDeclaration", + line: 1, + column: 15, + endColumn: 18 + }] + }, + { + code: "import foo, { bar } from 'mod';", + options: [{ + paths: [{ + name: "mod", + importNames: ["default"] + }] + }], + errors: [{ + message: "'default' import from 'mod' is restricted.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 11 + }] + }, + { + code: "import foo, * as bar from 'mod';", + options: [{ + paths: [{ + name: "mod", + importNames: ["default"] + }] + }], + errors: [{ + message: "'default' import from 'mod' is restricted.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 11 + }, { + message: "* import is invalid because 'default' from 'mod' is restricted.", + type: "ImportDeclaration", + line: 1, + column: 13, + endColumn: 21 + }] + }, { + code: "import * as bar from 'foo';", + options: ["foo"], + errors: [{ + message: "'foo' import is restricted from being used.", + type: "ImportDeclaration", + line: 1, + column: 1, + endColumn: 28 + }] + }, { + code: "import { a, a as b } from 'mod';", + options: [{ + paths: [{ + name: "mod", + importNames: ["a"] + }] + }], + errors: [{ + message: "'a' import from 'mod' is restricted.", + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 11 + }, { + message: "'a' import from 'mod' is restricted.", + type: "ImportDeclaration", + line: 1, + column: 13, + endColumn: 19 + }] + }, { + code: "export { x as y, x as z } from 'mod';", + options: [{ + paths: [{ + name: "mod", + importNames: ["x"] + }] + }], + errors: [{ + message: "'x' import from 'mod' is restricted.", + type: "ExportNamedDeclaration", + line: 1, + column: 10, + endColumn: 16 + }, { + message: "'x' import from 'mod' is restricted.", + type: "ExportNamedDeclaration", + line: 1, + column: 18, + endColumn: 24 + }] + }, { + code: "import foo, { default as bar } from 'mod';", + options: [{ + paths: [{ + name: "mod", + importNames: ["default"] + }] + }], + errors: [{ + message: "'default' import from 'mod' is restricted.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 11 + }, { + message: "'default' import from 'mod' is restricted.", + type: "ImportDeclaration", + line: 1, + column: 15, + endColumn: 29 }] } ]