Skip to content

Commit 7955c3d

Browse files
committed
fix: Rust comment merge
--bug=1
1 parent c24cb32 commit 7955c3d

File tree

7 files changed

+125
-20
lines changed

7 files changed

+125
-20
lines changed

adapters/rust/context.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ func FindOwnerNode(commentNode *sitter.Node, src []byte) *sitter.Node {
4444
curr = curr.NextNamedSibling()
4545
continue
4646
}
47+
// Skip other comments to find the actual item they are documenting
48+
if curr.Type() == "line_comment" || curr.Type() == "block_comment" {
49+
curr = curr.NextNamedSibling()
50+
continue
51+
}
4752
return curr
4853
}
4954
// Fallback to parent if no sibling found (orphaned doc comment)

adapters/rust/extractor.go

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ func (a *RustAdapter) extractComments(root *sitter.Node, src []byte, file string
3232
node := c.Node
3333
content := node.Content(src)
3434

35-
if isEmptyComment(content) {
36-
continue
37-
}
38-
3935
// Find Owner and Symbol Path
4036
owner := FindOwnerNode(node, src)
4137
symbolPath := ResolveSymbolPath(owner, src)
@@ -79,7 +75,17 @@ func (a *RustAdapter) extractComments(root *sitter.Node, src []byte, file string
7975
}
8076
}
8177

82-
return mergeComments(comments), nil
78+
merged := mergeComments(comments)
79+
80+
// Filter empty comments after merge
81+
var finalComments []*domain.Comment
82+
for _, c := range merged {
83+
if !isEmptyComment(c.SourceText) {
84+
finalComments = append(finalComments, c)
85+
}
86+
}
87+
88+
return finalComments, nil
8389
}
8490

