diff --git a/docs/src/rules/no-restricted-imports.md b/docs/src/rules/no-restricted-imports.md index 72d97baf3e70..81f7ad4a6f97 100644 --- a/docs/src/rules/no-restricted-imports.md +++ b/docs/src/rules/no-restricted-imports.md @@ -118,6 +118,17 @@ Pattern matches can restrict specific import names only, similar to the `paths` }] ``` +Regex patterns can also be used to restrict specific import Name: + +```json +"no-restricted-imports": ["error", { + "patterns": [{ + "group": ["import-foo/*"], + "importNamePattern": "^foo", + }] +}] +``` + To restrict the use of all Node.js core imports (via ): ```json @@ -266,6 +277,48 @@ import { isEmpty } from 'utils/collection-utils'; ::: +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["utils/*"], + importNamePattern: '^is', + message: "Use 'is*' functions from lodash instead." +}]}]*/ + +import { isEmpty } from 'utils/collection-utils'; +``` + +::: + +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["foo/*"], + importNamePattern: '^(is|has)', + message: "Use 'is*' and 'has*' functions from baz/bar instead" +}]}]*/ + +import { isSomething, hasSomething } from 'foo/bar'; +``` + +::: + +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["foo/*"], + importNames: ["bar"], + importNamePattern: '^baz', +}]}]*/ + +import { bar, bazQux } from 'foo/quux'; +``` + +::: + Examples of **correct** code for this rule: ::: correct { "sourceType": "module" } @@ -355,6 +408,20 @@ import { hasValues } from 'utils/collection-utils'; ::: +::: correct { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["utils/*"], + importNamePattern: '^is', + message: "Use 'is*' functions from lodash instead." +}]}]*/ + +import isEmpty, { hasValue } from 'utils/collection-utils'; +``` + +::: + ## When Not To Use It Don't use this rule or don't include a module in the list for this rule if you want to be able to import a module in your project without an ESLint error or warning. diff --git a/lib/rules/no-restricted-imports.js b/lib/rules/no-restricted-imports.js index 6abfcacae137..eb59f4c23a9b 100644 --- a/lib/rules/no-restricted-imports.js +++ b/lib/rules/no-restricted-imports.js @@ -74,6 +74,9 @@ const arrayOfStringsOrObjectPatterns = { minItems: 1, uniqueItems: true }, + importNamePattern: { + type: "string" + }, message: { type: "string", minLength: 1 @@ -115,8 +118,12 @@ module.exports = { patternAndImportNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted from being used by a pattern. {{customMessage}}", patternAndEverything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted from being used by a pattern.", + + patternAndEverythingWithRegexImportName: "* import is invalid because import name matching '{{importNames}}' pattern from '{{importSource}}' is restricted from being used.", // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period patternAndEverythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted from being used by a pattern. {{customMessage}}", + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + patternAndEverythingWithRegexImportNameAndCustomMessage: "* import is invalid because import name matching '{{importNames}}' pattern from '{{importSource}}' is restricted from being used. {{customMessage}}", everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.", // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period @@ -175,10 +182,11 @@ module.exports = { } // relative paths are supported for this rule - const restrictedPatternGroups = restrictedPatterns.map(({ group, message, caseSensitive, importNames }) => ({ + const restrictedPatternGroups = restrictedPatterns.map(({ group, message, caseSensitive, importNames, importNamePattern }) => ({ matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group), customMessage: message, - importNames + importNames, + importNamePattern })); // if no imports are restricted we don't need to check @@ -262,12 +270,13 @@ module.exports = { const customMessage = group.customMessage; const restrictedImportNames = group.importNames; + const restrictedImportNamePattern = group.importNamePattern ? new RegExp(group.importNamePattern, "u") : null; /* * If we are not restricting to any specific import names and just the pattern itself, * report the error and move on */ - if (!restrictedImportNames) { + if (!restrictedImportNames && !restrictedImportNamePattern) { context.report({ node, messageId: customMessage ? "patternWithCustomMessage" : "patterns", @@ -279,40 +288,54 @@ module.exports = { return; } - if (importNames.has("*")) { - const specifierData = importNames.get("*")[0]; - - context.report({ - node, - messageId: customMessage ? "patternAndEverythingWithCustomMessage" : "patternAndEverything", - loc: specifierData.loc, - data: { - importSource, - importNames: restrictedImportNames, - customMessage + importNames.forEach((specifiers, importName) => { + if (importName === "*") { + const [specifier] = specifiers; + + if (restrictedImportNames) { + context.report({ + node, + messageId: customMessage ? "patternAndEverythingWithCustomMessage" : "patternAndEverything", + loc: specifier.loc, + data: { + importSource, + importNames: restrictedImportNames, + customMessage + } + }); + } else { + context.report({ + node, + messageId: customMessage ? "patternAndEverythingWithRegexImportNameAndCustomMessage" : "patternAndEverythingWithRegexImportName", + loc: specifier.loc, + data: { + importSource, + importNames: restrictedImportNamePattern, + customMessage + } + }); } - }); - } - restrictedImportNames.forEach(importName => { - if (!importNames.has(importName)) { return; } - const specifiers = importNames.get(importName); - - specifiers.forEach(specifier => { - context.report({ - node, - messageId: customMessage ? "patternAndImportNameWithCustomMessage" : "patternAndImportName", - loc: specifier.loc, - data: { - importSource, - customMessage, - importName - } + if ( + (restrictedImportNames && restrictedImportNames.includes(importName)) || + (restrictedImportNamePattern && restrictedImportNamePattern.test(importName)) + ) { + specifiers.forEach(specifier => { + context.report({ + node, + messageId: customMessage ? "patternAndImportNameWithCustomMessage" : "patternAndImportName", + loc: specifier.loc, + data: { + importSource, + customMessage, + importName + } + }); }); - }); + } }); } diff --git a/tests/lib/rules/no-restricted-imports.js b/tests/lib/rules/no-restricted-imports.js index 5403812de368..b756770d686f 100644 --- a/tests/lib/rules/no-restricted-imports.js +++ b/tests/lib/rules/no-restricted-imports.js @@ -282,6 +282,99 @@ ruleTester.run("no-restricted-imports", rule, { importNames: ["Foo"] }] }] + }, + { + code: "import Foo from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + importNamePattern: "^Foo" + }] + }] + }, + { + code: "import Foo from 'foo';", + options: [{ + patterns: [{ + importNames: ["Foo"], + group: ["foo"], + importNamePattern: "^Foo" + }] + }] + }, + { + code: "import Foo from '../../my/relative-module';", + options: [{ + patterns: [{ + group: ["**/my/relative-module"], + importNamePattern: "^Foo" + }] + }] + }, + { + code: "import { Bar } from '../../my/relative-module';", + options: [{ + patterns: [{ + group: ["**/my/relative-module"], + importNamePattern: "^Foo" + }] + }] + }, + { + code: "import { Bar as Foo } from '../../my/relative-module';", + options: [{ + patterns: [{ + group: ["**/my/relative-module"], + importNamePattern: "^Foo" + }] + }] + }, + { + code: "import { Bar as Foo } from '../../my/relative-module';", + options: [{ + patterns: [{ + importNames: ["Foo"], + group: ["**/my/relative-module"], + importNamePattern: "^Foo" + }] + }] + }, + { + code: "import Foo, { Baz as Bar } from '../../my/relative-module';", + options: [{ + patterns: [{ + group: ["**/my/relative-module"], + importNamePattern: "^(Foo|Bar)" + }] + }] + }, + { + code: "import Foo, { Baz as Bar } from '../../my/relative-module';", + options: [{ + patterns: [{ + importNames: ["Foo"], + group: ["**/my/relative-module"], + importNamePattern: "^Bar" + }] + }] + }, + { + code: "export { Bar } from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + importNamePattern: "^Foo" + }] + }] + }, + { + code: "export { Bar as Foo } from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + importNamePattern: "^Foo" + }] + }] } ], invalid: [{ @@ -1235,6 +1328,353 @@ ruleTester.run("no-restricted-imports", rule, { endColumn: 11, message: "'default' import from 'mod' is restricted from being used by a pattern." }] + }, + { + code: "import { Foo } from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + importNamePattern: "^Foo" + }] + }], + errors: [{ + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 13, + message: "'Foo' import from 'foo' is restricted from being used by a pattern." + }] + }, + { + code: "import { Foo as Bar } from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + importNamePattern: "^Foo" + }] + }], + errors: [{ + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 20, + message: "'Foo' import from 'foo' is restricted from being used by a pattern." + }] + }, + { + code: "import Foo, { Bar } from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + importNamePattern: "^(Foo|Bar)" + }] + }], + errors: [{ + type: "ImportDeclaration", + line: 1, + column: 15, + endColumn: 18, + message: "'Bar' import from 'foo' is restricted from being used by a pattern." + }] + }, + { + code: "import { Foo } from '../../my/relative-module';", + options: [{ + patterns: [{ + group: ["**/my/relative-module"], + importNamePattern: "^Foo" + }] + }], + errors: [{ + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 13, + message: "'Foo' import from '../../my/relative-module' is restricted from being used by a pattern." + }] + }, + { + code: "import { FooBar } from '../../my/relative-module';", + options: [{ + patterns: [{ + group: ["**/my/relative-module"], + importNamePattern: "^Foo" + }] + }], + errors: [{ + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 16, + message: "'FooBar' import from '../../my/relative-module' is restricted from being used by a pattern." + }] + }, + { + code: "import Foo, { Bar } from '../../my/relative-module';", + options: [{ + patterns: [{ + group: ["**/my/relative-module"], + importNamePattern: "^Foo|^Bar" + }] + }], + errors: [{ + type: "ImportDeclaration", + line: 1, + column: 15, + endColumn: 18, + message: "'Bar' import from '../../my/relative-module' is restricted from being used by a pattern." + }] + }, + { + code: "import { Foo, Bar } from '../../my/relative-module';", + options: [{ + patterns: [{ + group: ["**/my/relative-module"], + importNamePattern: "^(Foo|Bar)" + }] + }], + errors: [ + { + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 13, + message: "'Foo' import from '../../my/relative-module' is restricted from being used by a pattern." + }, + { + type: "ImportDeclaration", + line: 1, + column: 15, + endColumn: 18, + message: "'Bar' import from '../../my/relative-module' is restricted from being used by a pattern." + } + ] + }, + { + code: "import * as Foo from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + importNamePattern: "^Foo" + }] + }], + errors: [{ + message: "* import is invalid because import name matching '/^Foo/u' pattern from 'foo' is restricted from being used.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 16 + }] + }, + { + code: "import * as All from '../../my/relative-module';", + options: [{ + patterns: [{ + group: ["**/my/relative-module"], + importNamePattern: "^Foo" + }] + }], + errors: [{ + message: "* import is invalid because import name matching '/^Foo/u' pattern from '../../my/relative-module' is restricted from being used.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 16 + }] + }, + { + code: "import * as AllWithCustomMessage from '../../my/relative-module';", + options: [{ + patterns: [{ + group: ["**/my/relative-module"], + importNamePattern: "^Foo", + message: "Import from @/utils instead." + }] + }], + errors: [{ + message: "* import is invalid because import name matching '/^Foo/u' pattern from '../../my/relative-module' is restricted from being used. Import from @/utils instead.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 33 + }] + }, + { + code: "import * as AllWithCustomMessage from '../../my/relative-module';", + options: [{ + patterns: [{ + importNames: ["Foo"], + group: ["**/my/relative-module"], + importNamePattern: "^Foo", + message: "Import from @/utils instead." + }] + }], + errors: [{ + message: "* import is invalid because 'Foo' from '../../my/relative-module' is restricted from being used by a pattern. Import from @/utils instead.", + type: "ImportDeclaration", + line: 1, + column: 8, + endColumn: 33 + }] + }, + { + code: "import { Foo } from '../../my/relative-module';", + options: [{ + patterns: [{ + importNames: ["Foo"], + group: ["**/my/relative-module"], + importNamePattern: "^Foo" + }] + }], + errors: [{ + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 13, + message: "'Foo' import from '../../my/relative-module' is restricted from being used by a pattern." + }] + }, + { + code: "import { Foo } from '../../my/relative-module';", + options: [{ + patterns: [{ + importNames: ["Foo", "Bar"], + group: ["**/my/relative-module"], + importNamePattern: "^Foo" + }] + }], + errors: [{ + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 13, + message: "'Foo' import from '../../my/relative-module' is restricted from being used by a pattern." + }] + }, + { + code: "import { Foo } from '../../my/relative-module';", + options: [{ + patterns: [{ + importNames: ["Bar"], + group: ["**/my/relative-module"], + importNamePattern: "^Foo" + }] + }], + errors: [{ + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 13, + message: "'Foo' import from '../../my/relative-module' is restricted from being used by a pattern." + }] + }, + { + code: "import { Foo } from '../../my/relative-module';", + options: [{ + patterns: [{ + importNames: ["Foo"], + group: ["**/my/relative-module"], + importNamePattern: "^Bar" + }] + }], + errors: [{ + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 13, + message: "'Foo' import from '../../my/relative-module' is restricted from being used by a pattern." + }] + }, + { + code: "import { Foo, Bar } from '../../my/relative-module';", + options: [{ + patterns: [{ + importNames: ["Foo"], + group: ["**/my/relative-module"], + importNamePattern: "^Bar" + }] + }], + errors: [ + { + type: "ImportDeclaration", + line: 1, + column: 10, + endColumn: 13, + message: "'Foo' import from '../../my/relative-module' is restricted from being used by a pattern." + }, + { + type: "ImportDeclaration", + line: 1, + column: 15, + endColumn: 18, + message: "'Bar' import from '../../my/relative-module' is restricted from being used by a pattern." + } + ] + }, + { + code: "export { Foo } from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + importNamePattern: "^Foo" + }] + }], + errors: [{ + type: "ExportNamedDeclaration", + line: 1, + column: 10, + endColumn: 13, + message: "'Foo' import from 'foo' is restricted from being used by a pattern." + }] + }, + { + code: "export { Foo as Bar } from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + importNamePattern: "^Foo" + }] + }], + errors: [{ + type: "ExportNamedDeclaration", + line: 1, + column: 10, + endColumn: 20, + message: "'Foo' import from 'foo' is restricted from being used by a pattern." + }] + }, + { + code: "export { Foo } from 'foo';", + options: [{ + patterns: [{ + importNames: ["Bar"], + group: ["foo"], + importNamePattern: "^Foo" + }] + }], + errors: [{ + type: "ExportNamedDeclaration", + line: 1, + column: 10, + endColumn: 13, + message: "'Foo' import from 'foo' is restricted from being used by a pattern." + }] + }, + { + code: "export * from 'foo';", + options: [{ + patterns: [{ + group: ["foo"], + importNamePattern: "^Foo" + }] + }], + errors: [{ + type: "ExportAllDeclaration", + line: 1, + column: 8, + endColumn: 9, + message: "* import is invalid because import name matching '/^Foo/u' pattern from 'foo' is restricted from being used." + }] } ] });