Skip to content

Commit a260e09

Browse files
committed
feat: Add Java support
--story=1
1 parent be407bd commit a260e09

File tree

15 files changed

+2139
-0
lines changed

15 files changed

+2139
-0
lines changed

.codei18n/mappings.json

Lines changed: 807 additions & 0 deletions
Large diffs are not rendered by default.

adapters/java/adapter.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package java
2+
3+
import (
4+
"context"
5+
"os"
6+
"strings"
7+
8+
sitter "github.com/smacker/go-tree-sitter"
9+
"github.com/smacker/go-tree-sitter/java"
10+
"github.com/studyzy/codei18n/core/domain"
11+
)
12+
13+
// Adapter 实现 Java 语言的 LanguageAdapter 接口
14+
// 使用 Tree-sitter 解析 Java 源代码并提取注释及其上下文
15+
type Adapter struct {
16+
parser *sitter.Parser
17+
}
18+
19+
// NewAdapter 创建一个新的 Java 适配器实例
20+
// 初始化 Tree-sitter 解析器并加载 Java 语法
21+
func NewAdapter() *Adapter {
22+
p := sitter.NewParser()
23+
p.SetLanguage(java.GetLanguage())
24+
return &Adapter{parser: p}
25+
}
26+
27+
// Language 返回语言标识符 ("java")
28+
func (a *Adapter) Language() string {
29+
return "java"
30+
}
31+
32+
// Parse 解析提供的 Java 源代码并提取注释
33+
// 返回包含注释文本、位置和上下文符号的 domain.Comment 列表
34+
// 如果 src 为 nil,则从文件路径读取源代码
35+
func (a *Adapter) Parse(file string, src []byte) ([]*domain.Comment, error) {
36+
if src == nil {
37+
data, err := os.ReadFile(file)
38+
if err != nil {
39+
return nil, err
40+
}
41+
src = data
42+
}
43+
44+
// Parse the source code using Tree-sitter
45+
tree, err := a.parser.ParseCtx(context.Background(), nil, src)
46+
if err != nil {
47+
return nil, err
48+
}
49+
defer tree.Close()
50+
51+
return a.extractComments(tree.RootNode(), src, file)
52+
}
53+
54+
// extractComments 从语法树中提取注释
55+
func (a *Adapter) extractComments(root *sitter.Node, src []byte, file string) ([]*domain.Comment, error) {
56+
// 首先提取包名
57+
packageName := extractPackageName(root, src)
58+
59+
// 创建查询
60+
q, err := sitter.NewQuery([]byte(javaCommentQuery), java.GetLanguage())
61+
if err != nil {
62+
return nil, err
63+
}
64+
defer q.Close()
65+
66+
qc := sitter.NewQueryCursor()
67+
defer qc.Close()
68+
69+
qc.Exec(q, root)
70+
71+
var comments []*domain.Comment
72+
73+
for {
74+
m, ok := qc.NextMatch()
75+
if !ok {
76+
break
77+
}
78+
79+
for _, c := range m.Captures {
80+
node := c.Node
81+
text := node.Content(src)
82+
83+
// 规范化文本并识别注释类型
84+
cType := domain.CommentTypeLine
85+
normalized := text
86+
87+
if strings.HasPrefix(text, "//") {
88+
cType = domain.CommentTypeLine
89+
normalized = strings.TrimPrefix(text, "//")
90+
} else if strings.HasPrefix(text, "/*") {
91+
if strings.HasPrefix(text, "/**") {
92+
cType = domain.CommentTypeDoc
93+
normalized = strings.TrimPrefix(text, "/**")
94+
} else {
95+
cType = domain.CommentTypeBlock
96+
normalized = strings.TrimPrefix(text, "/*")
97+
}
98+
normalized = strings.TrimSuffix(normalized, "*/")
99+
}
100+
normalized = strings.TrimSpace(normalized)
101+
102+
// 解析符号路径
103+
symbol := resolveSymbol(node, src, packageName)
104+
105+
comment := &domain.Comment{
106+
File: file,
107+
Language: "java",
108+
Symbol: symbol,
109+
Range: domain.TextRange{
110+
StartLine: int(node.StartPoint().Row) + 1,
111+
StartCol: int(node.StartPoint().Column) + 1,
112+
EndLine: int(node.EndPoint().Row) + 1,
113+
EndCol: int(node.EndPoint().Column) + 1,
114+
},
115+
SourceText: text,
116+
Type: cType,
117+
}
118+
119+
comments = append(comments, comment)
120+
}
121+
}
122+
123+
return comments, nil
124+
}

