indent-legacy.js 44 KB

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