aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-01-31 17:36:39 +0000
committerFuwn <[email protected]>2026-01-31 17:36:39 +0000
commitfa619e7b74519416430de4388a1b19d54e25bad0 (patch)
tree76800bf7ab9d6100f460849312d6b9092fed7acc
parentrefactor: Extract line pattern detection to patterns.go (diff)
downloadiku-fa619e7b74519416430de4388a1b19d54e25bad0.tar.xz
iku-fa619e7b74519416430de4388a1b19d54e25bad0.zip
refactor: Split formatter.go into separate files
-rw-r--r--formatter.go297
-rw-r--r--inspect.go164
-rw-r--r--rewrite.go140
3 files changed, 304 insertions, 297 deletions
diff --git a/formatter.go b/formatter.go
index 6cee9b6..7b05b70 100644
--- a/formatter.go
+++ b/formatter.go
@@ -1,12 +1,9 @@
package main
import (
- "go/ast"
"go/format"
"go/parser"
"go/token"
- "reflect"
- "strings"
)
type CommentMode int
@@ -46,297 +43,3 @@ func (f *Formatter) Format(source []byte) ([]byte, error) {
return f.rewrite(formattedSource, lineInformationMap), nil
}
-
-func isGeneralDeclarationScoped(generalDeclaration *ast.GenDecl) bool {
- for _, specification := range generalDeclaration.Specs {
- if typeSpecification, isTypeSpecification := specification.(*ast.TypeSpec); isTypeSpecification {
- switch typeSpecification.Type.(type) {
- case *ast.StructType, *ast.InterfaceType:
- return true
- }
- }
- }
-
- return false
-}
-
-func (f *Formatter) buildLineInfo(tokenFileSet *token.FileSet, parsedFile *ast.File) map[int]*lineInformation {
- lineInformationMap := make(map[int]*lineInformation)
- tokenFile := tokenFileSet.File(parsedFile.Pos())
-
- if tokenFile == nil {
- return lineInformationMap
- }
-
- for _, declaration := range parsedFile.Decls {
- startLine := tokenFile.Line(declaration.Pos())
- endLine := tokenFile.Line(declaration.End())
- statementType := ""
- isScoped := false
-
- switch typedDeclaration := declaration.(type) {
- case *ast.GenDecl:
- statementType = typedDeclaration.Tok.String()
- isScoped = isGeneralDeclarationScoped(typedDeclaration)
- case *ast.FuncDecl:
- statementType = "func"
- isScoped = true
- default:
- statementType = reflect.TypeOf(declaration).String()
- }
-
- lineInformationMap[startLine] = &lineInformation{statementType: statementType, isTopLevel: true, isScoped: isScoped, isStartLine: true}
- lineInformationMap[endLine] = &lineInformation{statementType: statementType, isTopLevel: true, isScoped: isScoped, isStartLine: false}
- }
-
- ast.Inspect(parsedFile, func(astNode ast.Node) bool {
- if astNode == nil {
- return true
- }
-
- switch typedNode := astNode.(type) {
- case *ast.BlockStmt:
- f.processBlock(tokenFile, typedNode, lineInformationMap)
- case *ast.CaseClause:
- f.processStatementList(tokenFile, typedNode.Body, lineInformationMap)
- case *ast.CommClause:
- f.processStatementList(tokenFile, typedNode.Body, lineInformationMap)
- }
-
- return true
- })
-
- return lineInformationMap
-}
-
-func (f *Formatter) processBlock(tokenFile *token.File, blockStatement *ast.BlockStmt, lineInformationMap map[int]*lineInformation) {
- if blockStatement == nil {
- return
- }
-
- f.processStatementList(tokenFile, blockStatement.List, lineInformationMap)
-}
-
-func (f *Formatter) processStatementList(tokenFile *token.File, statements []ast.Stmt, lineInformationMap map[int]*lineInformation) {
- for _, statement := range statements {
- startLine := tokenFile.Line(statement.Pos())
- endLine := tokenFile.Line(statement.End())
- statementType := ""
- isScoped := false
-
- switch typedStatement := statement.(type) {
- case *ast.DeclStmt:
- if generalDeclaration, isGeneralDeclaration := typedStatement.Decl.(*ast.GenDecl); isGeneralDeclaration {
- statementType = generalDeclaration.Tok.String()
- } else {
- statementType = reflect.TypeOf(statement).String()
- }
- case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt,
- *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.BlockStmt:
- statementType = reflect.TypeOf(statement).String()
- isScoped = true
- default:
- statementType = reflect.TypeOf(statement).String()
- }
-
- existingStart := lineInformationMap[startLine]
-
- if existingStart == nil || !existingStart.isStartLine {
- lineInformationMap[startLine] = &lineInformation{statementType: statementType, isTopLevel: false, isScoped: isScoped, isStartLine: true}
- }
-
- existingEnd := lineInformationMap[endLine]
-
- if existingEnd == nil || !existingEnd.isStartLine {
- lineInformationMap[endLine] = &lineInformation{statementType: statementType, isTopLevel: false, isScoped: isScoped, isStartLine: false}
- }
-
- switch typedStatement := statement.(type) {
- case *ast.IfStmt:
- f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
-
- if typedStatement.Else != nil {
- if elseBlock, isBlockStatement := typedStatement.Else.(*ast.BlockStmt); isBlockStatement {
- f.processBlock(tokenFile, elseBlock, lineInformationMap)
- } else if elseIfStatement, isIfStatement := typedStatement.Else.(*ast.IfStmt); isIfStatement {
- f.processIfStatement(tokenFile, elseIfStatement, lineInformationMap)
- }
- }
- case *ast.ForStmt:
- f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
- case *ast.RangeStmt:
- f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
- case *ast.SwitchStmt:
- f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
- case *ast.TypeSwitchStmt:
- f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
- case *ast.SelectStmt:
- f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
- case *ast.BlockStmt:
- f.processBlock(tokenFile, typedStatement, lineInformationMap)
- }
- }
-}
-
-func (f *Formatter) processIfStatement(tokenFile *token.File, ifStatement *ast.IfStmt, lineInformationMap map[int]*lineInformation) {
- startLine := tokenFile.Line(ifStatement.Pos())
- endLine := tokenFile.Line(ifStatement.End())
- existingStart := lineInformationMap[startLine]
-
- if existingStart == nil || !existingStart.isStartLine {
- lineInformationMap[startLine] = &lineInformation{statementType: "*ast.IfStmt", isTopLevel: false, isScoped: true, isStartLine: true}
- }
-
- existingEnd := lineInformationMap[endLine]
-
- if existingEnd == nil || !existingEnd.isStartLine {
- lineInformationMap[endLine] = &lineInformation{statementType: "*ast.IfStmt", isTopLevel: false, isScoped: true, isStartLine: false}
- }
-
- f.processBlock(tokenFile, ifStatement.Body, lineInformationMap)
-
- if ifStatement.Else != nil {
- if elseBlock, isBlockStatement := ifStatement.Else.(*ast.BlockStmt); isBlockStatement {
- f.processBlock(tokenFile, elseBlock, lineInformationMap)
- } else if elseIfStatement, isIfStatement := ifStatement.Else.(*ast.IfStmt); isIfStatement {
- f.processIfStatement(tokenFile, elseIfStatement, lineInformationMap)
- }
- }
-}
-
-func (f *Formatter) rewrite(formattedSource []byte, lineInformationMap map[int]*lineInformation) []byte {
- sourceLines := strings.Split(string(formattedSource), "\n")
- resultLines := make([]string, 0, len(sourceLines))
- previousWasOpenBrace := false
- previousStatementType := ""
- previousWasComment := false
- previousWasTopLevel := false
- previousWasScoped := false
- insideRawString := false
-
- for lineIndex, currentLine := range sourceLines {
- backtickCount := countRawStringDelimiters(currentLine)
- wasInsideRawString := insideRawString
-
- if backtickCount%2 == 1 {
- insideRawString = !insideRawString
- }
-
- if wasInsideRawString {
- resultLines = append(resultLines, currentLine)
-
- continue
- }
-
- lineNumber := lineIndex + 1
- trimmedLine := strings.TrimSpace(currentLine)
-
- if trimmedLine == "" {
- continue
- }
-
- isClosingBrace := closingBracePattern.MatchString(currentLine)
- isOpeningBrace := openingBracePattern.MatchString(currentLine)
- isCaseLabel := caseLabelPattern.MatchString(currentLine)
- isCommentOnlyLine := isCommentOnly(currentLine)
- isPackageDeclaration := isPackageLine(trimmedLine)
- currentInformation := lineInformationMap[lineNumber]
- currentStatementType := ""
-
- if currentInformation != nil {
- currentStatementType = currentInformation.statementType
- }
-
- if isPackageDeclaration {
- currentStatementType = "package"
- }
-
- needsBlankLine := false
- currentIsTopLevel := currentInformation != nil && currentInformation.isTopLevel
- currentIsScoped := currentInformation != nil && currentInformation.isScoped
-
- if len(resultLines) > 0 && !previousWasOpenBrace && !isClosingBrace && !isCaseLabel {
- if currentIsTopLevel && previousWasTopLevel && currentStatementType != previousStatementType {
- if f.CommentMode == CommentsFollow && previousWasComment {
- } else {
- needsBlankLine = true
- }
- } else if currentInformation != nil && (currentIsScoped || previousWasScoped) {
- if f.CommentMode == CommentsFollow && previousWasComment {
- } else {
- needsBlankLine = true
- }
- } else if currentStatementType != "" && previousStatementType != "" && currentStatementType != previousStatementType {
- if f.CommentMode == CommentsFollow && previousWasComment {
- } else {
- needsBlankLine = true
- }
- }
-
- if f.CommentMode == CommentsFollow && isCommentOnlyLine && !previousWasComment {
- nextLineNumber := f.findNextNonCommentLine(sourceLines, lineIndex+1)
-
- if nextLineNumber > 0 {
- nextInformation := lineInformationMap[nextLineNumber]
-
- if nextInformation != nil {
- nextIsTopLevel := nextInformation.isTopLevel
- nextIsScoped := nextInformation.isScoped
-
- if nextIsTopLevel && previousWasTopLevel && nextInformation.statementType != previousStatementType {
- needsBlankLine = true
- } else if nextIsScoped || previousWasScoped {
- needsBlankLine = true
- } else if nextInformation.statementType != "" && previousStatementType != "" && nextInformation.statementType != previousStatementType {
- needsBlankLine = true
- }
- }
- }
- }
- }
-
- if needsBlankLine {
- resultLines = append(resultLines, "")
- }
-
- resultLines = append(resultLines, currentLine)
- previousWasOpenBrace = isOpeningBrace || isCaseLabel
- previousWasComment = isCommentOnlyLine
-
- if currentInformation != nil {
- previousStatementType = currentInformation.statementType
- previousWasTopLevel = currentInformation.isTopLevel
- previousWasScoped = currentInformation.isScoped
- } else if currentStatementType != "" {
- previousStatementType = currentStatementType
- previousWasTopLevel = false
- previousWasScoped = false
- }
- }
-
- outputString := strings.Join(resultLines, "\n")
-
- if !strings.HasSuffix(outputString, "\n") {
- outputString += "\n"
- }
-
- return []byte(outputString)
-}
-
-func (f *Formatter) findNextNonCommentLine(sourceLines []string, startLineIndex int) int {
- for lineIndex := startLineIndex; lineIndex < len(sourceLines); lineIndex++ {
- trimmedLine := strings.TrimSpace(sourceLines[lineIndex])
-
- if trimmedLine == "" {
- continue
- }
-
- if isCommentOnly(sourceLines[lineIndex]) {
- continue
- }
-
- return lineIndex + 1
- }
-
- return 0
-}
diff --git a/inspect.go b/inspect.go
new file mode 100644
index 0000000..a2e39c9
--- /dev/null
+++ b/inspect.go
@@ -0,0 +1,164 @@
+package main
+
+import (
+ "go/ast"
+ "go/token"
+ "reflect"
+)
+
+func isGeneralDeclarationScoped(generalDeclaration *ast.GenDecl) bool {
+ for _, specification := range generalDeclaration.Specs {
+ if typeSpecification, isTypeSpecification := specification.(*ast.TypeSpec); isTypeSpecification {
+ switch typeSpecification.Type.(type) {
+ case *ast.StructType, *ast.InterfaceType:
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+func (f *Formatter) buildLineInfo(tokenFileSet *token.FileSet, parsedFile *ast.File) map[int]*lineInformation {
+ lineInformationMap := make(map[int]*lineInformation)
+ tokenFile := tokenFileSet.File(parsedFile.Pos())
+
+ if tokenFile == nil {
+ return lineInformationMap
+ }
+
+ for _, declaration := range parsedFile.Decls {
+ startLine := tokenFile.Line(declaration.Pos())
+ endLine := tokenFile.Line(declaration.End())
+ statementType := ""
+ isScoped := false
+
+ switch typedDeclaration := declaration.(type) {
+ case *ast.GenDecl:
+ statementType = typedDeclaration.Tok.String()
+ isScoped = isGeneralDeclarationScoped(typedDeclaration)
+ case *ast.FuncDecl:
+ statementType = "func"
+ isScoped = true
+ default:
+ statementType = reflect.TypeOf(declaration).String()
+ }
+
+ lineInformationMap[startLine] = &lineInformation{statementType: statementType, isTopLevel: true, isScoped: isScoped, isStartLine: true}
+ lineInformationMap[endLine] = &lineInformation{statementType: statementType, isTopLevel: true, isScoped: isScoped, isStartLine: false}
+ }
+
+ ast.Inspect(parsedFile, func(astNode ast.Node) bool {
+ if astNode == nil {
+ return true
+ }
+
+ switch typedNode := astNode.(type) {
+ case *ast.BlockStmt:
+ f.processBlock(tokenFile, typedNode, lineInformationMap)
+ case *ast.CaseClause:
+ f.processStatementList(tokenFile, typedNode.Body, lineInformationMap)
+ case *ast.CommClause:
+ f.processStatementList(tokenFile, typedNode.Body, lineInformationMap)
+ }
+
+ return true
+ })
+
+ return lineInformationMap
+}
+
+func (f *Formatter) processBlock(tokenFile *token.File, blockStatement *ast.BlockStmt, lineInformationMap map[int]*lineInformation) {
+ if blockStatement == nil {
+ return
+ }
+
+ f.processStatementList(tokenFile, blockStatement.List, lineInformationMap)
+}
+
+func (f *Formatter) processStatementList(tokenFile *token.File, statements []ast.Stmt, lineInformationMap map[int]*lineInformation) {
+ for _, statement := range statements {
+ startLine := tokenFile.Line(statement.Pos())
+ endLine := tokenFile.Line(statement.End())
+ statementType := ""
+ isScoped := false
+
+ switch typedStatement := statement.(type) {
+ case *ast.DeclStmt:
+ if generalDeclaration, isGeneralDeclaration := typedStatement.Decl.(*ast.GenDecl); isGeneralDeclaration {
+ statementType = generalDeclaration.Tok.String()
+ } else {
+ statementType = reflect.TypeOf(statement).String()
+ }
+ case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt,
+ *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.BlockStmt:
+ statementType = reflect.TypeOf(statement).String()
+ isScoped = true
+ default:
+ statementType = reflect.TypeOf(statement).String()
+ }
+
+ existingStart := lineInformationMap[startLine]
+
+ if existingStart == nil || !existingStart.isStartLine {
+ lineInformationMap[startLine] = &lineInformation{statementType: statementType, isTopLevel: false, isScoped: isScoped, isStartLine: true}
+ }
+
+ existingEnd := lineInformationMap[endLine]
+
+ if existingEnd == nil || !existingEnd.isStartLine {
+ lineInformationMap[endLine] = &lineInformation{statementType: statementType, isTopLevel: false, isScoped: isScoped, isStartLine: false}
+ }
+
+ switch typedStatement := statement.(type) {
+ case *ast.IfStmt:
+ f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
+
+ if typedStatement.Else != nil {
+ if elseBlock, isBlockStatement := typedStatement.Else.(*ast.BlockStmt); isBlockStatement {
+ f.processBlock(tokenFile, elseBlock, lineInformationMap)
+ } else if elseIfStatement, isIfStatement := typedStatement.Else.(*ast.IfStmt); isIfStatement {
+ f.processIfStatement(tokenFile, elseIfStatement, lineInformationMap)
+ }
+ }
+ case *ast.ForStmt:
+ f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
+ case *ast.RangeStmt:
+ f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
+ case *ast.SwitchStmt:
+ f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
+ case *ast.TypeSwitchStmt:
+ f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
+ case *ast.SelectStmt:
+ f.processBlock(tokenFile, typedStatement.Body, lineInformationMap)
+ case *ast.BlockStmt:
+ f.processBlock(tokenFile, typedStatement, lineInformationMap)
+ }
+ }
+}
+
+func (f *Formatter) processIfStatement(tokenFile *token.File, ifStatement *ast.IfStmt, lineInformationMap map[int]*lineInformation) {
+ startLine := tokenFile.Line(ifStatement.Pos())
+ endLine := tokenFile.Line(ifStatement.End())
+ existingStart := lineInformationMap[startLine]
+
+ if existingStart == nil || !existingStart.isStartLine {
+ lineInformationMap[startLine] = &lineInformation{statementType: "*ast.IfStmt", isTopLevel: false, isScoped: true, isStartLine: true}
+ }
+
+ existingEnd := lineInformationMap[endLine]
+
+ if existingEnd == nil || !existingEnd.isStartLine {
+ lineInformationMap[endLine] = &lineInformation{statementType: "*ast.IfStmt", isTopLevel: false, isScoped: true, isStartLine: false}
+ }
+
+ f.processBlock(tokenFile, ifStatement.Body, lineInformationMap)
+
+ if ifStatement.Else != nil {
+ if elseBlock, isBlockStatement := ifStatement.Else.(*ast.BlockStmt); isBlockStatement {
+ f.processBlock(tokenFile, elseBlock, lineInformationMap)
+ } else if elseIfStatement, isIfStatement := ifStatement.Else.(*ast.IfStmt); isIfStatement {
+ f.processIfStatement(tokenFile, elseIfStatement, lineInformationMap)
+ }
+ }
+}
diff --git a/rewrite.go b/rewrite.go
new file mode 100644
index 0000000..88b7642
--- /dev/null
+++ b/rewrite.go
@@ -0,0 +1,140 @@
+package main
+
+import "strings"
+
+func (f *Formatter) rewrite(formattedSource []byte, lineInformationMap map[int]*lineInformation) []byte {
+ sourceLines := strings.Split(string(formattedSource), "\n")
+ resultLines := make([]string, 0, len(sourceLines))
+ previousWasOpenBrace := false
+ previousStatementType := ""
+ previousWasComment := false
+ previousWasTopLevel := false
+ previousWasScoped := false
+ insideRawString := false
+
+ for lineIndex, currentLine := range sourceLines {
+ backtickCount := countRawStringDelimiters(currentLine)
+ wasInsideRawString := insideRawString
+
+ if backtickCount%2 == 1 {
+ insideRawString = !insideRawString
+ }
+
+ if wasInsideRawString {
+ resultLines = append(resultLines, currentLine)
+
+ continue
+ }
+
+ lineNumber := lineIndex + 1
+ trimmedLine := strings.TrimSpace(currentLine)
+
+ if trimmedLine == "" {
+ continue
+ }
+
+ isClosingBrace := closingBracePattern.MatchString(currentLine)
+ isOpeningBrace := openingBracePattern.MatchString(currentLine)
+ isCaseLabel := caseLabelPattern.MatchString(currentLine)
+ isCommentOnlyLine := isCommentOnly(currentLine)
+ isPackageDeclaration := isPackageLine(trimmedLine)
+ currentInformation := lineInformationMap[lineNumber]
+ currentStatementType := ""
+
+ if currentInformation != nil {
+ currentStatementType = currentInformation.statementType
+ }
+
+ if isPackageDeclaration {
+ currentStatementType = "package"
+ }
+
+ needsBlankLine := false
+ currentIsTopLevel := currentInformation != nil && currentInformation.isTopLevel
+ currentIsScoped := currentInformation != nil && currentInformation.isScoped
+
+ if len(resultLines) > 0 && !previousWasOpenBrace && !isClosingBrace && !isCaseLabel {
+ if currentIsTopLevel && previousWasTopLevel && currentStatementType != previousStatementType {
+ if f.CommentMode == CommentsFollow && previousWasComment {
+ } else {
+ needsBlankLine = true
+ }
+ } else if currentInformation != nil && (currentIsScoped || previousWasScoped) {
+ if f.CommentMode == CommentsFollow && previousWasComment {
+ } else {
+ needsBlankLine = true
+ }
+ } else if currentStatementType != "" && previousStatementType != "" && currentStatementType != previousStatementType {
+ if f.CommentMode == CommentsFollow && previousWasComment {
+ } else {
+ needsBlankLine = true
+ }
+ }
+
+ if f.CommentMode == CommentsFollow && isCommentOnlyLine && !previousWasComment {
+ nextLineNumber := f.findNextNonCommentLine(sourceLines, lineIndex+1)
+
+ if nextLineNumber > 0 {
+ nextInformation := lineInformationMap[nextLineNumber]
+
+ if nextInformation != nil {
+ nextIsTopLevel := nextInformation.isTopLevel
+ nextIsScoped := nextInformation.isScoped
+
+ if nextIsTopLevel && previousWasTopLevel && nextInformation.statementType != previousStatementType {
+ needsBlankLine = true
+ } else if nextIsScoped || previousWasScoped {
+ needsBlankLine = true
+ } else if nextInformation.statementType != "" && previousStatementType != "" && nextInformation.statementType != previousStatementType {
+ needsBlankLine = true
+ }
+ }
+ }
+ }
+ }
+
+ if needsBlankLine {
+ resultLines = append(resultLines, "")
+ }
+
+ resultLines = append(resultLines, currentLine)
+ previousWasOpenBrace = isOpeningBrace || isCaseLabel
+ previousWasComment = isCommentOnlyLine
+
+ if currentInformation != nil {
+ previousStatementType = currentInformation.statementType
+ previousWasTopLevel = currentInformation.isTopLevel
+ previousWasScoped = currentInformation.isScoped
+ } else if currentStatementType != "" {
+ previousStatementType = currentStatementType
+ previousWasTopLevel = false
+ previousWasScoped = false
+ }
+ }
+
+ outputString := strings.Join(resultLines, "\n")
+
+ if !strings.HasSuffix(outputString, "\n") {
+ outputString += "\n"
+ }
+
+ return []byte(outputString)
+}
+
+func (f *Formatter) findNextNonCommentLine(sourceLines []string, startLineIndex int) int {
+ for lineIndex := startLineIndex; lineIndex < len(sourceLines); lineIndex++ {
+ trimmedLine := strings.TrimSpace(sourceLines[lineIndex])
+
+ if trimmedLine == "" {
+ continue
+ }
+
+ if isCommentOnly(sourceLines[lineIndex]) {
+ continue
+ }
+
+ return lineIndex + 1
+ }
+
+ return 0
+}