aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-05 07:14:33 +0000
committerFuwn <[email protected]>2026-02-05 07:14:33 +0000
commit30fb266c13d32f838b1c5ee5be6036fc8eed4430 (patch)
treec4228b7f2a0902e5d0ab88e31946b12f85b68255
parentdocs(README): Update "How It Works" source (diff)
downloadiku-30fb266c13d32f838b1c5ee5be6036fc8eed4430.tar.xz
iku-30fb266c13d32f838b1c5ee5be6036fc8eed4430.zip
feat(engine): Add language-agnostic formatting engine with Go adapter
-rw-r--r--adapter_go.go89
-rw-r--r--engine/engine.go130
-rw-r--r--engine/engine_test.go200
-rw-r--r--engine/event.go30
-rw-r--r--parity_test.go308
5 files changed, 757 insertions, 0 deletions
diff --git a/adapter_go.go b/adapter_go.go
new file mode 100644
index 0000000..59096bb
--- /dev/null
+++ b/adapter_go.go
@@ -0,0 +1,89 @@
+package main
+
+import (
+ "github.com/Fuwn/iku/engine"
+ "go/format"
+ "go/parser"
+ "go/token"
+ "strings"
+)
+
+type GoAdapter struct{}
+
+func (a *GoAdapter) Analyze(source []byte) ([]byte, []engine.LineEvent, error) {
+ formattedSource, err := format.Source(source)
+
+ if err != nil {
+ return nil, nil, err
+ }
+
+ tokenFileSet := token.NewFileSet()
+ parsedFile, err := parser.ParseFile(tokenFileSet, "", formattedSource, parser.ParseComments)
+
+ if err != nil {
+ return nil, nil, err
+ }
+
+ formatter := &Formatter{}
+ lineInformationMap := formatter.buildLineInfo(tokenFileSet, parsedFile)
+ sourceLines := strings.Split(string(formattedSource), "\n")
+ events := make([]engine.LineEvent, len(sourceLines))
+ insideRawString := false
+
+ for lineIndex, currentLine := range sourceLines {
+ backtickCount := countRawStringDelimiters(currentLine)
+ wasInsideRawString := insideRawString
+
+ if backtickCount%2 == 1 {
+ insideRawString = !insideRawString
+ }
+
+ event := engine.NewLineEvent(currentLine)
+
+ if wasInsideRawString {
+ event.InRawString = true
+ events[lineIndex] = event
+
+ continue
+ }
+
+ if event.IsBlank {
+ events[lineIndex] = event
+
+ continue
+ }
+
+ lineNumber := lineIndex + 1
+ currentInformation := lineInformationMap[lineNumber]
+
+ if currentInformation != nil {
+ event.HasASTInfo = true
+ event.StatementType = currentInformation.statementType
+ event.IsTopLevel = currentInformation.isTopLevel
+ event.IsScoped = currentInformation.isScoped
+ event.IsStartLine = currentInformation.isStartLine
+ }
+
+ event.IsClosingBrace = isClosingBrace(currentLine)
+ event.IsOpeningBrace = isOpeningBrace(currentLine)
+ event.IsCaseLabel = isCaseLabel(currentLine)
+ event.IsCommentOnly = isCommentOnly(currentLine)
+ event.IsPackageDecl = isPackageLine(event.TrimmedContent)
+ events[lineIndex] = event
+ }
+
+ return formattedSource, events, nil
+}
+
+func MapCommentMode(mode CommentMode) engine.CommentMode {
+ switch mode {
+ case CommentsFollow:
+ return engine.CommentsFollow
+ case CommentsPrecede:
+ return engine.CommentsPrecede
+ case CommentsStandalone:
+ return engine.CommentsStandalone
+ default:
+ return engine.CommentsFollow
+ }
+}
diff --git a/engine/engine.go b/engine/engine.go
new file mode 100644
index 0000000..7a5399c
--- /dev/null
+++ b/engine/engine.go
@@ -0,0 +1,130 @@
+package engine
+
+import "strings"
+
+type CommentMode int
+
+const (
+ CommentsFollow CommentMode = iota
+ CommentsPrecede
+ CommentsStandalone
+)
+
+type Engine struct {
+ CommentMode CommentMode
+}
+
+func (e *Engine) Format(events []LineEvent) []string {
+ resultLines := make([]string, 0, len(events))
+ previousWasOpenBrace := false
+ previousStatementType := ""
+ previousWasComment := false
+ previousWasTopLevel := false
+ previousWasScoped := false
+
+ for eventIndex, event := range events {
+ if event.InRawString {
+ resultLines = append(resultLines, event.Content)
+
+ continue
+ }
+
+ if event.IsBlank {
+ continue
+ }
+
+ currentStatementType := event.StatementType
+
+ if event.IsPackageDecl {
+ currentStatementType = "package"
+ }
+
+ needsBlankLine := false
+ currentIsTopLevel := event.HasASTInfo && event.IsTopLevel
+ currentIsScoped := event.HasASTInfo && event.IsScoped
+
+ if len(resultLines) > 0 && !previousWasOpenBrace && !event.IsClosingBrace && !event.IsCaseLabel {
+ if currentIsTopLevel && previousWasTopLevel && currentStatementType != previousStatementType {
+ if !(e.CommentMode == CommentsFollow && previousWasComment) {
+ needsBlankLine = true
+ }
+ } else if event.HasASTInfo && (currentIsScoped || previousWasScoped) {
+ if !(e.CommentMode == CommentsFollow && previousWasComment) {
+ needsBlankLine = true
+ }
+ } else if currentStatementType != "" && previousStatementType != "" && currentStatementType != previousStatementType {
+ if !(e.CommentMode == CommentsFollow && previousWasComment) {
+ needsBlankLine = true
+ }
+ }
+
+ if e.CommentMode == CommentsFollow && event.IsCommentOnly && !previousWasComment {
+ nextIndex := e.findNextNonComment(events, eventIndex+1)
+
+ if nextIndex >= 0 {
+ next := events[nextIndex]
+
+ if next.HasASTInfo {
+ nextIsTopLevel := next.IsTopLevel
+ nextIsScoped := next.IsScoped
+
+ if nextIsTopLevel && previousWasTopLevel && next.StatementType != previousStatementType {
+ needsBlankLine = true
+ } else if nextIsScoped || previousWasScoped {
+ needsBlankLine = true
+ } else if next.StatementType != "" && previousStatementType != "" && next.StatementType != previousStatementType {
+ needsBlankLine = true
+ }
+ }
+ }
+ }
+ }
+
+ if needsBlankLine {
+ resultLines = append(resultLines, "")
+ }
+
+ resultLines = append(resultLines, event.Content)
+ previousWasOpenBrace = event.IsOpeningBrace || event.IsCaseLabel
+ previousWasComment = event.IsCommentOnly
+
+ if event.HasASTInfo {
+ previousStatementType = event.StatementType
+ previousWasTopLevel = event.IsTopLevel
+ previousWasScoped = event.IsScoped
+ } else if currentStatementType != "" {
+ previousStatementType = currentStatementType
+ previousWasTopLevel = false
+ previousWasScoped = false
+ }
+ }
+
+ return resultLines
+}
+
+func (e *Engine) FormatToString(events []LineEvent) string {
+ lines := e.Format(events)
+ output := strings.Join(lines, "\n")
+
+ if !strings.HasSuffix(output, "\n") {
+ output += "\n"
+ }
+
+ return output
+}
+
+func (e *Engine) findNextNonComment(events []LineEvent, startIndex int) int {
+ for eventIndex := startIndex; eventIndex < len(events); eventIndex++ {
+ if events[eventIndex].IsBlank {
+ continue
+ }
+
+ if events[eventIndex].IsCommentOnly {
+ continue
+ }
+
+ return eventIndex
+ }
+
+ return -1
+}
diff --git a/engine/engine_test.go b/engine/engine_test.go
new file mode 100644
index 0000000..f961277
--- /dev/null
+++ b/engine/engine_test.go
@@ -0,0 +1,200 @@
+package engine
+
+import (
+ "strings"
+ "testing"
+)
+
+func formatResult(formattingEngine *Engine, events []LineEvent) string {
+ return strings.Join(formattingEngine.Format(events), "\n")
+}
+
+func TestEngineCollapsesBlanks(t *testing.T) {
+ events := []LineEvent{
+ {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsStartLine: true},
+ {Content: "", TrimmedContent: "", IsBlank: true},
+ {Content: "", TrimmedContent: "", IsBlank: true},
+ {Content: "\ty := 2", TrimmedContent: "y := 2", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsStartLine: true},
+ }
+ formattingEngine := &Engine{CommentMode: CommentsFollow}
+ result := formatResult(formattingEngine, events)
+
+ if result != "\tx := 1\n\ty := 2" {
+ t.Errorf("expected blanks collapsed, got:\n%s", result)
+ }
+}
+
+func TestEngineScopeBoundary(t *testing.T) {
+ events := []LineEvent{
+ {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
+ {Content: "\tif x > 0 {", TrimmedContent: "if x > 0 {", HasASTInfo: true, StatementType: "*ast.IfStmt", IsScoped: true, IsStartLine: true, IsOpeningBrace: true},
+ {Content: "\t\ty := 2", TrimmedContent: "y := 2", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
+ {Content: "\t}", TrimmedContent: "}", IsClosingBrace: true, HasASTInfo: true, StatementType: "*ast.IfStmt", IsScoped: true},
+ {Content: "\tz := 3", TrimmedContent: "z := 3", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
+ }
+ formattingEngine := &Engine{CommentMode: CommentsFollow}
+ result := formatResult(formattingEngine, events)
+ expected := "\tx := 1\n\n\tif x > 0 {\n\t\ty := 2\n\t}\n\n\tz := 3"
+
+ if result != expected {
+ t.Errorf("expected scope boundaries, got:\n%s\nwant:\n%s", result, expected)
+ }
+}
+
+func TestEngineStatementTypeTransition(t *testing.T) {
+ events := []LineEvent{
+ {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
+ {Content: "\tvar a = 3", TrimmedContent: "var a = 3", HasASTInfo: true, StatementType: "var"},
+ }
+ formattingEngine := &Engine{CommentMode: CommentsFollow}
+ result := formatResult(formattingEngine, events)
+ expected := "\tx := 1\n\n\tvar a = 3"
+
+ if result != expected {
+ t.Errorf("expected blank between different types, got:\n%s\nwant:\n%s", result, expected)
+ }
+}
+
+func TestEngineSuppressAfterOpenBrace(t *testing.T) {
+ events := []LineEvent{
+ {Content: "func main() {", TrimmedContent: "func main() {", HasASTInfo: true, StatementType: "func", IsScoped: true, IsTopLevel: true, IsStartLine: true, IsOpeningBrace: true},
+ {Content: "\tif true {", TrimmedContent: "if true {", HasASTInfo: true, StatementType: "*ast.IfStmt", IsScoped: true, IsStartLine: true, IsOpeningBrace: true},
+ {Content: "\t\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
+ {Content: "\t}", TrimmedContent: "}", IsClosingBrace: true},
+ }
+ formattingEngine := &Engine{CommentMode: CommentsFollow}
+ result := formatResult(formattingEngine, events)
+ expected := "func main() {\n\tif true {\n\t\tx := 1\n\t}"
+
+ if result != expected {
+ t.Errorf("should not insert blank after open brace, got:\n%s\nwant:\n%s", result, expected)
+ }
+}
+
+func TestEngineSuppressBeforeCloseBrace(t *testing.T) {
+ events := []LineEvent{
+ {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsScoped: false},
+ {Content: "}", TrimmedContent: "}", IsClosingBrace: true},
+ }
+ formattingEngine := &Engine{CommentMode: CommentsFollow}
+ result := formatResult(formattingEngine, events)
+ expected := "\tx := 1\n}"
+
+ if result != expected {
+ t.Errorf("should not insert blank before close brace, got:\n%s\nwant:\n%s", result, expected)
+ }
+}
+
+func TestEngineSuppressCaseLabel(t *testing.T) {
+ events := []LineEvent{
+ {Content: "\tcase 1:", TrimmedContent: "case 1:", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsCaseLabel: true, IsOpeningBrace: false},
+ {Content: "\t\tfoo()", TrimmedContent: "foo()", HasASTInfo: true, StatementType: "*ast.ExprStmt"},
+ {Content: "\tcase 2:", TrimmedContent: "case 2:", HasASTInfo: true, StatementType: "*ast.AssignStmt", IsCaseLabel: true},
+ }
+ formattingEngine := &Engine{CommentMode: CommentsFollow}
+ result := formatResult(formattingEngine, events)
+ expected := "\tcase 1:\n\t\tfoo()\n\tcase 2:"
+
+ if result != expected {
+ t.Errorf("should not insert blank before case label, got:\n%s\nwant:\n%s", result, expected)
+ }
+}
+
+func TestEngineRawStringPassthrough(t *testing.T) {
+ events := []LineEvent{
+ {Content: "\tx := `", TrimmedContent: "x := `", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
+ {Content: "raw line 1", TrimmedContent: "raw line 1", InRawString: true},
+ {Content: "", TrimmedContent: "", InRawString: true},
+ {Content: "raw line 2`", TrimmedContent: "raw line 2`", InRawString: true},
+ {Content: "\ty := 1", TrimmedContent: "y := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
+ }
+ formattingEngine := &Engine{CommentMode: CommentsFollow}
+ result := formatResult(formattingEngine, events)
+ expected := "\tx := `\nraw line 1\n\nraw line 2`\n\ty := 1"
+
+ if result != expected {
+ t.Errorf("raw strings should pass through unchanged, got:\n%s\nwant:\n%s", result, expected)
+ }
+}
+
+func TestEngineTopLevelDifferentTypes(t *testing.T) {
+ events := []LineEvent{
+ {Content: "type Foo struct {", TrimmedContent: "type Foo struct {", HasASTInfo: true, StatementType: "type", IsTopLevel: true, IsScoped: true, IsStartLine: true, IsOpeningBrace: true},
+ {Content: "\tX int", TrimmedContent: "X int"},
+ {Content: "}", TrimmedContent: "}", IsClosingBrace: true, HasASTInfo: true, StatementType: "type", IsTopLevel: true, IsScoped: true},
+ {Content: "var x = 1", TrimmedContent: "var x = 1", HasASTInfo: true, StatementType: "var", IsTopLevel: true, IsStartLine: true},
+ }
+ formattingEngine := &Engine{CommentMode: CommentsFollow}
+ result := formatResult(formattingEngine, events)
+ expected := "type Foo struct {\n\tX int\n}\n\nvar x = 1"
+
+ if result != expected {
+ t.Errorf("expected blank between different top-level types, got:\n%s\nwant:\n%s", result, expected)
+ }
+}
+
+func TestEngineCommentLookAhead(t *testing.T) {
+ events := []LineEvent{
+ {Content: "\tx := 1", TrimmedContent: "x := 1", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
+ {Content: "\t// comment about if", TrimmedContent: "// comment about if", IsCommentOnly: true},
+ {Content: "\tif true {", TrimmedContent: "if true {", HasASTInfo: true, StatementType: "*ast.IfStmt", IsScoped: true, IsStartLine: true, IsOpeningBrace: true},
+ {Content: "\t\ty := 2", TrimmedContent: "y := 2", HasASTInfo: true, StatementType: "*ast.AssignStmt"},
+ {Content: "\t}", TrimmedContent: "}", IsClosingBrace: true},
+ }
+ formattingEngine := &Engine{CommentMode: CommentsFollow}
+ result := formatResult(formattingEngine, events)
+ expected := "\tx := 1\n\n\t// comment about if\n\tif true {\n\t\ty := 2\n\t}"
+
+ if result != expected {
+ t.Errorf("comment should trigger look-ahead blank, got:\n%s\nwant:\n%s", result, expected)
+ }
+}
+
+func TestEnginePackageDeclaration(t *testing.T) {
+ events := []LineEvent{
+ {Content: "package main", TrimmedContent: "package main", IsPackageDecl: true},
+ {Content: "", TrimmedContent: "", IsBlank: true},
+ {Content: "func main() {", TrimmedContent: "func main() {", HasASTInfo: true, StatementType: "func", IsTopLevel: true, IsScoped: true, IsStartLine: true, IsOpeningBrace: true},
+ {Content: "}", TrimmedContent: "}", IsClosingBrace: true, HasASTInfo: true, StatementType: "func", IsTopLevel: true, IsScoped: true},
+ }
+ formattingEngine := &Engine{CommentMode: CommentsFollow}
+ result := formatResult(formattingEngine, events)
+ expected := "package main\n\nfunc main() {\n}"
+
+ if result != expected {
+ t.Errorf("package should separate from func, got:\n%s\nwant:\n%s", result, expected)
+ }
+}
+
+func TestEngineFormatToString(t *testing.T) {
+ events := []LineEvent{
+ {Content: "package main", TrimmedContent: "package main", IsPackageDecl: true},
+ }
+ formattingEngine := &Engine{CommentMode: CommentsFollow}
+ result := formattingEngine.FormatToString(events)
+
+ if result != "package main\n" {
+ t.Errorf("FormatToString should end with newline, got: %q", result)
+ }
+}
+
+func TestEngineFindNextNonComment(t *testing.T) {
+ events := []LineEvent{
+ {Content: "x", TrimmedContent: "x"},
+ {Content: "", TrimmedContent: "", IsBlank: true},
+ {Content: "// comment", TrimmedContent: "// comment", IsCommentOnly: true},
+ {Content: "y", TrimmedContent: "y"},
+ }
+ formattingEngine := &Engine{}
+ index := formattingEngine.findNextNonComment(events, 1)
+
+ if index != 3 {
+ t.Errorf("expected index 3, got %d", index)
+ }
+
+ index = formattingEngine.findNextNonComment(events, 4)
+
+ if index != -1 {
+ t.Errorf("expected -1 when past end, got %d", index)
+ }
+}
diff --git a/engine/event.go b/engine/event.go
new file mode 100644
index 0000000..e9253e8
--- /dev/null
+++ b/engine/event.go
@@ -0,0 +1,30 @@
+package engine
+
+import "strings"
+
+type LineEvent struct {
+ Content string
+ TrimmedContent string
+ StatementType string
+ IsTopLevel bool
+ IsScoped bool
+ IsStartLine bool
+ HasASTInfo bool
+ IsClosingBrace bool
+ IsOpeningBrace bool
+ IsCaseLabel bool
+ IsCommentOnly bool
+ IsBlank bool
+ InRawString bool
+ IsPackageDecl bool
+}
+
+func NewLineEvent(content string) LineEvent {
+ trimmed := strings.TrimSpace(content)
+
+ return LineEvent{
+ Content: content,
+ TrimmedContent: trimmed,
+ IsBlank: trimmed == "",
+ }
+}
diff --git a/parity_test.go b/parity_test.go
new file mode 100644
index 0000000..af747f8
--- /dev/null
+++ b/parity_test.go
@@ -0,0 +1,308 @@
+package main
+
+import (
+ "github.com/Fuwn/iku/engine"
+ "testing"
+)
+
+type parityInput struct {
+ name string
+ source string
+}
+
+var parityInputs = []parityInput{
+ {
+ name: "extra blank lines collapsed",
+ source: `package main
+
+func main() {
+ x := 1
+
+
+ y := 2
+}
+`,
+ },
+ {
+ name: "scoped statements",
+ source: `package main
+
+func main() {
+ x := 1
+ if x > 0 {
+ y := 2
+ }
+ z := 3
+}
+`,
+ },
+ {
+ name: "nested scopes",
+ source: `package main
+
+func main() {
+ if true {
+ x := 1
+ if false {
+ y := 2
+ }
+ z := 3
+ }
+}
+`,
+ },
+ {
+ name: "for loop",
+ source: `package main
+
+func main() {
+ x := 1
+ for i := 0; i < 10; i++ {
+ y := i
+ }
+ z := 2
+}
+`,
+ },
+ {
+ name: "switch statement",
+ source: `package main
+
+func main() {
+ x := 1
+ switch x {
+ case 1:
+ y := 2
+ }
+ z := 3
+}
+`,
+ },
+ {
+ name: "multiple functions",
+ source: `package main
+
+func foo() {
+ x := 1
+}
+
+
+func bar() {
+ y := 2
+}
+`,
+ },
+ {
+ name: "type struct before var",
+ source: `package main
+
+type Foo struct {
+ X int
+}
+var x = 1
+`,
+ },
+ {
+ name: "different statement types",
+ source: `package main
+
+func main() {
+ x := 1
+ y := 2
+ var a = 3
+ defer cleanup()
+ defer cleanup2()
+ go worker()
+ return
+}
+`,
+ },
+ {
+ name: "consecutive ifs",
+ source: `package main
+
+func main() {
+ if err != nil {
+ return
+ }
+ if x > 0 {
+ y = 1
+ }
+}
+`,
+ },
+ {
+ name: "case clause with scoped statement",
+ source: `package main
+
+func main() {
+ switch x {
+ case 1:
+ foo()
+ if err != nil {
+ return
+ }
+ }
+}
+`,
+ },
+ {
+ name: "defer inline func",
+ source: `package main
+
+func main() {
+ defer func() { _ = file.Close() }()
+ fileInfo, err := file.Stat()
+}
+`,
+ },
+ {
+ name: "case clause assignments only",
+ source: `package main
+
+func main() {
+ switch x {
+ case "user":
+ roleStyle = UserStyle
+ contentStyle = ContentStyle
+ prefix = "You"
+ case "assistant":
+ roleStyle = AssistantStyle
+ }
+}
+`,
+ },
+ {
+ name: "raw string literal",
+ source: "package main\n\nvar x = `\nline 1\n\nline 2\n`\nvar y = 1\n",
+ },
+ {
+ name: "mixed top-level declarations",
+ source: `package main
+
+import "fmt"
+
+const x = 1
+
+var y = 2
+
+type Z struct{}
+
+func main() {
+ fmt.Println(x, y)
+}
+`,
+ },
+ {
+ name: "empty function body",
+ source: `package main
+
+func main() {
+}
+`,
+ },
+ {
+ name: "comment before scoped statement",
+ source: `package main
+
+func main() {
+ x := 1
+ // this is a comment
+ if x > 0 {
+ y := 2
+ }
+}
+`,
+ },
+ {
+ name: "multiple blank lines between functions",
+ source: `package main
+
+func a() {}
+
+
+
+func b() {}
+
+
+
+
+func c() {}
+`,
+ },
+ {
+ name: "select statement",
+ source: `package main
+
+func main() {
+ x := 1
+ select {
+ case <-ch:
+ y := 2
+ }
+ z := 3
+}
+`,
+ },
+ {
+ name: "range loop",
+ source: `package main
+
+func main() {
+ items := []int{1, 2, 3}
+ for _, item := range items {
+ _ = item
+ }
+ done := true
+}
+`,
+ },
+ {
+ name: "interface declaration",
+ source: `package main
+
+type Reader interface {
+ Read(p []byte) (n int, err error)
+}
+var x = 1
+`,
+ },
+}
+
+func TestEngineParityWithFormatter(t *testing.T) {
+ for _, commentMode := range []CommentMode{CommentsFollow, CommentsPrecede, CommentsStandalone} {
+ for _, input := range parityInputs {
+ name := input.name
+
+ switch commentMode {
+ case CommentsPrecede:
+ name += "/precede"
+ case CommentsStandalone:
+ name += "/standalone"
+ }
+
+ t.Run(name, func(t *testing.T) {
+ formatter := &Formatter{CommentMode: commentMode}
+ oldResult, err := formatter.Format([]byte(input.source))
+
+ if err != nil {
+ t.Fatalf("old formatter error: %v", err)
+ }
+
+ adapter := &GoAdapter{}
+ _, events, err := adapter.Analyze([]byte(input.source))
+
+ if err != nil {
+ t.Fatalf("adapter error: %v", err)
+ }
+
+ formattingEngine := &engine.Engine{CommentMode: MapCommentMode(commentMode)}
+ newResult := formattingEngine.FormatToString(events)
+
+ if string(oldResult) != newResult {
+ t.Errorf("parity mismatch\nold:\n%s\nnew:\n%s", oldResult, newResult)
+ }
+ })
+ }
+ }
+}