source: imaps-frontend/node_modules/eslint/lib/rules/indent-legacy.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 43.6 KB
Line 
1/**
2 * @fileoverview This option sets a specific tab width for your code
3 *
4 * This rule has been ported and modified from nodeca.
5 * @author Vitaly Puzrin
6 * @author Gyandeep Singh
7 * @deprecated in ESLint v4.0.0
8 */
9
10"use strict";
11
12//------------------------------------------------------------------------------
13// Requirements
14//------------------------------------------------------------------------------
15
16const astUtils = require("./utils/ast-utils");
17
18//------------------------------------------------------------------------------
19// Rule Definition
20//------------------------------------------------------------------------------
21// this rule has known coverage issues, but it's deprecated and shouldn't be updated in the future anyway.
22/* c8 ignore next */
23/** @type {import('../shared/types').Rule} */
24module.exports = {
25 meta: {
26 type: "layout",
27
28 docs: {
29 description: "Enforce consistent indentation",
30 recommended: false,
31 url: "https://eslint.org/docs/latest/rules/indent-legacy"
32 },
33
34 deprecated: true,
35
36 replacedBy: ["indent"],
37
38 fixable: "whitespace",
39
40 schema: [
41 {
42 oneOf: [
43 {
44 enum: ["tab"]
45 },
46 {
47 type: "integer",
48 minimum: 0
49 }
50 ]
51 },
52 {
53 type: "object",
54 properties: {
55 SwitchCase: {
56 type: "integer",
57 minimum: 0
58 },
59 VariableDeclarator: {
60 oneOf: [
61 {
62 type: "integer",
63 minimum: 0
64 },
65 {
66 type: "object",
67 properties: {
68 var: {
69 type: "integer",
70 minimum: 0
71 },
72 let: {
73 type: "integer",
74 minimum: 0
75 },
76 const: {
77 type: "integer",
78 minimum: 0
79 }
80 }
81 }
82 ]
83 },
84 outerIIFEBody: {
85 type: "integer",
86 minimum: 0
87 },
88 MemberExpression: {
89 type: "integer",
90 minimum: 0
91 },
92 FunctionDeclaration: {
93 type: "object",
94 properties: {
95 parameters: {
96 oneOf: [
97 {
98 type: "integer",
99 minimum: 0
100 },
101 {
102 enum: ["first"]
103 }
104 ]
105 },
106 body: {
107 type: "integer",
108 minimum: 0
109 }
110 }
111 },
112 FunctionExpression: {
113 type: "object",
114 properties: {
115 parameters: {
116 oneOf: [
117 {
118 type: "integer",
119 minimum: 0
120 },
121 {
122 enum: ["first"]
123 }
124 ]
125 },
126 body: {
127 type: "integer",
128 minimum: 0
129 }
130 }
131 },
132 CallExpression: {
133 type: "object",
134 properties: {
135 parameters: {
136 oneOf: [
137 {
138 type: "integer",
139 minimum: 0
140 },
141 {
142 enum: ["first"]
143 }
144 ]
145 }
146 }
147 },
148 ArrayExpression: {
149 oneOf: [
150 {
151 type: "integer",
152 minimum: 0
153 },
154 {
155 enum: ["first"]
156 }
157 ]
158 },
159 ObjectExpression: {
160 oneOf: [
161 {
162 type: "integer",
163 minimum: 0
164 },
165 {
166 enum: ["first"]
167 }
168 ]
169 }
170 },
171 additionalProperties: false
172 }
173 ],
174 messages: {
175 expected: "Expected indentation of {{expected}} but found {{actual}}."
176 }
177 },
178
179 create(context) {
180 const DEFAULT_VARIABLE_INDENT = 1;
181 const DEFAULT_PARAMETER_INDENT = null; // For backwards compatibility, don't check parameter indentation unless specified in the config
182 const DEFAULT_FUNCTION_BODY_INDENT = 1;
183
184 let indentType = "space";
185 let indentSize = 4;
186 const options = {
187 SwitchCase: 0,
188 VariableDeclarator: {
189 var: DEFAULT_VARIABLE_INDENT,
190 let: DEFAULT_VARIABLE_INDENT,
191 const: DEFAULT_VARIABLE_INDENT
192 },
193 outerIIFEBody: null,
194 FunctionDeclaration: {
195 parameters: DEFAULT_PARAMETER_INDENT,
196 body: DEFAULT_FUNCTION_BODY_INDENT
197 },
198 FunctionExpression: {
199 parameters: DEFAULT_PARAMETER_INDENT,
200 body: DEFAULT_FUNCTION_BODY_INDENT
201 },
202 CallExpression: {
203 arguments: DEFAULT_PARAMETER_INDENT
204 },
205 ArrayExpression: 1,
206 ObjectExpression: 1
207 };
208
209 const sourceCode = context.sourceCode;
210
211 if (context.options.length) {
212 if (context.options[0] === "tab") {
213 indentSize = 1;
214 indentType = "tab";
215 } else /* c8 ignore start */ if (typeof context.options[0] === "number") {
216 indentSize = context.options[0];
217 indentType = "space";
218 }/* c8 ignore stop */
219
220 if (context.options[1]) {
221 const opts = context.options[1];
222
223 options.SwitchCase = opts.SwitchCase || 0;
224 const variableDeclaratorRules = opts.VariableDeclarator;
225
226 if (typeof variableDeclaratorRules === "number") {
227 options.VariableDeclarator = {
228 var: variableDeclaratorRules,
229 let: variableDeclaratorRules,
230 const: variableDeclaratorRules
231 };
232 } else if (typeof variableDeclaratorRules === "object") {
233 Object.assign(options.VariableDeclarator, variableDeclaratorRules);
234 }
235
236 if (typeof opts.outerIIFEBody === "number") {
237 options.outerIIFEBody = opts.outerIIFEBody;
238 }
239
240 if (typeof opts.MemberExpression === "number") {
241 options.MemberExpression = opts.MemberExpression;
242 }
243
244 if (typeof opts.FunctionDeclaration === "object") {
245 Object.assign(options.FunctionDeclaration, opts.FunctionDeclaration);
246 }
247
248 if (typeof opts.FunctionExpression === "object") {
249 Object.assign(options.FunctionExpression, opts.FunctionExpression);
250 }
251
252 if (typeof opts.CallExpression === "object") {
253 Object.assign(options.CallExpression, opts.CallExpression);
254 }
255
256 if (typeof opts.ArrayExpression === "number" || typeof opts.ArrayExpression === "string") {
257 options.ArrayExpression = opts.ArrayExpression;
258 }
259
260 if (typeof opts.ObjectExpression === "number" || typeof opts.ObjectExpression === "string") {
261 options.ObjectExpression = opts.ObjectExpression;
262 }
263 }
264 }
265
266 const caseIndentStore = {};
267
268 /**
269 * Creates an error message for a line, given the expected/actual indentation.
270 * @param {int} expectedAmount The expected amount of indentation characters for this line
271 * @param {int} actualSpaces The actual number of indentation spaces that were found on this line
272 * @param {int} actualTabs The actual number of indentation tabs that were found on this line
273 * @returns {string} An error message for this line
274 */
275 function createErrorMessageData(expectedAmount, actualSpaces, actualTabs) {
276 const expectedStatement = `${expectedAmount} ${indentType}${expectedAmount === 1 ? "" : "s"}`; // e.g. "2 tabs"
277 const foundSpacesWord = `space${actualSpaces === 1 ? "" : "s"}`; // e.g. "space"
278 const foundTabsWord = `tab${actualTabs === 1 ? "" : "s"}`; // e.g. "tabs"
279 let foundStatement;
280
281 if (actualSpaces > 0 && actualTabs > 0) {
282 foundStatement = `${actualSpaces} ${foundSpacesWord} and ${actualTabs} ${foundTabsWord}`; // e.g. "1 space and 2 tabs"
283 } else if (actualSpaces > 0) {
284
285 /*
286 * Abbreviate the message if the expected indentation is also spaces.
287 * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces'
288 */
289 foundStatement = indentType === "space" ? actualSpaces : `${actualSpaces} ${foundSpacesWord}`;
290 } else if (actualTabs > 0) {
291 foundStatement = indentType === "tab" ? actualTabs : `${actualTabs} ${foundTabsWord}`;
292 } else {
293 foundStatement = "0";
294 }
295 return {
296 expected: expectedStatement,
297 actual: foundStatement
298 };
299 }
300
301 /**
302 * Reports a given indent violation
303 * @param {ASTNode} node Node violating the indent rule
304 * @param {int} needed Expected indentation character count
305 * @param {int} gottenSpaces Indentation space count in the actual node/code
306 * @param {int} gottenTabs Indentation tab count in the actual node/code
307 * @param {Object} [loc] Error line and column location
308 * @param {boolean} isLastNodeCheck Is the error for last node check
309 * @returns {void}
310 */
311 function report(node, needed, gottenSpaces, gottenTabs, loc, isLastNodeCheck) {
312 if (gottenSpaces && gottenTabs) {
313
314 // To avoid conflicts with `no-mixed-spaces-and-tabs`, don't report lines that have both spaces and tabs.
315 return;
316 }
317
318 const desiredIndent = (indentType === "space" ? " " : "\t").repeat(needed);
319
320 const textRange = isLastNodeCheck
321 ? [node.range[1] - node.loc.end.column, node.range[1] - node.loc.end.column + gottenSpaces + gottenTabs]
322 : [node.range[0] - node.loc.start.column, node.range[0] - node.loc.start.column + gottenSpaces + gottenTabs];
323
324 context.report({
325 node,
326 loc,
327 messageId: "expected",
328 data: createErrorMessageData(needed, gottenSpaces, gottenTabs),
329 fix: fixer => fixer.replaceTextRange(textRange, desiredIndent)
330 });
331 }
332
333 /**
334 * Get the actual indent of node
335 * @param {ASTNode|Token} node Node to examine
336 * @param {boolean} [byLastLine=false] get indent of node's last line
337 * @returns {Object} The node's indent. Contains keys `space` and `tab`, representing the indent of each character. Also
338 * contains keys `goodChar` and `badChar`, where `goodChar` is the amount of the user's desired indentation character, and
339 * `badChar` is the amount of the other indentation character.
340 */
341 function getNodeIndent(node, byLastLine) {
342 const token = byLastLine ? sourceCode.getLastToken(node) : sourceCode.getFirstToken(node);
343 const srcCharsBeforeNode = sourceCode.getText(token, token.loc.start.column).split("");
344 const indentChars = srcCharsBeforeNode.slice(0, srcCharsBeforeNode.findIndex(char => char !== " " && char !== "\t"));
345 const spaces = indentChars.filter(char => char === " ").length;
346 const tabs = indentChars.filter(char => char === "\t").length;
347
348 return {
349 space: spaces,
350 tab: tabs,
351 goodChar: indentType === "space" ? spaces : tabs,
352 badChar: indentType === "space" ? tabs : spaces
353 };
354 }
355
356 /**
357 * Checks node is the first in its own start line. By default it looks by start line.
358 * @param {ASTNode} node The node to check
359 * @param {boolean} [byEndLocation=false] Lookup based on start position or end
360 * @returns {boolean} true if its the first in the its start line
361 */
362 function isNodeFirstInLine(node, byEndLocation) {
363 const firstToken = byEndLocation === true ? sourceCode.getLastToken(node, 1) : sourceCode.getTokenBefore(node),
364 startLine = byEndLocation === true ? node.loc.end.line : node.loc.start.line,
365 endLine = firstToken ? firstToken.loc.end.line : -1;
366
367 return startLine !== endLine;
368 }
369
370 /**
371 * Check indent for node
372 * @param {ASTNode} node Node to check
373 * @param {int} neededIndent needed indent
374 * @returns {void}
375 */
376 function checkNodeIndent(node, neededIndent) {
377 const actualIndent = getNodeIndent(node, false);
378
379 if (
380 node.type !== "ArrayExpression" &&
381 node.type !== "ObjectExpression" &&
382 (actualIndent.goodChar !== neededIndent || actualIndent.badChar !== 0) &&
383 isNodeFirstInLine(node)
384 ) {
385 report(node, neededIndent, actualIndent.space, actualIndent.tab);
386 }
387
388 if (node.type === "IfStatement" && node.alternate) {
389 const elseToken = sourceCode.getTokenBefore(node.alternate);
390
391 checkNodeIndent(elseToken, neededIndent);
392
393 if (!isNodeFirstInLine(node.alternate)) {
394 checkNodeIndent(node.alternate, neededIndent);
395 }
396 }
397
398 if (node.type === "TryStatement" && node.handler) {
399 const catchToken = sourceCode.getFirstToken(node.handler);
400
401 checkNodeIndent(catchToken, neededIndent);
402 }
403
404 if (node.type === "TryStatement" && node.finalizer) {
405 const finallyToken = sourceCode.getTokenBefore(node.finalizer);
406
407 checkNodeIndent(finallyToken, neededIndent);
408 }
409
410 if (node.type === "DoWhileStatement") {
411 const whileToken = sourceCode.getTokenAfter(node.body);
412
413 checkNodeIndent(whileToken, neededIndent);
414 }
415 }
416
417 /**
418 * Check indent for nodes list
419 * @param {ASTNode[]} nodes list of node objects
420 * @param {int} indent needed indent
421 * @returns {void}
422 */
423 function checkNodesIndent(nodes, indent) {
424 nodes.forEach(node => checkNodeIndent(node, indent));
425 }
426
427 /**
428 * Check last node line indent this detects, that block closed correctly
429 * @param {ASTNode} node Node to examine
430 * @param {int} lastLineIndent needed indent
431 * @returns {void}
432 */
433 function checkLastNodeLineIndent(node, lastLineIndent) {
434 const lastToken = sourceCode.getLastToken(node);
435 const endIndent = getNodeIndent(lastToken, true);
436
437 if ((endIndent.goodChar !== lastLineIndent || endIndent.badChar !== 0) && isNodeFirstInLine(node, true)) {
438 report(
439 node,
440 lastLineIndent,
441 endIndent.space,
442 endIndent.tab,
443 { line: lastToken.loc.start.line, column: lastToken.loc.start.column },
444 true
445 );
446 }
447 }
448
449 /**
450 * Check last node line indent this detects, that block closed correctly
451 * This function for more complicated return statement case, where closing parenthesis may be followed by ';'
452 * @param {ASTNode} node Node to examine
453 * @param {int} firstLineIndent first line needed indent
454 * @returns {void}
455 */
456 function checkLastReturnStatementLineIndent(node, firstLineIndent) {
457
458 /*
459 * in case if return statement ends with ');' we have traverse back to ')'
460 * otherwise we'll measure indent for ';' and replace ')'
461 */
462 const lastToken = sourceCode.getLastToken(node, astUtils.isClosingParenToken);
463 const textBeforeClosingParenthesis = sourceCode.getText(lastToken, lastToken.loc.start.column).slice(0, -1);
464
465 if (textBeforeClosingParenthesis.trim()) {
466
467 // There are tokens before the closing paren, don't report this case
468 return;
469 }
470
471 const endIndent = getNodeIndent(lastToken, true);
472
473 if (endIndent.goodChar !== firstLineIndent) {
474 report(
475 node,
476 firstLineIndent,
477 endIndent.space,
478 endIndent.tab,
479 { line: lastToken.loc.start.line, column: lastToken.loc.start.column },
480 true
481 );
482 }
483 }
484
485 /**
486 * Check first node line indent is correct
487 * @param {ASTNode} node Node to examine
488 * @param {int} firstLineIndent needed indent
489 * @returns {void}
490 */
491 function checkFirstNodeLineIndent(node, firstLineIndent) {
492 const startIndent = getNodeIndent(node, false);
493
494 if ((startIndent.goodChar !== firstLineIndent || startIndent.badChar !== 0) && isNodeFirstInLine(node)) {
495 report(
496 node,
497 firstLineIndent,
498 startIndent.space,
499 startIndent.tab,
500 { line: node.loc.start.line, column: node.loc.start.column }
501 );
502 }
503 }
504
505 /**
506 * Returns a parent node of given node based on a specified type
507 * if not present then return null
508 * @param {ASTNode} node node to examine
509 * @param {string} type type that is being looked for
510 * @param {string} stopAtList end points for the evaluating code
511 * @returns {ASTNode|void} if found then node otherwise null
512 */
513 function getParentNodeByType(node, type, stopAtList) {
514 let parent = node.parent;
515 const stopAtSet = new Set(stopAtList || ["Program"]);
516
517 while (parent.type !== type && !stopAtSet.has(parent.type) && parent.type !== "Program") {
518 parent = parent.parent;
519 }
520
521 return parent.type === type ? parent : null;
522 }
523
524 /**
525 * Returns the VariableDeclarator based on the current node
526 * if not present then return null
527 * @param {ASTNode} node node to examine
528 * @returns {ASTNode|void} if found then node otherwise null
529 */
530 function getVariableDeclaratorNode(node) {
531 return getParentNodeByType(node, "VariableDeclarator");
532 }
533
534 /**
535 * Check to see if the node is part of the multi-line variable declaration.
536 * Also if its on the same line as the varNode
537 * @param {ASTNode} node node to check
538 * @param {ASTNode} varNode variable declaration node to check against
539 * @returns {boolean} True if all the above condition satisfy
540 */
541 function isNodeInVarOnTop(node, varNode) {
542 return varNode &&
543 varNode.parent.loc.start.line === node.loc.start.line &&
544 varNode.parent.declarations.length > 1;
545 }
546
547 /**
548 * Check to see if the argument before the callee node is multi-line and
549 * there should only be 1 argument before the callee node
550 * @param {ASTNode} node node to check
551 * @returns {boolean} True if arguments are multi-line
552 */
553 function isArgBeforeCalleeNodeMultiline(node) {
554 const parent = node.parent;
555
556 if (parent.arguments.length >= 2 && parent.arguments[1] === node) {
557 return parent.arguments[0].loc.end.line > parent.arguments[0].loc.start.line;
558 }
559
560 return false;
561 }
562
563 /**
564 * Check to see if the node is a file level IIFE
565 * @param {ASTNode} node The function node to check.
566 * @returns {boolean} True if the node is the outer IIFE
567 */
568 function isOuterIIFE(node) {
569 const parent = node.parent;
570 let stmt = parent.parent;
571
572 /*
573 * Verify that the node is an IIEF
574 */
575 if (
576 parent.type !== "CallExpression" ||
577 parent.callee !== node) {
578
579 return false;
580 }
581
582 /*
583 * Navigate legal ancestors to determine whether this IIEF is outer
584 */
585 while (
586 stmt.type === "UnaryExpression" && (
587 stmt.operator === "!" ||
588 stmt.operator === "~" ||
589 stmt.operator === "+" ||
590 stmt.operator === "-") ||
591 stmt.type === "AssignmentExpression" ||
592 stmt.type === "LogicalExpression" ||
593 stmt.type === "SequenceExpression" ||
594 stmt.type === "VariableDeclarator") {
595
596 stmt = stmt.parent;
597 }
598
599 return ((
600 stmt.type === "ExpressionStatement" ||
601 stmt.type === "VariableDeclaration") &&
602 stmt.parent && stmt.parent.type === "Program"
603 );
604 }
605
606 /**
607 * Check indent for function block content
608 * @param {ASTNode} node A BlockStatement node that is inside of a function.
609 * @returns {void}
610 */
611 function checkIndentInFunctionBlock(node) {
612
613 /*
614 * Search first caller in chain.
615 * Ex.:
616 *
617 * Models <- Identifier
618 * .User
619 * .find()
620 * .exec(function() {
621 * // function body
622 * });
623 *
624 * Looks for 'Models'
625 */
626 const calleeNode = node.parent; // FunctionExpression
627 let indent;
628
629 if (calleeNode.parent &&
630 (calleeNode.parent.type === "Property" ||
631 calleeNode.parent.type === "ArrayExpression")) {
632
633 // If function is part of array or object, comma can be put at left
634 indent = getNodeIndent(calleeNode, false).goodChar;
635 } else {
636
637 // If function is standalone, simple calculate indent
638 indent = getNodeIndent(calleeNode).goodChar;
639 }
640
641 if (calleeNode.parent.type === "CallExpression") {
642 const calleeParent = calleeNode.parent;
643
644 if (calleeNode.type !== "FunctionExpression" && calleeNode.type !== "ArrowFunctionExpression") {
645 if (calleeParent && calleeParent.loc.start.line < node.loc.start.line) {
646 indent = getNodeIndent(calleeParent).goodChar;
647 }
648 } else {
649 if (isArgBeforeCalleeNodeMultiline(calleeNode) &&
650 calleeParent.callee.loc.start.line === calleeParent.callee.loc.end.line &&
651 !isNodeFirstInLine(calleeNode)) {
652 indent = getNodeIndent(calleeParent).goodChar;
653 }
654 }
655 }
656
657 /*
658 * function body indent should be indent + indent size, unless this
659 * is a FunctionDeclaration, FunctionExpression, or outer IIFE and the corresponding options are enabled.
660 */
661 let functionOffset = indentSize;
662
663 if (options.outerIIFEBody !== null && isOuterIIFE(calleeNode)) {
664 functionOffset = options.outerIIFEBody * indentSize;
665 } else if (calleeNode.type === "FunctionExpression") {
666 functionOffset = options.FunctionExpression.body * indentSize;
667 } else if (calleeNode.type === "FunctionDeclaration") {
668 functionOffset = options.FunctionDeclaration.body * indentSize;
669 }
670 indent += functionOffset;
671
672 // check if the node is inside a variable
673 const parentVarNode = getVariableDeclaratorNode(node);
674
675 if (parentVarNode && isNodeInVarOnTop(node, parentVarNode)) {
676 indent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
677 }
678
679 if (node.body.length > 0) {
680 checkNodesIndent(node.body, indent);
681 }
682
683 checkLastNodeLineIndent(node, indent - functionOffset);
684 }
685
686
687 /**
688 * Checks if the given node starts and ends on the same line
689 * @param {ASTNode} node The node to check
690 * @returns {boolean} Whether or not the block starts and ends on the same line.
691 */
692 function isSingleLineNode(node) {
693 const lastToken = sourceCode.getLastToken(node),
694 startLine = node.loc.start.line,
695 endLine = lastToken.loc.end.line;
696
697 return startLine === endLine;
698 }
699
700 /**
701 * Check indent for array block content or object block content
702 * @param {ASTNode} node node to examine
703 * @returns {void}
704 */
705 function checkIndentInArrayOrObjectBlock(node) {
706
707 // Skip inline
708 if (isSingleLineNode(node)) {
709 return;
710 }
711
712 let elements = (node.type === "ArrayExpression") ? node.elements : node.properties;
713
714 // filter out empty elements example would be [ , 2] so remove first element as espree considers it as null
715 elements = elements.filter(elem => elem !== null);
716
717 let nodeIndent;
718 let elementsIndent;
719 const parentVarNode = getVariableDeclaratorNode(node);
720
721 // TODO - come up with a better strategy in future
722 if (isNodeFirstInLine(node)) {
723 const parent = node.parent;
724
725 nodeIndent = getNodeIndent(parent).goodChar;
726 if (!parentVarNode || parentVarNode.loc.start.line !== node.loc.start.line) {
727 if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) {
728 if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === parent.loc.start.line) {
729 nodeIndent += (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]);
730 } else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
731 const parentElements = node.parent.type === "ObjectExpression" ? node.parent.properties : node.parent.elements;
732
733 if (parentElements[0] &&
734 parentElements[0].loc.start.line === parent.loc.start.line &&
735 parentElements[0].loc.end.line !== parent.loc.start.line) {
736
737 /*
738 * If the first element of the array spans multiple lines, don't increase the expected indentation of the rest.
739 * e.g. [{
740 * foo: 1
741 * },
742 * {
743 * bar: 1
744 * }]
745 * the second object is not indented.
746 */
747 } else if (typeof options[parent.type] === "number") {
748 nodeIndent += options[parent.type] * indentSize;
749 } else {
750 nodeIndent = parentElements[0].loc.start.column;
751 }
752 } else if (parent.type === "CallExpression" || parent.type === "NewExpression") {
753 if (typeof options.CallExpression.arguments === "number") {
754 nodeIndent += options.CallExpression.arguments * indentSize;
755 } else if (options.CallExpression.arguments === "first") {
756 if (parent.arguments.includes(node)) {
757 nodeIndent = parent.arguments[0].loc.start.column;
758 }
759 } else {
760 nodeIndent += indentSize;
761 }
762 } else if (parent.type === "LogicalExpression" || parent.type === "ArrowFunctionExpression") {
763 nodeIndent += indentSize;
764 }
765 }
766 }
767
768 checkFirstNodeLineIndent(node, nodeIndent);
769 } else {
770 nodeIndent = getNodeIndent(node).goodChar;
771 }
772
773 if (options[node.type] === "first") {
774 elementsIndent = elements.length ? elements[0].loc.start.column : 0; // If there are no elements, elementsIndent doesn't matter.
775 } else {
776 elementsIndent = nodeIndent + indentSize * options[node.type];
777 }
778
779 /*
780 * Check if the node is a multiple variable declaration; if so, then
781 * make sure indentation takes that into account.
782 */
783 if (isNodeInVarOnTop(node, parentVarNode)) {
784 elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
785 }
786
787 checkNodesIndent(elements, elementsIndent);
788
789 if (elements.length > 0) {
790
791 // Skip last block line check if last item in same line
792 if (elements[elements.length - 1].loc.end.line === node.loc.end.line) {
793 return;
794 }
795 }
796
797 checkLastNodeLineIndent(node, nodeIndent +
798 (isNodeInVarOnTop(node, parentVarNode) ? options.VariableDeclarator[parentVarNode.parent.kind] * indentSize : 0));
799 }
800
801 /**
802 * Check if the node or node body is a BlockStatement or not
803 * @param {ASTNode} node node to test
804 * @returns {boolean} True if it or its body is a block statement
805 */
806 function isNodeBodyBlock(node) {
807 return node.type === "BlockStatement" || node.type === "ClassBody" || (node.body && node.body.type === "BlockStatement") ||
808 (node.consequent && node.consequent.type === "BlockStatement");
809 }
810
811 /**
812 * Check indentation for blocks
813 * @param {ASTNode} node node to check
814 * @returns {void}
815 */
816 function blockIndentationCheck(node) {
817
818 // Skip inline blocks
819 if (isSingleLineNode(node)) {
820 return;
821 }
822
823 if (node.parent && (
824 node.parent.type === "FunctionExpression" ||
825 node.parent.type === "FunctionDeclaration" ||
826 node.parent.type === "ArrowFunctionExpression")
827 ) {
828 checkIndentInFunctionBlock(node);
829 return;
830 }
831
832 let indent;
833 let nodesToCheck = [];
834
835 /*
836 * For this statements we should check indent from statement beginning,
837 * not from the beginning of the block.
838 */
839 const statementsWithProperties = [
840 "IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration", "TryStatement"
841 ];
842
843 if (node.parent && statementsWithProperties.includes(node.parent.type) && isNodeBodyBlock(node)) {
844 indent = getNodeIndent(node.parent).goodChar;
845 } else if (node.parent && node.parent.type === "CatchClause") {
846 indent = getNodeIndent(node.parent.parent).goodChar;
847 } else {
848 indent = getNodeIndent(node).goodChar;
849 }
850
851 if (node.type === "IfStatement" && node.consequent.type !== "BlockStatement") {
852 nodesToCheck = [node.consequent];
853 } else if (Array.isArray(node.body)) {
854 nodesToCheck = node.body;
855 } else {
856 nodesToCheck = [node.body];
857 }
858
859 if (nodesToCheck.length > 0) {
860 checkNodesIndent(nodesToCheck, indent + indentSize);
861 }
862
863 if (node.type === "BlockStatement") {
864 checkLastNodeLineIndent(node, indent);
865 }
866 }
867
868 /**
869 * Filter out the elements which are on the same line of each other or the node.
870 * basically have only 1 elements from each line except the variable declaration line.
871 * @param {ASTNode} node Variable declaration node
872 * @returns {ASTNode[]} Filtered elements
873 */
874 function filterOutSameLineVars(node) {
875 return node.declarations.reduce((finalCollection, elem) => {
876 const lastElem = finalCollection[finalCollection.length - 1];
877
878 if ((elem.loc.start.line !== node.loc.start.line && !lastElem) ||
879 (lastElem && lastElem.loc.start.line !== elem.loc.start.line)) {
880 finalCollection.push(elem);
881 }
882
883 return finalCollection;
884 }, []);
885 }
886
887 /**
888 * Check indentation for variable declarations
889 * @param {ASTNode} node node to examine
890 * @returns {void}
891 */
892 function checkIndentInVariableDeclarations(node) {
893 const elements = filterOutSameLineVars(node);
894 const nodeIndent = getNodeIndent(node).goodChar;
895 const lastElement = elements[elements.length - 1];
896
897 const elementsIndent = nodeIndent + indentSize * options.VariableDeclarator[node.kind];
898
899 checkNodesIndent(elements, elementsIndent);
900
901 // Only check the last line if there is any token after the last item
902 if (sourceCode.getLastToken(node).loc.end.line <= lastElement.loc.end.line) {
903 return;
904 }
905
906 const tokenBeforeLastElement = sourceCode.getTokenBefore(lastElement);
907
908 if (tokenBeforeLastElement.value === ",") {
909
910 // Special case for comma-first syntax where the semicolon is indented
911 checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement).goodChar);
912 } else {
913 checkLastNodeLineIndent(node, elementsIndent - indentSize);
914 }
915 }
916
917 /**
918 * Check and decide whether to check for indentation for blockless nodes
919 * Scenarios are for or while statements without braces around them
920 * @param {ASTNode} node node to examine
921 * @returns {void}
922 */
923 function blockLessNodes(node) {
924 if (node.body.type !== "BlockStatement") {
925 blockIndentationCheck(node);
926 }
927 }
928
929 /**
930 * Returns the expected indentation for the case statement
931 * @param {ASTNode} node node to examine
932 * @param {int} [providedSwitchIndent] indent for switch statement
933 * @returns {int} indent size
934 */
935 function expectedCaseIndent(node, providedSwitchIndent) {
936 const switchNode = (node.type === "SwitchStatement") ? node : node.parent;
937 const switchIndent = typeof providedSwitchIndent === "undefined"
938 ? getNodeIndent(switchNode).goodChar
939 : providedSwitchIndent;
940 let caseIndent;
941
942 if (caseIndentStore[switchNode.loc.start.line]) {
943 return caseIndentStore[switchNode.loc.start.line];
944 }
945
946 if (switchNode.cases.length > 0 && options.SwitchCase === 0) {
947 caseIndent = switchIndent;
948 } else {
949 caseIndent = switchIndent + (indentSize * options.SwitchCase);
950 }
951
952 caseIndentStore[switchNode.loc.start.line] = caseIndent;
953 return caseIndent;
954
955 }
956
957 /**
958 * Checks whether a return statement is wrapped in ()
959 * @param {ASTNode} node node to examine
960 * @returns {boolean} the result
961 */
962 function isWrappedInParenthesis(node) {
963 const regex = /^return\s*?\(\s*?\);*?/u;
964
965 const statementWithoutArgument = sourceCode.getText(node).replace(
966 sourceCode.getText(node.argument), ""
967 );
968
969 return regex.test(statementWithoutArgument);
970 }
971
972 return {
973 Program(node) {
974 if (node.body.length > 0) {
975
976 // Root nodes should have no indent
977 checkNodesIndent(node.body, getNodeIndent(node).goodChar);
978 }
979 },
980
981 ClassBody: blockIndentationCheck,
982
983 BlockStatement: blockIndentationCheck,
984
985 WhileStatement: blockLessNodes,
986
987 ForStatement: blockLessNodes,
988
989 ForInStatement: blockLessNodes,
990
991 ForOfStatement: blockLessNodes,
992
993 DoWhileStatement: blockLessNodes,
994
995 IfStatement(node) {
996 if (node.consequent.type !== "BlockStatement" && node.consequent.loc.start.line > node.loc.start.line) {
997 blockIndentationCheck(node);
998 }
999 },
1000
1001 VariableDeclaration(node) {
1002 if (node.declarations[node.declarations.length - 1].loc.start.line > node.declarations[0].loc.start.line) {
1003 checkIndentInVariableDeclarations(node);
1004 }
1005 },
1006
1007 ObjectExpression(node) {
1008 checkIndentInArrayOrObjectBlock(node);
1009 },
1010
1011 ArrayExpression(node) {
1012 checkIndentInArrayOrObjectBlock(node);
1013 },
1014
1015 MemberExpression(node) {
1016
1017 if (typeof options.MemberExpression === "undefined") {
1018 return;
1019 }
1020
1021 if (isSingleLineNode(node)) {
1022 return;
1023 }
1024
1025 /*
1026 * The typical layout of variable declarations and assignments
1027 * alter the expectation of correct indentation. Skip them.
1028 * TODO: Add appropriate configuration options for variable
1029 * declarations and assignments.
1030 */
1031 if (getParentNodeByType(node, "VariableDeclarator", ["FunctionExpression", "ArrowFunctionExpression"])) {
1032 return;
1033 }
1034
1035 if (getParentNodeByType(node, "AssignmentExpression", ["FunctionExpression"])) {
1036 return;
1037 }
1038
1039 const propertyIndent = getNodeIndent(node).goodChar + indentSize * options.MemberExpression;
1040
1041 const checkNodes = [node.property];
1042
1043 const dot = sourceCode.getTokenBefore(node.property);
1044
1045 if (dot.type === "Punctuator" && dot.value === ".") {
1046 checkNodes.push(dot);
1047 }
1048
1049 checkNodesIndent(checkNodes, propertyIndent);
1050 },
1051
1052 SwitchStatement(node) {
1053
1054 // Switch is not a 'BlockStatement'
1055 const switchIndent = getNodeIndent(node).goodChar;
1056 const caseIndent = expectedCaseIndent(node, switchIndent);
1057
1058 checkNodesIndent(node.cases, caseIndent);
1059
1060
1061 checkLastNodeLineIndent(node, switchIndent);
1062 },
1063
1064 SwitchCase(node) {
1065
1066 // Skip inline cases
1067 if (isSingleLineNode(node)) {
1068 return;
1069 }
1070 const caseIndent = expectedCaseIndent(node);
1071
1072 checkNodesIndent(node.consequent, caseIndent + indentSize);
1073 },
1074
1075 FunctionDeclaration(node) {
1076 if (isSingleLineNode(node)) {
1077 return;
1078 }
1079 if (options.FunctionDeclaration.parameters === "first" && node.params.length) {
1080 checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column);
1081 } else if (options.FunctionDeclaration.parameters !== null) {
1082 checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionDeclaration.parameters);
1083 }
1084 },
1085
1086 FunctionExpression(node) {
1087 if (isSingleLineNode(node)) {
1088 return;
1089 }
1090 if (options.FunctionExpression.parameters === "first" && node.params.length) {
1091 checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column);
1092 } else if (options.FunctionExpression.parameters !== null) {
1093 checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionExpression.parameters);
1094 }
1095 },
1096
1097 ReturnStatement(node) {
1098 if (isSingleLineNode(node)) {
1099 return;
1100 }
1101
1102 const firstLineIndent = getNodeIndent(node).goodChar;
1103
1104 // in case if return statement is wrapped in parenthesis
1105 if (isWrappedInParenthesis(node)) {
1106 checkLastReturnStatementLineIndent(node, firstLineIndent);
1107 } else {
1108 checkNodeIndent(node, firstLineIndent);
1109 }
1110 },
1111
1112 CallExpression(node) {
1113 if (isSingleLineNode(node)) {
1114 return;
1115 }
1116 if (options.CallExpression.arguments === "first" && node.arguments.length) {
1117 checkNodesIndent(node.arguments.slice(1), node.arguments[0].loc.start.column);
1118 } else if (options.CallExpression.arguments !== null) {
1119 checkNodesIndent(node.arguments, getNodeIndent(node).goodChar + indentSize * options.CallExpression.arguments);
1120 }
1121 }
1122
1123 };
1124
1125 }
1126};
Note: See TracBrowser for help on using the repository browser.