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

216 lines
8.2 KiB
Go

package format
import (
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
)
type IndentStyle int
const (
IndentStyleNone IndentStyle = iota
IndentStyleBlock
IndentStyleSmart
)
type SemicolonPreference string
const (
SemicolonPreferenceIgnore SemicolonPreference = "ignore"
SemicolonPreferenceInsert SemicolonPreference = "insert"
SemicolonPreferenceRemove SemicolonPreference = "remove"
)
type EditorSettings struct {
BaseIndentSize int
IndentSize int
TabSize int
NewLineCharacter string
ConvertTabsToSpaces bool
IndentStyle IndentStyle
TrimTrailingWhitespace bool
}
type FormatCodeSettings struct {
EditorSettings
InsertSpaceAfterCommaDelimiter core.Tristate
InsertSpaceAfterSemicolonInForStatements core.Tristate
InsertSpaceBeforeAndAfterBinaryOperators core.Tristate
InsertSpaceAfterConstructor core.Tristate
InsertSpaceAfterKeywordsInControlFlowStatements core.Tristate
InsertSpaceAfterFunctionKeywordForAnonymousFunctions core.Tristate
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis core.Tristate
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets core.Tristate
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces core.Tristate
InsertSpaceAfterOpeningAndBeforeClosingEmptyBraces core.Tristate
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces core.Tristate
InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces core.Tristate
InsertSpaceAfterTypeAssertion core.Tristate
InsertSpaceBeforeFunctionParenthesis core.Tristate
PlaceOpenBraceOnNewLineForFunctions core.Tristate
PlaceOpenBraceOnNewLineForControlBlocks core.Tristate
InsertSpaceBeforeTypeAnnotation core.Tristate
IndentMultiLineObjectLiteralBeginningOnBlankLine core.Tristate
Semicolons SemicolonPreference
IndentSwitchCase core.Tristate
}
func GetDefaultFormatCodeSettings(newLineCharacter string) *FormatCodeSettings {
return &FormatCodeSettings{
EditorSettings: EditorSettings{
IndentSize: 4,
TabSize: 4,
NewLineCharacter: newLineCharacter,
ConvertTabsToSpaces: true,
IndentStyle: IndentStyleSmart,
TrimTrailingWhitespace: true,
},
InsertSpaceAfterConstructor: core.TSFalse,
InsertSpaceAfterCommaDelimiter: core.TSTrue,
InsertSpaceAfterSemicolonInForStatements: core.TSTrue,
InsertSpaceBeforeAndAfterBinaryOperators: core.TSTrue,
InsertSpaceAfterKeywordsInControlFlowStatements: core.TSTrue,
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: core.TSFalse,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: core.TSFalse,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: core.TSFalse,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: core.TSTrue,
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: core.TSFalse,
InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: core.TSFalse,
InsertSpaceBeforeFunctionParenthesis: core.TSFalse,
PlaceOpenBraceOnNewLineForFunctions: core.TSFalse,
PlaceOpenBraceOnNewLineForControlBlocks: core.TSFalse,
Semicolons: SemicolonPreferenceIgnore,
IndentSwitchCase: core.TSTrue,
}
}
type formattingContext struct {
currentTokenSpan TextRangeWithKind
nextTokenSpan TextRangeWithKind
contextNode *ast.Node
currentTokenParent *ast.Node
nextTokenParent *ast.Node
contextNodeAllOnSameLine core.Tristate
nextNodeAllOnSameLine core.Tristate
tokensAreOnSameLine core.Tristate
contextNodeBlockIsOnOneLine core.Tristate
nextNodeBlockIsOnOneLine core.Tristate
SourceFile *ast.SourceFile
FormattingRequestKind FormatRequestKind
Options *FormatCodeSettings
scanner *scanner.Scanner
}
func NewFormattingContext(file *ast.SourceFile, kind FormatRequestKind, options *FormatCodeSettings) *formattingContext {
res := &formattingContext{
SourceFile: file,
FormattingRequestKind: kind,
Options: options,
scanner: scanner.NewScanner(),
}
res.scanner.SetText(file.Text())
res.scanner.SetSkipTrivia(true)
return res
}
func (this *formattingContext) UpdateContext(cur TextRangeWithKind, curParent *ast.Node, next TextRangeWithKind, nextParent *ast.Node, commonParent *ast.Node) {
if curParent == nil {
panic("nil current range node parent in update context")
}
if nextParent == nil {
panic("nil next range node parent in update context")
}
if commonParent == nil {
panic("nil common parent node in update context")
}
this.currentTokenSpan = cur
this.currentTokenParent = curParent
this.nextTokenSpan = next
this.nextTokenParent = nextParent
this.contextNode = commonParent
// drop cached results
this.contextNodeAllOnSameLine = core.TSUnknown
this.nextNodeAllOnSameLine = core.TSUnknown
this.tokensAreOnSameLine = core.TSUnknown
this.contextNodeBlockIsOnOneLine = core.TSUnknown
this.nextNodeBlockIsOnOneLine = core.TSUnknown
}
func (this *formattingContext) rangeIsOnOneLine(node core.TextRange) core.Tristate {
if rangeIsOnOneLine(node, this.SourceFile) {
return core.TSTrue
}
return core.TSFalse
}
func (this *formattingContext) nodeIsOnOneLine(node *ast.Node) core.Tristate {
return this.rangeIsOnOneLine(withTokenStart(node, this.SourceFile))
}
func withTokenStart(loc *ast.Node, file *ast.SourceFile) core.TextRange {
startPos := scanner.GetTokenPosOfNode(loc, file, false)
return core.NewTextRange(startPos, loc.End())
}
func (this *formattingContext) blockIsOnOneLine(node *ast.Node) core.Tristate {
// In strada, this relies on token child manifesting - we just use the scanner here,
// so this will have a differing performance profile. Is this OK? Needs profiling to know.
this.scanner.ResetPos(node.Pos())
end := node.End()
firstOpenBrace := -1
lastCloseBrace := -1
for this.scanner.TokenEnd() < end {
// tokenStart instead of tokenfullstart to skip trivia
if firstOpenBrace == -1 && this.scanner.Token() == ast.KindOpenBraceToken {
firstOpenBrace = this.scanner.TokenStart()
} else if this.scanner.Token() == ast.KindCloseBraceToken {
lastCloseBrace = this.scanner.TokenStart()
}
this.scanner.Scan()
}
if firstOpenBrace != -1 && lastCloseBrace != -1 {
return this.rangeIsOnOneLine(core.NewTextRange(firstOpenBrace, lastCloseBrace))
}
return core.TSFalse
}
func (this *formattingContext) ContextNodeAllOnSameLine() bool {
if this.contextNodeAllOnSameLine == core.TSUnknown {
this.contextNodeAllOnSameLine = this.nodeIsOnOneLine(this.contextNode)
}
return this.contextNodeAllOnSameLine == core.TSTrue
}
func (this *formattingContext) NextNodeAllOnSameLine() bool {
if this.nextNodeAllOnSameLine == core.TSUnknown {
this.nextNodeAllOnSameLine = this.nodeIsOnOneLine(this.nextTokenParent)
}
return this.nextNodeAllOnSameLine == core.TSTrue
}
func (this *formattingContext) TokensAreOnSameLine() bool {
if this.tokensAreOnSameLine == core.TSUnknown {
this.tokensAreOnSameLine = this.rangeIsOnOneLine(core.NewTextRange(this.currentTokenSpan.Loc.Pos(), this.nextTokenSpan.Loc.End()))
}
return this.tokensAreOnSameLine == core.TSTrue
}
func (this *formattingContext) ContextNodeBlockIsOnOneLine() bool {
if this.contextNodeBlockIsOnOneLine == core.TSUnknown {
this.contextNodeBlockIsOnOneLine = this.blockIsOnOneLine(this.contextNode)
}
return this.contextNodeBlockIsOnOneLine == core.TSTrue
}
func (this *formattingContext) NextNodeBlockIsOnOneLine() bool {
if this.nextNodeBlockIsOnOneLine == core.TSUnknown {
this.nextNodeBlockIsOnOneLine = this.blockIsOnOneLine(this.nextTokenParent)
}
return this.nextNodeBlockIsOnOneLine == core.TSTrue
}