1 | /**
|
2 | * @fileoverview Rule to enforce grouped require statements for Node.JS
|
3 | * @author Raphael Pigulla
|
4 | */
|
5 |
|
6 | ;
|
7 |
|
8 | //------------------------------------------------------------------------------
|
9 | // Rule Definition
|
10 | //------------------------------------------------------------------------------
|
11 |
|
12 | module.exports = {
|
13 | meta: {
|
14 | deprecated: true,
|
15 |
|
16 | replacedBy: [],
|
17 |
|
18 | type: "suggestion",
|
19 |
|
20 | docs: {
|
21 | description: "disallow `require` calls to be mixed with regular variable declarations",
|
22 | category: "Node.js and CommonJS",
|
23 | recommended: false,
|
24 | url: "https://eslint.org/docs/rules/no-mixed-requires"
|
25 | },
|
26 |
|
27 | schema: [
|
28 | {
|
29 | oneOf: [
|
30 | {
|
31 | type: "boolean"
|
32 | },
|
33 | {
|
34 | type: "object",
|
35 | properties: {
|
36 | grouping: {
|
37 | type: "boolean"
|
38 | },
|
39 | allowCall: {
|
40 | type: "boolean"
|
41 | }
|
42 | },
|
43 | additionalProperties: false
|
44 | }
|
45 | ]
|
46 | }
|
47 | ],
|
48 |
|
49 | messages: {
|
50 | noMixRequire: "Do not mix 'require' and other declarations.",
|
51 | noMixCoreModuleFileComputed: "Do not mix core, module, file and computed requires."
|
52 | }
|
53 | },
|
54 |
|
55 | create(context) {
|
56 |
|
57 | const options = context.options[0];
|
58 | let grouping = false,
|
59 | allowCall = false;
|
60 |
|
61 | if (typeof options === "object") {
|
62 | grouping = options.grouping;
|
63 | allowCall = options.allowCall;
|
64 | } else {
|
65 | grouping = !!options;
|
66 | }
|
67 |
|
68 | /**
|
69 | * Returns the list of built-in modules.
|
70 | * @returns {string[]} An array of built-in Node.js modules.
|
71 | */
|
72 | function getBuiltinModules() {
|
73 |
|
74 | /*
|
75 | * This list is generated using:
|
76 | * `require("repl")._builtinLibs.concat('repl').sort()`
|
77 | * This particular list is as per nodejs v0.12.2 and iojs v0.7.1
|
78 | */
|
79 | return [
|
80 | "assert", "buffer", "child_process", "cluster", "crypto",
|
81 | "dgram", "dns", "domain", "events", "fs", "http", "https",
|
82 | "net", "os", "path", "punycode", "querystring", "readline",
|
83 | "repl", "smalloc", "stream", "string_decoder", "tls", "tty",
|
84 | "url", "util", "v8", "vm", "zlib"
|
85 | ];
|
86 | }
|
87 |
|
88 | const BUILTIN_MODULES = getBuiltinModules();
|
89 |
|
90 | const DECL_REQUIRE = "require",
|
91 | DECL_UNINITIALIZED = "uninitialized",
|
92 | DECL_OTHER = "other";
|
93 |
|
94 | const REQ_CORE = "core",
|
95 | REQ_FILE = "file",
|
96 | REQ_MODULE = "module",
|
97 | REQ_COMPUTED = "computed";
|
98 |
|
99 | /**
|
100 | * Determines the type of a declaration statement.
|
101 | * @param {ASTNode} initExpression The init node of the VariableDeclarator.
|
102 | * @returns {string} The type of declaration represented by the expression.
|
103 | */
|
104 | function getDeclarationType(initExpression) {
|
105 | if (!initExpression) {
|
106 |
|
107 | // "var x;"
|
108 | return DECL_UNINITIALIZED;
|
109 | }
|
110 |
|
111 | if (initExpression.type === "CallExpression" &&
|
112 | initExpression.callee.type === "Identifier" &&
|
113 | initExpression.callee.name === "require"
|
114 | ) {
|
115 |
|
116 | // "var x = require('util');"
|
117 | return DECL_REQUIRE;
|
118 | }
|
119 | if (allowCall &&
|
120 | initExpression.type === "CallExpression" &&
|
121 | initExpression.callee.type === "CallExpression"
|
122 | ) {
|
123 |
|
124 | // "var x = require('diagnose')('sub-module');"
|
125 | return getDeclarationType(initExpression.callee);
|
126 | }
|
127 | if (initExpression.type === "MemberExpression") {
|
128 |
|
129 | // "var x = require('glob').Glob;"
|
130 | return getDeclarationType(initExpression.object);
|
131 | }
|
132 |
|
133 | // "var x = 42;"
|
134 | return DECL_OTHER;
|
135 | }
|
136 |
|
137 | /**
|
138 | * Determines the type of module that is loaded via require.
|
139 | * @param {ASTNode} initExpression The init node of the VariableDeclarator.
|
140 | * @returns {string} The module type.
|
141 | */
|
142 | function inferModuleType(initExpression) {
|
143 | if (initExpression.type === "MemberExpression") {
|
144 |
|
145 | // "var x = require('glob').Glob;"
|
146 | return inferModuleType(initExpression.object);
|
147 | }
|
148 | if (initExpression.arguments.length === 0) {
|
149 |
|
150 | // "var x = require();"
|
151 | return REQ_COMPUTED;
|
152 | }
|
153 |
|
154 | const arg = initExpression.arguments[0];
|
155 |
|
156 | if (arg.type !== "Literal" || typeof arg.value !== "string") {
|
157 |
|
158 | // "var x = require(42);"
|
159 | return REQ_COMPUTED;
|
160 | }
|
161 |
|
162 | if (BUILTIN_MODULES.indexOf(arg.value) !== -1) {
|
163 |
|
164 | // "var fs = require('fs');"
|
165 | return REQ_CORE;
|
166 | }
|
167 | if (/^\.{0,2}\//u.test(arg.value)) {
|
168 |
|
169 | // "var utils = require('./utils');"
|
170 | return REQ_FILE;
|
171 | }
|
172 |
|
173 | // "var async = require('async');"
|
174 | return REQ_MODULE;
|
175 |
|
176 | }
|
177 |
|
178 | /**
|
179 | * Check if the list of variable declarations is mixed, i.e. whether it
|
180 | * contains both require and other declarations.
|
181 | * @param {ASTNode} declarations The list of VariableDeclarators.
|
182 | * @returns {boolean} True if the declarations are mixed, false if not.
|
183 | */
|
184 | function isMixed(declarations) {
|
185 | const contains = {};
|
186 |
|
187 | declarations.forEach(declaration => {
|
188 | const type = getDeclarationType(declaration.init);
|
189 |
|
190 | contains[type] = true;
|
191 | });
|
192 |
|
193 | return !!(
|
194 | contains[DECL_REQUIRE] &&
|
195 | (contains[DECL_UNINITIALIZED] || contains[DECL_OTHER])
|
196 | );
|
197 | }
|
198 |
|
199 | /**
|
200 | * Check if all require declarations in the given list are of the same
|
201 | * type.
|
202 | * @param {ASTNode} declarations The list of VariableDeclarators.
|
203 | * @returns {boolean} True if the declarations are grouped, false if not.
|
204 | */
|
205 | function isGrouped(declarations) {
|
206 | const found = {};
|
207 |
|
208 | declarations.forEach(declaration => {
|
209 | if (getDeclarationType(declaration.init) === DECL_REQUIRE) {
|
210 | found[inferModuleType(declaration.init)] = true;
|
211 | }
|
212 | });
|
213 |
|
214 | return Object.keys(found).length <= 1;
|
215 | }
|
216 |
|
217 |
|
218 | return {
|
219 |
|
220 | VariableDeclaration(node) {
|
221 |
|
222 | if (isMixed(node.declarations)) {
|
223 | context.report({
|
224 | node,
|
225 | messageId: "noMixRequire"
|
226 | });
|
227 | } else if (grouping && !isGrouped(node.declarations)) {
|
228 | context.report({
|
229 | node,
|
230 | messageId: "noMixCoreModuleFileComputed"
|
231 | });
|
232 | }
|
233 | }
|
234 | };
|
235 |
|
236 | }
|
237 | };
|