UNPKG

45.6 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 const stopAtSet = new Set(stopAtList || ["Program"]);
509
510 while (parent.type !== type && !stopAtSet.has(parent.type) && parent.type !== "Program") {
511 parent = parent.parent;
512 }
513
514 return parent.type === type ? parent : null;
515 }
516
517 /**
518 * Returns the VariableDeclarator based on the current node
519 * if not present then return null
520 * @param {ASTNode} node node to examine
521 * @returns {ASTNode|void} if found then node otherwise null
522 */
523 function getVariableDeclaratorNode(node) {
524 return getParentNodeByType(node, "VariableDeclarator");
525 }
526
527 /**
528 * Check to see if the node is part of the multi-line variable declaration.
529 * Also if its on the same line as the varNode
530 * @param {ASTNode} node node to check
531 * @param {ASTNode} varNode variable declaration node to check against
532 * @returns {boolean} True if all the above condition satisfy
533 */
534 function isNodeInVarOnTop(node, varNode) {
535 return varNode &&
536 varNode.parent.loc.start.line === node.loc.start.line &&
537 varNode.parent.declarations.length > 1;
538 }
539
540 /**
541 * Check to see if the argument before the callee node is multi-line and
542 * there should only be 1 argument before the callee node
543 * @param {ASTNode} node node to check
544 * @returns {boolean} True if arguments are multi-line
545 */
546 function isArgBeforeCalleeNodeMultiline(node) {
547 const parent = node.parent;
548
549 if (parent.arguments.length >= 2 && parent.arguments[1] === node) {
550 return parent.arguments[0].loc.end.line > parent.arguments[0].loc.start.line;
551 }
552
553 return false;
554 }
555
556 /**
557 * Check to see if the node is a file level IIFE
558 * @param {ASTNode} node The function node to check.
559 * @returns {boolean} True if the node is the outer IIFE
560 */
561 function isOuterIIFE(node) {
562 const parent = node.parent;
563 let stmt = parent.parent;
564
565 /*
566 * Verify that the node is an IIEF
567 */
568 if (
569 parent.type !== "CallExpression" ||
570 parent.callee !== node) {
571
572 return false;
573 }
574
575 /*
576 * Navigate legal ancestors to determine whether this IIEF is outer
577 */
578 while (
579 stmt.type === "UnaryExpression" && (
580 stmt.operator === "!" ||
581 stmt.operator === "~" ||
582 stmt.operator === "+" ||
583 stmt.operator === "-") ||
584 stmt.type === "AssignmentExpression" ||
585 stmt.type === "LogicalExpression" ||
586 stmt.type === "SequenceExpression" ||
587 stmt.type === "VariableDeclarator") {
588
589 stmt = stmt.parent;
590 }
591
592 return ((
593 stmt.type === "ExpressionStatement" ||
594 stmt.type === "VariableDeclaration") &&
595 stmt.parent && stmt.parent.type === "Program"
596 );
597 }
598
599 /**
600 * Check indent for function block content
601 * @param {ASTNode} node A BlockStatement node that is inside of a function.
602 * @returns {void}
603 */
604 function checkIndentInFunctionBlock(node) {
605
606 /*
607 * Search first caller in chain.
608 * Ex.:
609 *
610 * Models <- Identifier
611 * .User
612 * .find()
613 * .exec(function() {
614 * // function body
615 * });
616 *
617 * Looks for 'Models'
618 */
619 const calleeNode = node.parent; // FunctionExpression
620 let indent;
621
622 if (calleeNode.parent &&
623 (calleeNode.parent.type === "Property" ||
624 calleeNode.parent.type === "ArrayExpression")) {
625
626 // If function is part of array or object, comma can be put at left
627 indent = getNodeIndent(calleeNode, false).goodChar;
628 } else {
629
630 // If function is standalone, simple calculate indent
631 indent = getNodeIndent(calleeNode).goodChar;
632 }
633
634 if (calleeNode.parent.type === "CallExpression") {
635 const calleeParent = calleeNode.parent;
636
637 if (calleeNode.type !== "FunctionExpression" && calleeNode.type !== "ArrowFunctionExpression") {
638 if (calleeParent && calleeParent.loc.start.line < node.loc.start.line) {
639 indent = getNodeIndent(calleeParent).goodChar;
640 }
641 } else {
642 if (isArgBeforeCalleeNodeMultiline(calleeNode) &&
643 calleeParent.callee.loc.start.line === calleeParent.callee.loc.end.line &&
644 !isNodeFirstInLine(calleeNode)) {
645 indent = getNodeIndent(calleeParent).goodChar;
646 }
647 }
648 }
649
650 /*
651 * function body indent should be indent + indent size, unless this
652 * is a FunctionDeclaration, FunctionExpression, or outer IIFE and the corresponding options are enabled.
653 */
654 let functionOffset = indentSize;
655
656 if (options.outerIIFEBody !== null && isOuterIIFE(calleeNode)) {
657 functionOffset = options.outerIIFEBody * indentSize;
658 } else if (calleeNode.type === "FunctionExpression") {
659 functionOffset = options.FunctionExpression.body * indentSize;
660 } else if (calleeNode.type === "FunctionDeclaration") {
661 functionOffset = options.FunctionDeclaration.body * indentSize;
662 }
663 indent += functionOffset;
664
665 // check if the node is inside a variable
666 const parentVarNode = getVariableDeclaratorNode(node);
667
668 if (parentVarNode && isNodeInVarOnTop(node, parentVarNode)) {
669 indent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
670 }
671
672 if (node.body.length > 0) {
673 checkNodesIndent(node.body, indent);
674 }
675
676 checkLastNodeLineIndent(node, indent - functionOffset);
677 }
678
679
680 /**
681 * Checks if the given node starts and ends on the same line
682 * @param {ASTNode} node The node to check
683 * @returns {boolean} Whether or not the block starts and ends on the same line.
684 */
685 function isSingleLineNode(node) {
686 const lastToken = sourceCode.getLastToken(node),
687 startLine = node.loc.start.line,
688 endLine = lastToken.loc.end.line;
689
690 return startLine === endLine;
691 }
692
693 /**
694 * Check to see if the first element inside an array is an object and on the same line as the node
695 * If the node is not an array then it will return false.
696 * @param {ASTNode} node node to check
697 * @returns {boolean} success/failure
698 */
699 function isFirstArrayElementOnSameLine(node) {
700 if (node.type === "ArrayExpression" && node.elements[0]) {
701 return node.elements[0].loc.start.line === node.loc.start.line && node.elements[0].type === "ObjectExpression";
702 }
703 return false;
704
705 }
706
707 /**
708 * Check indent for array block content or object block content
709 * @param {ASTNode} node node to examine
710 * @returns {void}
711 */
712 function checkIndentInArrayOrObjectBlock(node) {
713
714 // Skip inline
715 if (isSingleLineNode(node)) {
716 return;
717 }
718
719 let elements = (node.type === "ArrayExpression") ? node.elements : node.properties;
720
721 // filter out empty elements example would be [ , 2] so remove first element as espree considers it as null
722 elements = elements.filter(elem => elem !== null);
723
724 let nodeIndent;
725 let elementsIndent;
726 const parentVarNode = getVariableDeclaratorNode(node);
727
728 // TODO - come up with a better strategy in future
729 if (isNodeFirstInLine(node)) {
730 const parent = node.parent;
731
732 nodeIndent = getNodeIndent(parent).goodChar;
733 if (!parentVarNode || parentVarNode.loc.start.line !== node.loc.start.line) {
734 if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) {
735 if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === parent.loc.start.line) {
736 nodeIndent += (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]);
737 } else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
738 const parentElements = node.parent.type === "ObjectExpression" ? node.parent.properties : node.parent.elements;
739
740 if (parentElements[0] &&
741 parentElements[0].loc.start.line === parent.loc.start.line &&
742 parentElements[0].loc.end.line !== parent.loc.start.line) {
743
744 /*
745 * If the first element of the array spans multiple lines, don't increase the expected indentation of the rest.
746 * e.g. [{
747 * foo: 1
748 * },
749 * {
750 * bar: 1
751 * }]
752 * the second object is not indented.
753 */
754 } else if (typeof options[parent.type] === "number") {
755 nodeIndent += options[parent.type] * indentSize;
756 } else {
757 nodeIndent = parentElements[0].loc.start.column;
758 }
759 } else if (parent.type === "CallExpression" || parent.type === "NewExpression") {
760 if (typeof options.CallExpression.arguments === "number") {
761 nodeIndent += options.CallExpression.arguments * indentSize;
762 } else if (options.CallExpression.arguments === "first") {
763 if (parent.arguments.indexOf(node) !== -1) {
764 nodeIndent = parent.arguments[0].loc.start.column;
765 }
766 } else {
767 nodeIndent += indentSize;
768 }
769 } else if (parent.type === "LogicalExpression" || parent.type === "ArrowFunctionExpression") {
770 nodeIndent += indentSize;
771 }
772 }
773 } else if (!parentVarNode && !isFirstArrayElementOnSameLine(parent) && parent.type !== "MemberExpression" && parent.type !== "ExpressionStatement" && parent.type !== "AssignmentExpression" && parent.type !== "Property") {
774 nodeIndent += indentSize;
775 }
776
777 checkFirstNodeLineIndent(node, nodeIndent);
778 } else {
779 nodeIndent = getNodeIndent(node).goodChar;
780 }
781
782 if (options[node.type] === "first") {
783 elementsIndent = elements.length ? elements[0].loc.start.column : 0; // If there are no elements, elementsIndent doesn't matter.
784 } else {
785 elementsIndent = nodeIndent + indentSize * options[node.type];
786 }
787
788 /*
789 * Check if the node is a multiple variable declaration; if so, then
790 * make sure indentation takes that into account.
791 */
792 if (isNodeInVarOnTop(node, parentVarNode)) {
793 elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind];
794 }
795
796 checkNodesIndent(elements, elementsIndent);
797
798 if (elements.length > 0) {
799
800 // Skip last block line check if last item in same line
801 if (elements[elements.length - 1].loc.end.line === node.loc.end.line) {
802 return;
803 }
804 }
805
806 checkLastNodeLineIndent(node, nodeIndent +
807 (isNodeInVarOnTop(node, parentVarNode) ? options.VariableDeclarator[parentVarNode.parent.kind] * indentSize : 0));
808 }
809
810 /**
811 * Check if the node or node body is a BlockStatement or not
812 * @param {ASTNode} node node to test
813 * @returns {boolean} True if it or its body is a block statement
814 */
815 function isNodeBodyBlock(node) {
816 return node.type === "BlockStatement" || node.type === "ClassBody" || (node.body && node.body.type === "BlockStatement") ||
817 (node.consequent && node.consequent.type === "BlockStatement");
818 }
819
820 /**
821 * Check indentation for blocks
822 * @param {ASTNode} node node to check
823 * @returns {void}
824 */
825 function blockIndentationCheck(node) {
826
827 // Skip inline blocks
828 if (isSingleLineNode(node)) {
829 return;
830 }
831
832 if (node.parent && (
833 node.parent.type === "FunctionExpression" ||
834 node.parent.type === "FunctionDeclaration" ||
835 node.parent.type === "ArrowFunctionExpression")
836 ) {
837 checkIndentInFunctionBlock(node);
838 return;
839 }
840
841 let indent;
842 let nodesToCheck = [];
843
844 /*
845 * For this statements we should check indent from statement beginning,
846 * not from the beginning of the block.
847 */
848 const statementsWithProperties = [
849 "IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration", "TryStatement"
850 ];
851
852 if (node.parent && statementsWithProperties.indexOf(node.parent.type) !== -1 && isNodeBodyBlock(node)) {
853 indent = getNodeIndent(node.parent).goodChar;
854 } else if (node.parent && node.parent.type === "CatchClause") {
855 indent = getNodeIndent(node.parent.parent).goodChar;
856 } else {
857 indent = getNodeIndent(node).goodChar;
858 }
859
860 if (node.type === "IfStatement" && node.consequent.type !== "BlockStatement") {
861 nodesToCheck = [node.consequent];
862 } else if (Array.isArray(node.body)) {
863 nodesToCheck = node.body;
864 } else {
865 nodesToCheck = [node.body];
866 }
867
868 if (nodesToCheck.length > 0) {
869 checkNodesIndent(nodesToCheck, indent + indentSize);
870 }
871
872 if (node.type === "BlockStatement") {
873 checkLastNodeLineIndent(node, indent);
874 }
875 }
876
877 /**
878 * Filter out the elements which are on the same line of each other or the node.
879 * basically have only 1 elements from each line except the variable declaration line.
880 * @param {ASTNode} node Variable declaration node
881 * @returns {ASTNode[]} Filtered elements
882 */
883 function filterOutSameLineVars(node) {
884 return node.declarations.reduce((finalCollection, elem) => {
885 const lastElem = finalCollection[finalCollection.length - 1];
886
887 if ((elem.loc.start.line !== node.loc.start.line && !lastElem) ||
888 (lastElem && lastElem.loc.start.line !== elem.loc.start.line)) {
889 finalCollection.push(elem);
890 }
891
892 return finalCollection;
893 }, []);
894 }
895
896 /**
897 * Check indentation for variable declarations
898 * @param {ASTNode} node node to examine
899 * @returns {void}
900 */
901 function checkIndentInVariableDeclarations(node) {
902 const elements = filterOutSameLineVars(node);
903 const nodeIndent = getNodeIndent(node).goodChar;
904 const lastElement = elements[elements.length - 1];
905
906 const elementsIndent = nodeIndent + indentSize * options.VariableDeclarator[node.kind];
907
908 checkNodesIndent(elements, elementsIndent);
909
910 // Only check the last line if there is any token after the last item
911 if (sourceCode.getLastToken(node).loc.end.line <= lastElement.loc.end.line) {
912 return;
913 }
914
915 const tokenBeforeLastElement = sourceCode.getTokenBefore(lastElement);
916
917 if (tokenBeforeLastElement.value === ",") {
918
919 // Special case for comma-first syntax where the semicolon is indented
920 checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement).goodChar);
921 } else {
922 checkLastNodeLineIndent(node, elementsIndent - indentSize);
923 }
924 }
925
926 /**
927 * Check and decide whether to check for indentation for blockless nodes
928 * Scenarios are for or while statements without braces around them
929 * @param {ASTNode} node node to examine
930 * @returns {void}
931 */
932 function blockLessNodes(node) {
933 if (node.body.type !== "BlockStatement") {
934 blockIndentationCheck(node);
935 }
936 }
937
938 /**
939 * Returns the expected indentation for the case statement
940 * @param {ASTNode} node node to examine
941 * @param {int} [providedSwitchIndent] indent for switch statement
942 * @returns {int} indent size
943 */
944 function expectedCaseIndent(node, providedSwitchIndent) {
945 const switchNode = (node.type === "SwitchStatement") ? node : node.parent;
946 const switchIndent = typeof providedSwitchIndent === "undefined"
947 ? getNodeIndent(switchNode).goodChar
948 : providedSwitchIndent;
949 let caseIndent;
950
951 if (caseIndentStore[switchNode.loc.start.line]) {
952 return caseIndentStore[switchNode.loc.start.line];
953 }
954
955 if (switchNode.cases.length > 0 && options.SwitchCase === 0) {
956 caseIndent = switchIndent;
957 } else {
958 caseIndent = switchIndent + (indentSize * options.SwitchCase);
959 }
960
961 caseIndentStore[switchNode.loc.start.line] = caseIndent;
962 return caseIndent;
963
964 }
965
966 /**
967 * Checks wether a return statement is wrapped in ()
968 * @param {ASTNode} node node to examine
969 * @returns {boolean} the result
970 */
971 function isWrappedInParenthesis(node) {
972 const regex = /^return\s*?\(\s*?\);*?/;
973
974 const statementWithoutArgument = sourceCode.getText(node).replace(
975 sourceCode.getText(node.argument), ""
976 );
977
978 return regex.test(statementWithoutArgument);
979 }
980
981 return {
982 Program(node) {
983 if (node.body.length > 0) {
984
985 // Root nodes should have no indent
986 checkNodesIndent(node.body, getNodeIndent(node).goodChar);
987 }
988 },
989
990 ClassBody: blockIndentationCheck,
991
992 BlockStatement: blockIndentationCheck,
993
994 WhileStatement: blockLessNodes,
995
996 ForStatement: blockLessNodes,
997
998 ForInStatement: blockLessNodes,
999
1000 ForOfStatement: blockLessNodes,
1001
1002 DoWhileStatement: blockLessNodes,
1003
1004 IfStatement(node) {
1005 if (node.consequent.type !== "BlockStatement" && node.consequent.loc.start.line > node.loc.start.line) {
1006 blockIndentationCheck(node);
1007 }
1008 },
1009
1010 VariableDeclaration(node) {
1011 if (node.declarations[node.declarations.length - 1].loc.start.line > node.declarations[0].loc.start.line) {
1012 checkIndentInVariableDeclarations(node);
1013 }
1014 },
1015
1016 ObjectExpression(node) {
1017 checkIndentInArrayOrObjectBlock(node);
1018 },
1019
1020 ArrayExpression(node) {
1021 checkIndentInArrayOrObjectBlock(node);
1022 },
1023
1024 MemberExpression(node) {
1025
1026 if (typeof options.MemberExpression === "undefined") {
1027 return;
1028 }
1029
1030 if (isSingleLineNode(node)) {
1031 return;
1032 }
1033
1034 /*
1035 * The typical layout of variable declarations and assignments
1036 * alter the expectation of correct indentation. Skip them.
1037 * TODO: Add appropriate configuration options for variable
1038 * declarations and assignments.
1039 */
1040 if (getParentNodeByType(node, "VariableDeclarator", ["FunctionExpression", "ArrowFunctionExpression"])) {
1041 return;
1042 }
1043
1044 if (getParentNodeByType(node, "AssignmentExpression", ["FunctionExpression"])) {
1045 return;
1046 }
1047
1048 const propertyIndent = getNodeIndent(node).goodChar + indentSize * options.MemberExpression;
1049
1050 const checkNodes = [node.property];
1051
1052 const dot = sourceCode.getTokenBefore(node.property);
1053
1054 if (dot.type === "Punctuator" && dot.value === ".") {
1055 checkNodes.push(dot);
1056 }
1057
1058 checkNodesIndent(checkNodes, propertyIndent);
1059 },
1060
1061 SwitchStatement(node) {
1062
1063 // Switch is not a 'BlockStatement'
1064 const switchIndent = getNodeIndent(node).goodChar;
1065 const caseIndent = expectedCaseIndent(node, switchIndent);
1066
1067 checkNodesIndent(node.cases, caseIndent);
1068
1069
1070 checkLastNodeLineIndent(node, switchIndent);
1071 },
1072
1073 SwitchCase(node) {
1074
1075 // Skip inline cases
1076 if (isSingleLineNode(node)) {
1077 return;
1078 }
1079 const caseIndent = expectedCaseIndent(node);
1080
1081 checkNodesIndent(node.consequent, caseIndent + indentSize);
1082 },
1083
1084 FunctionDeclaration(node) {
1085 if (isSingleLineNode(node)) {
1086 return;
1087 }
1088 if (options.FunctionDeclaration.parameters === "first" && node.params.length) {
1089 checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column);
1090 } else if (options.FunctionDeclaration.parameters !== null) {
1091 checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionDeclaration.parameters);
1092 }
1093 },
1094
1095 FunctionExpression(node) {
1096 if (isSingleLineNode(node)) {
1097 return;
1098 }
1099 if (options.FunctionExpression.parameters === "first" && node.params.length) {
1100 checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column);
1101 } else if (options.FunctionExpression.parameters !== null) {
1102 checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionExpression.parameters);
1103 }
1104 },
1105
1106 ReturnStatement(node) {
1107 if (isSingleLineNode(node)) {
1108 return;
1109 }
1110
1111 const firstLineIndent = getNodeIndent(node).goodChar;
1112
1113 // in case if return statement is wrapped in parenthesis
1114 if (isWrappedInParenthesis(node)) {
1115 checkLastReturnStatementLineIndent(node, firstLineIndent);
1116 } else {
1117 checkNodeIndent(node, firstLineIndent);
1118 }
1119 },
1120
1121 CallExpression(node) {
1122 if (isSingleLineNode(node)) {
1123 return;
1124 }
1125 if (options.CallExpression.arguments === "first" && node.arguments.length) {
1126 checkNodesIndent(node.arguments.slice(1), node.arguments[0].loc.start.column);
1127 } else if (options.CallExpression.arguments !== null) {
1128 checkNodesIndent(node.arguments, getNodeIndent(node).goodChar + indentSize * options.CallExpression.arguments);
1129 }
1130 }
1131
1132 };
1133
1134 }
1135};