UNPKG

7.04 kBJavaScriptView Raw
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};