indent-legacy.js 35 KB

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