Skip to content

Commit 52655dd

Browse files
Update: no-restricted-imports custom message for patterns (fixes #11843) (#14580)
* Update: no-restricted-imports rule with custom messages (fixes #11843) * Update docs/rules/no-restricted-imports.md Co-authored-by: Milos Djermanovic <[email protected]> * Update docs/rules/no-restricted-imports.md Co-authored-by: Milos Djermanovic <[email protected]> * Code review feedback - better test cases and schema change * Doc updates * Added correct/incorrect examples to docs Co-authored-by: Milos Djermanovic <[email protected]>
1 parent 967b1c4 commit 52655dd

3 files changed

Lines changed: 128 additions & 25 deletions

File tree

docs/rules/no-restricted-imports.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,21 @@ or like this if you need to restrict only certain imports from a module:
7575
}]
7676
```
7777

78-
The custom message will be appended to the default error message. Please note that you may not specify custom error messages for restricted patterns as a particular import may match more than one pattern.
78+
or like this if you want to apply a custom message to pattern matches:
79+
80+
```json
81+
"no-restricted-imports": ["error", {
82+
"patterns": [{
83+
"group": ["import1/private/*"],
84+
"message": "usage of import1 private modules not allowed."
85+
}, {
86+
"group": ["import2/*", "!import2/good"],
87+
"message": "import2 is deprecated, except the modules in import2/good."
88+
}]
89+
}]
90+
```
91+
92+
The custom message will be appended to the default error message.
7993

8094
To restrict the use of all Node.js core imports (via https://github.com/nodejs/node/tree/master/lib):
8195

@@ -149,6 +163,15 @@ import { DisallowedObject as AllowedObject } from "foo";
149163
import * as Foo from "foo";
150164
```
151165

166+
```js
167+
/*eslint no-restricted-imports: ["error", { patterns: [{
168+
group: ["lodash/*"],
169+
message: "Please use the default import from 'lodash' instead."
170+
}]}]*/
171+
172+
import pick from 'lodash/pick';
173+
```
174+
152175
Examples of **correct** code for this rule:
153176

154177
```js
@@ -182,6 +205,15 @@ import DisallowedObject from "foo"
182205
import { AllowedObject as DisallowedObject } from "foo";
183206
```
184207

208+
```js
209+
/*eslint no-restricted-imports: ["error", { patterns: [{
210+
group: ["lodash/*"],
211+
message: "Please use the default import from 'lodash' instead."
212+
}]}]*/
213+
214+
import lodash from 'lodash';
215+
```
216+
185217
## When Not To Use It
186218

187219
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.

lib/rules/no-restricted-imports.js

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,6 @@
1010

1111
const ignore = require("ignore");
1212

