Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add partial support for TS to indent rule #30

Merged
merged 11 commits into from
Jun 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
!/.vscode
!/.github
/prettier-playground
/tests/fixtures/rules/indent/invalid/ts
6 changes: 6 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ module.exports = {
{
files: ["*.svelte"],
parser: "svelte-eslint-parser",
parserOptions: {
parser: {
ts: "@typescript-eslint/parser",
js: "espree",
},
},
},
{
files: ["*.ts"],
Expand Down
2 changes: 1 addition & 1 deletion docs/rules/indent.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ since: "v0.3.0"
This rule enforces a consistent indentation style in `.svelte`. The default style is 2 spaces.

- This rule checks all tags, also all expressions in directives and mustaches.
- In the expressions, this rule supports ECMAScript 2021 syntaxes. It ignores unknown AST nodes, but it might be confused by non-standard syntaxes.
- 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.

<eslint-code-block fix>

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"pretest:base": "cross-env DEBUG=eslint-plugin-svelte*",
"test": "mocha --require ts-node/register \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
"cover": "nyc --reporter=lcov npm run test",
"debug": "mocha --require ts-node/register/transpile-only \"tests/src/**/*.ts\" --reporter dot",
"debug": "mocha --require ts-node/register/transpile-only \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
"lint": "eslint .",
"eslint-fix": "eslint . --fix",
"update": "ts-node --transpile-only ./tools/update.ts && npm run eslint-fix && npm run test",
Expand Down
4 changes: 3 additions & 1 deletion src/rules/indent-helpers/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export function isNotWhitespace(
token: AnyToken | ESTree.Comment | null | undefined,
): boolean {
return (
token != null && (token.type !== "HTMLText" || Boolean(token.value.trim()))
token != null &&
(token.type !== "HTMLText" || Boolean(token.value.trim())) &&
(token.type !== "JSXText" || Boolean(token.value.trim()))
)
}
27 changes: 22 additions & 5 deletions src/rules/indent-helpers/commons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import type { AST } from "svelte-eslint-parser"
import { isOpeningParenToken, isClosingParenToken } from "eslint-utils"
import { isNotWhitespace, isWhitespace } from "./ast"

type AnyToken = AST.Token | AST.Comment
export type AnyToken = AST.Token | AST.Comment
export type MaybeNode = {
type: string
range: [number, number]
loc: AST.SourceLocation
}

export type IndentOptions = {
indentChar: " " | "\t"
Expand Down Expand Up @@ -49,9 +54,9 @@ export type IndentContext = {
*/
export function setOffsetNodes(
{ sourceCode, setOffset }: IndentContext,
nodes: (ASTNode | AnyToken | null | undefined)[],
baseNodeOrToken: ASTNode | AnyToken,
lastNodeOrToken: ASTNode | AnyToken | null,
nodes: (ASTNode | AnyToken | MaybeNode | null | undefined)[],
baseNodeOrToken: ASTNode | AnyToken | MaybeNode,
lastNodeOrToken: ASTNode | AnyToken | MaybeNode | null,
offset: number,
): void {
const baseToken = sourceCode.getFirstToken(baseNodeOrToken)
Expand Down Expand Up @@ -105,7 +110,7 @@ export function setOffsetNodes(
*/
export function getFirstAndLastTokens(
sourceCode: SourceCode,
node: ASTNode | AnyToken,
node: ASTNode | AnyToken | MaybeNode,
borderOffset = 0,
): { firstToken: AST.Token; lastToken: AST.Token } {
let firstToken = sourceCode.getFirstToken(node)
Expand Down Expand Up @@ -133,3 +138,15 @@ export function getFirstAndLastTokens(

return { firstToken, lastToken }
}

/**
* Check whether the given node or token is the beginning of a line.
*/
export function isBeginningOfLine(
sourceCode: SourceCode,
node: ASTNode | AnyToken | MaybeNode,
): boolean {
const prevToken = sourceCode.getTokenBefore(node, { includeComments: false })

return !prevToken || prevToken.loc.end.line < node.loc!.start.line
}
121 changes: 60 additions & 61 deletions src/rules/indent-helpers/es.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { AST } from "svelte-eslint-parser"
import type * as ESTree from "estree"
import type { TSESTree } from "@typescript-eslint/types"
import type { ASTNode } from "../../types"
import type { IndentContext } from "./commons"
import { getFirstAndLastTokens } from "./commons"
Expand Down Expand Up @@ -35,10 +36,7 @@ type NodeListener = {
* @param context The rule context.
* @returns AST event handlers.
*/
export function defineVisitor(context: IndentContext): NodeListener & {
":expression": (node: ESTree.Expression) => void
":statement": (node: ESTree.Statement) => void
} {
export function defineVisitor(context: IndentContext): NodeListener {
const { sourceCode, options, setOffsetBaseLine, setOffset } = context

/**
Expand Down Expand Up @@ -80,13 +78,12 @@ export function defineVisitor(context: IndentContext): NodeListener & {
}
},
ArrayExpression(node: ESTree.ArrayExpression | ESTree.ArrayPattern) {
setOffsetNodes(
context,
node.elements,
sourceCode.getFirstToken(node),
sourceCode.getLastToken(node),
1,
const firstToken = sourceCode.getFirstToken(node)
const rightToken = sourceCode.getTokenAfter(
node.elements[node.elements.length - 1] || firstToken,
{ filter: isClosingBracketToken, includeComments: false },
)
setOffsetNodes(context, node.elements, firstToken, rightToken, 1)
},
ArrayPattern(node: ESTree.ArrayPattern) {
visitor.ArrayExpression(node)
Expand Down Expand Up @@ -250,7 +247,7 @@ export function defineVisitor(context: IndentContext): NodeListener & {
setOffset(sourceCode.getFirstToken(node.id), 1, classToken)
}
if (node.superClass != null) {
const extendsToken = sourceCode.getTokenAfter(node.id || classToken)!
const extendsToken = sourceCode.getTokenBefore(node.superClass)!
const superClassToken = sourceCode.getTokenAfter(extendsToken)
setOffset(extendsToken, 1, classToken)
setOffset(superClassToken, 1, extendsToken)
Expand Down Expand Up @@ -461,43 +458,49 @@ export function defineVisitor(context: IndentContext): NodeListener & {
) {
const firstToken = sourceCode.getFirstToken(node)
let leftParenToken, bodyBaseToken
if (isOpeningParenToken(firstToken)) {
if (firstToken.type === "Punctuator") {
// method
leftParenToken = firstToken
bodyBaseToken = sourceCode.getFirstToken(getParent(node)!)
} else {
const functionToken = node.async
? sourceCode.getTokenAfter(firstToken)!
: firstToken
const starToken = node.generator
? sourceCode.getTokenAfter(functionToken)
: null
const idToken = node.id && sourceCode.getFirstToken(node.id)

if (node.async) {
setOffset(functionToken, 0, firstToken)
}
if (node.generator) {
setOffset(starToken, 1, firstToken)
}
if (node.id != null) {
setOffset(idToken, 1, firstToken)
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
}
setOffset(nextToken, nextTokenOffset, firstToken)
nextToken = sourceCode.getTokenAfter(nextToken)
}

leftParenToken = nextToken!
bodyBaseToken = firstToken
}

if (
!isOpeningParenToken(leftParenToken) &&
(node as TSESTree.FunctionExpression).typeParameters
) {
leftParenToken = sourceCode.getTokenAfter(
idToken || starToken || functionToken,
(node as TSESTree.FunctionExpression).typeParameters!,
)!
bodyBaseToken = firstToken
}

const rightParenToken = sourceCode.getTokenAfter(
node.params[node.params.length - 1] || leftParenToken,
{ filter: isClosingParenToken, includeComments: false },
)!
const bodyToken = sourceCode.getFirstToken(node.body)

setOffset(leftParenToken, 1, bodyBaseToken)
setOffsetNodes(context, node.params, leftParenToken, rightParenToken, 1)

const bodyToken = sourceCode.getFirstToken(node.body)
setOffset(bodyToken, 0, bodyBaseToken)
},
FunctionExpression(node: ESTree.FunctionExpression) {
Expand Down Expand Up @@ -709,22 +712,13 @@ export function defineVisitor(context: IndentContext): NodeListener & {
lastKeyToken = keyTokens.lastToken
}

if (
node.type === "MethodDefinition" ||
(node.type === "Property" && node.method === true)
) {
const leftParenToken = sourceCode.getTokenAfter(lastKeyToken)
setOffset(leftParenToken, 1, lastKeyToken)
} else if (node.type === "Property" && !node.shorthand) {
const colonToken = sourceCode.getTokenAfter(lastKeyToken)!
const valueToken = sourceCode.getTokenAfter(colonToken)

setOffset([colonToken, valueToken], 1, lastKeyToken)
} else if (node.type === "PropertyDefinition" && node.value != null) {
const eqToken = sourceCode.getTokenAfter(lastKeyToken)!
const initToken = sourceCode.getTokenAfter(eqToken)

setOffset([eqToken, initToken], 1, lastKeyToken)
if (node.value) {
const initToken = sourceCode.getFirstToken(node.value)
setOffset(
[...sourceCode.getTokensBetween(lastKeyToken, initToken), initToken],
1,
lastKeyToken,
)
}
},
Property(node: ESTree.Property) {
Expand Down Expand Up @@ -753,13 +747,12 @@ export function defineVisitor(context: IndentContext): NodeListener & {
}
},
ObjectExpression(node: ESTree.ObjectExpression | ESTree.ObjectPattern) {
setOffsetNodes(
context,
node.properties,
sourceCode.getFirstToken(node),
sourceCode.getLastToken(node),
1,
const firstToken = sourceCode.getFirstToken(node)
const rightToken = sourceCode.getTokenAfter(
node.properties[node.properties.length - 1] || firstToken,
{ filter: isClosingBraceToken, includeComments: false },
)
setOffsetNodes(context, node.properties, firstToken, rightToken, 1)
},
ObjectPattern(node: ESTree.ObjectPattern) {
visitor.ObjectExpression(node)
Expand Down Expand Up @@ -929,9 +922,6 @@ export function defineVisitor(context: IndentContext): NodeListener & {
DebuggerStatement() {
// noop
},
EmptyStatement() {
// noop
},
Identifier() {
// noop
},
Expand Down Expand Up @@ -962,17 +952,20 @@ export function defineVisitor(context: IndentContext): NodeListener & {
ChainExpression() {
// noop
},
EmptyStatement() {
// noop
},
}

return {
...visitor,
":statement"(node: ESTree.Statement) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ignore
const commonVisitor: any = {
":statement, PropertyDefinition"(node: ESTree.Statement) {
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) {
// Lone semicolons
// End of line semicolons
setOffset(lastToken, 0, firstToken)
}
}
Expand All @@ -998,6 +991,12 @@ export function defineVisitor(context: IndentContext): NodeListener & {
}
},
}
const v: NodeListener = visitor

return {
...v,
...commonVisitor,
}
}

/** Get the parent node from the given node */
Expand Down
16 changes: 13 additions & 3 deletions src/rules/indent-helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import type * as ESTree from "estree"
import type { ASTNode, RuleContext, RuleListener } from "../../types"
import * as SV from "./svelte"
import * as ES from "./es"
import * as TS from "./ts"
import { isNotWhitespace } from "./ast"
import { isCommentToken } from "eslint-utils"
import type { IndentOptions } from "./commons"
import type { AnyToken, IndentOptions } from "./commons"

type IndentUserOptions = {
indent?: number | "tab"
switchCase?: number
ignoredNodes?: string[]
}
type AnyToken = AST.Token | AST.Comment

/**
* Normalize options.
Expand Down Expand Up @@ -309,7 +309,14 @@ export function defineVisitor(
saveExpectedIndent(tokens, actualIndent)
return
}
saveExpectedIndent(tokens, expectedIndent)
saveExpectedIndent(
tokens,
Math.min(
...tokens
.map(getExpectedIndentFromToken)
.filter((i): i is number => i != null),
),
)

let prev = prevToken
if (prevComments.length) {
Expand All @@ -334,6 +341,7 @@ export function defineVisitor(
const nodesVisitor = {
...ES.defineVisitor(indentContext),
...SV.defineVisitor(indentContext),
...TS.defineVisitor(indentContext),
}
const knownNodes = new Set(Object.keys(nodesVisitor))

Expand Down Expand Up @@ -365,6 +373,8 @@ export function defineVisitor(
"*:exit"(node: ASTNode) {
// Ignore tokens of unknown nodes.
if (!knownNodes.has(node.type)) {
// debugger
// console.log(node.type, node.loc!.start.line)
ignore(node)
}
},
Expand Down
6 changes: 3 additions & 3 deletions src/rules/indent-helpers/svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { isNotWhitespace } from "./ast"
import type { IndentContext } from "./commons"
import { getFirstAndLastTokens } from "./commons"
import { setOffsetNodes } from "./commons"
type NodeWithParent = Exclude<
type NodeWithoutES = Exclude<
AST.SvelteNode,
AST.SvelteProgram | AST.SvelteReactiveStatement
>
type NodeListenerMap<T extends NodeWithParent = NodeWithParent> = {
[key in NodeWithParent["type"]]: T extends { type: key } ? T : never
type NodeListenerMap<T extends NodeWithoutES = NodeWithoutES> = {
[key in NodeWithoutES["type"]]: T extends { type: key } ? T : never
}

type NodeListener = {
Expand Down
Loading