Skip to content

Commit 3c379ff

Browse files
ljharbilyavolodin
authored andcommitted
Update: no-restricted-{imports,modules}: add “patterns” (fixes #6963) (#7433)
1 parent f5764ee commit 3c379ff

File tree

7 files changed

+222
-57
lines changed

7 files changed

+222
-57
lines changed

docs/rules/no-restricted-imports.md

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,27 @@ This rule allows you to specify imports that you don't want to use in your appli
1414

1515
## Options
1616

17-
The syntax to specify restricted modules looks like this:
17+
The syntax to specify restricted imports looks like this:
1818

1919
```json
2020
"no-restricted-imports": ["error", "import1", "import2"]
2121
```
2222

23+
or like this:
24+
25+
```json
26+
"no-restricted-imports": ["error", { "paths": ["import1", "import2"] }]
27+
```
28+
29+
When using the object form, you can also specify an array of gitignore-style patterns:
30+
31+
```json
32+
"no-restricted-imports": ["error", {
33+
"paths": ["import1", "import2"],
34+
"patterns": ["import1/private/*", "import2/*", "!import2/good"]
35+
}]
36+
```
37+
2338
To restrict the use of all Node.js core imports (via https://github.com/nodejs/node/tree/master/lib):
2439

2540
```json
@@ -39,9 +54,15 @@ import fs from 'fs';
3954
```
4055

4156
```js
42-
/*eslint no-restricted-imports: ["error", "cluster"]*/
57+
/*eslint no-restricted-imports: ["error", { "paths": ["cluster"] }]*/
4358

44-
import cluster from ' cluster ';
59+
import cluster from 'cluster';
60+
```
61+
62+
```js
63+
/*eslint no-restricted-imports: ["error", { "patterns": ["lodash/*"] }]*/
64+
65+
import pick from 'lodash/pick';
4566
```
4667

4768
Examples of **correct** code for this rule:
@@ -52,6 +73,13 @@ Examples of **correct** code for this rule:
5273
import crypto from 'crypto';
5374
```
5475

76+
```js
77+
/*eslint no-restricted-imports: ["error", { "paths": ["fs"], "patterns": ["eslint/*"] }]*/
78+
79+
import crypto from 'crypto';
80+
import eslint from 'eslint';
81+
```
82+
5583
## When Not To Use It
5684

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

docs/rules/no-restricted-modules.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ This rule allows you to specify modules that you don't want to use in your appli
1414

1515
The rule takes one or more strings as options: the names of restricted modules.
1616

17+
It can also take an object with lists of "paths" and gitignore-style "patterns" strings.
18+
1719
For example, to restrict the use of all Node.js core modules (via https://github.com/nodejs/node/tree/master/lib):
1820

1921
```json
@@ -30,7 +32,19 @@ Examples of **incorrect** code for this rule with sample `"fs", "cluster"` restr
3032
/*eslint no-restricted-modules: ["error", "fs", "cluster"]*/
3133

3234
var fs = require('fs');
33-
var cluster = require(' cluster ');
35+
var cluster = require('cluster');
36+
```
37+
38+
```js
39+
/*eslint no-restricted-modules: ["error", { "paths": ["cluster"] }]*/
40+
41+
var cluster = require('cluster');
42+
```
43+
44+
```js
45+
/*eslint no-restricted-modules: ["error", { "patterns": ["lodash/*"] }]*/
46+
47+
var cluster = require('lodash/pick');
3448
```
3549

3650
Examples of **correct** code for this rule with sample `"fs", "cluster"` restricted modules:
@@ -40,3 +54,13 @@ Examples of **correct** code for this rule with sample `"fs", "cluster"` restric
4054

4155
var crypto = require('crypto');
4256
```
57+
58+
```js
59+
/*eslint no-restricted-modules: ["error", {
60+
"paths": ["fs", "cluster"],
61+
"patterns": ["lodash/*", "!lodash/pick"]
62+
}]*/
63+
64+
var crypto = require('crypto');
65+
var eslint = require('lodash/pick');
66+
```

lib/rules/no-restricted-imports.js

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88
// Rule Definition
99
//------------------------------------------------------------------------------
1010

11+
const ignore = require("ignore");
12+
13+
const arrayOfStrings = {
14+
type: "array",
15+
items: {
16+
type: "string"
17+
},
18+
uniqueItems: true
19+
};
20+
1121
module.exports = {
1222
meta: {
1323
docs: {
@@ -17,31 +27,55 @@ module.exports = {
1727
},
1828

1929
schema: {
20-
type: "array",
21-
items: {
22-
type: "string"
23-
},
24-
uniqueItems: true
30+
anyOf: [
31+
arrayOfStrings,
32+
{
33+
type: "array",
34+
items: [{
35+
type: "object",
36+
properties: {
37+
paths: arrayOfStrings,
38+
patterns: arrayOfStrings
39+
},
40+
additionalProperties: false
41+
}],
42+
additionalItems: false
43+
}
44+
]
2545
}
2646
},
2747

2848
create(context) {
29-
const restrictedImports = context.options;
49+
const options = Array.isArray(context.options) ? context.options : [];
50+
const isStringArray = typeof options[0] !== "object";
51+
const restrictedPaths = new Set(isStringArray ? context.options : options[0].paths || []);
52+
const restrictedPatterns = isStringArray ? [] : options[0].patterns || [];
3053

3154
// if no imports are restricted we don"t need to check
32-
if (restrictedImports.length === 0) {
55+
if (restrictedPaths.size === 0 && restrictedPatterns.length === 0) {
3356
return {};
3457
}
3558

59+
const ig = ignore().add(restrictedPatterns);
60+
3661
return {
3762
ImportDeclaration(node) {
3863
if (node && node.source && node.source.value) {
3964

40-
const value = node.source.value.trim();
65+
const importName = node.source.value.trim();
4166

42-
if (restrictedImports.indexOf(value) !== -1) {
43-
context.report(node, "'{{importName}}' import is restricted from being used.", {
44-
importName: value
67+
if (restrictedPaths.has(importName)) {
68+
context.report({
69+
node,
70+
message: "'{{importName}}' import is restricted from being used.",
71+
data: { importName }
72+
});
73+
}
74+
if (restrictedPatterns.length > 0 && ig.ignores(importName)) {
75+
context.report({
76+
node,
77+
message: "'{{importName}}' import is restricted from being used by a pattern.",
78+
data: { importName }
4579
});
4680
}
4781
}

lib/rules/no-restricted-modules.js

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88
// Rule Definition
99
//------------------------------------------------------------------------------
1010

11+
const ignore = require("ignore");
12+
13+
const arrayOfStrings = {
14+
type: "array",
15+
items: {
16+
type: "string"
17+
},
18+
uniqueItems: true
19+
};
20+
1121
module.exports = {
1222
meta: {
1323
docs: {
@@ -17,24 +27,37 @@ module.exports = {
1727
},
1828

1929
schema: {
20-
type: "array",
21-
items: {
22-
type: "string"
23-
},
24-
uniqueItems: true
30+
anyOf: [
31+
arrayOfStrings,
32+
{
33+
type: "array",
34+
items: [{
35+
type: "object",
36+
properties: {
37+
paths: arrayOfStrings,
38+
patterns: arrayOfStrings
39+
},
40+
additionalProperties: false
41+
}],
42+
additionalItems: false
43+
}
44+
]
2545
}
2646
},
2747

2848
create(context) {
49+
const options = Array.isArray(context.options) ? context.options : [];
50+
const isStringArray = typeof options[0] !== "object";
51+
const restrictedPaths = new Set(isStringArray ? context.options : options[0].paths || []);
52+
const restrictedPatterns = isStringArray ? [] : options[0].patterns || [];
2953

30-
// trim restricted module names
31-
const restrictedModules = context.options;
32-
33-
// if no modules are restricted we don't need to check the CallExpressions
34-
if (restrictedModules.length === 0) {
54+
// if no imports are restricted we don"t need to check
55+
if (restrictedPaths.size === 0 && restrictedPatterns.length === 0) {
3556
return {};
3657
}
3758

59+
const ig = ignore().add(restrictedPatterns);
60+
3861
/**
3962
* Function to check if a node is a string literal.
4063
* @param {ASTNode} node The node to check.
@@ -53,36 +76,30 @@ module.exports = {
5376
return node.callee.type === "Identifier" && node.callee.name === "require";
5477
}
5578

56-
/**
57-
* Function to check if a node has an argument that is an restricted module and return its name.
58-
* @param {ASTNode} node The node to check
59-
* @returns {undefined|string} restricted module name or undefined if node argument isn't restricted.
60-
*/
61-
function getRestrictedModuleName(node) {
62-
let moduleName;
63-
64-
// node has arguments and first argument is string
65-
if (node.arguments.length && isString(node.arguments[0])) {
66-
const argumentValue = node.arguments[0].value.trim();
67-
68-
// check if argument value is in restricted modules array
69-
if (restrictedModules.indexOf(argumentValue) !== -1) {
70-
moduleName = argumentValue;
71-
}
72-
}
73-
74-
return moduleName;
75-
}
76-
7779
return {
7880
CallExpression(node) {
7981
if (isRequireCall(node)) {
80-
const restrictedModuleName = getRestrictedModuleName(node);
8182

82-
if (restrictedModuleName) {
83-
context.report(node, "'{{moduleName}}' module is restricted from being used.", {
84-
moduleName: restrictedModuleName
85-
});
83+
// node has arguments and first argument is string
84+
if (node.arguments.length && isString(node.arguments[0])) {
85+
const moduleName = node.arguments[0].value.trim();
86+
87+
// check if argument value is in restricted modules array
88+
if (restrictedPaths.has(moduleName)) {
89+
context.report({
90+
node,
91+
message: "'{{moduleName}}' module is restricted from being used.",
92+
data: { moduleName }
93+
});
94+
}
95+
96+
if (restrictedPatterns.length > 0 && ig.ignores(moduleName)) {
97+
context.report({
98+
node,
99+
message: "'{{moduleName}}' module is restricted from being used by a pattern.",
100+
data: { moduleName }
101+
});
102+
}
86103
}
87104
}
88105
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"file-entry-cache": "^2.0.0",
4747
"glob": "^7.0.3",
4848
"globals": "^9.2.0",
49-
"ignore": "^3.1.5",
49+
"ignore": "^3.2.0",
5050
"imurmurhash": "^0.1.4",
5151
"inquirer": "^0.12.0",
5252
"is-my-json-valid": "^2.10.0",

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

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,48 @@ ruleTester.run("no-restricted-imports", rule, {
2424
{ code: "import fs from \"fs\";", options: ["crypto"], parserOptions: { sourceType: "module" } },
2525
{ code: "import path from \"path\";", options: ["crypto", "stream", "os"], parserOptions: { sourceType: "module" } },
2626
{ code: "import async from \"async\";", parserOptions: { sourceType: "module" } },
27-
{ code: "import \"foo\"", options: ["crypto"], parserOptions: { sourceType: "module" } }
27+
{ code: "import \"foo\"", options: ["crypto"], parserOptions: { sourceType: "module" } },
28+
{ code: "import \"foo/bar\";", options: ["foo"], parserOptions: { sourceType: "module" } },
29+
{ code: "import withPaths from \"foo/bar\";", options: [{ paths: ["foo", "bar"] }], parserOptions: { sourceType: "module" } },
30+
{ code: "import withPatterns from \"foo/bar\";", options: [{ patterns: ["foo/c*"] }], parserOptions: { sourceType: "module" } },
31+
{
32+
code: "import withPatternsAndPaths from \"foo/bar\";",
33+
options: [{ paths: ["foo"], patterns: ["foo/c*"] }],
34+
parserOptions: { sourceType: "module" }
35+
},
36+
{
37+
code: "import withGitignores from \"foo/bar\";",
38+
options: [{ patterns: ["foo/*", "!foo/bar"] }],
39+
parserOptions: { sourceType: "module" }
40+
}
2841
],
2942
invalid: [{
3043
code: "import \"fs\"", options: ["fs"], parserOptions: { sourceType: "module" },
3144
errors: [{ message: "'fs' import is restricted from being used.", type: "ImportDeclaration"}]
3245
}, {
33-
code: "import os from \"os \";", options: ["fs", "crypto ", "stream", "os"], parserOptions: { sourceType: "module" },
46+
code: "import os from \"os \";",
47+
options: ["fs", "crypto ", "stream", "os"],
48+
parserOptions: { sourceType: "module" },
3449
errors: [{ message: "'os' import is restricted from being used.", type: "ImportDeclaration"}]
50+
}, {
51+
code: "import \"foo/bar\";",
52+
options: ["foo/bar"],
53+
parserOptions: { sourceType: "module" },
54+
errors: [{ message: "'foo/bar' import is restricted from being used.", type: "ImportDeclaration"}]
55+
}, {
56+
code: "import withPaths from \"foo/bar\";",
57+
options: [{ paths: ["foo/bar"] }],
58+
parserOptions: { sourceType: "module" },
59+
errors: [{ message: "'foo/bar' import is restricted from being used.", type: "ImportDeclaration"}]
60+
}, {
61+
code: "import withPatterns from \"foo/bar\";",
62+
options: [{ patterns: ["foo/*"] }],
63+
parserOptions: { sourceType: "module" },
64+
errors: [{ message: "'foo/bar' import is restricted from being used by a pattern.", type: "ImportDeclaration"}]
65+
}, {
66+
code: "import withGitignores from \"foo/bar\";",
67+
options: [{ patterns: ["foo/*", "!foo/baz"] }],
68+
parserOptions: { sourceType: "module" },
69+
errors: [{ message: "'foo/bar' import is restricted from being used by a pattern.", type: "ImportDeclaration"}]
3570
}]
3671
});

0 commit comments

Comments
 (0)