1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | "use strict";
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | module.exports = {
|
13 | meta: {
|
14 | docs: {
|
15 | description: "enforce variables to be declared either together or separately in functions",
|
16 | category: "Stylistic Issues",
|
17 | recommended: false,
|
18 | url: "https://eslint.org/docs/rules/one-var"
|
19 | },
|
20 |
|
21 | schema: [
|
22 | {
|
23 | oneOf: [
|
24 | {
|
25 | enum: ["always", "never"]
|
26 | },
|
27 | {
|
28 | type: "object",
|
29 | properties: {
|
30 | separateRequires: {
|
31 | type: "boolean"
|
32 | },
|
33 | var: {
|
34 | enum: ["always", "never"]
|
35 | },
|
36 | let: {
|
37 | enum: ["always", "never"]
|
38 | },
|
39 | const: {
|
40 | enum: ["always", "never"]
|
41 | }
|
42 | },
|
43 | additionalProperties: false
|
44 | },
|
45 | {
|
46 | type: "object",
|
47 | properties: {
|
48 | initialized: {
|
49 | enum: ["always", "never"]
|
50 | },
|
51 | uninitialized: {
|
52 | enum: ["always", "never"]
|
53 | }
|
54 | },
|
55 | additionalProperties: false
|
56 | }
|
57 | ]
|
58 | }
|
59 | ]
|
60 | },
|
61 |
|
62 | create(context) {
|
63 |
|
64 | const MODE_ALWAYS = "always",
|
65 | MODE_NEVER = "never";
|
66 |
|
67 | const mode = context.options[0] || MODE_ALWAYS;
|
68 |
|
69 | const options = {};
|
70 |
|
71 | if (typeof mode === "string") {
|
72 | options.var = { uninitialized: mode, initialized: mode };
|
73 | options.let = { uninitialized: mode, initialized: mode };
|
74 | options.const = { uninitialized: mode, initialized: mode };
|
75 | } else if (typeof mode === "object") {
|
76 | if (mode.hasOwnProperty("separateRequires")) {
|
77 | options.separateRequires = !!mode.separateRequires;
|
78 | }
|
79 | if (mode.hasOwnProperty("var")) {
|
80 | options.var = { uninitialized: mode.var, initialized: mode.var };
|
81 | }
|
82 | if (mode.hasOwnProperty("let")) {
|
83 | options.let = { uninitialized: mode.let, initialized: mode.let };
|
84 | }
|
85 | if (mode.hasOwnProperty("const")) {
|
86 | options.const = { uninitialized: mode.const, initialized: mode.const };
|
87 | }
|
88 | if (mode.hasOwnProperty("uninitialized")) {
|
89 | if (!options.var) {
|
90 | options.var = {};
|
91 | }
|
92 | if (!options.let) {
|
93 | options.let = {};
|
94 | }
|
95 | if (!options.const) {
|
96 | options.const = {};
|
97 | }
|
98 | options.var.uninitialized = mode.uninitialized;
|
99 | options.let.uninitialized = mode.uninitialized;
|
100 | options.const.uninitialized = mode.uninitialized;
|
101 | }
|
102 | if (mode.hasOwnProperty("initialized")) {
|
103 | if (!options.var) {
|
104 | options.var = {};
|
105 | }
|
106 | if (!options.let) {
|
107 | options.let = {};
|
108 | }
|
109 | if (!options.const) {
|
110 | options.const = {};
|
111 | }
|
112 | options.var.initialized = mode.initialized;
|
113 | options.let.initialized = mode.initialized;
|
114 | options.const.initialized = mode.initialized;
|
115 | }
|
116 | }
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 | const functionStack = [];
|
123 | const blockStack = [];
|
124 |
|
125 | |
126 |
|
127 |
|
128 |
|
129 |
|
130 | function startBlock() {
|
131 | blockStack.push({
|
132 | let: { initialized: false, uninitialized: false },
|
133 | const: { initialized: false, uninitialized: false }
|
134 | });
|
135 | }
|
136 |
|
137 | |
138 |
|
139 |
|
140 |
|
141 |
|
142 | function startFunction() {
|
143 | functionStack.push({ initialized: false, uninitialized: false });
|
144 | startBlock();
|
145 | }
|
146 |
|
147 | |
148 |
|
149 |
|
150 |
|
151 |
|
152 | function endBlock() {
|
153 | blockStack.pop();
|
154 | }
|
155 |
|
156 | |
157 |
|
158 |
|
159 |
|
160 |
|
161 | function endFunction() {
|
162 | functionStack.pop();
|
163 | endBlock();
|
164 | }
|
165 |
|
166 | |
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 | function isRequire(decl) {
|
173 | return decl.init && decl.init.type === "CallExpression" && decl.init.callee.name === "require";
|
174 | }
|
175 |
|
176 | |
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 | function recordTypes(statementType, declarations, currentScope) {
|
185 | for (let i = 0; i < declarations.length; i++) {
|
186 | if (declarations[i].init === null) {
|
187 | if (options[statementType] && options[statementType].uninitialized === MODE_ALWAYS) {
|
188 | currentScope.uninitialized = true;
|
189 | }
|
190 | } else {
|
191 | if (options[statementType] && options[statementType].initialized === MODE_ALWAYS) {
|
192 | if (options.separateRequires && isRequire(declarations[i])) {
|
193 | currentScope.required = true;
|
194 | } else {
|
195 | currentScope.initialized = true;
|
196 | }
|
197 | }
|
198 | }
|
199 | }
|
200 | }
|
201 |
|
202 | |
203 |
|
204 |
|
205 |
|
206 |
|
207 | function getCurrentScope(statementType) {
|
208 | let currentScope;
|
209 |
|
210 | if (statementType === "var") {
|
211 | currentScope = functionStack[functionStack.length - 1];
|
212 | } else if (statementType === "let") {
|
213 | currentScope = blockStack[blockStack.length - 1].let;
|
214 | } else if (statementType === "const") {
|
215 | currentScope = blockStack[blockStack.length - 1].const;
|
216 | }
|
217 | return currentScope;
|
218 | }
|
219 |
|
220 | |
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 | function countDeclarations(declarations) {
|
227 | const counts = { uninitialized: 0, initialized: 0 };
|
228 |
|
229 | for (let i = 0; i < declarations.length; i++) {
|
230 | if (declarations[i].init === null) {
|
231 | counts.uninitialized++;
|
232 | } else {
|
233 | counts.initialized++;
|
234 | }
|
235 | }
|
236 | return counts;
|
237 | }
|
238 |
|
239 | |
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 | function hasOnlyOneStatement(statementType, declarations) {
|
247 |
|
248 | const declarationCounts = countDeclarations(declarations);
|
249 | const currentOptions = options[statementType] || {};
|
250 | const currentScope = getCurrentScope(statementType);
|
251 | const hasRequires = declarations.some(isRequire);
|
252 |
|
253 | if (currentOptions.uninitialized === MODE_ALWAYS && currentOptions.initialized === MODE_ALWAYS) {
|
254 | if (currentScope.uninitialized || currentScope.initialized) {
|
255 | return false;
|
256 | }
|
257 | }
|
258 |
|
259 | if (declarationCounts.uninitialized > 0) {
|
260 | if (currentOptions.uninitialized === MODE_ALWAYS && currentScope.uninitialized) {
|
261 | return false;
|
262 | }
|
263 | }
|
264 | if (declarationCounts.initialized > 0) {
|
265 | if (currentOptions.initialized === MODE_ALWAYS && currentScope.initialized) {
|
266 | return false;
|
267 | }
|
268 | }
|
269 | if (currentScope.required && hasRequires) {
|
270 | return false;
|
271 | }
|
272 | recordTypes(statementType, declarations, currentScope);
|
273 | return true;
|
274 | }
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 | return {
|
282 | Program: startFunction,
|
283 | FunctionDeclaration: startFunction,
|
284 | FunctionExpression: startFunction,
|
285 | ArrowFunctionExpression: startFunction,
|
286 | BlockStatement: startBlock,
|
287 | ForStatement: startBlock,
|
288 | ForInStatement: startBlock,
|
289 | ForOfStatement: startBlock,
|
290 | SwitchStatement: startBlock,
|
291 |
|
292 | VariableDeclaration(node) {
|
293 | const parent = node.parent;
|
294 | const type = node.kind;
|
295 |
|
296 | if (!options[type]) {
|
297 | return;
|
298 | }
|
299 |
|
300 | const declarations = node.declarations;
|
301 | const declarationCounts = countDeclarations(declarations);
|
302 | const mixedRequires = declarations.some(isRequire) && !declarations.every(isRequire);
|
303 |
|
304 | if (options[type].initialized === MODE_ALWAYS) {
|
305 | if (options.separateRequires && mixedRequires) {
|
306 | context.report({
|
307 | node,
|
308 | message: "Split requires to be separated into a single block."
|
309 | });
|
310 | }
|
311 | }
|
312 |
|
313 |
|
314 | if (!hasOnlyOneStatement(type, declarations)) {
|
315 | if (options[type].initialized === MODE_ALWAYS && options[type].uninitialized === MODE_ALWAYS) {
|
316 | context.report({
|
317 | node,
|
318 | message: "Combine this with the previous '{{type}}' statement.",
|
319 | data: {
|
320 | type
|
321 | }
|
322 | });
|
323 | } else {
|
324 | if (options[type].initialized === MODE_ALWAYS) {
|
325 | context.report({
|
326 | node,
|
327 | message: "Combine this with the previous '{{type}}' statement with initialized variables.",
|
328 | data: {
|
329 | type
|
330 | }
|
331 | });
|
332 | }
|
333 | if (options[type].uninitialized === MODE_ALWAYS) {
|
334 | if (node.parent.left === node && (node.parent.type === "ForInStatement" || node.parent.type === "ForOfStatement")) {
|
335 | return;
|
336 | }
|
337 | context.report({
|
338 | node,
|
339 | message: "Combine this with the previous '{{type}}' statement with uninitialized variables.",
|
340 | data: {
|
341 | type
|
342 | }
|
343 | });
|
344 | }
|
345 | }
|
346 | }
|
347 |
|
348 |
|
349 | if (parent.type !== "ForStatement" || parent.init !== node) {
|
350 | const totalDeclarations = declarationCounts.uninitialized + declarationCounts.initialized;
|
351 |
|
352 | if (totalDeclarations > 1) {
|
353 |
|
354 | if (options[type].initialized === MODE_NEVER && options[type].uninitialized === MODE_NEVER) {
|
355 |
|
356 |
|
357 | context.report({
|
358 | node,
|
359 | message: "Split '{{type}}' declarations into multiple statements.",
|
360 | data: {
|
361 | type
|
362 | }
|
363 | });
|
364 | } else if (options[type].initialized === MODE_NEVER && declarationCounts.initialized > 0) {
|
365 |
|
366 |
|
367 | context.report({
|
368 | node,
|
369 | message: "Split initialized '{{type}}' declarations into multiple statements.",
|
370 | data: {
|
371 | type
|
372 | }
|
373 | });
|
374 | } else if (options[type].uninitialized === MODE_NEVER && declarationCounts.uninitialized > 0) {
|
375 |
|
376 |
|
377 | context.report({
|
378 | node,
|
379 | message: "Split uninitialized '{{type}}' declarations into multiple statements.",
|
380 | data: {
|
381 | type
|
382 | }
|
383 | });
|
384 | }
|
385 | }
|
386 | }
|
387 | },
|
388 |
|
389 | "ForStatement:exit": endBlock,
|
390 | "ForOfStatement:exit": endBlock,
|
391 | "ForInStatement:exit": endBlock,
|
392 | "SwitchStatement:exit": endBlock,
|
393 | "BlockStatement:exit": endBlock,
|
394 | "Program:exit": endFunction,
|
395 | "FunctionDeclaration:exit": endFunction,
|
396 | "FunctionExpression:exit": endFunction,
|
397 | "ArrowFunctionExpression:exit": endFunction
|
398 | };
|
399 |
|
400 | }
|
401 | };
|