Skip to content

Commit

Permalink
⚒ simplify the options of no-restricted-require
Browse files Browse the repository at this point in the history
  • Loading branch information
mysticatea committed Mar 28, 2020
1 parent 8788a11 commit 578110e
Show file tree
Hide file tree
Showing 4 changed files with 323 additions and 305 deletions.
127 changes: 69 additions & 58 deletions docs/rules/no-restricted-require.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,96 +14,107 @@ This rule allows you to specify modules that you don’t want to use in your app

### Options

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

```json
"no-restricted-require": ["error", "foo-module", "bar-module"]
```

It can also take an object with lists of `paths` and gitignore-style `patterns` strings.

```json
"no-restricted-require": ["error", { "paths": ["foo-module", "bar-module"] }]
```

```json
"no-restricted-require": ["error", {
"paths": ["foo-module", "bar-module"],
"patterns": ["foo-module/private/*", "bar-module/*","!baz-module/good"]
}]
```

You may also specify a custom message for any paths you want to restrict as follows:

```json
"no-restricted-require": ["error", {
"name": "foo-module",
"message": "Please use bar-module instead."
}
]
{
"no-restricted-require": ["error", [
"foo-module",
"bar-module"
]]
}
```

or like this:
You may also specify a custom message for each module you want to restrict as follows:

```json
"no-restricted-require": ["error",{
"paths":[{
"name": "foo-module",
"message": "Please use bar-module instead."
}]
}]
{
"no-restricted-require": ["error", [
{
"name": "foo-module",
"message": "Please use foo-module2 instead."
},
{
"name": "bar-module",
"message": "Please use bar-module2 instead."
}
]]
}
```

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 module may match more than one pattern.


