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 }