feat: Reinitialize frontend with SvelteKit and TypeScript

- Delete old Vite+Svelte frontend
- Initialize new SvelteKit project with TypeScript
- Configure Tailwind CSS v4 + DaisyUI
- Implement JWT authentication with auto-refresh
- Create login page with form validation (Zod)
- Add protected route guards
- Update Docker configuration for single-stage build
- Add E2E tests with Playwright (6/11 passing)
- Fix Svelte 5 reactivity with $state() runes

Known issues:
- 5 E2E tests failing (timing/async issues)
- Token refresh implementation needs debugging
- Validation error display timing
This commit is contained in:
2026-02-17 16:19:59 -05:00
parent 54df6018f5
commit de2d83092e
28274 changed files with 3816354 additions and 90 deletions

View File

@@ -0,0 +1,12 @@
import type { AST } from 'svelte-eslint-parser';
import type { TSESTree } from '@typescript-eslint/types';
type AnyToken = AST.Token | AST.Comment;
/**
* Check whether the given token is a whitespace.
*/
export declare function isWhitespace(token: AnyToken | TSESTree.Comment | null | undefined): boolean;
/**
* Check whether the given token is a not whitespace.
*/
export declare function isNotWhitespace(token: AnyToken | TSESTree.Comment | null | undefined): boolean;
export {};

View File

@@ -0,0 +1,16 @@
/**
* Check whether the given token is a whitespace.
*/
export function isWhitespace(token) {
return (token != null &&
((token.type === 'HTMLText' && !token.value.trim()) ||
(token.type === 'JSXText' && !token.value.trim())));
}
/**
* Check whether the given token is a not whitespace.
*/
export function isNotWhitespace(token) {
return (token != null &&
(token.type !== 'HTMLText' || Boolean(token.value.trim())) &&
(token.type !== 'JSXText' || Boolean(token.value.trim())));
}

View File

@@ -0,0 +1,39 @@
import type { ASTNode, SourceCode } from '../../types.js';
import type { AST } from 'svelte-eslint-parser';
import type { OffsetContext } from './offset-context.js';
export type AnyToken = AST.Token | AST.Comment;
export type MaybeNode = {
type: string;
range: [number, number];
loc: AST.SourceLocation;
};
export type IndentOptions = {
indentChar: ' ' | '\t';
indentScript: boolean;
indentSize: number;
switchCase: number;
alignAttributesVertically: boolean;
ignoredNodes: string[];
};
export type IndentContext = {
sourceCode: SourceCode;
options: IndentOptions;
offsets: OffsetContext;
};
/**
* Get the first and last tokens of the given node.
* If the node is parenthesized, this gets the outermost parentheses.
* If the node have whitespace at the start and the end, they will be skipped.
*/
export declare function getFirstAndLastTokens(sourceCode: SourceCode, node: ASTNode | AnyToken | MaybeNode, borderOffset?: number): {
firstToken: AST.Token;
lastToken: AST.Token;
};
/**
* Check whether the given node or token is the beginning of a line.
*/
export declare function isBeginningOfLine(sourceCode: SourceCode, node: ASTNode | AnyToken | MaybeNode): boolean;
/**
* Check whether the given node is the beginning of element.
*/
export declare function isBeginningOfElement(node: AST.SvelteText): boolean;

View File

@@ -0,0 +1,65 @@
import { isOpeningParenToken, isClosingParenToken } from '@eslint-community/eslint-utils';
import { isNotWhitespace, isWhitespace } from './ast.js';
/**
* Get the first and last tokens of the given node.
* If the node is parenthesized, this gets the outermost parentheses.
* If the node have whitespace at the start and the end, they will be skipped.
*/
export function getFirstAndLastTokens(sourceCode, node, borderOffset = 0) {
let firstToken = sourceCode.getFirstToken(node);
let lastToken = sourceCode.getLastToken(node);
// Get the outermost left parenthesis if it's parenthesized.
let left, right;
while ((left = sourceCode.getTokenBefore(firstToken)) != null &&
(right = sourceCode.getTokenAfter(lastToken)) != null &&
isOpeningParenToken(left) &&
isClosingParenToken(right) &&
borderOffset <= left.range[0]) {
firstToken = left;
lastToken = right;
}
while (isWhitespace(firstToken) && firstToken.range[0] < lastToken.range[0]) {
firstToken = sourceCode.getTokenAfter(firstToken);
}
while (isWhitespace(lastToken) && firstToken.range[0] < lastToken.range[0]) {
lastToken = sourceCode.getTokenBefore(lastToken);
}
return { firstToken, lastToken };
}
/**
* Check whether the given node or token is the beginning of a line.
*/
export function isBeginningOfLine(sourceCode, node) {
const prevToken = sourceCode.getTokenBefore(node, {
includeComments: false,
filter: isNotWhitespace
});
return !prevToken || prevToken.loc.end.line < node.loc.start.line;
}
/**
* Check whether the given node is the beginning of element.
*/
export function isBeginningOfElement(node) {
if (node.parent.type === 'SvelteElement' ||
node.parent.type === 'SvelteAwaitCatchBlock' ||
node.parent.type === 'SvelteAwaitPendingBlock' ||
node.parent.type === 'SvelteAwaitThenBlock' ||
node.parent.type === 'SvelteEachBlock' ||
node.parent.type === 'SvelteElseBlock' ||
node.parent.type === 'SvelteIfBlock' ||
node.parent.type === 'SvelteKeyBlock' ||
node.parent.type === 'SvelteSnippetBlock' ||
node.parent.type === 'SvelteStyleElement') {
return node.parent.children[0] === node;
}
if (node.parent.type === 'Program') {
return node.parent.body[0] === node;
}
return assertNever(node.parent);
}
/**
* Throws an error when invoked.
*/
function assertNever(value) {
throw new Error(`This part of the code should never be reached but ${value} made it through.`);
}

View File

@@ -0,0 +1,11 @@
import type { IndentContext } from './commons.js';
import type { ESNodeListener } from '../../types-for-node.js';
type NodeListener = ESNodeListener;
/**
* Creates AST event handlers for ES nodes.
*
* @param context The rule context.
* @returns AST event handlers.
*/
export declare function defineVisitor(context: IndentContext): NodeListener;
export {};

View File

