UNPKG

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