indent.js 65 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334
  1. /**
  2. * @fileoverview This rule sets a specific indentation style and width for your code
  3. *
  4. * @author Teddy Katz
  5. * @author Vitaly Puzrin
  6. * @author Gyandeep Singh
  7. * @deprecated in ESLint v8.53.0
  8. */
  9. "use strict";
  10. //------------------------------------------------------------------------------
  11. // Requirements
  12. //------------------------------------------------------------------------------
  13. const astUtils = require("./utils/ast-utils");
  14. //------------------------------------------------------------------------------
  15. // Rule Definition
  16. //------------------------------------------------------------------------------
  17. const KNOWN_NODES = new Set([
  18. "AssignmentExpression",
  19. "AssignmentPattern",
  20. "ArrayExpression",
  21. "ArrayPattern",
  22. "ArrowFunctionExpression",
  23. "AwaitExpression",
  24. "BlockStatement",
  25. "BinaryExpression",
  26. "BreakStatement",
  27. "CallExpression",
  28. "CatchClause",
  29. "ChainExpression",
  30. "ClassBody",
  31. "ClassDeclaration",
  32. "ClassExpression",
  33. "ConditionalExpression",
  34. "ContinueStatement",
  35. "DoWhileStatement",
  36. "DebuggerStatement",
  37. "EmptyStatement",
  38. "ExperimentalRestProperty",
  39. "ExperimentalSpreadProperty",
  40. "ExpressionStatement",
  41. "ForStatement",
  42. "ForInStatement",
  43. "ForOfStatement",
  44. "FunctionDeclaration",
  45. "FunctionExpression",
  46. "Identifier",
  47. "IfStatement",
  48. "Literal",
  49. "LabeledStatement",
  50. "LogicalExpression",
  51. "MemberExpression",
  52. "MetaProperty",
  53. "MethodDefinition",
  54. "NewExpression",
  55. "ObjectExpression",
  56. "ObjectPattern",
  57. "PrivateIdentifier",
  58. "Program",
  59. "Property",
  60. "PropertyDefinition",
  61. "RestElement",
  62. "ReturnStatement",
  63. "SequenceExpression",
  64. "SpreadElement",
  65. "StaticBlock",
  66. "Super",
  67. "SwitchCase",
  68. "SwitchStatement",
  69. "TaggedTemplateExpression",
  70. "TemplateElement",
  71. "TemplateLiteral",
  72. "ThisExpression",
  73. "ThrowStatement",
  74. "TryStatement",
  75. "UnaryExpression",
  76. "UpdateExpression",
  77. "VariableDeclaration",
  78. "VariableDeclarator",
  79. "WhileStatement",
  80. "WithStatement",
  81. "YieldExpression",
  82. "JSXFragment",
  83. "JSXOpeningFragment",
  84. "JSXClosingFragment",
  85. "JSXIdentifier",
  86. "JSXNamespacedName",
  87. "JSXMemberExpression",
  88. "JSXEmptyExpression",
  89. "JSXExpressionContainer",
  90. "JSXElement",
  91. "JSXClosingElement",
  92. "JSXOpeningElement",
  93. "JSXAttribute",
  94. "JSXSpreadAttribute",
  95. "JSXText",
  96. "ExportDefaultDeclaration",
  97. "ExportNamedDeclaration",
  98. "ExportAllDeclaration",
  99. "ExportSpecifier",
  100. "ImportDeclaration",
  101. "ImportSpecifier",
  102. "ImportDefaultSpecifier",
  103. "ImportNamespaceSpecifier",
  104. "ImportExpression",
  105. ]);
  106. /*
  107. * General rule strategy:
  108. * 1. An OffsetStorage instance stores a map of desired offsets, where each token has a specified offset from another
  109. * specified token or to the first column.
  110. * 2. As the AST is traversed, modify the desired offsets of tokens accordingly. For example, when entering a
  111. * BlockStatement, offset all of the tokens in the BlockStatement by 1 indent level from the opening curly
  112. * brace of the BlockStatement.
  113. * 3. After traversing the AST, calculate the expected indentation levels of every token according to the
  114. * OffsetStorage container.
  115. * 4. For each line, compare the expected indentation of the first token to the actual indentation in the file,
  116. * and report the token if the two values are not equal.
  117. */
  118. /**
  119. * A mutable map that stores (key, value) pairs. The keys are numeric indices, and must be unique.
  120. * This is intended to be a generic wrapper around a map with non-negative integer keys, so that the underlying implementation
  121. * can easily be swapped out.
  122. */
  123. class IndexMap {
  124. /**
  125. * Creates an empty map
  126. * @param {number} maxKey The maximum key
  127. */
  128. constructor(maxKey) {
  129. // Initializing the array with the maximum expected size avoids dynamic reallocations that could degrade performance.
  130. this._values = Array(maxKey + 1);
  131. }
  132. /**
  133. * Inserts an entry into the map.
  134. * @param {number} key The entry's key
  135. * @param {any} value The entry's value
  136. * @returns {void}
  137. */
  138. insert(key, value) {
  139. this._values[key] = value;
  140. }
  141. /**
  142. * Finds the value of the entry with the largest key less than or equal to the provided key
  143. * @param {number} key The provided key
  144. * @returns {*|undefined} The value of the found entry, or undefined if no such entry exists.
  145. */
  146. findLastNotAfter(key) {
  147. const values = this._values;
  148. for (let index = key; index >= 0; index--) {
  149. const value = values[index];
  150. if (value) {
  151. return value;
  152. }
  153. }
  154. return void 0;
  155. }
  156. /**
  157. * Deletes all of the keys in the interval [start, end)
  158. * @param {number} start The start of the range
  159. * @param {number} end The end of the range
  160. * @returns {void}
  161. */
  162. deleteRange(start, end) {
  163. this._values.fill(void 0, start, end);
  164. }
  165. }
  166. /**
  167. * A helper class to get token-based info related to indentation
  168. */
  169. class TokenInfo {
  170. /**
  171. * @param {SourceCode} sourceCode A SourceCode object
  172. */
  173. constructor(sourceCode) {
  174. this.sourceCode = sourceCode;
  175. this.firstTokensByLineNumber = new Map();
  176. const tokens = sourceCode.tokensAndComments;
  177. for (let i = 0; i < tokens.length; i++) {
  178. const token = tokens[i];
  179. if (!this.firstTokensByLineNumber.has(token.loc.start.line)) {
  180. this.firstTokensByLineNumber.set(token.loc.start.line, token);
  181. }
  182. if (
  183. !this.firstTokensByLineNumber.has(token.loc.end.line) &&
  184. sourceCode.text
  185. .slice(
  186. token.range[1] - token.loc.end.column,
  187. token.range[1],
  188. )
  189. .trim()
  190. ) {
  191. this.firstTokensByLineNumber.set(token.loc.end.line, token);
  192. }
  193. }
  194. }
  195. /**
  196. * Gets the first token on a given token's line
  197. * @param {Token|ASTNode} token a node or token
  198. * @returns {Token} The first token on the given line
  199. */
  200. getFirstTokenOfLine(token) {
  201. return this.firstTokensByLineNumber.get(token.loc.start.line);
  202. }
  203. /**
  204. * Determines whether a token is the first token in its line
  205. * @param {Token} token The token
  206. * @returns {boolean} `true` if the token is the first on its line
  207. */
  208. isFirstTokenOfLine(token) {
  209. return this.getFirstTokenOfLine(token) === token;
  210. }
  211. /**
  212. * Get the actual indent of a token
  213. * @param {Token} token Token to examine. This should be the first token on its line.
  214. * @returns {string} The indentation characters that precede the token
  215. */
  216. getTokenIndent(token) {
  217. return this.sourceCode.text.slice(
  218. token.range[0] - token.loc.start.column,
  219. token.range[0],
  220. );
  221. }
  222. }
  223. /**
  224. * A class to store information on desired offsets of tokens from each other
  225. */
  226. class OffsetStorage {
  227. /**
  228. * @param {TokenInfo} tokenInfo a TokenInfo instance
  229. * @param {number} indentSize The desired size of each indentation level
  230. * @param {string} indentType The indentation character
  231. * @param {number} maxIndex The maximum end index of any token
  232. */
  233. constructor(tokenInfo, indentSize, indentType, maxIndex) {
  234. this._tokenInfo = tokenInfo;
  235. this._indentSize = indentSize;
  236. this._indentType = indentType;
  237. this._indexMap = new IndexMap(maxIndex);
  238. this._indexMap.insert(0, { offset: 0, from: null, force: false });
  239. this._lockedFirstTokens = new WeakMap();
  240. this._desiredIndentCache = new WeakMap();
  241. this._ignoredTokens = new WeakSet();
  242. }
  243. _getOffsetDescriptor(token) {
  244. return this._indexMap.findLastNotAfter(token.range[0]);
  245. }
  246. /**
  247. * Sets the offset column of token B to match the offset column of token A.
  248. * - **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In
  249. * most cases, `setDesiredOffset` should be used instead.
  250. * @param {Token} baseToken The first token
  251. * @param {Token} offsetToken The second token, whose offset should be matched to the first token
  252. * @returns {void}
  253. */
  254. matchOffsetOf(baseToken, offsetToken) {
  255. /*
  256. * lockedFirstTokens is a map from a token whose indentation is controlled by the "first" option to
  257. * the token that it depends on. For example, with the `ArrayExpression: first` option, the first
  258. * token of each element in the array after the first will be mapped to the first token of the first
  259. * element. The desired indentation of each of these tokens is computed based on the desired indentation
  260. * of the "first" element, rather than through the normal offset mechanism.
  261. */
  262. this._lockedFirstTokens.set(offsetToken, baseToken);
  263. }
  264. /**
  265. * Sets the desired offset of a token.
  266. *
  267. * This uses a line-based offset collapsing behavior to handle tokens on the same line.
  268. * For example, consider the following two cases:
  269. *
  270. * (
  271. * [
  272. * bar
  273. * ]
  274. * )
  275. *
  276. * ([
  277. * bar
  278. * ])
  279. *
  280. * Based on the first case, it's clear that the `bar` token needs to have an offset of 1 indent level (4 spaces) from
  281. * the `[` token, and the `[` token has to have an offset of 1 indent level from the `(` token. Since the `(` token is
  282. * the first on its line (with an indent of 0 spaces), the `bar` token needs to be offset by 2 indent levels (8 spaces)
  283. * from the start of its line.
  284. *
  285. * However, in the second case `bar` should only be indented by 4 spaces. This is because the offset of 1 indent level
  286. * between the `(` and the `[` tokens gets "collapsed" because the two tokens are on the same line. As a result, the
  287. * `(` token is mapped to the `[` token with an offset of 0, and the rule correctly decides that `bar` should be indented
  288. * by 1 indent level from the start of the line.
  289. *
  290. * This is useful because rule listeners can usually just call `setDesiredOffset` for all the tokens in the node,
  291. * without needing to check which lines those tokens are on.
  292. *
  293. * Note that since collapsing only occurs when two tokens are on the same line, there are a few cases where non-intuitive
  294. * behavior can occur. For example, consider the following cases:
  295. *
  296. * foo(
  297. * ).
  298. * bar(
  299. * baz
  300. * )
  301. *
  302. * foo(
  303. * ).bar(
  304. * baz
  305. * )
  306. *
  307. * Based on the first example, it would seem that `bar` should be offset by 1 indent level from `foo`, and `baz`
  308. * should be offset by 1 indent level from `bar`. However, this is not correct, because it would result in `baz`
  309. * being indented by 2 indent levels in the second case (since `foo`, `bar`, and `baz` are all on separate lines, no
  310. * collapsing would occur).
  311. *
  312. * Instead, the correct way would be to offset `baz` by 1 level from `bar`, offset `bar` by 1 level from the `)`, and
  313. * offset the `)` by 0 levels from `foo`. This ensures that the offset between `bar` and the `)` are correctly collapsed
  314. * in the second case.
  315. * @param {Token} token The token
  316. * @param {Token} fromToken The token that `token` should be offset from
  317. * @param {number} offset The desired indent level
  318. * @returns {void}
  319. */
  320. setDesiredOffset(token, fromToken, offset) {
  321. return this.setDesiredOffsets(token.range, fromToken, offset);
  322. }
  323. /**
  324. * Sets the desired offset of all tokens in a range
  325. * It's common for node listeners in this file to need to apply the same offset to a large, contiguous range of tokens.
  326. * Moreover, the offset of any given token is usually updated multiple times (roughly once for each node that contains
  327. * it). This means that the offset of each token is updated O(AST depth) times.
  328. * It would not be performant to store and update the offsets for each token independently, because the rule would end
  329. * up having a time complexity of O(number of tokens * AST depth), which is quite slow for large files.
  330. *
  331. * Instead, the offset tree is represented as a collection of contiguous offset ranges in a file. For example, the following
  332. * list could represent the state of the offset tree at a given point:
  333. *
  334. * - Tokens starting in the interval [0, 15) are aligned with the beginning of the file
  335. * - Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token
  336. * - Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token
  337. * - Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token
  338. * - Tokens starting in the interval [820, ∞) are offset by 1 indent level from the `baz` token
  339. *
  340. * The `setDesiredOffsets` methods inserts ranges like the ones above. The third line above would be inserted by using:
  341. * `setDesiredOffsets([30, 43], fooToken, 1);`
  342. * @param {[number, number]} range A [start, end] pair. All tokens with range[0] <= token.start < range[1] will have the offset applied.
  343. * @param {Token} fromToken The token that this is offset from
  344. * @param {number} offset The desired indent level
  345. * @param {boolean} force `true` if this offset should not use the normal collapsing behavior. This should almost always be false.
  346. * @returns {void}
  347. */
  348. setDesiredOffsets(range, fromToken, offset, force) {
  349. /*
  350. * Offset ranges are stored as a collection of nodes, where each node maps a numeric key to an offset
  351. * descriptor. The tree for the example above would have the following nodes:
  352. *
  353. * * key: 0, value: { offset: 0, from: null }
  354. * * key: 15, value: { offset: 1, from: barToken }
  355. * * key: 30, value: { offset: 1, from: fooToken }
  356. * * key: 43, value: { offset: 2, from: barToken }
  357. * * key: 820, value: { offset: 1, from: bazToken }
  358. *
  359. * To find the offset descriptor for any given token, one needs to find the node with the largest key
  360. * which is <= token.start. To make this operation fast, the nodes are stored in a map indexed by key.
  361. */
  362. const descriptorToInsert = { offset, from: fromToken, force };
  363. const descriptorAfterRange = this._indexMap.findLastNotAfter(range[1]);
  364. const fromTokenIsInRange =
  365. fromToken &&
  366. fromToken.range[0] >= range[0] &&
  367. fromToken.range[1] <= range[1];
  368. const fromTokenDescriptor =
  369. fromTokenIsInRange && this._getOffsetDescriptor(fromToken);
  370. // First, remove any existing nodes in the range from the map.
  371. this._indexMap.deleteRange(range[0] + 1, range[1]);
  372. // Insert a new node into the map for this range
  373. this._indexMap.insert(range[0], descriptorToInsert);
  374. /*
  375. * To avoid circular offset dependencies, keep the `fromToken` token mapped to whatever it was mapped to previously,
  376. * even if it's in the current range.
  377. */
  378. if (fromTokenIsInRange) {
  379. this._indexMap.insert(fromToken.range[0], fromTokenDescriptor);
  380. this._indexMap.insert(fromToken.range[1], descriptorToInsert);
  381. }
  382. /*
  383. * To avoid modifying the offset of tokens after the range, insert another node to keep the offset of the following
  384. * tokens the same as it was before.
  385. */
  386. this._indexMap.insert(range[1], descriptorAfterRange);
  387. }
  388. /**
  389. * Gets the desired indent of a token
  390. * @param {Token} token The token
  391. * @returns {string} The desired indent of the token
  392. */
  393. getDesiredIndent(token) {
  394. if (!this._desiredIndentCache.has(token)) {
  395. if (this._ignoredTokens.has(token)) {
  396. /*
  397. * If the token is ignored, use the actual indent of the token as the desired indent.
  398. * This ensures that no errors are reported for this token.
  399. */
  400. this._desiredIndentCache.set(
  401. token,
  402. this._tokenInfo.getTokenIndent(token),
  403. );
  404. } else if (this._lockedFirstTokens.has(token)) {
  405. const firstToken = this._lockedFirstTokens.get(token);
  406. this._desiredIndentCache.set(
  407. token,
  408. // (indentation for the first element's line)
  409. this.getDesiredIndent(
  410. this._tokenInfo.getFirstTokenOfLine(firstToken),
  411. ) +
  412. // (space between the start of the first element's line and the first element)
  413. this._indentType.repeat(
  414. firstToken.loc.start.column -
  415. this._tokenInfo.getFirstTokenOfLine(firstToken)
  416. .loc.start.column,
  417. ),
  418. );
  419. } else {
  420. const offsetInfo = this._getOffsetDescriptor(token);
  421. const offset =
  422. offsetInfo.from &&
  423. offsetInfo.from.loc.start.line === token.loc.start.line &&
  424. !/^\s*?\n/u.test(token.value) &&
  425. !offsetInfo.force
  426. ? 0
  427. : offsetInfo.offset * this._indentSize;
  428. this._desiredIndentCache.set(
  429. token,
  430. (offsetInfo.from
  431. ? this.getDesiredIndent(offsetInfo.from)
  432. : "") + this._indentType.repeat(offset),
  433. );
  434. }
  435. }
  436. return this._desiredIndentCache.get(token);
  437. }
  438. /**
  439. * Ignores a token, preventing it from being reported.
  440. * @param {Token} token The token
  441. * @returns {void}
  442. */
  443. ignoreToken(token) {
  444. if (this._tokenInfo.isFirstTokenOfLine(token)) {
  445. this._ignoredTokens.add(token);
  446. }
  447. }
  448. /**
  449. * Gets the first token that the given token's indentation is dependent on
  450. * @param {Token} token The token
  451. * @returns {Token} The token that the given token depends on, or `null` if the given token is at the top level
  452. */
  453. getFirstDependency(token) {
  454. return this._getOffsetDescriptor(token).from;
  455. }
  456. }
  457. const ELEMENT_LIST_SCHEMA = {
  458. oneOf: [
  459. {
  460. type: "integer",
  461. minimum: 0,
  462. },
  463. {
  464. enum: ["first", "off"],
  465. },
  466. ],
  467. };
  468. /** @type {import('../types').Rule.RuleModule} */
  469. module.exports = {
  470. meta: {
  471. deprecated: {
  472. message: "Formatting rules are being moved out of ESLint core.",
  473. url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
  474. deprecatedSince: "8.53.0",
  475. availableUntil: "11.0.0",
  476. replacedBy: [
  477. {
  478. message:
  479. "ESLint Stylistic now maintains deprecated stylistic core rules.",
  480. url: "https://eslint.style/guide/migration",
  481. plugin: {
  482. name: "@stylistic/eslint-plugin",
  483. url: "https://eslint.style",
  484. },
  485. rule: {
  486. name: "indent",
  487. url: "https://eslint.style/rules/indent",
  488. },
  489. },
  490. ],
  491. },
  492. type: "layout",
  493. docs: {
  494. description: "Enforce consistent indentation",
  495. recommended: false,
  496. url: "https://eslint.org/docs/latest/rules/indent",
  497. },
  498. fixable: "whitespace",
  499. schema: [
  500. {
  501. oneOf: [
  502. {
  503. enum: ["tab"],
  504. },
  505. {
  506. type: "integer",
  507. minimum: 0,
  508. },
  509. ],
  510. },
  511. {
  512. type: "object",
  513. properties: {
  514. SwitchCase: {
  515. type: "integer",
  516. minimum: 0,
  517. default: 0,
  518. },
  519. VariableDeclarator: {
  520. oneOf: [
  521. ELEMENT_LIST_SCHEMA,
  522. {
  523. type: "object",
  524. properties: {
  525. var: ELEMENT_LIST_SCHEMA,
  526. let: ELEMENT_LIST_SCHEMA,
  527. const: ELEMENT_LIST_SCHEMA,
  528. },
  529. additionalProperties: false,
  530. },
  531. ],
  532. },
  533. outerIIFEBody: {
  534. oneOf: [
  535. {
  536. type: "integer",
  537. minimum: 0,
  538. },
  539. {
  540. enum: ["off"],
  541. },
  542. ],
  543. },
  544. MemberExpression: {
  545. oneOf: [
  546. {
  547. type: "integer",
  548. minimum: 0,
  549. },
  550. {
  551. enum: ["off"],
  552. },
  553. ],
  554. },
  555. FunctionDeclaration: {
  556. type: "object",
  557. properties: {
  558. parameters: ELEMENT_LIST_SCHEMA,
  559. body: {
  560. type: "integer",
  561. minimum: 0,
  562. },
  563. },
  564. additionalProperties: false,
  565. },
  566. FunctionExpression: {
  567. type: "object",
  568. properties: {
  569. parameters: ELEMENT_LIST_SCHEMA,
  570. body: {
  571. type: "integer",
  572. minimum: 0,
  573. },
  574. },
  575. additionalProperties: false,
  576. },
  577. StaticBlock: {
  578. type: "object",
  579. properties: {
  580. body: {
  581. type: "integer",
  582. minimum: 0,
  583. },
  584. },
  585. additionalProperties: false,
  586. },
  587. CallExpression: {
  588. type: "object",
  589. properties: {
  590. arguments: ELEMENT_LIST_SCHEMA,
  591. },
  592. additionalProperties: false,
  593. },
  594. ArrayExpression: ELEMENT_LIST_SCHEMA,
  595. ObjectExpression: ELEMENT_LIST_SCHEMA,
  596. ImportDeclaration: ELEMENT_LIST_SCHEMA,
  597. flatTernaryExpressions: {
  598. type: "boolean",
  599. default: false,
  600. },
  601. offsetTernaryExpressions: {
  602. type: "boolean",
  603. default: false,
  604. },
  605. ignoredNodes: {
  606. type: "array",
  607. items: {
  608. type: "string",
  609. not: {
  610. pattern: ":exit$",
  611. },
  612. },
  613. },
  614. ignoreComments: {
  615. type: "boolean",
  616. default: false,
  617. },
  618. },
  619. additionalProperties: false,
  620. },
  621. ],
  622. messages: {
  623. wrongIndentation:
  624. "Expected indentation of {{expected}} but found {{actual}}.",
  625. },
  626. },
  627. create(context) {
  628. const DEFAULT_VARIABLE_INDENT = 1;
  629. const DEFAULT_PARAMETER_INDENT = 1;
  630. const DEFAULT_FUNCTION_BODY_INDENT = 1;
  631. let indentType = "space";
  632. let indentSize = 4;
  633. const options = {
  634. SwitchCase: 0,
  635. VariableDeclarator: {
  636. var: DEFAULT_VARIABLE_INDENT,
  637. let: DEFAULT_VARIABLE_INDENT,
  638. const: DEFAULT_VARIABLE_INDENT,
  639. },
  640. outerIIFEBody: 1,
  641. FunctionDeclaration: {
  642. parameters: DEFAULT_PARAMETER_INDENT,
  643. body: DEFAULT_FUNCTION_BODY_INDENT,
  644. },
  645. FunctionExpression: {
  646. parameters: DEFAULT_PARAMETER_INDENT,
  647. body: DEFAULT_FUNCTION_BODY_INDENT,
  648. },
  649. StaticBlock: {
  650. body: DEFAULT_FUNCTION_BODY_INDENT,
  651. },
  652. CallExpression: {
  653. arguments: DEFAULT_PARAMETER_INDENT,
  654. },
  655. MemberExpression: 1,
  656. ArrayExpression: 1,
  657. ObjectExpression: 1,
  658. ImportDeclaration: 1,
  659. flatTernaryExpressions: false,
  660. ignoredNodes: [],
  661. ignoreComments: false,
  662. };
  663. if (context.options.length) {
  664. if (context.options[0] === "tab") {
  665. indentSize = 1;
  666. indentType = "tab";
  667. } else {
  668. indentSize = context.options[0];
  669. indentType = "space";
  670. }
  671. if (context.options[1]) {
  672. Object.assign(options, context.options[1]);
  673. if (
  674. typeof options.VariableDeclarator === "number" ||
  675. options.VariableDeclarator === "first"
  676. ) {
  677. options.VariableDeclarator = {
  678. var: options.VariableDeclarator,
  679. let: options.VariableDeclarator,
  680. const: options.VariableDeclarator,
  681. };
  682. }
  683. }
  684. }
  685. const sourceCode = context.sourceCode;
  686. const tokenInfo = new TokenInfo(sourceCode);
  687. const offsets = new OffsetStorage(
  688. tokenInfo,
  689. indentSize,
  690. indentType === "space" ? " " : "\t",
  691. sourceCode.text.length,
  692. );
  693. const parameterParens = new WeakSet();
  694. /**
  695. * Creates an error message for a line, given the expected/actual indentation.
  696. * @param {number} expectedAmount The expected amount of indentation characters for this line
  697. * @param {number} actualSpaces The actual number of indentation spaces that were found on this line
  698. * @param {number} actualTabs The actual number of indentation tabs that were found on this line
  699. * @returns {string} An error message for this line
  700. */
  701. function createErrorMessageData(
  702. expectedAmount,
  703. actualSpaces,
  704. actualTabs,
  705. ) {
  706. const expectedStatement = `${expectedAmount} ${indentType}${expectedAmount === 1 ? "" : "s"}`; // e.g. "2 tabs"
  707. const foundSpacesWord = `space${actualSpaces === 1 ? "" : "s"}`; // e.g. "space"
  708. const foundTabsWord = `tab${actualTabs === 1 ? "" : "s"}`; // e.g. "tabs"
  709. let foundStatement;
  710. if (actualSpaces > 0) {
  711. /*
  712. * Abbreviate the message if the expected indentation is also spaces.
  713. * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces'
  714. */
  715. foundStatement =
  716. indentType === "space"
  717. ? actualSpaces
  718. : `${actualSpaces} ${foundSpacesWord}`;
  719. } else if (actualTabs > 0) {
  720. foundStatement =
  721. indentType === "tab"
  722. ? actualTabs
  723. : `${actualTabs} ${foundTabsWord}`;
  724. } else {
  725. foundStatement = "0";
  726. }
  727. return {
  728. expected: expectedStatement,
  729. actual: foundStatement,
  730. };
  731. }
  732. /**
  733. * Reports a given indent violation
  734. * @param {Token} token Token violating the indent rule
  735. * @param {string} neededIndent Expected indentation string
  736. * @returns {void}
  737. */
  738. function report(token, neededIndent) {
  739. const actualIndent = Array.from(tokenInfo.getTokenIndent(token));
  740. const numSpaces = actualIndent.filter(char => char === " ").length;
  741. const numTabs = actualIndent.filter(char => char === "\t").length;
  742. context.report({
  743. node: token,
  744. messageId: "wrongIndentation",
  745. data: createErrorMessageData(
  746. neededIndent.length,
  747. numSpaces,
  748. numTabs,
  749. ),
  750. loc: {
  751. start: { line: token.loc.start.line, column: 0 },
  752. end: {
  753. line: token.loc.start.line,
  754. column: token.loc.start.column,
  755. },
  756. },
  757. fix(fixer) {
  758. const range = [
  759. token.range[0] - token.loc.start.column,
  760. token.range[0],
  761. ];
  762. const newText = neededIndent;
  763. return fixer.replaceTextRange(range, newText);
  764. },
  765. });
  766. }
  767. /**
  768. * Checks if a token's indentation is correct
  769. * @param {Token} token Token to examine
  770. * @param {string} desiredIndent Desired indentation of the string
  771. * @returns {boolean} `true` if the token's indentation is correct
  772. */
  773. function validateTokenIndent(token, desiredIndent) {
  774. const indentation = tokenInfo.getTokenIndent(token);
  775. return (
  776. indentation === desiredIndent ||
  777. // To avoid conflicts with no-mixed-spaces-and-tabs, don't report mixed spaces and tabs.
  778. (indentation.includes(" ") && indentation.includes("\t"))
  779. );
  780. }
  781. /**
  782. * Check to see if the node is a file level IIFE
  783. * @param {ASTNode} node The function node to check.
  784. * @returns {boolean} True if the node is the outer IIFE
  785. */
  786. function isOuterIIFE(node) {
  787. /*
  788. * Verify that the node is an IIFE
  789. */
  790. if (
  791. !node.parent ||
  792. node.parent.type !== "CallExpression" ||
  793. node.parent.callee !== node
  794. ) {
  795. return false;
  796. }
  797. /*
  798. * Navigate legal ancestors to determine whether this IIFE is outer.
  799. * A "legal ancestor" is an expression or statement that causes the function to get executed immediately.
  800. * For example, `!(function(){})()` is an outer IIFE even though it is preceded by a ! operator.
  801. */
  802. let statement = node.parent && node.parent.parent;
  803. while (
  804. (statement.type === "UnaryExpression" &&
  805. ["!", "~", "+", "-"].includes(statement.operator)) ||
  806. statement.type === "AssignmentExpression" ||
  807. statement.type === "LogicalExpression" ||
  808. statement.type === "SequenceExpression" ||
  809. statement.type === "VariableDeclarator"
  810. ) {
  811. statement = statement.parent;
  812. }
  813. return (
  814. (statement.type === "ExpressionStatement" ||
  815. statement.type === "VariableDeclaration") &&
  816. statement.parent.type === "Program"
  817. );
  818. }
  819. /**
  820. * Counts the number of linebreaks that follow the last non-whitespace character in a string
  821. * @param {string} string The string to check
  822. * @returns {number} The number of JavaScript linebreaks that follow the last non-whitespace character,
  823. * or the total number of linebreaks if the string is all whitespace.
  824. */
  825. function countTrailingLinebreaks(string) {
  826. const trailingWhitespace = string.match(/\s*$/u)[0];
  827. const linebreakMatches = trailingWhitespace.match(
  828. astUtils.createGlobalLinebreakMatcher(),
  829. );
  830. return linebreakMatches === null ? 0 : linebreakMatches.length;
  831. }
  832. /**
  833. * Check indentation for lists of elements (arrays, objects, function params)
  834. * @param {ASTNode[]} elements List of elements that should be offset
  835. * @param {Token} startToken The start token of the list that element should be aligned against, e.g. '['
  836. * @param {Token} endToken The end token of the list, e.g. ']'
  837. * @param {number|string} offset The amount that the elements should be offset
  838. * @returns {void}
  839. */
  840. function addElementListIndent(elements, startToken, endToken, offset) {
  841. /**
  842. * Gets the first token of a given element, including surrounding parentheses.
  843. * @param {ASTNode} element A node in the `elements` list
  844. * @returns {Token} The first token of this element
  845. */
  846. function getFirstToken(element) {
  847. let token = sourceCode.getTokenBefore(element);
  848. while (
  849. astUtils.isOpeningParenToken(token) &&
  850. token !== startToken
  851. ) {
  852. token = sourceCode.getTokenBefore(token);
  853. }
  854. return sourceCode.getTokenAfter(token);
  855. }
  856. // Run through all the tokens in the list, and offset them by one indent level (mainly for comments, other things will end up overridden)
  857. offsets.setDesiredOffsets(
  858. [startToken.range[1], endToken.range[0]],
  859. startToken,
  860. typeof offset === "number" ? offset : 1,
  861. );
  862. offsets.setDesiredOffset(endToken, startToken, 0);
  863. // If the preference is "first" but there is no first element (e.g. sparse arrays w/ empty first slot), fall back to 1 level.
  864. if (offset === "first" && elements.length && !elements[0]) {
  865. return;
  866. }
  867. elements.forEach((element, index) => {
  868. if (!element) {
  869. // Skip holes in arrays
  870. return;
  871. }
  872. if (offset === "off") {
  873. // Ignore the first token of every element if the "off" option is used
  874. offsets.ignoreToken(getFirstToken(element));
  875. }
  876. // Offset the following elements correctly relative to the first element
  877. if (index === 0) {
  878. return;
  879. }
  880. if (
  881. offset === "first" &&
  882. tokenInfo.isFirstTokenOfLine(getFirstToken(element))
  883. ) {
  884. offsets.matchOffsetOf(
  885. getFirstToken(elements[0]),
  886. getFirstToken(element),
  887. );
  888. } else {
  889. const previousElement = elements[index - 1];
  890. const firstTokenOfPreviousElement =
  891. previousElement && getFirstToken(previousElement);
  892. const previousElementLastToken =
  893. previousElement &&
  894. sourceCode.getLastToken(previousElement);
  895. if (
  896. previousElement &&
  897. previousElementLastToken.loc.end.line -
  898. countTrailingLinebreaks(
  899. previousElementLastToken.value,
  900. ) >
  901. startToken.loc.end.line
  902. ) {
  903. offsets.setDesiredOffsets(
  904. [previousElement.range[1], element.range[1]],
  905. firstTokenOfPreviousElement,
  906. 0,
  907. );
  908. }
  909. }
  910. });
  911. }
  912. /**
  913. * Check and decide whether to check for indentation for blockless nodes
  914. * Scenarios are for or while statements without braces around them
  915. * @param {ASTNode} node node to examine
  916. * @returns {void}
  917. */
  918. function addBlocklessNodeIndent(node) {
  919. if (node.type !== "BlockStatement") {
  920. const lastParentToken = sourceCode.getTokenBefore(
  921. node,
  922. astUtils.isNotOpeningParenToken,
  923. );
  924. let firstBodyToken = sourceCode.getFirstToken(node);
  925. let lastBodyToken = sourceCode.getLastToken(node);
  926. while (
  927. astUtils.isOpeningParenToken(
  928. sourceCode.getTokenBefore(firstBodyToken),
  929. ) &&
  930. astUtils.isClosingParenToken(
  931. sourceCode.getTokenAfter(lastBodyToken),
  932. )
  933. ) {
  934. firstBodyToken = sourceCode.getTokenBefore(firstBodyToken);
  935. lastBodyToken = sourceCode.getTokenAfter(lastBodyToken);
  936. }
  937. offsets.setDesiredOffsets(
  938. [firstBodyToken.range[0], lastBodyToken.range[1]],
  939. lastParentToken,
  940. 1,
  941. );
  942. }
  943. }
  944. /**
  945. * Checks the indentation for nodes that are like function calls (`CallExpression` and `NewExpression`)
  946. * @param {ASTNode} node A CallExpression or NewExpression node
  947. * @returns {void}
  948. */
  949. function addFunctionCallIndent(node) {
  950. let openingParen;
  951. if (node.arguments.length) {
  952. openingParen = sourceCode.getFirstTokenBetween(
  953. node.callee,
  954. node.arguments[0],
  955. astUtils.isOpeningParenToken,
  956. );
  957. } else {
  958. openingParen = sourceCode.getLastToken(node, 1);
  959. }
  960. const closingParen = sourceCode.getLastToken(node);
  961. parameterParens.add(openingParen);
  962. parameterParens.add(closingParen);
  963. /*
  964. * If `?.` token exists, set desired offset for that.
  965. * This logic is copied from `MemberExpression`'s.
  966. */
  967. if (node.optional) {
  968. const dotToken = sourceCode.getTokenAfter(
  969. node.callee,
  970. astUtils.isQuestionDotToken,
  971. );
  972. const calleeParenCount = sourceCode.getTokensBetween(
  973. node.callee,
  974. dotToken,
  975. { filter: astUtils.isClosingParenToken },
  976. ).length;
  977. const firstTokenOfCallee = calleeParenCount
  978. ? sourceCode.getTokenBefore(node.callee, {
  979. skip: calleeParenCount - 1,
  980. })
  981. : sourceCode.getFirstToken(node.callee);
  982. const lastTokenOfCallee = sourceCode.getTokenBefore(dotToken);
  983. const offsetBase =
  984. lastTokenOfCallee.loc.end.line ===
  985. openingParen.loc.start.line
  986. ? lastTokenOfCallee
  987. : firstTokenOfCallee;
  988. offsets.setDesiredOffset(dotToken, offsetBase, 1);
  989. }
  990. const offsetAfterToken =
  991. node.callee.type === "TaggedTemplateExpression"
  992. ? sourceCode.getFirstToken(node.callee.quasi)
  993. : openingParen;
  994. const offsetToken = sourceCode.getTokenBefore(offsetAfterToken);
  995. offsets.setDesiredOffset(openingParen, offsetToken, 0);
  996. addElementListIndent(
  997. node.arguments,
  998. openingParen,
  999. closingParen,
  1000. options.CallExpression.arguments,
  1001. );
  1002. }
  1003. /**
  1004. * Checks the indentation of parenthesized values, given a list of tokens in a program
  1005. * @param {Token[]} tokens A list of tokens
  1006. * @returns {void}
  1007. */
  1008. function addParensIndent(tokens) {
  1009. const parenStack = [];
  1010. const parenPairs = [];
  1011. for (let i = 0; i < tokens.length; i++) {
  1012. const nextToken = tokens[i];
  1013. if (astUtils.isOpeningParenToken(nextToken)) {
  1014. parenStack.push(nextToken);
  1015. } else if (astUtils.isClosingParenToken(nextToken)) {
  1016. parenPairs.push({
  1017. left: parenStack.pop(),
  1018. right: nextToken,
  1019. });
  1020. }
  1021. }
  1022. for (let i = parenPairs.length - 1; i >= 0; i--) {
  1023. const leftParen = parenPairs[i].left;
  1024. const rightParen = parenPairs[i].right;
  1025. // We only want to handle parens around expressions, so exclude parentheses that are in function parameters and function call arguments.
  1026. if (
  1027. !parameterParens.has(leftParen) &&
  1028. !parameterParens.has(rightParen)
  1029. ) {
  1030. const parenthesizedTokens = new Set(
  1031. sourceCode.getTokensBetween(leftParen, rightParen),
  1032. );
  1033. parenthesizedTokens.forEach(token => {
  1034. if (
  1035. !parenthesizedTokens.has(
  1036. offsets.getFirstDependency(token),
  1037. )
  1038. ) {
  1039. offsets.setDesiredOffset(token, leftParen, 1);
  1040. }
  1041. });
  1042. }
  1043. offsets.setDesiredOffset(rightParen, leftParen, 0);
  1044. }
  1045. }
  1046. /**
  1047. * Ignore all tokens within an unknown node whose offset do not depend
  1048. * on another token's offset within the unknown node
  1049. * @param {ASTNode} node Unknown Node
  1050. * @returns {void}
  1051. */
  1052. function ignoreNode(node) {
  1053. const unknownNodeTokens = new Set(
  1054. sourceCode.getTokens(node, { includeComments: true }),
  1055. );
  1056. unknownNodeTokens.forEach(token => {
  1057. if (!unknownNodeTokens.has(offsets.getFirstDependency(token))) {
  1058. const firstTokenOfLine =
  1059. tokenInfo.getFirstTokenOfLine(token);
  1060. if (token === firstTokenOfLine) {
  1061. offsets.ignoreToken(token);
  1062. } else {
  1063. offsets.setDesiredOffset(token, firstTokenOfLine, 0);
  1064. }
  1065. }
  1066. });
  1067. }
  1068. /**
  1069. * Check whether the given token is on the first line of a statement.
  1070. * @param {Token} token The token to check.
  1071. * @param {ASTNode} leafNode The expression node that the token belongs directly.
  1072. * @returns {boolean} `true` if the token is on the first line of a statement.
  1073. */
  1074. function isOnFirstLineOfStatement(token, leafNode) {
  1075. let node = leafNode;
  1076. while (
  1077. node.parent &&
  1078. !node.parent.type.endsWith("Statement") &&
  1079. !node.parent.type.endsWith("Declaration")
  1080. ) {
  1081. node = node.parent;
  1082. }
  1083. node = node.parent;
  1084. return !node || node.loc.start.line === token.loc.start.line;
  1085. }
  1086. /**
  1087. * Check whether there are any blank (whitespace-only) lines between
  1088. * two tokens on separate lines.
  1089. * @param {Token} firstToken The first token.
  1090. * @param {Token} secondToken The second token.
  1091. * @returns {boolean} `true` if the tokens are on separate lines and
  1092. * there exists a blank line between them, `false` otherwise.
  1093. */
  1094. function hasBlankLinesBetween(firstToken, secondToken) {
  1095. const firstTokenLine = firstToken.loc.end.line;
  1096. const secondTokenLine = secondToken.loc.start.line;
  1097. if (
  1098. firstTokenLine === secondTokenLine ||
  1099. firstTokenLine === secondTokenLine - 1
  1100. ) {
  1101. return false;
  1102. }
  1103. for (
  1104. let line = firstTokenLine + 1;
  1105. line < secondTokenLine;
  1106. ++line
  1107. ) {
  1108. if (!tokenInfo.firstTokensByLineNumber.has(line)) {
  1109. return true;
  1110. }
  1111. }
  1112. return false;
  1113. }
  1114. const ignoredNodeFirstTokens = new Set();
  1115. const baseOffsetListeners = {
  1116. "ArrayExpression, ArrayPattern"(node) {
  1117. const openingBracket = sourceCode.getFirstToken(node);
  1118. const closingBracket = sourceCode.getTokenAfter(
  1119. [...node.elements].reverse().find(_ => _) || openingBracket,
  1120. astUtils.isClosingBracketToken,
  1121. );
  1122. addElementListIndent(
  1123. node.elements,
  1124. openingBracket,
  1125. closingBracket,
  1126. options.ArrayExpression,
  1127. );
  1128. },
  1129. "ObjectExpression, ObjectPattern"(node) {
  1130. const openingCurly = sourceCode.getFirstToken(node);
  1131. const closingCurly = sourceCode.getTokenAfter(
  1132. node.properties.length
  1133. ? node.properties.at(-1)
  1134. : openingCurly,
  1135. astUtils.isClosingBraceToken,
  1136. );
  1137. addElementListIndent(
  1138. node.properties,
  1139. openingCurly,
  1140. closingCurly,
  1141. options.ObjectExpression,
  1142. );
  1143. },
  1144. ArrowFunctionExpression(node) {
  1145. const maybeOpeningParen = sourceCode.getFirstToken(node, {
  1146. skip: node.async ? 1 : 0,
  1147. });
  1148. if (astUtils.isOpeningParenToken(maybeOpeningParen)) {
  1149. const openingParen = maybeOpeningParen;
  1150. const closingParen = sourceCode.getTokenBefore(
  1151. node.body,
  1152. astUtils.isClosingParenToken,
  1153. );
  1154. parameterParens.add(openingParen);
  1155. parameterParens.add(closingParen);
  1156. addElementListIndent(
  1157. node.params,
  1158. openingParen,
  1159. closingParen,
  1160. options.FunctionExpression.parameters,
  1161. );
  1162. }
  1163. addBlocklessNodeIndent(node.body);
  1164. },
  1165. AssignmentExpression(node) {
  1166. const operator = sourceCode.getFirstTokenBetween(
  1167. node.left,
  1168. node.right,
  1169. token => token.value === node.operator,
  1170. );
  1171. offsets.setDesiredOffsets(
  1172. [operator.range[0], node.range[1]],
  1173. sourceCode.getLastToken(node.left),
  1174. 1,
  1175. );
  1176. offsets.ignoreToken(operator);
  1177. offsets.ignoreToken(sourceCode.getTokenAfter(operator));
  1178. },
  1179. "BinaryExpression, LogicalExpression"(node) {
  1180. const operator = sourceCode.getFirstTokenBetween(
  1181. node.left,
  1182. node.right,
  1183. token => token.value === node.operator,
  1184. );
  1185. /*
  1186. * For backwards compatibility, don't check BinaryExpression indents, e.g.
  1187. * var foo = bar &&
  1188. * baz;
  1189. */
  1190. const tokenAfterOperator = sourceCode.getTokenAfter(operator);
  1191. offsets.ignoreToken(operator);
  1192. offsets.ignoreToken(tokenAfterOperator);
  1193. offsets.setDesiredOffset(tokenAfterOperator, operator, 0);
  1194. },
  1195. "BlockStatement, ClassBody"(node) {
  1196. let blockIndentLevel;
  1197. if (node.parent && isOuterIIFE(node.parent)) {
  1198. blockIndentLevel = options.outerIIFEBody;
  1199. } else if (
  1200. node.parent &&
  1201. (node.parent.type === "FunctionExpression" ||
  1202. node.parent.type === "ArrowFunctionExpression")
  1203. ) {
  1204. blockIndentLevel = options.FunctionExpression.body;
  1205. } else if (
  1206. node.parent &&
  1207. node.parent.type === "FunctionDeclaration"
  1208. ) {
  1209. blockIndentLevel = options.FunctionDeclaration.body;
  1210. } else {
  1211. blockIndentLevel = 1;
  1212. }
  1213. /*
  1214. * For blocks that aren't lone statements, ensure that the opening curly brace
  1215. * is aligned with the parent.
  1216. */
  1217. if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
  1218. offsets.setDesiredOffset(
  1219. sourceCode.getFirstToken(node),
  1220. sourceCode.getFirstToken(node.parent),
  1221. 0,
  1222. );
  1223. }
  1224. addElementListIndent(
  1225. node.body,
  1226. sourceCode.getFirstToken(node),
  1227. sourceCode.getLastToken(node),
  1228. blockIndentLevel,
  1229. );
  1230. },
  1231. CallExpression: addFunctionCallIndent,
  1232. "ClassDeclaration[superClass], ClassExpression[superClass]"(node) {
  1233. const classToken = sourceCode.getFirstToken(node);
  1234. const extendsToken = sourceCode.getTokenBefore(
  1235. node.superClass,
  1236. astUtils.isNotOpeningParenToken,
  1237. );
  1238. offsets.setDesiredOffsets(
  1239. [extendsToken.range[0], node.body.range[0]],
  1240. classToken,
  1241. 1,
  1242. );
  1243. },
  1244. ConditionalExpression(node) {
  1245. const firstToken = sourceCode.getFirstToken(node);
  1246. // `flatTernaryExpressions` option is for the following style:
  1247. // var a =
  1248. // foo > 0 ? bar :
  1249. // foo < 0 ? baz :
  1250. // /*else*/ qiz ;
  1251. if (
  1252. !options.flatTernaryExpressions ||
  1253. !astUtils.isTokenOnSameLine(node.test, node.consequent) ||
  1254. isOnFirstLineOfStatement(firstToken, node)
  1255. ) {
  1256. const questionMarkToken = sourceCode.getFirstTokenBetween(
  1257. node.test,
  1258. node.consequent,
  1259. token =>
  1260. token.type === "Punctuator" && token.value === "?",
  1261. );
  1262. const colonToken = sourceCode.getFirstTokenBetween(
  1263. node.consequent,
  1264. node.alternate,
  1265. token =>
  1266. token.type === "Punctuator" && token.value === ":",
  1267. );
  1268. const firstConsequentToken =
  1269. sourceCode.getTokenAfter(questionMarkToken);
  1270. const lastConsequentToken =
  1271. sourceCode.getTokenBefore(colonToken);
  1272. const firstAlternateToken =
  1273. sourceCode.getTokenAfter(colonToken);
  1274. offsets.setDesiredOffset(questionMarkToken, firstToken, 1);
  1275. offsets.setDesiredOffset(colonToken, firstToken, 1);
  1276. offsets.setDesiredOffset(
  1277. firstConsequentToken,
  1278. firstToken,
  1279. firstConsequentToken.type === "Punctuator" &&
  1280. options.offsetTernaryExpressions
  1281. ? 2
  1282. : 1,
  1283. );
  1284. /*
  1285. * The alternate and the consequent should usually have the same indentation.
  1286. * If they share part of a line, align the alternate against the first token of the consequent.
  1287. * This allows the alternate to be indented correctly in cases like this:
  1288. * foo ? (
  1289. * bar
  1290. * ) : ( // this '(' is aligned with the '(' above, so it's considered to be aligned with `foo`
  1291. * baz // as a result, `baz` is offset by 1 rather than 2
  1292. * )
  1293. */
  1294. if (
  1295. lastConsequentToken.loc.end.line ===
  1296. firstAlternateToken.loc.start.line
  1297. ) {
  1298. offsets.setDesiredOffset(
  1299. firstAlternateToken,
  1300. firstConsequentToken,
  1301. 0,
  1302. );
  1303. } else {
  1304. /**
  1305. * If the alternate and consequent do not share part of a line, offset the alternate from the first
  1306. * token of the conditional expression. For example:
  1307. * foo ? bar
  1308. * : baz
  1309. *
  1310. * If `baz` were aligned with `bar` rather than being offset by 1 from `foo`, `baz` would end up
  1311. * having no expected indentation.
  1312. */
  1313. offsets.setDesiredOffset(
  1314. firstAlternateToken,
  1315. firstToken,
  1316. firstAlternateToken.type === "Punctuator" &&
  1317. options.offsetTernaryExpressions
  1318. ? 2
  1319. : 1,
  1320. );
  1321. }
  1322. }
  1323. },
  1324. "DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement, WithStatement":
  1325. node => addBlocklessNodeIndent(node.body),
  1326. ExportNamedDeclaration(node) {
  1327. if (node.declaration === null) {
  1328. const closingCurly = sourceCode.getLastToken(
  1329. node,
  1330. astUtils.isClosingBraceToken,
  1331. );
  1332. // Indent the specifiers in `export {foo, bar, baz}`
  1333. addElementListIndent(
  1334. node.specifiers,
  1335. sourceCode.getFirstToken(node, { skip: 1 }),
  1336. closingCurly,
  1337. 1,
  1338. );
  1339. if (node.source) {
  1340. // Indent everything after and including the `from` token in `export {foo, bar, baz} from 'qux'`
  1341. offsets.setDesiredOffsets(
  1342. [closingCurly.range[1], node.range[1]],
  1343. sourceCode.getFirstToken(node),
  1344. 1,
  1345. );
  1346. }
  1347. }
  1348. },
  1349. ForStatement(node) {
  1350. const forOpeningParen = sourceCode.getFirstToken(node, 1);
  1351. if (node.init) {
  1352. offsets.setDesiredOffsets(
  1353. node.init.range,
  1354. forOpeningParen,
  1355. 1,
  1356. );
  1357. }
  1358. if (node.test) {
  1359. offsets.setDesiredOffsets(
  1360. node.test.range,
  1361. forOpeningParen,
  1362. 1,
  1363. );
  1364. }
  1365. if (node.update) {
  1366. offsets.setDesiredOffsets(
  1367. node.update.range,
  1368. forOpeningParen,
  1369. 1,
  1370. );
  1371. }
  1372. addBlocklessNodeIndent(node.body);
  1373. },
  1374. "FunctionDeclaration, FunctionExpression"(node) {
  1375. const closingParen = sourceCode.getTokenBefore(node.body);
  1376. const openingParen = sourceCode.getTokenBefore(
  1377. node.params.length ? node.params[0] : closingParen,
  1378. );
  1379. parameterParens.add(openingParen);
  1380. parameterParens.add(closingParen);
  1381. addElementListIndent(
  1382. node.params,
  1383. openingParen,
  1384. closingParen,
  1385. options[node.type].parameters,
  1386. );
  1387. },
  1388. IfStatement(node) {
  1389. addBlocklessNodeIndent(node.consequent);
  1390. if (node.alternate) {
  1391. addBlocklessNodeIndent(node.alternate);
  1392. }
  1393. },
  1394. /*
  1395. * For blockless nodes with semicolon-first style, don't indent the semicolon.
  1396. * e.g.
  1397. * if (foo)
  1398. * bar()
  1399. * ; [1, 2, 3].map(foo)
  1400. *
  1401. * Traversal into the node sets indentation of the semicolon, so we need to override it on exit.
  1402. */
  1403. ":matches(DoWhileStatement, ForStatement, ForInStatement, ForOfStatement, IfStatement, WhileStatement, WithStatement):exit"(
  1404. node,
  1405. ) {
  1406. let nodesToCheck;
  1407. if (node.type === "IfStatement") {
  1408. nodesToCheck = [node.consequent];
  1409. if (node.alternate) {
  1410. nodesToCheck.push(node.alternate);
  1411. }
  1412. } else {
  1413. nodesToCheck = [node.body];
  1414. }
  1415. for (const nodeToCheck of nodesToCheck) {
  1416. const lastToken = sourceCode.getLastToken(nodeToCheck);
  1417. if (astUtils.isSemicolonToken(lastToken)) {
  1418. const tokenBeforeLast =
  1419. sourceCode.getTokenBefore(lastToken);
  1420. const tokenAfterLast =
  1421. sourceCode.getTokenAfter(lastToken);
  1422. // override indentation of `;` only if its line looks like a semicolon-first style line
  1423. if (
  1424. !astUtils.isTokenOnSameLine(
  1425. tokenBeforeLast,
  1426. lastToken,
  1427. ) &&
  1428. tokenAfterLast &&
  1429. astUtils.isTokenOnSameLine(
  1430. lastToken,
  1431. tokenAfterLast,
  1432. )
  1433. ) {
  1434. offsets.setDesiredOffset(
  1435. lastToken,
  1436. sourceCode.getFirstToken(node),
  1437. 0,
  1438. );
  1439. }
  1440. }
  1441. }
  1442. },
  1443. ImportDeclaration(node) {
  1444. if (
  1445. node.specifiers.some(
  1446. specifier => specifier.type === "ImportSpecifier",
  1447. )
  1448. ) {
  1449. const openingCurly = sourceCode.getFirstToken(
  1450. node,
  1451. astUtils.isOpeningBraceToken,
  1452. );
  1453. const closingCurly = sourceCode.getLastToken(
  1454. node,
  1455. astUtils.isClosingBraceToken,
  1456. );
  1457. addElementListIndent(
  1458. node.specifiers.filter(
  1459. specifier => specifier.type === "ImportSpecifier",
  1460. ),
  1461. openingCurly,
  1462. closingCurly,
  1463. options.ImportDeclaration,
  1464. );
  1465. }
  1466. const fromToken = sourceCode.getLastToken(
  1467. node,
  1468. token =>
  1469. token.type === "Identifier" && token.value === "from",
  1470. );
  1471. const sourceToken = sourceCode.getLastToken(
  1472. node,
  1473. token => token.type === "String",
  1474. );
  1475. const semiToken = sourceCode.getLastToken(
  1476. node,
  1477. token => token.type === "Punctuator" && token.value === ";",
  1478. );
  1479. if (fromToken) {
  1480. const end =
  1481. semiToken && semiToken.range[1] === sourceToken.range[1]
  1482. ? node.range[1]
  1483. : sourceToken.range[1];
  1484. offsets.setDesiredOffsets(
  1485. [fromToken.range[0], end],
  1486. sourceCode.getFirstToken(node),
  1487. 1,
  1488. );
  1489. }
  1490. },
  1491. ImportExpression(node) {
  1492. const openingParen = sourceCode.getFirstToken(node, 1);
  1493. const closingParen = sourceCode.getLastToken(node);
  1494. parameterParens.add(openingParen);
  1495. parameterParens.add(closingParen);
  1496. offsets.setDesiredOffset(
  1497. openingParen,
  1498. sourceCode.getTokenBefore(openingParen),
  1499. 0,
  1500. );
  1501. addElementListIndent(
  1502. [node.source],
  1503. openingParen,
  1504. closingParen,
  1505. options.CallExpression.arguments,
  1506. );
  1507. },
  1508. "MemberExpression, JSXMemberExpression, MetaProperty"(node) {
  1509. const object =
  1510. node.type === "MetaProperty" ? node.meta : node.object;
  1511. const firstNonObjectToken = sourceCode.getFirstTokenBetween(
  1512. object,
  1513. node.property,
  1514. astUtils.isNotClosingParenToken,
  1515. );
  1516. const secondNonObjectToken =
  1517. sourceCode.getTokenAfter(firstNonObjectToken);
  1518. const objectParenCount = sourceCode.getTokensBetween(
  1519. object,
  1520. node.property,
  1521. { filter: astUtils.isClosingParenToken },
  1522. ).length;
  1523. const firstObjectToken = objectParenCount
  1524. ? sourceCode.getTokenBefore(object, {
  1525. skip: objectParenCount - 1,
  1526. })
  1527. : sourceCode.getFirstToken(object);
  1528. const lastObjectToken =
  1529. sourceCode.getTokenBefore(firstNonObjectToken);
  1530. const firstPropertyToken = node.computed
  1531. ? firstNonObjectToken
  1532. : secondNonObjectToken;
  1533. if (node.computed) {
  1534. // For computed MemberExpressions, match the closing bracket with the opening bracket.
  1535. offsets.setDesiredOffset(
  1536. sourceCode.getLastToken(node),
  1537. firstNonObjectToken,
  1538. 0,
  1539. );
  1540. offsets.setDesiredOffsets(
  1541. node.property.range,
  1542. firstNonObjectToken,
  1543. 1,
  1544. );
  1545. }
  1546. /*
  1547. * If the object ends on the same line that the property starts, match against the last token
  1548. * of the object, to ensure that the MemberExpression is not indented.
  1549. *
  1550. * Otherwise, match against the first token of the object, e.g.
  1551. * foo
  1552. * .bar
  1553. * .baz // <-- offset by 1 from `foo`
  1554. */
  1555. const offsetBase =
  1556. lastObjectToken.loc.end.line ===
  1557. firstPropertyToken.loc.start.line
  1558. ? lastObjectToken
  1559. : firstObjectToken;
  1560. if (typeof options.MemberExpression === "number") {
  1561. // Match the dot (for non-computed properties) or the opening bracket (for computed properties) against the object.
  1562. offsets.setDesiredOffset(
  1563. firstNonObjectToken,
  1564. offsetBase,
  1565. options.MemberExpression,
  1566. );
  1567. /*
  1568. * For computed MemberExpressions, match the first token of the property against the opening bracket.
  1569. * Otherwise, match the first token of the property against the object.
  1570. */
  1571. offsets.setDesiredOffset(
  1572. secondNonObjectToken,
  1573. node.computed ? firstNonObjectToken : offsetBase,
  1574. options.MemberExpression,
  1575. );
  1576. } else {
  1577. // If the MemberExpression option is off, ignore the dot and the first token of the property.
  1578. offsets.ignoreToken(firstNonObjectToken);
  1579. offsets.ignoreToken(secondNonObjectToken);
  1580. // To ignore the property indentation, ensure that the property tokens depend on the ignored tokens.
  1581. offsets.setDesiredOffset(
  1582. firstNonObjectToken,
  1583. offsetBase,
  1584. 0,
  1585. );
  1586. offsets.setDesiredOffset(
  1587. secondNonObjectToken,
  1588. firstNonObjectToken,
  1589. 0,
  1590. );
  1591. }
  1592. },
  1593. NewExpression(node) {
  1594. // Only indent the arguments if the NewExpression has parens (e.g. `new Foo(bar)` or `new Foo()`, but not `new Foo`
  1595. if (
  1596. node.arguments.length > 0 ||
  1597. (astUtils.isClosingParenToken(
  1598. sourceCode.getLastToken(node),
  1599. ) &&
  1600. astUtils.isOpeningParenToken(
  1601. sourceCode.getLastToken(node, 1),
  1602. ))
  1603. ) {
  1604. addFunctionCallIndent(node);
  1605. }
  1606. },
  1607. Property(node) {
  1608. if (!node.shorthand && !node.method && node.kind === "init") {
  1609. const colon = sourceCode.getFirstTokenBetween(
  1610. node.key,
  1611. node.value,
  1612. astUtils.isColonToken,
  1613. );
  1614. offsets.ignoreToken(sourceCode.getTokenAfter(colon));
  1615. }
  1616. },
  1617. PropertyDefinition(node) {
  1618. const firstToken = sourceCode.getFirstToken(node);
  1619. const maybeSemicolonToken = sourceCode.getLastToken(node);
  1620. let keyLastToken;
  1621. // Indent key.
  1622. if (node.computed) {
  1623. const bracketTokenL = sourceCode.getTokenBefore(
  1624. node.key,
  1625. astUtils.isOpeningBracketToken,
  1626. );
  1627. const bracketTokenR = (keyLastToken =
  1628. sourceCode.getTokenAfter(
  1629. node.key,
  1630. astUtils.isClosingBracketToken,
  1631. ));
  1632. const keyRange = [
  1633. bracketTokenL.range[1],
  1634. bracketTokenR.range[0],
  1635. ];
  1636. if (bracketTokenL !== firstToken) {
  1637. offsets.setDesiredOffset(bracketTokenL, firstToken, 0);
  1638. }
  1639. offsets.setDesiredOffsets(keyRange, bracketTokenL, 1);
  1640. offsets.setDesiredOffset(bracketTokenR, bracketTokenL, 0);
  1641. } else {
  1642. const idToken = (keyLastToken = sourceCode.getFirstToken(
  1643. node.key,
  1644. ));
  1645. if (idToken !== firstToken) {
  1646. offsets.setDesiredOffset(idToken, firstToken, 1);
  1647. }
  1648. }
  1649. // Indent initializer.
  1650. if (node.value) {
  1651. const eqToken = sourceCode.getTokenBefore(
  1652. node.value,
  1653. astUtils.isEqToken,
  1654. );
  1655. const valueToken = sourceCode.getTokenAfter(eqToken);
  1656. offsets.setDesiredOffset(eqToken, keyLastToken, 1);
  1657. offsets.setDesiredOffset(valueToken, eqToken, 1);
  1658. if (astUtils.isSemicolonToken(maybeSemicolonToken)) {
  1659. offsets.setDesiredOffset(
  1660. maybeSemicolonToken,
  1661. eqToken,
  1662. 1,
  1663. );
  1664. }
  1665. } else if (astUtils.isSemicolonToken(maybeSemicolonToken)) {
  1666. offsets.setDesiredOffset(
  1667. maybeSemicolonToken,
  1668. keyLastToken,
  1669. 1,
  1670. );
  1671. }
  1672. },
  1673. StaticBlock(node) {
  1674. const openingCurly = sourceCode.getFirstToken(node, {
  1675. skip: 1,
  1676. }); // skip the `static` token
  1677. const closingCurly = sourceCode.getLastToken(node);
  1678. addElementListIndent(
  1679. node.body,
  1680. openingCurly,
  1681. closingCurly,
  1682. options.StaticBlock.body,
  1683. );
  1684. },
  1685. SwitchStatement(node) {
  1686. const openingCurly = sourceCode.getTokenAfter(
  1687. node.discriminant,
  1688. astUtils.isOpeningBraceToken,
  1689. );
  1690. const closingCurly = sourceCode.getLastToken(node);
  1691. offsets.setDesiredOffsets(
  1692. [openingCurly.range[1], closingCurly.range[0]],
  1693. openingCurly,
  1694. options.SwitchCase,
  1695. );
  1696. if (node.cases.length) {
  1697. sourceCode
  1698. .getTokensBetween(node.cases.at(-1), closingCurly, {
  1699. includeComments: true,
  1700. filter: astUtils.isCommentToken,
  1701. })
  1702. .forEach(token => offsets.ignoreToken(token));
  1703. }
  1704. },
  1705. SwitchCase(node) {
  1706. if (
  1707. !(
  1708. node.consequent.length === 1 &&
  1709. node.consequent[0].type === "BlockStatement"
  1710. )
  1711. ) {
  1712. const caseKeyword = sourceCode.getFirstToken(node);
  1713. const tokenAfterCurrentCase =
  1714. sourceCode.getTokenAfter(node);
  1715. offsets.setDesiredOffsets(
  1716. [caseKeyword.range[1], tokenAfterCurrentCase.range[0]],
  1717. caseKeyword,
  1718. 1,
  1719. );
  1720. }
  1721. },
  1722. TemplateLiteral(node) {
  1723. node.expressions.forEach((expression, index) => {
  1724. const previousQuasi = node.quasis[index];
  1725. const nextQuasi = node.quasis[index + 1];
  1726. const tokenToAlignFrom =
  1727. previousQuasi.loc.start.line ===
  1728. previousQuasi.loc.end.line
  1729. ? sourceCode.getFirstToken(previousQuasi)
  1730. : null;
  1731. offsets.setDesiredOffsets(
  1732. [previousQuasi.range[1], nextQuasi.range[0]],
  1733. tokenToAlignFrom,
  1734. 1,
  1735. );
  1736. offsets.setDesiredOffset(
  1737. sourceCode.getFirstToken(nextQuasi),
  1738. tokenToAlignFrom,
  1739. 0,
  1740. );
  1741. });
  1742. },
  1743. VariableDeclaration(node) {
  1744. let variableIndent = Object.hasOwn(
  1745. options.VariableDeclarator,
  1746. node.kind,
  1747. )
  1748. ? options.VariableDeclarator[node.kind]
  1749. : DEFAULT_VARIABLE_INDENT;
  1750. const firstToken = sourceCode.getFirstToken(node),
  1751. lastToken = sourceCode.getLastToken(node);
  1752. if (options.VariableDeclarator[node.kind] === "first") {
  1753. if (node.declarations.length > 1) {
  1754. addElementListIndent(
  1755. node.declarations,
  1756. firstToken,
  1757. lastToken,
  1758. "first",
  1759. );
  1760. return;
  1761. }
  1762. variableIndent = DEFAULT_VARIABLE_INDENT;
  1763. }
  1764. if (
  1765. node.declarations.at(-1).loc.start.line >
  1766. node.loc.start.line
  1767. ) {
  1768. /*
  1769. * VariableDeclarator indentation is a bit different from other forms of indentation, in that the
  1770. * indentation of an opening bracket sometimes won't match that of a closing bracket. For example,
  1771. * the following indentations are correct:
  1772. *
  1773. * var foo = {
  1774. * ok: true
  1775. * };
  1776. *
  1777. * var foo = {
  1778. * ok: true,
  1779. * },
  1780. * bar = 1;
  1781. *
  1782. * Account for when exiting the AST (after indentations have already been set for the nodes in
  1783. * the declaration) by manually increasing the indentation level of the tokens in this declarator
  1784. * on the same line as the start of the declaration, provided that there are declarators that
  1785. * follow this one.
  1786. */
  1787. offsets.setDesiredOffsets(
  1788. node.range,
  1789. firstToken,
  1790. variableIndent,
  1791. true,
  1792. );
  1793. } else {
  1794. offsets.setDesiredOffsets(
  1795. node.range,
  1796. firstToken,
  1797. variableIndent,
  1798. );
  1799. }
  1800. if (astUtils.isSemicolonToken(lastToken)) {
  1801. offsets.ignoreToken(lastToken);
  1802. }
  1803. },
  1804. VariableDeclarator(node) {
  1805. if (node.init) {
  1806. const equalOperator = sourceCode.getTokenBefore(
  1807. node.init,
  1808. astUtils.isNotOpeningParenToken,
  1809. );
  1810. const tokenAfterOperator =
  1811. sourceCode.getTokenAfter(equalOperator);
  1812. offsets.ignoreToken(equalOperator);
  1813. offsets.ignoreToken(tokenAfterOperator);
  1814. offsets.setDesiredOffsets(
  1815. [tokenAfterOperator.range[0], node.range[1]],
  1816. equalOperator,
  1817. 1,
  1818. );
  1819. offsets.setDesiredOffset(
  1820. equalOperator,
  1821. sourceCode.getLastToken(node.id),
  1822. 0,
  1823. );
  1824. }
  1825. },
  1826. "JSXAttribute[value]"(node) {
  1827. const equalsToken = sourceCode.getFirstTokenBetween(
  1828. node.name,
  1829. node.value,
  1830. token => token.type === "Punctuator" && token.value === "=",
  1831. );
  1832. offsets.setDesiredOffsets(
  1833. [equalsToken.range[0], node.value.range[1]],
  1834. sourceCode.getFirstToken(node.name),
  1835. 1,
  1836. );
  1837. },
  1838. JSXElement(node) {
  1839. if (node.closingElement) {
  1840. addElementListIndent(
  1841. node.children,
  1842. sourceCode.getFirstToken(node.openingElement),
  1843. sourceCode.getFirstToken(node.closingElement),
  1844. 1,
  1845. );
  1846. }
  1847. },
  1848. JSXOpeningElement(node) {
  1849. const firstToken = sourceCode.getFirstToken(node);
  1850. let closingToken;
  1851. if (node.selfClosing) {
  1852. closingToken = sourceCode.getLastToken(node, { skip: 1 });
  1853. offsets.setDesiredOffset(
  1854. sourceCode.getLastToken(node),
  1855. closingToken,
  1856. 0,
  1857. );
  1858. } else {
  1859. closingToken = sourceCode.getLastToken(node);
  1860. }
  1861. offsets.setDesiredOffsets(
  1862. node.name.range,
  1863. sourceCode.getFirstToken(node),
  1864. );
  1865. addElementListIndent(
  1866. node.attributes,
  1867. firstToken,
  1868. closingToken,
  1869. 1,
  1870. );
  1871. },
  1872. JSXClosingElement(node) {
  1873. const firstToken = sourceCode.getFirstToken(node);
  1874. offsets.setDesiredOffsets(node.name.range, firstToken, 1);
  1875. },
  1876. JSXFragment(node) {
  1877. const firstOpeningToken = sourceCode.getFirstToken(
  1878. node.openingFragment,
  1879. );
  1880. const firstClosingToken = sourceCode.getFirstToken(
  1881. node.closingFragment,
  1882. );
  1883. addElementListIndent(
  1884. node.children,
  1885. firstOpeningToken,
  1886. firstClosingToken,
  1887. 1,
  1888. );
  1889. },
  1890. JSXOpeningFragment(node) {
  1891. const firstToken = sourceCode.getFirstToken(node);
  1892. const closingToken = sourceCode.getLastToken(node);
  1893. offsets.setDesiredOffsets(node.range, firstToken, 1);
  1894. offsets.matchOffsetOf(firstToken, closingToken);
  1895. },
  1896. JSXClosingFragment(node) {
  1897. const firstToken = sourceCode.getFirstToken(node);
  1898. const slashToken = sourceCode.getLastToken(node, { skip: 1 });
  1899. const closingToken = sourceCode.getLastToken(node);
  1900. const tokenToMatch = astUtils.isTokenOnSameLine(
  1901. slashToken,
  1902. closingToken,
  1903. )
  1904. ? slashToken
  1905. : closingToken;
  1906. offsets.setDesiredOffsets(node.range, firstToken, 1);
  1907. offsets.matchOffsetOf(firstToken, tokenToMatch);
  1908. },
  1909. JSXExpressionContainer(node) {
  1910. const openingCurly = sourceCode.getFirstToken(node);
  1911. const closingCurly = sourceCode.getLastToken(node);
  1912. offsets.setDesiredOffsets(
  1913. [openingCurly.range[1], closingCurly.range[0]],
  1914. openingCurly,
  1915. 1,
  1916. );
  1917. },
  1918. JSXSpreadAttribute(node) {
  1919. const openingCurly = sourceCode.getFirstToken(node);
  1920. const closingCurly = sourceCode.getLastToken(node);
  1921. offsets.setDesiredOffsets(
  1922. [openingCurly.range[1], closingCurly.range[0]],
  1923. openingCurly,
  1924. 1,
  1925. );
  1926. },
  1927. "*"(node) {
  1928. const firstToken = sourceCode.getFirstToken(node);
  1929. // Ensure that the children of every node are indented at least as much as the first token.
  1930. if (firstToken && !ignoredNodeFirstTokens.has(firstToken)) {
  1931. offsets.setDesiredOffsets(node.range, firstToken, 0);
  1932. }
  1933. },
  1934. };
  1935. const listenerCallQueue = [];
  1936. /*
  1937. * To ignore the indentation of a node:
  1938. * 1. Don't call the node's listener when entering it (if it has a listener)
  1939. * 2. Don't set any offsets against the first token of the node.
  1940. * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets.
  1941. */
  1942. const offsetListeners = {};
  1943. for (const [selector, listener] of Object.entries(
  1944. baseOffsetListeners,
  1945. )) {
  1946. /*
  1947. * Offset listener calls are deferred until traversal is finished, and are called as
  1948. * part of the final `Program:exit` listener. This is necessary because a node might
  1949. * be matched by multiple selectors.
  1950. *
  1951. * Example: Suppose there is an offset listener for `Identifier`, and the user has
  1952. * specified in configuration that `MemberExpression > Identifier` should be ignored.
  1953. * Due to selector specificity rules, the `Identifier` listener will get called first. However,
  1954. * if a given Identifier node is supposed to be ignored, then the `Identifier` offset listener
  1955. * should not have been called at all. Without doing extra selector matching, we don't know
  1956. * whether the Identifier matches the `MemberExpression > Identifier` selector until the
  1957. * `MemberExpression > Identifier` listener is called.
  1958. *
  1959. * To avoid this, the `Identifier` listener isn't called until traversal finishes and all
  1960. * ignored nodes are known.
  1961. */
  1962. offsetListeners[selector] = node =>
  1963. listenerCallQueue.push({ listener, node });
  1964. }
  1965. // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set.
  1966. const ignoredNodes = new Set();
  1967. /**
  1968. * Ignores a node
  1969. * @param {ASTNode} node The node to ignore
  1970. * @returns {void}
  1971. */
  1972. function addToIgnoredNodes(node) {
  1973. ignoredNodes.add(node);
  1974. ignoredNodeFirstTokens.add(sourceCode.getFirstToken(node));
  1975. }
  1976. const ignoredNodeListeners = options.ignoredNodes.reduce(
  1977. (listeners, ignoredSelector) =>
  1978. Object.assign(listeners, {
  1979. [ignoredSelector]: addToIgnoredNodes,
  1980. }),
  1981. {},
  1982. );
  1983. /*
  1984. * Join the listeners, and add a listener to verify that all tokens actually have the correct indentation
  1985. * at the end.
  1986. *
  1987. * Using Object.assign will cause some offset listeners to be overwritten if the same selector also appears
  1988. * in `ignoredNodeListeners`. This isn't a problem because all of the matching nodes will be ignored,
  1989. * so those listeners wouldn't be called anyway.
  1990. */
  1991. return Object.assign(offsetListeners, ignoredNodeListeners, {
  1992. "*:exit"(node) {
  1993. // If a node's type is nonstandard, we can't tell how its children should be offset, so ignore it.
  1994. if (!KNOWN_NODES.has(node.type)) {
  1995. addToIgnoredNodes(node);
  1996. }
  1997. },
  1998. "Program:exit"() {
  1999. // If ignoreComments option is enabled, ignore all comment tokens.
  2000. if (options.ignoreComments) {
  2001. sourceCode
  2002. .getAllComments()
  2003. .forEach(comment => offsets.ignoreToken(comment));
  2004. }
  2005. // Invoke the queued offset listeners for the nodes that aren't ignored.
  2006. for (let i = 0; i < listenerCallQueue.length; i++) {
  2007. const nodeInfo = listenerCallQueue[i];
  2008. if (!ignoredNodes.has(nodeInfo.node)) {
  2009. nodeInfo.listener(nodeInfo.node);
  2010. }
  2011. }
  2012. // Update the offsets for ignored nodes to prevent their child tokens from being reported.
  2013. ignoredNodes.forEach(ignoreNode);
  2014. addParensIndent(sourceCode.ast.tokens);
  2015. /*
  2016. * Create a Map from (tokenOrComment) => (precedingToken).
  2017. * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly.
  2018. */
  2019. const precedingTokens = new WeakMap();
  2020. for (let i = 0; i < sourceCode.ast.comments.length; i++) {
  2021. const comment = sourceCode.ast.comments[i];
  2022. const tokenOrCommentBefore = sourceCode.getTokenBefore(
  2023. comment,
  2024. { includeComments: true },
  2025. );
  2026. const hasToken = precedingTokens.has(tokenOrCommentBefore)
  2027. ? precedingTokens.get(tokenOrCommentBefore)
  2028. : tokenOrCommentBefore;
  2029. precedingTokens.set(comment, hasToken);
  2030. }
  2031. for (let i = 1; i < sourceCode.lines.length + 1; i++) {
  2032. if (!tokenInfo.firstTokensByLineNumber.has(i)) {
  2033. // Don't check indentation on blank lines
  2034. continue;
  2035. }
  2036. const firstTokenOfLine =
  2037. tokenInfo.firstTokensByLineNumber.get(i);
  2038. if (firstTokenOfLine.loc.start.line !== i) {
  2039. // Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice.
  2040. continue;
  2041. }
  2042. if (astUtils.isCommentToken(firstTokenOfLine)) {
  2043. const tokenBefore =
  2044. precedingTokens.get(firstTokenOfLine);
  2045. const tokenAfter = tokenBefore
  2046. ? sourceCode.getTokenAfter(tokenBefore)
  2047. : sourceCode.ast.tokens[0];
  2048. const mayAlignWithBefore =
  2049. tokenBefore &&
  2050. !hasBlankLinesBetween(
  2051. tokenBefore,
  2052. firstTokenOfLine,
  2053. );
  2054. const mayAlignWithAfter =
  2055. tokenAfter &&
  2056. !hasBlankLinesBetween(firstTokenOfLine, tokenAfter);
  2057. /*
  2058. * If a comment precedes a line that begins with a semicolon token, align to that token, i.e.
  2059. *
  2060. * let foo
  2061. * // comment
  2062. * ;(async () => {})()
  2063. */
  2064. if (
  2065. tokenAfter &&
  2066. astUtils.isSemicolonToken(tokenAfter) &&
  2067. !astUtils.isTokenOnSameLine(
  2068. firstTokenOfLine,
  2069. tokenAfter,
  2070. )
  2071. ) {
  2072. offsets.setDesiredOffset(
  2073. firstTokenOfLine,
  2074. tokenAfter,
  2075. 0,
  2076. );
  2077. }
  2078. // If a comment matches the expected indentation of the token immediately before or after, don't report it.
  2079. if (
  2080. (mayAlignWithBefore &&
  2081. validateTokenIndent(
  2082. firstTokenOfLine,
  2083. offsets.getDesiredIndent(tokenBefore),
  2084. )) ||
  2085. (mayAlignWithAfter &&
  2086. validateTokenIndent(
  2087. firstTokenOfLine,
  2088. offsets.getDesiredIndent(tokenAfter),
  2089. ))
  2090. ) {
  2091. continue;
  2092. }
  2093. }
  2094. // If the token matches the expected indentation, don't report it.
  2095. if (
  2096. validateTokenIndent(
  2097. firstTokenOfLine,
  2098. offsets.getDesiredIndent(firstTokenOfLine),
  2099. )
  2100. ) {
  2101. continue;
  2102. }
  2103. // Otherwise, report the token/comment.
  2104. report(
  2105. firstTokenOfLine,
  2106. offsets.getDesiredIndent(firstTokenOfLine),
  2107. );
  2108. }
  2109. },
  2110. });
  2111. },
  2112. };