| /** | |
| * @fileoverview Restrict usage of specified node modules. | |
| * @author Christian Schulz | |
| */ | |
| "use strict"; | |
| //------------------------------------------------------------------------------ | |
| // Helpers | |
| //------------------------------------------------------------------------------ | |
| const DEFAULT_MESSAGE_TEMPLATE = "'{{moduleName}}' module is restricted from being used."; | |
| const CUSTOM_MESSAGE_TEMPLATE = "'{{moduleName}}' module is restricted from being used. {{customMessage}}"; | |
| //------------------------------------------------------------------------------ | |
| // Rule Definition | |
| //------------------------------------------------------------------------------ | |
| 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 | |
| }; | |
| module.exports = { | |
| meta: { | |
| type: "suggestion", | |
| docs: { | |
| description: "disallow specified modules when loaded by `require`", | |
| category: "Node.js and CommonJS", | |
| recommended: false, | |
| url: "https://eslint.org/docs/rules/no-restricted-modules" | |
| }, | |
| schema: { | |
| anyOf: [ | |
| arrayOfStringsOrObjects, | |
| { | |
| type: "array", | |
| items: { | |
| type: "object", | |
| properties: { | |
| paths: arrayOfStringsOrObjects, | |
| patterns: arrayOfStrings | |
| }, | |
| additionalProperties: false | |
| }, | |
| additionalItems: false | |
| } | |
| ] | |
| } | |
| }, | |
| 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 moduleName = node.arguments[0].value.trim(); | |
| const customMessage = restrictedPathMessages[moduleName]; | |
| const message = customMessage | |
| ? CUSTOM_MESSAGE_TEMPLATE | |
| : DEFAULT_MESSAGE_TEMPLATE; | |
| context.report({ | |
| node, | |
| message, | |
| data: { | |
| moduleName, | |
| 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 moduleName = node.arguments[0].value.trim(); | |
| // check if argument value is in restricted modules array | |
| if (isRestrictedPath(moduleName)) { | |
| reportPath(node); | |
| } | |
| if (restrictedPatterns.length > 0 && ig.ignores(moduleName)) { | |
| context.report({ | |
| node, | |
| message: "'{{moduleName}}' module is restricted from being used by a pattern.", | |
| data: { moduleName } | |
| }); | |
| } | |
| } | |
| } | |
| } | |
| }; | |
| } | |
| }; |