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 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}; 238