From e57fd5574fa0c65788ac66f2415c4ad0dfd86c47 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Fri, 17 Jan 2020 02:25:23 +0900 Subject: [PATCH 1/9] Make the indent rules supports TypeScript --- lib/utils/indent-common.js | 875 +++++------ lib/utils/indent-ts.js | 1304 +++++++++++++++++ lib/utils/indent-utils.js | 113 ++ package.json | 2 +- tests/fixtures/script-indent/ignore-01.vue | 19 + tests/fixtures/script-indent/ignore-02.vue | 19 + .../ts-abstract-class-property-01.vue | 22 + .../ts-abstract-class-property-02.vue | 17 + .../ts-abstract-method-definition-01.vue | 30 + .../script-indent/ts-as-expression-01.vue | 6 + .../ts-call-signature-declaration-01.vue | 13 + .../ts-call-signature-declaration-02.vue | 13 + .../script-indent/ts-class-declaration-01.vue | 11 + .../script-indent/ts-class-declaration-02.vue | 16 + .../script-indent/ts-class-declaration-03.vue | 26 + .../script-indent/ts-class-declaration-04.vue | 26 + .../script-indent/ts-class-declaration-05.vue | 14 + .../script-indent/ts-class-property-01.vue | 15 + .../script-indent/ts-class-property-02.vue | 17 + .../script-indent/ts-class-property-03.vue | 36 + .../script-indent/ts-conditional-type-01.vue | 10 + .../script-indent/ts-conditional-type-02.vue | 10 + .../script-indent/ts-conditional-type-03.vue | 17 + .../script-indent/ts-constructor-type-01.vue | 21 + .../script-indent/ts-declare-function-01.vue | 12 + .../script-indent/ts-declare-function-02.vue | 12 + .../script-indent/ts-declare-function-03.vue | 12 + .../script-indent/ts-declare-function-04.vue | 16 + tests/fixtures/script-indent/ts-enum-01.vue | 9 + tests/fixtures/script-indent/ts-enum-02.vue | 9 + tests/fixtures/script-indent/ts-enum-03.vue | 12 + tests/fixtures/script-indent/ts-enum-04.vue | 7 + tests/fixtures/script-indent/ts-enum-05.vue | 8 + tests/fixtures/script-indent/ts-enum-06.vue | 10 + .../script-indent/ts-enum-member-01.vue | 15 + .../script-indent/ts-enum-member-02.vue | 26 + .../script-indent/ts-export-assignment-01.vue | 5 + .../script-indent/ts-export-assignment-02.vue | 13 + .../script-indent/ts-function-type-01.vue | 6 + .../script-indent/ts-function-type-02.vue | 17 + .../script-indent/ts-import-equal-01.vue | 6 + .../script-indent/ts-import-equal-02.vue | 19 + .../script-indent/ts-import-equal-03.vue | 13 + .../script-indent/ts-import-type-01.vue | 8 + .../script-indent/ts-import-type-02.vue | 18 + .../script-indent/ts-import-type-03.vue | 9 + .../ts-indexed-access-type-01.vue | 9 + .../ts-indexed-access-type-02.vue | 11 + tests/fixtures/script-indent/ts-infer-01.vue | 7 + .../ts-interface-declaration-01.vue | 7 + .../ts-interface-declaration-02.vue | 15 + .../ts-interface-declaration-03.vue | 17 + .../ts-interface-declaration-04.vue | 19 + .../ts-type-alias-seclaration-01.vue | 21 + .../script-indent/ts-type-annotation-01.vue | 9 + .../script-indent/ts-type-annotation-02.vue | 33 + .../script-indent/ts-type-annotation-03.vue | 28 + .../script-indent/ts-type-annotation-04.vue | 25 + .../script-indent/ts-type-annotation-05.vue | 21 + .../script-indent/ts-type-annotation-06.vue | 17 + .../ts-type-parameter-seclaration-01.vue | 17 + .../util-types/ast/ts-ast.ts | 1 + .../util-types/indent-helper.ts | 9 + typings/eslint-utils/index.d.ts | 23 +- 64 files changed, 2691 insertions(+), 512 deletions(-) create mode 100644 lib/utils/indent-ts.js create mode 100644 lib/utils/indent-utils.js create mode 100644 tests/fixtures/script-indent/ignore-01.vue create mode 100644 tests/fixtures/script-indent/ignore-02.vue create mode 100644 tests/fixtures/script-indent/ts-abstract-class-property-01.vue create mode 100644 tests/fixtures/script-indent/ts-abstract-class-property-02.vue create mode 100644 tests/fixtures/script-indent/ts-abstract-method-definition-01.vue create mode 100644 tests/fixtures/script-indent/ts-as-expression-01.vue create mode 100644 tests/fixtures/script-indent/ts-call-signature-declaration-01.vue create mode 100644 tests/fixtures/script-indent/ts-call-signature-declaration-02.vue create mode 100644 tests/fixtures/script-indent/ts-class-declaration-01.vue create mode 100644 tests/fixtures/script-indent/ts-class-declaration-02.vue create mode 100644 tests/fixtures/script-indent/ts-class-declaration-03.vue create mode 100644 tests/fixtures/script-indent/ts-class-declaration-04.vue create mode 100644 tests/fixtures/script-indent/ts-class-declaration-05.vue create mode 100644 tests/fixtures/script-indent/ts-class-property-01.vue create mode 100644 tests/fixtures/script-indent/ts-class-property-02.vue create mode 100644 tests/fixtures/script-indent/ts-class-property-03.vue create mode 100644 tests/fixtures/script-indent/ts-conditional-type-01.vue create mode 100644 tests/fixtures/script-indent/ts-conditional-type-02.vue create mode 100644 tests/fixtures/script-indent/ts-conditional-type-03.vue create mode 100644 tests/fixtures/script-indent/ts-constructor-type-01.vue create mode 100644 tests/fixtures/script-indent/ts-declare-function-01.vue create mode 100644 tests/fixtures/script-indent/ts-declare-function-02.vue create mode 100644 tests/fixtures/script-indent/ts-declare-function-03.vue create mode 100644 tests/fixtures/script-indent/ts-declare-function-04.vue create mode 100644 tests/fixtures/script-indent/ts-enum-01.vue create mode 100644 tests/fixtures/script-indent/ts-enum-02.vue create mode 100644 tests/fixtures/script-indent/ts-enum-03.vue create mode 100644 tests/fixtures/script-indent/ts-enum-04.vue create mode 100644 tests/fixtures/script-indent/ts-enum-05.vue create mode 100644 tests/fixtures/script-indent/ts-enum-06.vue create mode 100644 tests/fixtures/script-indent/ts-enum-member-01.vue create mode 100644 tests/fixtures/script-indent/ts-enum-member-02.vue create mode 100644 tests/fixtures/script-indent/ts-export-assignment-01.vue create mode 100644 tests/fixtures/script-indent/ts-export-assignment-02.vue create mode 100644 tests/fixtures/script-indent/ts-function-type-01.vue create mode 100644 tests/fixtures/script-indent/ts-function-type-02.vue create mode 100644 tests/fixtures/script-indent/ts-import-equal-01.vue create mode 100644 tests/fixtures/script-indent/ts-import-equal-02.vue create mode 100644 tests/fixtures/script-indent/ts-import-equal-03.vue create mode 100644 tests/fixtures/script-indent/ts-import-type-01.vue create mode 100644 tests/fixtures/script-indent/ts-import-type-02.vue create mode 100644 tests/fixtures/script-indent/ts-import-type-03.vue create mode 100644 tests/fixtures/script-indent/ts-indexed-access-type-01.vue create mode 100644 tests/fixtures/script-indent/ts-indexed-access-type-02.vue create mode 100644 tests/fixtures/script-indent/ts-infer-01.vue create mode 100644 tests/fixtures/script-indent/ts-interface-declaration-01.vue create mode 100644 tests/fixtures/script-indent/ts-interface-declaration-02.vue create mode 100644 tests/fixtures/script-indent/ts-interface-declaration-03.vue create mode 100644 tests/fixtures/script-indent/ts-interface-declaration-04.vue create mode 100644 tests/fixtures/script-indent/ts-type-alias-seclaration-01.vue create mode 100644 tests/fixtures/script-indent/ts-type-annotation-01.vue create mode 100644 tests/fixtures/script-indent/ts-type-annotation-02.vue create mode 100644 tests/fixtures/script-indent/ts-type-annotation-03.vue create mode 100644 tests/fixtures/script-indent/ts-type-annotation-04.vue create mode 100644 tests/fixtures/script-indent/ts-type-annotation-05.vue create mode 100644 tests/fixtures/script-indent/ts-type-annotation-06.vue create mode 100644 tests/fixtures/script-indent/ts-type-parameter-seclaration-01.vue create mode 100644 typings/eslint-plugin-vue/util-types/indent-helper.ts diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js index dafe58ad8..4e17ab4d5 100644 --- a/lib/utils/indent-common.js +++ b/lib/utils/indent-common.js @@ -8,102 +8,33 @@ // Requirements // ------------------------------------------------------------------------------ +const { + isArrowToken, + isOpeningParenToken, + isClosingParenToken, + isNotOpeningParenToken, + isNotClosingParenToken, + isOpeningBraceToken, + isClosingBraceToken, + isOpeningBracketToken, + isClosingBracketToken, + isSemicolonToken +} = require('eslint-utils') +const { + isComment, + isNotComment, + isWildcard, + isExtendsKeyword, + isNotWhitespace, + isNotEmptyTextNode, + isPipeOperator, + last +} = require('./indent-utils') +const { defineVisitor: tsDefineVisitor } = require('./indent-ts') + // ------------------------------------------------------------------------------ // Helpers // ------------------------------------------------------------------------------ - -/** @type {Set} */ -const KNOWN_NODES = new Set([ - 'ArrayExpression', - 'ArrayPattern', - 'ArrowFunctionExpression', - 'AssignmentExpression', - 'AssignmentPattern', - 'AwaitExpression', - 'BinaryExpression', - 'BlockStatement', - 'BreakStatement', - 'CallExpression', - 'CatchClause', - 'ChainExpression', - 'ClassBody', - 'ClassDeclaration', - 'ClassExpression', - 'ConditionalExpression', - 'ContinueStatement', - 'DebuggerStatement', - 'DoWhileStatement', - 'EmptyStatement', - 'ExportAllDeclaration', - 'ExportDefaultDeclaration', - 'ExportNamedDeclaration', - 'ExportSpecifier', - 'ExpressionStatement', - 'ForInStatement', - 'ForOfStatement', - 'ForStatement', - 'FunctionDeclaration', - 'FunctionExpression', - 'Identifier', - 'IfStatement', - 'ImportDeclaration', - 'ImportDefaultSpecifier', - 'ImportExpression', - 'ImportNamespaceSpecifier', - 'ImportSpecifier', - 'LabeledStatement', - 'Literal', - 'LogicalExpression', - 'MemberExpression', - 'MetaProperty', - 'MethodDefinition', - 'NewExpression', - 'ObjectExpression', - 'ObjectPattern', - 'PrivateIdentifier', - 'Program', - 'Property', - 'PropertyDefinition', - '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' -]) -const NON_STANDARD_KNOWN_NODES = new Set([ - 'ExperimentalRestProperty', - 'ExperimentalSpreadProperty' -]) const LT_CHAR = /[\r\n\u2028\u2029]/ const LINES = /[^\r\n\u2028\u2029]+(?:$|\r\n|[\r\n\u2028\u2029])/g const BLOCK_COMMENT_PREFIX = /^\s*\*/ @@ -209,186 +140,6 @@ function parseOptions(type, options, defaultOptions) { return ret } -/** - * Check whether the given token is an arrow. - * @param {Token|undefined|null} 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|undefined|null} 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|undefined|null} 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|undefined|null} 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|undefined|null} 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|undefined|null} 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|undefined|null} 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|undefined|null} 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|undefined|null} 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|undefined|null} 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|undefined|null} 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 wildcard. - * @param {Token} token The token to check. - * @returns {boolean} `true` if the token is a wildcard. - */ -function isWildcard(token) { - return token != null && token.type === 'Punctuator' && token.value === '*' -} - -/** - * Check whether the given token is a whitespace. - * @param {Token|undefined|null} 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|undefined|null} 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' || - (typeof token.type === - 'string' /* Although acorn supports new tokens, espree may not yet support new tokens.*/ && - token.type.endsWith('Comment'))) - ) -} - -/** - * Check whether the given token is a comment. - * @param {Token|undefined|null} 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' && - !( - typeof token.type === - 'string' /* Although acorn supports new tokens, espree may not yet support new tokens.*/ && - token.type.endsWith('Comment') - ) - ) -} - -/** - * Check whether the given node is not an empty text node. - * @param {ASTNode} node The node to check. - * @returns {boolean} `false` if the token is empty text node. - */ -function isNotEmptyTextNode(node) { - return !(node.type === 'VText' && node.value.trim() === '') -} - -/** - * Check whether the given token is a pipe operator. - * @param {Token|undefined|null} token The token to check. - * @returns {boolean} `true` if the token is a pipe operator. - */ -function isPipeOperator(token) { - return token != null && token.type === 'Punctuator' && token.value === '|' -} - -/** - * Get the last element. - * @template T - * @param {T[]} xs The array to get the last element. - * @returns {T | 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 {ASTNode|null} node The node to check. @@ -425,6 +176,15 @@ function isClosingToken(token) { ) } +/** + * Checks whether a given token is a optional token. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a optional token. + */ +function isOptionalToken(token) { + return token.type === 'Punctuator' && token.value === '?.' +} + /** * Creates AST event handlers for html-indent. * @@ -457,11 +217,12 @@ module.exports.defineVisitor = function create( * @returns {void} */ function setOffset(token, offset, baseToken) { - if (!token) { + if (!token || token === baseToken) { return } if (Array.isArray(token)) { for (const t of token) { + if (t === baseToken) continue offsets.set(t, { baseToken, offset, @@ -479,6 +240,27 @@ module.exports.defineVisitor = function create( } } + /** + * Copy offset to the given tokens from srcToken. + * @param {Token} token The token to set. + * @param {Token} srcToken The token of the source offset. + * @returns {void} + */ + function copyOffset(token, srcToken) { + if (!token) { + return + } + const offsetData = offsets.get(srcToken) + if (!offsetData) { + return + } + + setOffset(token, offsetData.offset, offsetData.baseToken) + if (offsetData.baseline) { + setBaseline(token) + } + } + /** * Set baseline flag to the given token. * @param {Token} token The token to set. @@ -540,8 +322,8 @@ module.exports.defineVisitor = function create( while ( (t = tokenStore.getTokenBefore(firstToken)) != null && (u = tokenStore.getTokenAfter(lastToken)) != null && - isLeftParen(t) && - isRightParen(u) && + isOpeningParenToken(t) && + isClosingParenToken(u) && t.range[0] >= borderOffset ) { firstToken = t @@ -663,7 +445,34 @@ module.exports.defineVisitor = function create( */ function processMaybeBlock(node, baseToken) { const firstToken = getFirstAndLastTokens(node).firstToken - setOffset(firstToken, isLeftBrace(firstToken) ? 0 : 1, baseToken) + setOffset(firstToken, isOpeningBraceToken(firstToken) ? 0 : 1, baseToken) + } + + /** + * Process semicolons of the given statement node. + * @param {ASTNode} node The statement node to process. + * @returns {void} + */ + function processSemicolons(node) { + const firstToken = tokenStore.getFirstToken(node) + const lastToken = tokenStore.getLastToken(node) + if (isSemicolonToken(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 && + isSemicolonToken(prevToken) && + prevToken.loc.end.line === firstToken.loc.start.line + ) { + offsets.set(prevToken, info) + } } /** @@ -671,16 +480,19 @@ module.exports.defineVisitor = function create( * The prefix includes `async`, `get`, `set`, `static`, and `*`. * @param {Property|MethodDefinition|PropertyDefinition} node The property node to collect prefix tokens. */ - function getPrefixTokens(node) { + function getPrefixTokens(node, keyNode) { const prefixes = [] /** @type {Token|null} */ let token = tokenStore.getFirstToken(node) - while (token != null && token.range[1] <= node.key.range[0]) { + while (token != null && token.range[1] <= keyNode.range[0]) { prefixes.push(token) token = tokenStore.getTokenAfter(token) } - while (isLeftParen(last(prefixes)) || isLeftBracket(last(prefixes))) { + while ( + isOpeningParenToken(prefixes[prefixes.length - 1]) || + isOpeningBracketToken(prefixes[prefixes.length - 1]) + ) { prefixes.pop() } @@ -696,7 +508,7 @@ module.exports.defineVisitor = function create( const type = node.type while (node.parent && node.parent.type === type) { const prevToken = tokenStore.getTokenBefore(node) - if (isLeftParen(prevToken)) { + if (isOpeningParenToken(prevToken)) { // The chaining is broken by parentheses. break } @@ -734,7 +546,7 @@ module.exports.defineVisitor = function create( return false } const prevToken = tokenStore.getTokenBefore(belongingNode) - if (isLeftParen(prevToken)) { + if (isOpeningParenToken(prevToken)) { // It is not the first token because it is enclosed in parentheses. return false } @@ -742,7 +554,7 @@ module.exports.defineVisitor = function create( } if (parent.type === 'CallExpression' || parent.type === 'NewExpression') { const openParen = /** @type {Token} */ ( - tokenStore.getTokenAfter(parent.callee, isNotRightParen) + tokenStore.getTokenAfter(parent.callee, isNotClosingParenToken) ) return parent.arguments.some( (param) => @@ -1125,8 +937,14 @@ module.exports.defineVisitor = function create( // Main // ------------------------------------------------------------------------------ - return processIgnores({ - /** @param {VAttribute} node */ + /** @type {Set} */ + const knownNodes = new Set() + /** @type {TemplateListener} */ + const visitor = { + // ---------------------------------------------------------------------- + // Vue NODES + // ---------------------------------------------------------------------- + /** @param {VAttribute | VDirective} node */ VAttribute(node) { const keyToken = tokenStore.getFirstToken(node) const eqToken = tokenStore.getTokenAfter(node.key) @@ -1187,7 +1005,7 @@ module.exports.defineVisitor = function create( VFilter(node) { const idToken = tokenStore.getFirstToken(node) const lastToken = tokenStore.getLastToken(node) - if (isRightParen(lastToken)) { + if (isClosingParenToken(lastToken)) { const leftParenToken = tokenStore.getTokenAfter(node.callee) setOffset(leftParenToken, 1, idToken) processNodeList(node.arguments, leftParenToken, lastToken, 1) @@ -1217,12 +1035,15 @@ module.exports.defineVisitor = function create( const firstToken = tokenStore.getFirstToken(node) const lastOfLeft = last(node.left) || firstToken const inToken = /** @type {Token} */ ( - tokenStore.getTokenAfter(lastOfLeft, isNotRightParen) + tokenStore.getTokenAfter(lastOfLeft, isNotClosingParenToken) ) const rightToken = tokenStore.getFirstToken(node.right) - if (isLeftParen(firstToken)) { - const rightToken = tokenStore.getTokenAfter(lastOfLeft, isRightParen) + if (isOpeningParenToken(firstToken)) { + const rightToken = tokenStore.getTokenAfter( + lastOfLeft, + isClosingParenToken + ) processNodeList(node.left, firstToken, rightToken, 1) } setOffset(inToken, 1, firstToken) @@ -1261,29 +1082,42 @@ module.exports.defineVisitor = function create( offsets.set(token, Object.assign({}, firstTokenInfo)) } }, + // ---------------------------------------------------------------------- + // SINGLE TOKEN NODES + // ---------------------------------------------------------------------- + VIdentifier() {}, + VLiteral() {}, + // ---------------------------------------------------------------------- + // WRAPPER NODES + // ---------------------------------------------------------------------- + VDirectiveKey() {}, + VSlotScopeExpression() {}, + // ---------------------------------------------------------------------- + // ES NODES + // ---------------------------------------------------------------------- /** @param {ArrayExpression | ArrayPattern} node */ 'ArrayExpression, ArrayPattern'(node) { - processNodeList( - node.elements, - tokenStore.getFirstToken(node), - tokenStore.getLastToken(node), - 1 + const firstToken = tokenStore.getFirstToken(node) + const rightToken = tokenStore.getTokenAfter( + node.elements[node.elements.length - 1] || firstToken, + isClosingBracketToken ) + processNodeList(node.elements, firstToken, rightToken, 1) }, /** @param {ArrowFunctionExpression} node */ 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) + const arrowToken = tokenStore.getTokenBefore(node.body, isArrowToken) if (node.async) { setOffset(secondToken, 1, firstToken) } - if (isLeftParen(leftToken)) { + if (isOpeningParenToken(leftToken)) { const rightToken = tokenStore.getTokenAfter( last(node.params) || leftToken, - isRightParen + isClosingParenToken ) processNodeList(node.params, leftToken, rightToken, 1) } @@ -1297,7 +1131,7 @@ module.exports.defineVisitor = function create( ) { const leftToken = getChainHeadToken(node) const opToken = /** @type {Token} */ ( - tokenStore.getTokenAfter(node.left, isNotRightParen) + tokenStore.getTokenAfter(node.left, isNotClosingParenToken) ) const rightToken = tokenStore.getTokenAfter(opToken) const prevToken = tokenStore.getTokenBefore(leftToken) @@ -1343,7 +1177,17 @@ module.exports.defineVisitor = function create( CallExpression(node) { const firstToken = tokenStore.getFirstToken(node) const rightToken = tokenStore.getLastToken(node) - const leftToken = tokenStore.getTokenAfter(node.callee, isLeftParen) + const leftToken = /** @type {Token} */ ( + tokenStore.getTokenAfter(node.callee, isOpeningParenToken) + ) + + for (const optionalToken of tokenStore.getTokensBetween( + tokenStore.getLastToken(node.callee), + leftToken, + isOptionalToken + )) { + setOffset(optionalToken, 1, firstToken) + } setOffset(leftToken, 1, firstToken) processNodeList(node.arguments, leftToken, rightToken, 1) @@ -1352,7 +1196,10 @@ module.exports.defineVisitor = function create( ImportExpression(node) { const firstToken = tokenStore.getFirstToken(node) const rightToken = tokenStore.getLastToken(node) - const leftToken = tokenStore.getTokenAfter(firstToken, isLeftParen) + const leftToken = tokenStore.getTokenAfter( + firstToken, + isOpeningParenToken + ) setOffset(leftToken, 1, firstToken) processNodeList([node.source], leftToken, rightToken, 1) @@ -1380,7 +1227,9 @@ module.exports.defineVisitor = function create( setOffset(tokenStore.getFirstToken(node.id), 1, firstToken) } if (node.superClass != null) { - const extendsToken = tokenStore.getTokenAfter(node.id || firstToken) + const extendsToken = /** @type {Token} */ ( + tokenStore.getTokenBefore(node.superClass, isExtendsKeyword) + ) const superClassToken = tokenStore.getTokenAfter(extendsToken) setOffset(extendsToken, 1, firstToken) setOffset(superClassToken, 1, extendsToken) @@ -1392,11 +1241,11 @@ module.exports.defineVisitor = function create( const prevToken = tokenStore.getTokenBefore(node) const firstToken = tokenStore.getFirstToken(node) const questionToken = /** @type {Token} */ ( - tokenStore.getTokenAfter(node.test, isNotRightParen) + tokenStore.getTokenAfter(node.test, isNotClosingParenToken) ) const consequentToken = tokenStore.getTokenAfter(questionToken) const colonToken = /** @type {Token} */ ( - tokenStore.getTokenAfter(node.consequent, isNotRightParen) + tokenStore.getTokenAfter(node.consequent, isNotClosingParenToken) ) const alternateToken = tokenStore.getTokenAfter(colonToken) const isFlat = @@ -1419,12 +1268,12 @@ module.exports.defineVisitor = function create( DoWhileStatement(node) { const doToken = tokenStore.getFirstToken(node) const whileToken = /** @type {Token} */ ( - tokenStore.getTokenAfter(node.body, isNotRightParen) + tokenStore.getTokenAfter(node.body, isNotClosingParenToken) ) const leftToken = tokenStore.getTokenAfter(whileToken) const testToken = tokenStore.getTokenAfter(leftToken) const lastToken = tokenStore.getLastToken(node) - const rightToken = isSemicolon(lastToken) + const rightToken = isSemicolonToken(lastToken) ? tokenStore.getTokenBefore(lastToken) : lastToken @@ -1438,7 +1287,7 @@ module.exports.defineVisitor = function create( ExportAllDeclaration(node) { const tokens = tokenStore.getTokens(node) const firstToken = /** @type {Token} */ (tokens.shift()) - if (isSemicolon(last(tokens))) { + if (isSemicolonToken(tokens[tokens.length - 1])) { tokens.pop() } if (!node.exported) { @@ -1478,16 +1327,13 @@ module.exports.defineVisitor = function create( // export {foo, bar}; or export {foo, bar} from "mod"; const leftParenToken = tokenStore.getFirstToken(node, 1) const rightParenToken = /** @type {Token} */ ( - tokenStore.getLastToken(node, isRightBrace) + tokenStore.getLastToken(node, isClosingBraceToken) ) setOffset(leftParenToken, 0, exportToken) processNodeList(node.specifiers, leftParenToken, rightParenToken, 1) const maybeFromToken = tokenStore.getTokenAfter(rightParenToken) - if ( - maybeFromToken != null && - sourceCode.getText(maybeFromToken) === 'from' - ) { + if (maybeFromToken != null && maybeFromToken.value === 'from') { const fromToken = maybeFromToken const nameToken = tokenStore.getTokenAfter(fromToken) setOffset([fromToken, nameToken], 1, exportToken) @@ -1514,12 +1360,12 @@ module.exports.defineVisitor = function create( const leftParenToken = tokenStore.getTokenAfter(awaitToken || forToken) const leftToken = tokenStore.getTokenAfter(leftParenToken) const inToken = /** @type {Token} */ ( - tokenStore.getTokenAfter(leftToken, isNotRightParen) + tokenStore.getTokenAfter(leftToken, isNotClosingParenToken) ) const rightToken = tokenStore.getTokenAfter(inToken) const rightParenToken = tokenStore.getTokenBefore( node.body, - isNotLeftParen + isNotOpeningParenToken ) if (awaitToken != null) { @@ -1538,7 +1384,7 @@ module.exports.defineVisitor = function create( const leftParenToken = tokenStore.getTokenAfter(forToken) const rightParenToken = tokenStore.getTokenBefore( node.body, - isNotLeftParen + isNotOpeningParenToken ) setOffset(leftParenToken, 1, forToken) @@ -1553,48 +1399,51 @@ module.exports.defineVisitor = function create( /** @param {FunctionDeclaration | FunctionExpression} node */ 'FunctionDeclaration, FunctionExpression'(node) { const firstToken = tokenStore.getFirstToken(node) - if (isLeftParen(firstToken)) { + let leftParenToken, bodyBaseToken + if (isOpeningParenToken(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)) + leftParenToken = firstToken + bodyBaseToken = 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) + let nextToken = tokenStore.getTokenAfter(firstToken) + let nextTokenOffset = 0 + while ( + nextToken && + !isOpeningParenToken(nextToken) && + nextToken.value !== '<' + ) { + if ( + nextToken.value === '*' || + (node.id && nextToken.range[0] === node.id.range[0]) + ) { + nextTokenOffset = 1 + } + setOffset(nextToken, nextTokenOffset, firstToken) + nextToken = tokenStore.getTokenAfter(nextToken) } - setOffset(leftToken, 1, firstToken) - processNodeList(node.params, leftToken, rightToken, 1) - setOffset(bodyToken, 0, firstToken) + + leftParenToken = nextToken + bodyBaseToken = firstToken + } + + if ( + !isOpeningParenToken(leftParenToken) && + /** @type {any} */ (node).typeParameters + ) { + leftParenToken = tokenStore.getTokenAfter( + /** @type {any} */ (node).typeParameters + ) } + const rightParenToken = tokenStore.getTokenAfter( + node.params[node.params.length - 1] || leftParenToken, + isClosingParenToken + ) + setOffset(leftParenToken, 1, bodyBaseToken) + processNodeList(node.params, leftParenToken, rightParenToken, 1) + + const bodyToken = tokenStore.getFirstToken(node.body) + setOffset(bodyToken, 0, bodyBaseToken) }, /** @param {IfStatement} node */ IfStatement(node) { @@ -1602,7 +1451,7 @@ module.exports.defineVisitor = function create( const ifLeftParenToken = tokenStore.getTokenAfter(ifToken) const ifRightParenToken = tokenStore.getTokenBefore( node.consequent, - isRightParen + isClosingParenToken ) setOffset(ifLeftParenToken, 1, ifToken) @@ -1611,7 +1460,7 @@ module.exports.defineVisitor = function create( if (node.alternate != null) { const elseToken = /** @type {Token} */ ( - tokenStore.getTokenAfter(node.consequent, isNotRightParen) + tokenStore.getTokenAfter(node.consequent, isNotClosingParenToken) ) setOffset(elseToken, 0, ifToken) @@ -1620,92 +1469,71 @@ module.exports.defineVisitor = function create( }, /** @param {ImportDeclaration} node */ ImportDeclaration(node) { - const firstSpecifier = node.specifiers[0] - const secondSpecifier = node.specifiers[1] const importToken = tokenStore.getFirstToken(node) - const hasSemi = tokenStore.getLastToken(node).value === ';' - /** @type {Token[]} */ - 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" - ) + const tokens = tokenStore.getTokensBetween(importToken, node.source) + const fromIndex = tokens.map((t) => t.value).lastIndexOf('from') + const { fromToken, beforeTokens, afterTokens } = + fromIndex >= 0 + ? { + fromToken: tokens[fromIndex], + beforeTokens: tokens.slice(0, fromIndex), + afterTokens: [ + ...tokens.slice(fromIndex + 1), + tokenStore.getFirstToken(node.source) + ] + } + : { + fromToken: null, + beforeTokens: [...tokens, tokenStore.getFirstToken(node.source)], + afterTokens: [] + } + + /** @type {ImportSpecifier[]} */ + const namedSpecifiers = [] + for (const specifier of node.specifiers) { + if (specifier.type === 'ImportSpecifier') { + namedSpecifiers.push(specifier) } else { - tokens.push(tokenStore.getLastToken(node, hasSemi ? 1 : 0)) + const removeTokens = tokenStore.getTokens(specifier) + removeTokens.shift() + for (const token of removeTokens) { + const i = beforeTokens.indexOf(token) + if (i >= 0) { + beforeTokens.splice(i, 1) + } + } } - } 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" - ) + } + if (namedSpecifiers.length) { + const leftBrace = tokenStore.getTokenBefore(namedSpecifiers[0]) + const rightBrace = tokenStore.getTokenAfter( + namedSpecifiers[namedSpecifiers.length - 1] + ) + processNodeList(namedSpecifiers, leftBrace, rightBrace, 1) + for (const token of tokenStore.getTokensBetween( + leftBrace, + rightBrace + )) { + const i = beforeTokens.indexOf(token) + if (i >= 0) { + beforeTokens.splice(i, 1) } } - } 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" + } + + if ( + beforeTokens.every( + (t) => isOpeningBraceToken(t) || isClosingBraceToken(t) ) + ) { + setOffset(beforeTokens, 0, importToken) } 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(beforeTokens, 1, importToken) + } + if (fromToken) { + setOffset(fromToken, 1, importToken) + setOffset(afterTokens, 0, fromToken) } - - setOffset(tokens, 1, importToken) }, /** @param {ImportSpecifier} node */ ImportSpecifier(node) { @@ -1734,14 +1562,22 @@ module.exports.defineVisitor = function create( const objectToken = tokenStore.getFirstToken(node) if (node.type === 'MemberExpression' && node.computed) { const leftBracketToken = /** @type {Token} */ ( - tokenStore.getTokenBefore(node.property, isLeftBracket) + tokenStore.getTokenBefore(node.property, isOpeningBracketToken) ) const propertyToken = tokenStore.getTokenAfter(leftBracketToken) const rightBracketToken = tokenStore.getTokenAfter( node.property, - isRightBracket + isClosingBracketToken ) + for (const optionalToken of tokenStore.getTokensBetween( + tokenStore.getLastToken(node.object), + leftBracketToken, + isOptionalToken + )) { + setOffset(optionalToken, 1, objectToken) + } + setOffset(leftBracketToken, 1, objectToken) setOffset(propertyToken, 1, leftBracketToken) setOffset(rightBracketToken, 0, leftBracketToken) @@ -1754,54 +1590,37 @@ module.exports.defineVisitor = function create( }, /** @param {MethodDefinition | Property | PropertyDefinition} node */ 'MethodDefinition, Property, PropertyDefinition'(node) { - const prefixTokens = getPrefixTokens(node) - const hasPrefix = prefixTokens.length >= 1 - - for (let i = 1; i < prefixTokens.length; ++i) { - setOffset(prefixTokens[i], 0, prefixTokens[i - 1]) + const firstToken = tokenStore.getFirstToken(node) + const keyTokens = getFirstAndLastTokens(node.key) + const prefixTokens = tokenStore.getTokensBetween( + firstToken, + keyTokens.firstToken + ) + if (node.computed) { + prefixTokens.pop() // pop [ } + setOffset(prefixTokens, 0, firstToken) - /** @type {Token} */ let lastKeyToken if (node.computed) { - const keyLeftToken = /** @type {Token} */ ( - tokenStore.getFirstToken(node, isLeftBracket) - ) - const keyToken = tokenStore.getTokenAfter(keyLeftToken) - const keyRightToken = (lastKeyToken = /** @type {Token} */ ( - tokenStore.getTokenAfter(node.key, isRightBracket) + const leftBracketToken = tokenStore.getTokenBefore(keyTokens.firstToken) + const rightBracketToken = (lastKeyToken = tokenStore.getTokenAfter( + keyTokens.lastToken )) - - if (hasPrefix) { - setOffset(keyLeftToken, 0, /** @type {Token} */ (last(prefixTokens))) - } - setOffset(keyToken, 1, keyLeftToken) - setOffset(keyRightToken, 0, keyLeftToken) + setOffset(leftBracketToken, 0, firstToken) + processNodeList([node.key], leftBracketToken, rightBracketToken, 1) } else { - const idToken = (lastKeyToken = tokenStore.getFirstToken(node.key)) - - if (hasPrefix) { - setOffset(idToken, 0, /** @type {Token} */ (last(prefixTokens))) - } + setOffset(keyTokens.firstToken, 0, firstToken) + lastKeyToken = keyTokens.lastToken } - if ( - node.type === 'MethodDefinition' || - (node.type === 'Property' && node.method === true) - ) { - const leftParenToken = tokenStore.getTokenAfter(lastKeyToken) - - setOffset(leftParenToken, 1, lastKeyToken) - } else if (node.type === 'Property' && !node.shorthand) { - const colonToken = tokenStore.getTokenAfter(lastKeyToken) - const valueToken = tokenStore.getTokenAfter(colonToken) - - setOffset([colonToken, valueToken], 1, lastKeyToken) - } else if (node.type === 'PropertyDefinition' && node.value != null) { - const eqToken = tokenStore.getTokenAfter(lastKeyToken) - const initToken = tokenStore.getTokenAfter(eqToken) - - setOffset([eqToken, initToken], 1, lastKeyToken) + if (node.value != null) { + const initToken = tokenStore.getFirstToken(node.value) + setOffset( + [...tokenStore.getTokensBetween(lastKeyToken, initToken), initToken], + 1, + lastKeyToken + ) } }, /** @param {NewExpression} node */ @@ -1809,8 +1628,12 @@ module.exports.defineVisitor = function create( 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) + const leftToken = isClosingParenToken(rightToken) + ? tokenStore.getFirstTokenBetween( + node.callee, + rightToken, + isOpeningParenToken + ) : null setOffset(calleeToken, 1, newToken) @@ -1821,12 +1644,12 @@ module.exports.defineVisitor = function create( }, /** @param {ObjectExpression | ObjectPattern} node */ 'ObjectExpression, ObjectPattern'(node) { - processNodeList( - node.properties, - tokenStore.getFirstToken(node), - tokenStore.getLastToken(node), - 1 + const firstToken = tokenStore.getFirstToken(node) + const rightToken = tokenStore.getTokenAfter( + node.properties[node.properties.length - 1] || firstToken, + isClosingBraceToken ) + processNodeList(node.properties, firstToken, rightToken, 1) }, /** @param {SequenceExpression} node */ SequenceExpression(node) { @@ -1838,7 +1661,10 @@ module.exports.defineVisitor = function create( if (node.test != null) { const testToken = tokenStore.getTokenAfter(caseToken) - const colonToken = tokenStore.getTokenAfter(node.test, isNotRightParen) + const colonToken = tokenStore.getTokenAfter( + node.test, + isNotClosingParenToken + ) setOffset([testToken, colonToken], 1, caseToken) } else { @@ -1863,7 +1689,7 @@ module.exports.defineVisitor = function create( const leftParenToken = tokenStore.getTokenAfter(switchToken) const discriminantToken = tokenStore.getTokenAfter(leftParenToken) const leftBraceToken = /** @type {Token} */ ( - tokenStore.getTokenAfter(node.discriminant, isLeftBrace) + tokenStore.getTokenAfter(node.discriminant, isOpeningBraceToken) ) const rightParenToken = tokenStore.getTokenBefore(leftBraceToken) const rightBraceToken = tokenStore.getLastToken(node) @@ -1949,7 +1775,10 @@ module.exports.defineVisitor = function create( 'WhileStatement, WithStatement'(node) { const firstToken = tokenStore.getFirstToken(node) const leftParenToken = tokenStore.getTokenAfter(firstToken) - const rightParenToken = tokenStore.getTokenBefore(node.body, isRightParen) + const rightParenToken = tokenStore.getTokenBefore( + node.body, + isClosingParenToken + ) setOffset(leftParenToken, 1, firstToken) setOffset(rightParenToken, 0, leftParenToken) @@ -1966,38 +1795,43 @@ module.exports.defineVisitor = function create( } } }, + // ---------------------------------------------------------------------- + // SINGLE TOKEN NODES + // ---------------------------------------------------------------------- + DebuggerStatement() {}, + Identifier() {}, + ImportDefaultSpecifier() {}, + Literal() {}, + PrivateIdentifier() {}, + Super() {}, + TemplateElement() {}, + ThisExpression() {}, + // ---------------------------------------------------------------------- + // WRAPPER NODES + // ---------------------------------------------------------------------- + ExpressionStatement() {}, + ChainExpression() {}, + EmptyStatement() {}, + // ---------------------------------------------------------------------- + // COMMONS + // ---------------------------------------------------------------------- /** @param {Statement} node */ // 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) - } + ':statement, PropertyDefinition'(node) { + processSemicolons(node) }, /** @param {Expression | MetaProperty | TemplateLiteral} node */ // Process parentheses. // `:expression` does not match with MetaProperty and TemplateLiteral as a bug: https://github.com/estools/esquery/pull/59 - ':expression, MetaProperty, TemplateLiteral'(node) { + ':expression'(node) { let leftToken = tokenStore.getTokenBefore(node) let rightToken = tokenStore.getTokenAfter(node) let firstToken = tokenStore.getFirstToken(node) - while (isLeftParen(leftToken) && isRightParen(rightToken)) { + while ( + isOpeningParenToken(leftToken) && + isClosingParenToken(rightToken) + ) { setOffset(firstToken, 1, leftToken) setOffset(rightToken, 0, leftToken) @@ -2006,13 +1840,23 @@ module.exports.defineVisitor = function create( rightToken = tokenStore.getTokenAfter(rightToken) } }, + + .../** @type {TemplateListener} */ ( + tsDefineVisitor({ + processNodeList, + tokenStore, + setOffset, + copyOffset, + getPrefixTokens, + processSemicolons, + getFirstAndLastTokens + }) + ), + /** @param {ASTNode} node */ // Ignore tokens of unknown nodes. '*:exit'(node) { - if ( - !KNOWN_NODES.has(node.type) && - !NON_STANDARD_KNOWN_NODES.has(node.type) - ) { + if (!knownNodes.has(node.type)) { ignore(node) } }, @@ -2078,5 +1922,16 @@ module.exports.defineVisitor = function create( validate(tokensOnSameLine, comments, lastValidatedToken) } } - }) + } + + for (const key of Object.keys(visitor)) { + for (const nodeName of key + .split(/\s*,\s*/gu) + .map((s) => s.trim()) + .filter((s) => /[a-z]+/i.test(s))) { + knownNodes.add(nodeName) + } + } + + return processIgnores(visitor) } diff --git a/lib/utils/indent-ts.js b/lib/utils/indent-ts.js new file mode 100644 index 000000000..5d4e14aef --- /dev/null +++ b/lib/utils/indent-ts.js @@ -0,0 +1,1304 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +const { + isClosingParenToken, + isOpeningParenToken, + isOpeningBraceToken, + isNotClosingParenToken, + isClosingBracketToken, + isOpeningBracketToken +} = require('eslint-utils') + +/** + * @typedef {import('@typescript-eslint/types').TSESTree} TSESTree + * @typedef {import('../../typings/eslint-plugin-vue/util-types/indent-helper').TSNodeListener} TSNodeListener + */ + +module.exports = { + defineVisitor +} + +/** + * Process the given node list. + * The first node is offsetted from the given left token. + * Rest nodes are adjusted to the first node. + * @callback ProcessNodeList + * @param {(ASTNode|null)[]} nodeList The node to process. + * @param {ASTNode|Token|null} left The left parenthesis token. + * @param {ASTNode|Token|null} right The right parenthesis token. + * @param {number} offset The offset to set. + * @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. + * @returns {void} + */ +/** + * Set offset to the given tokens. + * @callback SetOffset + * @param {Token|Token[]|null|(Token|null)[]} token The token to set. + * @param {number} offset The offset of the tokens. + * @param {Token} baseToken The token of the base offset. + * @returns {void} + */ +/** + * + * Copy offset to the given tokens from srcToken. + * @callback CopyOffset + * @param {Token} token The token to set. + * @param {Token} srcToken The token of the source offset. + * @returns {void} + */ +/** + * Collect prefix tokens of the given property. + * The prefix includes `async`, `get`, `set`, `static`, and `*`. + * @callback GetPrefixTokens + * @param {ASTNode} node The property node to collect prefix tokens. + * @param {ASTNode | Token} keyNode The key node. + * @returns {Token[]} + */ +/** + * Process semicolons of the given statement node. + * @callback ProcessSemicolons + * @param {ASTNode} node The statement node to process. + * @returns {void} + */ +/** + * Get the first and last tokens of the given node. + * If the node is parenthesized, this gets the outermost parentheses. + * @callback GetFirstAndLastTokens + * @param {ASTNode} 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. + */ +/** + * @typedef {object} DefineVisitorParam + * @property {ProcessNodeList} processNodeList + * @property {ParserServices.TokenStore | SourceCode} tokenStore + * @property {SetOffset} setOffset + * @property {CopyOffset} copyOffset + * @property {GetPrefixTokens} getPrefixTokens + * @property {ProcessSemicolons} processSemicolons + * @property {GetFirstAndLastTokens} getFirstAndLastTokens + */ + +/** + * @param {DefineVisitorParam} param + * @returns {TSNodeListener} + */ +function defineVisitor({ + processNodeList, + tokenStore, + setOffset, + copyOffset, + getPrefixTokens, + processSemicolons, + getFirstAndLastTokens +}) { + return { + // Support TypeScript + ['ClassDeclaration[implements], ClassDeclaration[typeParameters], ClassDeclaration[superTypeParameters],' + + 'ClassExpression[implements], ClassExpression[typeParameters], ClassExpression[superTypeParameters]']( + node + ) { + if (node.typeParameters != null) { + setOffset( + tokenStore.getFirstToken(node.typeParameters), + 1, + tokenStore.getFirstToken(node.id || node) + ) + } + if (node.superTypeParameters != null && node.superClass != null) { + setOffset( + tokenStore.getFirstToken(node.superTypeParameters), + 1, + tokenStore.getFirstToken(node.superClass) + ) + } + if (node.implements != null && node.implements.length) { + const classToken = tokenStore.getFirstToken(node) + const implementsToken = tokenStore.getTokenBefore(node.implements[0]) + setOffset(implementsToken, 1, classToken) + processNodeList(node.implements, implementsToken, null, 1) + } + }, + // Process semicolons. + ['TSTypeAliasDeclaration, TSCallSignatureDeclaration, TSConstructSignatureDeclaration, TSImportEqualsDeclaration,' + + 'TSAbstractMethodDefinition, TSAbstractClassProperty, TSEnumMember, ClassProperty,' + + 'TSPropertySignature, TSIndexSignature, TSMethodSignature'](node) { + processSemicolons(node) + }, + /** + * Process type annotation + * + * e.g. + * ``` + * const foo: Type + * // ^^^^^^ + * type foo = () => string + * // ^^^^^^^^^ + * ``` + */ + TSTypeAnnotation(node) { + const [colonOrArrowToken, secondToken] = tokenStore.getFirstTokens(node, { + count: 2, + includeComments: false + }) + const baseToken = tokenStore.getFirstToken(node.parent) + setOffset([colonOrArrowToken, secondToken], 1, baseToken) + + if (node.parent.range[1] < node.range[0]) { + setOffset(tokenStore.getTokensBetween(node.parent, node), 1, baseToken) + } + + // a ?: T + const before = tokenStore.getTokenBefore(colonOrArrowToken) + if (before && before.value === '?') { + setOffset(before, 1, baseToken) + } + }, + /** + * Process as expression + * + * e.g. + * ``` + * var foo = bar as boolean + * // ^^^^^^^^^^^^^^ + * ``` + */ + TSAsExpression(node) { + const expressionTokens = getFirstAndLastTokens(node.expression) + const asToken = tokenStore.getTokenAfter(expressionTokens.lastToken) + setOffset( + [asToken, getFirstAndLastTokens(node.typeAnnotation).firstToken], + 1, + expressionTokens.firstToken + ) + }, + /** + * Process type reference + * + * e.g. + * ``` + * const foo: Type

