Skip to content

Commit

Permalink
feat: importNamePattern option in no-restricted-imports (eslint#17721)
Browse files Browse the repository at this point in the history
* feat: importNamePatterns option in no-restricted-imports

* feat: importNamePattern works with importName

* feat: add more valid/invalid tests

* feat: update reporting message

* docs: remove note statement

* feat: refactor previous code and update docs

* feat: update previous code
  • Loading branch information
Tanujkanti4441 authored Dec 1, 2023
1 parent 83ece2a commit 8c9e6c1
Show file tree
Hide file tree
Showing 3 changed files with 561 additions and 31 deletions.
67 changes: 67 additions & 0 deletions docs/src/rules/no-restricted-imports.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/nodejs/node/tree/master/lib>):

```json
Expand Down Expand Up @@ -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" }
Expand Down Expand Up @@ -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.
85 changes: 54 additions & 31 deletions lib/rules/no-restricted-imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ const arrayOfStringsOrObjectPatterns = {
minItems: 1,
uniqueItems: true
},
importNamePattern: {
type: "string"
},
message: {
type: "string",
minLength: 1
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand All @@ -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
}
});
});
});
}
});
}

Expand Down
Loading

0 comments on commit 8c9e6c1

Please sign in to comment.