2025-10-15 10:12:44 +03:00

191 lines
6.6 KiB
Go

package format
import (
"slices"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
)
func rangeIsOnOneLine(node core.TextRange, file *ast.SourceFile) bool {
startLine, _ := scanner.GetECMALineAndCharacterOfPosition(file, node.Pos())
endLine, _ := scanner.GetECMALineAndCharacterOfPosition(file, node.End())
return startLine == endLine
}
func getOpenTokenForList(node *ast.Node, list *ast.NodeList) ast.Kind {
switch node.Kind {
case ast.KindConstructor,
ast.KindFunctionDeclaration,
ast.KindFunctionExpression,
ast.KindMethodDeclaration,
ast.KindMethodSignature,
ast.KindArrowFunction,
ast.KindCallSignature,
ast.KindConstructSignature,
ast.KindFunctionType,
ast.KindConstructorType,
ast.KindGetAccessor,
ast.KindSetAccessor:
if node.TypeParameterList() == list {
return ast.KindLessThanToken
} else if node.ParameterList() == list {
return ast.KindOpenParenToken
}
case ast.KindCallExpression, ast.KindNewExpression:
if node.TypeArgumentList() == list {
return ast.KindLessThanToken
} else if node.ArgumentList() == list {
return ast.KindOpenParenToken
}
case ast.KindClassDeclaration,
ast.KindClassExpression,
ast.KindInterfaceDeclaration,
ast.KindTypeAliasDeclaration:
if node.TypeParameterList() == list {
return ast.KindLessThanToken
}
case ast.KindTypeReference,
ast.KindTaggedTemplateExpression,
ast.KindTypeQuery,
ast.KindExpressionWithTypeArguments,
ast.KindImportType:
if node.TypeArgumentList() == list {
return ast.KindLessThanToken
}
case ast.KindTypeLiteral:
return ast.KindOpenBraceToken
}
return ast.KindUnknown
}
func getCloseTokenForOpenToken(kind ast.Kind) ast.Kind {
// TODO: matches strada - seems like it could handle more pairs of braces, though? [] notably missing
switch kind {
case ast.KindOpenParenToken:
return ast.KindCloseParenToken
case ast.KindLessThanToken:
return ast.KindGreaterThanToken
case ast.KindOpenBraceToken:
return ast.KindCloseBraceToken
}
return ast.KindUnknown
}
func GetLineStartPositionForPosition(position int, sourceFile *ast.SourceFile) int {
lineStarts := scanner.GetECMALineStarts(sourceFile)
line, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, position)
return int(lineStarts[line])
}
/**
* Tests whether `child` is a grammar error on `parent`.
* In strada, this also checked node arrays, but it is never actually called with one in practice.
*/
func isGrammarError(parent *ast.Node, child *ast.Node) bool {
if ast.IsTypeParameterDeclaration(parent) {
return child == parent.AsTypeParameter().Expression
}
if ast.IsPropertySignatureDeclaration(parent) {
return child == parent.AsPropertySignatureDeclaration().Initializer
}
if ast.IsPropertyDeclaration(parent) {
return ast.IsAutoAccessorPropertyDeclaration(parent) && child == parent.AsPropertyDeclaration().PostfixToken && child.Kind == ast.KindQuestionToken
}
if ast.IsPropertyAssignment(parent) {
pa := parent.AsPropertyAssignment()
mods := pa.Modifiers()
return child == pa.PostfixToken || (mods != nil && isGrammarErrorElement(&mods.NodeList, child, ast.IsModifierLike))
}
if ast.IsShorthandPropertyAssignment(parent) {
sp := parent.AsShorthandPropertyAssignment()
mods := sp.Modifiers()
return child == sp.EqualsToken || child == sp.PostfixToken || (mods != nil && isGrammarErrorElement(&mods.NodeList, child, ast.IsModifierLike))
}
if ast.IsMethodDeclaration(parent) {
return child == parent.AsMethodDeclaration().PostfixToken && child.Kind == ast.KindExclamationToken
}
if ast.IsConstructorDeclaration(parent) {
return child == parent.AsConstructorDeclaration().Type || isGrammarErrorElement(parent.AsConstructorDeclaration().TypeParameters, child, ast.IsTypeParameterDeclaration)
}
if ast.IsGetAccessorDeclaration(parent) {
return isGrammarErrorElement(parent.AsGetAccessorDeclaration().TypeParameters, child, ast.IsTypeParameterDeclaration)
}
if ast.IsSetAccessorDeclaration(parent) {
return child == parent.AsSetAccessorDeclaration().Type || isGrammarErrorElement(parent.AsSetAccessorDeclaration().TypeParameters, child, ast.IsTypeParameterDeclaration)
}
if ast.IsNamespaceExportDeclaration(parent) {
mods := parent.AsNamespaceExportDeclaration().Modifiers()
return mods != nil && isGrammarErrorElement(&mods.NodeList, child, ast.IsModifierLike)
}
return false
}
func isGrammarErrorElement(list *ast.NodeList, child *ast.Node, isPossibleElement func(node *ast.Node) bool) bool {
if list == nil || len(list.Nodes) == 0 {
return false
}
if !isPossibleElement(child) {
return false
}
return slices.Contains(list.Nodes, child)
}
/**
* Validating `expectedTokenKind` ensures the token was typed in the context we expect (eg: not a comment).
* @param expectedTokenKind The kind of the last token constituting the desired parent node.
*/
func findImmediatelyPrecedingTokenOfKind(end int, expectedTokenKind ast.Kind, sourceFile *ast.SourceFile) *ast.Node {
precedingToken := astnav.FindPrecedingToken(sourceFile, end)
if precedingToken == nil || precedingToken.Kind != expectedTokenKind || precedingToken.End() != end {
return nil
}
return precedingToken
}
/**
* Finds the highest node enclosing `node` at the same list level as `node`
* and whose end does not exceed `node.end`.
*
* Consider typing the following
* ```
* let x = 1;
* while (true) {
* }
* ```
* Upon typing the closing curly, we want to format the entire `while`-statement, but not the preceding
* variable declaration.
*/
func findOutermostNodeWithinListLevel(node *ast.Node) *ast.Node {
current := node
for current != nil &&
current.Parent != nil &&
current.Parent.End() == node.End() &&
!isListElement(current.Parent, current) {
current = current.Parent
}
return current
}
// Returns true if node is a element in some list in parent
// i.e. parent is class declaration with the list of members and node is one of members.
func isListElement(parent *ast.Node, node *ast.Node) bool {
switch parent.Kind {
case ast.KindClassDeclaration, ast.KindInterfaceDeclaration:
return node.Loc.ContainedBy(parent.MemberList().Loc)
case ast.KindModuleDeclaration:
body := parent.Body()
return body != nil && body.Kind == ast.KindModuleBlock && node.Loc.ContainedBy(body.StatementList().Loc)
case ast.KindSourceFile, ast.KindBlock, ast.KindModuleBlock:
return node.Loc.ContainedBy(parent.StatementList().Loc)
case ast.KindCatchClause:
return node.Loc.ContainedBy(parent.AsCatchClause().Block.StatementList().Loc)
}
return false
}