Skip to content

Commit fdbff2a

Browse files
authored
Add partial support for TS to indent rule (#30)
* Add support for TS to indent rule * update * update * fix * update * update * update doc * update * update * update * Fix decorator indent
1 parent 6d8d765 commit fdbff2a

File tree

82 files changed

+12511
-76
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+12511
-76
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
!/.vscode
88
!/.github
99
/prettier-playground
10+
/tests/fixtures/rules/indent/invalid/ts

.eslintrc.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ module.exports = {
3737
{
3838
files: ["*.svelte"],
3939
parser: "svelte-eslint-parser",
40+
parserOptions: {
41+
parser: {
42+
ts: "@typescript-eslint/parser",
43+
js: "espree",
44+
},
45+
},
4046
},
4147
{
4248
files: ["*.ts"],

docs/rules/indent.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ since: "v0.3.0"
1717
This rule enforces a consistent indentation style in `.svelte`. The default style is 2 spaces.
1818

1919
- This rule checks all tags, also all expressions in directives and mustaches.
20-
- In the expressions, this rule supports ECMAScript 2021 syntaxes. It ignores unknown AST nodes, but it might be confused by non-standard syntaxes.
20+
- In the expressions, this rule supports ECMAScript 2021 syntaxes and some TypeScript syntaxes. It ignores unknown AST nodes, but it might be confused by non-standard syntaxes.
2121

2222
<eslint-code-block fix>
2323

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"pretest:base": "cross-env DEBUG=eslint-plugin-svelte*",
1818
"test": "mocha --require ts-node/register \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
1919
"cover": "nyc --reporter=lcov npm run test",
20-
"debug": "mocha --require ts-node/register/transpile-only \"tests/src/**/*.ts\" --reporter dot",
20+
"debug": "mocha --require ts-node/register/transpile-only \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
2121
"lint": "eslint .",
2222
"eslint-fix": "eslint . --fix",
2323
"update": "ts-node --transpile-only ./tools/update.ts && npm run eslint-fix && npm run test",

src/rules/indent-helpers/ast.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export function isNotWhitespace(
1717
token: AnyToken | ESTree.Comment | null | undefined,
1818
): boolean {
1919
return (
20-
token != null && (token.type !== "HTMLText" || Boolean(token.value.trim()))
20+
token != null &&
21+
(token.type !== "HTMLText" || Boolean(token.value.trim())) &&
22+
(token.type !== "JSXText" || Boolean(token.value.trim()))
2123
)
2224
}

src/rules/indent-helpers/commons.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import type { AST } from "svelte-eslint-parser"
33
import { isOpeningParenToken, isClosingParenToken } from "eslint-utils"
44
import { isNotWhitespace, isWhitespace } from "./ast"
55

6-
type AnyToken = AST.Token | AST.Comment
6+
export type AnyToken = AST.Token | AST.Comment
7+
export type MaybeNode = {
8+
type: string
9+
range: [number, number]
10+
loc: AST.SourceLocation
11+
}
712

813
export type IndentOptions = {
914
indentChar: " " | "\t"
@@ -49,9 +54,9 @@ export type IndentContext = {
4954
*/
5055
export function setOffsetNodes(
5156
{ sourceCode, setOffset }: IndentContext,
52-
nodes: (ASTNode | AnyToken | null | undefined)[],
53-
baseNodeOrToken: ASTNode | AnyToken,
54-
lastNodeOrToken: ASTNode | AnyToken | null,
57+
nodes: (ASTNode | AnyToken | MaybeNode | null | undefined)[],
58+
baseNodeOrToken: ASTNode | AnyToken | MaybeNode,
59+
lastNodeOrToken: ASTNode | AnyToken | MaybeNode | null,
5560
offset: number,
5661
): void {
5762
const baseToken = sourceCode.getFirstToken(baseNodeOrToken)
@@ -105,7 +110,7 @@ export function setOffsetNodes(
105110
*/
106111
export function getFirstAndLastTokens(
107112
sourceCode: SourceCode,
108-
node: ASTNode | AnyToken,
113+
node: ASTNode | AnyToken | MaybeNode,
109114
borderOffset = 0,
110115
): { firstToken: AST.Token; lastToken: AST.Token } {
111116
let firstToken = sourceCode.getFirstToken(node)
@@ -133,3 +138,15 @@ export function getFirstAndLastTokens(
133138

134139
return { firstToken, lastToken }
135140
}
141+
142+
/**
143+
* Check whether the given node or token is the beginning of a line.
144+
*/
145+
export function isBeginningOfLine(
146+
sourceCode: SourceCode,
147+
node: ASTNode | AnyToken | MaybeNode,
148+
): boolean {
149+
const prevToken = sourceCode.getTokenBefore(node, { includeComments: false })
150+
151+
return !prevToken || prevToken.loc.end.line < node.loc!.start.line
152+
}

src/rules/indent-helpers/es.ts

Lines changed: 60 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { AST } from "svelte-eslint-parser"
22
import type * as ESTree from "estree"
3+
import type { TSESTree } from "@typescript-eslint/types"
34
import type { ASTNode } from "../../types"
45
import type { IndentContext } from "./commons"
56
import { getFirstAndLastTokens } from "./commons"
@@ -35,10 +36,7 @@ type NodeListener = {
3536
* @param context The rule context.
3637
* @returns AST event handlers.
3738
*/
38-
export function defineVisitor(context: IndentContext): NodeListener & {
39-
":expression": (node: ESTree.Expression) => void
40-
":statement": (node: ESTree.Statement) => void
41-
} {
39+
export function defineVisitor(context: IndentContext): NodeListener {
4240
const { sourceCode, options, setOffsetBaseLine, setOffset } = context
4341

4442
/**
@@ -80,13 +78,12 @@ export function defineVisitor(context: IndentContext): NodeListener & {
8078
}
8179
},
8280
ArrayExpression(node: ESTree.ArrayExpression | ESTree.ArrayPattern) {
83-
setOffsetNodes(
84-
context,
85-
node.elements,
86-
sourceCode.getFirstToken(node),
87-
sourceCode.getLastToken(node),
88-
1,
81+
const firstToken = sourceCode.getFirstToken(node)
82+
const rightToken = sourceCode.getTokenAfter(
83+
node.elements[node.elements.length - 1] || firstToken,
84+
{ filter: isClosingBracketToken, includeComments: false },
8985
)
86+
setOffsetNodes(context, node.elements, firstToken, rightToken, 1)
9087
},
9188
ArrayPattern(node: ESTree.ArrayPattern) {
9289
visitor.ArrayExpression(node)
@@ -250,7 +247,7 @@ export function defineVisitor(context: IndentContext): NodeListener & {
250247
setOffset(sourceCode.getFirstToken(node.id), 1, classToken)
251248
}
252249
if (node.superClass != null) {
253-
const extendsToken = sourceCode.getTokenAfter(node.id || classToken)!
250+
const extendsToken = sourceCode.getTokenBefore(node.superClass)!
254251
const superClassToken = sourceCode.getTokenAfter(extendsToken)
255252
setOffset(extendsToken, 1, classToken)
256253
setOffset(superClassToken, 1, extendsToken)
@@ -461,43 +458,49 @@ export function defineVisitor(context: IndentContext): NodeListener & {
461458
) {
462459
const firstToken = sourceCode.getFirstToken(node)
463460
let leftParenToken, bodyBaseToken
464-
if (isOpeningParenToken(firstToken)) {
461+
if (firstToken.type === "Punctuator") {
465462
// method
466463
leftParenToken = firstToken
467464
bodyBaseToken = sourceCode.getFirstToken(getParent(node)!)
468465
} else {
469-
const functionToken = node.async
470-
? sourceCode.getTokenAfter(firstToken)!
471-
: firstToken
472-
const starToken = node.generator
473-
? sourceCode.getTokenAfter(functionToken)
474-
: null
475-
const idToken = node.id && sourceCode.getFirstToken(node.id)
476-
477-
if (node.async) {
478-
setOffset(functionToken, 0, firstToken)
479-
}
480-
if (node.generator) {
481-
setOffset(starToken, 1, firstToken)
482-
}
483-
if (node.id != null) {
484-
setOffset(idToken, 1, firstToken)
466+
let nextToken = sourceCode.getTokenAfter(firstToken)
467+
let nextTokenOffset = 0
468+
while (
469+
nextToken &&
470+
!isOpeningParenToken(nextToken) &&
471+
nextToken.value !== "<"
472+
) {
473+
if (
474+
nextToken.value === "*" ||
475+
(node.id && nextToken.range[0] === node.id.range![0])
476+
) {
477+
nextTokenOffset = 1
478+
}
479+
setOffset(nextToken, nextTokenOffset, firstToken)
480+
nextToken = sourceCode.getTokenAfter(nextToken)
485481
}
486482

483+
leftParenToken = nextToken!
484+
bodyBaseToken = firstToken
485+
}
486+
487+
if (
488+
!isOpeningParenToken(leftParenToken) &&
489+
(node as TSESTree.FunctionExpression).typeParameters
490+
) {
487491
leftParenToken = sourceCode.getTokenAfter(
488-
idToken || starToken || functionToken,
492+
(node as TSESTree.FunctionExpression).typeParameters!,
489493
)!
490-
bodyBaseToken = firstToken
491494
}
492495

493496
const rightParenToken = sourceCode.getTokenAfter(
494497
node.params[node.params.length - 1] || leftParenToken,
495498
{ filter: isClosingParenToken, includeComments: false },
496499
)!
497-
const bodyToken = sourceCode.getFirstToken(node.body)
498-
499500
setOffset(leftParenToken, 1, bodyBaseToken)
500501
setOffsetNodes(context, node.params, leftParenToken, rightParenToken, 1)
502+
503+
const bodyToken = sourceCode.getFirstToken(node.body)
501504
setOffset(bodyToken, 0, bodyBaseToken)
502505
},
503506
FunctionExpression(node: ESTree.FunctionExpression) {
@@ -709,22 +712,13 @@ export function defineVisitor(context: IndentContext): NodeListener & {
709712
lastKeyToken = keyTokens.lastToken
710713
}
711714

712-
if (
713-
node.type === "MethodDefinition" ||
714-
(node.type === "Property" && node.method === true)
715-
) {
716-
const leftParenToken = sourceCode.getTokenAfter(lastKeyToken)
717-
setOffset(leftParenToken, 1, lastKeyToken)
718-
} else if (node.type === "Property" && !node.shorthand) {
719-
const colonToken = sourceCode.getTokenAfter(lastKeyToken)!
720-
const valueToken = sourceCode.getTokenAfter(colonToken)
721-
722-
setOffset([colonToken, valueToken], 1, lastKeyToken)
723-
} else if (node.type === "PropertyDefinition" && node.value != null) {
724-
const eqToken = sourceCode.getTokenAfter(lastKeyToken)!
725-
const initToken = sourceCode.getTokenAfter(eqToken)
726-
727-
setOffset([eqToken, initToken], 1, lastKeyToken)
715+
if (node.value) {
716+
const initToken = sourceCode.getFirstToken(node.value)
717+
setOffset(
718+
[...sourceCode.getTokensBetween(lastKeyToken, initToken), initToken],
719+
1,
720+
lastKeyToken,
721+
)
728722
}
729723
},
730724
Property(node: ESTree.Property) {
@@ -753,13 +747,12 @@ export function defineVisitor(context: IndentContext): NodeListener & {
753747
}
754748
},
755749
ObjectExpression(node: ESTree.ObjectExpression | ESTree.ObjectPattern) {
756-
setOffsetNodes(
757-
context,
758-
node.properties,
759-
sourceCode.getFirstToken(node),
760-
sourceCode.getLastToken(node),
761-
1,
750+
const firstToken = sourceCode.getFirstToken(node)
751+
const rightToken = sourceCode.getTokenAfter(
752+
node.properties[node.properties.length - 1] || firstToken,
753+
{ filter: isClosingBraceToken, includeComments: false },
762754
)
755+
setOffsetNodes(context, node.properties, firstToken, rightToken, 1)
763756
},
764757
ObjectPattern(node: ESTree.ObjectPattern) {
765758
visitor.ObjectExpression(node)
@@ -929,9 +922,6 @@ export function defineVisitor(context: IndentContext): NodeListener & {
929922
DebuggerStatement() {
930923
// noop
931924
},
932-
EmptyStatement() {
933-
// noop
934-
},
935925
Identifier() {
936926
// noop
937927
},
@@ -962,17 +952,20 @@ export function defineVisitor(context: IndentContext): NodeListener & {
962952
ChainExpression() {
963953
// noop
964954
},
955+
EmptyStatement() {
956+
// noop
957+
},
965958
}
966959

967-
return {
968-
...visitor,
969-
":statement"(node: ESTree.Statement) {
960+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
961+
const commonVisitor: any = {
962+
":statement, PropertyDefinition"(node: ESTree.Statement) {
970963
const firstToken = sourceCode.getFirstToken(node)
971964
const lastToken = sourceCode.getLastToken(node)
972965
if (isSemicolonToken(lastToken) && firstToken !== lastToken) {
973966
const next = sourceCode.getTokenAfter(lastToken)
974967
if (!next || lastToken.loc.start.line < next.loc.start.line) {
975-
// Lone semicolons
968+
// End of line semicolons
976969
setOffset(lastToken, 0, firstToken)
977970
}
978971
}
@@ -998,6 +991,12 @@ export function defineVisitor(context: IndentContext): NodeListener & {
998991
}
999992
},
1000993
}
994+
const v: NodeListener = visitor
995+
996+
return {
997+
...v,
998+
...commonVisitor,
999+
}
10011000
}
10021001

10031002
/** Get the parent node from the given node */

src/rules/indent-helpers/index.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ import type * as ESTree from "estree"
33
import type { ASTNode, RuleContext, RuleListener } from "../../types"
44
import * as SV from "./svelte"
55
import * as ES from "./es"
6+
import * as TS from "./ts"
67
import { isNotWhitespace } from "./ast"
78
import { isCommentToken } from "eslint-utils"
8-
import type { IndentOptions } from "./commons"
9+
import type { AnyToken, IndentOptions } from "./commons"
910

1011
type IndentUserOptions = {
1112
indent?: number | "tab"
1213
switchCase?: number
1314
ignoredNodes?: string[]
1415
}
15-
type AnyToken = AST.Token | AST.Comment
1616

1717
/**
1818
* Normalize options.
@@ -309,7 +309,14 @@ export function defineVisitor(
309309
saveExpectedIndent(tokens, actualIndent)
310310
return
311311
}
312-
saveExpectedIndent(tokens, expectedIndent)
312+
saveExpectedIndent(
313+
tokens,
314+
Math.min(
315+
...tokens
316+
.map(getExpectedIndentFromToken)
317+
.filter((i): i is number => i != null),
318+
),
319+
)
313320

314321
let prev = prevToken
315322
if (prevComments.length) {
@@ -334,6 +341,7 @@ export function defineVisitor(
334341
const nodesVisitor = {
335342
...ES.defineVisitor(indentContext),
336343
...SV.defineVisitor(indentContext),
344+
...TS.defineVisitor(indentContext),
337345
}
338346
const knownNodes = new Set(Object.keys(nodesVisitor))
339347

@@ -365,6 +373,8 @@ export function defineVisitor(
365373
"*:exit"(node: ASTNode) {
366374
// Ignore tokens of unknown nodes.
367375
if (!knownNodes.has(node.type)) {
376+
// debugger
377+
// console.log(node.type, node.loc!.start.line)
368378
ignore(node)
369379
}
370380
},

src/rules/indent-helpers/svelte.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { isNotWhitespace } from "./ast"
44
import type { IndentContext } from "./commons"
55
import { getFirstAndLastTokens } from "./commons"
66
import { setOffsetNodes } from "./commons"
7-
type NodeWithParent = Exclude<
7+
type NodeWithoutES = Exclude<
88
AST.SvelteNode,
99
AST.SvelteProgram | AST.SvelteReactiveStatement
1010
>
11-
type NodeListenerMap<T extends NodeWithParent = NodeWithParent> = {
12-
[key in NodeWithParent["type"]]: T extends { type: key } ? T : never
11+
type NodeListenerMap<T extends NodeWithoutES = NodeWithoutES> = {
12+
[key in NodeWithoutES["type"]]: T extends { type: key } ? T : never
1313
}
1414

1515
type NodeListener = {

0 commit comments

Comments
 (0)