UNPKG

5.13 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to enforce grouped require statements for Node.JS
3 * @author Raphael Pigulla
4 */
5
6//------------------------------------------------------------------------------
7// Rule Definition
8//------------------------------------------------------------------------------
9
10module.exports = function(context) {
11 "use strict";
12
13 /**
14 * Returns the list of built-in modules.
15 *
16 * @returns {string[]} An array of built-in Node.js modules.
17 */
18 function getBuiltinModules() {
19 // This list is generated using `require("repl")._builtinLibs.concat('repl').sort()`
20 // This particular list was generated using node v0.11.9
21 return [
22 "assert", "buffer", "child_process", "cluster", "crypto",
23 "dgram", "dns", "domain", "events", "fs", "http", "https",
24 "net", "os", "path", "punycode", "querystring", "readline",
25 "repl", "smalloc", "stream", "string_decoder", "tls", "tty",
26 "url", "util", "vm", "zlib"
27 ];
28 }
29
30 var BUILTIN_MODULES = getBuiltinModules();
31
32 var DECL_REQUIRE = "require",
33 DECL_UNINITIALIZED = "uninitialized",
34 DECL_OTHER = "other";
35
36 var REQ_CORE = "core",
37 REQ_FILE = "file",
38 REQ_MODULE = "module",
39 REQ_COMPUTED = "computed";
40
41 /**
42 * Determines the type of a declaration statement.
43 * @param {ASTNode} initExpression The init node of the VariableDeclarator.
44 * @returns {string} The type of declaration represented by the expression.
45 */
46 function getDeclarationType(initExpression) {
47 if (!initExpression) {
48 // "var x;"
49 return DECL_UNINITIALIZED;
50 }
51
52 if (initExpression.type === "CallExpression" &&
53 initExpression.callee.type === "Identifier" &&
54 initExpression.callee.name === "require"
55 ) {
56 // "var x = require('util');"
57 return DECL_REQUIRE;
58 } else if (initExpression.type === "MemberExpression") {
59 // "var x = require('glob').Glob;"
60 return getDeclarationType(initExpression.object);
61 }
62
63 // "var x = 42;"
64 return DECL_OTHER;
65 }
66
67 /**
68 * Determines the type of module that is loaded via require.
69 * @param {ASTNode} initExpression The init node of the VariableDeclarator.
70 * @returns {string} The module type.
71 */
72 function inferModuleType(initExpression) {
73 if (initExpression.type === "MemberExpression") {
74 // "var x = require('glob').Glob;"
75 return inferModuleType(initExpression.object);
76 } else if (initExpression["arguments"].length === 0) {
77 // "var x = require();"
78 return REQ_COMPUTED;
79 }
80
81 var arg = initExpression["arguments"][0];
82
83 if (arg.type !== "Literal" || typeof arg.value !== "string") {
84 // "var x = require(42);"
85 return REQ_COMPUTED;
86 }
87
88 if (BUILTIN_MODULES.indexOf(arg.value) !== -1) {
89 // "var fs = require('fs');"
90 return REQ_CORE;
91 } else if (/^\.{0,2}\//.test(arg.value)) {
92 // "var utils = require('./utils');"
93 return REQ_FILE;
94 } else {
95 // "var async = require('async');"
96 return REQ_MODULE;
97 }
98 }
99
100 /**
101 * Check if the list of variable declarations is mixed, i.e. whether it
102 * contains both require and other declarations.
103 * @param {ASTNode} declarations The list of VariableDeclarators.
104 * @returns {boolean} True if the declarations are mixed, false if not.
105 */
106 function isMixed(declarations) {
107 var contains = {};
108
109 declarations.forEach(function (declaration) {
110 var type = getDeclarationType(declaration.init);
111 contains[type] = true;
112 });
113
114 return !!(
115 contains[DECL_REQUIRE] &&
116 (contains[DECL_UNINITIALIZED] || contains[DECL_OTHER])
117 );
118 }
119
120 /**
121 * Check if all require declarations in the given list are of the same
122 * type.
123 * @param {ASTNode} declarations The list of VariableDeclarators.
124 * @returns {boolean} True if the declarations are grouped, false if not.
125 */
126 function isGrouped(declarations) {
127 var found = {};
128
129 declarations.forEach(function (declaration) {
130 if(getDeclarationType(declaration.init) === DECL_REQUIRE) {
131 found[inferModuleType(declaration.init)] = true;
132 }
133 });
134
135 return Object.keys(found).length <= 1;
136 }
137
138
139 return {
140
141 "VariableDeclaration": function(node) {
142 var grouping = !!context.options[0];
143
144 if (isMixed(node.declarations)) {
145 context.report(
146 node,
147 "Do not mix 'require' and other declarations."
148 );
149 } else if (grouping && !isGrouped(node.declarations)) {
150 context.report(
151 node,
152 "Do not mix core, module, file and computed requires."
153 );
154 }
155 }
156 };
157
158};