• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Restrict usage of specified node modules.
3 * @author Christian Schulz
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Rule Definition
9//------------------------------------------------------------------------------
10
11const ignore = require("ignore");
12
13const arrayOfStrings = {
14    type: "array",
15    items: { type: "string" },
16    uniqueItems: true
17};
18
19const arrayOfStringsOrObjects = {
20    type: "array",
21    items: {
22        anyOf: [
23            { type: "string" },
24            {
25                type: "object",
26                properties: {
27                    name: { type: "string" },
28                    message: {
29                        type: "string",
30                        minLength: 1
31                    }
32                },
33                additionalProperties: false,
34                required: ["name"]
35            }
36        ]
37    },
38    uniqueItems: true
39};
40
41module.exports = {
42    meta: {
43        deprecated: true,
44
45        replacedBy: [],
46
47        type: "suggestion",
48
49        docs: {
50            description: "disallow specified modules when loaded by `require`",
51            category: "Node.js and CommonJS",
52            recommended: false,
53            url: "https://eslint.org/docs/rules/no-restricted-modules"
54        },
55
56        schema: {
57            anyOf: [
58                arrayOfStringsOrObjects,
59                {
60                    type: "array",
61                    items: {
62                        type: "object",
63                        properties: {
64                            paths: arrayOfStringsOrObjects,
65                            patterns: arrayOfStrings
66                        },
67                        additionalProperties: false
68                    },
69                    additionalItems: false
70                }
71            ]
72        },
73
74        messages: {
75            defaultMessage: "'{{name}}' module is restricted from being used.",
76            // eslint-disable-next-line eslint-plugin/report-message-format
77            customMessage: "'{{name}}' module is restricted from being used. {{customMessage}}",
78            patternMessage: "'{{name}}' module is restricted from being used by a pattern."
79        }
80    },
81
82    create(context) {
83        const options = Array.isArray(context.options) ? context.options : [];
84        const isPathAndPatternsObject =
85            typeof options[0] === "object" &&
86            (Object.prototype.hasOwnProperty.call(options[0], "paths") || Object.prototype.hasOwnProperty.call(options[0], "patterns"));
87
88        const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
89        const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
90
91        const restrictedPathMessages = restrictedPaths.reduce((memo, importName) => {
92            if (typeof importName === "string") {
93                memo[importName] = null;
94            } else {
95                memo[importName.name] = importName.message;
96            }
97            return memo;
98        }, {});
99
100        // if no imports are restricted we don"t need to check
101        if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) {
102            return {};
103        }
104
105        const ig = ignore().add(restrictedPatterns);
106
107
108        /**
109         * Function to check if a node is a string literal.
110         * @param {ASTNode} node The node to check.
111         * @returns {boolean} If the node is a string literal.
112         */
113        function isStringLiteral(node) {
114            return node && node.type === "Literal" && typeof node.value === "string";
115        }
116
117        /**
118         * Function to check if a node is a static string template literal.
119         * @param {ASTNode} node The node to check.
120         * @returns {boolean} If the node is a string template literal.
121         */
122        function isStaticTemplateLiteral(node) {
123            return node && node.type === "TemplateLiteral" && node.expressions.length === 0;
124        }
125
126        /**
127         * Function to check if a node is a require call.
128         * @param {ASTNode} node The node to check.
129         * @returns {boolean} If the node is a require call.
130         */
131        function isRequireCall(node) {
132            return node.callee.type === "Identifier" && node.callee.name === "require";
133        }
134
135        /**
136         * Extract string from Literal or TemplateLiteral node
137         * @param {ASTNode} node The node to extract from
138         * @returns {string|null} Extracted string or null if node doesn't represent a string
139         */
140        function getFirstArgumentString(node) {
141            if (isStringLiteral(node)) {
142                return node.value.trim();
143            }
144
145            if (isStaticTemplateLiteral(node)) {
146                return node.quasis[0].value.cooked.trim();
147            }
148
149            return null;
150        }
151
152        /**
153         * Report a restricted path.
154         * @param {node} node representing the restricted path reference
155         * @param {string} name restricted path
156         * @returns {void}
157         * @private
158         */
159        function reportPath(node, name) {
160            const customMessage = restrictedPathMessages[name];
161            const messageId = customMessage
162                ? "customMessage"
163                : "defaultMessage";
164
165            context.report({
166                node,
167                messageId,
168                data: {
169                    name,
170                    customMessage
171                }
172            });
173        }
174
175        /**
176         * Check if the given name is a restricted path name
177         * @param {string} name name of a variable
178         * @returns {boolean} whether the variable is a restricted path or not
179         * @private
180         */
181        function isRestrictedPath(name) {
182            return Object.prototype.hasOwnProperty.call(restrictedPathMessages, name);
183        }
184
185        return {
186            CallExpression(node) {
187                if (isRequireCall(node)) {
188
189                    // node has arguments
190                    if (node.arguments.length) {
191                        const name = getFirstArgumentString(node.arguments[0]);
192
193                        // if first argument is a string literal or a static string template literal
194                        if (name) {
195
196                            // check if argument value is in restricted modules array
197                            if (isRestrictedPath(name)) {
198                                reportPath(node, name);
199                            }
200
201                            if (restrictedPatterns.length > 0 && ig.ignores(name)) {
202                                context.report({
203                                    node,
204                                    messageId: "patternMessage",
205                                    data: { name }
206                                });
207                            }
208                        }
209                    }
210                }
211            }
212        };
213    }
214};
215