13-
const arrayOfStrings = {
14-
type: "array",
15-
items: { type: "string" },
16-
uniqueItems: true
17-
};
18-
1913
const arrayOfStringsOrObjects = {
2014
type: "array",
2115
items: {
@@ -44,6 +38,41 @@ const arrayOfStringsOrObjects = {
4438
uniqueItems: true
4539
};
4640

41+
const arrayOfStringsOrObjectPatterns = {
42+
anyOf: [
43+
{
44+
type: "array",
45+
items: {
46+
type: "string"
47+
},
48+
uniqueItems: true
49+
},
50+
{
51+
type: "array",
52+
items: {
53+
type: "object",
54+
properties: {
55+
group: {
56+
type: "array",
57+
items: {
58+
type: "string"
59+
},
60+
minItems: 1,
61+
uniqueItems: true
62+
},
63+
message: {
64+
type: "string",
65+
minLength: 1
66+
}
67+
},
68+
additionalProperties: false,
69+
required: ["group"]
70+
},
71+
uniqueItems: true
72+
}
73+
]
74+
};
75+
4776
module.exports = {
4877
meta: {
4978
type: "suggestion",
@@ -61,6 +90,8 @@ module.exports = {
6190
pathWithCustomMessage: "'{{importSource}}' import is restricted from being used. {{customMessage}}",
6291

6392
patterns: "'{{importSource}}' import is restricted from being used by a pattern.",
93+
// eslint-disable-next-line eslint-plugin/report-message-format
94+
patternWithCustomMessage: "'{{importSource}}' import is restricted from being used by a pattern. {{customMessage}}",
6495

6596
everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.",
6697
// eslint-disable-next-line eslint-plugin/report-message-format
@@ -80,7 +111,7 @@ module.exports = {
80111
type: "object",
81112
properties: {
82113
paths: arrayOfStringsOrObjects,
83-
patterns: arrayOfStrings
114+
patterns: arrayOfStringsOrObjectPatterns
84115
},
85116
additionalProperties: false
86117
}],
@@ -98,13 +129,6 @@ module.exports = {
98129
(Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns"));
99130

100131
const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
101-
const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
102-
103-
// if no imports are restricted we don"t need to check
104-
if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
105-
return {};
106-
}
107-
108132
const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
109133
if (typeof importSource === "string") {
110134
memo[importSource] = { message: null };
@@ -117,7 +141,16 @@ module.exports = {
117141
return memo;
118142
}, {});
119143

120-
const restrictedPatternsMatcher = ignore().add(restrictedPatterns);
144+
// Handle patterns too, either as strings or groups
145+
const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
146+
const restrictedPatternGroups = restrictedPatterns.length > 0 && typeof restrictedPatterns[0] === "string"
147+
? [{ matcher: ignore().add(restrictedPatterns) }]
148+
: restrictedPatterns.map(({ group, message }) => ({ matcher: ignore().add(group), customMessage: message }));
149+
150+
// if no imports are restricted we don"t need to check
151+
if (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) {
152+
return {};
153+
}
121154

122155
/**
123156
* Report a restricted path.
@@ -184,29 +217,32 @@ module.exports = {
184217
/**
185218
* Report a restricted path specifically for patterns.
186219
* @param {node} node representing the restricted path reference
220+
* @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails
187221
* @returns {void}
188222
* @private
189223
*/
190-
function reportPathForPatterns(node) {
224+
function reportPathForPatterns(node, group) {
191225
const importSource = node.source.value.trim();
192226

193227
context.report({
194228
node,
195-
messageId: "patterns",
229+
messageId: group.customMessage ? "patternWithCustomMessage" : "patterns",
196230
data: {
197-
importSource
231+
importSource,
232+
customMessage: group.customMessage
198233
}
199234
});
200235
}
201236

202237
/**
203238
* Check if the given importSource is restricted by a pattern.
204239
* @param {string} importSource path of the import
240+
* @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails
205241
* @returns {boolean} whether the variable is a restricted pattern or not
206242
* @private
207243
*/
208-
function isRestrictedPattern(importSource) {
209-
return restrictedPatterns.length > 0 && restrictedPatternsMatcher.ignores(importSource);
244+
function isRestrictedPattern(importSource, group) {
245+
return group.matcher.ignores(importSource);
210246
}
211247

212248
/**
@@ -249,10 +285,11 @@ module.exports = {
249285
}
250286

251287
checkRestrictedPathAndReport(importSource, importNames, node);
252-
253-
if (isRestrictedPattern(importSource)) {
254-
reportPathForPatterns(node);
255-
}
288+
restrictedPatternGroups.forEach(group => {
289+
if (isRestrictedPattern(importSource, group)) {
290+
reportPathForPatterns(node, group);
291+
}
292+
});
256293
}
257294

258295
return {

tests/lib/rules/no-restricted-imports.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ ruleTester.run("no-restricted-imports", rule, {
3737
code: "import withGitignores from \"foo/bar\";",
3838
options: [{ patterns: ["foo/*", "!foo/bar"] }]
3939
},
40+
{
41+
code: "import withPatterns from \"foo/bar\";",
42+
options: [{ patterns: [{ group: ["foo/*", "!foo/bar"], message: "foo is forbidden, use bar instead" }] }]
43+
},
4044
{
4145
code: "import AllowedObject from \"foo\";",
4246
options: [{
@@ -241,6 +245,36 @@ ruleTester.run("no-restricted-imports", rule, {
241245
column: 1,
242246
endColumn: 36
243247
}]
248+
}, {
249+
code: "import withPatterns from \"foo/baz\";",
250+
options: [{ patterns: [{ group: ["foo/*", "!foo/bar"], message: "foo is forbidden, use foo/bar instead" }] }],
251+
errors: [{
252+
message: "'foo/baz' import is restricted from being used by a pattern. foo is forbidden, use foo/bar instead",
253+
type: "ImportDeclaration",
254+
line: 1,
255+
column: 1,
256+
endColumn: 36
257+
}]
258+
}, {
259+
code: "import withPatterns from \"foo/baz\";",
260+
options: [{ patterns: [{ group: ["foo/bar", "foo/baz"], message: "some foo subimports are restricted" }] }],
261+
errors: [{
262+
message: "'foo/baz' import is restricted from being used by a pattern. some foo subimports are restricted",
263+
type: "ImportDeclaration",
264+
line: 1,
265+
column: 1,
266+
endColumn: 36
267+
}]
268+
}, {
269+
code: "import withPatterns from \"foo/bar\";",
270+
options: [{ patterns: [{ group: ["foo/bar"] }] }],
271+
errors: [{
272+
message: "'foo/bar' import is restricted from being used by a pattern.",
273+
type: "ImportDeclaration",
274+
line: 1,
275+
column: 1,
276+
endColumn: 36
277+
}]
244278
}, {
245279
code: "import withGitignores from \"foo/bar\";",
246280
options: [{ patterns: ["foo/*", "!foo/baz"] }],

0 commit comments

Comments
 (0)