To restrict the use of all Node.js core modules (via https://github.com/nodejs/node/tree/master/lib):
And you can use glob patterns in the `name` property.

```json
{
"no-restricted-require": ["error",
"assert","buffer","child_process","cluster","crypto","dgram","dns","domain","events","freelist","fs","http","https","module","net","os","path","punycode","querystring","readline","repl","smalloc","stream","string_decoder","sys","timers","tls","tracing","tty","url","util","vm","zlib"
]
"no-restricted-require": ["error", [
{
"name": "lodash/*",
"message": "Please use xyz-module instead."
},
{
"name": ["foo-module/private/*", "bar-module/*", "!baz-module/good"],
"message": "Please use xyz-module instead."
}
]]
}
```

Examples of **incorrect** code for this rule with sample `"fs", "cluster", "lodash"` restricted modules:
And you can use absolute paths in the `name` property.

```js
/*eslint no-restricted-require: ["error", "fs", "cluster"]*/

var fs = require('fs');
var cluster = require('cluster');
module.exports = {
overrides: [
{
files: "client/**",
rules: {
"no-restricted-require": ["error", [
{
name: path.resolve(__dirname, "server/**"),
message: "Don't use server code from client code."
}
]]
}
},
{
files: "server/**",
rules: {
"no-restricted-require": ["error", [
{
name: path.resolve(__dirname, "client/**"),
message: "Don't use client code from server code."
}
]]
}
}
]
}
```

```js
/*eslint no-restricted-require: ["error", {"paths": ["cluster"] }]*/
### Examples

var cluster = require('cluster');
```
Examples of **incorrect** code for this rule with sample `"fs", "cluster", "lodash"` restricted modules:

```js
/*eslint no-restricted-require: ["error", { "patterns": ["lodash/*"] }]*/
/*eslint no-restricted-require: ["error", ["fs", "cluster", "lodash/*"]]*/

var pick = require('lodash/pick');
const fs = require('fs');
const cluster = require('cluster');
const pick = require('lodash/pick');
```

Examples of **correct** code for this rule with sample `"fs", "cluster", "lodash"` restricted modules:

```js
/*eslint no-restricted-require: ["error", "fs", "cluster"]*/
/*eslint no-restricted-require: ["error", ["fs", "cluster", "lodash/*"]]*/

var crypto = require('crypto');
const crypto = require('crypto');
const _ = require('lodash');
```

```js
/*eslint no-restricted-require: ["error", {
"paths": ["fs", "cluster"],
"patterns": ["lodash/*", "!lodash/pick"]
}]*/
/*eslint no-restricted-require: ["error", ["fs", "cluster", { "name": ["lodash/*", "!lodash/pick"] }]]*/

var crypto = require('crypto');
var pick = require('lodash/pick');
const pick = require('lodash/pick');
```

## 🔎 Implementation
Expand Down
198 changes: 34 additions & 164 deletions lib/rules/no-restricted-require.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,12 @@
/**
* @author Christian Schulz
* @author Toru Nagashima
* See LICENSE file in root directory for full license.
*/
"use strict"

const ignore = require("ignore")

const arrayOfStrings = {
type: "array",
items: { type: "string" },
uniqueItems: true,
}

const arrayOfStringsOrObjects = {
type: "array",
items: {
anyOf: [
{ type: "string" },
{
type: "object",
properties: {
name: { type: "string" },
message: {
type: "string",
minLength: 1,
},
},
additionalProperties: false,
required: ["name"],
},
],
},
uniqueItems: true,
}
const check = require("../util/check-restricted")
const visit = require("../util/visit-require")

module.exports = {
meta: {
Expand All @@ -45,148 +19,44 @@ module.exports = {
"https://github.com/mysticatea/eslint-plugin-node/blob/v11.0.0/docs/rules/no-restricted-require.md",
},
fixable: null,
schema: {
anyOf: [
arrayOfStringsOrObjects,
{
type: "array",
items: {
type: "object",
properties: {
paths: arrayOfStringsOrObjects,
patterns: arrayOfStrings,
schema: [
{
type: "array",
items: {
anyOf: [
{ type: "string" },
{
type: "object",
properties: {
name: {
anyOf: [
{ type: "string" },
{
type: "array",
items: { type: "string" },
additionalItems: false,
},
],
},
message: { type: "string" },
},
additionalProperties: false,
required: ["name"],
},
additionalProperties: false,
},
additionalItems: false,
],
},
],
},
additionalItems: false,
},
],
messages: {
defaultMessage: "'{{name}}' module is restricted from being used.",
customMessage:
restricted:
// eslint-disable-next-line @mysticatea/eslint-plugin/report-message-format
"'{{name}}' module is restricted from being used. {{customMessage}}",
patternMessage:
"'{{name}}' module is restricted from being used by a pattern.",
"'{{name}}' module is restricted from being used.{{customMessage}}",
},
},

create(context) {
const options = Array.isArray(context.options) ? context.options : []
const isPathAndPatternsObject =
typeof options[0] === "object" &&
(Object.prototype.hasOwnProperty.call(options[0], "paths") ||
Object.prototype.hasOwnProperty.call(options[0], "patterns"))

const restrictedPaths =
(isPathAndPatternsObject ? options[0].paths : context.options) || []
const restrictedPatterns =
(isPathAndPatternsObject ? options[0].patterns : []) || []

const restrictedPathMessages = restrictedPaths.reduce(
(memo, importName) => {
if (typeof importName === "string") {
memo[importName] = null
} else {
memo[importName.name] = importName.message
}
return memo
},
{}
)

// if no imports are restricted we don"t need to check
if (
Object.keys(restrictedPaths).length === 0 &&
restrictedPatterns.length === 0
) {
return {}
}

const ig = ignore().add(restrictedPatterns)

/**
* Function to check if a node is a string literal.
* @param {ASTNode} node The node to check.
* @returns {boolean} If the node is a string literal.
*/
function isString(node) {
return (
node &&
node.type === "Literal" &&
typeof node.value === "string"
)
}

/**
* Function to check if a node is a require call.
* @param {ASTNode} node The node to check.
* @returns {boolean} If the node is a require call.
*/
function isRequireCall(node) {
return (
node.callee.type === "Identifier" &&
node.callee.name === "require"
)
}

/**
* Report a restricted path.
* @param {node} node representing the restricted path reference
* @returns {void}
* @private
*/
function reportPath(node) {
const name = node.arguments[0].value.trim()
const customMessage = restrictedPathMessages[name]
const messageId = customMessage ? "customMessage" : "defaultMessage"

context.report({
node,
messageId,
data: {
name,
customMessage,
},
})
}

/**
* Check if the given name is a restricted path name
* @param {string} name name of a variable
* @returns {boolean} whether the variable is a restricted path or not
* @private
*/
function isRestrictedPath(name) {
return Object.prototype.hasOwnProperty.call(
restrictedPathMessages,
name
)
}

return {
CallExpression(node) {
if (isRequireCall(node)) {
// node has arguments and first argument is string
if (node.arguments.length && isString(node.arguments[0])) {
const name = node.arguments[0].value.trim()

// check if argument value is in restricted modules array
if (isRestrictedPath(name)) {
reportPath(node)
}

if (restrictedPatterns.length > 0 && ig.ignores(name)) {
context.report({
node,
messageId: "patternMessage",
data: { name },
})
}
}
}
},
}
const opts = { includeCore: true }
return visit(context, opts, targets => check(context, targets))
},
}
Loading

0 comments on commit 578110e

Please sign in to comment.