@@ -0,0 +1,785 @@
import { getFirstAndLastTokens } from './commons.js';
import { isArrowToken, isClosingBraceToken, isClosingBracketToken, isClosingParenToken, isNotClosingParenToken, isNotOpeningParenToken, isNotSemicolonToken, isOpeningBraceToken, isOpeningBracketToken, isOpeningParenToken, isSemicolonToken } from '@eslint-community/eslint-utils';
import { getParent } from '../../utils/ast-utils.js';
/**
* Creates AST event handlers for ES nodes.
*
* @param context The rule context.
* @returns AST event handlers.
*/
export function defineVisitor(context) {
const { sourceCode, offsets, options } = context;
/**
* Find the head of chaining nodes.
*/
function getChainHeadNode(node) {
let target = node;
let parent = getParent(target);
while (parent &&
(parent.type === 'AssignmentExpression' ||
parent.type === 'AssignmentPattern' ||
parent.type === 'BinaryExpression' ||
parent.type === 'LogicalExpression')) {
const prevToken = sourceCode.getTokenBefore(target);
const nextToken = sourceCode.getTokenAfter(target);
if (prevToken &&
isOpeningParenToken(prevToken) &&
nextToken &&
isClosingParenToken(nextToken)) {
// The chain is broken because it is enclosed in parentheses.
break;
}
target = parent;
parent = getParent(target);
}
return target;
}
const visitor = {
Program(node) {
for (const body of node.body) {
if (body.type === 'SvelteText' && !body.value.trim()) {
continue;
}
offsets.setStartOffsetToken(sourceCode.getFirstToken(body), 0);
}
},
ArrayExpression(node) {
const firstToken = sourceCode.getFirstToken(node);
const rightToken = sourceCode.getTokenAfter(node.elements[node.elements.length - 1] || firstToken, { filter: isClosingBracketToken, includeComments: false });
offsets.setOffsetElementList(node.elements, firstToken, rightToken, 1);
},
ArrayPattern(node) {
visitor.ArrayExpression(node);
},
ArrowFunctionExpression(node) {
const [firstToken, secondToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
const leftToken = node.async ? secondToken : firstToken;
const arrowToken = sourceCode.getTokenBefore(node.body, {
filter: isArrowToken,
includeComments: false
});
if (node.async) {
offsets.setOffsetToken(secondToken, 1, firstToken);
}
if (isOpeningParenToken(leftToken)) {
const rightToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftToken, { filter: isClosingParenToken, includeComments: false });
offsets.setOffsetElementList(node.params, leftToken, rightToken, 1);
}
offsets.setOffsetToken(arrowToken, 1, firstToken);
const bodyFirstToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyFirstToken, isOpeningBraceToken(bodyFirstToken) ? 0 : 1, firstToken);
},
AssignmentExpression(node) {
const baseNode = getChainHeadNode(node);
const opToken = sourceCode.getTokenAfter(node.left, {
filter: isNotClosingParenToken,
includeComments: false
});
const baseToken = baseNode.type === 'AssignmentExpression' || baseNode.type === 'AssignmentPattern'
? sourceCode.getFirstToken(baseNode)
: getFirstAndLastTokens(sourceCode, baseNode).firstToken;
const leftToken = getFirstAndLastTokens(sourceCode, node.left).firstToken;
const rightToken = getFirstAndLastTokens(sourceCode, node.right).firstToken;
offsets.setOffsetToken([leftToken === baseToken ? null : leftToken, opToken, rightToken], 1, baseToken);
},
AssignmentPattern(node) {
visitor.AssignmentExpression(node);
},
BinaryExpression(node) {
visitor.AssignmentExpression(node);
},
LogicalExpression(node) {
visitor.AssignmentExpression(node);
},
AwaitExpression(node) {
// `await`, `...`, or UnaryOperator
const firstToken = sourceCode.getFirstToken(node);
const nextToken = sourceCode.getTokenAfter(firstToken);
offsets.setOffsetToken(nextToken, 1, firstToken);
},
RestElement(node) {
visitor.AwaitExpression(node);
},
SpreadElement(node) {
visitor.AwaitExpression(node);
},
UnaryExpression(node) {
visitor.AwaitExpression(node);
},
BlockStatement(node) {
offsets.setOffsetElementList(node.body, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), 1);
},
ClassBody(node) {
visitor.BlockStatement(node);
},
BreakStatement(node) {
if (node.label) {
const firstToken = sourceCode.getFirstToken(node);
const nextToken = sourceCode.getTokenAfter(firstToken);
offsets.setOffsetToken(nextToken, 1, firstToken);
}
},
ContinueStatement(node) {
visitor.BreakStatement(node);
},
CallExpression(node) {
const typeArguments = node.typeArguments ??
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Support old typescript-eslint
node.typeParameters;
const firstToken = sourceCode.getFirstToken(node);
const leftParenToken = sourceCode.getTokenAfter(typeArguments || node.callee, {
filter: isOpeningParenToken,
includeComments: false
});
const rightParenToken = sourceCode.getLastToken(node);
if (typeArguments) {
offsets.setOffsetToken(sourceCode.getFirstToken(typeArguments), 1, firstToken);
}
for (const optionalToken of sourceCode.getTokensBetween(sourceCode.getLastToken(typeArguments || node.callee), leftParenToken, { filter: isOptionalToken, includeComments: false })) {
offsets.setOffsetToken(optionalToken, 1, firstToken);
}
offsets.setOffsetToken(leftParenToken, 1, firstToken);
offsets.setOffsetElementList(node.arguments, leftParenToken, rightParenToken, 1);
},
CatchClause(node) {
const catchToken = sourceCode.getFirstToken(node);
if (node.param != null) {
const leftParenToken = sourceCode.getTokenBefore(node.param);
const rightParenToken = sourceCode.getTokenAfter(node.param);
offsets.setOffsetToken(leftParenToken, 1, catchToken);
offsets.setOffsetElementList([node.param], leftParenToken, rightParenToken, 1);
}
const bodyToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyToken, 0, catchToken);
},
ClassDeclaration(node) {
const classToken = sourceCode.getFirstToken(node);
if (node.id != null) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.id), 1, classToken);
}
if (node.superClass != null) {
const extendsToken = sourceCode.getTokenBefore(node.superClass);
const superClassToken = sourceCode.getTokenAfter(extendsToken);
offsets.setOffsetToken(extendsToken, 1, classToken);
offsets.setOffsetToken(superClassToken, 1, extendsToken);
}
const bodyToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyToken, 0, classToken);
},
ClassExpression(node) {
visitor.ClassDeclaration(node);
},
ConditionalExpression(node) {
const questionToken = sourceCode.getTokenAfter(node.test, {
filter: isNotClosingParenToken,
includeComments: false
});
const consequentToken = sourceCode.getTokenAfter(questionToken);
const colonToken = sourceCode.getTokenAfter(node.consequent, {
filter: isNotClosingParenToken,
includeComments: false
});
const alternateToken = sourceCode.getTokenAfter(colonToken);
let baseNode = node;
let parent = getParent(baseNode);
while (parent && parent.type === 'ConditionalExpression' && parent.alternate === baseNode) {
baseNode = parent;
parent = getParent(baseNode);
}
const baseToken = sourceCode.getFirstToken(baseNode);
offsets.setOffsetToken([questionToken, colonToken], 1, baseToken);
offsets.setOffsetToken(consequentToken, 1, questionToken);
offsets.setOffsetToken(alternateToken, 1, colonToken);
},
DoWhileStatement(node) {
const doToken = sourceCode.getFirstToken(node);
const whileToken = sourceCode.getTokenAfter(node.body, {
filter: isNotClosingParenToken,
includeComments: false
});
const leftParenToken = sourceCode.getTokenAfter(whileToken);
const rightParenToken = sourceCode.getTokenAfter(node.test);
const bodyFirstToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyFirstToken, isOpeningBraceToken(bodyFirstToken) ? 0 : 1, doToken);
offsets.setOffsetToken(whileToken, 0, doToken);
offsets.setOffsetToken(leftParenToken, 1, whileToken);
offsets.setOffsetElementList([node.test], leftParenToken, rightParenToken, 1);
},
ExportAllDeclaration(node) {
const exportToken = sourceCode.getFirstToken(node);
const tokens = sourceCode.getTokensBetween(exportToken, node.source);
const fromIndex = tokens.findIndex((t) => t.value === 'from');
const fromToken = tokens[fromIndex];
const beforeTokens = tokens.slice(0, fromIndex);
const afterTokens = [...tokens.slice(fromIndex + 1), sourceCode.getFirstToken(node.source)];
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- type bug?
if (!node.exported) {
// export * from "mod"
offsets.setOffsetToken(beforeTokens, 1, exportToken);
}
else {
// export * as foo from "mod"
const asIndex = beforeTokens.findIndex((t) => t.value === 'as');
offsets.setOffsetToken(beforeTokens.slice(0, asIndex), 1, exportToken);
offsets.setOffsetToken(beforeTokens.slice(asIndex), 1, beforeTokens[asIndex - 1]);
}
offsets.setOffsetToken(fromToken, 0, exportToken);
offsets.setOffsetToken(afterTokens, 1, fromToken);
// assertions
const lastToken = sourceCode.getLastToken(node, {
filter: isNotSemicolonToken,
includeComments: false
});
const assertionTokens = sourceCode.getTokensBetween(node.source, lastToken);
if (assertionTokens.length) {
const assertToken = assertionTokens.shift();
offsets.setOffsetToken(assertToken, 0, exportToken);
const assertionOpen = assertionTokens.shift();
if (assertionOpen) {
offsets.setOffsetToken(assertionOpen, 1, assertToken);
offsets.setOffsetElementList(assertionTokens, assertionOpen, lastToken, 1);
}
}
},
ExportDefaultDeclaration(node) {
const exportToken = sourceCode.getFirstToken(node);
const declarationToken = getFirstAndLastTokens(sourceCode, node.declaration).firstToken;
const defaultTokens = sourceCode.getTokensBetween(exportToken, declarationToken);
offsets.setOffsetToken([...defaultTokens, declarationToken], 1, exportToken);
},
ExportNamedDeclaration(node) {
const exportToken = sourceCode.getFirstToken(node);
if (node.declaration) {
// export var foo = 1;
const declarationToken = sourceCode.getFirstToken(node.declaration);
offsets.setOffsetToken(declarationToken, 1, exportToken);
}
else {
const firstSpecifier = node.specifiers[0];
if (!firstSpecifier || firstSpecifier.type === 'ExportSpecifier') {
// export {foo, bar}; or export {foo, bar} from "mod";
const leftBraceTokens = firstSpecifier
? sourceCode.getTokensBetween(exportToken, firstSpecifier)
: [sourceCode.getTokenAfter(exportToken)];
const rightBraceToken = node.source
? sourceCode.getTokenBefore(node.source, {
filter: isClosingBraceToken,
includeComments: false
})
: sourceCode.getLastToken(node, {
filter: isClosingBraceToken,
includeComments: false
});
offsets.setOffsetToken(leftBraceTokens, 0, exportToken);
offsets.setOffsetElementList(node.specifiers, leftBraceTokens[leftBraceTokens.length - 1], rightBraceToken, 1);
if (node.source) {
const [fromToken, ...tokens] = sourceCode.getTokensBetween(rightBraceToken, node.source);
offsets.setOffsetToken(fromToken, 0, exportToken);
offsets.setOffsetToken([...tokens, sourceCode.getFirstToken(node.source)], 1, fromToken);
// assertions
const lastToken = sourceCode.getLastToken(node, {
filter: isNotSemicolonToken,
includeComments: false
});
const assertionTokens = sourceCode.getTokensBetween(node.source, lastToken);
if (assertionTokens.length) {
const assertToken = assertionTokens.shift();
offsets.setOffsetToken(assertToken, 0, exportToken);
const assertionOpen = assertionTokens.shift();
if (assertionOpen) {
offsets.setOffsetToken(assertionOpen, 1, assertToken);
offsets.setOffsetElementList(assertionTokens, assertionOpen, lastToken, 1);
}
}
}
}
else {
// maybe babel-eslint
}
}
},
ExportSpecifier(node) {
const tokens = sourceCode.getTokens(node);
let firstToken = tokens.shift();
if (firstToken.value === 'type') {
const typeToken = firstToken;
firstToken = tokens.shift();
offsets.setOffsetToken(firstToken, 0, typeToken);
}
offsets.setOffsetToken(tokens, 1, firstToken);
},
ForInStatement(node) {
const forToken = sourceCode.getFirstToken(node);
const awaitToken = (node.type === 'ForOfStatement' && node.await && sourceCode.getTokenAfter(forToken)) ||
null;
const leftParenToken = sourceCode.getTokenAfter(awaitToken || forToken);
const leftToken = sourceCode.getFirstToken(node.left);
const inOrOfToken = sourceCode.getTokenAfter(node.left, {
filter: isNotClosingParenToken,
includeComments: false
});
const rightToken = sourceCode.getTokenAfter(inOrOfToken);
const rightParenToken = sourceCode.getTokenBefore(node.body, {
filter: isNotOpeningParenToken,
includeComments: false
});
if (awaitToken != null) {
offsets.setOffsetToken(awaitToken, 0, forToken);
}
offsets.setOffsetToken(leftParenToken, 1, forToken);
offsets.setOffsetToken(leftToken, 1, leftParenToken);
offsets.setOffsetToken([inOrOfToken, rightToken], 1, leftToken);
offsets.setOffsetToken(rightParenToken, 0, leftParenToken);
const bodyFirstToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyFirstToken, isOpeningBraceToken(bodyFirstToken) ? 0 : 1, forToken);
},
ForOfStatement(node) {
visitor.ForInStatement(node);
},
ForStatement(node) {
const forToken = sourceCode.getFirstToken(node);
const leftParenToken = sourceCode.getTokenAfter(forToken);
const rightParenToken = sourceCode.getTokenBefore(node.body, {
filter: isNotOpeningParenToken,
includeComments: false
});
offsets.setOffsetToken(leftParenToken, 1, forToken);
offsets.setOffsetElementList([node.init, node.test, node.update], leftParenToken, rightParenToken, 1);
const bodyFirstToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyFirstToken, isOpeningBraceToken(bodyFirstToken) ? 0 : 1, forToken);
},
FunctionDeclaration(node) {
const firstToken = sourceCode.getFirstToken(node);
const leftParenToken = sourceCode.getTokenBefore(node.params[0] ||
node.returnType ||
sourceCode.getTokenBefore(node.body), {
filter: isOpeningParenToken,
includeComments: false
});
let bodyBaseToken = null;
if (firstToken.type === 'Punctuator') {
// method
bodyBaseToken = sourceCode.getFirstToken(getParent(node));
}
else {
let tokenOffset = 0;
for (const token of sourceCode.getTokensBetween(firstToken, leftParenToken)) {
if (token.value === '<') {
break;
}
if (token.value === '*' || (node.id && token.range[0] === node.id.range[0])) {
tokenOffset = 1;
}
offsets.setOffsetToken(token, tokenOffset, firstToken);
}
bodyBaseToken = firstToken;
}
const rightParenToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftParenToken, { filter: isClosingParenToken, includeComments: false });
offsets.setOffsetToken(leftParenToken, 1, bodyBaseToken);
offsets.setOffsetElementList(node.params, leftParenToken, rightParenToken, 1);
const bodyToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyToken, 0, bodyBaseToken);
},
FunctionExpression(node) {
visitor.FunctionDeclaration(node);
},
IfStatement(node) {
const [ifToken, ifLeftParenToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
const ifRightParenToken = sourceCode.getTokenBefore(node.consequent, {
filter: isClosingParenToken,
includeComments: false
});
offsets.setOffsetToken(ifLeftParenToken, 1, ifToken);
offsets.setOffsetToken(ifRightParenToken, 0, ifLeftParenToken);
const consequentFirstToken = sourceCode.getFirstToken(node.consequent);
offsets.setOffsetToken(consequentFirstToken, isOpeningBraceToken(consequentFirstToken) ? 0 : 1, ifToken);
if (node.alternate != null) {
const elseToken = sourceCode.getTokenAfter(node.consequent, {
filter: isNotClosingParenToken,
includeComments: false
});
offsets.setOffsetToken(elseToken, 0, ifToken);
const alternateFirstToken = sourceCode.getFirstToken(node.alternate);
offsets.setOffsetToken(alternateFirstToken, isOpeningBraceToken(alternateFirstToken) ? 0 : 1, elseToken);
}
},
ImportDeclaration(node) {
const importToken = sourceCode.getFirstToken(node);
const tokens = sourceCode.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), sourceCode.getFirstToken(node.source)]
}
: {
fromToken: null,
beforeTokens: [...tokens, sourceCode.getFirstToken(node.source)],
afterTokens: []
};
const namedSpecifiers = [];
for (const specifier of node.specifiers) {
if (specifier.type === 'ImportSpecifier') {
namedSpecifiers.push(specifier);
}
else {
const removeTokens = sourceCode.getTokens(specifier);
removeTokens.shift();
for (const token of removeTokens) {
const i = beforeTokens.indexOf(token);
if (i >= 0) {
beforeTokens.splice(i, 1);
}
}
}
}
if (namedSpecifiers.length) {
const leftBrace = sourceCode.getTokenBefore(namedSpecifiers[0]);
const rightBrace = sourceCode.getTokenAfter(namedSpecifiers[namedSpecifiers.length - 1], {
filter: isClosingBraceToken,
includeComments: false
});
offsets.setOffsetElementList(namedSpecifiers, leftBrace, rightBrace, 1);
for (const token of [...sourceCode.getTokensBetween(leftBrace, rightBrace), rightBrace]) {
const i = beforeTokens.indexOf(token);
if (i >= 0) {
beforeTokens.splice(i, 1);
}
}
}
if (beforeTokens.every((t) => isOpeningBraceToken(t) || isClosingBraceToken(t))) {
offsets.setOffsetToken(beforeTokens, 0, importToken);
}
else {
offsets.setOffsetToken(beforeTokens, 1, importToken);
}
if (fromToken) {
offsets.setOffsetToken(fromToken, 0, importToken);
offsets.setOffsetToken(afterTokens, 1, fromToken);
}
// assertions
const lastToken = sourceCode.getLastToken(node, {
filter: isNotSemicolonToken,
includeComments: false
});
const assertionTokens = sourceCode.getTokensBetween(node.source, lastToken);
if (assertionTokens.length) {
const assertToken = assertionTokens.shift();
offsets.setOffsetToken(assertToken, 0, importToken);
const assertionOpen = assertionTokens.shift();
if (assertionOpen) {
offsets.setOffsetToken(assertionOpen, 1, assertToken);
offsets.setOffsetElementList(assertionTokens, assertionOpen, lastToken, 1);
}
}
},
ImportExpression(node) {
const firstToken = sourceCode.getFirstToken(node);
const rightToken = sourceCode.getLastToken(node);
const leftToken = sourceCode.getTokenAfter(firstToken, {
filter: isOpeningParenToken,
includeComments: false
});
offsets.setOffsetToken(leftToken, 1, firstToken);
offsets.setOffsetElementList([node.source], leftToken, rightToken, 1);
},
ImportNamespaceSpecifier(node) {
const tokens = sourceCode.getTokens(node);
const firstToken = tokens.shift();
offsets.setOffsetToken(tokens, 1, firstToken);
},
ImportSpecifier(node) {
visitor.ExportSpecifier(node);
},
LabeledStatement(node) {
const labelToken = sourceCode.getFirstToken(node);
const colonToken = sourceCode.getTokenAfter(labelToken);
const bodyToken = sourceCode.getTokenAfter(colonToken);
offsets.setOffsetToken([colonToken, bodyToken], 1, labelToken);
},
SvelteReactiveStatement(node) {
visitor.LabeledStatement(node);
},
MemberExpression(node) {
const objectToken = sourceCode.getFirstToken(node);
if (node.type === 'MemberExpression' && node.computed) {
const leftBracketToken = sourceCode.getTokenBefore(node.property, {
filter: isOpeningBracketToken,
includeComments: false
});
const rightBracketToken = sourceCode.getTokenAfter(node.property, {
filter: isClosingBracketToken,
includeComments: false
});
for (const optionalToken of sourceCode.getTokensBetween(sourceCode.getLastToken(node.object), leftBracketToken, { filter: isOptionalToken, includeComments: false })) {
offsets.setOffsetToken(optionalToken, 1, objectToken);
}
offsets.setOffsetToken(leftBracketToken, 1, objectToken);
offsets.setOffsetElementList([node.property], leftBracketToken, rightBracketToken, 1);
}
else {
const dotToken = sourceCode.getTokenBefore(node.property);
const propertyToken = sourceCode.getTokenAfter(dotToken);
offsets.setOffsetToken([dotToken, propertyToken], 1, objectToken);
}
},
MetaProperty(node) {
visitor.MemberExpression(node);
},
MethodDefinition(node) {
const firstToken = sourceCode.getFirstToken(node);
const keyTokens = getFirstAndLastTokens(sourceCode, node.key);
const prefixTokens = sourceCode.getTokensBetween(firstToken, keyTokens.firstToken);
if (node.computed) {
prefixTokens.pop(); // pop [
}
offsets.setOffsetToken(prefixTokens, 0, firstToken);
let lastKeyToken;
if (node.computed) {
const leftBracketToken = sourceCode.getTokenBefore(keyTokens.firstToken);
const rightBracketToken = (lastKeyToken = sourceCode.getTokenAfter(keyTokens.lastToken));
offsets.setOffsetToken(leftBracketToken, 0, firstToken);
offsets.setOffsetElementList([node.key], leftBracketToken, rightBracketToken, 1);
}
else {
offsets.setOffsetToken(keyTokens.firstToken, 0, firstToken);
lastKeyToken = keyTokens.lastToken;
}
if (node.value) {
const initToken = sourceCode.getFirstToken(node.value);
offsets.setOffsetToken([...sourceCode.getTokensBetween(lastKeyToken, initToken), initToken], 1, lastKeyToken);
}
},
Property(node) {
visitor.MethodDefinition(node);
},
NewExpression(node) {
const typeArguments = node.typeArguments ??
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Support old typescript-eslint
node.typeParameters;
const newToken = sourceCode.getFirstToken(node);
const calleeTokens = getFirstAndLastTokens(sourceCode, node.callee);
offsets.setOffsetToken(calleeTokens.firstToken, 1, newToken);
if (typeArguments) {
offsets.setOffsetToken(sourceCode.getFirstToken(typeArguments), 1, calleeTokens.firstToken);
}
const leftParenBefore = typeArguments || calleeTokens.lastToken;
if (node.arguments.length || leftParenBefore.range[1] < node.range[1]) {
const rightParenToken = sourceCode.getLastToken(node);
const leftParenToken = sourceCode.getTokenAfter(leftParenBefore);
offsets.setOffsetToken(leftParenToken, 1, calleeTokens.firstToken);
offsets.setOffsetElementList(node.arguments, leftParenToken, rightParenToken, 1);
}
},
ObjectExpression(node) {
const firstToken = sourceCode.getFirstToken(node);
const rightToken = sourceCode.getTokenAfter(node.properties[node.properties.length - 1] || firstToken, { filter: isClosingBraceToken, includeComments: false });
offsets.setOffsetElementList(node.properties, firstToken, rightToken, 1);
},
ObjectPattern(node) {
visitor.ObjectExpression(node);
},
PropertyDefinition(node) {
visitor.MethodDefinition(node);
},
ReturnStatement(node) {
if (node.argument) {
const firstToken = sourceCode.getFirstToken(node);
const nextToken = sourceCode.getTokenAfter(firstToken);
offsets.setOffsetToken(nextToken, 1, firstToken);
}
},
ThrowStatement(node) {
visitor.ReturnStatement(node);
},
SequenceExpression(node) {
const firstToken = sourceCode.getFirstToken(node);
offsets.setOffsetElementList(node.expressions, firstToken, null, 0);
},
SwitchCase(node) {
const caseToken = sourceCode.getFirstToken(node);
if (node.test != null) {
const testTokens = getFirstAndLastTokens(sourceCode, node.test);
const colonToken = sourceCode.getTokenAfter(testTokens.lastToken);
offsets.setOffsetToken([testTokens.firstToken, colonToken], 1, caseToken);
}
else {
const colonToken = sourceCode.getTokenAfter(caseToken);
offsets.setOffsetToken(colonToken, 1, caseToken);
}
if (node.consequent.length === 1 && node.consequent[0].type === 'BlockStatement') {
offsets.setOffsetToken(sourceCode.getFirstToken(node.consequent[0]), 0, caseToken);
}
else {
for (const statement of node.consequent) {
offsets.setOffsetToken(getFirstAndLastTokens(sourceCode, statement).firstToken, 1, caseToken);
}
}
},
SwitchStatement(node) {
const switchToken = sourceCode.getFirstToken(node);
const { firstToken: leftParenToken, lastToken: rightParenToken } = getFirstAndLastTokens(sourceCode, node.discriminant);
const leftBraceToken = sourceCode.getTokenAfter(rightParenToken);
const rightBraceToken = sourceCode.getLastToken(node);
offsets.setOffsetToken(leftParenToken, 1, switchToken);
offsets.setOffsetElementList([node.discriminant], leftParenToken, rightParenToken, 1);
offsets.setOffsetToken(leftBraceToken, 0, switchToken);
offsets.setOffsetElementList(node.cases, leftBraceToken, rightBraceToken, options.switchCase);
},
TaggedTemplateExpression(node) {
const tagTokens = getFirstAndLastTokens(sourceCode, node.tag);
offsets.setOffsetToken(sourceCode.getFirstToken(node.quasi), 1, tagTokens.firstToken);
},
TemplateLiteral(node) {
const firstToken = sourceCode.getFirstToken(node);
const quasiTokens = node.quasis.slice(1).map((n) => sourceCode.getFirstToken(n));
const expressionToken = node.quasis.slice(0, -1).map((n) => sourceCode.getTokenAfter(n));
offsets.setOffsetToken(quasiTokens, 0, firstToken);
offsets.setOffsetToken(expressionToken, 1, firstToken);
},
TryStatement(node) {
const tryToken = sourceCode.getFirstToken(node);
const tryBlockToken = sourceCode.getFirstToken(node.block);
offsets.setOffsetToken(tryBlockToken, 0, tryToken);
if (node.handler != null) {
const catchToken = sourceCode.getFirstToken(node.handler);
offsets.setOffsetToken(catchToken, 0, tryToken);
}
if (node.finalizer != null) {
const finallyToken = sourceCode.getTokenBefore(node.finalizer);
const finallyBlockToken = sourceCode.getFirstToken(node.finalizer);
offsets.setOffsetToken([finallyToken, finallyBlockToken], 0, tryToken);
}
},
UpdateExpression(node) {
const firstToken = sourceCode.getFirstToken(node);
const nextToken = sourceCode.getTokenAfter(firstToken);
offsets.setOffsetToken(nextToken, 1, firstToken);
},
VariableDeclaration(node) {
offsets.setOffsetElementList(node.declarations, sourceCode.getFirstToken(node), null, 1);
},
VariableDeclarator(node) {
if (node.init != null) {
const idToken = sourceCode.getFirstToken(node);
const eqToken = sourceCode.getTokenAfter(node.id);
const initToken = sourceCode.getTokenAfter(eqToken);
offsets.setOffsetToken([eqToken, initToken], 1, idToken);
}
},
WhileStatement(node) {
const firstToken = sourceCode.getFirstToken(node);
const leftParenToken = sourceCode.getTokenAfter(firstToken);
const rightParenToken = sourceCode.getTokenBefore(node.body, {
filter: isClosingParenToken,
includeComments: false
});
offsets.setOffsetToken(leftParenToken, 1, firstToken);
offsets.setOffsetToken(rightParenToken, 0, leftParenToken);
const bodyFirstToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyFirstToken, isOpeningBraceToken(bodyFirstToken) ? 0 : 1, firstToken);
},
WithStatement(node) {
visitor.WhileStatement(node);
},
YieldExpression(node) {
if (node.argument != null) {
const [yieldToken, secondToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(secondToken, 1, yieldToken);
if (node.delegate) {
offsets.setOffsetToken(sourceCode.getTokenAfter(secondToken), 1, yieldToken);
}
}
},
// ----------------------------------------------------------------------
// SINGLE TOKEN NODES
// ----------------------------------------------------------------------
DebuggerStatement() {
// noop
},
Identifier() {
// noop
},
ImportDefaultSpecifier() {
// noop
},
Literal() {
// noop
},
PrivateIdentifier() {
// noop
},
Super() {
// noop
},
TemplateElement() {
// noop
},
ThisExpression() {
// noop
},
// ----------------------------------------------------------------------
// WRAPPER NODES
// ----------------------------------------------------------------------
ExpressionStatement() {
// noop
},
ChainExpression() {
// noop
},
EmptyStatement() {
// noop
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
const commonVisitor = {
':statement, PropertyDefinition'(node) {
const firstToken = sourceCode.getFirstToken(node);
const lastToken = sourceCode.getLastToken(node);
if (isSemicolonToken(lastToken) && firstToken !== lastToken) {
const next = sourceCode.getTokenAfter(lastToken);
if (!next || lastToken.loc.start.line < next.loc.start.line) {
// End of line semicolons
offsets.setOffsetToken(lastToken, 0, firstToken);
}
}
},
':expression'(node) {
// Proc parentheses.
let leftToken = sourceCode.getTokenBefore(node);
let rightToken = sourceCode.getTokenAfter(node);
let firstToken = sourceCode.getFirstToken(node);
while (leftToken &&
isOpeningParenToken(leftToken) &&
rightToken &&
isClosingParenToken(rightToken)) {
offsets.setOffsetToken(firstToken, 1, leftToken);
offsets.setOffsetToken(rightToken, 0, leftToken);
firstToken = leftToken;
leftToken = sourceCode.getTokenBefore(leftToken);
rightToken = sourceCode.getTokenAfter(rightToken);
}
}
};
const v = visitor;
return {
...v,
...commonVisitor
};
}
/**
* Checks whether given text is known button type
*/
function isOptionalToken(token) {
return token.type === 'Punctuator' && token.value === '?.';
}

View File

@@ -0,0 +1,10 @@
import type { RuleContext, RuleListener } from '../../types.js';
import type { IndentOptions } from './commons.js';
/**
* Creates AST event handlers for html-indent.
*
* @param context The rule context.
* @param defaultOptions The default value of options.
* @returns AST event handlers.
*/
export declare function defineVisitor(context: RuleContext, defaultOptions: Partial<IndentOptions>): RuleListener;

View File

@@ -0,0 +1,237 @@
import * as SV from './svelte.js';
import * as ES from './es.js';
import * as TS from './ts.js';
import { isNotWhitespace } from './ast.js';
import { isCommentToken } from '@eslint-community/eslint-utils';
import { OffsetContext } from './offset-context.js';
/**
* Normalize options.
* @param type The type of indentation.
* @param options Other options.
* @param defaultOptions The default value of options.
* @returns Normalized options.
*/
function parseOptions(options, defaultOptions) {
const ret = {
indentChar: ' ',
indentScript: true,
indentSize: 2,
switchCase: 1,
alignAttributesVertically: false,
ignoredNodes: [],
...defaultOptions
};
if (Number.isSafeInteger(options.indent)) {
ret.indentSize = Number(options.indent);
}
else if (options.indent === 'tab') {
ret.indentChar = '\t';
ret.indentSize = 1;
}
if (typeof options.indentScript === 'boolean') {
ret.indentScript = options.indentScript;
}
if (options.switchCase != null && Number.isSafeInteger(options.switchCase)) {
ret.switchCase = options.switchCase;
}
if (options.ignoredNodes != null) {
ret.ignoredNodes = options.ignoredNodes;
}
if (options.alignAttributesVertically && ret.indentChar === ' ') {
ret.alignAttributesVertically = true;
}
else if (ret.indentChar !== ' ') {
ret.alignAttributesVertically = false;
}
return ret;
}
/**
* Creates AST event handlers for html-indent.
*
* @param context The rule context.
* @param defaultOptions The default value of options.
* @returns AST event handlers.
*/
export function defineVisitor(context, defaultOptions) {
if (!context.filename.endsWith('.svelte'))
return {};
const options = parseOptions(context.options[0] || {}, defaultOptions);
const sourceCode = context.sourceCode;
const offsets = new OffsetContext({ sourceCode, options });
/**
* Get the text of the indentation part of the given location.
*/
function getIndentText({ line, column }) {
return sourceCode.lines[line - 1].slice(0, column);
}
/**
* Validate the given token with the pre-calculated expected indentation.
*/
function validateToken(token, expectedIndent) {
const line = token.loc.start.line;
const indentText = getIndentText(token.loc.start);
// `indentText` contains non-whitespace characters.
if (indentText.trim() !== '') {
return;
}
const actualIndent = token.loc.start.column;
const mismatchCharIndexes = [];
for (let i = 0; i < indentText.length; ++i) {
if (indentText[i] !== options.indentChar) {
mismatchCharIndexes.push(i);
}
}
if (actualIndent !== expectedIndent) {
const loc = {
start: { line, column: 0 },
end: { line, column: actualIndent }
};
context.report({
loc,
messageId: 'unexpectedIndentation',
data: {
expectedIndent: String(expectedIndent),
actualIndent: String(actualIndent),
expectedUnit: options.indentChar === '\t' ? 'tab' : 'space',
actualUnit: mismatchCharIndexes.length
? 'whitespace'
: options.indentChar === '\t'
? 'tab'
: 'space',
expectedIndentPlural: expectedIndent === 1 ? '' : 's',
actualIndentPlural: actualIndent === 1 ? '' : 's'
},
fix(fixer) {
return fixer.replaceTextRange([sourceCode.getIndexFromLoc(loc.start), sourceCode.getIndexFromLoc(loc.end)], options.indentChar.repeat(expectedIndent));
}
});
return;
}
for (const i of mismatchCharIndexes) {
const loc = {
start: { line, column: i },
end: { line, column: i + 1 }
};
context.report({
loc,
messageId: 'unexpectedChar',
data: {
expected: JSON.stringify(options.indentChar),
actual: JSON.stringify(indentText[i])
},
fix(fixer) {
return fixer.replaceTextRange([sourceCode.getIndexFromLoc(loc.start), sourceCode.getIndexFromLoc(loc.end)], options.indentChar);
}
});
}
}
/** Process line tokens */
function processLine(tokens, prevComments, prevToken, calculator) {
const firstToken = tokens[0];
const actualIndent = firstToken.loc.start.column;
const expectedIndent = calculator.getExpectedIndentFromTokens(tokens);
if (expectedIndent == null) {
calculator.saveExpectedIndent(tokens, actualIndent);
return;
}
calculator.saveExpectedIndent(tokens, Math.min(...tokens
.map((t) => calculator.getExpectedIndentFromToken(t))
.filter((i) => i != null)));
let prev = prevToken;
if (prevComments.length) {
if (prev && prev.loc.end.line < prevComments[0].loc.start.line) {
validateToken(prevComments[0], expectedIndent);
}
prev = prevComments[prevComments.length - 1];
}
if (prev && prev.loc.end.line < tokens[0].loc.start.line) {
validateToken(tokens[0], expectedIndent);
}
}
const indentContext = {
sourceCode,
options,
offsets
};
const nodesVisitor = {
...ES.defineVisitor(indentContext),
...SV.defineVisitor(indentContext),
...TS.defineVisitor(indentContext)
};
const knownNodes = new Set(Object.keys(nodesVisitor));
/**
* Build a visitor combined with a visitor to handle the given ignore selector.
*/
function compositingIgnoresVisitor(visitor) {
for (const ignoreSelector of options.ignoredNodes) {
const key = `${ignoreSelector}:exit`;
if (visitor[key]) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
const handler = visitor[key];
visitor[key] = function (node, ...args) {
const ret = handler.call(this, node, ...args);
offsets.ignore(node);
return ret;
};
}
else {
visitor[key] = (node) => offsets.ignore(node);
}
}
return visitor;
}
return compositingIgnoresVisitor({
...nodesVisitor,
'*:exit'(node) {
// Ignore tokens of unknown nodes.
if (!knownNodes.has(node.type)) {
// debugger
// console.log(node.type, node.loc!.start.line)
offsets.ignore(node);
}
},
'Program:exit'(node) {
const calculator = offsets.getOffsetCalculator();
let prevToken = null;
for (const { prevComments, tokens } of iterateLineTokens()) {
processLine(tokens, prevComments, prevToken, calculator);
prevToken = tokens[tokens.length - 1];
}
/** Iterate line tokens */
function* iterateLineTokens() {
let line = 0;
let prevComments = [];
let bufferTokens = [];
for (const token of sourceCode.getTokens(node, {
includeComments: true,
filter: isNotWhitespace
})) {
const thisLine = token.loc.start.line;
if (line === thisLine || bufferTokens.length === 0) {
bufferTokens.push(token);
}
else {
if (isCommentToken(bufferTokens[0]) && bufferTokens.every(isCommentToken)) {
prevComments.push(bufferTokens[0]);
}
else {
yield {
prevComments,
tokens: bufferTokens
};
prevComments = [];
}
bufferTokens = [token];
}
line = thisLine;
}
if (bufferTokens.length && !bufferTokens.every(isCommentToken)) {
yield {
prevComments,
tokens: bufferTokens
};
}
}
}
});
}

