1 | /**
|
2 | * @fileoverview Rule to enforce grouped require statements for Node.JS
|
3 | * @author Raphael Pigulla
|
4 | */
|
5 |
|
6 | //------------------------------------------------------------------------------
|
7 | // Rule Definition
|
8 | //------------------------------------------------------------------------------
|
9 |
|
10 | module.exports = function(context) {
|
11 | ;
|
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 | };
|