+ * // ^^^^^^^ + * ``` + */ + TSTypeReference(node) { + if (node.typeParameters) { + const typeNameTokens = getFirstAndLastTokens(node.typeName) + setOffset( + tokenStore.getFirstToken(node.typeParameters), + 1, + typeNameTokens.firstToken + ) + } + }, + /** + * Process type parameter instantiation and type parameter declaration + * + * e.g. + * ``` + * const foo: Type

+ * // ^^^ + * ``` + * + * e.g. + * ``` + * type Foo + * // ^^^ + * ``` + * @param {TSESTree.TSTypeParameterInstantiation | TSESTree.TSTypeParameterDeclaration} node + */ + 'TSTypeParameterInstantiation, TSTypeParameterDeclaration'(node) { + // + processNodeList( + node.params, + tokenStore.getFirstToken(node), + tokenStore.getLastToken(node), + 1 + ) + }, + /** + * Process type alias declaration + * + * e.g. + * ``` + * type Foo + * ``` + */ + TSTypeAliasDeclaration(node) { + // type T = {} + const typeToken = tokenStore.getFirstToken(node) + const idToken = tokenStore.getFirstToken(node.id) + setOffset(idToken, 1, typeToken) + let eqToken + if (node.typeParameters) { + setOffset(tokenStore.getFirstToken(node.typeParameters), 1, idToken) + eqToken = tokenStore.getTokenAfter(node.typeParameters) + } else { + eqToken = tokenStore.getTokenAfter(node.id) + } + const initToken = tokenStore.getTokenAfter(eqToken) + setOffset([eqToken, initToken], 1, idToken) + }, + /** + * Process constructor type or function type + * + * e.g. + * ``` + * type Foo = new () => T + * // ^^^^^^^^^^^ + * type Foo = () => void + * // ^^^^^^^^^^ + * ``` + */ + 'TSConstructorType, TSFunctionType'(node) { + // ()=>void + const firstToken = tokenStore.getFirstToken(node) + // new or < or ( + let currToken = firstToken + if (node.type === 'TSConstructorType') { + // currToken is new token + // < or ( + currToken = tokenStore.getTokenAfter(currToken) + setOffset(currToken, 1, firstToken) + } + if (node.typeParameters) { + // currToken is < token + // ( + currToken = tokenStore.getTokenAfter(node.typeParameters) + setOffset(currToken, 1, firstToken) + } + const leftParenToken = currToken + const rightParenToken = tokenStore.getTokenAfter( + node.params[node.params.length - 1] || leftParenToken, + isClosingParenToken + ) + processNodeList(node.params, leftParenToken, rightParenToken, 1) + const arrowToken = tokenStore.getTokenAfter(rightParenToken) + setOffset(arrowToken, 1, leftParenToken) + }, + /** + * Process type literal + * + * e.g. + * ``` + * const foo: { bar: string } + * // ^^^^^^^^^^^^^^^ + * ``` + */ + TSTypeLiteral(node) { + processNodeList( + node.members, + tokenStore.getFirstToken(node), + tokenStore.getLastToken(node), + 1 + ) + }, + /** + * Process property signature + * + * e.g. + * ``` + * const foo: { bar: string } + * // ^^^^^^^^^^^ + * ``` + */ + TSPropertySignature(node) { + const firstToken = tokenStore.getFirstToken(node) + const keyTokens = getFirstAndLastTokens(node.key) + let keyLast + if (node.computed) { + const closeBracket = tokenStore.getTokenAfter(keyTokens.lastToken) + processNodeList([node.key], firstToken, closeBracket, 1) + keyLast = closeBracket + } else { + keyLast = keyTokens.lastToken + } + if (node.typeAnnotation) { + const typeAnnotationToken = tokenStore.getFirstToken( + node.typeAnnotation + ) + setOffset( + [ + ...tokenStore.getTokensBetween(keyLast, typeAnnotationToken), + typeAnnotationToken + ], + 1, + firstToken + ) + } else if (node.optional) { + const qToken = tokenStore.getLastToken(node) + setOffset(qToken, 1, firstToken) + } + }, + /** + * Process index signature + * + * e.g. + * ``` + * const foo: { [bar: string]: string } + * // ^^^^^^^^^^^^^^^^^^^^^ + * ``` + */ + TSIndexSignature(node) { + const leftBracketToken = tokenStore.getFirstToken(node) + const rightBracketToken = tokenStore.getTokenAfter( + node.parameters[node.parameters.length - 1] || leftBracketToken, + isClosingBracketToken + ) + processNodeList(node.parameters, leftBracketToken, rightBracketToken, 1) + const keyLast = rightBracketToken + if (node.typeAnnotation) { + const typeAnnotationToken = tokenStore.getFirstToken( + node.typeAnnotation + ) + setOffset( + [ + ...tokenStore.getTokensBetween(keyLast, typeAnnotationToken), + typeAnnotationToken + ], + 1, + leftBracketToken + ) + } + }, + /** + * Process array type + * + * e.g. + * ``` + * const foo: Type[] + * // ^^^^^^ + * ``` + */ + TSArrayType(node) { + const firstToken = tokenStore.getFirstToken(node) + setOffset( + tokenStore.getLastTokens(node, { count: 2, includeComments: false }), + 0, + firstToken + ) + }, + TSTupleType(node) { + // [T, U] + processNodeList( + node.elementTypes, + tokenStore.getFirstToken(node), + tokenStore.getLastToken(node), + 1 + ) + }, + TSQualifiedName(node) { + // A.B + const objectToken = tokenStore.getFirstToken(node) + const dotToken = tokenStore.getTokenBefore(node.right) + const propertyToken = tokenStore.getTokenAfter(dotToken) + setOffset([dotToken, propertyToken], 1, objectToken) + }, + TSIndexedAccessType(node) { + // A[B] + const objectToken = tokenStore.getFirstToken(node) + const leftBracketToken = tokenStore.getTokenBefore( + node.indexType, + isOpeningBracketToken + ) + const rightBracketToken = tokenStore.getTokenAfter( + node.indexType, + isClosingBracketToken + ) + setOffset(leftBracketToken, 1, objectToken) + processNodeList([node.indexType], leftBracketToken, rightBracketToken, 1) + }, + 'TSUnionType, TSIntersectionType'(node) { + // A | B + // A & B + const firstToken = tokenStore.getFirstToken(node) + const types = [...node.types] + if (getFirstAndLastTokens(types[0]).firstToken === firstToken) { + types.shift() + } + processNodeList( + types, + firstToken, + null, + isBeginningOfLine(tokenStore, firstToken) ? 0 : 1 + ) + }, + TSParenthesizedType(node) { + // (T) + processNodeList( + [node.typeAnnotation], + tokenStore.getFirstToken(node), + tokenStore.getLastToken(node), + 1 + ) + }, + TSMappedType(node) { + // {[key in foo]: bar} + const leftBraceToken = tokenStore.getFirstToken(node) + const leftBracketToken = tokenStore.getTokenBefore(node.typeParameter) + const rightBracketToken = tokenStore.getTokenAfter( + node.nameType || node.typeParameter + ) + setOffset( + [ + ...tokenStore.getTokensBetween(leftBraceToken, leftBracketToken), + leftBracketToken + ], + 1, + leftBraceToken + ) + processNodeList( + [node.typeParameter, node.nameType], + leftBracketToken, + rightBracketToken, + 1 + ) + const rightBraceToken = tokenStore.getLastToken(node) + if (node.typeAnnotation) { + const typeAnnotationToken = tokenStore.getFirstToken( + node.typeAnnotation + ) + setOffset( + [ + ...tokenStore.getTokensBetween( + rightBracketToken, + typeAnnotationToken + ), + typeAnnotationToken + ], + 1, + leftBraceToken + ) + } else { + setOffset( + [...tokenStore.getTokensBetween(rightBracketToken, rightBraceToken)], + 1, + leftBraceToken + ) + } + setOffset(rightBraceToken, 0, leftBraceToken) + }, + /** + * Process type parameter + * + * e.g. + * ``` + * type Foo + * // ^ ^^^^^^^^^^^ ^^^^^ + * type Foo = {[key in foo]: bar} + * // ^^^^^^^^^^ + * ``` + */ + TSTypeParameter(node) { + const [firstToken, ...afterTokens] = tokenStore.getTokens(node) + for (const child of [node.constraint, node.default]) { + if (!child) { + continue + } + const [, ...removeTokens] = tokenStore.getTokens(child) + for (const token of removeTokens) { + const i = afterTokens.indexOf(token) + if (i >= 0) { + afterTokens.splice(i, 1) + } + } + } + const secondToken = afterTokens.shift() + if (!secondToken) { + return + } + setOffset(secondToken, 1, firstToken) + if (secondToken.value === 'extends') { + let prevToken = null + let token = afterTokens.shift() + while (token) { + if (token.value === '=') { + break + } + setOffset(token, 1, secondToken) + prevToken = token + token = afterTokens.shift() + } + while (token) { + setOffset(token, 1, prevToken || secondToken) + token = afterTokens.shift() + } + } else { + setOffset(afterTokens, 1, firstToken) + } + }, + /** + * Process conditional type + * + * e.g. + * ``` + * type Foo = A extends B ? Bar : Baz + * // ^^^^^^^^^^^^^^^^^^^^^^^ + * ``` + */ + TSConditionalType(node) { + // T extends Foo ? T : U + const checkTypeToken = tokenStore.getFirstToken(node) + const extendsToken = tokenStore.getTokenAfter(node.checkType) + const extendsTypeToken = tokenStore.getFirstToken(node.extendsType) + setOffset(extendsToken, 1, checkTypeToken) + setOffset(extendsTypeToken, 1, extendsToken) + const questionToken = tokenStore.getTokenAfter( + node.extendsType, + isNotClosingParenToken + ) + const consequentToken = tokenStore.getTokenAfter(questionToken) + const colonToken = tokenStore.getTokenAfter( + node.trueType, + isNotClosingParenToken + ) + const alternateToken = tokenStore.getTokenAfter(colonToken) + let baseNode = node + let parent = baseNode.parent + while ( + parent && + parent.type === 'TSConditionalType' && + parent.falseType === baseNode + ) { + baseNode = parent + parent = baseNode.parent + } + const baseToken = tokenStore.getFirstToken(baseNode) + setOffset([questionToken, colonToken], 1, baseToken) + setOffset(consequentToken, 1, questionToken) + setOffset(alternateToken, 1, colonToken) + }, + /** + * Process interface declaration + * + * e.g. + * ``` + * interface Foo { } + * ``` + */ + TSInterfaceDeclaration(node) { + const interfaceToken = tokenStore.getFirstToken(node) + setOffset(tokenStore.getFirstToken(node.id), 1, interfaceToken) + if (node.typeParameters != null) { + setOffset( + tokenStore.getFirstToken(node.typeParameters), + 1, + tokenStore.getFirstToken(node.id) + ) + } + if (node.extends != null && node.extends.length) { + const extendsToken = tokenStore.getTokenBefore(node.extends[0]) + setOffset(extendsToken, 1, interfaceToken) + processNodeList(node.extends, extendsToken, null, 1) + } + if (node.implements != null && node.implements.length) { + const implementsToken = tokenStore.getTokenBefore(node.implements[0]) + setOffset(implementsToken, 1, interfaceToken) + processNodeList(node.implements, implementsToken, null, 1) + } + const bodyToken = tokenStore.getFirstToken(node.body) + setOffset(bodyToken, 0, interfaceToken) + }, + /** + * Process interface body + * + * e.g. + * ``` + * interface Foo { } + * // ^^^ + * ``` + * + * @param {TSESTree.TSInterfaceBody | TSESTree.TSModuleBlock} node + */ + 'TSInterfaceBody, TSModuleBlock'(node) { + processNodeList( + node.body, + tokenStore.getFirstToken(node), + tokenStore.getLastToken(node), + 1 + ) + }, + /** + * Process interface heritage and class implements + * + * e.g. + * ``` + * interface Foo extends Bar { } + * // ^^^^^^ + * class Foo implements Bar { } + * // ^^^^^^ + * ``` + * @param {TSESTree.TSInterfaceHeritage|TSESTree.TSClassImplements} node + */ + 'TSInterfaceHeritage, TSClassImplements'(node) { + if (node.typeParameters) { + setOffset( + tokenStore.getFirstToken(node.typeParameters), + 1, + tokenStore.getFirstToken(node) + ) + } + }, + /** + * Process enum + * + * e.g. + * ``` + * enum Foo { } + * ``` + */ + TSEnumDeclaration(node) { + const firstToken = tokenStore.getFirstToken(node) + const idTokens = getFirstAndLastTokens(node.id) + const prefixTokens = tokenStore.getTokensBetween( + firstToken, + idTokens.firstToken + ) + setOffset(prefixTokens, 0, firstToken) + setOffset(idTokens.firstToken, 1, firstToken) + const leftBraceToken = tokenStore.getTokenAfter(idTokens.lastToken) + const rightBraceToken = tokenStore.getLastToken(node) + setOffset(leftBraceToken, 0, firstToken) + processNodeList(node.members, leftBraceToken, rightBraceToken, 1) + }, + TSModuleDeclaration(node) { + const firstToken = tokenStore.getFirstToken(node) + const idTokens = getFirstAndLastTokens(node.id) + const prefixTokens = tokenStore.getTokensBetween( + firstToken, + idTokens.firstToken + ) + setOffset(prefixTokens, 0, firstToken) + setOffset(idTokens.firstToken, 1, firstToken) + if (node.body) { + const bodyFirstToken = tokenStore.getFirstToken(node.body) + setOffset( + bodyFirstToken, + isOpeningBraceToken(bodyFirstToken) ? 0 : 1, + firstToken + ) + } + }, + TSMethodSignature(node) { + // fn(arg: A): R | null; + const firstToken = tokenStore.getFirstToken(node) + const keyTokens = getFirstAndLastTokens(node.key) + let keyLast + if (node.computed) { + const closeBracket = tokenStore.getTokenAfter(keyTokens.lastToken) + processNodeList([node.key], firstToken, closeBracket, 1) + keyLast = closeBracket + } else { + keyLast = keyTokens.lastToken + } + const leftParenToken = tokenStore.getTokenAfter( + keyLast, + isOpeningParenToken + ) + setOffset( + [ + ...tokenStore.getTokensBetween(keyLast, leftParenToken), + leftParenToken + ], + 1, + firstToken + ) + const rightParenToken = tokenStore.getTokenAfter( + node.params[node.params.length - 1] || leftParenToken, + isClosingParenToken + ) + processNodeList(node.params, leftParenToken, rightParenToken, 1) + if (node.returnType) { + const typeAnnotationToken = tokenStore.getFirstToken(node.returnType) + setOffset( + [ + ...tokenStore.getTokensBetween(keyLast, typeAnnotationToken), + typeAnnotationToken + ], + 1, + firstToken + ) + } + }, + /** + * Process call signature declaration and construct signature declaration + * + * e.g. + * ``` + * interface Foo { + * (): string; + * //^^^^^^^^^^^ + * (e: E): R + * //^^^^^^^^^^^^^ + * } + * ``` + * + * e.g. + * ``` + * interface Foo { + * new (); + * //^^^^^^^ + * } + * interface A { new (e: E): R } + * // ^^^^^^^^^^^^^^^^^ + * ``` + * @param {TSESTree.TSCallSignatureDeclaration | TSESTree.TSConstructSignatureDeclaration} node + */ + 'TSCallSignatureDeclaration, TSConstructSignatureDeclaration'(node) { + const firstToken = tokenStore.getFirstToken(node) + // new or < or ( + let currToken = firstToken + if (node.type === 'TSConstructSignatureDeclaration') { + // currToken is new token + // < or ( + currToken = tokenStore.getTokenAfter(currToken) + setOffset(currToken, 1, firstToken) + } + if (node.typeParameters) { + // currToken is < token + // ( + currToken = tokenStore.getTokenAfter(node.typeParameters) + setOffset(currToken, 1, firstToken) + } + const leftParenToken = currToken + const rightParenToken = tokenStore.getTokenAfter( + node.params[node.params.length - 1] || leftParenToken, + isClosingParenToken + ) + processNodeList(node.params, leftParenToken, rightParenToken, 1) + if (node.returnType) { + const typeAnnotationToken = tokenStore.getFirstToken(node.returnType) + setOffset( + [ + ...tokenStore.getTokensBetween( + rightParenToken, + typeAnnotationToken + ), + typeAnnotationToken + ], + 1, + firstToken + ) + } + }, + /** + * Process declare function and empty body function + * + * e.g. + * ``` + * declare function foo(); + * ``` + * + * e.g. + * ``` + * class Foo { + * abstract fn(); + * // ^^^ + * } + * ``` + * @param {TSESTree.TSDeclareFunction | TSESTree.TSEmptyBodyFunctionExpression} node + */ + 'TSDeclareFunction, TSEmptyBodyFunctionExpression'(node) { + const firstToken = tokenStore.getFirstToken(node) + let leftParenToken, bodyBaseToken + if (firstToken.type === 'Punctuator') { + // method + leftParenToken = firstToken + bodyBaseToken = tokenStore.getFirstToken(node.parent) + } else { + let nextToken = tokenStore.getTokenAfter(firstToken) + let nextTokenOffset = 0 + while ( + nextToken && + !isOpeningParenToken(nextToken) && + nextToken.value !== '<' + ) { + if ( + nextToken.value === '*' || + (node.id && nextToken.range[0] === node.id.range[0]) + ) { + nextTokenOffset = 1 + } + setOffset(nextToken, nextTokenOffset, firstToken) + nextToken = tokenStore.getTokenAfter(nextToken) + } + + leftParenToken = nextToken + bodyBaseToken = firstToken + } + if (!isOpeningParenToken(leftParenToken) && node.typeParameters) { + leftParenToken = tokenStore.getTokenAfter(node.typeParameters) + } + const rightParenToken = tokenStore.getTokenAfter( + node.params[node.params.length - 1] || leftParenToken, + isClosingParenToken + ) + setOffset(leftParenToken, 1, bodyBaseToken) + processNodeList(node.params, leftParenToken, rightParenToken, 1) + }, + /** + * Process type operator, type query and infer type + * + * e.g. + * ``` + * type Foo = keyof Bar + * // ^^^^^^^^^ + * ``` + * + * e.g. + * ``` + * type T = typeof a + * // ^^^^^^^^ + * ``` + * + * e.g. + * ``` + * type Foo = T extends Bar ? U : T; + * // ^^^^^^^ + * ``` + * + * @param {TSESTree.TSTypeOperator | TSESTree.TSTypeQuery | TSESTree.TSInferType} node + */ + 'TSTypeOperator, TSTypeQuery, TSInferType'(node) { + // keyof T + // type T = typeof av + // infer U + const firstToken = tokenStore.getFirstToken(node) + const nextToken = tokenStore.getTokenAfter(firstToken) + setOffset(nextToken, 1, firstToken) + }, + /** + * Process type predicate + * + * e.g. + * ``` + * function foo(value): value is string; + * // ^^^^^^^^^^^^^^^ + * ``` + */ + TSTypePredicate(node) { + const firstToken = tokenStore.getFirstToken(node) + const opToken = tokenStore.getTokenAfter( + node.parameterName, + isNotClosingParenToken + ) + const rightToken = + node.typeAnnotation && + getFirstAndLastTokens(node.typeAnnotation).firstToken + setOffset( + [opToken, rightToken], + 1, + getFirstAndLastTokens(firstToken).firstToken + ) + }, + /** + * Process abstract method definition, abstract class property, enum member and class property + * + * e.g. + * ``` + * class Foo { + * abstract fn() + * //^^^^^^^^^^^^^ + * abstract x + * //^^^^^^^^^^ + * x + * //^ + * } + * ``` + * + * e.g. + * ``` + * enum Foo { Bar = x } + * // ^^^^^^^ + * ``` + * + * @param {TSESTree.TSAbstractMethodDefinition | TSESTree.TSAbstractClassProperty | TSESTree.TSEnumMember | TSESTree.ClassProperty} node + * + */ + 'TSAbstractMethodDefinition, TSAbstractClassProperty, TSEnumMember, ClassProperty'( + node + ) { + const { keyNode, valueNode } = + node.type === 'TSEnumMember' + ? { keyNode: node.id, valueNode: node.initializer } + : { keyNode: node.key, valueNode: node.value } + const firstToken = tokenStore.getFirstToken(node) + const keyTokens = getFirstAndLastTokens(keyNode) + const prefixTokens = tokenStore.getTokensBetween( + firstToken, + keyTokens.firstToken + ) + if (node.computed) { + prefixTokens.pop() // pop [ + } + setOffset(prefixTokens, 0, firstToken) + let lastKeyToken + if (node.computed) { + const leftBracketToken = tokenStore.getTokenBefore(keyTokens.firstToken) + const rightBracketToken = (lastKeyToken = tokenStore.getTokenAfter( + keyTokens.lastToken + )) + setOffset(leftBracketToken, 0, firstToken) + processNodeList([keyNode], leftBracketToken, rightBracketToken, 1) + } else { + setOffset(keyTokens.firstToken, 0, firstToken) + lastKeyToken = keyTokens.lastToken + } + + if (valueNode != null) { + const initToken = tokenStore.getFirstToken(valueNode) + setOffset( + [...tokenStore.getTokensBetween(lastKeyToken, initToken), initToken], + 1, + lastKeyToken + ) + } + }, + + /** + * Process optional type, non-null expression and JSDocNonNullableType + * + * e.g. + * ``` + * type Foo = [number?] + * // ^^^^^^^ + * const a = v! + * // ^ + * type T = U! + * // ^^ + * ``` + * + * @param {TSESTree.TSOptionalType, TSESTree.TSNonNullExpression} node + */ + 'TSOptionalType, TSNonNullExpression, TSJSDocNonNullableType'(node) { + setOffset( + tokenStore.getLastToken(node), + 1, + tokenStore.getFirstToken(node) + ) + }, + TSTypeAssertion(node) { + // + const firstToken = tokenStore.getFirstToken(node) + const expressionToken = getFirstAndLastTokens(node.expression).firstToken + processNodeList( + [node.typeAnnotation], + firstToken, + tokenStore.getTokenBefore(expressionToken), + 1 + ) + setOffset(expressionToken, 1, firstToken) + }, + /** + * Process import type + * + * e.g. + * ``` + * const foo: import('foo').Bar + * // ^^^^^^^^^^^^^^^^^^^^ + * ``` + */ + TSImportType(node) { + const firstToken = tokenStore.getFirstToken(node) + const leftParenToken = tokenStore.getTokenAfter( + firstToken, + isOpeningParenToken + ) + setOffset(leftParenToken, 1, firstToken) + const rightParenToken = tokenStore.getTokenAfter( + node.parameter, + isClosingParenToken + ) + processNodeList([node.parameter], leftParenToken, rightParenToken, 1) + if (node.qualifier) { + const dotToken = tokenStore.getTokenBefore(node.qualifier) + const propertyToken = tokenStore.getTokenAfter(dotToken) + setOffset([dotToken, propertyToken], 1, firstToken) + } + if (node.typeParameters) { + setOffset(tokenStore.getFirstToken(node.typeParameters), 1, firstToken) + } + }, + TSParameterProperty(node) { + // constructor(private a) + const firstToken = tokenStore.getFirstToken(node) + const parameterToken = tokenStore.getFirstToken(node.parameter) + setOffset( + [ + ...tokenStore.getTokensBetween(firstToken, parameterToken), + parameterToken + ], + 1, + firstToken + ) + }, + /** + * Process import equal + * + * e.g. + * ``` + * import foo = require('foo') + * ``` + */ + TSImportEqualsDeclaration(node) { + const importToken = tokenStore.getFirstToken(node) + const idTokens = getFirstAndLastTokens(node.id) + setOffset(idTokens.firstToken, 1, importToken) + const opToken = tokenStore.getTokenAfter(idTokens.lastToken) + setOffset( + [opToken, tokenStore.getFirstToken(node.moduleReference)], + 1, + idTokens.lastToken + ) + }, + /** + * Process external module reference + * + * e.g. + * ``` + * import foo = require('foo') + * // ^^^^^^^^^^^^^^ + * ``` + */ + TSExternalModuleReference(node) { + const requireToken = tokenStore.getFirstToken(node) + const leftParenToken = tokenStore.getTokenAfter( + requireToken, + isOpeningParenToken + ) + const rightParenToken = tokenStore.getLastToken(node) + setOffset(leftParenToken, 1, requireToken) + processNodeList([node.expression], leftParenToken, rightParenToken, 1) + }, + /** + * Process export assignment + * + * e.g. + * ``` + * export = foo + * ``` + */ + TSExportAssignment(node) { + const exportNode = tokenStore.getFirstToken(node) + const exprTokens = getFirstAndLastTokens(node.expression) + const opToken = tokenStore.getTokenBefore(exprTokens.firstToken) + setOffset([opToken, exprTokens.firstToken], 1, exportNode) + }, + TSNamedTupleMember(node) { + // [a: string, ...b: string[]] + // ^^^^^^^^^ + const labelToken = tokenStore.getFirstToken(node) + const elementTokens = getFirstAndLastTokens(node.elementType) + setOffset( + [ + ...tokenStore.getTokensBetween(labelToken, elementTokens.firstToken), + elementTokens.firstToken + ], + 1, + labelToken + ) + }, + TSRestType(node) { + // [a: string, ...b: string[]] + // ^^^^^^^^^^^^^^ + const firstToken = tokenStore.getFirstToken(node) + const nextToken = tokenStore.getTokenAfter(firstToken) + setOffset(nextToken, 1, firstToken) + }, + TSNamespaceExportDeclaration(node) { + const firstToken = tokenStore.getFirstToken(node) + const idToken = tokenStore.getFirstToken(node.id) + setOffset( + [...tokenStore.getTokensBetween(firstToken, idToken), idToken], + 1, + firstToken + ) + }, + TSTemplateLiteralType(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) + }, + // ---------------------------------------------------------------------- + // NON-STANDARD NODES + // ---------------------------------------------------------------------- + Decorator(node) { + // @Decorator + const [atToken, secondToken] = tokenStore.getFirstTokens(node, { + count: 2, + includeComments: false + }) + setOffset(secondToken, 0, atToken) + const parent = node.parent + const { decorators } = parent + if (!decorators || decorators.length === 0) { + return + } + if (decorators[0] === node) { + if (parent.range[0] === node.range[0]) { + const startParentToken = tokenStore.getTokenAfter( + decorators[decorators.length - 1] + ) + setOffset(startParentToken, 0, atToken) + } else { + const startParentToken = tokenStore.getFirstToken(parent) + copyOffset(atToken, startParentToken) + } + } else { + setOffset(atToken, 0, tokenStore.getFirstToken(decorators[0])) + } + }, + // ---------------------------------------------------------------------- + // SINGLE TOKEN NODES + // ---------------------------------------------------------------------- + // VALUES KEYWORD + TSAnyKeyword() {}, + TSBigIntKeyword() {}, + TSBooleanKeyword() {}, + TSNeverKeyword() {}, + TSNullKeyword() {}, + TSNumberKeyword() {}, + TSObjectKeyword() {}, + TSStringKeyword() {}, + TSSymbolKeyword() {}, + TSUndefinedKeyword() {}, + TSUnknownKeyword() {}, + TSVoidKeyword() {}, + // MODIFIERS KEYWORD + TSAbstractKeyword() {}, + TSAsyncKeyword() {}, + TSPrivateKeyword() {}, + TSProtectedKeyword() {}, + TSPublicKeyword() {}, + TSReadonlyKeyword() {}, + TSStaticKeyword() {}, + // OTHERS KEYWORD + TSDeclareKeyword() {}, + TSExportKeyword() {}, + TSIntrinsicKeyword() {}, + // OTHERS + TSThisType() {}, + // ---------------------------------------------------------------------- + // WRAPPER NODES + // ---------------------------------------------------------------------- + TSLiteralType() {}, + // ---------------------------------------------------------------------- + // JSX + // ---------------------------------------------------------------------- + JSXElement(node) { + processNodeList( + node.children, + node.openingElement, + node.closingElement, + 1 + ) + }, + JSXFragment(node) { + processNodeList( + node.children, + node.openingFragment, + node.closingFragment, + 1 + ) + }, + JSXOpeningElement(node) { + const openToken = tokenStore.getFirstToken(node) + const closeToken = tokenStore.getLastToken(node) + const nameToken = tokenStore.getFirstToken(node.name) + setOffset(nameToken, 1, openToken) + if (node.typeParameters) { + setOffset(tokenStore.getFirstToken(node.typeParameters), 1, nameToken) + } + for (const attr of node.attributes) { + setOffset(tokenStore.getFirstToken(attr), 1, openToken) + } + if (node.selfClosing) { + const slash = tokenStore.getTokenBefore(closeToken) + if (slash.value === '/') { + setOffset(slash, 0, openToken) + } + } + setOffset(closeToken, 0, openToken) + }, + JSXClosingElement(node) { + const openToken = tokenStore.getFirstToken(node) + const slash = tokenStore.getTokenAfter(openToken) + if (slash.value === '/') { + setOffset(slash, 0, openToken) + } + const closeToken = tokenStore.getLastToken(node) + const nameToken = tokenStore.getFirstToken(node.name) + setOffset(nameToken, 1, openToken) + setOffset(closeToken, 0, openToken) + }, + /** @param {TSESTree.JSXOpeningFragment | TSESTree.JSXClosingFragment} node */ + 'JSXOpeningFragment, JSXClosingFragment'(node) { + const [firstToken, ...tokens] = tokenStore.getTokens(node) + setOffset(tokens, 0, firstToken) + }, + JSXAttribute(node) { + if (!node.value) { + return + } + const keyToken = tokenStore.getFirstToken(node) + const eqToken = tokenStore.getTokenAfter(node.name) + setOffset(eqToken, 1, keyToken) + setOffset(tokenStore.getFirstToken(node.value), 1, keyToken) + }, + JSXExpressionContainer(node) { + processNodeList( + [node.expression], + tokenStore.getFirstToken(node), + tokenStore.getLastToken(node), + 1 + ) + }, + JSXSpreadAttribute(node) { + processNodeList( + [node.argument], + tokenStore.getFirstToken(node), + tokenStore.getLastToken(node), + 1 + ) + }, + JSXSpreadChild(node) { + processNodeList( + [node.expression], + tokenStore.getFirstToken(node), + tokenStore.getLastToken(node), + 1 + ) + }, + JSXMemberExpression(node) { + const objectToken = tokenStore.getFirstToken(node) + const dotToken = tokenStore.getTokenBefore(node.property) + const propertyToken = tokenStore.getTokenAfter(dotToken) + setOffset([dotToken, propertyToken], 1, objectToken) + }, + // ---------------------------------------------------------------------- + // JSX SINGLE TOKEN NODES + // ---------------------------------------------------------------------- + JSXEmptyExpression() {}, + JSXIdentifier() {}, + JSXNamespacedName() {}, + JSXText() {} + } +} + +/** + * Check whether the given node or token is the beginning of a line. + */ +function isBeginningOfLine(tokenStore, node) { + const prevToken = tokenStore.getTokenBefore(node, { includeComments: false }) + return !prevToken || prevToken.loc.end.line < node.loc.start.line +} diff --git a/lib/utils/indent-utils.js b/lib/utils/indent-utils.js new file mode 100644 index 000000000..0a7f25dec --- /dev/null +++ b/lib/utils/indent-utils.js @@ -0,0 +1,113 @@ +'use strict' + +/** + * Check whether the given token is a wildcard. + * @param {Token|undefined|null} token The token to check. + * @returns {boolean} `true` if the token is a wildcard. + */ +function isWildcard(token) { + return token != null && token.type === 'Punctuator' && token.value === '*' +} + +/** + * Check whether the given token is a question. + * @param {Token|undefined|null} token The token to check. + * @returns {boolean} `true` if the token is a question. + */ +function isQuestion(token) { + return token != null && token.type === 'Punctuator' && token.value === '?' +} + +/** + * Check whether the given token is an extends keyword. + * @param {Token|undefined|null} token The token to check. + * @returns {boolean} `true` if the token is an extends keywordn. + */ +function isExtendsKeyword(token) { + return token != null && token.type === 'Keyword' && token.value === 'extends' +} + +/** + * Check whether the given token is a whitespace. + * @param {Token|undefined|null} 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|undefined|null} 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' || + (typeof token.type === + 'string' /* Although acorn supports new tokens, espree may not yet support new tokens.*/ && + token.type.endsWith('Comment'))) + ) +} + +/** + * Check whether the given token is a comment. + * @param {Token|undefined|null} 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' && + !( + typeof token.type === + 'string' /* Although acorn supports new tokens, espree may not yet support new tokens.*/ && + token.type.endsWith('Comment') + ) + ) +} + +/** + * Check whether the given node is not an empty text node. + * @param {ASTNode} node The node to check. + * @returns {boolean} `false` if the token is empty text node. + */ +function isNotEmptyTextNode(node) { + return !(node.type === 'VText' && node.value.trim() === '') +} + +/** + * Check whether the given token is a pipe operator. + * @param {Token|undefined|null} token The token to check. + * @returns {boolean} `true` if the token is a pipe operator. + */ +function isPipeOperator(token) { + return token != null && token.type === 'Punctuator' && token.value === '|' +} + +/** + * Get the last element. + * @template T + * @param {T[]} xs The array to get the last element. + * @returns {T | undefined} The last element or undefined. + */ +function last(xs) { + return xs.length === 0 ? undefined : xs[xs.length - 1] +} + +module.exports = { + isWildcard, + isQuestion, + isExtendsKeyword, + isNotWhitespace, + isComment, + isNotComment, + isNotEmptyTextNode, + isPipeOperator, + last +} diff --git a/package.json b/package.json index d5b43b412..180920872 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@types/natural-compare": "^1.4.0", "@types/node": "^13.13.5", "@types/semver": "^7.2.0", - "@typescript-eslint/parser": "^3.0.2", + "@typescript-eslint/parser": "^4.28.0", "@vuepress/plugin-pwa": "^1.4.1", "babel-eslint": "^10.1.0", "env-cmd": "^10.1.0", diff --git a/tests/fixtures/script-indent/ignore-01.vue b/tests/fixtures/script-indent/ignore-01.vue new file mode 100644 index 000000000..e323cdc08 --- /dev/null +++ b/tests/fixtures/script-indent/ignore-01.vue @@ -0,0 +1,19 @@ + + diff --git a/tests/fixtures/script-indent/ignore-02.vue b/tests/fixtures/script-indent/ignore-02.vue new file mode 100644 index 000000000..18c557476 --- /dev/null +++ b/tests/fixtures/script-indent/ignore-02.vue @@ -0,0 +1,19 @@ + + diff --git a/tests/fixtures/script-indent/ts-abstract-class-property-01.vue b/tests/fixtures/script-indent/ts-abstract-class-property-01.vue new file mode 100644 index 000000000..ec38f5ff3 --- /dev/null +++ b/tests/fixtures/script-indent/ts-abstract-class-property-01.vue @@ -0,0 +1,22 @@ + + diff --git a/tests/fixtures/script-indent/ts-abstract-class-property-02.vue b/tests/fixtures/script-indent/ts-abstract-class-property-02.vue new file mode 100644 index 000000000..9e3214a0f --- /dev/null +++ b/tests/fixtures/script-indent/ts-abstract-class-property-02.vue @@ -0,0 +1,17 @@ + + diff --git a/tests/fixtures/script-indent/ts-abstract-method-definition-01.vue b/tests/fixtures/script-indent/ts-abstract-method-definition-01.vue new file mode 100644 index 000000000..bed8e269e --- /dev/null +++ b/tests/fixtures/script-indent/ts-abstract-method-definition-01.vue @@ -0,0 +1,30 @@ + + diff --git a/tests/fixtures/script-indent/ts-as-expression-01.vue b/tests/fixtures/script-indent/ts-as-expression-01.vue new file mode 100644 index 000000000..869101939 --- /dev/null +++ b/tests/fixtures/script-indent/ts-as-expression-01.vue @@ -0,0 +1,6 @@ + + diff --git a/tests/fixtures/script-indent/ts-call-signature-declaration-01.vue b/tests/fixtures/script-indent/ts-call-signature-declaration-01.vue new file mode 100644 index 000000000..a6b14b935 --- /dev/null +++ b/tests/fixtures/script-indent/ts-call-signature-declaration-01.vue @@ -0,0 +1,13 @@ + + diff --git a/tests/fixtures/script-indent/ts-call-signature-declaration-02.vue b/tests/fixtures/script-indent/ts-call-signature-declaration-02.vue new file mode 100644 index 000000000..7ef9cce2b --- /dev/null +++ b/tests/fixtures/script-indent/ts-call-signature-declaration-02.vue @@ -0,0 +1,13 @@ + + diff --git a/tests/fixtures/script-indent/ts-class-declaration-01.vue b/tests/fixtures/script-indent/ts-class-declaration-01.vue new file mode 100644 index 000000000..972eb91d2 --- /dev/null +++ b/tests/fixtures/script-indent/ts-class-declaration-01.vue @@ -0,0 +1,11 @@ + + diff --git a/tests/fixtures/script-indent/ts-class-declaration-02.vue b/tests/fixtures/script-indent/ts-class-declaration-02.vue new file mode 100644 index 000000000..6ddf94570 --- /dev/null +++ b/tests/fixtures/script-indent/ts-class-declaration-02.vue @@ -0,0 +1,16 @@ + + diff --git a/tests/fixtures/script-indent/ts-class-declaration-03.vue b/tests/fixtures/script-indent/ts-class-declaration-03.vue new file mode 100644 index 000000000..206c64347 --- /dev/null +++ b/tests/fixtures/script-indent/ts-class-declaration-03.vue @@ -0,0 +1,26 @@ + + diff --git a/tests/fixtures/script-indent/ts-class-declaration-04.vue b/tests/fixtures/script-indent/ts-class-declaration-04.vue new file mode 100644 index 000000000..1b7e09fdb --- /dev/null +++ b/tests/fixtures/script-indent/ts-class-declaration-04.vue @@ -0,0 +1,26 @@ + + diff --git a/tests/fixtures/script-indent/ts-class-declaration-05.vue b/tests/fixtures/script-indent/ts-class-declaration-05.vue new file mode 100644 index 000000000..69bff1cc1 --- /dev/null +++ b/tests/fixtures/script-indent/ts-class-declaration-05.vue @@ -0,0 +1,14 @@ + + diff --git a/tests/fixtures/script-indent/ts-class-property-01.vue b/tests/fixtures/script-indent/ts-class-property-01.vue new file mode 100644 index 000000000..8f55b5483 --- /dev/null +++ b/tests/fixtures/script-indent/ts-class-property-01.vue @@ -0,0 +1,15 @@ + + diff --git a/tests/fixtures/script-indent/ts-class-property-02.vue b/tests/fixtures/script-indent/ts-class-property-02.vue new file mode 100644 index 000000000..71c059b7c --- /dev/null +++ b/tests/fixtures/script-indent/ts-class-property-02.vue @@ -0,0 +1,17 @@ + + diff --git a/tests/fixtures/script-indent/ts-class-property-03.vue b/tests/fixtures/script-indent/ts-class-property-03.vue new file mode 100644 index 000000000..7a44d6a7d --- /dev/null +++ b/tests/fixtures/script-indent/ts-class-property-03.vue @@ -0,0 +1,36 @@ + + diff --git a/tests/fixtures/script-indent/ts-conditional-type-01.vue b/tests/fixtures/script-indent/ts-conditional-type-01.vue new file mode 100644 index 000000000..188ee1654 --- /dev/null +++ b/tests/fixtures/script-indent/ts-conditional-type-01.vue @@ -0,0 +1,10 @@ + + diff --git a/tests/fixtures/script-indent/ts-conditional-type-02.vue b/tests/fixtures/script-indent/ts-conditional-type-02.vue new file mode 100644 index 000000000..778ae1ec1 --- /dev/null +++ b/tests/fixtures/script-indent/ts-conditional-type-02.vue @@ -0,0 +1,10 @@ + + diff --git a/tests/fixtures/script-indent/ts-conditional-type-03.vue b/tests/fixtures/script-indent/ts-conditional-type-03.vue new file mode 100644 index 000000000..8735d9f8b --- /dev/null +++ b/tests/fixtures/script-indent/ts-conditional-type-03.vue @@ -0,0 +1,17 @@ + + diff --git a/tests/fixtures/script-indent/ts-constructor-type-01.vue b/tests/fixtures/script-indent/ts-constructor-type-01.vue new file mode 100644 index 000000000..07a3d7f59 --- /dev/null +++ b/tests/fixtures/script-indent/ts-constructor-type-01.vue @@ -0,0 +1,21 @@ + + diff --git a/tests/fixtures/script-indent/ts-declare-function-01.vue b/tests/fixtures/script-indent/ts-declare-function-01.vue new file mode 100644 index 000000000..164611be6 --- /dev/null +++ b/tests/fixtures/script-indent/ts-declare-function-01.vue @@ -0,0 +1,12 @@ + + diff --git a/tests/fixtures/script-indent/ts-declare-function-02.vue b/tests/fixtures/script-indent/ts-declare-function-02.vue new file mode 100644 index 000000000..fcca210b0 --- /dev/null +++ b/tests/fixtures/script-indent/ts-declare-function-02.vue @@ -0,0 +1,12 @@ + + diff --git a/tests/fixtures/script-indent/ts-declare-function-03.vue b/tests/fixtures/script-indent/ts-declare-function-03.vue new file mode 100644 index 000000000..397c36f0f --- /dev/null +++ b/tests/fixtures/script-indent/ts-declare-function-03.vue @@ -0,0 +1,12 @@ + + diff --git a/tests/fixtures/script-indent/ts-declare-function-04.vue b/tests/fixtures/script-indent/ts-declare-function-04.vue new file mode 100644 index 000000000..3a62cc896 --- /dev/null +++ b/tests/fixtures/script-indent/ts-declare-function-04.vue @@ -0,0 +1,16 @@ + + diff --git a/tests/fixtures/script-indent/ts-enum-01.vue b/tests/fixtures/script-indent/ts-enum-01.vue new file mode 100644 index 000000000..2b438689d --- /dev/null +++ b/tests/fixtures/script-indent/ts-enum-01.vue @@ -0,0 +1,9 @@ + + diff --git a/tests/fixtures/script-indent/ts-enum-02.vue b/tests/fixtures/script-indent/ts-enum-02.vue new file mode 100644 index 000000000..03034cef1 --- /dev/null +++ b/tests/fixtures/script-indent/ts-enum-02.vue @@ -0,0 +1,9 @@ + + diff --git a/tests/fixtures/script-indent/ts-enum-03.vue b/tests/fixtures/script-indent/ts-enum-03.vue new file mode 100644 index 000000000..53f6d2e51 --- /dev/null +++ b/tests/fixtures/script-indent/ts-enum-03.vue @@ -0,0 +1,12 @@ + + diff --git a/tests/fixtures/script-indent/ts-enum-04.vue b/tests/fixtures/script-indent/ts-enum-04.vue new file mode 100644 index 000000000..2995e29ab --- /dev/null +++ b/tests/fixtures/script-indent/ts-enum-04.vue @@ -0,0 +1,7 @@ + + diff --git a/tests/fixtures/script-indent/ts-enum-05.vue b/tests/fixtures/script-indent/ts-enum-05.vue new file mode 100644 index 000000000..6b266680c --- /dev/null +++ b/tests/fixtures/script-indent/ts-enum-05.vue @@ -0,0 +1,8 @@ + + diff --git a/tests/fixtures/script-indent/ts-enum-06.vue b/tests/fixtures/script-indent/ts-enum-06.vue new file mode 100644 index 000000000..aae884251 --- /dev/null +++ b/tests/fixtures/script-indent/ts-enum-06.vue @@ -0,0 +1,10 @@ + + diff --git a/tests/fixtures/script-indent/ts-enum-member-01.vue b/tests/fixtures/script-indent/ts-enum-member-01.vue new file mode 100644 index 000000000..925dc7b32 --- /dev/null +++ b/tests/fixtures/script-indent/ts-enum-member-01.vue @@ -0,0 +1,15 @@ + + diff --git a/tests/fixtures/script-indent/ts-enum-member-02.vue b/tests/fixtures/script-indent/ts-enum-member-02.vue new file mode 100644 index 000000000..04cb70b31 --- /dev/null +++ b/tests/fixtures/script-indent/ts-enum-member-02.vue @@ -0,0 +1,26 @@ + + diff --git a/tests/fixtures/script-indent/ts-export-assignment-01.vue b/tests/fixtures/script-indent/ts-export-assignment-01.vue new file mode 100644 index 000000000..f19bd6611 --- /dev/null +++ b/tests/fixtures/script-indent/ts-export-assignment-01.vue @@ -0,0 +1,5 @@ + + diff --git a/tests/fixtures/script-indent/ts-export-assignment-02.vue b/tests/fixtures/script-indent/ts-export-assignment-02.vue new file mode 100644 index 000000000..8844b3744 --- /dev/null +++ b/tests/fixtures/script-indent/ts-export-assignment-02.vue @@ -0,0 +1,13 @@ + + diff --git a/tests/fixtures/script-indent/ts-function-type-01.vue b/tests/fixtures/script-indent/ts-function-type-01.vue new file mode 100644 index 000000000..be51a6404 --- /dev/null +++ b/tests/fixtures/script-indent/ts-function-type-01.vue @@ -0,0 +1,6 @@ + + diff --git a/tests/fixtures/script-indent/ts-function-type-02.vue b/tests/fixtures/script-indent/ts-function-type-02.vue new file mode 100644 index 000000000..bd976af7c --- /dev/null +++ b/tests/fixtures/script-indent/ts-function-type-02.vue @@ -0,0 +1,17 @@ + + diff --git a/tests/fixtures/script-indent/ts-import-equal-01.vue b/tests/fixtures/script-indent/ts-import-equal-01.vue new file mode 100644 index 000000000..e2136f100 --- /dev/null +++ b/tests/fixtures/script-indent/ts-import-equal-01.vue @@ -0,0 +1,6 @@ + + diff --git a/tests/fixtures/script-indent/ts-import-equal-02.vue b/tests/fixtures/script-indent/ts-import-equal-02.vue new file mode 100644 index 000000000..b4a7146c4 --- /dev/null +++ b/tests/fixtures/script-indent/ts-import-equal-02.vue @@ -0,0 +1,19 @@ + + diff --git a/tests/fixtures/script-indent/ts-import-equal-03.vue b/tests/fixtures/script-indent/ts-import-equal-03.vue new file mode 100644 index 000000000..066306f07 --- /dev/null +++ b/tests/fixtures/script-indent/ts-import-equal-03.vue @@ -0,0 +1,13 @@ + + diff --git a/tests/fixtures/script-indent/ts-import-type-01.vue b/tests/fixtures/script-indent/ts-import-type-01.vue new file mode 100644 index 000000000..8c224b3c9 --- /dev/null +++ b/tests/fixtures/script-indent/ts-import-type-01.vue @@ -0,0 +1,8 @@ + + diff --git a/tests/fixtures/script-indent/ts-import-type-02.vue b/tests/fixtures/script-indent/ts-import-type-02.vue new file mode 100644 index 000000000..c869a5be3 --- /dev/null +++ b/tests/fixtures/script-indent/ts-import-type-02.vue @@ -0,0 +1,18 @@ + + diff --git a/tests/fixtures/script-indent/ts-import-type-03.vue b/tests/fixtures/script-indent/ts-import-type-03.vue new file mode 100644 index 000000000..cdbba7a14 --- /dev/null +++ b/tests/fixtures/script-indent/ts-import-type-03.vue @@ -0,0 +1,9 @@ + + diff --git a/tests/fixtures/script-indent/ts-indexed-access-type-01.vue b/tests/fixtures/script-indent/ts-indexed-access-type-01.vue new file mode 100644 index 000000000..4cb163bdf --- /dev/null +++ b/tests/fixtures/script-indent/ts-indexed-access-type-01.vue @@ -0,0 +1,9 @@ + + diff --git a/tests/fixtures/script-indent/ts-indexed-access-type-02.vue b/tests/fixtures/script-indent/ts-indexed-access-type-02.vue new file mode 100644 index 000000000..8c6e9381e --- /dev/null +++ b/tests/fixtures/script-indent/ts-indexed-access-type-02.vue @@ -0,0 +1,11 @@ + + diff --git a/tests/fixtures/script-indent/ts-infer-01.vue b/tests/fixtures/script-indent/ts-infer-01.vue new file mode 100644 index 000000000..d7818834c --- /dev/null +++ b/tests/fixtures/script-indent/ts-infer-01.vue @@ -0,0 +1,7 @@ + + diff --git a/tests/fixtures/script-indent/ts-interface-declaration-01.vue b/tests/fixtures/script-indent/ts-interface-declaration-01.vue new file mode 100644 index 000000000..f3584b7f8 --- /dev/null +++ b/tests/fixtures/script-indent/ts-interface-declaration-01.vue @@ -0,0 +1,7 @@ + + diff --git a/tests/fixtures/script-indent/ts-interface-declaration-02.vue b/tests/fixtures/script-indent/ts-interface-declaration-02.vue new file mode 100644 index 000000000..0ffa8677c --- /dev/null +++ b/tests/fixtures/script-indent/ts-interface-declaration-02.vue @@ -0,0 +1,15 @@ + + diff --git a/tests/fixtures/script-indent/ts-interface-declaration-03.vue b/tests/fixtures/script-indent/ts-interface-declaration-03.vue new file mode 100644 index 000000000..9aaabb813 --- /dev/null +++ b/tests/fixtures/script-indent/ts-interface-declaration-03.vue @@ -0,0 +1,17 @@ + + diff --git a/tests/fixtures/script-indent/ts-interface-declaration-04.vue b/tests/fixtures/script-indent/ts-interface-declaration-04.vue new file mode 100644 index 000000000..3d99d6614 --- /dev/null +++ b/tests/fixtures/script-indent/ts-interface-declaration-04.vue @@ -0,0 +1,19 @@ + + diff --git a/tests/fixtures/script-indent/ts-type-alias-seclaration-01.vue b/tests/fixtures/script-indent/ts-type-alias-seclaration-01.vue new file mode 100644 index 000000000..29c696859 --- /dev/null +++ b/tests/fixtures/script-indent/ts-type-alias-seclaration-01.vue @@ -0,0 +1,21 @@ + + diff --git a/tests/fixtures/script-indent/ts-type-annotation-01.vue b/tests/fixtures/script-indent/ts-type-annotation-01.vue new file mode 100644 index 000000000..66981525e --- /dev/null +++ b/tests/fixtures/script-indent/ts-type-annotation-01.vue @@ -0,0 +1,9 @@ + + diff --git a/tests/fixtures/script-indent/ts-type-annotation-02.vue b/tests/fixtures/script-indent/ts-type-annotation-02.vue new file mode 100644 index 000000000..8f5a3b84c --- /dev/null +++ b/tests/fixtures/script-indent/ts-type-annotation-02.vue @@ -0,0 +1,33 @@ + + diff --git a/tests/fixtures/script-indent/ts-type-annotation-03.vue b/tests/fixtures/script-indent/ts-type-annotation-03.vue new file mode 100644 index 000000000..ed1bab907 --- /dev/null +++ b/tests/fixtures/script-indent/ts-type-annotation-03.vue @@ -0,0 +1,28 @@ + + diff --git a/tests/fixtures/script-indent/ts-type-annotation-04.vue b/tests/fixtures/script-indent/ts-type-annotation-04.vue new file mode 100644 index 000000000..1450696bb --- /dev/null +++ b/tests/fixtures/script-indent/ts-type-annotation-04.vue @@ -0,0 +1,25 @@ + + diff --git a/tests/fixtures/script-indent/ts-type-annotation-05.vue b/tests/fixtures/script-indent/ts-type-annotation-05.vue new file mode 100644 index 000000000..aeb3b7bc3 --- /dev/null +++ b/tests/fixtures/script-indent/ts-type-annotation-05.vue @@ -0,0 +1,21 @@ + + diff --git a/tests/fixtures/script-indent/ts-type-annotation-06.vue b/tests/fixtures/script-indent/ts-type-annotation-06.vue new file mode 100644 index 000000000..11ca0c386 --- /dev/null +++ b/tests/fixtures/script-indent/ts-type-annotation-06.vue @@ -0,0 +1,17 @@ + + diff --git a/tests/fixtures/script-indent/ts-type-parameter-seclaration-01.vue b/tests/fixtures/script-indent/ts-type-parameter-seclaration-01.vue new file mode 100644 index 000000000..8b216f458 --- /dev/null +++ b/tests/fixtures/script-indent/ts-type-parameter-seclaration-01.vue @@ -0,0 +1,17 @@ + + diff --git a/typings/eslint-plugin-vue/util-types/ast/ts-ast.ts b/typings/eslint-plugin-vue/util-types/ast/ts-ast.ts index 28dc29fe2..91319043b 100644 --- a/typings/eslint-plugin-vue/util-types/ast/ts-ast.ts +++ b/typings/eslint-plugin-vue/util-types/ast/ts-ast.ts @@ -8,4 +8,5 @@ export type TSNode = TSAsExpression export interface TSAsExpression extends HasParentNode { type: 'TSAsExpression' expression: ES.Expression + typeAnnotation: any; } diff --git a/typings/eslint-plugin-vue/util-types/indent-helper.ts b/typings/eslint-plugin-vue/util-types/indent-helper.ts new file mode 100644 index 000000000..f9260c7e5 --- /dev/null +++ b/typings/eslint-plugin-vue/util-types/indent-helper.ts @@ -0,0 +1,9 @@ +import type { TSESTree } from "@typescript-eslint/types" +import type * as ESTree from "estree" +type TSNodeWithoutES = Exclude +type TSNodeListenerMap = { + [key in TSNodeWithoutES["type"]]: T extends { type: key } ? T : never +} +export type TSNodeListener = { + [T in keyof TSNodeListenerMap]: (node: TSNodeListenerMap[T]) => void +} diff --git a/typings/eslint-utils/index.d.ts b/typings/eslint-utils/index.d.ts index a97522876..60a59f7c0 100644 --- a/typings/eslint-utils/index.d.ts +++ b/typings/eslint-utils/index.d.ts @@ -72,4 +72,25 @@ export namespace ReferenceTracker { const ESM: unique symbol } -export function isCommentToken(token: Token): token is Comment +export function isArrowToken(token: Token): boolean +export function isCommaToken(token: Token): boolean +export function isSemicolonToken(token: Token): boolean +export function isColonToken(token: Token): boolean +export function isOpeningParenToken(token: Token): boolean +export function isClosingParenToken(token: Token): boolean +export function isOpeningBracketToken(token: Token): boolean +export function isClosingBracketToken(token: Token): boolean +export function isOpeningBraceToken(token: Token): boolean +export function isClosingBraceToken(token: Token): boolean +// export function isCommentToken(token: Token): token is Comment +export function isNotArrowToken(token: Token): boolean +export function isNotCommaToken(token: Token): boolean +export function isNotSemicolonToken(token: Token): boolean +export function isNotColonToken(token: Token): boolean +export function isNotOpeningParenToken(token: Token): boolean +export function isNotClosingParenToken(token: Token): boolean +export function isNotOpeningBracketToken(token: Token): boolean +export function isNotClosingBracketToken(token: Token): boolean +export function isNotOpeningBraceToken(token: Token): boolean +export function isNotClosingBraceToken(token: Token): boolean +// export function isNotCommentToken(token: Token): boolean From 037aeb12c6c6fe2f9a09e92020343df491aba80c Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Tue, 22 Jun 2021 18:30:27 +0900 Subject: [PATCH 2/9] fix --- lib/utils/indent-common.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js index 4e17ab4d5..aa55680f8 100644 --- a/lib/utils/indent-common.js +++ b/lib/utils/indent-common.js @@ -468,6 +468,7 @@ module.exports.defineVisitor = function create( const prevToken = tokenStore.getTokenBefore(firstToken) if ( info != null && + prevToken && isSemicolonToken(prevToken) && prevToken.loc.end.line === firstToken.loc.start.line ) { From 7e71af12b768a45f1828182e3919dd1cd9215705 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Tue, 22 Jun 2021 18:35:48 +0900 Subject: [PATCH 3/9] Update docs --- docs/rules/html-indent.md | 2 +- docs/rules/script-indent.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/rules/html-indent.md b/docs/rules/html-indent.md index 61ecf57be..18fddf12d 100644 --- a/docs/rules/html-indent.md +++ b/docs/rules/html-indent.md @@ -17,7 +17,7 @@ since: v3.14.0 This rule enforces a consistent indentation style in `