1 | /**
|
2 | * @fileoverview Restrict usage of specified node modules.
|
3 | * @author Christian Schulz
|
4 | */
|
5 | ;
|
6 |
|
7 | //------------------------------------------------------------------------------
|
8 | // Rule Definition
|
9 | //------------------------------------------------------------------------------
|
10 |
|
11 | const ignore = require("ignore");
|
12 |
|
13 | const arrayOfStrings = {
|
14 | type: "array",
|
15 | items: { type: "string" },
|
16 | uniqueItems: true
|
17 | };
|
18 |
|
19 | const 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 |
|
41 | module.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 | };
|