๐Ÿš€ Grammar-Aware Code Formatter: Structure through separation (supports Go, JavaScript, TypeScript, JSX, and TSX)
go formatter code-formatter javascript typescript jsx tsx

refactor(formatter): Replace rewrite logic with engine

fuwn.net acd264ee 30fb266c

verified
+5 -155
+5 -15
formatter.go
··· 1 1 package main 2 2 3 - import ( 4 - "go/format" 5 - "go/parser" 6 - "go/token" 7 - ) 3 + import "github.com/Fuwn/iku/engine" 8 4 9 5 type CommentMode int 10 6 ··· 26 22 } 27 23 28 24 func (f *Formatter) Format(source []byte) ([]byte, error) { 29 - formattedSource, err := format.Source(source) 30 - 31 - if err != nil { 32 - return nil, err 33 - } 34 - 35 - tokenFileSet := token.NewFileSet() 36 - parsedFile, err := parser.ParseFile(tokenFileSet, "", formattedSource, parser.ParseComments) 25 + adapter := &GoAdapter{} 26 + _, events, err := adapter.Analyze(source) 37 27 38 28 if err != nil { 39 29 return nil, err 40 30 } 41 31 42 - lineInformationMap := f.buildLineInfo(tokenFileSet, parsedFile) 32 + formattingEngine := &engine.Engine{CommentMode: MapCommentMode(f.CommentMode)} 43 33 44 - return f.rewrite(formattedSource, lineInformationMap), nil 34 + return []byte(formattingEngine.FormatToString(events)), nil 45 35 }
-140
rewrite.go
··· 1 - package main 2 - 3 - import "strings" 4 - 5 - func (f *Formatter) rewrite(formattedSource []byte, lineInformationMap map[int]*lineInformation) []byte { 6 - sourceLines := strings.Split(string(formattedSource), "\n") 7 - resultLines := make([]string, 0, len(sourceLines)) 8 - previousWasOpenBrace := false 9 - previousStatementType := "" 10 - previousWasComment := false 11 - previousWasTopLevel := false 12 - previousWasScoped := false 13 - insideRawString := false 14 - 15 - for lineIndex, currentLine := range sourceLines { 16 - backtickCount := countRawStringDelimiters(currentLine) 17 - wasInsideRawString := insideRawString 18 - 19 - if backtickCount%2 == 1 { 20 - insideRawString = !insideRawString 21 - } 22 - 23 - if wasInsideRawString { 24 - resultLines = append(resultLines, currentLine) 25 - 26 - continue 27 - } 28 - 29 - lineNumber := lineIndex + 1 30 - trimmedLine := strings.TrimSpace(currentLine) 31 - 32 - if trimmedLine == "" { 33 - continue 34 - } 35 - 36 - isClosingBraceLine := isClosingBrace(currentLine) 37 - isOpeningBraceLine := isOpeningBrace(currentLine) 38 - isCaseLabelLine := isCaseLabel(currentLine) 39 - isCommentOnlyLine := isCommentOnly(currentLine) 40 - isPackageDeclaration := isPackageLine(trimmedLine) 41 - currentInformation := lineInformationMap[lineNumber] 42 - currentStatementType := "" 43 - 44 - if currentInformation != nil { 45 - currentStatementType = currentInformation.statementType 46 - } 47 - 48 - if isPackageDeclaration { 49 - currentStatementType = "package" 50 - } 51 - 52 - needsBlankLine := false 53 - currentIsTopLevel := currentInformation != nil && currentInformation.isTopLevel 54 - currentIsScoped := currentInformation != nil && currentInformation.isScoped 55 - 56 - if len(resultLines) > 0 && !previousWasOpenBrace && !isClosingBraceLine && !isCaseLabelLine { 57 - if currentIsTopLevel && previousWasTopLevel && currentStatementType != previousStatementType { 58 - if f.CommentMode == CommentsFollow && previousWasComment { 59 - } else { 60 - needsBlankLine = true 61 - } 62 - } else if currentInformation != nil && (currentIsScoped || previousWasScoped) { 63 - if f.CommentMode == CommentsFollow && previousWasComment { 64 - } else { 65 - needsBlankLine = true 66 - } 67 - } else if currentStatementType != "" && previousStatementType != "" && currentStatementType != previousStatementType { 68 - if f.CommentMode == CommentsFollow && previousWasComment { 69 - } else { 70 - needsBlankLine = true 71 - } 72 - } 73 - 74 - if f.CommentMode == CommentsFollow && isCommentOnlyLine && !previousWasComment { 75 - nextLineNumber := f.findNextNonCommentLine(sourceLines, lineIndex+1) 76 - 77 - if nextLineNumber > 0 { 78 - nextInformation := lineInformationMap[nextLineNumber] 79 - 80 - if nextInformation != nil { 81 - nextIsTopLevel := nextInformation.isTopLevel 82 - nextIsScoped := nextInformation.isScoped 83 - 84 - if nextIsTopLevel && previousWasTopLevel && nextInformation.statementType != previousStatementType { 85 - needsBlankLine = true 86 - } else if nextIsScoped || previousWasScoped { 87 - needsBlankLine = true 88 - } else if nextInformation.statementType != "" && previousStatementType != "" && nextInformation.statementType != previousStatementType { 89 - needsBlankLine = true 90 - } 91 - } 92 - } 93 - } 94 - } 95 - 96 - if needsBlankLine { 97 - resultLines = append(resultLines, "") 98 - } 99 - 100 - resultLines = append(resultLines, currentLine) 101 - previousWasOpenBrace = isOpeningBraceLine || isCaseLabelLine 102 - previousWasComment = isCommentOnlyLine 103 - 104 - if currentInformation != nil { 105 - previousStatementType = currentInformation.statementType 106 - previousWasTopLevel = currentInformation.isTopLevel 107 - previousWasScoped = currentInformation.isScoped 108 - } else if currentStatementType != "" { 109 - previousStatementType = currentStatementType 110 - previousWasTopLevel = false 111 - previousWasScoped = false 112 - } 113 - } 114 - 115 - outputString := strings.Join(resultLines, "\n") 116 - 117 - if !strings.HasSuffix(outputString, "\n") { 118 - outputString += "\n" 119 - } 120 - 121 - return []byte(outputString) 122 - } 123 - 124 - func (f *Formatter) findNextNonCommentLine(sourceLines []string, startLineIndex int) int { 125 - for lineIndex := startLineIndex; lineIndex < len(sourceLines); lineIndex++ { 126 - trimmedLine := strings.TrimSpace(sourceLines[lineIndex]) 127 - 128 - if trimmedLine == "" { 129 - continue 130 - } 131 - 132 - if isCommentOnly(sourceLines[lineIndex]) { 133 - continue 134 - } 135 - 136 - return lineIndex + 1 137 - } 138 - 139 - return 0 140 - }