项目原始demo,不改动
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
Tento repozitář je archivovaný. Můžete prohlížet soubory, klonovat, ale nemůžete nahrávat a vytvářet nové úkoly a požadavky na natažení.

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