1 | /**
|
2 | * Disallows multiple `var` declaration (except for-loop).
|
3 | *
|
4 | * Types: `Boolean` or `Object`
|
5 | *
|
6 | * Values:
|
7 | *
|
8 | * - `true` disallows multiple variable declarations except within a for loop
|
9 | * - `Object`:
|
10 | * - `'strict'` disallows all multiple variable declarations
|
11 | * - `'allExcept'` array of exceptions:
|
12 | * - `'undefined'` allows declarations where all variables are not defined
|
13 | * - `'require'` allows declarations where all variables are importing external modules with require
|
14 | *
|
15 | * #### Example
|
16 | *
|
17 | * ```js
|
18 | * "disallowMultipleVarDecl": true
|
19 | * ```
|
20 | *
|
21 | * ##### Valid for `true`
|
22 | *
|
23 | * ```js
|
24 | * var x = 1;
|
25 | * var y = 2;
|
26 | *
|
27 | * for (var i = 0, j = arr.length; i < j; i++) {}
|
28 | * ```
|
29 | *
|
30 | * ##### Valid for `{ strict: true }`
|
31 | *
|
32 | * ```js
|
33 | * var x = 1;
|
34 | * var y = 2;
|
35 | * ```
|
36 | *
|
37 | * ##### Valid for `{ allExcept: ['undefined'] }`
|
38 | *
|
39 | * ```js
|
40 | * var a, b;
|
41 | * var x = 1;
|
42 | * var y = 2;
|
43 | *
|
44 | * for (var i = 0, j = arr.length; i < j; i++) {}
|
45 | * ```
|
46 | * ##### Valid for `{ allExcept: ['require'] }`
|
47 | *
|
48 | * ```js
|
49 | * var a = require('a'),
|
50 | * b = require('b');
|
51 | *
|
52 | * var x = 1;
|
53 | * var y = 2;
|
54 | *
|
55 | * for (var i = 0, j = arr.length; i < j; i++) {}
|
56 | * ```
|
57 | *
|
58 | * ##### Invalid
|
59 | *
|
60 | * ```js
|
61 | * var x = 1,
|
62 | * y = 2;
|
63 | *
|
64 | * var x, y = 2, z;
|
65 | * ```
|
66 | */
|
67 |
|
68 | var assert = require('assert');
|
69 |
|
70 | module.exports = function() {};
|
71 |
|
72 | module.exports.prototype = {
|
73 |
|
74 | configure: function(options) {
|
75 | // support for legacy options
|
76 | if (typeof options !== 'object') {
|
77 | assert(
|
78 | options === true ||
|
79 | options === 'strict' ||
|
80 | options === 'exceptUndefined',
|
81 | this.getOptionName() +
|
82 | ' option requires a true value, "strict", "exceptUndefined", or an object'
|
83 | );
|
84 |
|
85 | var _options = {
|
86 | strict: options === 'strict',
|
87 | allExcept: []
|
88 | };
|
89 |
|
90 | if (options === 'exceptUndefined') {
|
91 | _options.allExcept.push('undefined');
|
92 | }
|
93 |
|
94 | return this.configure(_options);
|
95 | }
|
96 |
|
97 | if (Array.isArray(options.allExcept)) {
|
98 | this._exceptUndefined = options.allExcept.indexOf('undefined') > -1;
|
99 | this._exceptRequire = options.allExcept.indexOf('require') > -1;
|
100 | }
|
101 |
|
102 | this._strictMode = options.strict === true;
|
103 | },
|
104 |
|
105 | getOptionName: function() {
|
106 | return 'disallowMultipleVarDecl';
|
107 | },
|
108 |
|
109 | check: function(file, errors) {
|
110 |
|
111 | function isSourcedFromRequire(node) {
|
112 | // If this node is a CallExpression it has a callee,
|
113 | // check if this is the `require` function
|
114 | if (node.callee && node.callee.name === 'require') {
|
115 | return true;
|
116 | }
|
117 |
|
118 | // If this CallExpression is not a `require` we keep looking for
|
119 | // the `require` method up in the tree
|
120 | if (node.callee && node.callee.object) {
|
121 | return isSourcedFromRequire(node.callee.object);
|
122 | }
|
123 |
|
124 | // If there is no `callee` this might be a MemberExpression, keep
|
125 | // look for the `require` method up in the tree.
|
126 | if (node.object) {
|
127 | return isSourcedFromRequire(node.object);
|
128 | }
|
129 |
|
130 | return false;
|
131 | }
|
132 |
|
133 | var inStrictMode = this._strictMode;
|
134 | var exceptUndefined = this._exceptUndefined;
|
135 | var exceptRequire = this._exceptRequire;
|
136 |
|
137 | file.iterateNodesByType('VariableDeclaration', function(node) {
|
138 | var definedVariables = node.declarations.filter(function(declaration) {
|
139 | return !!declaration.init;
|
140 | });
|
141 | var hasDefinedVariables = definedVariables.length > 0;
|
142 |
|
143 | var requireStatements = node.declarations.filter(function(declaration) {
|
144 | var init = declaration.init;
|
145 | return init && isSourcedFromRequire(init);
|
146 | });
|
147 | var allRequireStatements = requireStatements.length === node.declarations.length;
|
148 |
|
149 | var isForStatement = node.parentElement.type === 'ForStatement';
|
150 |
|
151 | // allow single var declarations
|
152 | if (node.declarations.length === 1) {
|
153 | return;
|
154 | }
|
155 |
|
156 | // allow multiple var declarations in for statement unless we're in strict mode
|
157 | // for (var i = 0, j = myArray.length; i < j; i++) {}
|
158 | if (!inStrictMode && isForStatement) {
|
159 | return;
|
160 | }
|
161 |
|
162 | // allow multiple var declarations with all undefined variables in exceptUndefined mode
|
163 | // var a, b, c
|
164 | if (exceptUndefined && !hasDefinedVariables) {
|
165 | return;
|
166 | }
|
167 |
|
168 | // allow multiple var declaration with all require
|
169 | // var a = require("a"), b = require("b")
|
170 | if (exceptRequire && allRequireStatements) {
|
171 | return;
|
172 | }
|
173 |
|
174 | // allow multiple var declarations only with require && undefined
|
175 | // var a = require("a"), b = require("b"), x, y
|
176 | if (exceptUndefined && exceptRequire && definedVariables.length === requireStatements.length) {
|
177 | return;
|
178 | }
|
179 |
|
180 | errors.add('Multiple var declaration', node);
|
181 | });
|
182 | }
|
183 | };
|