View File

@@ -0,0 +1,88 @@
import type { ASTNode, SourceCode } from '../../types.js';
import type { AnyToken, IndentOptions, MaybeNode } from './commons.js';
declare const enum OffsetDataType {
normal = 0,
align = 1,
start = 2
}
type OffsetData = {
type: OffsetDataType.normal;
base: number;
offset: number;
expectedIndent?: number;
} | {
type: OffsetDataType.align;
base: number;
alignIndent: number;
expectedIndent?: number;
} | {
type: OffsetDataType.start;
offset: number;
expectedIndent?: number;
};
export declare class OffsetContext {
private readonly sourceCode;
private readonly options;
private readonly offsets;
private readonly ignoreRanges;
constructor(arg: {
sourceCode: SourceCode;
options: IndentOptions;
});
/**
* Set offset to the given index.
*/
setOffsetIndex(index: number, offset: number, base: number): void;
/**
* Set align indent to the given index.
*/
private setAlignIndent;
/**
* Set offset to the given tokens.
*/
setOffsetToken(token: AnyToken | null | undefined | (AnyToken | null | undefined)[], offset: number, baseToken: AnyToken): void;
/**
* Copy offset to the given index from srcIndex.
*/
copyOffset(index: number, srcIndex: number): void;
/**
* Set start offset to the given index.
*/
setStartOffsetIndex(index: number, offset: number): void;
/**
* Set start offset to the given token.
*/
setStartOffsetToken(token: AnyToken | null | undefined | (AnyToken | null | undefined)[], offset: number): void;
setOffsetElementList(nodes: (ASTNode | AnyToken | MaybeNode | null | undefined)[], baseNodeOrToken: ASTNode | AnyToken | MaybeNode, lastNodeOrToken: ASTNode | AnyToken | MaybeNode | null, offset: number, align?: boolean): void;
private _setOffsetElementList;
/**
* Ignore range of the given node.
*/
ignore(node: ASTNode): void;
getOffsetCalculator(): OffsetCalculator;
}
export declare class OffsetCalculator {
private readonly options;
private readonly offsets;
private readonly ignoreRanges;
constructor(arg: {
offsets: Map<number, OffsetData>;
options: IndentOptions;
ignoreRanges: [number, number][];
});
/**
* Calculate correct indentation of the given index.
*/
private getExpectedIndentFromIndex;
/**
* Calculate correct indentation of the given token.
*/
getExpectedIndentFromToken(token: AnyToken): number | null;
/**
* Calculate correct indentation of the line of the given tokens.
*/
getExpectedIndentFromTokens(tokens: AnyToken[]): null | number;
/** Save expected indent to give tokens */
saveExpectedIndent(tokens: AnyToken[], expectedIndent: number): void;
}
export {};