adapters/java/adapter_test.go

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
package java
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/studyzy/codei18n/core/domain"
8+
)
9+
10+
func TestAdapter_Parse_Class(t *testing.T) {
11+
src := `package com.example;
12+
13+
// 计算器类
14+
public class Calculator {
15+
}
16+
`
17+
adapter := NewAdapter()
18+
comments, err := adapter.Parse("test.java", []byte(src))
19+
assert.NoError(t, err)
20+
assert.Len(t, comments, 1)
21+
22+
assert.Equal(t, "// 计算器类", comments[0].SourceText)
23+
assert.Equal(t, "com.example.Calculator", comments[0].Symbol)
24+
assert.Equal(t, domain.CommentTypeLine, comments[0].Type)
25+
}
26+
27+
func TestAdapter_Parse_Method(t *testing.T) {
28+
src := `package com.example;
29+
30+
public class Calculator {
31+
// 计算两个数的和
32+
public int add(int a, int b) {
33+
return a + b;
34+
}
35+
}
36+
`
37+
adapter := NewAdapter()
38+
comments, err := adapter.Parse("test.java", []byte(src))
39+
assert.NoError(t, err)
40+
assert.Len(t, comments, 1)
41+
42+
assert.Equal(t, "// 计算两个数的和", comments[0].SourceText)
43+
assert.Equal(t, "com.example.Calculator#add", comments[0].Symbol)
44+
assert.Equal(t, domain.CommentTypeLine, comments[0].Type)
45+
}
46+
47+
func TestAdapter_Parse_Field(t *testing.T) {
48+
src := `package com.example;
49+
50+
public class Config {
51+
// 默认超时时间(毫秒)
52+
private int timeoutMs = 1000;
53+
}
54+
`
55+
adapter := NewAdapter()
56+
comments, err := adapter.Parse("test.java", []byte(src))
57+
assert.NoError(t, err)
58+
assert.Len(t, comments, 1)
59+
60+
assert.Equal(t, "// 默认超时时间(毫秒)", comments[0].SourceText)
61+
assert.Equal(t, "com.example.Config#timeoutMs", comments[0].Symbol)
62+
assert.Equal(t, domain.CommentTypeLine, comments[0].Type)
63+
}
64+
65+
func TestAdapter_Parse_Interface(t *testing.T) {
66+
src := `package com.example;
67+
68+
// 用户接口
69+
public interface User {
70+
String getName();
71+
}
72+
`
73+
adapter := NewAdapter()
74+
comments, err := adapter.Parse("test.java", []byte(src))
75+
assert.NoError(t, err)
76+
assert.Len(t, comments, 1)
77+
78+
assert.Equal(t, "// 用户接口", comments[0].SourceText)
79+
assert.Equal(t, "com.example.User", comments[0].Symbol)
80+
assert.Equal(t, domain.CommentTypeLine, comments[0].Type)
81+
}
82+
83+
func TestAdapter_Parse_Enum(t *testing.T) {
84+
src := `package com.example;
85+
86+
// 账户类型
87+
public enum AccountType {
88+
STANDARD,
89+
PREMIUM
90+
}
91+
`
92+
adapter := NewAdapter()
93+
comments, err := adapter.Parse("test.java", []byte(src))
94+
assert.NoError(t, err)
95+
assert.Len(t, comments, 1)
96+
97+
assert.Equal(t, "// 账户类型", comments[0].SourceText)
98+
assert.Equal(t, "com.example.AccountType", comments[0].Symbol)
99+
assert.Equal(t, domain.CommentTypeLine, comments[0].Type)
100+
}
101+
102+
func TestAdapter_Parse_BlockComment(t *testing.T) {
103+
src := `package com.example;
104+
105+
/* 这是一个块注释 */
106+
public class Test {
107+
}
108+
`
109+
adapter := NewAdapter()
110+
comments, err := adapter.Parse("test.java", []byte(src))
111+
assert.NoError(t, err)
112+
assert.Len(t, comments, 1)
113+
114+
assert.Equal(t, "/* 这是一个块注释 */", comments[0].SourceText)
115+
assert.Equal(t, "com.example.Test", comments[0].Symbol)
116+
assert.Equal(t, domain.CommentTypeBlock, comments[0].Type)
117+
}
118+
119+
func TestAdapter_Parse_Javadoc(t *testing.T) {
120+
src := `package com.example;
121+
122+
/**
123+
* 计算器类
124+
* 提供基本的数学运算
125+
*/
126+
public class Calculator {
127+
/**
128+
* 计算两个数的和
129+
* @param a 第一个数
130+
* @param b 第二个数
131+
* @return 两数之和
132+
*/
133+
public int add(int a, int b) {
134+
return a + b;
135+
}
136+
}
137+
`
138+
adapter := NewAdapter()
139+
comments, err := adapter.Parse("test.java", []byte(src))
140+
assert.NoError(t, err)
141+
assert.Len(t, comments, 2)
142+
143+
// 类的 Javadoc
144+
assert.Contains(t, comments[0].SourceText, "/**")
145+
assert.Equal(t, "com.example.Calculator", comments[0].Symbol)
146+
assert.Equal(t, domain.CommentTypeDoc, comments[0].Type)
147+
148+
// 方法的 Javadoc
149+
assert.Contains(t, comments[1].SourceText, "/**")
150+
assert.Equal(t, "com.example.Calculator#add", comments[1].Symbol)
151+
assert.Equal(t, domain.CommentTypeDoc, comments[1].Type)
152+
}
153+
154+
func TestAdapter_Parse_NoPackage(t *testing.T) {
155+
src := `
156+
// 简单类
157+
public class Simple {
158+
}
159+
`
160+
adapter := NewAdapter()
161+
comments, err := adapter.Parse("test.java", []byte(src))
162+
assert.NoError(t, err)
163+
assert.Len(t, comments, 1)
164+
165+
assert.Equal(t, "// 简单类", comments[0].SourceText)
166+
assert.Equal(t, "Simple", comments[0].Symbol)
167+
assert.Equal(t, domain.CommentTypeLine, comments[0].Type)
168+
}
169+
170+
func TestAdapter_Parse_FileLevel(t *testing.T) {
171+
src := `// Copyright (c) 2025 Example Corp
172+
// 本文件包含示例代码
173+
package com.example;
174+
175+
public class Test {
176+
}
177+
`
178+
adapter := NewAdapter()
179+
comments, err := adapter.Parse("test.java", []byte(src))
180+
assert.NoError(t, err)
181+
// 文件顶部的注释可能不会附加到 package 声明
182+
// 具体行为取决于 Tree-sitter 的解析
183+
assert.Greater(t, len(comments), 0)
184+
}
185+
186+
func TestAdapter_Parse_Range(t *testing.T) {
187+
src := `package com.example;
188+
189+
public class Test {
190+
// 测试方法
191+
public void test() {
192+
}
193+
}
194+
`
195+
adapter := NewAdapter()
196+
comments, err := adapter.Parse("test.java", []byte(src))
197+
assert.NoError(t, err)
198+
assert.Len(t, comments, 1)
199+
200+
// 验证位置信息
201+
comment := comments[0]
202+
assert.Greater(t, comment.Range.StartLine, 0)
203+
assert.Greater(t, comment.Range.StartCol, 0)
204+
assert.Greater(t, comment.Range.EndLine, 0)
205+
assert.Greater(t, comment.Range.EndCol, 0)
206+
assert.GreaterOrEqual(t, comment.Range.EndLine, comment.Range.StartLine)
207+
if comment.Range.EndLine == comment.Range.StartLine {
208+
assert.Greater(t, comment.Range.EndCol, comment.Range.StartCol)
209+
}
210+
}
211+
212+
func TestAdapter_Parse_SyntaxError(t *testing.T) {
213+
src := `package com.example;
214+
215+
public class Broken {
216+
// 未完成的方法定义
217+
public void doSomething( {
218+
}
219+
`
220+
adapter := NewAdapter()
221+
comments, err := adapter.Parse("test.java", []byte(src))
222+
// Tree-sitter 应该能够容错处理,至少返回注释
223+
assert.NoError(t, err)
224+
assert.Greater(t, len(comments), 0)
225+
226+
// 验证至少能提取到注释
227+
found := false
228+
for _, c := range comments {
229+
if c.SourceText == "// 未完成的方法定义" {
230+
found = true
231+
break
232+
}
233+
}
234+
assert.True(t, found, "应该能提取到注释,即使代码有语法错误")
235+
}

adapters/java/queries.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package java
2+
3+
// javaCommentQuery 定义用于提取 Java 注释的 Tree-sitter 查询
4+
// 匹配行注释 (//)、块注释 (/* */) 和 Javadoc (/** */)
5+
const javaCommentQuery = `
6+
(line_comment) @comment
7+
(block_comment) @comment
8+
`

0 commit comments

Comments
 (0)