UNPKG

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