View File

@@ -0,0 +1,220 @@
import { isNotWhitespace } from './ast.js';
import { isBeginningOfLine } from './commons.js';
import { getFirstAndLastTokens } from './commons.js';
export class OffsetContext {
constructor(arg) {
this.offsets = new Map();
this.ignoreRanges = new Map();
this.sourceCode = arg.sourceCode;
this.options = arg.options;
}
/**
* Set offset to the given index.
*/
setOffsetIndex(index, offset, base) {
if (index === base) {
return;
}
this.offsets.set(index, {
type: 0 /* OffsetDataType.normal */,
base,
offset
});
}
/**
* Set align indent to the given index.
*/
setAlignIndent(index, alignIndent, base) {
if (index === base) {
return;
}
this.offsets.set(index, {
type: 1 /* OffsetDataType.align */,
base,
alignIndent
});
}
/**
* Set offset to the given tokens.
*/
setOffsetToken(token, offset, baseToken) {
if (!token) {
return;
}
if (Array.isArray(token)) {
for (const t of token) {
this.setOffsetToken(t, offset, baseToken);
}
return;
}
this.setOffsetIndex(token.range[0], offset, baseToken.range[0]);
}
/**
* Copy offset to the given index from srcIndex.
*/
copyOffset(index, srcIndex) {
const offsetData = this.offsets.get(srcIndex);
if (!offsetData) {
return;
}
if (offsetData.type === 2 /* OffsetDataType.start */) {
this.setStartOffsetIndex(index, offsetData.offset);
}
else if (offsetData.type === 1 /* OffsetDataType.align */) {
this.setAlignIndent(index, offsetData.alignIndent, offsetData.base);
}
else {
this.setOffsetIndex(index, offsetData.offset, offsetData.base);
}
}
/**
* Set start offset to the given index.
*/
setStartOffsetIndex(index, offset) {
this.offsets.set(index, {
type: 2 /* OffsetDataType.start */,
offset
});
}
/**
* Set start offset to the given token.
*/
setStartOffsetToken(token, offset) {
if (!token) {
return;
}
if (Array.isArray(token)) {
for (const t of token) {
this.setStartOffsetToken(t, offset);
}
return;
}
this.setStartOffsetIndex(token.range[0], offset);
}
setOffsetElementList(nodes, baseNodeOrToken, lastNodeOrToken, offset, align) {
let setIndent = (token, baseToken) => this.setOffsetToken(token, offset, baseToken);
if (align) {
for (const n of nodes) {
if (n) {
if (!isBeginningOfLine(this.sourceCode, n)) {
const startLoc = n.loc.start;
const alignIndent = startLoc.column - /^\s*/u.exec(this.sourceCode.lines[startLoc.line - 1])[0].length;
setIndent = (token, baseToken) => this.setAlignIndent(token.range[0], alignIndent, baseToken.range[0]);
}
break;
}
}
}
this._setOffsetElementList(nodes, baseNodeOrToken, lastNodeOrToken, setIndent);
}
_setOffsetElementList(nodes, baseNodeOrToken, lastNodeOrToken, setIndent) {
const baseToken = this.sourceCode.getFirstToken(baseNodeOrToken);
let prevToken = this.sourceCode.getLastToken(baseNodeOrToken);
for (const node of nodes) {
if (node == null) {
continue;
}
const elementTokens = getFirstAndLastTokens(this.sourceCode, node, prevToken.range[1]);
let t = prevToken;
while ((t = this.sourceCode.getTokenAfter(t, {
includeComments: true,
filter: isNotWhitespace
})) != null &&
t.range[1] <= elementTokens.firstToken.range[0]) {
setIndent(t, baseToken);
}
setIndent(elementTokens.firstToken, baseToken);
prevToken = elementTokens.lastToken;
}
if (lastNodeOrToken) {
const lastToken = this.sourceCode.getFirstToken(lastNodeOrToken);
let t = prevToken;
while ((t = this.sourceCode.getTokenAfter(t, {
includeComments: true,
filter: isNotWhitespace
})) != null &&
t.range[1] <= lastToken.range[0]) {
setIndent(t, baseToken);
}
this.setOffsetToken(lastToken, 0, baseToken);
}
}
/**
* Ignore range of the given node.
*/
ignore(node) {
const range = node.range;
const n = this.ignoreRanges.get(range[0]) ?? 0;
this.ignoreRanges.set(range[0], Math.max(n, range[1]));
}
getOffsetCalculator() {
// eslint-disable-next-line @typescript-eslint/no-use-before-define -- ignore
return new OffsetCalculator({
offsets: this.offsets,
options: this.options,
ignoreRanges: [...this.ignoreRanges]
});
}
}
export class OffsetCalculator {
constructor(arg) {
this.offsets = arg.offsets;
this.options = arg.options;
this.ignoreRanges = arg.ignoreRanges;
}
/**
* Calculate correct indentation of the given index.
*/
getExpectedIndentFromIndex(index) {
const offsetInfo = this.offsets.get(index);
if (offsetInfo == null) {
return null;
}
if (offsetInfo.expectedIndent != null) {
return offsetInfo.expectedIndent;
}
if (offsetInfo.type === 2 /* OffsetDataType.start */) {
return offsetInfo.offset * this.options.indentSize;
}
const baseIndent = this.getExpectedIndentFromIndex(offsetInfo.base);
if (baseIndent == null) {
return null;
}
if (offsetInfo.type === 1 /* OffsetDataType.align */) {
return baseIndent + offsetInfo.alignIndent;
}
return baseIndent + offsetInfo.offset * this.options.indentSize;
}
/**
* Calculate correct indentation of the given token.
*/
getExpectedIndentFromToken(token) {
return this.getExpectedIndentFromIndex(token.range[0]);
}
/**
* Calculate correct indentation of the line of the given tokens.
*/
getExpectedIndentFromTokens(tokens) {
for (const token of tokens) {
const index = token.range[0];
if (this.ignoreRanges.some(([f, t]) => f <= index && index < t)) {
return null;
}
const expectedIndent = this.getExpectedIndentFromIndex(index);
if (expectedIndent != null) {
return expectedIndent;
}
}
return null;
}
/** Save expected indent to give tokens */
saveExpectedIndent(tokens, expectedIndent) {
for (const token of tokens) {
const offsetInfo = this.offsets.get(token.range[0]);
if (offsetInfo == null) {
continue;
}
offsetInfo.expectedIndent = offsetInfo.expectedIndent ?? expectedIndent;
}
}
}

