indent-common.js 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649
  1. /**
  2. * @author Toru Nagashima <https://github.com/mysticatea>
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. // ------------------------------------------------------------------------------
  7. // Requirements
  8. // ------------------------------------------------------------------------------
  9. const assert = require('assert')
  10. // ------------------------------------------------------------------------------
  11. // Helpers
  12. // ------------------------------------------------------------------------------
  13. const KNOWN_NODES = new Set(['ArrayExpression', 'ArrayPattern', 'ArrowFunctionExpression', 'AssignmentExpression', 'AssignmentPattern', 'AwaitExpression', 'BinaryExpression', 'BlockStatement', 'BreakStatement', 'CallExpression', 'CatchClause', 'ClassBody', 'ClassDeclaration', 'ClassExpression', 'ConditionalExpression', 'ContinueStatement', 'DebuggerStatement', 'DoWhileStatement', 'EmptyStatement', 'ExperimentalRestProperty', 'ExperimentalSpreadProperty', 'ExportAllDeclaration', 'ExportDefaultDeclaration', 'ExportNamedDeclaration', 'ExportSpecifier', 'ExpressionStatement', 'ForInStatement', 'ForOfStatement', 'ForStatement', 'FunctionDeclaration', 'FunctionExpression', 'Identifier', 'IfStatement', 'ImportDeclaration', 'ImportDefaultSpecifier', 'ImportNamespaceSpecifier', 'ImportSpecifier', 'LabeledStatement', 'Literal', 'LogicalExpression', 'MemberExpression', 'MetaProperty', 'MethodDefinition', 'NewExpression', 'ObjectExpression', 'ObjectPattern', 'Program', 'Property', 'RestElement', 'ReturnStatement', 'SequenceExpression', 'SpreadElement', 'Super', 'SwitchCase', 'SwitchStatement', 'TaggedTemplateExpression', 'TemplateElement', 'TemplateLiteral', 'ThisExpression', 'ThrowStatement', 'TryStatement', 'UnaryExpression', 'UpdateExpression', 'VariableDeclaration', 'VariableDeclarator', 'WhileStatement', 'WithStatement', 'YieldExpression', 'VAttribute', 'VDirectiveKey', 'VDocumentFragment', 'VElement', 'VEndTag', 'VExpressionContainer', 'VFilter', 'VFilterSequenceExpression', 'VForExpression', 'VIdentifier', 'VLiteral', 'VOnExpression', 'VSlotScopeExpression', 'VStartTag', 'VText'])
  14. const LT_CHAR = /[\r\n\u2028\u2029]/
  15. const LINES = /[^\r\n\u2028\u2029]+(?:$|\r\n|[\r\n\u2028\u2029])/g
  16. const BLOCK_COMMENT_PREFIX = /^\s*\*/
  17. const ITERATION_OPTS = Object.freeze({ includeComments: true, filter: isNotWhitespace })
  18. const PREFORMATTED_ELEMENT_NAMES = ['pre', 'textarea']
  19. /**
  20. * Normalize options.
  21. * @param {number|"tab"|undefined} type The type of indentation.
  22. * @param {Object} options Other options.
  23. * @param {Object} defaultOptions The default value of options.
  24. * @returns {{indentChar:" "|"\t",indentSize:number,baseIndent:number,attribute:number,closeBracket:number,switchCase:number,alignAttributesVertically:boolean,ignores:string[]}} Normalized options.
  25. */
  26. function parseOptions (type, options, defaultOptions) {
  27. const ret = Object.assign({
  28. indentChar: ' ',
  29. indentSize: 2,
  30. baseIndent: 0,
  31. attribute: 1,
  32. closeBracket: 0,
  33. switchCase: 0,
  34. alignAttributesVertically: true,
  35. ignores: []
  36. }, defaultOptions)
  37. if (Number.isSafeInteger(type)) {
  38. ret.indentSize = type
  39. } else if (type === 'tab') {
  40. ret.indentChar = '\t'
  41. ret.indentSize = 1
  42. }
  43. if (Number.isSafeInteger(options.baseIndent)) {
  44. ret.baseIndent = options.baseIndent
  45. }
  46. if (Number.isSafeInteger(options.attribute)) {
  47. ret.attribute = options.attribute
  48. }
  49. if (Number.isSafeInteger(options.closeBracket)) {
  50. ret.closeBracket = options.closeBracket
  51. }
  52. if (Number.isSafeInteger(options.switchCase)) {
  53. ret.switchCase = options.switchCase
  54. }
  55. if (options.alignAttributesVertically != null) {
  56. ret.alignAttributesVertically = options.alignAttributesVertically
  57. }
  58. if (options.ignores != null) {
  59. ret.ignores = options.ignores
  60. }
  61. return ret
  62. }
  63. /**
  64. * Check whether the given token is an arrow.
  65. * @param {Token} token The token to check.
  66. * @returns {boolean} `true` if the token is an arrow.
  67. */
  68. function isArrow (token) {
  69. return token != null && token.type === 'Punctuator' && token.value === '=>'
  70. }
  71. /**
  72. * Check whether the given token is a left parenthesis.
  73. * @param {Token} token The token to check.
  74. * @returns {boolean} `true` if the token is a left parenthesis.
  75. */
  76. function isLeftParen (token) {
  77. return token != null && token.type === 'Punctuator' && token.value === '('
  78. }
  79. /**
  80. * Check whether the given token is a left parenthesis.
  81. * @param {Token} token The token to check.
  82. * @returns {boolean} `false` if the token is a left parenthesis.
  83. */
  84. function isNotLeftParen (token) {
  85. return token != null && (token.type !== 'Punctuator' || token.value !== '(')
  86. }
  87. /**
  88. * Check whether the given token is a right parenthesis.
  89. * @param {Token} token The token to check.
  90. * @returns {boolean} `true` if the token is a right parenthesis.
  91. */
  92. function isRightParen (token) {
  93. return token != null && token.type === 'Punctuator' && token.value === ')'
  94. }
  95. /**
  96. * Check whether the given token is a right parenthesis.
  97. * @param {Token} token The token to check.
  98. * @returns {boolean} `false` if the token is a right parenthesis.
  99. */
  100. function isNotRightParen (token) {
  101. return token != null && (token.type !== 'Punctuator' || token.value !== ')')
  102. }
  103. /**
  104. * Check whether the given token is a left brace.
  105. * @param {Token} token The token to check.
  106. * @returns {boolean} `true` if the token is a left brace.
  107. */
  108. function isLeftBrace (token) {
  109. return token != null && token.type === 'Punctuator' && token.value === '{'
  110. }
  111. /**
  112. * Check whether the given token is a right brace.
  113. * @param {Token} token The token to check.
  114. * @returns {boolean} `true` if the token is a right brace.
  115. */
  116. function isRightBrace (token) {
  117. return token != null && token.type === 'Punctuator' && token.value === '}'
  118. }
  119. /**
  120. * Check whether the given token is a left bracket.
  121. * @param {Token} token The token to check.
  122. * @returns {boolean} `true` if the token is a left bracket.
  123. */
  124. function isLeftBracket (token) {
  125. return token != null && token.type === 'Punctuator' && token.value === '['
  126. }
  127. /**
  128. * Check whether the given token is a right bracket.
  129. * @param {Token} token The token to check.
  130. * @returns {boolean} `true` if the token is a right bracket.
  131. */
  132. function isRightBracket (token) {
  133. return token != null && token.type === 'Punctuator' && token.value === ']'
  134. }
  135. /**
  136. * Check whether the given token is a semicolon.
  137. * @param {Token} token The token to check.
  138. * @returns {boolean} `true` if the token is a semicolon.
  139. */
  140. function isSemicolon (token) {
  141. return token != null && token.type === 'Punctuator' && token.value === ';'
  142. }
  143. /**
  144. * Check whether the given token is a comma.
  145. * @param {Token} token The token to check.
  146. * @returns {boolean} `true` if the token is a comma.
  147. */
  148. function isComma (token) {
  149. return token != null && token.type === 'Punctuator' && token.value === ','
  150. }
  151. /**
  152. * Check whether the given token is a whitespace.
  153. * @param {Token} token The token to check.
  154. * @returns {boolean} `true` if the token is a whitespace.
  155. */
  156. function isNotWhitespace (token) {
  157. return token != null && token.type !== 'HTMLWhitespace'
  158. }
  159. /**
  160. * Check whether the given token is a comment.
  161. * @param {Token} token The token to check.
  162. * @returns {boolean} `true` if the token is a comment.
  163. */
  164. function isComment (token) {
  165. return token != null && (token.type === 'Block' || token.type === 'Line' || token.type === 'Shebang' || token.type.endsWith('Comment'))
  166. }
  167. /**
  168. * Check whether the given token is a comment.
  169. * @param {Token} token The token to check.
  170. * @returns {boolean} `false` if the token is a comment.
  171. */
  172. function isNotComment (token) {
  173. return token != null && token.type !== 'Block' && token.type !== 'Line' && token.type !== 'Shebang' && !token.type.endsWith('Comment')
  174. }
  175. /**
  176. * Check whether the given node is not an empty text node.
  177. * @param {Node} node The node to check.
  178. * @returns {boolean} `false` if the token is empty text node.
  179. */
  180. function isNotEmptyTextNode (node) {
  181. return !(node.type === 'VText' && node.value.trim() === '')
  182. }
  183. /**
  184. * Check whether the given token is a pipe operator.
  185. * @param {Token} token The token to check.
  186. * @returns {boolean} `true` if the token is a pipe operator.
  187. */
  188. function isPipeOperator (token) {
  189. return token != null && token.type === 'Punctuator' && token.value === '|'
  190. }
  191. /**
  192. * Get the last element.
  193. * @param {Array} xs The array to get the last element.
  194. * @returns {any|undefined} The last element or undefined.
  195. */
  196. function last (xs) {
  197. return xs.length === 0 ? undefined : xs[xs.length - 1]
  198. }
  199. /**
  200. * Check whether the node is at the beginning of line.
  201. * @param {Node} node The node to check.
  202. * @param {number} index The index of the node in the nodes.
  203. * @param {Node[]} nodes The array of nodes.
  204. * @returns {boolean} `true` if the node is at the beginning of line.
  205. */
  206. function isBeginningOfLine (node, index, nodes) {
  207. if (node != null) {
  208. for (let i = index - 1; i >= 0; --i) {
  209. const prevNode = nodes[i]
  210. if (prevNode == null) {
  211. continue
  212. }
  213. return node.loc.start.line !== prevNode.loc.end.line
  214. }
  215. }
  216. return false
  217. }
  218. /**
  219. * Check whether a given token is a closing token which triggers unindent.
  220. * @param {Token} token The token to check.
  221. * @returns {boolean} `true` if the token is a closing token.
  222. */
  223. function isClosingToken (token) {
  224. return token != null && (
  225. token.type === 'HTMLEndTagOpen' ||
  226. token.type === 'VExpressionEnd' ||
  227. (
  228. token.type === 'Punctuator' &&
  229. (
  230. token.value === ')' ||
  231. token.value === '}' ||
  232. token.value === ']'
  233. )
  234. )
  235. )
  236. }
  237. /**
  238. * Creates AST event handlers for html-indent.
  239. *
  240. * @param {RuleContext} context The rule context.
  241. * @param {TokenStore} tokenStore The token store object to get tokens.
  242. * @param {Object} defaultOptions The default value of options.
  243. * @returns {object} AST event handlers.
  244. */
  245. module.exports.defineVisitor = function create (context, tokenStore, defaultOptions) {
  246. if (!context.getFilename().endsWith('.vue')) return {}
  247. const options = parseOptions(context.options[0], context.options[1] || {}, defaultOptions)
  248. const sourceCode = context.getSourceCode()
  249. const offsets = new Map()
  250. const preformattedTokens = new Set()
  251. /**
  252. * Set offset to the given tokens.
  253. * @param {Token|Token[]} token The token to set.
  254. * @param {number} offset The offset of the tokens.
  255. * @param {Token} baseToken The token of the base offset.
  256. * @param {boolean} [trivial=false] The flag for trivial tokens.
  257. * @returns {void}
  258. */
  259. function setOffset (token, offset, baseToken) {
  260. assert(baseToken != null, "'baseToken' should not be null or undefined.")
  261. if (Array.isArray(token)) {
  262. for (const t of token) {
  263. offsets.set(t, {
  264. baseToken,
  265. offset,
  266. baseline: false,
  267. expectedIndent: undefined
  268. })
  269. }
  270. } else {
  271. offsets.set(token, {
  272. baseToken,
  273. offset,
  274. baseline: false,
  275. expectedIndent: undefined
  276. })
  277. }
  278. }
  279. /**
  280. * Set baseline flag to the given token.
  281. * @param {Token} token The token to set.
  282. * @returns {void}
  283. */
  284. function setBaseline (token) {
  285. const offsetInfo = offsets.get(token)
  286. if (offsetInfo != null) {
  287. offsetInfo.baseline = true
  288. }
  289. }
  290. /**
  291. * Sets preformatted tokens to the given element node.
  292. * @param {Node} node The node to set.
  293. * @returns {void}
  294. */
  295. function setPreformattedTokens (node) {
  296. const endToken = (node.endTag && tokenStore.getFirstToken(node.endTag)) || tokenStore.getTokenAfter(node)
  297. const option = {
  298. includeComments: true,
  299. filter: token => token != null && (
  300. token.type === 'HTMLText' ||
  301. token.type === 'HTMLRCDataText' ||
  302. token.type === 'HTMLTagOpen' ||
  303. token.type === 'HTMLEndTagOpen' ||
  304. token.type === 'HTMLComment'
  305. )
  306. }
  307. for (const token of tokenStore.getTokensBetween(node.startTag, endToken, option)) {
  308. preformattedTokens.add(token)
  309. }
  310. preformattedTokens.add(endToken)
  311. }
  312. /**
  313. * Get the first and last tokens of the given node.
  314. * If the node is parenthesized, this gets the outermost parentheses.
  315. * @param {Node} node The node to get.
  316. * @param {number} [borderOffset] The least offset of the first token. Defailt is 0. This value is used to prevent false positive in the following case: `(a) => {}` The parentheses are enclosing the whole parameter part rather than the first parameter, but this offset parameter is needed to distinguish.
  317. * @returns {{firstToken:Token,lastToken:Token}} The gotten tokens.
  318. */
  319. function getFirstAndLastTokens (node, borderOffset) {
  320. borderOffset |= 0
  321. let firstToken = tokenStore.getFirstToken(node)
  322. let lastToken = tokenStore.getLastToken(node)
  323. // Get the outermost left parenthesis if it's parenthesized.
  324. let t, u
  325. while ((t = tokenStore.getTokenBefore(firstToken)) != null && (u = tokenStore.getTokenAfter(lastToken)) != null && isLeftParen(t) && isRightParen(u) && t.range[0] >= borderOffset) {
  326. firstToken = t
  327. lastToken = u
  328. }
  329. return { firstToken, lastToken }
  330. }
  331. /**
  332. * Process the given node list.
  333. * The first node is offsetted from the given left token.
  334. * Rest nodes are adjusted to the first node.
  335. * @param {Node[]} nodeList The node to process.
  336. * @param {Node|Token|null} left The left parenthesis token.
  337. * @param {Node|Token|null} right The right parenthesis token.
  338. * @param {number} offset The offset to set.
  339. * @param {boolean} [alignVertically=true] The flag to align vertically. If `false`, this doesn't align vertically even if the first node is not at beginning of line.
  340. * @returns {void}
  341. */
  342. function processNodeList (nodeList, left, right, offset, alignVertically) {
  343. let t
  344. const leftToken = (left && tokenStore.getFirstToken(left)) || left
  345. const rightToken = (right && tokenStore.getFirstToken(right)) || right
  346. if (nodeList.length >= 1) {
  347. let baseToken = null
  348. let lastToken = left
  349. const alignTokensBeforeBaseToken = []
  350. const alignTokens = []
  351. for (let i = 0; i < nodeList.length; ++i) {
  352. const node = nodeList[i]
  353. if (node == null) {
  354. // Holes of an array.
  355. continue
  356. }
  357. const elementTokens = getFirstAndLastTokens(node, lastToken != null ? lastToken.range[1] : 0)
  358. // Collect comma/comment tokens between the last token of the previous node and the first token of this node.
  359. if (lastToken != null) {
  360. t = lastToken
  361. while (
  362. (t = tokenStore.getTokenAfter(t, ITERATION_OPTS)) != null &&
  363. t.range[1] <= elementTokens.firstToken.range[0]
  364. ) {
  365. if (baseToken == null) {
  366. alignTokensBeforeBaseToken.push(t)
  367. } else {
  368. alignTokens.push(t)
  369. }
  370. }
  371. }
  372. if (baseToken == null) {
  373. baseToken = elementTokens.firstToken
  374. } else {
  375. alignTokens.push(elementTokens.firstToken)
  376. }
  377. // Save the last token to find tokens between this node and the next node.
  378. lastToken = elementTokens.lastToken
  379. }
  380. // Check trailing commas and comments.
  381. if (rightToken != null && lastToken != null) {
  382. t = lastToken
  383. while (
  384. (t = tokenStore.getTokenAfter(t, ITERATION_OPTS)) != null &&
  385. t.range[1] <= rightToken.range[0]
  386. ) {
  387. if (baseToken == null) {
  388. alignTokensBeforeBaseToken.push(t)
  389. } else {
  390. alignTokens.push(t)
  391. }
  392. }
  393. }
  394. // Set offsets.
  395. if (leftToken != null) {
  396. setOffset(alignTokensBeforeBaseToken, offset, leftToken)
  397. }
  398. if (baseToken != null) {
  399. // Set offset to the first token.
  400. if (leftToken != null) {
  401. setOffset(baseToken, offset, leftToken)
  402. }
  403. // Set baseline.
  404. if (nodeList.some(isBeginningOfLine)) {
  405. setBaseline(baseToken)
  406. }
  407. if (alignVertically === false && leftToken != null) {
  408. // Align tokens relatively to the left token.
  409. setOffset(alignTokens, offset, leftToken)
  410. } else {
  411. // Align the rest tokens to the first token.
  412. setOffset(alignTokens, 0, baseToken)
  413. }
  414. }
  415. }
  416. if (rightToken != null) {
  417. setOffset(rightToken, 0, leftToken)
  418. }
  419. }
  420. /**
  421. * Process the given node as body.
  422. * The body node maybe a block statement or an expression node.
  423. * @param {Node} node The body node to process.
  424. * @param {Token} baseToken The base token.
  425. * @returns {void}
  426. */
  427. function processMaybeBlock (node, baseToken) {
  428. const firstToken = getFirstAndLastTokens(node).firstToken
  429. setOffset(firstToken, isLeftBrace(firstToken) ? 0 : 1, baseToken)
  430. }
  431. /**
  432. * Collect prefix tokens of the given property.
  433. * The prefix includes `async`, `get`, `set`, `static`, and `*`.
  434. * @param {Property|MethodDefinition} node The property node to collect prefix tokens.
  435. */
  436. function getPrefixTokens (node) {
  437. const prefixes = []
  438. let token = tokenStore.getFirstToken(node)
  439. while (token != null && token.range[1] <= node.key.range[0]) {
  440. prefixes.push(token)
  441. token = tokenStore.getTokenAfter(token)
  442. }
  443. while (isLeftParen(last(prefixes)) || isLeftBracket(last(prefixes))) {
  444. prefixes.pop()
  445. }
  446. return prefixes
  447. }
  448. /**
  449. * Find the head of chaining nodes.
  450. * @param {Node} node The start node to find the head.
  451. * @returns {Token} The head token of the chain.
  452. */
  453. function getChainHeadToken (node) {
  454. const type = node.type
  455. while (node.parent.type === type) {
  456. node = node.parent
  457. }
  458. return tokenStore.getFirstToken(node)
  459. }
  460. /**
  461. * Check whether a given token is the first token of:
  462. *
  463. * - ExpressionStatement
  464. * - VExpressionContainer
  465. * - A parameter of CallExpression/NewExpression
  466. * - An element of ArrayExpression
  467. * - An expression of SequenceExpression
  468. *
  469. * @param {Token} token The token to check.
  470. * @param {Node} belongingNode The node that the token is belonging to.
  471. * @returns {boolean} `true` if the token is the first token of an element.
  472. */
  473. function isBeginningOfElement (token, belongingNode) {
  474. let node = belongingNode
  475. while (node != null) {
  476. const parent = node.parent
  477. const t = parent && parent.type
  478. if (t != null && (t.endsWith('Statement') || t.endsWith('Declaration'))) {
  479. return parent.range[0] === token.range[0]
  480. }
  481. if (t === 'VExpressionContainer') {
  482. return node.range[0] === token.range[0]
  483. }
  484. if (t === 'CallExpression' || t === 'NewExpression') {
  485. const openParen = tokenStore.getTokenAfter(parent.callee, isNotRightParen)
  486. return parent.arguments.some(param =>
  487. getFirstAndLastTokens(param, openParen.range[1]).firstToken.range[0] === token.range[0]
  488. )
  489. }
  490. if (t === 'ArrayExpression') {
  491. return parent.elements.some(element =>
  492. element != null &&
  493. getFirstAndLastTokens(element).firstToken.range[0] === token.range[0]
  494. )
  495. }
  496. if (t === 'SequenceExpression') {
  497. return parent.expressions.some(expr =>
  498. getFirstAndLastTokens(expr).firstToken.range[0] === token.range[0]
  499. )
  500. }
  501. node = parent
  502. }
  503. return false
  504. }
  505. /**
  506. * Set the base indentation to a given top-level AST node.
  507. * @param {Node} node The node to set.
  508. * @param {number} expectedIndent The number of expected indent.
  509. * @returns {void}
  510. */
  511. function processTopLevelNode (node, expectedIndent) {
  512. const token = tokenStore.getFirstToken(node)
  513. const offsetInfo = offsets.get(token)
  514. if (offsetInfo != null) {
  515. offsetInfo.expectedIndent = expectedIndent
  516. } else {
  517. offsets.set(token, { baseToken: null, offset: 0, baseline: false, expectedIndent })
  518. }
  519. }
  520. /**
  521. * Ignore all tokens of the given node.
  522. * @param {Node} node The node to ignore.
  523. * @returns {void}
  524. */
  525. function ignore (node) {
  526. for (const token of tokenStore.getTokens(node)) {
  527. offsets.delete(token)
  528. }
  529. }
  530. /**
  531. * Define functions to ignore nodes into the given visitor.
  532. * @param {Object} visitor The visitor to define functions to ignore nodes.
  533. * @returns {Object} The visitor.
  534. */
  535. function processIgnores (visitor) {
  536. for (const ignorePattern of options.ignores) {
  537. const key = `${ignorePattern}:exit`
  538. if (visitor.hasOwnProperty(key)) {
  539. const handler = visitor[key]
  540. visitor[key] = function (node) {
  541. const ret = handler.apply(this, arguments)
  542. ignore(node)
  543. return ret
  544. }
  545. } else {
  546. visitor[key] = ignore
  547. }
  548. }
  549. return visitor
  550. }
  551. /**
  552. * Calculate correct indentation of the line of the given tokens.
  553. * @param {Token[]} tokens Tokens which are on the same line.
  554. * @returns {object|null} Correct indentation. If it failed to calculate then `null`.
  555. */
  556. function getExpectedIndents (tokens) {
  557. const expectedIndents = []
  558. for (let i = 0; i < tokens.length; ++i) {
  559. const token = tokens[i]
  560. const offsetInfo = offsets.get(token)
  561. if (offsetInfo != null) {
  562. if (offsetInfo.expectedIndent != null) {
  563. expectedIndents.push(offsetInfo.expectedIndent)
  564. } else {
  565. const baseOffsetInfo = offsets.get(offsetInfo.baseToken)
  566. if (baseOffsetInfo != null && baseOffsetInfo.expectedIndent != null && (i === 0 || !baseOffsetInfo.baseline)) {
  567. expectedIndents.push(baseOffsetInfo.expectedIndent + (offsetInfo.offset * options.indentSize))
  568. if (baseOffsetInfo.baseline) {
  569. break
  570. }
  571. }
  572. }
  573. }
  574. }
  575. if (!expectedIndents.length) {
  576. return null
  577. }
  578. return {
  579. expectedIndent: expectedIndents[0],
  580. expectedBaseIndent: expectedIndents.reduce((a, b) => Math.min(a, b))
  581. }
  582. }
  583. /**
  584. * Get the text of the indentation part of the line which the given token is on.
  585. * @param {Token} firstToken The first token on a line.
  586. * @returns {string} The text of indentation part.
  587. */
  588. function getIndentText (firstToken) {
  589. const text = sourceCode.text
  590. let i = firstToken.range[0] - 1
  591. while (i >= 0 && !LT_CHAR.test(text[i])) {
  592. i -= 1
  593. }
  594. return text.slice(i + 1, firstToken.range[0])
  595. }
  596. /**
  597. * Define the function which fixes the problem.
  598. * @param {Token} token The token to fix.
  599. * @param {number} actualIndent The number of actual indentaion.
  600. * @param {number} expectedIndent The number of expected indentation.
  601. * @returns {Function} The defined function.
  602. */
  603. function defineFix (token, actualIndent, expectedIndent) {
  604. if (token.type === 'Block' && token.loc.start.line !== token.loc.end.line) {
  605. // Fix indentation in multiline block comments.
  606. const lines = sourceCode.getText(token).match(LINES)
  607. const firstLine = lines.shift()
  608. if (lines.every(l => BLOCK_COMMENT_PREFIX.test(l))) {
  609. return fixer => {
  610. const range = [token.range[0] - actualIndent, token.range[1]]
  611. const indent = options.indentChar.repeat(expectedIndent)
  612. return fixer.replaceTextRange(
  613. range,
  614. `${indent}${firstLine}${lines.map(l => l.replace(BLOCK_COMMENT_PREFIX, `${indent} *`)).join('')}`
  615. )
  616. }
  617. }
  618. }
  619. return fixer => {
  620. const range = [token.range[0] - actualIndent, token.range[0]]
  621. const indent = options.indentChar.repeat(expectedIndent)
  622. return fixer.replaceTextRange(range, indent)
  623. }
  624. }
  625. /**
  626. * Validate the given token with the pre-calculated expected indentation.
  627. * @param {Token} token The token to validate.
  628. * @param {number} expectedIndent The expected indentation.
  629. * @param {number[]|undefined} optionalExpectedIndents The optional expected indentation.
  630. * @returns {void}
  631. */
  632. function validateCore (token, expectedIndent, optionalExpectedIndents) {
  633. const line = token.loc.start.line
  634. const indentText = getIndentText(token)
  635. // If there is no line terminator after the `<script>` start tag,
  636. // `indentText` contains non-whitespace characters.
  637. // In that case, do nothing in order to prevent removing the `<script>` tag.
  638. if (indentText.trim() !== '') {
  639. return
  640. }
  641. const actualIndent = token.loc.start.column
  642. const unit = (options.indentChar === '\t' ? 'tab' : 'space')
  643. for (let i = 0; i < indentText.length; ++i) {
  644. if (indentText[i] !== options.indentChar) {
  645. context.report({
  646. loc: {
  647. start: { line, column: i },
  648. end: { line, column: i + 1 }
  649. },
  650. message: 'Expected {{expected}} character, but found {{actual}} character.',
  651. data: {
  652. expected: JSON.stringify(options.indentChar),
  653. actual: JSON.stringify(indentText[i])
  654. },
  655. fix: defineFix(token, actualIndent, expectedIndent)
  656. })
  657. return
  658. }
  659. }
  660. if (actualIndent !== expectedIndent && (optionalExpectedIndents == null || !optionalExpectedIndents.includes(actualIndent))) {
  661. context.report({
  662. loc: {
  663. start: { line, column: 0 },
  664. end: { line, column: actualIndent }
  665. },
  666. message: 'Expected indentation of {{expectedIndent}} {{unit}}{{expectedIndentPlural}} but found {{actualIndent}} {{unit}}{{actualIndentPlural}}.',
  667. data: {
  668. expectedIndent,
  669. actualIndent,
  670. unit,
  671. expectedIndentPlural: (expectedIndent === 1) ? '' : 's',
  672. actualIndentPlural: (actualIndent === 1) ? '' : 's'
  673. },
  674. fix: defineFix(token, actualIndent, expectedIndent)
  675. })
  676. }
  677. }
  678. /**
  679. * Get the expected indent of comments.
  680. * @param {Token|null} nextToken The next token of comments.
  681. * @param {number|undefined} nextExpectedIndent The expected indent of the next token.
  682. * @param {number|undefined} lastExpectedIndent The expected indent of the last token.
  683. * @returns {number[]}
  684. */
  685. function getCommentExpectedIndents (nextToken, nextExpectedIndent, lastExpectedIndent) {
  686. if (typeof lastExpectedIndent === 'number' && isClosingToken(nextToken)) {
  687. if (nextExpectedIndent === lastExpectedIndent) {
  688. // For solo comment. E.g.,
  689. // <div>
  690. // <!-- comment -->
  691. // </div>
  692. return [nextExpectedIndent + options.indentSize, nextExpectedIndent]
  693. }
  694. // For last comment. E.g.,
  695. // <div>
  696. // <div></div>
  697. // <!-- comment -->
  698. // </div>
  699. return [lastExpectedIndent, nextExpectedIndent]
  700. }
  701. // Adjust to next normally. E.g.,
  702. // <div>
  703. // <!-- comment -->
  704. // <div></div>
  705. // </div>
  706. return [nextExpectedIndent]
  707. }
  708. /**
  709. * Validate indentation of the line that the given tokens are on.
  710. * @param {Token[]} tokens The tokens on the same line to validate.
  711. * @param {Token[]} comments The comments which are on the immediately previous lines of the tokens.
  712. * @param {Token|null} lastToken The last validated token. Comments can adjust to the token.
  713. * @returns {void}
  714. */
  715. function validate (tokens, comments, lastToken) {
  716. // Calculate and save expected indentation.
  717. const firstToken = tokens[0]
  718. const actualIndent = firstToken.loc.start.column
  719. const expectedIndents = getExpectedIndents(tokens)
  720. if (!expectedIndents) {
  721. return
  722. }
  723. const expectedBaseIndent = expectedIndents.expectedBaseIndent
  724. const expectedIndent = expectedIndents.expectedIndent
  725. // Debug log
  726. // console.log('line', firstToken.loc.start.line, '=', { actualIndent, expectedIndent }, 'from:')
  727. // for (const token of tokens) {
  728. // const offsetInfo = offsets.get(token)
  729. // if (offsetInfo == null) {
  730. // console.log(' ', JSON.stringify(sourceCode.getText(token)), 'is unknown.')
  731. // } else if (offsetInfo.expectedIndent != null) {
  732. // console.log(' ', JSON.stringify(sourceCode.getText(token)), 'is fixed at', offsetInfo.expectedIndent, '.')
  733. // } else {
  734. // const baseOffsetInfo = offsets.get(offsetInfo.baseToken)
  735. // console.log(' ', JSON.stringify(sourceCode.getText(token)), 'is', offsetInfo.offset, 'offset from ', JSON.stringify(sourceCode.getText(offsetInfo.baseToken)), '( line:', offsetInfo.baseToken && offsetInfo.baseToken.loc.start.line, ', indent:', baseOffsetInfo && baseOffsetInfo.expectedIndent, ', baseline:', baseOffsetInfo && baseOffsetInfo.baseline, ')')
  736. // }
  737. // }
  738. // Save.
  739. const baseline = new Set()
  740. for (const token of tokens) {
  741. const offsetInfo = offsets.get(token)
  742. if (offsetInfo != null) {
  743. if (offsetInfo.baseline) {
  744. // This is a baseline token, so the expected indent is the column of this token.
  745. if (options.indentChar === ' ') {
  746. offsetInfo.expectedIndent = Math.max(0, token.loc.start.column + expectedBaseIndent - actualIndent)
  747. } else {
  748. // In hard-tabs mode, it cannot align tokens strictly, so use one additional offset.
  749. // But the additional offset isn't needed if it's at the beginning of the line.
  750. offsetInfo.expectedIndent = expectedBaseIndent + (token === tokens[0] ? 0 : 1)
  751. }
  752. baseline.add(token)
  753. } else if (baseline.has(offsetInfo.baseToken)) {
  754. // The base token is a baseline token on this line, so inherit it.
  755. offsetInfo.expectedIndent = offsets.get(offsetInfo.baseToken).expectedIndent
  756. baseline.add(token)
  757. } else {
  758. // Otherwise, set the expected indent of this line.
  759. offsetInfo.expectedIndent = expectedBaseIndent
  760. }
  761. }
  762. }
  763. // It does not validate preformatted tokens.
  764. if (preformattedTokens.has(firstToken)) {
  765. return
  766. }
  767. // Calculate the expected indents for comments.
  768. // It allows the same indent level with the previous line.
  769. const lastOffsetInfo = offsets.get(lastToken)
  770. const lastExpectedIndent = lastOffsetInfo && lastOffsetInfo.expectedIndent
  771. const commentOptionalExpectedIndents = getCommentExpectedIndents(firstToken, expectedIndent, lastExpectedIndent)
  772. // Validate.
  773. for (const comment of comments) {
  774. const commentExpectedIndents = getExpectedIndents([comment])
  775. const commentExpectedIndent =
  776. commentExpectedIndents
  777. ? commentExpectedIndents.expectedIndent
  778. : commentOptionalExpectedIndents[0]
  779. validateCore(comment, commentExpectedIndent, commentOptionalExpectedIndents)
  780. }
  781. validateCore(firstToken, expectedIndent)
  782. }
  783. // ------------------------------------------------------------------------------
  784. // Main
  785. // ------------------------------------------------------------------------------
  786. return processIgnores({
  787. VAttribute (node) {
  788. const keyToken = tokenStore.getFirstToken(node)
  789. const eqToken = tokenStore.getFirstToken(node, 1)
  790. if (eqToken != null) {
  791. setOffset(eqToken, 1, keyToken)
  792. const valueToken = tokenStore.getFirstToken(node, 2)
  793. if (valueToken != null) {
  794. setOffset(valueToken, 1, keyToken)
  795. }
  796. }
  797. },
  798. VElement (node) {
  799. if (!PREFORMATTED_ELEMENT_NAMES.includes(node.name)) {
  800. const isTopLevel = node.parent.type !== 'VElement'
  801. const offset = isTopLevel ? options.baseIndent : 1
  802. processNodeList(node.children.filter(isNotEmptyTextNode), node.startTag, node.endTag, offset, false)
  803. } else {
  804. const startTagToken = tokenStore.getFirstToken(node)
  805. const endTagToken = node.endTag && tokenStore.getFirstToken(node.endTag)
  806. setOffset(endTagToken, 0, startTagToken)
  807. setPreformattedTokens(node)
  808. }
  809. },
  810. VEndTag (node) {
  811. const openToken = tokenStore.getFirstToken(node)
  812. const closeToken = tokenStore.getLastToken(node)
  813. if (closeToken.type.endsWith('TagClose')) {
  814. setOffset(closeToken, options.closeBracket, openToken)
  815. }
  816. },
  817. VExpressionContainer (node) {
  818. if (node.expression != null && node.range[0] !== node.expression.range[0]) {
  819. const startQuoteToken = tokenStore.getFirstToken(node)
  820. const endQuoteToken = tokenStore.getLastToken(node)
  821. const childToken = tokenStore.getTokenAfter(startQuoteToken)
  822. setOffset(childToken, 1, startQuoteToken)
  823. setOffset(endQuoteToken, 0, startQuoteToken)
  824. }
  825. },
  826. VFilter (node) {
  827. const idToken = tokenStore.getFirstToken(node)
  828. const lastToken = tokenStore.getLastToken(node)
  829. if (isRightParen(lastToken)) {
  830. const leftParenToken = tokenStore.getTokenAfter(node.callee)
  831. setOffset(leftParenToken, 1, idToken)
  832. processNodeList(node.arguments, leftParenToken, lastToken, 1)
  833. }
  834. },
  835. VFilterSequenceExpression (node) {
  836. if (node.filters.length === 0) {
  837. return
  838. }
  839. const firstToken = tokenStore.getFirstToken(node)
  840. const tokens = []
  841. for (const filter of node.filters) {
  842. tokens.push(
  843. tokenStore.getTokenBefore(filter, isPipeOperator),
  844. tokenStore.getFirstToken(filter)
  845. )
  846. }
  847. setOffset(tokens, 1, firstToken)
  848. },
  849. VForExpression (node) {
  850. const firstToken = tokenStore.getFirstToken(node)
  851. const lastOfLeft = last(node.left) || firstToken
  852. const inToken = tokenStore.getTokenAfter(lastOfLeft, isNotRightParen)
  853. const rightToken = tokenStore.getFirstToken(node.right)
  854. if (isLeftParen(firstToken)) {
  855. const rightToken = tokenStore.getTokenAfter(lastOfLeft, isRightParen)
  856. processNodeList(node.left, firstToken, rightToken, 1)
  857. }
  858. setOffset(inToken, 1, firstToken)
  859. setOffset(rightToken, 1, inToken)
  860. },
  861. VOnExpression (node) {
  862. processNodeList(node.body, null, null, 0)
  863. },
  864. VStartTag (node) {
  865. const openToken = tokenStore.getFirstToken(node)
  866. const closeToken = tokenStore.getLastToken(node)
  867. processNodeList(
  868. node.attributes,
  869. openToken,
  870. null,
  871. options.attribute,
  872. options.alignAttributesVertically
  873. )
  874. if (closeToken != null && closeToken.type.endsWith('TagClose')) {
  875. setOffset(closeToken, options.closeBracket, openToken)
  876. }
  877. },
  878. VText (node) {
  879. const tokens = tokenStore.getTokens(node, isNotWhitespace)
  880. const firstTokenInfo = offsets.get(tokenStore.getFirstToken(node))
  881. for (const token of tokens) {
  882. offsets.set(token, Object.assign({}, firstTokenInfo))
  883. }
  884. },
  885. 'ArrayExpression, ArrayPattern' (node) {
  886. processNodeList(node.elements, tokenStore.getFirstToken(node), tokenStore.getLastToken(node), 1)
  887. },
  888. ArrowFunctionExpression (node) {
  889. const firstToken = tokenStore.getFirstToken(node)
  890. const secondToken = tokenStore.getTokenAfter(firstToken)
  891. const leftToken = node.async ? secondToken : firstToken
  892. const arrowToken = tokenStore.getTokenBefore(node.body, isArrow)
  893. if (node.async) {
  894. setOffset(secondToken, 1, firstToken)
  895. }
  896. if (isLeftParen(leftToken)) {
  897. const rightToken = tokenStore.getTokenAfter(last(node.params) || leftToken, isRightParen)
  898. processNodeList(node.params, leftToken, rightToken, 1)
  899. }
  900. setOffset(arrowToken, 1, firstToken)
  901. processMaybeBlock(node.body, firstToken)
  902. },
  903. 'AssignmentExpression, AssignmentPattern, BinaryExpression, LogicalExpression' (node) {
  904. const leftToken = getChainHeadToken(node)
  905. const opToken = tokenStore.getTokenAfter(node.left, isNotRightParen)
  906. const rightToken = tokenStore.getTokenAfter(opToken)
  907. const prevToken = tokenStore.getTokenBefore(leftToken)
  908. const shouldIndent = (
  909. prevToken == null ||
  910. prevToken.loc.end.line === leftToken.loc.start.line ||
  911. isBeginningOfElement(leftToken, node)
  912. )
  913. setOffset([opToken, rightToken], shouldIndent ? 1 : 0, leftToken)
  914. },
  915. 'AwaitExpression, RestElement, SpreadElement, UnaryExpression' (node) {
  916. const firstToken = tokenStore.getFirstToken(node)
  917. const nextToken = tokenStore.getTokenAfter(firstToken)
  918. setOffset(nextToken, 1, firstToken)
  919. },
  920. 'BlockStatement, ClassBody' (node) {
  921. processNodeList(node.body, tokenStore.getFirstToken(node), tokenStore.getLastToken(node), 1)
  922. },
  923. 'BreakStatement, ContinueStatement, ReturnStatement, ThrowStatement' (node) {
  924. if (node.argument != null || node.label != null) {
  925. const firstToken = tokenStore.getFirstToken(node)
  926. const nextToken = tokenStore.getTokenAfter(firstToken)
  927. setOffset(nextToken, 1, firstToken)
  928. }
  929. },
  930. CallExpression (node) {
  931. const firstToken = tokenStore.getFirstToken(node)
  932. const rightToken = tokenStore.getLastToken(node)
  933. const leftToken = tokenStore.getTokenAfter(node.callee, isLeftParen)
  934. setOffset(leftToken, 1, firstToken)
  935. processNodeList(node.arguments, leftToken, rightToken, 1)
  936. },
  937. CatchClause (node) {
  938. const firstToken = tokenStore.getFirstToken(node)
  939. const bodyToken = tokenStore.getFirstToken(node.body)
  940. if (node.param != null) {
  941. const leftToken = tokenStore.getTokenAfter(firstToken)
  942. const rightToken = tokenStore.getTokenAfter(node.param)
  943. setOffset(leftToken, 1, firstToken)
  944. processNodeList([node.param], leftToken, rightToken, 1)
  945. }
  946. setOffset(bodyToken, 0, firstToken)
  947. },
  948. 'ClassDeclaration, ClassExpression' (node) {
  949. const firstToken = tokenStore.getFirstToken(node)
  950. const bodyToken = tokenStore.getFirstToken(node.body)
  951. if (node.id != null) {
  952. setOffset(tokenStore.getFirstToken(node.id), 1, firstToken)
  953. }
  954. if (node.superClass != null) {
  955. const extendsToken = tokenStore.getTokenAfter(node.id || firstToken)
  956. const superClassToken = tokenStore.getTokenAfter(extendsToken)
  957. setOffset(extendsToken, 1, firstToken)
  958. setOffset(superClassToken, 1, extendsToken)
  959. }
  960. setOffset(bodyToken, 0, firstToken)
  961. },
  962. ConditionalExpression (node) {
  963. const prevToken = tokenStore.getTokenBefore(node)
  964. const firstToken = tokenStore.getFirstToken(node)
  965. const questionToken = tokenStore.getTokenAfter(node.test, isNotRightParen)
  966. const consequentToken = tokenStore.getTokenAfter(questionToken)
  967. const colonToken = tokenStore.getTokenAfter(node.consequent, isNotRightParen)
  968. const alternateToken = tokenStore.getTokenAfter(colonToken)
  969. const isFlat =
  970. prevToken &&
  971. prevToken.loc.end.line !== node.loc.start.line &&
  972. node.test.loc.end.line === node.consequent.loc.start.line
  973. if (isFlat) {
  974. setOffset([questionToken, consequentToken, colonToken, alternateToken], 0, firstToken)
  975. } else {
  976. setOffset([questionToken, colonToken], 1, firstToken)
  977. setOffset([consequentToken, alternateToken], 1, questionToken)
  978. }
  979. },
  980. DoWhileStatement (node) {
  981. const doToken = tokenStore.getFirstToken(node)
  982. const whileToken = tokenStore.getTokenAfter(node.body, isNotRightParen)
  983. const leftToken = tokenStore.getTokenAfter(whileToken)
  984. const testToken = tokenStore.getTokenAfter(leftToken)
  985. const lastToken = tokenStore.getLastToken(node)
  986. const rightToken = isSemicolon(lastToken) ? tokenStore.getTokenBefore(lastToken) : lastToken
  987. processMaybeBlock(node.body, doToken)
  988. setOffset(whileToken, 0, doToken)
  989. setOffset(leftToken, 1, whileToken)
  990. setOffset(testToken, 1, leftToken)
  991. setOffset(rightToken, 0, leftToken)
  992. },
  993. ExportAllDeclaration (node) {
  994. const tokens = tokenStore.getTokens(node)
  995. const firstToken = tokens.shift()
  996. if (isSemicolon(last(tokens))) {
  997. tokens.pop()
  998. }
  999. setOffset(tokens, 1, firstToken)
  1000. },
  1001. ExportDefaultDeclaration (node) {
  1002. const exportToken = tokenStore.getFirstToken(node)
  1003. const defaultToken = tokenStore.getFirstToken(node, 1)
  1004. const declarationToken = getFirstAndLastTokens(node.declaration).firstToken
  1005. setOffset([defaultToken, declarationToken], 1, exportToken)
  1006. },
  1007. ExportNamedDeclaration (node) {
  1008. const exportToken = tokenStore.getFirstToken(node)
  1009. if (node.declaration) {
  1010. // export var foo = 1;
  1011. const declarationToken = tokenStore.getFirstToken(node, 1)
  1012. setOffset(declarationToken, 1, exportToken)
  1013. } else {
  1014. // export {foo, bar}; or export {foo, bar} from "mod";
  1015. const leftParenToken = tokenStore.getFirstToken(node, 1)
  1016. const rightParenToken = tokenStore.getLastToken(node, isRightBrace)
  1017. setOffset(leftParenToken, 0, exportToken)
  1018. processNodeList(node.specifiers, leftParenToken, rightParenToken, 1)
  1019. const maybeFromToken = tokenStore.getTokenAfter(rightParenToken)
  1020. if (maybeFromToken != null && sourceCode.getText(maybeFromToken) === 'from') {
  1021. const fromToken = maybeFromToken
  1022. const nameToken = tokenStore.getTokenAfter(fromToken)
  1023. setOffset([fromToken, nameToken], 1, exportToken)
  1024. }
  1025. }
  1026. },
  1027. ExportSpecifier (node) {
  1028. const tokens = tokenStore.getTokens(node)
  1029. const firstToken = tokens.shift()
  1030. setOffset(tokens, 1, firstToken)
  1031. },
  1032. 'ForInStatement, ForOfStatement' (node) {
  1033. const forToken = tokenStore.getFirstToken(node)
  1034. const leftParenToken = tokenStore.getTokenAfter(forToken)
  1035. const leftToken = tokenStore.getTokenAfter(leftParenToken)
  1036. const inToken = tokenStore.getTokenAfter(leftToken, isNotRightParen)
  1037. const rightToken = tokenStore.getTokenAfter(inToken)
  1038. const rightParenToken = tokenStore.getTokenBefore(node.body, isNotLeftParen)
  1039. setOffset(leftParenToken, 1, forToken)
  1040. setOffset(leftToken, 1, leftParenToken)
  1041. setOffset(inToken, 1, leftToken)
  1042. setOffset(rightToken, 1, leftToken)
  1043. setOffset(rightParenToken, 0, leftParenToken)
  1044. processMaybeBlock(node.body, forToken)
  1045. },
  1046. ForStatement (node) {
  1047. const forToken = tokenStore.getFirstToken(node)
  1048. const leftParenToken = tokenStore.getTokenAfter(forToken)
  1049. const rightParenToken = tokenStore.getTokenBefore(node.body, isNotLeftParen)
  1050. setOffset(leftParenToken, 1, forToken)
  1051. processNodeList([node.init, node.test, node.update], leftParenToken, rightParenToken, 1)
  1052. processMaybeBlock(node.body, forToken)
  1053. },
  1054. 'FunctionDeclaration, FunctionExpression' (node) {
  1055. const firstToken = tokenStore.getFirstToken(node)
  1056. if (isLeftParen(firstToken)) {
  1057. // Methods.
  1058. const leftToken = firstToken
  1059. const rightToken = tokenStore.getTokenAfter(last(node.params) || leftToken, isRightParen)
  1060. const bodyToken = tokenStore.getFirstToken(node.body)
  1061. processNodeList(node.params, leftToken, rightToken, 1)
  1062. setOffset(bodyToken, 0, tokenStore.getFirstToken(node.parent))
  1063. } else {
  1064. // Normal functions.
  1065. const functionToken = node.async ? tokenStore.getTokenAfter(firstToken) : firstToken
  1066. const starToken = node.generator ? tokenStore.getTokenAfter(functionToken) : null
  1067. const idToken = node.id && tokenStore.getFirstToken(node.id)
  1068. const leftToken = tokenStore.getTokenAfter(idToken || starToken || functionToken)
  1069. const rightToken = tokenStore.getTokenAfter(last(node.params) || leftToken, isRightParen)
  1070. const bodyToken = tokenStore.getFirstToken(node.body)
  1071. if (node.async) {
  1072. setOffset(functionToken, 0, firstToken)
  1073. }
  1074. if (node.generator) {
  1075. setOffset(starToken, 1, firstToken)
  1076. }
  1077. if (node.id != null) {
  1078. setOffset(idToken, 1, firstToken)
  1079. }
  1080. setOffset(leftToken, 1, firstToken)
  1081. processNodeList(node.params, leftToken, rightToken, 1)
  1082. setOffset(bodyToken, 0, firstToken)
  1083. }
  1084. },
  1085. IfStatement (node) {
  1086. const ifToken = tokenStore.getFirstToken(node)
  1087. const ifLeftParenToken = tokenStore.getTokenAfter(ifToken)
  1088. const ifRightParenToken = tokenStore.getTokenBefore(node.consequent, isRightParen)
  1089. setOffset(ifLeftParenToken, 1, ifToken)
  1090. setOffset(ifRightParenToken, 0, ifLeftParenToken)
  1091. processMaybeBlock(node.consequent, ifToken)
  1092. if (node.alternate != null) {
  1093. const elseToken = tokenStore.getTokenAfter(node.consequent, isNotRightParen)
  1094. setOffset(elseToken, 0, ifToken)
  1095. processMaybeBlock(node.alternate, elseToken)
  1096. }
  1097. },
  1098. ImportDeclaration (node) {
  1099. const firstSpecifier = node.specifiers[0]
  1100. const secondSpecifier = node.specifiers[1]
  1101. const importToken = tokenStore.getFirstToken(node)
  1102. const hasSemi = tokenStore.getLastToken(node).value === ';'
  1103. const tokens = [] // tokens to one indent
  1104. if (!firstSpecifier) {
  1105. // There are 2 patterns:
  1106. // import "foo"
  1107. // import {} from "foo"
  1108. const secondToken = tokenStore.getFirstToken(node, 1)
  1109. if (isLeftBrace(secondToken)) {
  1110. setOffset(
  1111. [secondToken, tokenStore.getTokenAfter(secondToken)],
  1112. 0,
  1113. importToken
  1114. )
  1115. tokens.push(
  1116. tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from
  1117. tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"
  1118. )
  1119. } else {
  1120. tokens.push(tokenStore.getLastToken(node, hasSemi ? 1 : 0))
  1121. }
  1122. } else if (firstSpecifier.type === 'ImportDefaultSpecifier') {
  1123. if (secondSpecifier && secondSpecifier.type === 'ImportNamespaceSpecifier') {
  1124. // There is a pattern:
  1125. // import Foo, * as foo from "foo"
  1126. tokens.push(
  1127. tokenStore.getFirstToken(firstSpecifier), // Foo
  1128. tokenStore.getTokenAfter(firstSpecifier), // comma
  1129. tokenStore.getFirstToken(secondSpecifier), // *
  1130. tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from
  1131. tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"
  1132. )
  1133. } else {
  1134. // There are 3 patterns:
  1135. // import Foo from "foo"
  1136. // import Foo, {} from "foo"
  1137. // import Foo, {a} from "foo"
  1138. const idToken = tokenStore.getFirstToken(firstSpecifier)
  1139. const nextToken = tokenStore.getTokenAfter(firstSpecifier)
  1140. if (isComma(nextToken)) {
  1141. const leftBrace = tokenStore.getTokenAfter(nextToken)
  1142. const rightBrace = tokenStore.getLastToken(node, hasSemi ? 3 : 2)
  1143. setOffset([idToken, nextToken], 1, importToken)
  1144. setOffset(leftBrace, 0, idToken)
  1145. processNodeList(node.specifiers.slice(1), leftBrace, rightBrace, 1)
  1146. tokens.push(
  1147. tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from
  1148. tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"
  1149. )
  1150. } else {
  1151. tokens.push(
  1152. idToken,
  1153. nextToken, // from
  1154. tokenStore.getTokenAfter(nextToken) // "foo"
  1155. )
  1156. }
  1157. }
  1158. } else if (firstSpecifier.type === 'ImportNamespaceSpecifier') {
  1159. // There is a pattern:
  1160. // import * as foo from "foo"
  1161. tokens.push(
  1162. tokenStore.getFirstToken(firstSpecifier), // *
  1163. tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from
  1164. tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"
  1165. )
  1166. } else {
  1167. // There is a pattern:
  1168. // import {a} from "foo"
  1169. const leftBrace = tokenStore.getFirstToken(node, 1)
  1170. const rightBrace = tokenStore.getLastToken(node, hasSemi ? 3 : 2)
  1171. setOffset(leftBrace, 0, importToken)
  1172. processNodeList(node.specifiers, leftBrace, rightBrace, 1)
  1173. tokens.push(
  1174. tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from
  1175. tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"
  1176. )
  1177. }
  1178. setOffset(tokens, 1, importToken)
  1179. },
  1180. ImportSpecifier (node) {
  1181. if (node.local.range[0] !== node.imported.range[0]) {
  1182. const tokens = tokenStore.getTokens(node)
  1183. const firstToken = tokens.shift()
  1184. setOffset(tokens, 1, firstToken)
  1185. }
  1186. },
  1187. ImportNamespaceSpecifier (node) {
  1188. const tokens = tokenStore.getTokens(node)
  1189. const firstToken = tokens.shift()
  1190. setOffset(tokens, 1, firstToken)
  1191. },
  1192. LabeledStatement (node) {
  1193. const labelToken = tokenStore.getFirstToken(node)
  1194. const colonToken = tokenStore.getTokenAfter(labelToken)
  1195. const bodyToken = tokenStore.getTokenAfter(colonToken)
  1196. setOffset([colonToken, bodyToken], 1, labelToken)
  1197. },
  1198. 'MemberExpression, MetaProperty' (node) {
  1199. const objectToken = tokenStore.getFirstToken(node)
  1200. if (node.computed) {
  1201. const leftBracketToken = tokenStore.getTokenBefore(node.property, isLeftBracket)
  1202. const propertyToken = tokenStore.getTokenAfter(leftBracketToken)
  1203. const rightBracketToken = tokenStore.getTokenAfter(node.property, isRightBracket)
  1204. setOffset(leftBracketToken, 1, objectToken)
  1205. setOffset(propertyToken, 1, leftBracketToken)
  1206. setOffset(rightBracketToken, 0, leftBracketToken)
  1207. } else {
  1208. const dotToken = tokenStore.getTokenBefore(node.property)
  1209. const propertyToken = tokenStore.getTokenAfter(dotToken)
  1210. setOffset([dotToken, propertyToken], 1, objectToken)
  1211. }
  1212. },
  1213. 'MethodDefinition, Property' (node) {
  1214. const isMethod = (node.type === 'MethodDefinition' || node.method === true)
  1215. const prefixTokens = getPrefixTokens(node)
  1216. const hasPrefix = prefixTokens.length >= 1
  1217. for (let i = 1; i < prefixTokens.length; ++i) {
  1218. setOffset(prefixTokens[i], 0, prefixTokens[i - 1])
  1219. }
  1220. let lastKeyToken = null
  1221. if (node.computed) {
  1222. const keyLeftToken = tokenStore.getFirstToken(node, isLeftBracket)
  1223. const keyToken = tokenStore.getTokenAfter(keyLeftToken)
  1224. const keyRightToken = lastKeyToken = tokenStore.getTokenAfter(node.key, isRightBracket)
  1225. if (hasPrefix) {
  1226. setOffset(keyLeftToken, 0, last(prefixTokens))
  1227. }
  1228. setOffset(keyToken, 1, keyLeftToken)
  1229. setOffset(keyRightToken, 0, keyLeftToken)
  1230. } else {
  1231. const idToken = lastKeyToken = tokenStore.getFirstToken(node.key)
  1232. if (hasPrefix) {
  1233. setOffset(idToken, 0, last(prefixTokens))
  1234. }
  1235. }
  1236. if (isMethod) {
  1237. const leftParenToken = tokenStore.getTokenAfter(lastKeyToken)
  1238. setOffset(leftParenToken, 1, lastKeyToken)
  1239. } else if (!node.shorthand) {
  1240. const colonToken = tokenStore.getTokenAfter(lastKeyToken)
  1241. const valueToken = tokenStore.getTokenAfter(colonToken)
  1242. setOffset([colonToken, valueToken], 1, lastKeyToken)
  1243. }
  1244. },
  1245. NewExpression (node) {
  1246. const newToken = tokenStore.getFirstToken(node)
  1247. const calleeToken = tokenStore.getTokenAfter(newToken)
  1248. const rightToken = tokenStore.getLastToken(node)
  1249. const leftToken = isRightParen(rightToken)
  1250. ? tokenStore.getFirstTokenBetween(node.callee, rightToken, isLeftParen)
  1251. : null
  1252. setOffset(calleeToken, 1, newToken)
  1253. if (leftToken != null) {
  1254. setOffset(leftToken, 1, calleeToken)
  1255. processNodeList(node.arguments, leftToken, rightToken, 1)
  1256. }
  1257. },
  1258. 'ObjectExpression, ObjectPattern' (node) {
  1259. processNodeList(node.properties, tokenStore.getFirstToken(node), tokenStore.getLastToken(node), 1)
  1260. },
  1261. SequenceExpression (node) {
  1262. processNodeList(node.expressions, null, null, 0)
  1263. },
  1264. SwitchCase (node) {
  1265. const caseToken = tokenStore.getFirstToken(node)
  1266. if (node.test != null) {
  1267. const testToken = tokenStore.getTokenAfter(caseToken)
  1268. const colonToken = tokenStore.getTokenAfter(node.test, isNotRightParen)
  1269. setOffset([testToken, colonToken], 1, caseToken)
  1270. } else {
  1271. const colonToken = tokenStore.getTokenAfter(caseToken)
  1272. setOffset(colonToken, 1, caseToken)
  1273. }
  1274. if (node.consequent.length === 1 && node.consequent[0].type === 'BlockStatement') {
  1275. setOffset(tokenStore.getFirstToken(node.consequent[0]), 0, caseToken)
  1276. } else if (node.consequent.length >= 1) {
  1277. setOffset(tokenStore.getFirstToken(node.consequent[0]), 1, caseToken)
  1278. processNodeList(node.consequent, null, null, 0)
  1279. }
  1280. },
  1281. SwitchStatement (node) {
  1282. const switchToken = tokenStore.getFirstToken(node)
  1283. const leftParenToken = tokenStore.getTokenAfter(switchToken)
  1284. const discriminantToken = tokenStore.getTokenAfter(leftParenToken)
  1285. const leftBraceToken = tokenStore.getTokenAfter(node.discriminant, isLeftBrace)
  1286. const rightParenToken = tokenStore.getTokenBefore(leftBraceToken)
  1287. const rightBraceToken = tokenStore.getLastToken(node)
  1288. setOffset(leftParenToken, 1, switchToken)
  1289. setOffset(discriminantToken, 1, leftParenToken)
  1290. setOffset(rightParenToken, 0, leftParenToken)
  1291. setOffset(leftBraceToken, 0, switchToken)
  1292. processNodeList(node.cases, leftBraceToken, rightBraceToken, options.switchCase)
  1293. },
  1294. TaggedTemplateExpression (node) {
  1295. const tagTokens = getFirstAndLastTokens(node.tag, node.range[0])
  1296. const quasiToken = tokenStore.getTokenAfter(tagTokens.lastToken)
  1297. setOffset(quasiToken, 1, tagTokens.firstToken)
  1298. },
  1299. TemplateLiteral (node) {
  1300. const firstToken = tokenStore.getFirstToken(node)
  1301. const quasiTokens = node.quasis.slice(1).map(n => tokenStore.getFirstToken(n))
  1302. const expressionToken = node.quasis.slice(0, -1).map(n => tokenStore.getTokenAfter(n))
  1303. setOffset(quasiTokens, 0, firstToken)
  1304. setOffset(expressionToken, 1, firstToken)
  1305. },
  1306. TryStatement (node) {
  1307. const tryToken = tokenStore.getFirstToken(node)
  1308. const tryBlockToken = tokenStore.getFirstToken(node.block)
  1309. setOffset(tryBlockToken, 0, tryToken)
  1310. if (node.handler != null) {
  1311. const catchToken = tokenStore.getFirstToken(node.handler)
  1312. setOffset(catchToken, 0, tryToken)
  1313. }
  1314. if (node.finalizer != null) {
  1315. const finallyToken = tokenStore.getTokenBefore(node.finalizer)
  1316. const finallyBlockToken = tokenStore.getFirstToken(node.finalizer)
  1317. setOffset([finallyToken, finallyBlockToken], 0, tryToken)
  1318. }
  1319. },
  1320. UpdateExpression (node) {
  1321. const firstToken = tokenStore.getFirstToken(node)
  1322. const nextToken = tokenStore.getTokenAfter(firstToken)
  1323. setOffset(nextToken, 1, firstToken)
  1324. },
  1325. VariableDeclaration (node) {
  1326. processNodeList(node.declarations, tokenStore.getFirstToken(node), null, 1)
  1327. },
  1328. VariableDeclarator (node) {
  1329. if (node.init != null) {
  1330. const idToken = tokenStore.getFirstToken(node)
  1331. const eqToken = tokenStore.getTokenAfter(node.id)
  1332. const initToken = tokenStore.getTokenAfter(eqToken)
  1333. setOffset([eqToken, initToken], 1, idToken)
  1334. }
  1335. },
  1336. 'WhileStatement, WithStatement' (node) {
  1337. const firstToken = tokenStore.getFirstToken(node)
  1338. const leftParenToken = tokenStore.getTokenAfter(firstToken)
  1339. const rightParenToken = tokenStore.getTokenBefore(node.body, isRightParen)
  1340. setOffset(leftParenToken, 1, firstToken)
  1341. setOffset(rightParenToken, 0, leftParenToken)
  1342. processMaybeBlock(node.body, firstToken)
  1343. },
  1344. YieldExpression (node) {
  1345. if (node.argument != null) {
  1346. const yieldToken = tokenStore.getFirstToken(node)
  1347. setOffset(tokenStore.getTokenAfter(yieldToken), 1, yieldToken)
  1348. if (node.delegate) {
  1349. setOffset(tokenStore.getTokenAfter(yieldToken, 1), 1, yieldToken)
  1350. }
  1351. }
  1352. },
  1353. // Process semicolons.
  1354. ':statement' (node) {
  1355. const firstToken = tokenStore.getFirstToken(node)
  1356. const lastToken = tokenStore.getLastToken(node)
  1357. if (isSemicolon(lastToken) && firstToken !== lastToken) {
  1358. setOffset(lastToken, 0, firstToken)
  1359. }
  1360. // Set to the semicolon of the previous token for semicolon-free style.
  1361. // E.g.,
  1362. // foo
  1363. // ;[1,2,3].forEach(f)
  1364. const info = offsets.get(firstToken)
  1365. const prevToken = tokenStore.getTokenBefore(firstToken)
  1366. if (info != null && isSemicolon(prevToken) && prevToken.loc.end.line === firstToken.loc.start.line) {
  1367. offsets.set(prevToken, info)
  1368. }
  1369. },
  1370. // Process parentheses.
  1371. // `:expression` does not match with MetaProperty and TemplateLiteral as a bug: https://github.com/estools/esquery/pull/59
  1372. ':expression, MetaProperty, TemplateLiteral' (node) {
  1373. let leftToken = tokenStore.getTokenBefore(node)
  1374. let rightToken = tokenStore.getTokenAfter(node)
  1375. let firstToken = tokenStore.getFirstToken(node)
  1376. while (isLeftParen(leftToken) && isRightParen(rightToken)) {
  1377. setOffset(firstToken, 1, leftToken)
  1378. setOffset(rightToken, 0, leftToken)
  1379. firstToken = leftToken
  1380. leftToken = tokenStore.getTokenBefore(leftToken)
  1381. rightToken = tokenStore.getTokenAfter(rightToken)
  1382. }
  1383. },
  1384. // Ignore tokens of unknown nodes.
  1385. '*:exit' (node) {
  1386. if (!KNOWN_NODES.has(node.type)) {
  1387. ignore(node)
  1388. }
  1389. },
  1390. // Top-level process.
  1391. Program (node) {
  1392. const firstToken = node.tokens[0]
  1393. const isScriptTag = (
  1394. firstToken != null &&
  1395. firstToken.type === 'Punctuator' &&
  1396. firstToken.value === '<script>'
  1397. )
  1398. const baseIndent =
  1399. isScriptTag ? (options.indentSize * options.baseIndent) : 0
  1400. for (const statement of node.body) {
  1401. processTopLevelNode(statement, baseIndent)
  1402. }
  1403. },
  1404. "VElement[parent.type!='VElement']" (node) {
  1405. processTopLevelNode(node, 0)
  1406. },
  1407. // Do validation.
  1408. ":matches(Program, VElement[parent.type!='VElement']):exit" (node) {
  1409. let comments = []
  1410. let tokensOnSameLine = []
  1411. let isBesideMultilineToken = false
  1412. let lastValidatedToken = null
  1413. // Validate indentation of tokens.
  1414. for (const token of tokenStore.getTokens(node, ITERATION_OPTS)) {
  1415. if (tokensOnSameLine.length === 0 || tokensOnSameLine[0].loc.start.line === token.loc.start.line) {
  1416. // This is on the same line (or the first token).
  1417. tokensOnSameLine.push(token)
  1418. } else if (tokensOnSameLine.every(isComment)) {
  1419. // New line is detected, but the all tokens of the previous line are comment.
  1420. // Comment lines are adjusted to the next code line.
  1421. comments.push(tokensOnSameLine[0])
  1422. isBesideMultilineToken = last(tokensOnSameLine).loc.end.line === token.loc.start.line
  1423. tokensOnSameLine = [token]
  1424. } else {
  1425. // New line is detected, so validate the tokens.
  1426. if (!isBesideMultilineToken) {
  1427. validate(tokensOnSameLine, comments, lastValidatedToken)
  1428. lastValidatedToken = tokensOnSameLine[0]
  1429. }
  1430. isBesideMultilineToken = last(tokensOnSameLine).loc.end.line === token.loc.start.line
  1431. tokensOnSameLine = [token]
  1432. comments = []
  1433. }
  1434. }
  1435. if (tokensOnSameLine.length >= 1 && tokensOnSameLine.some(isNotComment)) {
  1436. validate(tokensOnSameLine, comments, lastValidatedToken)
  1437. }
  1438. }
  1439. })
  1440. }