1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | "use strict";
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 | module.exports = function(context) {
|
17 |
|
18 | var MODE_ALWAYS = "always",
|
19 | MODE_NEVER = "never";
|
20 |
|
21 | var mode = context.options[0] || MODE_ALWAYS;
|
22 |
|
23 | var options = {
|
24 | };
|
25 |
|
26 | if (typeof mode === "string") {
|
27 | options.var = { uninitialized: mode, initialized: mode};
|
28 | options.let = { uninitialized: mode, initialized: mode};
|
29 | options.const = { uninitialized: mode, initialized: mode};
|
30 | } else if (typeof mode === "object") {
|
31 | if (mode.hasOwnProperty("var") && typeof mode.var === "string") {
|
32 | options.var = { uninitialized: mode.var, initialized: mode.var};
|
33 | }
|
34 | if (mode.hasOwnProperty("let") && typeof mode.let === "string") {
|
35 | options.let = { uninitialized: mode.let, initialized: mode.let};
|
36 | }
|
37 | if (mode.hasOwnProperty("const") && typeof mode.const === "string") {
|
38 | options.const = { uninitialized: mode.const, initialized: mode.const};
|
39 | }
|
40 | if (mode.hasOwnProperty("uninitialized")) {
|
41 | if (!options.var) {
|
42 | options.var = {};
|
43 | }
|
44 | if (!options.let) {
|
45 | options.let = {};
|
46 | }
|
47 | if (!options.const) {
|
48 | options.const = {};
|
49 | }
|
50 | options.var.uninitialized = mode.uninitialized;
|
51 | options.let.uninitialized = mode.uninitialized;
|
52 | options.const.uninitialized = mode.uninitialized;
|
53 | }
|
54 | if (mode.hasOwnProperty("initialized")) {
|
55 | if (!options.var) {
|
56 | options.var = {};
|
57 | }
|
58 | if (!options.let) {
|
59 | options.let = {};
|
60 | }
|
61 | if (!options.const) {
|
62 | options.const = {};
|
63 | }
|
64 | options.var.initialized = mode.initialized;
|
65 | options.let.initialized = mode.initialized;
|
66 | options.const.initialized = mode.initialized;
|
67 | }
|
68 | }
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 | var functionStack = [];
|
75 | var blockStack = [];
|
76 |
|
77 | |
78 |
|
79 |
|
80 |
|
81 |
|
82 | function startBlock() {
|
83 | blockStack.push({
|
84 | let: {initialized: false, uninitialized: false},
|
85 | const: {initialized: false, uninitialized: false}
|
86 | });
|
87 | }
|
88 |
|
89 | |
90 |
|
91 |
|
92 |
|
93 |
|
94 | function startFunction() {
|
95 | functionStack.push({initialized: false, uninitialized: false});
|
96 | startBlock();
|
97 | }
|
98 |
|
99 | |
100 |
|
101 |
|
102 |
|
103 |
|
104 | function endBlock() {
|
105 | blockStack.pop();
|
106 | }
|
107 |
|
108 | |
109 |
|
110 |
|
111 |
|
112 |
|
113 | function endFunction() {
|
114 | functionStack.pop();
|
115 | endBlock();
|
116 | }
|
117 |
|
118 | |
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 | function recordTypes(statementType, declarations, currentScope) {
|
127 | for (var i = 0; i < declarations.length; i++) {
|
128 | if (declarations[i].init === null) {
|
129 | if (options[statementType] && options[statementType].uninitialized === MODE_ALWAYS) {
|
130 | currentScope.uninitialized = true;
|
131 | }
|
132 | } else {
|
133 | if (options[statementType] && options[statementType].initialized === MODE_ALWAYS) {
|
134 | currentScope.initialized = true;
|
135 | }
|
136 | }
|
137 | }
|
138 | }
|
139 |
|
140 | |
141 |
|
142 |
|
143 |
|
144 |
|
145 | function getCurrentScope(statementType) {
|
146 | var currentScope;
|
147 | if (statementType === "var") {
|
148 | currentScope = functionStack[functionStack.length - 1];
|
149 | } else if (statementType === "let") {
|
150 | currentScope = blockStack[blockStack.length - 1].let;
|
151 | } else if (statementType === "const") {
|
152 | currentScope = blockStack[blockStack.length - 1].const;
|
153 | }
|
154 | return currentScope;
|
155 | }
|
156 |
|
157 | |
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | function countDeclarations(declarations) {
|
164 | var counts = { uninitialized: 0, initialized: 0 };
|
165 | for (var i = 0; i < declarations.length; i++) {
|
166 | if (declarations[i].init === null) {
|
167 | counts.uninitialized++;
|
168 | } else {
|
169 | counts.initialized++;
|
170 | }
|
171 | }
|
172 | return counts;
|
173 | }
|
174 |
|
175 | |
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 | function hasOnlyOneStatement(statementType, declarations) {
|
183 |
|
184 | var declarationCounts = countDeclarations(declarations);
|
185 | var currentOptions = options[statementType] || {};
|
186 | var currentScope = getCurrentScope(statementType);
|
187 |
|
188 | if (currentOptions.uninitialized === MODE_ALWAYS && currentOptions.initialized === MODE_ALWAYS) {
|
189 | if (currentScope.uninitialized || currentScope.initialized) {
|
190 | return false;
|
191 | }
|
192 | }
|
193 |
|
194 | if (declarationCounts.uninitialized > 0) {
|
195 | if (currentOptions.uninitialized === MODE_ALWAYS && currentScope.uninitialized) {
|
196 | return false;
|
197 | }
|
198 | }
|
199 | if (declarationCounts.initialized > 0) {
|
200 | if (currentOptions.initialized === MODE_ALWAYS && currentScope.initialized) {
|
201 | return false;
|
202 | }
|
203 | }
|
204 | recordTypes(statementType, declarations, currentScope);
|
205 | return true;
|
206 | }
|
207 |
|
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 | return {
|
214 | "Program": startFunction,
|
215 | "FunctionDeclaration": startFunction,
|
216 | "FunctionExpression": startFunction,
|
217 | "ArrowFunctionExpression": startFunction,
|
218 | "BlockStatement": startBlock,
|
219 | "ForStatement": startBlock,
|
220 | "ForInStatement": startBlock,
|
221 | "ForOfStatement": startBlock,
|
222 | "SwitchStatement": startBlock,
|
223 |
|
224 | "VariableDeclaration": function(node) {
|
225 | var parent = node.parent,
|
226 | type, declarations, declarationCounts;
|
227 |
|
228 | type = node.kind;
|
229 | if (!options[type]) {
|
230 | return;
|
231 | }
|
232 |
|
233 | declarations = node.declarations;
|
234 | declarationCounts = countDeclarations(declarations);
|
235 |
|
236 |
|
237 | if (!hasOnlyOneStatement(type, declarations)) {
|
238 | if (options[type].initialized === MODE_ALWAYS && options[type].uninitialized === MODE_ALWAYS) {
|
239 | context.report(node, "Combine this with the previous '" + type + "' statement.");
|
240 | } else {
|
241 | if (options[type].initialized === MODE_ALWAYS) {
|
242 | context.report(node, "Combine this with the previous '" + type + "' statement with initialized variables.");
|
243 | }
|
244 | if (options[type].uninitialized === MODE_ALWAYS) {
|
245 | context.report(node, "Combine this with the previous '" + type + "' statement with uninitialized variables.");
|
246 | }
|
247 | }
|
248 | }
|
249 |
|
250 | if (parent.type !== "ForStatement" || parent.init !== node) {
|
251 | var totalDeclarations = declarationCounts.uninitialized + declarationCounts.initialized;
|
252 | if (totalDeclarations > 1) {
|
253 |
|
254 | if (options[type].initialized === MODE_NEVER && options[type].uninitialized === MODE_NEVER) {
|
255 | context.report(node, "Split '" + type + "' declarations into multiple statements.");
|
256 |
|
257 | } else if (options[type].initialized === MODE_NEVER && declarationCounts.initialized > 0) {
|
258 | context.report(node, "Split initialized '" + type + "' declarations into multiple statements.");
|
259 |
|
260 | } else if (options[type].uninitialized === MODE_NEVER && declarationCounts.uninitialized > 0) {
|
261 | context.report(node, "Split uninitialized '" + type + "' declarations into multiple statements.");
|
262 | }
|
263 | }
|
264 | }
|
265 | },
|
266 |
|
267 | "ForStatement:exit": endBlock,
|
268 | "ForOfStatement:exit": endBlock,
|
269 | "ForInStatement:exit": endBlock,
|
270 | "SwitchStatement:exit": endBlock,
|
271 | "BlockStatement:exit": endBlock,
|
272 | "Program:exit": endFunction,
|
273 | "FunctionDeclaration:exit": endFunction,
|
274 | "FunctionExpression:exit": endFunction,
|
275 | "ArrowFunctionExpression:exit": endFunction
|
276 | };
|
277 |
|
278 | };
|
279 |
|
280 | module.exports.schema = [
|
281 | {
|
282 | "oneOf": [
|
283 | {
|
284 | "enum": ["always", "never"]
|
285 | },
|
286 | {
|
287 | "type": "object",
|
288 | "properties": {
|
289 | "var": {
|
290 | "enum": ["always", "never"]
|
291 | },
|
292 | "let": {
|
293 | "enum": ["always", "never"]
|
294 | },
|
295 | "const": {
|
296 | "enum": ["always", "never"]
|
297 | }
|
298 | },
|
299 | "additionalProperties": false
|
300 | },
|
301 | {
|
302 | "type": "object",
|
303 | "properties": {
|
304 | "initialized": {
|
305 | "enum": ["always", "never"]
|
306 | },
|
307 | "uninitialized": {
|
308 | "enum": ["always", "never"]
|
309 | }
|
310 | },
|
311 | "additionalProperties": false
|
312 | }
|
313 | ]
|
314 | }
|
315 | ];
|