| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545 | /** * @author Toru Nagashima <https://github.com/mysticatea> * See LICENSE file in root directory for full license. */'use strict'// ------------------------------------------------------------------------------// Requirements// ------------------------------------------------------------------------------const assert = require('assert')// ------------------------------------------------------------------------------// Helpers// ------------------------------------------------------------------------------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', 'VForExpression', 'VIdentifier', 'VLiteral', 'VOnExpression', 'VStartTag', 'VText'])const LT_CHAR = /[\r\n\u2028\u2029]/const LINES = /[^\r\n\u2028\u2029]+(?:$|\r\n|[\r\n\u2028\u2029])/gconst BLOCK_COMMENT_PREFIX = /^\s*\*/const TRIVIAL_PUNCTUATOR = /^[(){}[\],;]$//** * Normalize options. * @param {number|"tab"|undefined} type The type of indentation. * @param {Object} options Other options. * @param {Object} defaultOptions The default value of options. * @returns {{indentChar:" "|"\t",indentSize:number,baseIndent:number,attribute:number,closeBracket:number,switchCase:number,alignAttributesVertically:boolean,ignores:string[]}} Normalized options. */function parseOptions (type, options, defaultOptions) {  const ret = Object.assign({    indentChar: ' ',    indentSize: 2,    baseIndent: 0,    attribute: 1,    closeBracket: 0,    switchCase: 0,    alignAttributesVertically: true,    ignores: []  }, defaultOptions)  if (Number.isSafeInteger(type)) {    ret.indentSize = type  } else if (type === 'tab') {    ret.indentChar = '\t'    ret.indentSize = 1  }  if (Number.isSafeInteger(options.baseIndent)) {    ret.baseIndent = options.baseIndent  }  if (Number.isSafeInteger(options.attribute)) {    ret.attribute = options.attribute  }  if (Number.isSafeInteger(options.closeBracket)) {    ret.closeBracket = options.closeBracket  }  if (Number.isSafeInteger(options.switchCase)) {    ret.switchCase = options.switchCase  }  if (options.alignAttributesVertically != null) {    ret.alignAttributesVertically = options.alignAttributesVertically  }  if (options.ignores != null) {    ret.ignores = options.ignores  }  return ret}/** * Check whether the given token is an arrow. * @param {Token} token The token to check. * @returns {boolean} `true` if the token is an arrow. */function isArrow (token) {  return token != null && token.type === 'Punctuator' && token.value === '=>'}/** * Check whether the given token is a left parenthesis. * @param {Token} token The token to check. * @returns {boolean} `true` if the token is a left parenthesis. */function isLeftParen (token) {  return token != null && token.type === 'Punctuator' && token.value === '('}/** * Check whether the given token is a left parenthesis. * @param {Token} token The token to check. * @returns {boolean} `false` if the token is a left parenthesis. */function isNotLeftParen (token) {  return token != null && (token.type !== 'Punctuator' || token.value !== '(')}/** * Check whether the given token is a right parenthesis. * @param {Token} token The token to check. * @returns {boolean} `true` if the token is a right parenthesis. */function isRightParen (token) {  return token != null && token.type === 'Punctuator' && token.value === ')'}/** * Check whether the given token is a right parenthesis. * @param {Token} token The token to check. * @returns {boolean} `false` if the token is a right parenthesis. */function isNotRightParen (token) {  return token != null && (token.type !== 'Punctuator' || token.value !== ')')}/** * Check whether the given token is a left brace. * @param {Token} token The token to check. * @returns {boolean} `true` if the token is a left brace. */function isLeftBrace (token) {  return token != null && token.type === 'Punctuator' && token.value === '{'}/** * Check whether the given token is a right brace. * @param {Token} token The token to check. * @returns {boolean} `true` if the token is a right brace. */function isRightBrace (token) {  return token != null && token.type === 'Punctuator' && token.value === '}'}/** * Check whether the given token is a left bracket. * @param {Token} token The token to check. * @returns {boolean} `true` if the token is a left bracket. */function isLeftBracket (token) {  return token != null && token.type === 'Punctuator' && token.value === '['}/** * Check whether the given token is a right bracket. * @param {Token} token The token to check. * @returns {boolean} `true` if the token is a right bracket. */function isRightBracket (token) {  return token != null && token.type === 'Punctuator' && token.value === ']'}/** * Check whether the given token is a semicolon. * @param {Token} token The token to check. * @returns {boolean} `true` if the token is a semicolon. */function isSemicolon (token) {  return token != null && token.type === 'Punctuator' && token.value === ';'}/** * Check whether the given token is a comma. * @param {Token} token The token to check. * @returns {boolean} `true` if the token is a comma. */function isComma (token) {  return token != null && token.type === 'Punctuator' && token.value === ','}/** * Check whether the given token is a whitespace. * @param {Token} token The token to check. * @returns {boolean} `true` if the token is a whitespace. */function isNotWhitespace (token) {  return token != null && token.type !== 'HTMLWhitespace'}/** * Check whether the given token is a comment. * @param {Token} token The token to check. * @returns {boolean} `true` if the token is a comment. */function isComment (token) {  return token != null && (token.type === 'Block' || token.type === 'Line' || token.type === 'Shebang' || token.type.endsWith('Comment'))}/** * Check whether the given token is a comment. * @param {Token} token The token to check. * @returns {boolean} `false` if the token is a comment. */function isNotComment (token) {  return token != null && token.type !== 'Block' && token.type !== 'Line' && token.type !== 'Shebang' && !token.type.endsWith('Comment')}/** * Get the last element. * @param {Array} xs The array to get the last element. * @returns {any|undefined} The last element or undefined. */function last (xs) {  return xs.length === 0 ? undefined : xs[xs.length - 1]}/** * Check whether the node is at the beginning of line. * @param {Node} node The node to check. * @param {number} index The index of the node in the nodes. * @param {Node[]} nodes The array of nodes. * @returns {boolean} `true` if the node is at the beginning of line. */function isBeginningOfLine (node, index, nodes) {  if (node != null) {    for (let i = index - 1; i >= 0; --i) {      const prevNode = nodes[i]      if (prevNode == null) {        continue      }      return node.loc.start.line !== prevNode.loc.end.line    }  }  return false}/** * Check whether a given token is a closing token which triggers unindent. * @param {Token} token The token to check. * @returns {boolean} `true` if the token is a closing token. */function isClosingToken (token) {  return token != null && (    token.type === 'HTMLEndTagOpen' ||    token.type === 'VExpressionEnd' ||    (      token.type === 'Punctuator' &&      (        token.value === ')' ||        token.value === '}' ||        token.value === ']'      )    )  )}/** * Check whether a given token is trivial or not. * @param {Token} token The token to check. * @returns {boolean} `true` if the token is trivial. */function isTrivialToken (token) {  return token != null && (    (token.type === 'Punctuator' && TRIVIAL_PUNCTUATOR.test(token.value)) ||    token.type === 'HTMLTagOpen' ||    token.type === 'HTMLEndTagOpen' ||    token.type === 'HTMLTagClose' ||    token.type === 'HTMLSelfClosingTagClose'  )}/** * Creates AST event handlers for html-indent. * * @param {RuleContext} context The rule context. * @param {TokenStore} tokenStore The token store object to get tokens. * @param {Object} defaultOptions The default value of options. * @returns {object} AST event handlers. */module.exports.defineVisitor = function create (context, tokenStore, defaultOptions) {  const options = parseOptions(context.options[0], context.options[1] || {}, defaultOptions)  const sourceCode = context.getSourceCode()  const offsets = new Map()  /**   * Set offset to the given tokens.   * @param {Token|Token[]} token The token to set.   * @param {number} offset The offset of the tokens.   * @param {Token} baseToken The token of the base offset.   * @param {boolean} [trivial=false] The flag for trivial tokens.   * @returns {void}   */  function setOffset (token, offset, baseToken) {    assert(baseToken != null, "'baseToken' should not be null or undefined.")    if (Array.isArray(token)) {      for (const t of token) {        offsets.set(t, {          baseToken,          offset,          baseline: false,          expectedIndent: undefined        })      }    } else {      offsets.set(token, {        baseToken,        offset,        baseline: false,        expectedIndent: undefined      })    }  }  /**   * Set baseline flag to the given token.   * @param {Token} token The token to set.   * @returns {void}   */  function setBaseline (token, hardTabAdditional) {    const offsetInfo = offsets.get(token)    if (offsetInfo != null) {      offsetInfo.baseline = true    }  }  /**   * Get the first and last tokens of the given node.   * If the node is parenthesized, this gets the outermost parentheses.   * @param {Node} node The node to get.   * @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.   * @returns {{firstToken:Token,lastToken:Token}} The gotten tokens.   */  function getFirstAndLastTokens (node, borderOffset) {    borderOffset |= 0    let firstToken = tokenStore.getFirstToken(node)    let lastToken = tokenStore.getLastToken(node)    // Get the outermost left parenthesis if it's parenthesized.    let t, u    while ((t = tokenStore.getTokenBefore(firstToken)) != null && (u = tokenStore.getTokenAfter(lastToken)) != null && isLeftParen(t) && isRightParen(u) && t.range[0] >= borderOffset) {      firstToken = t      lastToken = u    }    return { firstToken, lastToken }  }  /**   * Process the given node list.   * The first node is offsetted from the given left token.   * Rest nodes are adjusted to the first node.   * @param {Node[]} nodeList The node to process.   * @param {Node|null} leftToken The left parenthesis token.   * @param {Node|null} rightToken The right parenthesis token.   * @param {number} offset The offset to set.   * @param {Node} [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.   * @returns {void}   */  function processNodeList (nodeList, leftToken, rightToken, offset, alignVertically) {    let t    if (nodeList.length >= 1) {      let lastToken = leftToken      const alignTokens = []      for (let i = 0; i < nodeList.length; ++i) {        const node = nodeList[i]        if (node == null) {          // Holes of an array.          continue        }        const elementTokens = getFirstAndLastTokens(node, lastToken != null ? lastToken.range[1] : 0)        // Collect related tokens.        // Commas between this and the previous, and the first token of this node.        if (lastToken != null) {          t = lastToken          while ((t = tokenStore.getTokenAfter(t)) != null && t.range[1] <= elementTokens.firstToken.range[0]) {            alignTokens.push(t)          }        }        alignTokens.push(elementTokens.firstToken)        // Save the last token to find tokens between the next token.        lastToken = elementTokens.lastToken      }      // Check trailing commas.      if (rightToken != null && lastToken != null) {        t = lastToken        while ((t = tokenStore.getTokenAfter(t)) != null && t.range[1] <= rightToken.range[0]) {          alignTokens.push(t)        }      }      // Set offsets.      const baseToken = alignTokens.shift()      if (baseToken != null) {        // Set offset to the first token.        if (leftToken != null) {          setOffset(baseToken, offset, leftToken)        }        // Set baseline.        if (nodeList.some(isBeginningOfLine)) {          setBaseline(baseToken)        }        if (alignVertically === false) {          // Align tokens relatively to the left token.          setOffset(alignTokens, offset, leftToken)        } else {          // Align the rest tokens to the first token.          setOffset(alignTokens, 0, baseToken)        }      }    }    if (rightToken != null) {      setOffset(rightToken, 0, leftToken)    }  }  /**   * Process the given node as body.   * The body node maybe a block statement or an expression node.   * @param {Node} node The body node to process.   * @param {Token} baseToken The base token.   * @returns {void}   */  function processMaybeBlock (node, baseToken) {    const firstToken = getFirstAndLastTokens(node).firstToken    setOffset(firstToken, isLeftBrace(firstToken) ? 0 : 1, baseToken)  }  /**   * Collect prefix tokens of the given property.   * The prefix includes `async`, `get`, `set`, `static`, and `*`.   * @param {Property|MethodDefinition} node The property node to collect prefix tokens.   */  function getPrefixTokens (node) {    const prefixes = []    let token = tokenStore.getFirstToken(node)    while (token != null && token.range[1] <= node.key.range[0]) {      prefixes.push(token)      token = tokenStore.getTokenAfter(token)    }    while (isLeftParen(last(prefixes)) || isLeftBracket(last(prefixes))) {      prefixes.pop()    }    return prefixes  }  /**   * Find the head of chaining nodes.   * @param {Node} node The start node to find the head.   * @returns {Token} The head token of the chain.   */  function getChainHeadToken (node) {    const type = node.type    while (node.parent.type === type) {      node = node.parent    }    return tokenStore.getFirstToken(node)  }  /**   * Check whether a given token is the first token of:   *   * - ExpressionStatement   * - VExpressionContainer   * - A parameter of CallExpression/NewExpression   * - An element of ArrayExpression   * - An expression of SequenceExpression   *   * @param {Token} token The token to check.   * @param {Node} belongingNode The node that the token is belonging to.   * @returns {boolean} `true` if the token is the first token of an element.   */  function isBeginningOfElement (token, belongingNode) {    let node = belongingNode    while (node != null) {      const parent = node.parent      const t = parent && parent.type      if (t != null && (t.endsWith('Statement') || t.endsWith('Declaration'))) {        return parent.range[0] === token.range[0]      }      if (t === 'VExpressionContainer') {        return node.range[0] === token.range[0]      }      if (t === 'CallExpression' || t === 'NewExpression') {        const openParen = tokenStore.getTokenAfter(parent.callee, isNotRightParen)        return parent.arguments.some(param =>          getFirstAndLastTokens(param, openParen.range[1]).firstToken.range[0] === token.range[0]        )      }      if (t === 'ArrayExpression') {        return parent.elements.some(element =>          element != null &&          getFirstAndLastTokens(element).firstToken.range[0] === token.range[0]        )      }      if (t === 'SequenceExpression') {        return parent.expressions.some(expr =>          getFirstAndLastTokens(expr).firstToken.range[0] === token.range[0]        )      }      node = parent    }    return false  }  /**   * Set the base indentation to a given top-level AST node.   * @param {Node} node The node to set.   * @param {number} expectedIndent The number of expected indent.   * @returns {void}   */  function processTopLevelNode (node, expectedIndent) {    const token = tokenStore.getFirstToken(node)    const offsetInfo = offsets.get(token)    if (offsetInfo != null) {      offsetInfo.expectedIndent = expectedIndent    } else {      offsets.set(token, { baseToken: null, offset: 0, baseline: false, expectedIndent })    }  }  /**   * Ignore all tokens of the given node.   * @param {Node} node The node to ignore.   * @returns {void}   */  function ignore (node) {    for (const token of tokenStore.getTokens(node)) {      offsets.delete(token)    }  }  /**   * Define functions to ignore nodes into the given visitor.   * @param {Object} visitor The visitor to define functions to ignore nodes.   * @returns {Object} The visitor.   */  function processIgnores (visitor) {    for (const ignorePattern of options.ignores) {      const key = `${ignorePattern}:exit`      if (visitor.hasOwnProperty(key)) {        const handler = visitor[key]        visitor[key] = function (node) {          const ret = handler.apply(this, arguments)          ignore(node)          return ret        }      } else {        visitor[key] = ignore      }    }    return visitor  }  /**   * Calculate correct indentation of the line of the given tokens.   * @param {Token[]} tokens Tokens which are on the same line.   * @returns {number} Correct indentation. If it failed to calculate then `Number.MAX_SAFE_INTEGER`.   */  function getExpectedIndent (tokens) {    const trivial = isTrivialToken(tokens[0])    let expectedIndent = Number.MAX_SAFE_INTEGER    for (let i = 0; i < tokens.length; ++i) {      const token = tokens[i]      const offsetInfo = offsets.get(token)      // If the first token is not trivial then ignore trivial following tokens.      if (offsetInfo != null && (trivial || !isTrivialToken(token))) {        if (offsetInfo.expectedIndent != null) {          expectedIndent = Math.min(expectedIndent, offsetInfo.expectedIndent)        } else {          const baseOffsetInfo = offsets.get(offsetInfo.baseToken)          if (baseOffsetInfo != null && baseOffsetInfo.expectedIndent != null && (i === 0 || !baseOffsetInfo.baseline)) {            expectedIndent = Math.min(expectedIndent, baseOffsetInfo.expectedIndent + offsetInfo.offset * options.indentSize)            if (baseOffsetInfo.baseline) {              break            }          }        }      }    }    return expectedIndent  }  /**   * Get the text of the indentation part of the line which the given token is on.   * @param {Token} firstToken The first token on a line.   * @returns {string} The text of indentation part.   */  function getIndentText (firstToken) {    const text = sourceCode.text    let i = firstToken.range[0] - 1    while (i >= 0 && !LT_CHAR.test(text[i])) {      i -= 1    }    return text.slice(i + 1, firstToken.range[0])  }  /**   * Define the function which fixes the problem.   * @param {Token} token The token to fix.   * @param {number} actualIndent The number of actual indentaion.   * @param {number} expectedIndent The number of expected indentation.   * @returns {Function} The defined function.   */  function defineFix (token, actualIndent, expectedIndent) {    if (token.type === 'Block' && token.loc.start.line !== token.loc.end.line) {      // Fix indentation in multiline block comments.      const lines = sourceCode.getText(token).match(LINES)      const firstLine = lines.shift()      if (lines.every(l => BLOCK_COMMENT_PREFIX.test(l))) {        return fixer => {          const range = [token.range[0] - actualIndent, token.range[1]]          const indent = options.indentChar.repeat(expectedIndent)          return fixer.replaceTextRange(            range,            `${indent}${firstLine}${lines.map(l => l.replace(BLOCK_COMMENT_PREFIX, `${indent} *`)).join('')}`          )        }      }    }    return fixer => {      const range = [token.range[0] - actualIndent, token.range[0]]      const indent = options.indentChar.repeat(expectedIndent)      return fixer.replaceTextRange(range, indent)    }  }  /**   * Validate the given token with the pre-calculated expected indentation.   * @param {Token} token The token to validate.   * @param {number} expectedIndent The expected indentation.   * @param {number|undefined} optionalExpectedIndent The optional expected indentation.   * @returns {void}   */  function validateCore (token, expectedIndent, optionalExpectedIndent) {    const line = token.loc.start.line    const indentText = getIndentText(token)    // If there is no line terminator after the `<script>` start tag,    // `indentText` contains non-whitespace characters.    // In that case, do nothing in order to prevent removing the `<script>` tag.    if (indentText.trim() !== '') {      return    }    const actualIndent = token.loc.start.column    const unit = (options.indentChar === '\t' ? 'tab' : 'space')    for (let i = 0; i < indentText.length; ++i) {      if (indentText[i] !== options.indentChar) {        context.report({          loc: {            start: { line, column: i },            end: { line, column: i + 1 }          },          message: 'Expected {{expected}} character, but found {{actual}} character.',          data: {            expected: JSON.stringify(options.indentChar),            actual: JSON.stringify(indentText[i])          },          fix: defineFix(token, actualIndent, expectedIndent)        })        return      }    }    if (actualIndent !== expectedIndent && (optionalExpectedIndent === undefined || actualIndent !== optionalExpectedIndent)) {      context.report({        loc: {          start: { line, column: 0 },          end: { line, column: actualIndent }        },        message: 'Expected indentation of {{expectedIndent}} {{unit}}{{expectedIndentPlural}} but found {{actualIndent}} {{unit}}{{actualIndentPlural}}.',        data: {          expectedIndent,          actualIndent,          unit,          expectedIndentPlural: (expectedIndent === 1) ? '' : 's',          actualIndentPlural: (actualIndent === 1) ? '' : 's'        },        fix: defineFix(token, actualIndent, expectedIndent)      })    }  }  /**   * Get the expected indent of comments.   * @param {Token|null} nextToken The next token of comments.   * @param {number|undefined} nextExpectedIndent The expected indent of the next token.   * @param {number|undefined} lastExpectedIndent The expected indent of the last token.   * @returns {{primary:number|undefined,secondary:number|undefined}}   */  function getCommentExpectedIndents (nextToken, nextExpectedIndent, lastExpectedIndent) {    if (typeof lastExpectedIndent === 'number' && isClosingToken(nextToken)) {      if (nextExpectedIndent === lastExpectedIndent) {        // For solo comment. E.g.,        // <div>        //    <!-- comment -->        // </div>        return {          primary: nextExpectedIndent + options.indentSize,          secondary: undefined        }      }      // For last comment. E.g.,      // <div>      //    <div></div>      //    <!-- comment -->      // </div>      return { primary: lastExpectedIndent, secondary: nextExpectedIndent }    }    // Adjust to next normally. E.g.,    // <div>    //    <!-- comment -->    //    <div></div>    // </div>    return { primary: nextExpectedIndent, secondary: undefined }  }  /**   * Validate indentation of the line that the given tokens are on.   * @param {Token[]} tokens The tokens on the same line to validate.   * @param {Token[]} comments The comments which are on the immediately previous lines of the tokens.   * @param {Token|null} lastToken The last validated token. Comments can adjust to the token.   * @returns {void}   */  function validate (tokens, comments, lastToken) {    // Calculate and save expected indentation.    const firstToken = tokens[0]    const actualIndent = firstToken.loc.start.column    const expectedIndent = getExpectedIndent(tokens)    if (expectedIndent === Number.MAX_SAFE_INTEGER) {      return    }    // Debug log    // console.log('line', firstToken.loc.start.line, '=', { actualIndent, expectedIndent }, 'from:')    // for (const token of tokens) {    //   const offsetInfo = offsets.get(token)    //   if (offsetInfo == null) {    //     console.log('    ', JSON.stringify(sourceCode.getText(token)), 'is unknown.')    //   } else if (offsetInfo.expectedIndent != null) {    //     console.log('    ', JSON.stringify(sourceCode.getText(token)), 'is fixed at', offsetInfo.expectedIndent, '.')    //   } else {    //     const baseOffsetInfo = offsets.get(offsetInfo.baseToken)    //     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, ')')    //   }    // }    // Save.    const baseline = new Set()    for (const token of tokens) {      const offsetInfo = offsets.get(token)      if (offsetInfo != null) {        if (offsetInfo.baseline) {          // This is a baseline token, so the expected indent is the column of this token.          if (options.indentChar === ' ') {            offsetInfo.expectedIndent = Math.max(0, token.loc.start.column + expectedIndent - actualIndent)          } else {            // In hard-tabs mode, it cannot align tokens strictly, so use one additional offset.            // But the additional offset isn't needed if it's at the beginning of the line.            offsetInfo.expectedIndent = expectedIndent + (token === tokens[0] ? 0 : 1)          }          baseline.add(token)        } else if (baseline.has(offsetInfo.baseToken)) {          // The base token is a baseline token on this line, so inherit it.          offsetInfo.expectedIndent = offsets.get(offsetInfo.baseToken).expectedIndent          baseline.add(token)        } else {          // Otherwise, set the expected indent of this line.          offsetInfo.expectedIndent = expectedIndent        }      }    }    // Calculate the expected indents for comments.    // It allows the same indent level with the previous line.    const lastOffsetInfo = offsets.get(lastToken)    const lastExpectedIndent = lastOffsetInfo && lastOffsetInfo.expectedIndent    const commentExpectedIndents = getCommentExpectedIndents(firstToken, expectedIndent, lastExpectedIndent)    // Validate.    for (const comment of comments) {      validateCore(comment, commentExpectedIndents.primary, commentExpectedIndents.secondary)    }    validateCore(firstToken, expectedIndent)  }  // ------------------------------------------------------------------------------  // Main  // ------------------------------------------------------------------------------  return processIgnores({    VAttribute (node) {      const keyToken = tokenStore.getFirstToken(node)      const eqToken = tokenStore.getFirstToken(node, 1)      if (eqToken != null) {        setOffset(eqToken, 1, keyToken)        const valueToken = tokenStore.getFirstToken(node, 2)        if (valueToken != null) {          setOffset(valueToken, 1, keyToken)        }      }    },    VElement (node) {      const startTagToken = tokenStore.getFirstToken(node)      const endTagToken = node.endTag && tokenStore.getFirstToken(node.endTag)      if (node.name !== 'pre') {        const childTokens = node.children.map(n => tokenStore.getFirstToken(n))        setOffset(childTokens, 1, startTagToken)      }      setOffset(endTagToken, 0, startTagToken)    },    VEndTag (node) {      const openToken = tokenStore.getFirstToken(node)      const closeToken = tokenStore.getLastToken(node)      if (closeToken.type.endsWith('TagClose')) {        setOffset(closeToken, options.closeBracket, openToken)      }    },    VExpressionContainer (node) {      if (node.expression != null && node.range[0] !== node.expression.range[0]) {        const startQuoteToken = tokenStore.getFirstToken(node)        const endQuoteToken = tokenStore.getLastToken(node)        const childToken = tokenStore.getFirstToken(node.expression)        setOffset(childToken, 1, startQuoteToken)        setOffset(endQuoteToken, 0, startQuoteToken)      }    },    VForExpression (node) {      const firstToken = tokenStore.getFirstToken(node)      const lastOfLeft = last(node.left) || firstToken      const inToken = tokenStore.getTokenAfter(lastOfLeft, isNotRightParen)      const rightToken = tokenStore.getFirstToken(node.right)      if (isLeftParen(firstToken)) {        const rightToken = tokenStore.getTokenAfter(lastOfLeft, isRightParen)        processNodeList(node.left, firstToken, rightToken, 1)      }      setOffset(inToken, 1, firstToken)      setOffset(rightToken, 1, inToken)    },    VOnExpression (node) {      processNodeList(node.body, null, null, 0)    },    VStartTag (node) {      const openToken = tokenStore.getFirstToken(node)      const closeToken = tokenStore.getLastToken(node)      processNodeList(        node.attributes,        openToken,        null,        options.attribute,        options.alignAttributesVertically      )      if (closeToken != null && closeToken.type.endsWith('TagClose')) {        setOffset(closeToken, options.closeBracket, openToken)      }    },    VText (node) {      const tokens = tokenStore.getTokens(node, isNotWhitespace)      const firstTokenInfo = offsets.get(tokenStore.getFirstToken(node))      for (const token of tokens) {        offsets.set(token, firstTokenInfo)      }    },    'ArrayExpression, ArrayPattern' (node) {      processNodeList(node.elements, tokenStore.getFirstToken(node), tokenStore.getLastToken(node), 1)    },    ArrowFunctionExpression (node) {      const firstToken = tokenStore.getFirstToken(node)      const secondToken = tokenStore.getTokenAfter(firstToken)      const leftToken = node.async ? secondToken : firstToken      const arrowToken = tokenStore.getTokenBefore(node.body, isArrow)      if (node.async) {        setOffset(secondToken, 1, firstToken)      }      if (isLeftParen(leftToken)) {        const rightToken = tokenStore.getTokenAfter(last(node.params) || leftToken, isRightParen)        processNodeList(node.params, leftToken, rightToken, 1)      }      setOffset(arrowToken, 1, firstToken)      processMaybeBlock(node.body, firstToken)    },    'AssignmentExpression, AssignmentPattern, BinaryExpression, LogicalExpression' (node) {      const leftToken = getChainHeadToken(node)      const opToken = tokenStore.getTokenAfter(node.left, isNotRightParen)      const rightToken = tokenStore.getTokenAfter(opToken)      const prevToken = tokenStore.getTokenBefore(leftToken)      const shouldIndent = (        prevToken == null ||        prevToken.loc.end.line === leftToken.loc.start.line ||        isBeginningOfElement(leftToken, node)      )      setOffset([opToken, rightToken], shouldIndent ? 1 : 0, leftToken)    },    'AwaitExpression, RestElement, SpreadElement, UnaryExpression' (node) {      const firstToken = tokenStore.getFirstToken(node)      const nextToken = tokenStore.getTokenAfter(firstToken)      setOffset(nextToken, 1, firstToken)    },    'BlockStatement, ClassBody' (node) {      processNodeList(node.body, tokenStore.getFirstToken(node), tokenStore.getLastToken(node), 1)    },    'BreakStatement, ContinueStatement, ReturnStatement, ThrowStatement' (node) {      if (node.argument != null || node.label != null) {        const firstToken = tokenStore.getFirstToken(node)        const nextToken = tokenStore.getTokenAfter(firstToken)        setOffset(nextToken, 1, firstToken)      }    },    CallExpression (node) {      const firstToken = tokenStore.getFirstToken(node)      const rightToken = tokenStore.getLastToken(node)      const leftToken = tokenStore.getTokenAfter(node.callee, isLeftParen)      setOffset(leftToken, 1, firstToken)      processNodeList(node.arguments, leftToken, rightToken, 1)    },    CatchClause (node) {      const firstToken = tokenStore.getFirstToken(node)      const bodyToken = tokenStore.getFirstToken(node.body)      if (node.param != null) {        const leftToken = tokenStore.getTokenAfter(firstToken)        const rightToken = tokenStore.getTokenAfter(node.param)        setOffset(leftToken, 1, firstToken)        processNodeList([node.param], leftToken, rightToken, 1)      }      setOffset(bodyToken, 0, firstToken)    },    'ClassDeclaration, ClassExpression' (node) {      const firstToken = tokenStore.getFirstToken(node)      const bodyToken = tokenStore.getFirstToken(node.body)      if (node.id != null) {        setOffset(tokenStore.getFirstToken(node.id), 1, firstToken)      }      if (node.superClass != null) {        const extendsToken = tokenStore.getTokenAfter(node.id || firstToken)        const superClassToken = tokenStore.getTokenAfter(extendsToken)        setOffset(extendsToken, 1, firstToken)        setOffset(superClassToken, 1, extendsToken)      }      setOffset(bodyToken, 0, firstToken)    },    ConditionalExpression (node) {      const firstToken = tokenStore.getFirstToken(node)      const questionToken = tokenStore.getTokenAfter(node.test, isNotRightParen)      const consequentToken = tokenStore.getTokenAfter(questionToken)      const colonToken = tokenStore.getTokenAfter(node.consequent, isNotRightParen)      const alternateToken = tokenStore.getTokenAfter(colonToken)      const isFlat = (node.test.loc.end.line === node.consequent.loc.start.line)      if (isFlat) {        setOffset([questionToken, consequentToken, colonToken, alternateToken], 0, firstToken)      } else {        setOffset([questionToken, colonToken], 1, firstToken)        setOffset([consequentToken, alternateToken], 1, questionToken)      }    },    DoWhileStatement (node) {      const doToken = tokenStore.getFirstToken(node)      const whileToken = tokenStore.getTokenAfter(node.body, isNotRightParen)      const leftToken = tokenStore.getTokenAfter(whileToken)      const testToken = tokenStore.getTokenAfter(leftToken)      const lastToken = tokenStore.getLastToken(node)      const rightToken = isSemicolon(lastToken) ? tokenStore.getTokenBefore(lastToken) : lastToken      processMaybeBlock(node.body, doToken)      setOffset(whileToken, 0, doToken)      setOffset(leftToken, 1, whileToken)      setOffset(testToken, 1, leftToken)      setOffset(rightToken, 0, leftToken)    },    ExportAllDeclaration (node) {      const tokens = tokenStore.getTokens(node)      const firstToken = tokens.shift()      if (isSemicolon(last(tokens))) {        tokens.pop()      }      setOffset(tokens, 1, firstToken)    },    ExportDefaultDeclaration (node) {      const exportToken = tokenStore.getFirstToken(node)      const defaultToken = tokenStore.getFirstToken(node, 1)      const declarationToken = getFirstAndLastTokens(node.declaration).firstToken      setOffset([defaultToken, declarationToken], 1, exportToken)    },    ExportNamedDeclaration (node) {      const exportToken = tokenStore.getFirstToken(node)      if (node.declaration) {        // export var foo = 1;        const declarationToken = tokenStore.getFirstToken(node, 1)        setOffset(declarationToken, 1, exportToken)      } else {        // export {foo, bar}; or export {foo, bar} from "mod";        const leftParenToken = tokenStore.getFirstToken(node, 1)        const rightParenToken = tokenStore.getLastToken(node, isRightBrace)        setOffset(leftParenToken, 0, exportToken)        processNodeList(node.specifiers, leftParenToken, rightParenToken, 1)        const maybeFromToken = tokenStore.getTokenAfter(rightParenToken)        if (maybeFromToken != null && sourceCode.getText(maybeFromToken) === 'from') {          const fromToken = maybeFromToken          const nameToken = tokenStore.getTokenAfter(fromToken)          setOffset([fromToken, nameToken], 1, exportToken)        }      }    },    ExportSpecifier (node) {      const tokens = tokenStore.getTokens(node)      const firstToken = tokens.shift()      setOffset(tokens, 1, firstToken)    },    'ForInStatement, ForOfStatement' (node) {      const forToken = tokenStore.getFirstToken(node)      const leftParenToken = tokenStore.getTokenAfter(forToken)      const leftToken = tokenStore.getTokenAfter(leftParenToken)      const inToken = tokenStore.getTokenAfter(leftToken, isNotRightParen)      const rightToken = tokenStore.getTokenAfter(inToken)      const rightParenToken = tokenStore.getTokenBefore(node.body, isNotLeftParen)      setOffset(leftParenToken, 1, forToken)      setOffset(leftToken, 1, leftParenToken)      setOffset(inToken, 1, leftToken)      setOffset(rightToken, 1, leftToken)      setOffset(rightParenToken, 0, leftParenToken)      processMaybeBlock(node.body, forToken)    },    ForStatement (node) {      const forToken = tokenStore.getFirstToken(node)      const leftParenToken = tokenStore.getTokenAfter(forToken)      const rightParenToken = tokenStore.getTokenBefore(node.body, isNotLeftParen)      setOffset(leftParenToken, 1, forToken)      processNodeList([node.init, node.test, node.update], leftParenToken, rightParenToken, 1)      setOffset(rightParenToken, 0, leftParenToken)      processMaybeBlock(node.body, forToken)    },    'FunctionDeclaration, FunctionExpression' (node) {      const firstToken = tokenStore.getFirstToken(node)      if (isLeftParen(firstToken)) {        // Methods.        const leftToken = firstToken        const rightToken = tokenStore.getTokenAfter(last(node.params) || leftToken, isRightParen)        const bodyToken = tokenStore.getFirstToken(node.body)        processNodeList(node.params, leftToken, rightToken, 1)        setOffset(bodyToken, 0, tokenStore.getFirstToken(node.parent))      } else {        // Normal functions.        const functionToken = node.async ? tokenStore.getTokenAfter(firstToken) : firstToken        const starToken = node.generator ? tokenStore.getTokenAfter(functionToken) : null        const idToken = node.id && tokenStore.getFirstToken(node.id)        const leftToken = tokenStore.getTokenAfter(idToken || starToken || functionToken)        const rightToken = tokenStore.getTokenAfter(last(node.params) || leftToken, isRightParen)        const bodyToken = tokenStore.getFirstToken(node.body)        if (node.async) {          setOffset(functionToken, 0, firstToken)        }        if (node.generator) {          setOffset(starToken, 1, firstToken)        }        if (node.id != null) {          setOffset(idToken, 1, firstToken)        }        setOffset(leftToken, 1, firstToken)        processNodeList(node.params, leftToken, rightToken, 1)        setOffset(bodyToken, 0, firstToken)      }    },    IfStatement (node) {      const ifToken = tokenStore.getFirstToken(node)      const ifLeftParenToken = tokenStore.getTokenAfter(ifToken)      const ifRightParenToken = tokenStore.getTokenBefore(node.consequent, isRightParen)      setOffset(ifLeftParenToken, 1, ifToken)      setOffset(ifRightParenToken, 0, ifLeftParenToken)      processMaybeBlock(node.consequent, ifToken)      if (node.alternate != null) {        const elseToken = tokenStore.getTokenAfter(node.consequent, isNotRightParen)        setOffset(elseToken, 0, ifToken)        processMaybeBlock(node.alternate, elseToken)      }    },    ImportDeclaration (node) {      const firstSpecifier = node.specifiers[0]      const secondSpecifier = node.specifiers[1]      const importToken = tokenStore.getFirstToken(node)      const hasSemi = tokenStore.getLastToken(node).value === ';'      const tokens = [] // tokens to one indent      if (!firstSpecifier) {        // There are 2 patterns:        //     import "foo"        //     import {} from "foo"        const secondToken = tokenStore.getFirstToken(node, 1)        if (isLeftBrace(secondToken)) {          setOffset(            [secondToken, tokenStore.getTokenAfter(secondToken)],            0,            importToken          )          tokens.push(            tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from            tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"          )        } else {          tokens.push(tokenStore.getLastToken(node, hasSemi ? 1 : 0))        }      } else if (firstSpecifier.type === 'ImportDefaultSpecifier') {        if (secondSpecifier && secondSpecifier.type === 'ImportNamespaceSpecifier') {          // There is a pattern:          //     import Foo, * as foo from "foo"          tokens.push(            tokenStore.getFirstToken(firstSpecifier), // Foo            tokenStore.getTokenAfter(firstSpecifier), // comma            tokenStore.getFirstToken(secondSpecifier), // *            tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from            tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"          )        } else {          // There are 3 patterns:          //     import Foo from "foo"          //     import Foo, {} from "foo"          //     import Foo, {a} from "foo"          const idToken = tokenStore.getFirstToken(firstSpecifier)          const nextToken = tokenStore.getTokenAfter(firstSpecifier)          if (isComma(nextToken)) {            const leftBrace = tokenStore.getTokenAfter(nextToken)            const rightBrace = tokenStore.getLastToken(node, hasSemi ? 3 : 2)            setOffset([idToken, nextToken], 1, importToken)            setOffset(leftBrace, 0, idToken)            processNodeList(node.specifiers.slice(1), leftBrace, rightBrace, 1)            tokens.push(              tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from              tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"            )          } else {            tokens.push(              idToken,              nextToken, // from              tokenStore.getTokenAfter(nextToken) // "foo"            )          }        }      } else if (firstSpecifier.type === 'ImportNamespaceSpecifier') {        // There is a pattern:        //     import * as foo from "foo"        tokens.push(          tokenStore.getFirstToken(firstSpecifier), // *          tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from          tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"        )      } else {        // There is a pattern:        //     import {a} from "foo"        const leftBrace = tokenStore.getFirstToken(node, 1)        const rightBrace = tokenStore.getLastToken(node, hasSemi ? 3 : 2)        setOffset(leftBrace, 0, importToken)        processNodeList(node.specifiers, leftBrace, rightBrace, 1)        tokens.push(          tokenStore.getLastToken(node, hasSemi ? 2 : 1), // from          tokenStore.getLastToken(node, hasSemi ? 1 : 0) // "foo"        )      }      setOffset(tokens, 1, importToken)    },    ImportSpecifier (node) {      if (node.local.range[0] !== node.imported.range[0]) {        const tokens = tokenStore.getTokens(node)        const firstToken = tokens.shift()        setOffset(tokens, 1, firstToken)      }    },    ImportNamespaceSpecifier (node) {      const tokens = tokenStore.getTokens(node)      const firstToken = tokens.shift()      setOffset(tokens, 1, firstToken)    },    LabeledStatement (node) {      const labelToken = tokenStore.getFirstToken(node)      const colonToken = tokenStore.getTokenAfter(labelToken)      const bodyToken = tokenStore.getTokenAfter(colonToken)      setOffset([colonToken, bodyToken], 1, labelToken)    },    'MemberExpression, MetaProperty' (node) {      const objectToken = tokenStore.getFirstToken(node)      if (node.computed) {        const leftBracketToken = tokenStore.getTokenBefore(node.property, isLeftBracket)        const propertyToken = tokenStore.getTokenAfter(leftBracketToken)        const rightBracketToken = tokenStore.getTokenAfter(node.property, isRightBracket)        setOffset(leftBracketToken, 1, objectToken)        setOffset(propertyToken, 1, leftBracketToken)        setOffset(rightBracketToken, 0, leftBracketToken)      } else {        const dotToken = tokenStore.getTokenBefore(node.property)        const propertyToken = tokenStore.getTokenAfter(dotToken)        setOffset([dotToken, propertyToken], 1, objectToken)      }    },    'MethodDefinition, Property' (node) {      const isMethod = (node.type === 'MethodDefinition' || node.method === true)      const prefixTokens = getPrefixTokens(node)      const hasPrefix = prefixTokens.length >= 1      for (let i = 1; i < prefixTokens.length; ++i) {        setOffset(prefixTokens[i], 0, prefixTokens[i - 1])      }      let lastKeyToken = null      if (node.computed) {        const keyLeftToken = tokenStore.getFirstToken(node, isLeftBracket)        const keyToken = tokenStore.getTokenAfter(keyLeftToken)        const keyRightToken = lastKeyToken = tokenStore.getTokenAfter(node.key, isRightBracket)        if (hasPrefix) {          setOffset(keyLeftToken, 0, last(prefixTokens))        }        setOffset(keyToken, 1, keyLeftToken)        setOffset(keyRightToken, 0, keyLeftToken)      } else {        const idToken = lastKeyToken = tokenStore.getFirstToken(node.key)        if (hasPrefix) {          setOffset(idToken, 0, last(prefixTokens))        }      }      if (isMethod) {        const leftParenToken = tokenStore.getTokenAfter(lastKeyToken)        setOffset(leftParenToken, 1, lastKeyToken)      } else if (!node.shorthand) {        const colonToken = tokenStore.getTokenAfter(lastKeyToken)        const valueToken = tokenStore.getTokenAfter(colonToken)        setOffset([colonToken, valueToken], 1, lastKeyToken)      }    },    NewExpression (node) {      const newToken = tokenStore.getFirstToken(node)      const calleeToken = tokenStore.getTokenAfter(newToken)      const rightToken = tokenStore.getLastToken(node)      const leftToken = isRightParen(rightToken)        ? tokenStore.getFirstTokenBetween(node.callee, rightToken, isLeftParen)        : null      setOffset(calleeToken, 1, newToken)      if (leftToken != null) {        setOffset(leftToken, 1, calleeToken)        processNodeList(node.arguments, leftToken, rightToken, 1)      }    },    'ObjectExpression, ObjectPattern' (node) {      processNodeList(node.properties, tokenStore.getFirstToken(node), tokenStore.getLastToken(node), 1)    },    SequenceExpression (node) {      processNodeList(node.expressions, null, null, 0)    },    SwitchCase (node) {      const caseToken = tokenStore.getFirstToken(node)      if (node.test != null) {        const testToken = tokenStore.getTokenAfter(caseToken)        const colonToken = tokenStore.getTokenAfter(node.test, isNotRightParen)        setOffset([testToken, colonToken], 1, caseToken)      } else {        const colonToken = tokenStore.getTokenAfter(caseToken)        setOffset(colonToken, 1, caseToken)      }      if (node.consequent.length === 1 && node.consequent[0].type === 'BlockStatement') {        setOffset(tokenStore.getFirstToken(node.consequent[0]), 0, caseToken)      } else if (node.consequent.length >= 1) {        setOffset(tokenStore.getFirstToken(node.consequent[0]), 1, caseToken)        processNodeList(node.consequent, null, null, 0)      }    },    SwitchStatement (node) {      const switchToken = tokenStore.getFirstToken(node)      const leftParenToken = tokenStore.getTokenAfter(switchToken)      const discriminantToken = tokenStore.getTokenAfter(leftParenToken)      const leftBraceToken = tokenStore.getTokenAfter(node.discriminant, isLeftBrace)      const rightParenToken = tokenStore.getTokenBefore(leftBraceToken)      const rightBraceToken = tokenStore.getLastToken(node)      setOffset(leftParenToken, 1, switchToken)      setOffset(discriminantToken, 1, leftParenToken)      setOffset(rightParenToken, 0, leftParenToken)      setOffset(leftBraceToken, 0, switchToken)      processNodeList(node.cases, leftBraceToken, rightBraceToken, options.switchCase)    },    TaggedTemplateExpression (node) {      const tagTokens = getFirstAndLastTokens(node.tag, node.range[0])      const quasiToken = tokenStore.getTokenAfter(tagTokens.lastToken)      setOffset(quasiToken, 1, tagTokens.firstToken)    },    TemplateLiteral (node) {      const firstToken = tokenStore.getFirstToken(node)      const quasiTokens = node.quasis.slice(1).map(n => tokenStore.getFirstToken(n))      const expressionToken = node.quasis.slice(0, -1).map(n => tokenStore.getTokenAfter(n))      setOffset(quasiTokens, 0, firstToken)      setOffset(expressionToken, 1, firstToken)    },    TryStatement (node) {      const tryToken = tokenStore.getFirstToken(node)      const tryBlockToken = tokenStore.getFirstToken(node.block)      setOffset(tryBlockToken, 0, tryToken)      if (node.handler != null) {        const catchToken = tokenStore.getFirstToken(node.handler)        setOffset(catchToken, 0, tryToken)      }      if (node.finalizer != null) {        const finallyToken = tokenStore.getTokenBefore(node.finalizer)        const finallyBlockToken = tokenStore.getFirstToken(node.finalizer)        setOffset([finallyToken, finallyBlockToken], 0, tryToken)      }    },    UpdateExpression (node) {      const firstToken = tokenStore.getFirstToken(node)      const nextToken = tokenStore.getTokenAfter(firstToken)      setOffset(nextToken, 1, firstToken)    },    VariableDeclaration (node) {      processNodeList(node.declarations, tokenStore.getFirstToken(node), null, 1)    },    VariableDeclarator (node) {      if (node.init != null) {        const idToken = tokenStore.getFirstToken(node)        const eqToken = tokenStore.getTokenAfter(node.id)        const initToken = tokenStore.getTokenAfter(eqToken)        setOffset([eqToken, initToken], 1, idToken)      }    },    'WhileStatement, WithStatement' (node) {      const firstToken = tokenStore.getFirstToken(node)      const leftParenToken = tokenStore.getTokenAfter(firstToken)      const rightParenToken = tokenStore.getTokenBefore(node.body, isRightParen)      setOffset(leftParenToken, 1, firstToken)      setOffset(rightParenToken, 0, leftParenToken)      processMaybeBlock(node.body, firstToken)    },    YieldExpression (node) {      if (node.argument != null) {        const yieldToken = tokenStore.getFirstToken(node)        setOffset(tokenStore.getTokenAfter(yieldToken), 1, yieldToken)        if (node.delegate) {          setOffset(tokenStore.getTokenAfter(yieldToken, 1), 1, yieldToken)        }      }    },    // Process semicolons.    ':statement' (node) {      const firstToken = tokenStore.getFirstToken(node)      const lastToken = tokenStore.getLastToken(node)      if (isSemicolon(lastToken) && firstToken !== lastToken) {        setOffset(lastToken, 0, firstToken)      }      // Set to the semicolon of the previous token for semicolon-free style.      // E.g.,      //   foo      //   ;[1,2,3].forEach(f)      const info = offsets.get(firstToken)      const prevToken = tokenStore.getTokenBefore(firstToken)      if (info != null && isSemicolon(prevToken) && prevToken.loc.end.line === firstToken.loc.start.line) {        offsets.set(prevToken, info)      }    },    // Process parentheses.    // `:expression` does not match with MetaProperty and TemplateLiteral as a bug: https://github.com/estools/esquery/pull/59    ':expression, MetaProperty, TemplateLiteral' (node) {      let leftToken = tokenStore.getTokenBefore(node)      let rightToken = tokenStore.getTokenAfter(node)      let firstToken = tokenStore.getFirstToken(node)      while (isLeftParen(leftToken) && isRightParen(rightToken)) {        setOffset(firstToken, 1, leftToken)        setOffset(rightToken, 0, leftToken)        firstToken = leftToken        leftToken = tokenStore.getTokenBefore(leftToken)        rightToken = tokenStore.getTokenAfter(rightToken)      }    },    // Ignore tokens of unknown nodes.    '*:exit' (node) {      if (!KNOWN_NODES.has(node.type)) {        ignore(node)      }    },    // Top-level process.    Program (node) {      const firstToken = node.tokens[0]      const isScriptTag = (        firstToken != null &&        firstToken.type === 'Punctuator' &&        firstToken.value === '<script>'      )      const baseIndent =        isScriptTag ? (options.indentSize * options.baseIndent) : 0      for (const statement of node.body) {        processTopLevelNode(statement, baseIndent)      }    },    "VElement[parent.type!='VElement']" (node) {      processTopLevelNode(node, 0)    },    // Do validation.    ":matches(Program, VElement[parent.type!='VElement']):exit" (node) {      let comments = []      let tokensOnSameLine = []      let isBesideMultilineToken = false      let lastValidatedToken = null      // Validate indentation of tokens.      for (const token of tokenStore.getTokens(node, { includeComments: true, filter: isNotWhitespace })) {        if (tokensOnSameLine.length === 0 || tokensOnSameLine[0].loc.start.line === token.loc.start.line) {          // This is on the same line (or the first token).          tokensOnSameLine.push(token)        } else if (tokensOnSameLine.every(isComment)) {          // New line is detected, but the all tokens of the previous line are comment.          // Comment lines are adjusted to the next code line.          comments.push(tokensOnSameLine[0])          isBesideMultilineToken = last(tokensOnSameLine).loc.end.line === token.loc.start.line          tokensOnSameLine = [token]        } else {          // New line is detected, so validate the tokens.          if (!isBesideMultilineToken) {            validate(tokensOnSameLine, comments, lastValidatedToken)            lastValidatedToken = tokensOnSameLine[0]          }          isBesideMultilineToken = last(tokensOnSameLine).loc.end.line === token.loc.start.line          tokensOnSameLine = [token]          comments = []        }      }      if (tokensOnSameLine.length >= 1 && tokensOnSameLine.some(isNotComment)) {        validate(tokensOnSameLine, comments, lastValidatedToken)      }    }  })}
 |