rbtr-lang-java 2026.7.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rbtr_lang_java/__init__.py +1 -0
- rbtr_lang_java/java.scm +45 -0
- rbtr_lang_java/plugin.py +52 -0
- rbtr_lang_java/py.typed +0 -0
- rbtr_lang_java/tests/__init__.py +0 -0
- rbtr_lang_java/tests/__snapshots__/test_samples/test_edges_match_snapshot.json +4 -0
- rbtr_lang_java/tests/__snapshots__/test_samples/test_extraction_matches_snapshot.json +488 -0
- rbtr_lang_java/tests/cases_docstrings.py +113 -0
- rbtr_lang_java/tests/cases_extraction.py +215 -0
- rbtr_lang_java/tests/samples/java/com/example/format/Formatter.java +6 -0
- rbtr_lang_java/tests/samples/java/java.java +75 -0
- rbtr_lang_java/tests/test_docstrings.py +43 -0
- rbtr_lang_java/tests/test_extraction.py +57 -0
- rbtr_lang_java/tests/test_samples.py +73 -0
- rbtr_lang_java-2026.7.0.dev0.dist-info/METADATA +8 -0
- rbtr_lang_java-2026.7.0.dev0.dist-info/RECORD +18 -0
- rbtr_lang_java-2026.7.0.dev0.dist-info/WHEEL +4 -0
- rbtr_lang_java-2026.7.0.dev0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Java language plugin package."""
|
rbtr_lang_java/java.scm
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
; Top-level comments (Java: `//` line and `/* */`, `/** */` block).
|
|
2
|
+
[(line_comment) (block_comment)] @comment
|
|
3
|
+
|
|
4
|
+
(method_declaration
|
|
5
|
+
name: (identifier) @_method_name) @method
|
|
6
|
+
|
|
7
|
+
(constructor_declaration
|
|
8
|
+
name: (identifier) @_method_name) @method
|
|
9
|
+
|
|
10
|
+
(field_declaration
|
|
11
|
+
declarator: (variable_declarator
|
|
12
|
+
name: (identifier) @_var_name)) @variable
|
|
13
|
+
|
|
14
|
+
(class_declaration
|
|
15
|
+
name: (identifier) @_cls_name) @class
|
|
16
|
+
|
|
17
|
+
(interface_declaration
|
|
18
|
+
name: (identifier) @_cls_name) @class
|
|
19
|
+
|
|
20
|
+
(enum_declaration
|
|
21
|
+
name: (identifier) @_cls_name) @class
|
|
22
|
+
|
|
23
|
+
(enum_declaration
|
|
24
|
+
body: (enum_body
|
|
25
|
+
(enum_constant
|
|
26
|
+
name: (identifier) @_var_name) @variable))
|
|
27
|
+
|
|
28
|
+
(record_declaration
|
|
29
|
+
name: (identifier) @_cls_name) @class
|
|
30
|
+
|
|
31
|
+
(record_declaration
|
|
32
|
+
parameters: (formal_parameters
|
|
33
|
+
(formal_parameter
|
|
34
|
+
name: (identifier) @_var_name) @variable))
|
|
35
|
+
|
|
36
|
+
(annotation_type_declaration
|
|
37
|
+
name: (identifier) @_cls_name) @class
|
|
38
|
+
|
|
39
|
+
(annotation_type_declaration
|
|
40
|
+
body: (annotation_type_body
|
|
41
|
+
(annotation_type_element_declaration
|
|
42
|
+
name: (identifier) @_method_name) @method))
|
|
43
|
+
|
|
44
|
+
(import_declaration
|
|
45
|
+
(scoped_identifier) @_import_module) @import
|
rbtr_lang_java/plugin.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Java language plugin.
|
|
2
|
+
|
|
3
|
+
Provides class, method, and import extraction.
|
|
4
|
+
|
|
5
|
+
Extracted chunks::
|
|
6
|
+
|
|
7
|
+
class User {} → class "User", scope ""
|
|
8
|
+
class Service {
|
|
9
|
+
void process() {} → method "process", scope "Service"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
import java.util.HashMap
|
|
13
|
+
→ import, metadata {module: "java.util.HashMap"}
|
|
14
|
+
import static org.junit.Assert.assertEquals
|
|
15
|
+
→ import, metadata {module: "org.junit.Assert.assertEquals"}
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from rbtr.languages.registration import (
|
|
21
|
+
LanguageRegistration,
|
|
22
|
+
ModuleStyle,
|
|
23
|
+
QueryExtraction,
|
|
24
|
+
load_query,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# ── Query ────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ── Plugin ───────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
java = LanguageRegistration(
|
|
34
|
+
id="java",
|
|
35
|
+
extensions=frozenset({".java"}),
|
|
36
|
+
grammar_module="tree_sitter_java",
|
|
37
|
+
extraction=QueryExtraction(
|
|
38
|
+
query=load_query(__package__, "java"),
|
|
39
|
+
scope_types=frozenset(
|
|
40
|
+
{
|
|
41
|
+
"class_declaration",
|
|
42
|
+
"interface_declaration",
|
|
43
|
+
"enum_declaration",
|
|
44
|
+
"record_declaration",
|
|
45
|
+
"annotation_type_declaration",
|
|
46
|
+
}
|
|
47
|
+
),
|
|
48
|
+
),
|
|
49
|
+
source_roots=("", "src/main/java"),
|
|
50
|
+
module_style=ModuleStyle.DOTTED,
|
|
51
|
+
extraction_serial=4,
|
|
52
|
+
)
|
rbtr_lang_java/py.typed
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "fc9fe882b78bd247",
|
|
4
|
+
"blob_sha": "sha1",
|
|
5
|
+
"file_path": "com/example/format/Formatter.java",
|
|
6
|
+
"kind": "class",
|
|
7
|
+
"name": "Formatter",
|
|
8
|
+
"scope": "",
|
|
9
|
+
"language": "java",
|
|
10
|
+
"content": "/** Formats a greeting for a recipient. */\npublic interface Formatter {\n String format(String name);\n}",
|
|
11
|
+
"line_start": 3,
|
|
12
|
+
"line_end": 6,
|
|
13
|
+
"metadata": {
|
|
14
|
+
"module": "",
|
|
15
|
+
"names": "",
|
|
16
|
+
"dots": "",
|
|
17
|
+
"language_hint": ""
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "831f35e91399ffb5",
|
|
22
|
+
"blob_sha": "sha1",
|
|
23
|
+
"file_path": "com/example/format/Formatter.java",
|
|
24
|
+
"kind": "method",
|
|
25
|
+
"name": "format",
|
|
26
|
+
"scope": "Formatter",
|
|
27
|
+
"language": "java",
|
|
28
|
+
"content": "String format(String name);",
|
|
29
|
+
"line_start": 5,
|
|
30
|
+
"line_end": 5,
|
|
31
|
+
"metadata": {
|
|
32
|
+
"module": "",
|
|
33
|
+
"names": "",
|
|
34
|
+
"dots": "",
|
|
35
|
+
"language_hint": ""
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": "71ecb390e7b4b164",
|
|
40
|
+
"blob_sha": "sha1",
|
|
41
|
+
"file_path": "java.java",
|
|
42
|
+
"kind": "comment",
|
|
43
|
+
"name": "<anonymous>",
|
|
44
|
+
"scope": "",
|
|
45
|
+
"language": "java",
|
|
46
|
+
"content": "// Greeter — format greetings for named recipients.\n//\n// The Java plugin extracts classes, methods (including constructors),\n// fields (as variables), and imports. Interfaces, enums, and records are\n// captured as classes, and their members scope to them.",
|
|
47
|
+
"line_start": 1,
|
|
48
|
+
"line_end": 5,
|
|
49
|
+
"metadata": {
|
|
50
|
+
"module": "",
|
|
51
|
+
"names": "",
|
|
52
|
+
"dots": "",
|
|
53
|
+
"language_hint": ""
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "496c3cb5ac915731",
|
|
58
|
+
"blob_sha": "sha1",
|
|
59
|
+
"file_path": "java.java",
|
|
60
|
+
"kind": "import",
|
|
61
|
+
"name": "import java.util.List;",
|
|
62
|
+
"scope": "",
|
|
63
|
+
"language": "java",
|
|
64
|
+
"content": "import java.util.List;",
|
|
65
|
+
"line_start": 8,
|
|
66
|
+
"line_end": 8,
|
|
67
|
+
"metadata": {
|
|
68
|
+
"module": "java.util.List",
|
|
69
|
+
"names": "",
|
|
70
|
+
"dots": "",
|
|
71
|
+
"language_hint": ""
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "1417b33862ecea43",
|
|
76
|
+
"blob_sha": "sha1",
|
|
77
|
+
"file_path": "java.java",
|
|
78
|
+
"kind": "import",
|
|
79
|
+
"name": "import static java.util.Collections.emptyList;",
|
|
80
|
+
"scope": "",
|
|
81
|
+
"language": "java",
|
|
82
|
+
"content": "import static java.util.Collections.emptyList;",
|
|
83
|
+
"line_start": 9,
|
|
84
|
+
"line_end": 9,
|
|
85
|
+
"metadata": {
|
|
86
|
+
"module": "java.util.Collections.emptyList",
|
|
87
|
+
"names": "",
|
|
88
|
+
"dots": "",
|
|
89
|
+
"language_hint": ""
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": "d73548d67a52b30e",
|
|
94
|
+
"blob_sha": "sha1",
|
|
95
|
+
"file_path": "java.java",
|
|
96
|
+
"kind": "import",
|
|
97
|
+
"name": "import com.example.format.Formatter;",
|
|
98
|
+
"scope": "",
|
|
99
|
+
"language": "java",
|
|
100
|
+
"content": "import com.example.format.Formatter;",
|
|
101
|
+
"line_start": 11,
|
|
102
|
+
"line_end": 11,
|
|
103
|
+
"metadata": {
|
|
104
|
+
"module": "com.example.format.Formatter",
|
|
105
|
+
"names": "",
|
|
106
|
+
"dots": "",
|
|
107
|
+
"language_hint": ""
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "680cae9e7fb01702",
|
|
112
|
+
"blob_sha": "sha1",
|
|
113
|
+
"file_path": "java.java",
|
|
114
|
+
"kind": "comment",
|
|
115
|
+
"name": "<anonymous>",
|
|
116
|
+
"scope": "",
|
|
117
|
+
"language": "java",
|
|
118
|
+
"content": "// Standalone note at top level, separated by blank lines.\n// Second line of the same block.",
|
|
119
|
+
"line_start": 13,
|
|
120
|
+
"line_end": 14,
|
|
121
|
+
"metadata": {
|
|
122
|
+
"module": "",
|
|
123
|
+
"names": "",
|
|
124
|
+
"dots": "",
|
|
125
|
+
"language_hint": ""
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"id": "9f228cd99b8a722d",
|
|
130
|
+
"blob_sha": "sha1",
|
|
131
|
+
"file_path": "java.java",
|
|
132
|
+
"kind": "class",
|
|
133
|
+
"name": "Greeter",
|
|
134
|
+
"scope": "",
|
|
135
|
+
"language": "java",
|
|
136
|
+
"content": "/** Formats greetings with a configurable prefix. */\npublic class Greeter implements Formatter {\n private final String prefix;\n\n public Greeter(String prefix) {\n this.prefix = prefix;\n }\n\n /** Format a greeting (implements the Formatter contract). */\n @Override\n public String format(String name) {\n return greet(name);\n }\n\n /** Greet a single recipient. */\n public String greet(String name) {\n return prefix + \", \" + name;\n }\n\n /** Build a greeter with the default prefix. */\n public static Greeter withDefault() {\n return new Greeter(\"Hello\");\n }\n\n /** A nested formatter. */\n static class Formatter {\n String format(String name) {\n return name.trim();\n }\n }\n}",
|
|
137
|
+
"line_start": 16,
|
|
138
|
+
"line_end": 46,
|
|
139
|
+
"metadata": {
|
|
140
|
+
"module": "",
|
|
141
|
+
"names": "",
|
|
142
|
+
"dots": "",
|
|
143
|
+
"language_hint": ""
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"id": "bb5b1c7efa17f676",
|
|
148
|
+
"blob_sha": "sha1",
|
|
149
|
+
"file_path": "java.java",
|
|
150
|
+
"kind": "variable",
|
|
151
|
+
"name": "prefix",
|
|
152
|
+
"scope": "Greeter",
|
|
153
|
+
"language": "java",
|
|
154
|
+
"content": "private final String prefix;",
|
|
155
|
+
"line_start": 18,
|
|
156
|
+
"line_end": 18,
|
|
157
|
+
"metadata": {
|
|
158
|
+
"module": "",
|
|
159
|
+
"names": "",
|
|
160
|
+
"dots": "",
|
|
161
|
+
"language_hint": ""
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"id": "b175d0c46cc7da3a",
|
|
166
|
+
"blob_sha": "sha1",
|
|
167
|
+
"file_path": "java.java",
|
|
168
|
+
"kind": "method",
|
|
169
|
+
"name": "Greeter",
|
|
170
|
+
"scope": "Greeter",
|
|
171
|
+
"language": "java",
|
|
172
|
+
"content": "public Greeter(String prefix) {\n this.prefix = prefix;\n }",
|
|
173
|
+
"line_start": 20,
|
|
174
|
+
"line_end": 22,
|
|
175
|
+
"metadata": {
|
|
176
|
+
"module": "",
|
|
177
|
+
"names": "",
|
|
178
|
+
"dots": "",
|
|
179
|
+
"language_hint": ""
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": "4bc1972beed0cee5",
|
|
184
|
+
"blob_sha": "sha1",
|
|
185
|
+
"file_path": "java.java",
|
|
186
|
+
"kind": "method",
|
|
187
|
+
"name": "format",
|
|
188
|
+
"scope": "Greeter",
|
|
189
|
+
"language": "java",
|
|
190
|
+
"content": "/** Format a greeting (implements the Formatter contract). */\n @Override\n public String format(String name) {\n return greet(name);\n }",
|
|
191
|
+
"line_start": 24,
|
|
192
|
+
"line_end": 28,
|
|
193
|
+
"metadata": {
|
|
194
|
+
"module": "",
|
|
195
|
+
"names": "",
|
|
196
|
+
"dots": "",
|
|
197
|
+
"language_hint": ""
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"id": "d158fb2366c6f729",
|
|
202
|
+
"blob_sha": "sha1",
|
|
203
|
+
"file_path": "java.java",
|
|
204
|
+
"kind": "method",
|
|
205
|
+
"name": "greet",
|
|
206
|
+
"scope": "Greeter",
|
|
207
|
+
"language": "java",
|
|
208
|
+
"content": "/** Greet a single recipient. */\n public String greet(String name) {\n return prefix + \", \" + name;\n }",
|
|
209
|
+
"line_start": 30,
|
|
210
|
+
"line_end": 33,
|
|
211
|
+
"metadata": {
|
|
212
|
+
"module": "",
|
|
213
|
+
"names": "",
|
|
214
|
+
"dots": "",
|
|
215
|
+
"language_hint": ""
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"id": "a430bbda70683d09",
|
|
220
|
+
"blob_sha": "sha1",
|
|
221
|
+
"file_path": "java.java",
|
|
222
|
+
"kind": "method",
|
|
223
|
+
"name": "withDefault",
|
|
224
|
+
"scope": "Greeter",
|
|
225
|
+
"language": "java",
|
|
226
|
+
"content": "/** Build a greeter with the default prefix. */\n public static Greeter withDefault() {\n return new Greeter(\"Hello\");\n }",
|
|
227
|
+
"line_start": 35,
|
|
228
|
+
"line_end": 38,
|
|
229
|
+
"metadata": {
|
|
230
|
+
"module": "",
|
|
231
|
+
"names": "",
|
|
232
|
+
"dots": "",
|
|
233
|
+
"language_hint": ""
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"id": "aa7f19020a4a06cf",
|
|
238
|
+
"blob_sha": "sha1",
|
|
239
|
+
"file_path": "java.java",
|
|
240
|
+
"kind": "class",
|
|
241
|
+
"name": "Formatter",
|
|
242
|
+
"scope": "Greeter",
|
|
243
|
+
"language": "java",
|
|
244
|
+
"content": "/** A nested formatter. */\n static class Formatter {\n String format(String name) {\n return name.trim();\n }\n }",
|
|
245
|
+
"line_start": 40,
|
|
246
|
+
"line_end": 45,
|
|
247
|
+
"metadata": {
|
|
248
|
+
"module": "",
|
|
249
|
+
"names": "",
|
|
250
|
+
"dots": "",
|
|
251
|
+
"language_hint": ""
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"id": "1345e44e8cd717a6",
|
|
256
|
+
"blob_sha": "sha1",
|
|
257
|
+
"file_path": "java.java",
|
|
258
|
+
"kind": "method",
|
|
259
|
+
"name": "format",
|
|
260
|
+
"scope": "Greeter::Formatter",
|
|
261
|
+
"language": "java",
|
|
262
|
+
"content": "String format(String name) {\n return name.trim();\n }",
|
|
263
|
+
"line_start": 42,
|
|
264
|
+
"line_end": 44,
|
|
265
|
+
"metadata": {
|
|
266
|
+
"module": "",
|
|
267
|
+
"names": "",
|
|
268
|
+
"dots": "",
|
|
269
|
+
"language_hint": ""
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
"id": "877bfd5e977208c1",
|
|
274
|
+
"blob_sha": "sha1",
|
|
275
|
+
"file_path": "java.java",
|
|
276
|
+
"kind": "class",
|
|
277
|
+
"name": "Strategy",
|
|
278
|
+
"scope": "",
|
|
279
|
+
"language": "java",
|
|
280
|
+
"content": "/** A greeting strategy contract. */\ninterface Strategy {\n String render(String name);\n}",
|
|
281
|
+
"line_start": 48,
|
|
282
|
+
"line_end": 51,
|
|
283
|
+
"metadata": {
|
|
284
|
+
"module": "",
|
|
285
|
+
"names": "",
|
|
286
|
+
"dots": "",
|
|
287
|
+
"language_hint": ""
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"id": "0b7ec1f5873ad261",
|
|
292
|
+
"blob_sha": "sha1",
|
|
293
|
+
"file_path": "java.java",
|
|
294
|
+
"kind": "method",
|
|
295
|
+
"name": "render",
|
|
296
|
+
"scope": "Strategy",
|
|
297
|
+
"language": "java",
|
|
298
|
+
"content": "String render(String name);",
|
|
299
|
+
"line_start": 50,
|
|
300
|
+
"line_end": 50,
|
|
301
|
+
"metadata": {
|
|
302
|
+
"module": "",
|
|
303
|
+
"names": "",
|
|
304
|
+
"dots": "",
|
|
305
|
+
"language_hint": ""
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
"id": "9fc1d873760c9177",
|
|
310
|
+
"blob_sha": "sha1",
|
|
311
|
+
"file_path": "java.java",
|
|
312
|
+
"kind": "class",
|
|
313
|
+
"name": "Tone",
|
|
314
|
+
"scope": "",
|
|
315
|
+
"language": "java",
|
|
316
|
+
"content": "/** Supported greeting tones. */\nenum Tone {\n FORMAL,\n CASUAL;\n\n /** Human-readable label for this tone. */\n String label() {\n return name().toLowerCase();\n }\n}",
|
|
317
|
+
"line_start": 53,
|
|
318
|
+
"line_end": 62,
|
|
319
|
+
"metadata": {
|
|
320
|
+
"module": "",
|
|
321
|
+
"names": "",
|
|
322
|
+
"dots": "",
|
|
323
|
+
"language_hint": ""
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"id": "be3519be22988025",
|
|
328
|
+
"blob_sha": "sha1",
|
|
329
|
+
"file_path": "java.java",
|
|
330
|
+
"kind": "variable",
|
|
331
|
+
"name": "FORMAL",
|
|
332
|
+
"scope": "Tone",
|
|
333
|
+
"language": "java",
|
|
334
|
+
"content": "FORMAL",
|
|
335
|
+
"line_start": 55,
|
|
336
|
+
"line_end": 55,
|
|
337
|
+
"metadata": {
|
|
338
|
+
"module": "",
|
|
339
|
+
"names": "",
|
|
340
|
+
"dots": "",
|
|
341
|
+
"language_hint": ""
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"id": "1a5686337ef56a40",
|
|
346
|
+
"blob_sha": "sha1",
|
|
347
|
+
"file_path": "java.java",
|
|
348
|
+
"kind": "variable",
|
|
349
|
+
"name": "CASUAL",
|
|
350
|
+
"scope": "Tone",
|
|
351
|
+
"language": "java",
|
|
352
|
+
"content": "CASUAL",
|
|
353
|
+
"line_start": 56,
|
|
354
|
+
"line_end": 56,
|
|
355
|
+
"metadata": {
|
|
356
|
+
"module": "",
|
|
357
|
+
"names": "",
|
|
358
|
+
"dots": "",
|
|
359
|
+
"language_hint": ""
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
"id": "9d6647628805f6cd",
|
|
364
|
+
"blob_sha": "sha1",
|
|
365
|
+
"file_path": "java.java",
|
|
366
|
+
"kind": "method",
|
|
367
|
+
"name": "label",
|
|
368
|
+
"scope": "Tone",
|
|
369
|
+
"language": "java",
|
|
370
|
+
"content": "/** Human-readable label for this tone. */\n String label() {\n return name().toLowerCase();\n }",
|
|
371
|
+
"line_start": 58,
|
|
372
|
+
"line_end": 61,
|
|
373
|
+
"metadata": {
|
|
374
|
+
"module": "",
|
|
375
|
+
"names": "",
|
|
376
|
+
"dots": "",
|
|
377
|
+
"language_hint": ""
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
"id": "7f673931d7852ccf",
|
|
382
|
+
"blob_sha": "sha1",
|
|
383
|
+
"file_path": "java.java",
|
|
384
|
+
"kind": "class",
|
|
385
|
+
"name": "Recipient",
|
|
386
|
+
"scope": "",
|
|
387
|
+
"language": "java",
|
|
388
|
+
"content": "/** An immutable named recipient. */\nrecord Recipient(String name, String locale) {\n /** Full display name including locale. */\n String display() {\n return name + \" (\" + locale + \")\";\n }\n}",
|
|
389
|
+
"line_start": 64,
|
|
390
|
+
"line_end": 70,
|
|
391
|
+
"metadata": {
|
|
392
|
+
"module": "",
|
|
393
|
+
"names": "",
|
|
394
|
+
"dots": "",
|
|
395
|
+
"language_hint": ""
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
"id": "47fb3098b5c5bbe0",
|
|
400
|
+
"blob_sha": "sha1",
|
|
401
|
+
"file_path": "java.java",
|
|
402
|
+
"kind": "variable",
|
|
403
|
+
"name": "name",
|
|
404
|
+
"scope": "Recipient",
|
|
405
|
+
"language": "java",
|
|
406
|
+
"content": "String name",
|
|
407
|
+
"line_start": 65,
|
|
408
|
+
"line_end": 65,
|
|
409
|
+
"metadata": {
|
|
410
|
+
"module": "",
|
|
411
|
+
"names": "",
|
|
412
|
+
"dots": "",
|
|
413
|
+
"language_hint": ""
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
"id": "e1c6599ad1ee4d57",
|
|
418
|
+
"blob_sha": "sha1",
|
|
419
|
+
"file_path": "java.java",
|
|
420
|
+
"kind": "variable",
|
|
421
|
+
"name": "locale",
|
|
422
|
+
"scope": "Recipient",
|
|
423
|
+
"language": "java",
|
|
424
|
+
"content": "String locale",
|
|
425
|
+
"line_start": 65,
|
|
426
|
+
"line_end": 65,
|
|
427
|
+
"metadata": {
|
|
428
|
+
"module": "",
|
|
429
|
+
"names": "",
|
|
430
|
+
"dots": "",
|
|
431
|
+
"language_hint": ""
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
"id": "e2ebf0a0194fd190",
|
|
436
|
+
"blob_sha": "sha1",
|
|
437
|
+
"file_path": "java.java",
|
|
438
|
+
"kind": "method",
|
|
439
|
+
"name": "display",
|
|
440
|
+
"scope": "Recipient",
|
|
441
|
+
"language": "java",
|
|
442
|
+
"content": "/** Full display name including locale. */\n String display() {\n return name + \" (\" + locale + \")\";\n }",
|
|
443
|
+
"line_start": 66,
|
|
444
|
+
"line_end": 69,
|
|
445
|
+
"metadata": {
|
|
446
|
+
"module": "",
|
|
447
|
+
"names": "",
|
|
448
|
+
"dots": "",
|
|
449
|
+
"language_hint": ""
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
"id": "3f07497ccd429eea",
|
|
454
|
+
"blob_sha": "sha1",
|
|
455
|
+
"file_path": "java.java",
|
|
456
|
+
"kind": "class",
|
|
457
|
+
"name": "Audited",
|
|
458
|
+
"scope": "",
|
|
459
|
+
"language": "java",
|
|
460
|
+
"content": "/** Marks a greeter method as audited. */\n@interface Audited {\n String value();\n}",
|
|
461
|
+
"line_start": 72,
|
|
462
|
+
"line_end": 75,
|
|
463
|
+
"metadata": {
|
|
464
|
+
"module": "",
|
|
465
|
+
"names": "",
|
|
466
|
+
"dots": "",
|
|
467
|
+
"language_hint": ""
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
"id": "8f7a02443382b322",
|
|
472
|
+
"blob_sha": "sha1",
|
|
473
|
+
"file_path": "java.java",
|
|
474
|
+
"kind": "method",
|
|
475
|
+
"name": "value",
|
|
476
|
+
"scope": "Audited",
|
|
477
|
+
"language": "java",
|
|
478
|
+
"content": "String value();",
|
|
479
|
+
"line_start": 74,
|
|
480
|
+
"line_end": 74,
|
|
481
|
+
"metadata": {
|
|
482
|
+
"module": "",
|
|
483
|
+
"names": "",
|
|
484
|
+
"dots": "",
|
|
485
|
+
"language_hint": ""
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Java docstring-extraction test cases.
|
|
2
|
+
|
|
3
|
+
Each `@case` returns `(lang, source, symbol_name, snippet)` consumed by `test_docstrings.py`.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pytest_cases import case
|
|
9
|
+
|
|
10
|
+
type DocstringCase = tuple[str, str, str, str]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@case(tags=["documented", "canonical", "exterior_doc"])
|
|
14
|
+
def case_java_javadoc_on_class() -> DocstringCase:
|
|
15
|
+
"""Canonical Javadoc above a class declaration."""
|
|
16
|
+
src = """\
|
|
17
|
+
/** A widget. */
|
|
18
|
+
public class Widget {}
|
|
19
|
+
"""
|
|
20
|
+
return "java", src, "Widget", "A widget"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@case(tags=["documented", "canonical", "exterior_doc"])
|
|
24
|
+
def case_java_javadoc_on_method() -> DocstringCase:
|
|
25
|
+
"""Canonical Javadoc above a method declaration."""
|
|
26
|
+
src = """\
|
|
27
|
+
public class Widget {
|
|
28
|
+
/** Render the widget. */
|
|
29
|
+
public void render() {}
|
|
30
|
+
}
|
|
31
|
+
"""
|
|
32
|
+
return "java", src, "render", "Render the widget"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@case(tags=["documented", "canonical", "exterior_doc"])
|
|
36
|
+
def case_java_multi_line_javadoc_with_tags() -> DocstringCase:
|
|
37
|
+
"""Multi-line Javadoc with `@param` / `@return`."""
|
|
38
|
+
src = """\
|
|
39
|
+
public class Widget {
|
|
40
|
+
/**
|
|
41
|
+
* Compute a hash.
|
|
42
|
+
* @param data input
|
|
43
|
+
* @return hex string
|
|
44
|
+
*/
|
|
45
|
+
public String hash(byte[] data) { return ""; }
|
|
46
|
+
}
|
|
47
|
+
"""
|
|
48
|
+
return "java", src, "hash", "@return hex string"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@case(tags=["documented", "edge_case", "exterior_doc"])
|
|
52
|
+
def case_java_javadoc_above_annotated_method() -> DocstringCase:
|
|
53
|
+
"""Annotations parse as part of the method node's
|
|
54
|
+
`modifiers` child, so the Javadoc remains the method's
|
|
55
|
+
`prev_named_sibling` and attaches correctly.
|
|
56
|
+
"""
|
|
57
|
+
src = """\
|
|
58
|
+
public class Widget {
|
|
59
|
+
/** Deprecated API. */
|
|
60
|
+
@Deprecated
|
|
61
|
+
public void old() {}
|
|
62
|
+
}
|
|
63
|
+
"""
|
|
64
|
+
return "java", src, "old", "Deprecated API"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@case(tags=["documented", "unconventional", "exterior_doc"])
|
|
68
|
+
def case_java_line_comment_run_on_method() -> DocstringCase:
|
|
69
|
+
"""Plain `//` comment run attaches too."""
|
|
70
|
+
src = """\
|
|
71
|
+
public class C {
|
|
72
|
+
// A line comment.
|
|
73
|
+
// Second line.
|
|
74
|
+
public void m() {}
|
|
75
|
+
}
|
|
76
|
+
"""
|
|
77
|
+
return "java", src, "m", "A line comment"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@case(tags=["undocumented", "no_docs"])
|
|
81
|
+
def case_java_method_without_doc() -> DocstringCase:
|
|
82
|
+
"""Undocumented method."""
|
|
83
|
+
src = """\
|
|
84
|
+
public class C { public void bare() {} }
|
|
85
|
+
"""
|
|
86
|
+
return "java", src, "bare", "PHANTOM_DOC_TEXT_SHOULD_NEVER_APPEAR"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@case(tags=["undocumented", "boundary_not_attached"])
|
|
90
|
+
def case_java_javadoc_detached_by_blank_line() -> DocstringCase:
|
|
91
|
+
"""Blank line between Javadoc and method breaks attachment."""
|
|
92
|
+
src = """\
|
|
93
|
+
public class C {
|
|
94
|
+
/** Orphan. */
|
|
95
|
+
|
|
96
|
+
public void later() {}
|
|
97
|
+
}
|
|
98
|
+
"""
|
|
99
|
+
return "java", src, "later", "Orphan"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@case(tags=["undocumented", "invalid"])
|
|
103
|
+
def case_java_javadoc_on_previous_method_does_not_attach_to_later() -> DocstringCase:
|
|
104
|
+
"""A Javadoc above method A is not attached to later method B."""
|
|
105
|
+
src = """\
|
|
106
|
+
public class C {
|
|
107
|
+
/** Doc for first. */
|
|
108
|
+
public void first() {}
|
|
109
|
+
|
|
110
|
+
public void second() {}
|
|
111
|
+
}
|
|
112
|
+
"""
|
|
113
|
+
return "java", src, "second", "Doc for first"
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Java extraction test cases."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from pytest_cases import case
|
|
7
|
+
|
|
8
|
+
type SymbolCase = tuple[str, str, list[tuple[str, str, str]]]
|
|
9
|
+
type ImportCase = tuple[str, str, dict[str, str]]
|
|
10
|
+
type MultiImportCase = tuple[str, str, int, list[dict[str, str]]]
|
|
11
|
+
type MixedCase = tuple[str, str, set[str], list[tuple[str, str]]]
|
|
12
|
+
|
|
13
|
+
_xfail_nested = pytest.mark.xfail(
|
|
14
|
+
reason="nested/chained destructuring unsupported — no query-only recursion",
|
|
15
|
+
strict=True,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@case(tags=["symbol"])
|
|
20
|
+
def case_java_class() -> SymbolCase:
|
|
21
|
+
"""class User {}."""
|
|
22
|
+
return "java", "class User {}\n", [("class", "User", "")]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@case(tags=["symbol"])
|
|
26
|
+
def case_java_public_class() -> SymbolCase:
|
|
27
|
+
"""public class App {}."""
|
|
28
|
+
return "java", "public class App {}\n", [("class", "App", "")]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@case(tags=["symbol"])
|
|
32
|
+
def case_java_class_extends() -> SymbolCase:
|
|
33
|
+
"""class Admin extends User {}."""
|
|
34
|
+
return "java", "class Admin extends User {}\n", [("class", "Admin", "")]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@case(tags=["symbol"])
|
|
38
|
+
def case_java_class_implements() -> SymbolCase:
|
|
39
|
+
"""class UserService implements Service {}."""
|
|
40
|
+
return "java", "class UserService implements Service {}\n", [("class", "UserService", "")]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@case(tags=["symbol"])
|
|
44
|
+
def case_java_multiple_classes() -> SymbolCase:
|
|
45
|
+
"""Multiple classes."""
|
|
46
|
+
src = """\
|
|
47
|
+
class Foo {}
|
|
48
|
+
class Bar {}
|
|
49
|
+
"""
|
|
50
|
+
return "java", src, [("class", "Foo", ""), ("class", "Bar", "")]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@case(tags=["symbol"])
|
|
54
|
+
def case_java_method_in_class() -> SymbolCase:
|
|
55
|
+
"""Method scoped to class."""
|
|
56
|
+
src = """\
|
|
57
|
+
class Service {
|
|
58
|
+
void process() {}
|
|
59
|
+
}
|
|
60
|
+
"""
|
|
61
|
+
return "java", src, [("method", "process", "Service")]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@case(tags=["symbol"])
|
|
65
|
+
def case_java_multiple_methods() -> SymbolCase:
|
|
66
|
+
"""Multiple methods."""
|
|
67
|
+
src = """\
|
|
68
|
+
class Svc {
|
|
69
|
+
void start() {}
|
|
70
|
+
void stop() {}
|
|
71
|
+
}
|
|
72
|
+
"""
|
|
73
|
+
return "java", src, [("method", "start", "Svc"), ("method", "stop", "Svc")]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@case(tags=["symbol"])
|
|
77
|
+
def case_java_static_method() -> SymbolCase:
|
|
78
|
+
"""Static method still scoped."""
|
|
79
|
+
src = """\
|
|
80
|
+
class Factory {
|
|
81
|
+
static Object create() { return null; }
|
|
82
|
+
}
|
|
83
|
+
"""
|
|
84
|
+
return "java", src, [("method", "create", "Factory")]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@case(tags=["symbol"])
|
|
88
|
+
def case_java_method_params() -> SymbolCase:
|
|
89
|
+
"""Method with parameters."""
|
|
90
|
+
src = """\
|
|
91
|
+
class Calc {
|
|
92
|
+
int add(int a, int b) { return a + b; }
|
|
93
|
+
}
|
|
94
|
+
"""
|
|
95
|
+
return "java", src, [("method", "add", "Calc")]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@case(tags=["symbol"])
|
|
99
|
+
def case_java_nested_class() -> SymbolCase:
|
|
100
|
+
"""Nested class scoped to outer, method scoped to inner."""
|
|
101
|
+
src = """\
|
|
102
|
+
class Outer {
|
|
103
|
+
class Inner {
|
|
104
|
+
void deep() {}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
"""
|
|
108
|
+
return (
|
|
109
|
+
"java",
|
|
110
|
+
src,
|
|
111
|
+
[("class", "Outer", ""), ("class", "Inner", "Outer"), ("method", "deep", "Outer::Inner")],
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@case(tags=["symbol"])
|
|
116
|
+
def case_java_triple_nested_class() -> SymbolCase:
|
|
117
|
+
"""Three levels of nested class compose the full path."""
|
|
118
|
+
src = """\
|
|
119
|
+
class Outer {
|
|
120
|
+
class Mid {
|
|
121
|
+
class Inner {
|
|
122
|
+
void deep() {}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
"""
|
|
127
|
+
return "java", src, [("method", "deep", "Outer::Mid::Inner")]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@case(tags=["import"])
|
|
131
|
+
def case_java_import_class() -> ImportCase:
|
|
132
|
+
"""import java.util.HashMap."""
|
|
133
|
+
return "java", "import java.util.HashMap;\n", {"module": "java.util.HashMap"}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@case(tags=["import"])
|
|
137
|
+
def case_java_import_deeply_nested() -> ImportCase:
|
|
138
|
+
"""import com.example.app.models.User."""
|
|
139
|
+
return (
|
|
140
|
+
"java",
|
|
141
|
+
"import com.example.app.models.User;\n",
|
|
142
|
+
{"module": "com.example.app.models.User"},
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@case(tags=["import"])
|
|
147
|
+
def case_java_import_static() -> ImportCase:
|
|
148
|
+
"""import static org.junit.Assert.assertEquals."""
|
|
149
|
+
return (
|
|
150
|
+
"java",
|
|
151
|
+
"import static org.junit.Assert.assertEquals;\n",
|
|
152
|
+
{"module": "org.junit.Assert.assertEquals"},
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@case(tags=["import"])
|
|
157
|
+
def case_java_import_static_method() -> ImportCase:
|
|
158
|
+
"""import static java.util.Collections.sort."""
|
|
159
|
+
return (
|
|
160
|
+
"java",
|
|
161
|
+
"import static java.util.Collections.sort;\n",
|
|
162
|
+
{"module": "java.util.Collections.sort"},
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@case(tags=["multi_import"])
|
|
167
|
+
def case_java_multiple_imports() -> MultiImportCase:
|
|
168
|
+
"""Two import statements."""
|
|
169
|
+
src = """\
|
|
170
|
+
import java.util.List;
|
|
171
|
+
import java.util.Map;
|
|
172
|
+
"""
|
|
173
|
+
return (
|
|
174
|
+
"java",
|
|
175
|
+
src,
|
|
176
|
+
2,
|
|
177
|
+
[
|
|
178
|
+
{"module": "java.util.List"},
|
|
179
|
+
{"module": "java.util.Map"},
|
|
180
|
+
],
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@case(tags=["mixed"])
|
|
185
|
+
def case_java_full_class() -> MixedCase:
|
|
186
|
+
"""Realistic Java class with Javadoc on every member.
|
|
187
|
+
|
|
188
|
+
Expected tuple unchanged; content assertions in
|
|
189
|
+
`test_docstrings.py`.
|
|
190
|
+
"""
|
|
191
|
+
src = """\
|
|
192
|
+
import java.util.List;
|
|
193
|
+
import java.util.ArrayList;
|
|
194
|
+
|
|
195
|
+
/** Tracks registered user names. */
|
|
196
|
+
public class UserService {
|
|
197
|
+
private List<String> names;
|
|
198
|
+
|
|
199
|
+
/** Append a new name to the registry. */
|
|
200
|
+
public void addName(String name) {
|
|
201
|
+
names.add(name);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Return the current list of names. */
|
|
205
|
+
public List<String> getNames() {
|
|
206
|
+
return names;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
"""
|
|
210
|
+
return (
|
|
211
|
+
"java",
|
|
212
|
+
src,
|
|
213
|
+
{"import", "class", "method"},
|
|
214
|
+
[("addName", "UserService"), ("getNames", "UserService")],
|
|
215
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Greeter — format greetings for named recipients.
|
|
2
|
+
//
|
|
3
|
+
// The Java plugin extracts classes, methods (including constructors),
|
|
4
|
+
// fields (as variables), and imports. Interfaces, enums, and records are
|
|
5
|
+
// captured as classes, and their members scope to them.
|
|
6
|
+
package com.example.greet;
|
|
7
|
+
|
|
8
|
+
import java.util.List;
|
|
9
|
+
import static java.util.Collections.emptyList;
|
|
10
|
+
|
|
11
|
+
import com.example.format.Formatter;
|
|
12
|
+
|
|
13
|
+
// Standalone note at top level, separated by blank lines.
|
|
14
|
+
// Second line of the same block.
|
|
15
|
+
|
|
16
|
+
/** Formats greetings with a configurable prefix. */
|
|
17
|
+
public class Greeter implements Formatter {
|
|
18
|
+
private final String prefix;
|
|
19
|
+
|
|
20
|
+
public Greeter(String prefix) {
|
|
21
|
+
this.prefix = prefix;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Format a greeting (implements the Formatter contract). */
|
|
25
|
+
@Override
|
|
26
|
+
public String format(String name) {
|
|
27
|
+
return greet(name);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Greet a single recipient. */
|
|
31
|
+
public String greet(String name) {
|
|
32
|
+
return prefix + ", " + name;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Build a greeter with the default prefix. */
|
|
36
|
+
public static Greeter withDefault() {
|
|
37
|
+
return new Greeter("Hello");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** A nested formatter. */
|
|
41
|
+
static class Formatter {
|
|
42
|
+
String format(String name) {
|
|
43
|
+
return name.trim();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A greeting strategy contract. */
|
|
49
|
+
interface Strategy {
|
|
50
|
+
String render(String name);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Supported greeting tones. */
|
|
54
|
+
enum Tone {
|
|
55
|
+
FORMAL,
|
|
56
|
+
CASUAL;
|
|
57
|
+
|
|
58
|
+
/** Human-readable label for this tone. */
|
|
59
|
+
String label() {
|
|
60
|
+
return name().toLowerCase();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** An immutable named recipient. */
|
|
65
|
+
record Recipient(String name, String locale) {
|
|
66
|
+
/** Full display name including locale. */
|
|
67
|
+
String display() {
|
|
68
|
+
return name + " (" + locale + ")";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Marks a greeter method as audited. */
|
|
73
|
+
@interface Audited {
|
|
74
|
+
String value();
|
|
75
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Java doc-comment extraction (Java is exterior-doc: leading comments attach via the sibling walk)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pytest_cases import parametrize_with_cases
|
|
6
|
+
|
|
7
|
+
from rbtr.git import FileEntry
|
|
8
|
+
from rbtr.languages.extract import extract_file
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@parametrize_with_cases(
|
|
12
|
+
"lang, source, name, snippet", cases=".cases_docstrings", has_tag="documented"
|
|
13
|
+
)
|
|
14
|
+
def test_documented_chunk_includes_doc_text(
|
|
15
|
+
lang: str, source: str, name: str, snippet: str
|
|
16
|
+
) -> None:
|
|
17
|
+
"""By default the chunk content carries the symbol's docs."""
|
|
18
|
+
chunks = extract_file(FileEntry("input", "sha1", source.encode()), lang)
|
|
19
|
+
chunk = next(c for c in chunks if c.name == name)
|
|
20
|
+
assert snippet in chunk.content, f"expected {snippet!r} in {lang}.{name}: {chunk.content!r}"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@parametrize_with_cases(
|
|
24
|
+
"lang, source, name, snippet", cases=".cases_docstrings", has_tag="undocumented"
|
|
25
|
+
)
|
|
26
|
+
def test_no_phantom_documentation(lang: str, source: str, name: str, snippet: str) -> None:
|
|
27
|
+
"""Symbols without documentation do not gain any in content."""
|
|
28
|
+
chunks = extract_file(FileEntry("input", "sha1", source.encode()), lang)
|
|
29
|
+
chunk = next(c for c in chunks if c.name == name)
|
|
30
|
+
assert snippet not in chunk.content, (
|
|
31
|
+
f"unexpected {snippet!r} in {lang}.{name}: {chunk.content!r}"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@parametrize_with_cases(
|
|
36
|
+
"lang, source, name, snippet", cases=".cases_docstrings", has_tag="exterior_doc"
|
|
37
|
+
)
|
|
38
|
+
def test_leading_doc_folds_into_symbol(lang: str, source: str, name: str, snippet: str) -> None:
|
|
39
|
+
"""A leading comment block folds into its symbol's chunk content."""
|
|
40
|
+
chunk = next(
|
|
41
|
+
c for c in extract_file(FileEntry("input", "sha1", source.encode()), lang) if c.name == name
|
|
42
|
+
)
|
|
43
|
+
assert snippet in chunk.content
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Java extraction tests (cases in `cases_extraction.py`)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pytest_cases import parametrize_with_cases
|
|
6
|
+
|
|
7
|
+
from rbtr.git import FileEntry
|
|
8
|
+
from rbtr.index.models import ChunkKind, ImportMeta
|
|
9
|
+
from rbtr.languages.extract import extract_file
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@parametrize_with_cases("lang, source, expected", cases=".cases_extraction", has_tag="symbol")
|
|
13
|
+
def test_extracts_expected_symbols(lang: str, source: str, expected: list) -> None:
|
|
14
|
+
"""Each expected (kind, name, scope) tuple appears in the output."""
|
|
15
|
+
chunks = extract_file(FileEntry("input", "sha1", source.encode()), lang)
|
|
16
|
+
symbols = [(c.kind, c.name, c.scope) for c in chunks]
|
|
17
|
+
for exp in expected:
|
|
18
|
+
assert exp in symbols, f"expected {exp} not found in {symbols}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@parametrize_with_cases(
|
|
22
|
+
"lang, source, expected_kinds, expected_methods", cases=".cases_extraction", has_tag="mixed"
|
|
23
|
+
)
|
|
24
|
+
def test_extracts_all_expected_kinds(
|
|
25
|
+
lang: str, source: str, expected_kinds: set[str], expected_methods: list[tuple[str, str]]
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Realistic source produces all expected chunk kinds and method scoping."""
|
|
28
|
+
chunks = extract_file(FileEntry("input", "sha1", source.encode()), lang)
|
|
29
|
+
kinds = {c.kind for c in chunks}
|
|
30
|
+
for kind in expected_kinds:
|
|
31
|
+
assert kind in kinds, f"expected kind {kind!r} not in {kinds}"
|
|
32
|
+
methods = [(c.name, c.scope) for c in chunks if c.kind == ChunkKind.METHOD]
|
|
33
|
+
for name, scope in expected_methods:
|
|
34
|
+
assert (name, scope) in methods, f"expected method ({name}, {scope}) not in {methods}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@parametrize_with_cases("lang, source, expected", cases=".cases_extraction", has_tag="import")
|
|
38
|
+
def test_extracts_import_metadata(lang: str, source: str, expected: dict) -> None:
|
|
39
|
+
"""First import chunk has the expected metadata."""
|
|
40
|
+
chunks = extract_file(FileEntry("input", "sha1", source.encode()), lang)
|
|
41
|
+
imports = [c for c in chunks if c.kind == ChunkKind.IMPORT]
|
|
42
|
+
assert len(imports) >= 1, f"no import chunks extracted from {source!r}"
|
|
43
|
+
assert imports[0].metadata == ImportMeta(**expected)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@parametrize_with_cases(
|
|
47
|
+
"lang, source, count, metadata_list", cases=".cases_extraction", has_tag="multi_import"
|
|
48
|
+
)
|
|
49
|
+
def test_extracts_multi_import(
|
|
50
|
+
lang: str, source: str, count: int, metadata_list: list[dict]
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Multiple imports have correct count and per-import metadata."""
|
|
53
|
+
chunks = extract_file(FileEntry("input", "sha1", source.encode()), lang)
|
|
54
|
+
imports = [c for c in chunks if c.kind == ChunkKind.IMPORT]
|
|
55
|
+
assert len(imports) == count
|
|
56
|
+
for imp, expected in zip(imports, metadata_list, strict=True):
|
|
57
|
+
assert imp.metadata == ImportMeta(**expected)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""java sample extraction: the `samples/java/` project through the real pipeline."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from tree_sitter import Parser
|
|
10
|
+
|
|
11
|
+
from rbtr.git import FileEntry
|
|
12
|
+
from rbtr.index.models import Chunk, ChunkKind, Edge
|
|
13
|
+
from rbtr.languages.edges import build_resolution_map, infer_import_edges
|
|
14
|
+
from rbtr.languages.extract import extract_file
|
|
15
|
+
from rbtr.languages.manager import get_manager
|
|
16
|
+
from rbtr.testing import render_edges
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from syrupy.assertion import SnapshotAssertion
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def project() -> list[tuple[str, str]]:
|
|
24
|
+
root = Path(__file__).parent / "samples" / "java"
|
|
25
|
+
return [
|
|
26
|
+
(str(p.relative_to(root)), p.read_text()) for p in sorted(root.rglob("*")) if p.is_file()
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def chunks(project: list[tuple[str, str]]) -> list[Chunk]:
|
|
32
|
+
manager = get_manager()
|
|
33
|
+
out: list[Chunk] = []
|
|
34
|
+
for path, text in project:
|
|
35
|
+
lang = manager.detect_language(path) or "java"
|
|
36
|
+
out.extend(extract_file(FileEntry(path, "sha1", text.encode()), lang))
|
|
37
|
+
return out
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.fixture
|
|
41
|
+
def edges(project: list[tuple[str, str]], chunks: list[Chunk]) -> list[Edge]:
|
|
42
|
+
manager = get_manager()
|
|
43
|
+
repo_files = {path for path, _ in project}
|
|
44
|
+
return infer_import_edges(chunks, repo_files, build_resolution_map(manager))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_emits_expected_kinds(chunks: list[Chunk]) -> None:
|
|
48
|
+
kinds = {c.kind for c in chunks}
|
|
49
|
+
assert {
|
|
50
|
+
ChunkKind.CLASS,
|
|
51
|
+
ChunkKind.METHOD,
|
|
52
|
+
ChunkKind.VARIABLE,
|
|
53
|
+
ChunkKind.IMPORT,
|
|
54
|
+
ChunkKind.COMMENT,
|
|
55
|
+
} <= kinds
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_parses_cleanly(project: list[tuple[str, str]]) -> None:
|
|
59
|
+
manager = get_manager()
|
|
60
|
+
for path, text in project:
|
|
61
|
+
grammar = manager.grammar(manager.detect_language(path) or "java")
|
|
62
|
+
assert grammar is not None
|
|
63
|
+
assert not Parser(grammar).parse(text.encode()).root_node.has_error, path
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_extraction_matches_snapshot(chunks: list[Chunk], snapshot_json: SnapshotAssertion) -> None:
|
|
67
|
+
assert chunks == snapshot_json
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_edges_match_snapshot(
|
|
71
|
+
chunks: list[Chunk], edges: list[Edge], snapshot_json: SnapshotAssertion
|
|
72
|
+
) -> None:
|
|
73
|
+
assert render_edges(edges, chunks) == snapshot_json
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
rbtr_lang_java/__init__.py,sha256=VFKv8iFU-fgKOWo3kiVJPUjKTzJpGOe56eJk-KTsKck,36
|
|
2
|
+
rbtr_lang_java/java.scm,sha256=w-ZXb30NpYY9vEMxhY-Xa1U0Af4UUbd-BegM1r_hv-A,1110
|
|
3
|
+
rbtr_lang_java/plugin.py,sha256=zGIibd6FqhWWzXEMLIFrh-iksWwfAARp0n8IQn4Lg7Y,1604
|
|
4
|
+
rbtr_lang_java/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
rbtr_lang_java/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
rbtr_lang_java/tests/__snapshots__/test_samples/test_edges_match_snapshot.json,sha256=5KJM-lNVfq6Go6vl08c8-bR2BebL_SH0y2iGYDLSLt8,222
|
|
7
|
+
rbtr_lang_java/tests/__snapshots__/test_samples/test_extraction_matches_snapshot.json,sha256=Cq_BNdMgeipaewijIlFYgmeNb5rC0PqGd9p3-sZZj5o,12701
|
|
8
|
+
rbtr_lang_java/tests/cases_docstrings.py,sha256=EtybMdvhrVx-c3eQqDieuSaNz2EIcrTLl9EUOTiRQYQ,2971
|
|
9
|
+
rbtr_lang_java/tests/cases_extraction.py,sha256=EWmf0tZ3AbAkRF6nXsw5_IPTUGkotqWS09BFs1aSkmU,5164
|
|
10
|
+
rbtr_lang_java/tests/samples/java/com/example/format/Formatter.java,sha256=54LpdddqFxN2M7Y3RY_pIDtXdaN_i5-3vvm8pPxDYQM,135
|
|
11
|
+
rbtr_lang_java/tests/samples/java/java.java,sha256=ZwGw2fjqUC33RuPJS2jBLabGYmiAt3AlaiP4gsBOOik,1823
|
|
12
|
+
rbtr_lang_java/tests/test_docstrings.py,sha256=oKBa0Ubvzxh_aiDMX2qvw3PV35sx46wfDBAFN-l6rbg,1752
|
|
13
|
+
rbtr_lang_java/tests/test_extraction.py,sha256=TNRzVn2T0MBtKXck-dvVO5nLUlI5vDCojySyfMhxQQI,2654
|
|
14
|
+
rbtr_lang_java/tests/test_samples.py,sha256=inbDO27F1Ss8KFRIy-yeN6GwAZfNn7n5UbGrJkhF3cY,2272
|
|
15
|
+
rbtr_lang_java-2026.7.0.dev0.dist-info/WHEEL,sha256=CoDSoyhtC_eO_tlxRYzsTraPv1fPJRXFx91k6ISeAvA,81
|
|
16
|
+
rbtr_lang_java-2026.7.0.dev0.dist-info/entry_points.txt,sha256=Uy4x3Y6jW3R6M_VV2bHXgcjs8GiGI-MAYu4wx9agXKg,52
|
|
17
|
+
rbtr_lang_java-2026.7.0.dev0.dist-info/METADATA,sha256=rtkyczRFo3qvFdf1Ig4i6qwpkwBMC8MOZ_EzsFX18Pk,220
|
|
18
|
+
rbtr_lang_java-2026.7.0.dev0.dist-info/RECORD,,
|