Skip to content

Commit 57089cb

Browse files
authored
feat!: no-restricted-imports allow multiple config entries for same path (#18021)
* feat!: no-restricted-imports allow multiple config entries for same path Fixes #15261 * add test with with more elements * update migration guide
1 parent 33d1ab0 commit 57089cb

File tree

3 files changed

+367
-43
lines changed

3 files changed

+367
-43
lines changed

docs/src/use/migrate-to-9.0.0.md

+36
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ The lists below are ordered roughly by the number of users each change is expect
3030
* [New checks in `no-implicit-coercion` by default](#no-implicit-coercion)
3131
* [Case-sensitive flags in `no-invalid-regexp`](#no-invalid-regexp)
3232
* [`varsIgnorePattern` option of `no-unused-vars` no longer applies to catch arguments](#vars-ignore-pattern)
33+
* [`no-restricted-imports` now accepts multiple config entries with the same `name`](#no-restricted-imports)
3334
* [`"eslint:recommended"` and `"eslint:all"` strings no longer accepted in flat config](#string-config)
3435
* [`no-inner-declarations` has a new default behavior with a new option](#no-inner-declarations)
3536

@@ -281,6 +282,41 @@ try {
281282

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

285+
## <a name="no-restricted-imports"></a> `no-restricted-imports` now accepts multiple config entries with the same `name`
286+
287+
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.
288+
289+
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:
290+
291+
```js
292+
{
293+
rules: {
294+
"no-restricted-imports": ["error", {
295+
paths: [
296+
{
297+
name: "react-native",
298+
importNames: ["Text"],
299+
message: "import 'Text' from 'ui/_components' instead"
300+
},
301+
{
302+
name: "react-native",
303+
importNames: ["View"],
304+
message: "import 'View' from 'ui/_components' instead"
305+
}
306+
]
307+
}]
308+
}
309+
}
310+
```
311+
312+
and both `import { Text } from "react-native"` and `import { View } from "react-native"` will be reported, with different messages.
313+
314+
In previous versions of ESLint, with this configuration only `import { View } from "react-native"` would be reported.
315+
316+
**To address:** If your configuration for this rule has multiple entries with the same `name`, you may need to remove unintentional ones.
317+
318+
**Related issue(s):** [#15261](https://github.com/eslint/eslint/issues/15261)
319+
284320
## <a name="string-config"></a> `"eslint:recommended"` and `"eslint:all"` no longer accepted in flat config
285321

286322
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:

lib/rules/no-restricted-imports.js

+53-43
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,25 @@ module.exports = {
161161
(Object.hasOwn(options[0], "paths") || Object.hasOwn(options[0], "patterns"));
162162

163163
const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
164-
const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
164+
const groupedRestrictedPaths = restrictedPaths.reduce((memo, importSource) => {
165+
const path = typeof importSource === "string"
166+
? importSource
167+
: importSource.name;
168+
169+
if (!memo[path]) {
170+
memo[path] = [];
171+
}
172+
165173
if (typeof importSource === "string") {
166-
memo[importSource] = { message: null };
174+
memo[path].push({});
167175
} else {
168-
memo[importSource.name] = {
176+
memo[path].push({
169177
message: importSource.message,
170178
importNames: importSource.importNames
171-
};
179+
});
172180
}
173181
return memo;
174-
}, {});
182+
}, Object.create(null));
175183

176184
// Handle patterns too, either as strings or groups
177185
let restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
@@ -203,57 +211,59 @@ module.exports = {
203211
* @private
204212
*/
205213
function checkRestrictedPathAndReport(importSource, importNames, node) {
206-
if (!Object.hasOwn(restrictedPathMessages, importSource)) {
214+
if (!Object.hasOwn(groupedRestrictedPaths, importSource)) {
207215
return;
208216
}
209217

210-
const customMessage = restrictedPathMessages[importSource].message;
211-
const restrictedImportNames = restrictedPathMessages[importSource].importNames;
218+
groupedRestrictedPaths[importSource].forEach(restrictedPathEntry => {
219+
const customMessage = restrictedPathEntry.message;
220+
const restrictedImportNames = restrictedPathEntry.importNames;
212221

213-
if (restrictedImportNames) {
214-
if (importNames.has("*")) {
215-
const specifierData = importNames.get("*")[0];
222+
if (restrictedImportNames) {
223+
if (importNames.has("*")) {
224+
const specifierData = importNames.get("*")[0];
225+
226+
context.report({
227+
node,
228+
messageId: customMessage ? "everythingWithCustomMessage" : "everything",
229+
loc: specifierData.loc,
230+
data: {
231+
importSource,
232+
importNames: restrictedImportNames,
233+
customMessage
234+
}
235+
});
236+
}
216237

238+
restrictedImportNames.forEach(importName => {
239+
if (importNames.has(importName)) {
240+
const specifiers = importNames.get(importName);
241+
242+
specifiers.forEach(specifier => {
243+
context.report({
244+
node,
245+
messageId: customMessage ? "importNameWithCustomMessage" : "importName",
246+
loc: specifier.loc,
247+
data: {
248+
importSource,
249+
customMessage,
250+
importName
251+
}
252+
});
253+
});
254+
}
255+
});
256+
} else {
217257
context.report({
218258
node,
219-
messageId: customMessage ? "everythingWithCustomMessage" : "everything",
220-
loc: specifierData.loc,
259+
messageId: customMessage ? "pathWithCustomMessage" : "path",
221260
data: {
222261
importSource,
223-
importNames: restrictedImportNames,
224262
customMessage
225263
}
226264
});
227265
}
228-
229-
restrictedImportNames.forEach(importName => {
230-
if (importNames.has(importName)) {
231-
const specifiers = importNames.get(importName);
232-
233-
specifiers.forEach(specifier => {
234-
context.report({
235-
node,
236-
messageId: customMessage ? "importNameWithCustomMessage" : "importName",
237-
loc: specifier.loc,
238-
data: {
239-
importSource,
240-
customMessage,
241-
importName
242-
}
243-
});
244-
});
245-
}
246-
});
247-
} else {
248-
context.report({
249-
node,
250-
messageId: customMessage ? "pathWithCustomMessage" : "path",
251-
data: {
252-
importSource,
253-
customMessage
254-
}
255-
});
256-
}
266+
});
257267
}
258268

259269
/**

0 commit comments

Comments
 (0)