8591
// mergeComments merges consecutive comments of the same type and symbol.
@@ -143,20 +149,39 @@ func getDomainCommentType(content string) domain.CommentType {
143149

144150
func isEmptyComment(content string) bool {
145151
trimmed := strings.TrimSpace(content)
146-
switch {
147-
case strings.HasPrefix(trimmed, "///"):
148-
return strings.TrimSpace(strings.TrimPrefix(trimmed, "///")) == ""
149-
case strings.HasPrefix(trimmed, "//!"):
150-
return strings.TrimSpace(strings.TrimPrefix(trimmed, "//!")) == ""
151-
case strings.HasPrefix(trimmed, "//"):
152-
return strings.TrimSpace(strings.TrimPrefix(trimmed, "//")) == ""
153-
case strings.HasPrefix(trimmed, "/*"):
152+
153+
// Block comments
154+
if strings.HasPrefix(trimmed, "/*") {
154155
inner := strings.TrimPrefix(trimmed, "/*")
155156
if strings.HasSuffix(inner, "*/") {
156157
inner = strings.TrimSuffix(inner, "*/")
157158
}
158159
return strings.TrimSpace(inner) == ""
159-
default:
160-
return strings.TrimSpace(trimmed) == ""
161160
}
161+
162+
// Line and Doc comments (check each line)
163+
lines := strings.Split(content, "\n")
164+
for _, line := range lines {
165+
t := strings.TrimSpace(line)
166+
if t == "" {
167+
continue
168+
}
169+
170+
if strings.HasPrefix(t, "///") {
171+
t = strings.TrimPrefix(t, "///")
172+
} else if strings.HasPrefix(t, "//!") {
173+
t = strings.TrimPrefix(t, "//!")
174+
} else if strings.HasPrefix(t, "//") {
175+
t = strings.TrimPrefix(t, "//")
176+
} else {
177+
// Not a standard line comment marker, assume content
178+
return false
179+
}
180+
181+
if strings.TrimSpace(t) != "" {
182+
return false
183+
}
184+
}
185+
186+
return true
162187
}

adapters/translator/llm.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ func (t *LLMTranslator) Translate(ctx context.Context, text, from, to string) (s
4242
"1. Keep technical terms, variable names, and code snippets unchanged.\n"+
4343
"2. Maintain the tone and style of the original comment.\n"+
4444
"3. Output ONLY the translated text, no explanations or quotes.\n"+
45-
"4. If the text is already in the target language, return it as is.\n\n"+
45+
"4. If the text is already in the target language, return it as is.\n"+
46+
"5. Preserve all line breaks and formatting.\n\n"+
4647
"Original: %s",
4748
from, to, text,
4849
)
@@ -139,7 +140,8 @@ func (t *LLMTranslator) buildBatchPrompt(texts []string, from, to string) string
139140
"2. The output must be a valid JSON string array [\"...\",\"...\"].\n"+
140141
"3. The number of elements MUST match the input.\n"+
141142
"4. Keep technical terms, variable names, and code snippets unchanged.\n"+
142-
"5. If a comment is already in the target language, return it as is.\n\n"+
143+
"5. If a comment is already in the target language, return it as is.\n"+
144+
"6. Preserve all line breaks and formatting.\n\n"+
143145
"Input:\n%s",
144146
from, to, string(inputJSON),
145147
)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package translator
2+
3+
import (
4+
"context"
5+
"os"
6+
"strings"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestTranslateNewlinePreservation(t *testing.T) {
14+
apiKey := os.Getenv("OPENAI_API_KEY")
15+
if apiKey == "" {
16+
t.Skip("Skipping integration test: OPENAI_API_KEY not set")
17+
}
18+
baseURL := os.Getenv("OPENAI_BASE_URL")
19+
model := os.Getenv("OPENAI_MODEL")
20+
if model == "" {
21+
model = "gpt-3.5-turbo"
22+
}
23+
24+
// Input with newlines
25+
input := "Line 1\nLine 2\nLine 3"
26+
27+
translator := NewLLMTranslator(apiKey, baseURL, model)
28+
29+
// 1. Test Single Translate
30+
t.Run("Single Translate", func(t *testing.T) {
31+
result, err := translator.Translate(context.Background(), input, "en", "zh-CN")
32+
require.NoError(t, err)
33+
34+
// Check if newlines are preserved
35+
assert.Contains(t, result, "\n", "Result should contain newlines")
36+
lines := strings.Split(result, "\n")
37+
// We expect roughly the same number of lines
38+
assert.GreaterOrEqual(t, len(lines), 3, "Result should have at least 3 lines")
39+
t.Logf("Single Translate Result:\n%s", result)
40+
})
41+
42+
// 2. Test Batch Translate
43+
t.Run("Batch Translate", func(t *testing.T) {
44+
results, err := translator.TranslateBatch(context.Background(), []string{input}, "en", "zh-CN")
45+
require.NoError(t, err)
46+
require.Len(t, results, 1)
47+
48+
// Check if newlines are preserved
49+
assert.Contains(t, results[0], "\n", "Result should contain newlines")
50+
lines := strings.Split(results[0], "\n")
51+
assert.GreaterOrEqual(t, len(lines), 3, "Result should have at least 3 lines")
52+
t.Logf("Batch Translate Result:\n%s", results[0])
53+
})
54+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/briandowns/spinner v1.23.2
77
github.com/fatih/color v1.18.0
88
github.com/sashabaranov/go-openai v1.41.2
9+
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82
910
github.com/spf13/cobra v1.10.2
1011
github.com/spf13/viper v1.21.0
1112
github.com/stretchr/testify v1.11.1
@@ -21,7 +22,6 @@ require (
2122
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
2223
github.com/pmezard/go-difflib v1.0.0 // indirect
2324
github.com/sagikazarmark/locafero v0.12.0 // indirect
24-
github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 // indirect
2525
github.com/spf13/afero v1.15.0 // indirect
2626
github.com/spf13/cast v1.10.0 // indirect
2727
github.com/spf13/pflag v1.0.10 // indirect

tests/rust_integration_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,15 @@ func TestRustSupport(t *testing.T) {
3939
assert.True(t, strings.HasSuffix(result.File, "lib.rs"))
4040
assert.NotEmpty(t, result.Comments)
4141

42-
// Expect 6 comments after merging consecutive lines:
42+
// Expect 7 comments after merging consecutive lines:
4343
// 1. Merged inner doc (lines 1-2)
4444
// 2. Inner doc (line 4)
4545
// 3. Outer doc (line 6)
4646
// 4. Line comment (line 8)
4747
// 5. Block comment (lines 9-10)
4848
// 6. Merged line comment (lines 11-12)
49-
assert.Equal(t, 6, len(result.Comments), "Should have exactly 6 comments after merging")
49+
// 7. Merged doc comment (lines 15-18)
50+
assert.Equal(t, 7, len(result.Comments), "Should have exactly 7 comments after merging")
5051

5152
// Verify Language and Types
5253
foundInnerDoc := false
@@ -92,4 +93,13 @@ func TestRustSupport(t *testing.T) {
9293
assert.Equal(t, 1, startLine, "First comment should start on line 1")
9394
assert.Equal(t, 2, endLine, "First comment (lines 1-2 merged) should end on line 2, not 3")
9495
}
96+
97+
// Verify the last merged doc comment (lines 15-18)
98+
if len(result.Comments) >= 7 {
99+
lastComment := result.Comments[6]
100+
assert.Equal(t, "PrecompileContract", lastComment["symbol"])
101+
src := lastComment["sourceText"].(string)
102+
assert.Contains(t, src, "A mapping of precompile contracts")
103+
assert.Contains(t, src, "dynamic representation")
104+
}
95105
}

tests/testdata/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,12 @@ fn main() {
1111
// good
1212
// luck
1313
}
14+
15+
/// A mapping of precompile contracts that can be either static (builtin) or dynamic.
16+
///
17+
/// This is an optimization that allows us to keep using the static precompiles
18+
/// until we need to modify them, at which point we convert to the dynamic representation.
19+
struct PrecompileContract {
20+
address: u64,
21+
name: String,
22+
}

0 commit comments

Comments
 (0)