UNPKG

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