View File

@@ -0,0 +1,11 @@
import type { SvelteNodeListener } from '../../types-for-node.js';
import type { IndentContext } from './commons.js';
type NodeListener = SvelteNodeListener;
/**
* Creates AST event handlers for svelte nodes.
*
* @param context The rule context.
* @returns AST event handlers.
*/
export declare function defineVisitor(context: IndentContext): NodeListener;
export {};

View File

@@ -0,0 +1,465 @@
import { isNotWhitespace } from './ast.js';
import { isBeginningOfElement } from './commons.js';
import { isBeginningOfLine } from './commons.js';
import { getFirstAndLastTokens } from './commons.js';
import { isClosingParenToken, isOpeningParenToken } from '@eslint-community/eslint-utils';
const PREFORMATTED_ELEMENT_NAMES = ['pre', 'textarea', 'template'];
/**
* Creates AST event handlers for svelte nodes.
*
* @param context The rule context.
* @returns AST event handlers.
*/
export function defineVisitor(context) {
const { sourceCode, offsets, options } = context;
const visitor = {
// ----------------------------------------------------------------------
// ELEMENTS
// ----------------------------------------------------------------------
SvelteScriptElement(node) {
offsets.setOffsetElementList(node.body, node.startTag, node.endTag, options.indentScript ? 1 : 0);
},
SvelteStyleElement(node) {
node.children.forEach((n) => offsets.ignore(n));
},
SvelteElement(node) {
if (node.name.type === 'Identifier' || node.name.type === 'SvelteName') {
if (PREFORMATTED_ELEMENT_NAMES.includes(node.name.name)) {
const startTagToken = sourceCode.getFirstToken(node);
const endTagToken = node.endTag && sourceCode.getFirstToken(node.endTag);
offsets.setOffsetToken(endTagToken, 0, startTagToken);
node.children.forEach((n) => offsets.ignore(n));
return;
}
if (node.name.name === 'style') {
// Inline style tag
node.children.forEach((n) => offsets.ignore(n));
return;
}
}
if (node.endTag) {
offsets.setOffsetElementList(node.children.filter(isNotEmptyTextNode), node.startTag, node.endTag, 1);
}
},
// ----------------------------------------------------------------------
// TAGS
// ----------------------------------------------------------------------
SvelteStartTag(node) {
const openToken = sourceCode.getFirstToken(node);
const closeToken = sourceCode.getLastToken(node);
offsets.setOffsetElementList(node.attributes, openToken, closeToken, 1, options.alignAttributesVertically);
if (node.selfClosing) {
const slash = sourceCode.getTokenBefore(closeToken);
if (slash.value === '/') {
offsets.setOffsetToken(slash, 0, openToken);
}
}
},
SvelteEndTag(node) {
const openToken = sourceCode.getFirstToken(node);
const closeToken = sourceCode.getLastToken(node);
offsets.setOffsetElementList([], openToken, closeToken, 1);
},
// ----------------------------------------------------------------------
// ATTRIBUTES
// ----------------------------------------------------------------------
SvelteAttribute(node) {
const keyToken = sourceCode.getFirstToken(node);
const eqToken = sourceCode.getTokenAfter(node.key);
if (eqToken != null && eqToken.range[1] <= node.range[1]) {
offsets.setOffsetToken(eqToken, 1, keyToken);
const valueStartToken = sourceCode.getTokenAfter(eqToken);
if (valueStartToken != null && valueStartToken.range[1] <= node.range[1]) {
offsets.setOffsetToken(valueStartToken, 1, keyToken);
const values = node.type === 'SvelteAttribute' || node.type === 'SvelteStyleDirective'
? node.value
: [];
// process quoted
let processedValues = false;
if (valueStartToken.type === 'Punctuator') {
const quoted = ['"', "'"].includes(valueStartToken.value);
const mustache = !quoted && valueStartToken.value === '{';
if (quoted || mustache) {
const last = sourceCode.getLastToken(node);
if (last.type === 'Punctuator' &&
((quoted && last.value === valueStartToken.value) ||
(mustache && last.value === '}'))) {
offsets.setOffsetToken(last, 0, valueStartToken);
offsets.setOffsetElementList(values, valueStartToken, last, 1);
processedValues = true;
}
}
}
if (!processedValues) {
for (const val of values) {
const token = sourceCode.getFirstToken(val);
offsets.setOffsetToken(token, 0, valueStartToken);
}
}
}
}
},
SvelteDirective(node) {
visitor.SvelteAttribute(node);
},
SvelteStyleDirective(node) {
visitor.SvelteAttribute(node);
},
SvelteSpecialDirective(node) {
visitor.SvelteAttribute(node);
},
SvelteShorthandAttribute(node) {
const openToken = sourceCode.getFirstToken(node);
const closeToken = sourceCode.getLastToken(node);
offsets.setOffsetElementList([], openToken, closeToken, 1);
},
SvelteSpreadAttribute(node) {
visitor.SvelteShorthandAttribute(node);
},
// ----------------------------------------------------------------------
// ATTRIBUTE KEYS
// ----------------------------------------------------------------------
SvelteDirectiveKey(_node) {
// noop
},
SvelteSpecialDirectiveKey(_node) {
// noop
},
// ----------------------------------------------------------------------
// CONTENTS
// ----------------------------------------------------------------------
SvelteText(node) {
const tokens = sourceCode.getTokens(node, {
filter: isNotWhitespace,
includeComments: false
});
const first = tokens.shift();
if (!first) {
return;
}
offsets.setOffsetToken(tokens, isBeginningOfLine(sourceCode, first) ? 0 : isBeginningOfElement(node) ? 1 : 0, first);
},
SvelteLiteral(node) {
const tokens = sourceCode.getTokens(node, {
filter: isNotWhitespace,
includeComments: false
});
const first = tokens.shift();
if (!first) {
return;
}
offsets.setOffsetToken(tokens, isBeginningOfLine(sourceCode, first) ? 0 : 1, first);
},
// ----------------------------------------------------------------------
// MUSTACHE TAGS
// ----------------------------------------------------------------------
SvelteMustacheTag(node) {
const openToken = sourceCode.getFirstToken(node);
const closeToken = sourceCode.getLastToken(node);
offsets.setOffsetElementList([node.expression], openToken, closeToken, 1);
},
SvelteDebugTag(node) {
const openToken = sourceCode.getFirstToken(node);
const closeToken = sourceCode.getLastToken(node);
offsets.setOffsetElementList(node.identifiers, openToken, closeToken, 1);
},
SvelteConstTag(node) {
const openToken = sourceCode.getFirstToken(node);
const constToken = sourceCode.getTokenAfter(openToken);
const declarationToken = sourceCode.getFirstToken(node.declaration);
const closeToken = sourceCode.getLastToken(node);
offsets.setOffsetToken(constToken, 1, openToken);
offsets.setOffsetToken(declarationToken, 1, openToken);
offsets.setOffsetToken(closeToken, 0, openToken);
},
SvelteRenderTag(node) {
const openToken = sourceCode.getFirstToken(node);
const renderToken = sourceCode.getTokenAfter(openToken);
offsets.setOffsetToken(renderToken, 1, openToken);
offsets.setOffsetToken(node.expression, 1, renderToken);
},
// ----------------------------------------------------------------------
// BLOCKS
// ----------------------------------------------------------------------
SvelteIfBlock(node) {
const [openToken, ...ifTokens] = sourceCode.getFirstTokens(node, {
count: node.elseif ? 3 : 2,
includeComments: false
});
offsets.setOffsetToken(ifTokens, 1, openToken);
const exp = getFirstAndLastTokens(sourceCode, node.expression);
offsets.setOffsetToken(exp.firstToken, 1, ifTokens[0]);
const closeOpenTagToken = sourceCode.getTokenAfter(exp.lastToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
if (node.else) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.else), 0, openToken);
if (node.else.elseif) {
// else if
return;
}
}
const [openCloseTagToken, endIfToken, closeCloseTagToken] = sourceCode.getLastTokens(node, {
count: 3,
includeComments: false
});
offsets.setOffsetToken(openCloseTagToken, 0, openToken);
offsets.setOffsetToken(endIfToken, 1, openCloseTagToken);
offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
},
SvelteElseBlock(node) {
if (node.elseif) {
return;
}
const [openToken, elseToken, closeToken] = sourceCode.getFirstTokens(node, {
count: 3,
includeComments: false
});
offsets.setOffsetToken(elseToken, 1, openToken);
offsets.setOffsetToken(closeToken, 0, openToken);
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
},
SvelteEachBlock(node) {
const [openToken, eachToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(eachToken, 1, openToken);
offsets.setOffsetElementList([node.expression, node.context, node.index], eachToken, null, 1);
if (node.key) {
const key = getFirstAndLastTokens(sourceCode, node.key);
offsets.setOffsetToken(key.firstToken, 1, eachToken);
const closeOpenTagToken = sourceCode.getTokenAfter(key.lastToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
}
else {
const closeOpenTagToken = sourceCode.getTokenAfter(node.index || node.context || node.expression);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
}
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
if (node.else) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.else), 0, openToken);
}
const [openCloseTagToken, endEachToken, closeCloseTagToken] = sourceCode.getLastTokens(node, {
count: 3,
includeComments: false
});
offsets.setOffsetToken(openCloseTagToken, 0, openToken);
offsets.setOffsetToken(endEachToken, 1, openCloseTagToken);
offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
},
SvelteAwaitBlock(node) {
const [openToken, awaitToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(awaitToken, 1, openToken);
const exp = getFirstAndLastTokens(sourceCode, node.expression);
offsets.setOffsetToken(exp.firstToken, 1, awaitToken);
if (node.pending) {
const closeOpenTagToken = sourceCode.getTokenAfter(exp.lastToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
offsets.setOffsetToken(sourceCode.getFirstToken(node.pending, {
includeComments: false,
filter: isNotWhitespace
}), 1, openToken);
}
if (node.then) {
if (node.kind === 'await-then') {
// {#await expression then value}
const thenToken = sourceCode.getTokenAfter(exp.lastToken);
offsets.setOffsetToken(thenToken, 1, openToken);
if (node.then.value) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.then.value), 1, thenToken);
}
const closeOpenTagToken = sourceCode.getTokenAfter(node.then.value || thenToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
}
else {
// {:then value}
offsets.setOffsetToken(sourceCode.getFirstToken(node.then), 0, openToken);
}
}
if (node.catch) {
if (node.kind === 'await-catch') {
// {#await expression catch error}
const catchToken = sourceCode.getTokenAfter(exp.lastToken);
offsets.setOffsetToken(catchToken, 1, openToken);
if (node.catch.error) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.catch.error), 1, catchToken);
}
const closeOpenTagToken = sourceCode.getTokenAfter(node.catch.error || catchToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
}
else {
// {:catch value}
offsets.setOffsetToken(sourceCode.getFirstToken(node.catch), 0, openToken);
}
}
const [openCloseTagToken, endAwaitToken, closeCloseTagToken] = sourceCode.getLastTokens(node, {
count: 3,
includeComments: false
});
offsets.setOffsetToken(openCloseTagToken, 0, openToken);
offsets.setOffsetToken(endAwaitToken, 1, openCloseTagToken);
offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
},
SvelteAwaitPendingBlock(node) {
const openToken = sourceCode.getFirstToken(node);
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
},
SvelteAwaitThenBlock(node) {
if (!node.awaitThen) {
// {:then value}
const [openToken, thenToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(thenToken, 1, openToken);
if (node.value) {
const valueToken = sourceCode.getFirstToken(node.value);
offsets.setOffsetToken(valueToken, 1, thenToken);
}
const closeOpenTagToken = sourceCode.getTokenAfter(node.value || thenToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
}
const openToken = sourceCode.getFirstToken(node);
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
},
SvelteAwaitCatchBlock(node) {
if (!node.awaitCatch) {
// {:catch error}
const [openToken, catchToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(catchToken, 1, openToken);
if (node.error) {
const errorToken = sourceCode.getFirstToken(node.error);
offsets.setOffsetToken(errorToken, 1, catchToken);
}
const closeOpenTagToken = sourceCode.getTokenAfter(node.error || catchToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
}
const openToken = sourceCode.getFirstToken(node);
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
},
SvelteKeyBlock(node) {
const [openToken, keyToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(keyToken, 1, openToken);
const exp = getFirstAndLastTokens(sourceCode, node.expression);
offsets.setOffsetToken(exp.firstToken, 1, keyToken);
const closeOpenTagToken = sourceCode.getTokenAfter(exp.lastToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
const [openCloseTagToken, endKeyToken, closeCloseTagToken] = sourceCode.getLastTokens(node, {
count: 3,
includeComments: false
});
offsets.setOffsetToken(openCloseTagToken, 0, openToken);
offsets.setOffsetToken(endKeyToken, 1, openCloseTagToken);
offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
},
SvelteSnippetBlock(node) {
const [openToken, snippetToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(snippetToken, 1, openToken);
const snippetName = sourceCode.getTokenAfter(snippetToken);
offsets.setOffsetToken(snippetName, 1, snippetToken);
const leftParenToken = sourceCode.getTokenBefore(node.params[0] || sourceCode.getLastToken(node), {
filter: isOpeningParenToken,
includeComments: false
});
const rightParenToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftParenToken, {
filter: isClosingParenToken,
includeComments: false
});
offsets.setOffsetToken(leftParenToken, 1, snippetName);
offsets.setOffsetElementList(node.params, leftParenToken, rightParenToken, 1);
const closeOpenTagToken = sourceCode.getTokenAfter(rightParenToken);
offsets.setOffsetToken(closeOpenTagToken, 0, openToken);
for (const child of node.children) {
const token = sourceCode.getFirstToken(child, {
includeComments: false,
filter: isNotWhitespace
});
offsets.setOffsetToken(token, 1, openToken);
}
const [openCloseTagToken, endSnippetToken, closeCloseTagToken] = sourceCode.getLastTokens(node, { count: 3, includeComments: false });
offsets.setOffsetToken(openCloseTagToken, 0, openToken);
offsets.setOffsetToken(endSnippetToken, 1, openCloseTagToken);
offsets.setOffsetToken(closeCloseTagToken, 0, openCloseTagToken);
},
// ----------------------------------------------------------------------
// COMMENTS
// ----------------------------------------------------------------------
SvelteHTMLComment(_node) {
// noop
},
// ----------------------------------------------------------------------
// NAMES
// ----------------------------------------------------------------------
SvelteName(_node) {
// noop
},
SvelteMemberExpressionName(_node) {
// noop
}
};
return visitor;
}
/**
* Check whether the given node is not an empty text node.
* @param node The node to check.
* @returns `false` if the token is empty text node.
*/
function isNotEmptyTextNode(node) {
return !(node.type === 'SvelteText' && node.value.trim() === '');
}

View File

@@ -0,0 +1,11 @@
import type { IndentContext } from './commons.js';
import type { TSNodeListener } from '../../types-for-node.js';
type NodeListener = TSNodeListener;
/**
* Creates AST event handlers for svelte nodes.
*
* @param context The rule context.
* @returns AST event handlers.
*/
export declare function defineVisitor(context: IndentContext): NodeListener;
export {};

View File

@@ -0,0 +1,915 @@
import { isClosingBracketToken, isClosingParenToken, isNotClosingParenToken, isNotOpeningBraceToken, isOpeningBraceToken, isOpeningBracketToken, isOpeningParenToken, isSemicolonToken } from '@eslint-community/eslint-utils';
import { isBeginningOfLine } from './commons.js';
import { getFirstAndLastTokens } from './commons.js';
/**
* Creates AST event handlers for svelte nodes.
*
* @param context The rule context.
* @returns AST event handlers.
*/
export function defineVisitor(context) {
const { offsets, sourceCode } = context;
const visitor = {
TSTypeAnnotation(node) {
// : Type
// => Type
const [colonOrArrowToken, secondToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
const baseToken = sourceCode.getFirstToken(node.parent);
offsets.setOffsetToken([colonOrArrowToken, secondToken], 1, baseToken);
const before = sourceCode.getTokenBefore(colonOrArrowToken);
if (before && before.value === '?') {
offsets.setOffsetToken(before, 1, baseToken);
}
},
TSAsExpression(node) {
// foo as T
// or
// foo satisfies T
const expressionTokens = getFirstAndLastTokens(sourceCode, node.expression);
const asOrSatisfiesToken = sourceCode.getTokenAfter(expressionTokens.lastToken);
offsets.setOffsetToken([asOrSatisfiesToken, getFirstAndLastTokens(sourceCode, node.typeAnnotation).firstToken], 1, expressionTokens.firstToken);
},
TSSatisfiesExpression(node) {
// foo satisfies T
visitor.TSAsExpression(node);
},
TSTypeReference(node) {
// T<U>
// or
// const ErrorMap = Map<string, Error>
// ^^^^^^^^^^^^^^^^^^
const typeArguments = node.typeArguments ??
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Support old typescript-eslint
node.typeParameters;
if (typeArguments) {
const firstToken = sourceCode.getFirstToken(node);
offsets.setOffsetToken(sourceCode.getFirstToken(typeArguments), 1, firstToken);
}
},
TSInstantiationExpression(node) {
// const ErrorMap = Map<string, Error>
// ^^^^^^^^^^^^^^^^^^
visitor.TSTypeReference(node);
},
TSTypeParameterInstantiation(node) {
// <T>
offsets.setOffsetElementList(node.params, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), 1);
},
TSTypeParameterDeclaration(node) {
// <T>
visitor.TSTypeParameterInstantiation(node);
},
TSTypeAliasDeclaration(node) {
// type T = {}
const typeToken = sourceCode.getFirstToken(node);
const idToken = sourceCode.getFirstToken(node.id);
offsets.setOffsetToken(idToken, 1, typeToken);
let eqToken;
if (node.typeParameters) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.typeParameters), 1, idToken);
eqToken = sourceCode.getTokenAfter(node.typeParameters);
}
else {
eqToken = sourceCode.getTokenAfter(node.id);
}
const initToken = sourceCode.getTokenAfter(eqToken);
offsets.setOffsetToken([eqToken, initToken], 1, idToken);
},
TSFunctionType(node) {
// ()=>void
const firstToken = sourceCode.getFirstToken(node);
// new or < or (
let currToken = firstToken;
if (node.type === 'TSConstructorType') {
// currToken is new token
// < or (
currToken = sourceCode.getTokenAfter(currToken);
offsets.setOffsetToken(currToken, 1, firstToken);
}
if (node.typeParameters) {
// currToken is < token
// (
currToken = sourceCode.getTokenAfter(node.typeParameters);
offsets.setOffsetToken(currToken, 1, firstToken);
}
const leftParenToken = currToken;
const rightParenToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftParenToken, { filter: isClosingParenToken, includeComments: false });
offsets.setOffsetElementList(node.params, leftParenToken, rightParenToken, 1);
const arrowToken = sourceCode.getTokenAfter(rightParenToken);
offsets.setOffsetToken(arrowToken, 1, leftParenToken);
},
TSConstructorType(node) {
// new ()=>void
visitor.TSFunctionType(node);
},
TSTypeLiteral(node) {
// {foo:any}
offsets.setOffsetElementList(node.members, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), 1);
},
TSPropertySignature(node) {
// { target:any }
// ^^^^^^^^^^
const firstToken = sourceCode.getFirstToken(node);
const keyTokens = getFirstAndLastTokens(sourceCode, node.key);
let keyLast;
if (node.computed) {
const closeBracket = sourceCode.getTokenAfter(keyTokens.lastToken);
offsets.setOffsetElementList([node.key], firstToken, closeBracket, 1);
keyLast = closeBracket;
}
else {
keyLast = keyTokens.lastToken;
}
if (node.typeAnnotation) {
const typeAnnotationToken = sourceCode.getFirstToken(node.typeAnnotation);
offsets.setOffsetToken([...sourceCode.getTokensBetween(keyLast, typeAnnotationToken), typeAnnotationToken], 1, firstToken);
}
else if (node.optional) {
const qToken = sourceCode.getLastToken(node);
offsets.setOffsetToken(qToken, 1, firstToken);
}
},
TSIndexSignature(node) {
// { [k: string ]: string[] };
// ^^^^^^^^^^^^^^^^^^^^^^
const leftBracketToken = sourceCode.getFirstToken(node);
const rightBracketToken = sourceCode.getTokenAfter(node.parameters[node.parameters.length - 1] || leftBracketToken, { filter: isClosingBracketToken, includeComments: false });
offsets.setOffsetElementList(node.parameters, leftBracketToken, rightBracketToken, 1);
const keyLast = rightBracketToken;
if (node.typeAnnotation) {
const typeAnnotationToken = sourceCode.getFirstToken(node.typeAnnotation);
offsets.setOffsetToken([...sourceCode.getTokensBetween(keyLast, typeAnnotationToken), typeAnnotationToken], 1, leftBracketToken);
}
},
TSArrayType(node) {
// T[]
const firstToken = sourceCode.getFirstToken(node);
offsets.setOffsetToken(sourceCode.getLastTokens(node, { count: 2, includeComments: false }), 0, firstToken);
},
TSTupleType(node) {
// [T, U]
offsets.setOffsetElementList(node.elementTypes, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), 1);
},
TSQualifiedName(node) {
// A.B
const objectToken = sourceCode.getFirstToken(node);
const dotToken = sourceCode.getTokenBefore(node.right);
const propertyToken = sourceCode.getTokenAfter(dotToken);
offsets.setOffsetToken([dotToken, propertyToken], 1, objectToken);
},
TSIndexedAccessType(node) {
// A[B]
const objectToken = sourceCode.getFirstToken(node);
const leftBracketToken = sourceCode.getTokenBefore(node.indexType, {
filter: isOpeningBracketToken,
includeComments: false
});
const rightBracketToken = sourceCode.getTokenAfter(node.indexType, {
filter: isClosingBracketToken,
includeComments: false
});
offsets.setOffsetToken(leftBracketToken, 1, objectToken);
offsets.setOffsetElementList([node.indexType], leftBracketToken, rightBracketToken, 1);
},
TSUnionType(node) {
// A | B
const firstToken = sourceCode.getFirstToken(node);
const types = [...node.types];
if (getFirstAndLastTokens(sourceCode, types[0]).firstToken === firstToken) {
types.shift();
}
offsets.setOffsetElementList(types, firstToken, null, isBeginningOfLine(sourceCode, firstToken) ? 0 : 1);
},
TSIntersectionType(node) {
// A & B
visitor.TSUnionType(node);
},
TSMappedType(node) {
// {[key in foo]: bar}
const leftBraceToken = sourceCode.getFirstToken(node);
const leftBracketToken = sourceCode.getTokenBefore(node.key || node.typeParameter);
const rightBracketToken = sourceCode.getTokenAfter(node.nameType || node.constraint || node.typeParameter);
offsets.setOffsetToken([...sourceCode.getTokensBetween(leftBraceToken, leftBracketToken), leftBracketToken], 1, leftBraceToken);
offsets.setOffsetElementList([node.key, node.constraint, node.typeParameter, node.nameType], leftBracketToken, rightBracketToken, 1);
if (node.key && node.constraint) {
const firstToken = sourceCode.getFirstToken(node.key);
offsets.setOffsetToken([
...sourceCode.getTokensBetween(firstToken, node.constraint),
sourceCode.getFirstToken(node.constraint)
], 1, firstToken);
}
const rightBraceToken = sourceCode.getLastToken(node);
if (node.typeAnnotation) {
const typeAnnotationToken = sourceCode.getFirstToken(node.typeAnnotation);
offsets.setOffsetToken([
...sourceCode.getTokensBetween(rightBracketToken, typeAnnotationToken),
typeAnnotationToken
], 1, leftBraceToken);
}
else {
offsets.setOffsetToken([...sourceCode.getTokensBetween(rightBracketToken, rightBraceToken)], 1, leftBraceToken);
}
offsets.setOffsetToken(rightBraceToken, 0, leftBraceToken);
},
TSTypeParameter(node) {
// {[key in foo]: bar}
// ^^^^^^^^^^
// type T<U extends V = W>
// ^^^^^^^^^^^^^^^
const [firstToken, ...afterTokens] = sourceCode.getTokens(node);
for (const child of [node.constraint, node.default]) {
if (!child) {
continue;
}
const [, ...removeTokens] = sourceCode.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;
}
offsets.setOffsetToken(secondToken, 1, firstToken);
if (secondToken.value === 'extends') {
let prevToken = null;
let token = afterTokens.shift();
while (token) {
if (token.value === '=') {
break;
}
offsets.setOffsetToken(token, 1, secondToken);
prevToken = token;
token = afterTokens.shift();
}
while (token) {
offsets.setOffsetToken(token, 1, prevToken || secondToken);
token = afterTokens.shift();
}
}
else {
offsets.setOffsetToken(afterTokens, 1, firstToken);
}
},
TSConditionalType(node) {
// T extends Foo ? T : U
const checkTypeToken = sourceCode.getFirstToken(node);
const extendsToken = sourceCode.getTokenAfter(node.checkType);
const extendsTypeToken = sourceCode.getFirstToken(node.extendsType);
offsets.setOffsetToken(extendsToken, 1, checkTypeToken);
offsets.setOffsetToken(extendsTypeToken, 1, extendsToken);
const questionToken = sourceCode.getTokenAfter(node.extendsType, {
filter: isNotClosingParenToken,
includeComments: false
});
const consequentToken = sourceCode.getTokenAfter(questionToken);
const colonToken = sourceCode.getTokenAfter(node.trueType, {
filter: isNotClosingParenToken,
includeComments: false
});
const alternateToken = sourceCode.getTokenAfter(colonToken);
let baseNode = node;
let parent = baseNode.parent;
while (parent && parent.type === 'TSConditionalType' && parent.falseType === baseNode) {
baseNode = parent;
parent = baseNode.parent;
}
const baseToken = sourceCode.getFirstToken(baseNode);
offsets.setOffsetToken([questionToken, colonToken], 1, baseToken);
offsets.setOffsetToken(consequentToken, 1, questionToken);
offsets.setOffsetToken(alternateToken, 1, colonToken);
},
TSInterfaceDeclaration(node) {
// interface I {}
const interfaceToken = sourceCode.getFirstToken(node);
offsets.setOffsetToken(sourceCode.getFirstToken(node.id), 1, interfaceToken);
if (node.typeParameters != null) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.typeParameters), 1, sourceCode.getFirstToken(node.id));
}
if (node.extends != null && node.extends.length) {
const extendsToken = sourceCode.getTokenBefore(node.extends[0]);
offsets.setOffsetToken(extendsToken, 1, interfaceToken);
offsets.setOffsetElementList(node.extends, extendsToken, null, 1);
}
// It may not calculate the correct location because the visitor key is not provided.
// if (node.implements != null && node.implements.length) {
// const implementsToken = sourceCode.getTokenBefore(node.implements[0])!
// offsets.setOffsetToken(implementsToken, 1, interfaceToken)
// offsets.setOffsetElementList( node.implements, implementsToken, null, 1)
// }
const bodyToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyToken, 0, interfaceToken);
},
TSInterfaceBody(node) {
offsets.setOffsetElementList(node.body, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), 1);
},
TSClassImplements(node) {
// class C implements T {}
// ^
const typeArguments = node.typeArguments ??
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Support old typescript-eslint
node.typeParameters;
if (typeArguments) {
offsets.setOffsetToken(sourceCode.getFirstToken(typeArguments), 1, sourceCode.getFirstToken(node));
}
},
TSInterfaceHeritage(node) {
// interface I extends E implements T {}
// ^ ^
visitor.TSClassImplements(node);
},
TSEnumDeclaration(node) {
// enum E {}
const firstToken = sourceCode.getFirstToken(node);
const idTokens = getFirstAndLastTokens(sourceCode, node.id);
const prefixTokens = sourceCode.getTokensBetween(firstToken, idTokens.firstToken);
offsets.setOffsetToken(prefixTokens, 0, firstToken);
offsets.setOffsetToken(idTokens.firstToken, 1, firstToken);
const leftBraceToken = sourceCode.getTokenAfter(idTokens.lastToken);
const rightBraceToken = sourceCode.getLastToken(node);
offsets.setOffsetToken(leftBraceToken, 0, firstToken);
if (node.members)
offsets.setOffsetElementList(node.members, leftBraceToken, rightBraceToken, 1);
},
TSEnumBody(node) {
// enum E {...}
// ^^^^^
const leftBraceToken = sourceCode.getFirstToken(node);
const rightBraceToken = sourceCode.getLastToken(node);
offsets.setOffsetElementList(node.members, leftBraceToken, rightBraceToken, 1);
},
TSModuleDeclaration(node) {
const firstToken = sourceCode.getFirstToken(node);
const idTokens = getFirstAndLastTokens(sourceCode, node.id);
const prefixTokens = sourceCode.getTokensBetween(firstToken, idTokens.firstToken);
offsets.setOffsetToken(prefixTokens, 0, firstToken);
offsets.setOffsetToken(idTokens.firstToken, 1, firstToken);
if (node.body) {
const bodyFirstToken = sourceCode.getFirstToken(node.body);
offsets.setOffsetToken(bodyFirstToken, isOpeningBraceToken(bodyFirstToken) ? 0 : 1, firstToken);
}
},
TSModuleBlock(node) {
visitor.TSInterfaceBody(node);
},
TSMethodSignature(node) {
// fn(arg: A): R | null;
const firstToken = sourceCode.getFirstToken(node);
const keyTokens = getFirstAndLastTokens(sourceCode, node.key);
let keyLast;
if (node.computed) {
const closeBracket = sourceCode.getTokenAfter(keyTokens.lastToken);
offsets.setOffsetElementList([node.key], firstToken, closeBracket, 1);
keyLast = closeBracket;
}
else {
keyLast = keyTokens.lastToken;
}
const leftParenToken = sourceCode.getTokenAfter(keyLast, {
filter: isOpeningParenToken,
includeComments: false
});
offsets.setOffsetToken([...sourceCode.getTokensBetween(keyLast, leftParenToken), leftParenToken], 1, firstToken);
const rightParenToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftParenToken, { filter: isClosingParenToken, includeComments: false });
offsets.setOffsetElementList(node.params, leftParenToken, rightParenToken, 1);
if (node.returnType) {
const typeAnnotationToken = sourceCode.getFirstToken(node.returnType);
offsets.setOffsetToken([...sourceCode.getTokensBetween(keyLast, typeAnnotationToken), typeAnnotationToken], 1, firstToken);
}
},
TSCallSignatureDeclaration(node) {
// interface A { <T> (e: E): R }
// ^^^^^^^^^^^^^
const firstToken = sourceCode.getFirstToken(node);
// new or < or (
let currToken = firstToken;
if (node.type === 'TSConstructSignatureDeclaration') {
// currToken is new token
// < or (
currToken = sourceCode.getTokenAfter(currToken);
offsets.setOffsetToken(currToken, 1, firstToken);
}
if (node.typeParameters) {
// currToken is < token
// (
currToken = sourceCode.getTokenAfter(node.typeParameters);
offsets.setOffsetToken(currToken, 1, firstToken);
}
const leftParenToken = currToken;
const rightParenToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftParenToken, { filter: isClosingParenToken, includeComments: false });
offsets.setOffsetElementList(node.params, leftParenToken, rightParenToken, 1);
if (node.returnType) {
const typeAnnotationToken = sourceCode.getFirstToken(node.returnType);
offsets.setOffsetToken([
...sourceCode.getTokensBetween(rightParenToken, typeAnnotationToken),
typeAnnotationToken
], 1, firstToken);
}
},
TSConstructSignatureDeclaration(node) {
// interface A { new <T> (e: E): R }
// ^^^^^^^^^^^^^^^^^
visitor.TSCallSignatureDeclaration(node);
},
TSEmptyBodyFunctionExpression(node) {
const firstToken = sourceCode.getFirstToken(node);
let leftParenToken, bodyBaseToken;
if (firstToken.type === 'Punctuator') {
// method
leftParenToken = firstToken;
bodyBaseToken = sourceCode.getFirstToken(node.parent);
}
else {
let nextToken = sourceCode.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;
}
offsets.setOffsetToken(nextToken, nextTokenOffset, firstToken);
nextToken = sourceCode.getTokenAfter(nextToken);
}
leftParenToken = nextToken;
bodyBaseToken = firstToken;
}
if (!isOpeningParenToken(leftParenToken) && node.typeParameters) {
leftParenToken = sourceCode.getTokenAfter(node.typeParameters);
}
const rightParenToken = sourceCode.getTokenAfter(node.params[node.params.length - 1] || leftParenToken, { filter: isClosingParenToken, includeComments: false });
offsets.setOffsetToken(leftParenToken, 1, bodyBaseToken);
offsets.setOffsetElementList(node.params, leftParenToken, rightParenToken, 1);
},
TSDeclareFunction(node) {
// function fn(): void;
visitor.TSEmptyBodyFunctionExpression(node);
},
TSTypeOperator(node) {
// keyof T
const firstToken = sourceCode.getFirstToken(node);
const nextToken = sourceCode.getTokenAfter(firstToken);
offsets.setOffsetToken(nextToken, 1, firstToken);
},
TSTypeQuery(node) {
// type T = typeof a
visitor.TSTypeOperator(node);
},
TSInferType(node) {
// infer U
visitor.TSTypeOperator(node);
},
TSTypePredicate(node) {
// v is T
const firstToken = sourceCode.getFirstToken(node);
const opToken = sourceCode.getTokenAfter(node.parameterName, {
filter: isNotClosingParenToken,
includeComments: false
});
const rightToken = node.typeAnnotation && getFirstAndLastTokens(sourceCode, node.typeAnnotation).firstToken;
offsets.setOffsetToken([opToken, rightToken], 1, getFirstAndLastTokens(sourceCode, firstToken).firstToken);
},
TSAbstractMethodDefinition(node) {
const { keyNode, valueNode } = node.type === 'TSEnumMember'
? { keyNode: node.id, valueNode: node.initializer }
: { keyNode: node.key, valueNode: node.value };
const firstToken = sourceCode.getFirstToken(node);
const keyTokens = getFirstAndLastTokens(sourceCode, keyNode);
const prefixTokens = sourceCode.getTokensBetween(firstToken, keyTokens.firstToken);
if (node.computed) {
prefixTokens.pop(); // pop [
}
offsets.setOffsetToken(prefixTokens, 0, firstToken);
let lastKeyToken;
if (node.computed) {
const leftBracketToken = sourceCode.getTokenBefore(keyTokens.firstToken);
const rightBracketToken = (lastKeyToken = sourceCode.getTokenAfter(keyTokens.lastToken));
offsets.setOffsetToken(leftBracketToken, 0, firstToken);
offsets.setOffsetElementList([keyNode], leftBracketToken, rightBracketToken, 1);
}
else {
offsets.setOffsetToken(keyTokens.firstToken, 0, firstToken);
lastKeyToken = keyTokens.lastToken;
}
if (valueNode != null) {
const initToken = sourceCode.getFirstToken(valueNode);
offsets.setOffsetToken([...sourceCode.getTokensBetween(lastKeyToken, initToken), initToken], 1, lastKeyToken);
}
},
TSAbstractPropertyDefinition(node) {
visitor.TSAbstractMethodDefinition(node);
},
TSEnumMember(node) {
visitor.TSAbstractMethodDefinition(node);
},
TSAbstractAccessorProperty(node) {
visitor.TSAbstractMethodDefinition(node);
},
TSOptionalType(node) {
// [number?]
// ^^^^^^^
offsets.setOffsetToken(sourceCode.getLastToken(node), 1, sourceCode.getFirstToken(node));
},
TSNonNullExpression(node) {
// v!
visitor.TSOptionalType(node);
},
TSJSDocNonNullableType(
// @ts-expect-error -- Missing TSJSDocNonNullableType type
node) {
// T!
visitor.TSOptionalType(node);
},
TSTypeAssertion(node) {
// <const>
const firstToken = sourceCode.getFirstToken(node);
const expressionToken = getFirstAndLastTokens(sourceCode, node.expression).firstToken;
offsets.setOffsetElementList([node.typeAnnotation], firstToken, sourceCode.getTokenBefore(expressionToken), 1);
offsets.setOffsetToken(expressionToken, 1, firstToken);
},
TSImportType(node) {
// import('foo').B
const typeArguments = node.typeArguments ??
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Support old typescript-eslint
node.typeParameters;
const firstToken = sourceCode.getFirstToken(node);
const leftParenToken = sourceCode.getTokenAfter(firstToken, {
filter: isOpeningParenToken,
includeComments: false
});
offsets.setOffsetToken(leftParenToken, 1, firstToken);
const args = [];
if (node.source) {
args.push(node.source);
if (node.options) {
args.push(node.options);
}
}
else {
// typescript-eslint<v8.48 node
args.push(node.argument ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- typescript-eslint<v6 node
node.parameter);
}
const rightParenToken = sourceCode.getTokenAfter(args[args.length - 1], {
filter: isClosingParenToken,
includeComments: false
});
offsets.setOffsetElementList(args, leftParenToken, rightParenToken, 1);
if (node.qualifier) {
const dotToken = sourceCode.getTokenBefore(node.qualifier);
const propertyToken = sourceCode.getTokenAfter(dotToken);
offsets.setOffsetToken([dotToken, propertyToken], 1, firstToken);
}
if (typeArguments) {
offsets.setOffsetToken(sourceCode.getFirstToken(typeArguments), 1, firstToken);
}
},
TSParameterProperty(node) {
// constructor(private a)
const firstToken = sourceCode.getFirstToken(node);
const parameterToken = sourceCode.getFirstToken(node.parameter);
offsets.setOffsetToken([...sourceCode.getTokensBetween(firstToken, parameterToken), parameterToken], 1, firstToken);
},
TSImportEqualsDeclaration(node) {
// import foo = require('foo')
const importToken = sourceCode.getFirstToken(node);
const idTokens = getFirstAndLastTokens(sourceCode, node.id);
offsets.setOffsetToken(idTokens.firstToken, 1, importToken);
const opToken = sourceCode.getTokenAfter(idTokens.lastToken);
offsets.setOffsetToken([opToken, sourceCode.getFirstToken(node.moduleReference)], 1, idTokens.lastToken);
},
TSExternalModuleReference(node) {
// require('foo')
const requireToken = sourceCode.getFirstToken(node);
const leftParenToken = sourceCode.getTokenAfter(requireToken, {
filter: isOpeningParenToken,
includeComments: false
});
const rightParenToken = sourceCode.getLastToken(node);
offsets.setOffsetToken(leftParenToken, 1, requireToken);
offsets.setOffsetElementList([node.expression], leftParenToken, rightParenToken, 1);
},
TSExportAssignment(node) {
// export = foo
const exportNode = sourceCode.getFirstToken(node);
const exprTokens = getFirstAndLastTokens(sourceCode, node.expression);
const opToken = sourceCode.getTokenBefore(exprTokens.firstToken);
offsets.setOffsetToken([opToken, exprTokens.firstToken], 1, exportNode);
},
TSNamedTupleMember(node) {
// [a: string, ...b: string[]]
// ^^^^^^^^^
const labelToken = sourceCode.getFirstToken(node);
const elementTokens = getFirstAndLastTokens(sourceCode, node.elementType);
offsets.setOffsetToken([
...sourceCode.getTokensBetween(labelToken, elementTokens.firstToken),
elementTokens.firstToken
], 1, labelToken);
},
TSRestType(node) {
// [a: string, ...b: string[]]
// ^^^^^^^^^^^^^^
const firstToken = sourceCode.getFirstToken(node);
const nextToken = sourceCode.getTokenAfter(firstToken);
offsets.setOffsetToken(nextToken, 1, firstToken);
},
TSNamespaceExportDeclaration(node) {
const firstToken = sourceCode.getFirstToken(node);
const idToken = sourceCode.getFirstToken(node.id);
offsets.setOffsetToken([...sourceCode.getTokensBetween(firstToken, idToken), idToken], 1, firstToken);
},
TSTemplateLiteralType(node) {
const firstToken = sourceCode.getFirstToken(node);
const quasiTokens = node.quasis.slice(1).map((n) => sourceCode.getFirstToken(n));
const expressionToken = node.quasis.slice(0, -1).map((n) => sourceCode.getTokenAfter(n));
offsets.setOffsetToken(quasiTokens, 0, firstToken);
offsets.setOffsetToken(expressionToken, 1, firstToken);
},
// ----------------------------------------------------------------------
// NON-STANDARD NODES
// ----------------------------------------------------------------------
Decorator(node) {
// @Decorator
const [atToken, secondToken] = sourceCode.getFirstTokens(node, {
count: 2,
includeComments: false
});
offsets.setOffsetToken(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 = sourceCode.getTokenAfter(decorators[decorators?.length - 1]);
offsets.setOffsetToken(startParentToken, 0, atToken);
}
else {
const startParentToken = sourceCode.getFirstToken(parent.parent &&
(parent.parent.type === 'ExportDefaultDeclaration' ||
parent.parent.type === 'ExportNamedDeclaration') &&
node.range[0] < parent.parent.range[0]
? parent.parent
: parent);
offsets.copyOffset(atToken.range[0], startParentToken.range[0]);
}
}
else {
offsets.setOffsetToken(atToken, 0, sourceCode.getFirstToken(decorators[0]));
}
},
AccessorProperty(node) {
const keyNode = node.key;
const valueNode = node.value;
const firstToken = sourceCode.getFirstToken(node);
const keyTokens = getFirstAndLastTokens(sourceCode, keyNode);
const prefixTokens = sourceCode.getTokensBetween(firstToken, keyTokens.firstToken);
if (node.computed) {
prefixTokens.pop(); // pop [
}
offsets.setOffsetToken(prefixTokens, 0, firstToken);
let lastKeyToken;
if (node.computed) {
const leftBracketToken = sourceCode.getTokenBefore(keyTokens.firstToken);
const rightBracketToken = (lastKeyToken = sourceCode.getTokenAfter(keyTokens.lastToken));
offsets.setOffsetToken(leftBracketToken, 0, firstToken);
offsets.setOffsetElementList([keyNode], leftBracketToken, rightBracketToken, 1);
}
else {
offsets.setOffsetToken(keyTokens.firstToken, 0, firstToken);
lastKeyToken = keyTokens.lastToken;
}
if (valueNode != null) {
const initToken = sourceCode.getFirstToken(valueNode);
offsets.setOffsetToken([...sourceCode.getTokensBetween(lastKeyToken, initToken), initToken], 1, lastKeyToken);
}
},
StaticBlock(node) {
const firstToken = sourceCode.getFirstToken(node);
let next = sourceCode.getTokenAfter(firstToken);
while (next && isNotOpeningBraceToken(next)) {
offsets.setOffsetToken(next, 0, firstToken);
next = sourceCode.getTokenAfter(next);
}
offsets.setOffsetToken(next, 0, firstToken);
offsets.setOffsetElementList(node.body, next, sourceCode.getLastToken(node), 1);
},
ImportAttribute(node) {
const firstToken = sourceCode.getFirstToken(node);
const keyTokens = getFirstAndLastTokens(sourceCode, node.key);
const prefixTokens = sourceCode.getTokensBetween(firstToken, keyTokens.firstToken);
offsets.setOffsetToken(prefixTokens, 0, firstToken);
offsets.setOffsetToken(keyTokens.firstToken, 0, firstToken);
const initToken = sourceCode.getFirstToken(node.value);
offsets.setOffsetToken([...sourceCode.getTokensBetween(keyTokens.lastToken, initToken), initToken], 1, keyTokens.lastToken);
},
// ----------------------------------------------------------------------
// SINGLE TOKEN NODES
// ----------------------------------------------------------------------
// VALUES KEYWORD
TSAnyKeyword() {
// noop
},
TSBigIntKeyword() {
// noop
},
TSBooleanKeyword() {
// noop
},
TSNeverKeyword() {
// noop
},
TSNullKeyword() {
// noop
},
TSNumberKeyword() {
// noop
},
TSObjectKeyword() {
// noop
},
TSStringKeyword() {
// noop
},
TSSymbolKeyword() {
// noop
},
TSUndefinedKeyword() {
// noop
},
TSUnknownKeyword() {
// noop
},
TSVoidKeyword() {
// noop
},
// MODIFIERS KEYWORD
TSAbstractKeyword() {
// noop
},
TSAsyncKeyword() {
// noop
},
TSPrivateKeyword() {
// noop
},
TSProtectedKeyword() {
// noop
},
TSPublicKeyword() {
// noop
},
TSReadonlyKeyword() {
// noop
},
TSStaticKeyword() {
// noop
},
// OTHERS KEYWORD
TSDeclareKeyword() {
// noop
},
TSExportKeyword() {
// noop
},
TSIntrinsicKeyword() {
// noop
},
// OTHERS
TSThisType() {
// noop
},
// ----------------------------------------------------------------------
// WRAPPER NODES
// ----------------------------------------------------------------------
TSLiteralType() {
// noop
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
const commonsVisitor = {
// Process semicolons.
['TSTypeAliasDeclaration, TSCallSignatureDeclaration, TSConstructSignatureDeclaration, TSImportEqualsDeclaration,' +
'TSAbstractMethodDefinition, TSAbstractPropertyDefinition, AccessorProperty, TSAbstractAccessorProperty, TSEnumMember,' +
'TSPropertySignature, TSIndexSignature, TSMethodSignature,' +
'TSAbstractClassProperty, ClassProperty'](node) {
const firstToken = sourceCode.getFirstToken(node);
const lastToken = sourceCode.getLastToken(node);
if (isSemicolonToken(lastToken) && firstToken !== lastToken) {
const next = sourceCode.getTokenAfter(lastToken);
if (!next || lastToken.loc.start.line < next.loc.start.line) {
// End of line semicolons
offsets.setOffsetToken(lastToken, 0, firstToken);
}
}
},
'*[type=/^TS/]'(node) {
if (node.type !== 'TSAnyKeyword' &&
node.type !== 'TSArrayType' &&
node.type !== 'TSBigIntKeyword' &&
node.type !== 'TSBooleanKeyword' &&
node.type !== 'TSConditionalType' &&
node.type !== 'TSConstructorType' &&
node.type !== 'TSFunctionType' &&
node.type !== 'TSImportType' &&
node.type !== 'TSIndexedAccessType' &&
node.type !== 'TSInferType' &&
node.type !== 'TSIntersectionType' &&
node.type !== 'TSIntrinsicKeyword' &&
node.type !== 'TSLiteralType' &&
node.type !== 'TSMappedType' &&
node.type !== 'TSNamedTupleMember' &&
node.type !== 'TSNeverKeyword' &&
node.type !== 'TSNullKeyword' &&
node.type !== 'TSNumberKeyword' &&
node.type !== 'TSObjectKeyword' &&
node.type !== 'TSOptionalType' &&
node.type !== 'TSRestType' &&
node.type !== 'TSStringKeyword' &&
node.type !== 'TSSymbolKeyword' &&
node.type !== 'TSTemplateLiteralType' &&
node.type !== 'TSThisType' &&
node.type !== 'TSTupleType' &&
node.type !== 'TSTypeLiteral' &&
node.type !== 'TSTypeOperator' &&
node.type !== 'TSTypePredicate' &&
node.type !== 'TSTypeQuery' &&
node.type !== 'TSTypeReference' &&
node.type !== 'TSUndefinedKeyword' &&
node.type !== 'TSUnionType' &&
node.type !== 'TSUnknownKeyword' &&
node.type !== 'TSVoidKeyword') {
return;
}
const typeNode = node;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
if (typeNode.parent.type === 'TSParenthesizedType') {
return;
}
// Process parentheses.
let leftToken = sourceCode.getTokenBefore(typeNode);
let rightToken = sourceCode.getTokenAfter(typeNode);
let firstToken = sourceCode.getFirstToken(typeNode);
while (leftToken &&
isOpeningParenToken(leftToken) &&
rightToken &&
isClosingParenToken(rightToken)) {
offsets.setOffsetToken(firstToken, 1, leftToken);
offsets.setOffsetToken(rightToken, 0, leftToken);
firstToken = leftToken;
leftToken = sourceCode.getTokenBefore(leftToken);
rightToken = sourceCode.getTokenAfter(rightToken);
}
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
const extendsESVisitor = {
['ClassDeclaration[implements], ClassDeclaration[typeParameters], ClassDeclaration[superTypeParameters],' +
'ClassExpression[implements], ClassExpression[typeParameters], ClassExpression[superTypeParameters]'](node) {
if (node.typeParameters != null) {
offsets.setOffsetToken(sourceCode.getFirstToken(node.typeParameters), 1, sourceCode.getFirstToken(node.id || node));
}
const superTypeArguments = node.superTypeArguments ??
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Support old typescript-eslint
node.superTypeParameters;
if (superTypeArguments != null && node.superClass != null) {
offsets.setOffsetToken(sourceCode.getFirstToken(superTypeArguments), 1, sourceCode.getFirstToken(node.superClass));
}
if (node.implements != null && node.implements.length) {
const classToken = sourceCode.getFirstToken(node);
const implementsToken = sourceCode.getTokenBefore(node.implements[0]);
offsets.setOffsetToken(implementsToken, 1, classToken);
offsets.setOffsetElementList(node.implements, implementsToken, null, 1);
}
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
const deprecatedVisitor = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
TSParenthesizedType(node) {
// (T)
offsets.setOffsetElementList([node.typeAnnotation], sourceCode.getFirstToken(node), sourceCode.getLastToken(node), 1);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
ClassProperty(node) {
visitor.TSAbstractMethodDefinition(node);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
TSAbstractClassProperty(node) {
visitor.TSAbstractMethodDefinition(node);
}
};
const v = visitor;
return {
...v,
...commonsVisitor,
...extendsESVisitor,
...deprecatedVisitor
};
}