Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

indent-legacy.js 45KB

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