Skip to content

Commit

Permalink
feat!: no-restricted-imports allow multiple config entries for same p…
Browse files Browse the repository at this point in the history
…ath (#18021)

* feat!: no-restricted-imports allow multiple config entries for same path

Fixes #15261

* add test with  with more elements

* update migration guide
  • Loading branch information
mdjermanovic authored Jan 24, 2024
1 parent 33d1ab0 commit 57089cb
Show file tree
Hide file tree
Showing 3 changed files with 367 additions and 43 deletions.
36 changes: 36 additions & 0 deletions docs/src/use/migrate-to-9.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ The lists below are ordered roughly by the number of users each change is expect
* [New checks in `no-implicit-coercion` by default](#no-implicit-coercion)
* [Case-sensitive flags in `no-invalid-regexp`](#no-invalid-regexp)
* [`varsIgnorePattern` option of `no-unused-vars` no longer applies to catch arguments](#vars-ignore-pattern)
* [`no-restricted-imports` now accepts multiple config entries with the same `name`](#no-restricted-imports)
* [`"eslint:recommended"` and `"eslint:all"` strings no longer accepted in flat config](#string-config)
* [`no-inner-declarations` has a new default behavior with a new option](#no-inner-declarations)

Expand Down Expand Up @@ -281,6 +282,41 @@ try {

**Related issue(s):** [#17540](https://github.com/eslint/eslint/issues/17540)

## <a name="no-restricted-imports"></a> `no-restricted-imports` now accepts multiple config entries with the same `name`

In previous versions of ESLint, if multiple entries in the `paths` array of your configuration for the `no-restricted-imports` rule had the same `name` property, only the last one would apply, while the previous ones would be ignored.

As of ESLint v9.0.0, all entries apply, allowing for specifying different messages for different imported names. For example, you can now configure the rule like this:

```js
{
rules: {
"no-restricted-imports": ["error", {
paths: [
{
name: "react-native",
importNames: ["Text"],
message: "import 'Text' from 'ui/_components' instead"
},
{
name: "react-native",
importNames: ["View"],
message: "import 'View' from 'ui/_components' instead"
}
]
}]
}
}
```

and both `import { Text } from "react-native"` and `import { View } from "react-native"` will be reported, with different messages.

In previous versions of ESLint, with this configuration only `import { View } from "react-native"` would be reported.

**To address:** If your configuration for this rule has multiple entries with the same `name`, you may need to remove unintentional ones.

**Related issue(s):** [#15261](https://github.com/eslint/eslint/issues/15261)

## <a name="string-config"></a> `"eslint:recommended"` and `"eslint:all"` no longer accepted in flat config

In ESLint v8.x, `eslint.config.js` could refer to `"eslint:recommended"` and `"eslint:all"` configurations by inserting a string into the config array, as in this example:
Expand Down
96 changes: 53 additions & 43 deletions lib/rules/no-restricted-imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,17 +161,25 @@ module.exports = {
(Object.hasOwn(options[0], "paths") || Object.hasOwn(options[0], "patterns"));

const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
const groupedRestrictedPaths = restrictedPaths.reduce((memo, importSource) => {
const path = typeof importSource === "string"
? importSource
: importSource.name;

if (!memo[path]) {
memo[path] = [];
}

if (typeof importSource === "string") {
memo[importSource] = { message: null };
memo[path].push({});
} else {
memo[importSource.name] = {
memo[path].push({
message: importSource.message,
importNames: importSource.importNames
};
});
}
return memo;
}, {});
}, Object.create(null));

// Handle patterns too, either as strings or groups
let restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
Expand Down Expand Up @@ -203,57 +211,59 @@ module.exports = {
* @private
*/
function checkRestrictedPathAndReport(importSource, importNames, node) {
if (!Object.hasOwn(restrictedPathMessages, importSource)) {
if (!Object.hasOwn(groupedRestrictedPaths, importSource)) {
return;
}

const customMessage = restrictedPathMessages[importSource].message;
const restrictedImportNames = restrictedPathMessages[importSource].importNames;
groupedRestrictedPaths[importSource].forEach(restrictedPathEntry => {
const customMessage = restrictedPathEntry.message;
const restrictedImportNames = restrictedPathEntry.importNames;

if (restrictedImportNames) {
if (importNames.has("*")) {
const specifierData = importNames.get("*")[0];
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 ? "everythingWithCustomMessage" : "everything",
loc: specifierData.loc,
messageId: customMessage ? "pathWithCustomMessage" : "path",
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
}
});
}
});
}

/**
Expand Down
Loading

1 comment on commit 57089cb

@Danyalkasiri
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.