• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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