rbtr-lang-cpp 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.
@@ -0,0 +1 @@
1
+ """C++ language plugin package."""
rbtr_lang_cpp/cpp.scm ADDED
@@ -0,0 +1,106 @@
1
+ ; Top-level comments (C++ uses one `comment` type for `//` and `/* */`).
2
+ (comment) @comment
3
+
4
+ (function_definition
5
+ declarator: (function_declarator
6
+ declarator: (identifier) @_fn_name)) @function
7
+
8
+ (function_definition
9
+ declarator: (function_declarator
10
+ declarator: (field_identifier) @_fn_name)) @function
11
+
12
+ (function_definition
13
+ declarator: (function_declarator
14
+ declarator: (operator_name) @_fn_name)) @function
15
+
16
+ (field_declaration
17
+ declarator: (function_declarator
18
+ declarator: (field_identifier) @_method_name)) @method
19
+
20
+ (field_declaration_list
21
+ (declaration
22
+ declarator: (function_declarator
23
+ declarator: (identifier) @_method_name)) @method)
24
+
25
+ (preproc_include
26
+ path: (system_lib_string) @_import_module) @import
27
+
28
+ (preproc_include
29
+ path: (string_literal) @_import_module) @import
30
+
31
+ (preproc_function_def
32
+ name: (identifier) @_fn_name) @function
33
+
34
+ (preproc_def
35
+ name: (identifier) @_var_name) @variable
36
+
37
+ (class_specifier
38
+ name: (type_identifier) @_cls_name
39
+ body: (field_declaration_list)) @class
40
+
41
+ (struct_specifier
42
+ name: (type_identifier) @_cls_name
43
+ body: (field_declaration_list)) @class
44
+
45
+ (union_specifier
46
+ name: (type_identifier) @_cls_name
47
+ body: (field_declaration_list)) @class
48
+
49
+ (enum_specifier
50
+ name: (type_identifier) @_cls_name
51
+ body: (enumerator_list)) @class
52
+
53
+ (alias_declaration
54
+ name: (type_identifier) @_cls_name) @class
55
+
56
+ (namespace_definition
57
+ name: (namespace_identifier) @_cls_name) @class
58
+
59
+ (concept_definition
60
+ name: (identifier) @_cls_name) @class
61
+
62
+ (translation_unit
63
+ (declaration
64
+ declarator: (function_declarator
65
+ declarator: (identifier) @_fn_name)) @function)
66
+
67
+ (namespace_definition
68
+ body: (declaration_list
69
+ (declaration
70
+ declarator: (function_declarator
71
+ declarator: (identifier) @_fn_name)) @function))
72
+
73
+ (translation_unit
74
+ (declaration
75
+ declarator: (init_declarator
76
+ declarator: (identifier) @_var_name)) @variable)
77
+
78
+ (translation_unit
79
+ (declaration
80
+ declarator: (pointer_declarator
81
+ declarator: (identifier) @_var_name)) @variable)
82
+
83
+ (translation_unit
84
+ (declaration
85
+ declarator: (init_declarator
86
+ declarator: (pointer_declarator
87
+ declarator: (identifier) @_var_name))) @variable)
88
+
89
+ (namespace_definition
90
+ body: (declaration_list
91
+ (declaration
92
+ declarator: (init_declarator
93
+ declarator: (identifier) @_var_name)) @variable))
94
+
95
+ (namespace_definition
96
+ body: (declaration_list
97
+ (declaration
98
+ declarator: (pointer_declarator
99
+ declarator: (identifier) @_var_name)) @variable))
100
+
101
+ (namespace_definition
102
+ body: (declaration_list
103
+ (declaration
104
+ declarator: (init_declarator
105
+ declarator: (pointer_declarator
106
+ declarator: (identifier) @_var_name))) @variable))
@@ -0,0 +1,43 @@
1
+ """C++ language plugin.
2
+
3
+ Provides symbol extraction (functions, prototypes, classes, structs,
4
+ unions, enums, type aliases, namespaces, methods including operator
5
+ overloads, namespace/global variables, and function/object-like
6
+ macros) and include directive capture. Namespaces are both a symbol
7
+ (class) and a naming scope.
8
+
9
+ Extracted chunks::
10
+
11
+ void greet() {} → function "greet", scope ""
12
+ class Shape {}; → class "Shape", scope ""
13
+ struct Point { ... }; → class "Point", scope ""
14
+ class Foo {
15
+ void bar() {} → method "bar", scope "Foo"
16
+ };
17
+
18
+ #include <iostream>
19
+ → import, metadata {module: "iostream"}
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from rbtr.languages.registration import LanguageRegistration, QueryExtraction, load_query
25
+
26
+ # ── Query ────────────────────────────────────────────────────────────
27
+
28
+
29
+ # ── Plugin ───────────────────────────────────────────────────────────
30
+
31
+
32
+ cpp = LanguageRegistration(
33
+ id="cpp",
34
+ extensions=frozenset({".cpp", ".cc", ".cxx", ".hpp", ".hxx"}),
35
+ grammar_module="tree_sitter_cpp",
36
+ extraction=QueryExtraction(
37
+ query=load_query(__package__, "cpp"),
38
+ scope_types=frozenset({"class_specifier", "struct_specifier", "namespace_definition"}),
39
+ class_scope_types=frozenset({"class_specifier", "struct_specifier"}),
40
+ ),
41
+ source_roots=("", "include", "src"),
42
+ extraction_serial=6,
43
+ )
rbtr_lang_cpp/py.typed ADDED
File without changes
File without changes
@@ -0,0 +1,7 @@
1
+ [
2
+ "cpp.cpp::#include \"greeter.hpp\" -> greeter.hpp::Greeting [imports]",
3
+ "cpp.cpp::#include \"greeter.hpp\" -> greeter.hpp::Greeting [imports]",
4
+ "cpp.cpp::#include \"greeter.hpp\" -> greeter.hpp::greet [imports]",
5
+ "cpp.cpp::#include \"greeter.hpp\" -> greeter.hpp::render [imports]",
6
+ "cpp.cpp::#include \"greeter.hpp\" -> greeter.hpp::withDefault [imports]"
7
+ ]
@@ -0,0 +1,488 @@
1
+ [
2
+ {
3
+ "id": "30717ce6f7a37685",
4
+ "blob_sha": "sha1",
5
+ "file_path": "cpp.cpp",
6
+ "kind": "comment",
7
+ "name": "<anonymous>",
8
+ "scope": "",
9
+ "language": "cpp",
10
+ "content": "// Greeter — format greetings for named recipients.\n//\n// The C++ plugin extracts free functions, prototypes, classes/structs/\n// unions, type aliases, namespaces (as classes and scopes), member\n// functions and operator overloads (as methods), namespace/global\n// variables (including pointer globals), macros, and #include imports.",
11
+ "line_start": 1,
12
+ "line_end": 6,
13
+ "metadata": {
14
+ "module": "",
15
+ "names": "",
16
+ "dots": "",
17
+ "language_hint": ""
18
+ }
19
+ },
20
+ {
21
+ "id": "b9a294f43e158d5e",
22
+ "blob_sha": "sha1",
23
+ "file_path": "cpp.cpp",
24
+ "kind": "import",
25
+ "name": "#include <string>",
26
+ "scope": "",
27
+ "language": "cpp",
28
+ "content": "#include <string>\n",
29
+ "line_start": 8,
30
+ "line_end": 9,
31
+ "metadata": {
32
+ "module": "string",
33
+ "names": "",
34
+ "dots": "",
35
+ "language_hint": ""
36
+ }
37
+ },
38
+ {
39
+ "id": "e67c80769188dd11",
40
+ "blob_sha": "sha1",
41
+ "file_path": "cpp.cpp",
42
+ "kind": "import",
43
+ "name": "#include \"greeter.hpp\"",
44
+ "scope": "",
45
+ "language": "cpp",
46
+ "content": "#include \"greeter.hpp\"\n",
47
+ "line_start": 10,
48
+ "line_end": 11,
49
+ "metadata": {
50
+ "module": "greeter.hpp",
51
+ "names": "",
52
+ "dots": "",
53
+ "language_hint": ""
54
+ }
55
+ },
56
+ {
57
+ "id": "2f6bcc64c3714d92",
58
+ "blob_sha": "sha1",
59
+ "file_path": "cpp.cpp",
60
+ "kind": "variable",
61
+ "name": "GREET_VERSION",
62
+ "scope": "",
63
+ "language": "cpp",
64
+ "content": "#define GREET_VERSION 2\n",
65
+ "line_start": 12,
66
+ "line_end": 13,
67
+ "metadata": {
68
+ "module": "",
69
+ "names": "",
70
+ "dots": "",
71
+ "language_hint": ""
72
+ }
73
+ },
74
+ {
75
+ "id": "8ba3d658ff2ec7c6",
76
+ "blob_sha": "sha1",
77
+ "file_path": "cpp.cpp",
78
+ "kind": "variable",
79
+ "name": "kAppName",
80
+ "scope": "",
81
+ "language": "cpp",
82
+ "content": "const char *kAppName = \"greeter\";",
83
+ "line_start": 14,
84
+ "line_end": 14,
85
+ "metadata": {
86
+ "module": "",
87
+ "names": "",
88
+ "dots": "",
89
+ "language_hint": ""
90
+ }
91
+ },
92
+ {
93
+ "id": "4eed4993fc15787d",
94
+ "blob_sha": "sha1",
95
+ "file_path": "cpp.cpp",
96
+ "kind": "comment",
97
+ "name": "<anonymous>",
98
+ "scope": "",
99
+ "language": "cpp",
100
+ "content": "// trailing comment: its own chunk",
101
+ "line_start": 14,
102
+ "line_end": 14,
103
+ "metadata": {
104
+ "module": "",
105
+ "names": "",
106
+ "dots": "",
107
+ "language_hint": ""
108
+ }
109
+ },
110
+ {
111
+ "id": "43d1546e4f810260",
112
+ "blob_sha": "sha1",
113
+ "file_path": "cpp.cpp",
114
+ "kind": "comment",
115
+ "name": "<anonymous>",
116
+ "scope": "",
117
+ "language": "cpp",
118
+ "content": "// Standalone note, separated by blank lines from any definition.\n// Second line of the same block.",
119
+ "line_start": 16,
120
+ "line_end": 17,
121
+ "metadata": {
122
+ "module": "",
123
+ "names": "",
124
+ "dots": "",
125
+ "language_hint": ""
126
+ }
127
+ },
128
+ {
129
+ "id": "b381fa11b53526df",
130
+ "blob_sha": "sha1",
131
+ "file_path": "cpp.cpp",
132
+ "kind": "class",
133
+ "name": "greet",
134
+ "scope": "",
135
+ "language": "cpp",
136
+ "content": "namespace greet {\n\nconst std::string DEFAULT_PREFIX = \"Hello\";\n\n// A short alias for a recipient name.\nusing Name = std::string;\n\n// Anything convertible to a std::string can name a recipient.\ntemplate <typename T>\nconcept Nameable = std::convertible_to<T, std::string>;\n\n// A tagged greeting payload.\nunion Payload {\n int code;\n const char *text;\n};\n\n// Build a greeter with the default prefix (declared here).\nGreeter make_default();\n\n// A greeter holding a prefix.\nclass Greeter {\n public:\n explicit Greeter(std::string prefix) : prefix_(std::move(prefix)) {}\n\n // Greet a single recipient.\n std::string greet(const std::string &name) const {\n return prefix_ + \", \" + name;\n }\n\n // Two greeters are equal when their prefixes match.\n bool operator==(const Greeter &other) const {\n return prefix_ == other.prefix_;\n }\n\n private:\n std::string prefix_;\n};\n\n// Format a greeting using a greeter.\nstd::string format_greeting(const Greeter &g, const std::string &name) {\n return g.greet(name);\n}\n\n}",
137
+ "line_start": 19,
138
+ "line_end": 63,
139
+ "metadata": {
140
+ "module": "",
141
+ "names": "",
142
+ "dots": "",
143
+ "language_hint": ""
144
+ }
145
+ },
146
+ {
147
+ "id": "59f56ee2ad81de8a",
148
+ "blob_sha": "sha1",
149
+ "file_path": "cpp.cpp",
150
+ "kind": "variable",
151
+ "name": "DEFAULT_PREFIX",
152
+ "scope": "greet",
153
+ "language": "cpp",
154
+ "content": "const std::string DEFAULT_PREFIX = \"Hello\";",
155
+ "line_start": 21,
156
+ "line_end": 21,
157
+ "metadata": {
158
+ "module": "",
159
+ "names": "",
160
+ "dots": "",
161
+ "language_hint": ""
162
+ }
163
+ },
164
+ {
165
+ "id": "bcdb6a9bb1ac1e73",
166
+ "blob_sha": "sha1",
167
+ "file_path": "cpp.cpp",
168
+ "kind": "class",
169
+ "name": "Name",
170
+ "scope": "greet",
171
+ "language": "cpp",
172
+ "content": "// A short alias for a recipient name.\nusing Name = std::string;",
173
+ "line_start": 23,
174
+ "line_end": 24,
175
+ "metadata": {
176
+ "module": "",
177
+ "names": "",
178
+ "dots": "",
179
+ "language_hint": ""
180
+ }
181
+ },
182
+ {
183
+ "id": "a23a15e987dc9b6c",
184
+ "blob_sha": "sha1",
185
+ "file_path": "cpp.cpp",
186
+ "kind": "class",
187
+ "name": "Nameable",
188
+ "scope": "greet",
189
+ "language": "cpp",
190
+ "content": "concept Nameable = std::convertible_to<T, std::string>;",
191
+ "line_start": 28,
192
+ "line_end": 28,
193
+ "metadata": {
194
+ "module": "",
195
+ "names": "",
196
+ "dots": "",
197
+ "language_hint": ""
198
+ }
199
+ },
200
+ {
201
+ "id": "b089f7380dbf0746",
202
+ "blob_sha": "sha1",
203
+ "file_path": "cpp.cpp",
204
+ "kind": "class",
205
+ "name": "Payload",
206
+ "scope": "greet",
207
+ "language": "cpp",
208
+ "content": "// A tagged greeting payload.\nunion Payload {\n int code;\n const char *text;\n}",
209
+ "line_start": 30,
210
+ "line_end": 34,
211
+ "metadata": {
212
+ "module": "",
213
+ "names": "",
214
+ "dots": "",
215
+ "language_hint": ""
216
+ }
217
+ },
218
+ {
219
+ "id": "e74269deae2ae442",
220
+ "blob_sha": "sha1",
221
+ "file_path": "cpp.cpp",
222
+ "kind": "function",
223
+ "name": "make_default",
224
+ "scope": "greet",
225
+ "language": "cpp",
226
+ "content": "// Build a greeter with the default prefix (declared here).\nGreeter make_default();",
227
+ "line_start": 36,
228
+ "line_end": 37,
229
+ "metadata": {
230
+ "module": "",
231
+ "names": "",
232
+ "dots": "",
233
+ "language_hint": ""
234
+ }
235
+ },
236
+ {
237
+ "id": "e8f130340606bc1b",
238
+ "blob_sha": "sha1",
239
+ "file_path": "cpp.cpp",
240
+ "kind": "class",
241
+ "name": "Greeter",
242
+ "scope": "greet",
243
+ "language": "cpp",
244
+ "content": "// A greeter holding a prefix.\nclass Greeter {\n public:\n explicit Greeter(std::string prefix) : prefix_(std::move(prefix)) {}\n\n // Greet a single recipient.\n std::string greet(const std::string &name) const {\n return prefix_ + \", \" + name;\n }\n\n // Two greeters are equal when their prefixes match.\n bool operator==(const Greeter &other) const {\n return prefix_ == other.prefix_;\n }\n\n private:\n std::string prefix_;\n}",
245
+ "line_start": 39,
246
+ "line_end": 56,
247
+ "metadata": {
248
+ "module": "",
249
+ "names": "",
250
+ "dots": "",
251
+ "language_hint": ""
252
+ }
253
+ },
254
+ {
255
+ "id": "b6cfb7f8d473a7fb",
256
+ "blob_sha": "sha1",
257
+ "file_path": "cpp.cpp",
258
+ "kind": "method",
259
+ "name": "Greeter",
260
+ "scope": "greet::Greeter",
261
+ "language": "cpp",
262
+ "content": "explicit Greeter(std::string prefix) : prefix_(std::move(prefix)) {}",
263
+ "line_start": 42,
264
+ "line_end": 42,
265
+ "metadata": {
266
+ "module": "",
267
+ "names": "",
268
+ "dots": "",
269
+ "language_hint": ""
270
+ }
271
+ },
272
+ {
273
+ "id": "719fbd3d44c0eea0",
274
+ "blob_sha": "sha1",
275
+ "file_path": "cpp.cpp",
276
+ "kind": "method",
277
+ "name": "greet",
278
+ "scope": "greet::Greeter",
279
+ "language": "cpp",
280
+ "content": "// Greet a single recipient.\n std::string greet(const std::string &name) const {\n return prefix_ + \", \" + name;\n }",
281
+ "line_start": 44,
282
+ "line_end": 47,
283
+ "metadata": {
284
+ "module": "",
285
+ "names": "",
286
+ "dots": "",
287
+ "language_hint": ""
288
+ }
289
+ },
290
+ {
291
+ "id": "87b56da6151de28f",
292
+ "blob_sha": "sha1",
293
+ "file_path": "cpp.cpp",
294
+ "kind": "method",
295
+ "name": "operator==",
296
+ "scope": "greet::Greeter",
297
+ "language": "cpp",
298
+ "content": "// Two greeters are equal when their prefixes match.\n bool operator==(const Greeter &other) const {\n return prefix_ == other.prefix_;\n }",
299
+ "line_start": 49,
300
+ "line_end": 52,
301
+ "metadata": {
302
+ "module": "",
303
+ "names": "",
304
+ "dots": "",
305
+ "language_hint": ""
306
+ }
307
+ },
308
+ {
309
+ "id": "0d709abd3b54cc0f",
310
+ "blob_sha": "sha1",
311
+ "file_path": "cpp.cpp",
312
+ "kind": "function",
313
+ "name": "format_greeting",
314
+ "scope": "greet",
315
+ "language": "cpp",
316
+ "content": "// Format a greeting using a greeter.\nstd::string format_greeting(const Greeter &g, const std::string &name) {\n return g.greet(name);\n}",
317
+ "line_start": 58,
318
+ "line_end": 61,
319
+ "metadata": {
320
+ "module": "",
321
+ "names": "",
322
+ "dots": "",
323
+ "language_hint": ""
324
+ }
325
+ },
326
+ {
327
+ "id": "471b8ad76059b629",
328
+ "blob_sha": "sha1",
329
+ "file_path": "cpp.cpp",
330
+ "kind": "comment",
331
+ "name": "<anonymous>",
332
+ "scope": "",
333
+ "language": "cpp",
334
+ "content": "// namespace greet",
335
+ "line_start": 63,
336
+ "line_end": 63,
337
+ "metadata": {
338
+ "module": "",
339
+ "names": "",
340
+ "dots": "",
341
+ "language_hint": ""
342
+ }
343
+ },
344
+ {
345
+ "id": "529189efb71ce7a6",
346
+ "blob_sha": "sha1",
347
+ "file_path": "greeter.hpp",
348
+ "kind": "comment",
349
+ "name": "<anonymous>",
350
+ "scope": "",
351
+ "language": "cpp",
352
+ "content": "// Header companion for cpp.cpp — declares the greeter API.\n//\n// Exercises in-class *declared-only* methods (no inline body): the\n// constructor and member functions are captured as methods scoped to\n// their class, just like inline definitions.",
353
+ "line_start": 1,
354
+ "line_end": 5,
355
+ "metadata": {
356
+ "module": "",
357
+ "names": "",
358
+ "dots": "",
359
+ "language_hint": ""
360
+ }
361
+ },
362
+ {
363
+ "id": "25bd5d7d5fb434a9",
364
+ "blob_sha": "sha1",
365
+ "file_path": "greeter.hpp",
366
+ "kind": "import",
367
+ "name": "#include <string>",
368
+ "scope": "",
369
+ "language": "cpp",
370
+ "content": "#include <string>\n",
371
+ "line_start": 8,
372
+ "line_end": 9,
373
+ "metadata": {
374
+ "module": "string",
375
+ "names": "",
376
+ "dots": "",
377
+ "language_hint": ""
378
+ }
379
+ },
380
+ {
381
+ "id": "e5c0d8124297f62b",
382
+ "blob_sha": "sha1",
383
+ "file_path": "greeter.hpp",
384
+ "kind": "class",
385
+ "name": "greet",
386
+ "scope": "",
387
+ "language": "cpp",
388
+ "content": "namespace greet {\n\nclass Greeting {\npublic:\n Greeting(std::string prefix);\n std::string render(const std::string &name) const;\n static Greeting withDefault();\n\nprivate:\n std::string prefix_;\n};\n\n}",
389
+ "line_start": 10,
390
+ "line_end": 22,
391
+ "metadata": {
392
+ "module": "",
393
+ "names": "",
394
+ "dots": "",
395
+ "language_hint": ""
396
+ }
397
+ },
398
+ {
399
+ "id": "a292f927d336479b",
400
+ "blob_sha": "sha1",
401
+ "file_path": "greeter.hpp",
402
+ "kind": "class",
403
+ "name": "Greeting",
404
+ "scope": "greet",
405
+ "language": "cpp",
406
+ "content": "class Greeting {\npublic:\n Greeting(std::string prefix);\n std::string render(const std::string &name) const;\n static Greeting withDefault();\n\nprivate:\n std::string prefix_;\n}",
407
+ "line_start": 12,
408
+ "line_end": 20,
409
+ "metadata": {
410
+ "module": "",
411
+ "names": "",
412
+ "dots": "",
413
+ "language_hint": ""
414
+ }
415
+ },
416
+ {
417
+ "id": "ebb10d89fefdf8fd",
418
+ "blob_sha": "sha1",
419
+ "file_path": "greeter.hpp",
420
+ "kind": "method",
421
+ "name": "Greeting",
422
+ "scope": "greet::Greeting",
423
+ "language": "cpp",
424
+ "content": "Greeting(std::string prefix);",
425
+ "line_start": 14,
426
+ "line_end": 14,
427
+ "metadata": {
428
+ "module": "",
429
+ "names": "",
430
+ "dots": "",
431
+ "language_hint": ""
432
+ }
433
+ },
434
+ {
435
+ "id": "85924dfd916f4ebe",
436
+ "blob_sha": "sha1",
437
+ "file_path": "greeter.hpp",
438
+ "kind": "method",
439
+ "name": "render",
440
+ "scope": "greet::Greeting",
441
+ "language": "cpp",
442
+ "content": "std::string render(const std::string &name) const;",
443
+ "line_start": 15,
444
+ "line_end": 15,
445
+ "metadata": {
446
+ "module": "",
447
+ "names": "",
448
+ "dots": "",
449
+ "language_hint": ""
450
+ }
451
+ },
452
+ {
453
+ "id": "e01bbd19e2d7ea97",
454
+ "blob_sha": "sha1",
455
+ "file_path": "greeter.hpp",
456
+ "kind": "method",
457
+ "name": "withDefault",
458
+ "scope": "greet::Greeting",
459
+ "language": "cpp",
460
+ "content": "static Greeting withDefault();",
461
+ "line_start": 16,
462
+ "line_end": 16,
463
+ "metadata": {
464
+ "module": "",
465
+ "names": "",
466
+ "dots": "",
467
+ "language_hint": ""
468
+ }
469
+ },
470
+ {
471
+ "id": "0d59d9d8617205fc",
472
+ "blob_sha": "sha1",
473
+ "file_path": "greeter.hpp",
474
+ "kind": "comment",
475
+ "name": "<anonymous>",
476
+ "scope": "",
477
+ "language": "cpp",
478
+ "content": "// namespace greet",
479
+ "line_start": 22,
480
+ "line_end": 22,
481
+ "metadata": {
482
+ "module": "",
483
+ "names": "",
484
+ "dots": "",
485
+ "language_hint": ""
486
+ }
487
+ }
488
+ ]
@@ -0,0 +1,58 @@
1
+ """C++ 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_cpp_doxygen_on_class() -> DocstringCase:
15
+ """Doxygen above a C++ class."""
16
+ src = """\
17
+ /** A widget. */
18
+ class Widget { public: int x; };
19
+ """
20
+ return "cpp", src, "Widget", "A widget"
21
+
22
+
23
+ @case(tags=["documented", "canonical", "exterior_doc"])
24
+ def case_cpp_doxygen_on_function() -> DocstringCase:
25
+ """Doxygen above a C++ function."""
26
+ src = """\
27
+ /** Get the answer. */
28
+ int answer() { return 42; }
29
+ """
30
+ return "cpp", src, "answer", "Get the answer"
31
+
32
+
33
+ @case(tags=["documented", "edge_case", "exterior_doc"])
34
+ def case_cpp_triple_slash_style() -> DocstringCase:
35
+ """Doxygen `///` style (single-line convention)."""
36
+ src = """\
37
+ /// Triple-slash style.
38
+ int foo() { return 0; }
39
+ """
40
+ return "cpp", src, "foo", "Triple-slash style"
41
+
42
+
43
+ @case(tags=["undocumented", "no_docs"])
44
+ def case_cpp_fn_without_doc() -> DocstringCase:
45
+ src = """\
46
+ int bare() { return 0; }
47
+ """
48
+ return "cpp", src, "bare", "PHANTOM_DOC_TEXT_SHOULD_NEVER_APPEAR"
49
+
50
+
51
+ @case(tags=["undocumented", "boundary_not_attached"])
52
+ def case_cpp_doc_detached_by_blank_line() -> DocstringCase:
53
+ src = """\
54
+ /** Orphan. */
55
+
56
+ int later() { return 0; }
57
+ """
58
+ return "cpp", src, "later", "Orphan"
@@ -0,0 +1,223 @@
1
+ """C++ extraction test cases."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pytest_cases import case
6
+
7
+ type SymbolCase = tuple[str, str, list[tuple[str, str, str]]]
8
+ type ImportCase = tuple[str, str, dict[str, str]]
9
+ type MixedCase = tuple[str, str, set[str], list[tuple[str, str]]]
10
+
11
+
12
+ @case(tags=["symbol"])
13
+ def case_cpp_free_function_void() -> SymbolCase:
14
+ """void greet()."""
15
+ return "cpp", "void greet() { }\n", [("function", "greet", "")]
16
+
17
+
18
+ @case(tags=["symbol"])
19
+ def case_cpp_free_function_returning() -> SymbolCase:
20
+ """int compute(int x)."""
21
+ return "cpp", "int compute(int x) { return x; }\n", [("function", "compute", "")]
22
+
23
+
24
+ @case(tags=["symbol"])
25
+ def case_cpp_multiple_functions() -> SymbolCase:
26
+ """Multiple C++ functions."""
27
+ src = """\
28
+ int foo() { return 0; }
29
+ void bar() { }
30
+ """
31
+ return "cpp", src, [("function", "foo", ""), ("function", "bar", "")]
32
+
33
+
34
+ @case(tags=["symbol"])
35
+ def case_cpp_class() -> SymbolCase:
36
+ """class Shape."""
37
+ return "cpp", "class Shape { };\n", [("class", "Shape", "")]
38
+
39
+
40
+ @case(tags=["symbol"])
41
+ def case_cpp_struct() -> SymbolCase:
42
+ """struct Point."""
43
+ return "cpp", "struct Point { double x; double y; };\n", [("class", "Point", "")]
44
+
45
+
46
+ @case(tags=["symbol"])
47
+ def case_cpp_enum_class() -> SymbolCase:
48
+ """enum class Color."""
49
+ return "cpp", "enum class Color { Red, Green, Blue };\n", [("class", "Color", "")]
50
+
51
+
52
+ @case(tags=["symbol"])
53
+ def case_cpp_class_inheritance() -> SymbolCase:
54
+ """class Derived : public Base."""
55
+ src = """\
56
+ class Base { };
57
+ class Derived : public Base { };
58
+ """
59
+ return "cpp", src, [("class", "Base", ""), ("class", "Derived", "")]
60
+
61
+
62
+ @case(tags=["symbol"])
63
+ def case_cpp_class_method() -> SymbolCase:
64
+ """Method scoped to class."""
65
+ src = """\
66
+ class Foo {
67
+ public:
68
+ void bar() { }
69
+ };
70
+ """
71
+ return "cpp", src, [("method", "bar", "Foo")]
72
+
73
+
74
+ @case(tags=["symbol"])
75
+ def case_cpp_struct_method() -> SymbolCase:
76
+ """Method scoped to struct."""
77
+ return "cpp", "struct Vec { void push(int v) { } };\n", [("method", "push", "Vec")]
78
+
79
+
80
+ @case(tags=["symbol"])
81
+ def case_cpp_multiple_methods() -> SymbolCase:
82
+ """Multiple methods in one class."""
83
+ src = """\
84
+ class Calculator {
85
+ public:
86
+ int add(int a, int b) { return a + b; }
87
+ int sub(int a, int b) { return a - b; }
88
+ };
89
+ """
90
+ return "cpp", src, [("method", "add", "Calculator"), ("method", "sub", "Calculator")]
91
+
92
+
93
+ @case(tags=["symbol"])
94
+ def case_cpp_namespace_function() -> SymbolCase:
95
+ """A free function in a namespace is addressed by the namespace.
96
+
97
+ C++ `namespace` is not tracked today (`f` → ""); target "ns".
98
+ """
99
+ src = """\
100
+ namespace ns {
101
+ void f() { }
102
+ }
103
+ """
104
+ return "cpp", src, [("function", "f", "ns")]
105
+
106
+
107
+ @case(tags=["symbol"])
108
+ def case_cpp_namespace_class_method() -> SymbolCase:
109
+ """A method in a class in a namespace carries the full path."""
110
+ src = """\
111
+ namespace ns {
112
+ class Widget {
113
+ public:
114
+ void draw() { }
115
+ };
116
+ }
117
+ """
118
+ return "cpp", src, [("class", "Widget", "ns"), ("method", "draw", "ns::Widget")]
119
+
120
+
121
+ @case(tags=["symbol"])
122
+ def case_cpp_nested_namespace() -> SymbolCase:
123
+ """Nested namespaces compose outermost-first."""
124
+ src = """\
125
+ namespace a {
126
+ namespace b {
127
+ void f() { }
128
+ }
129
+ }
130
+ """
131
+ return "cpp", src, [("function", "f", "a::b")]
132
+
133
+
134
+ @case(tags=["symbol"])
135
+ def case_cpp_namespace_nested_class_method() -> SymbolCase:
136
+ """A method in nested classes, inside a namespace, composes the full path.
137
+
138
+ Mixes a namespace with class nesting — `ns::Outer::Inner`.
139
+ """
140
+ src = """\
141
+ namespace ns {
142
+ class Outer {
143
+ public:
144
+ class Inner {
145
+ public:
146
+ void deep() { }
147
+ };
148
+ };
149
+ }
150
+ """
151
+ return "cpp", src, [("method", "deep", "ns::Outer::Inner")]
152
+
153
+
154
+ @case(tags=["symbol"])
155
+ def case_cpp_free_function_not_scoped() -> SymbolCase:
156
+ """Function after class is not scoped."""
157
+ src = """\
158
+ class C { };
159
+ void standalone() { }
160
+ """
161
+ return "cpp", src, [("function", "standalone", "")]
162
+
163
+
164
+ @case(tags=["import"])
165
+ def case_cpp_include_system() -> ImportCase:
166
+ """#include <iostream>."""
167
+ return "cpp", "#include <iostream>\n", {"module": "iostream"}
168
+
169
+
170
+ @case(tags=["import"])
171
+ def case_cpp_include_local() -> ImportCase:
172
+ """#include "myheader.h"."""
173
+ return "cpp", '#include "myheader.h"\n', {"module": "myheader.h"}
174
+
175
+
176
+ @case(tags=["import"])
177
+ def case_cpp_include_nested() -> ImportCase:
178
+ """#include <boost/optional.hpp>."""
179
+ return "cpp", "#include <boost/optional.hpp>\n", {"module": "boost/optional.hpp"}
180
+
181
+
182
+ @case(tags=["mixed"])
183
+ def case_cpp_full_file() -> MixedCase:
184
+ """Realistic C++ file with Doxygen comments.
185
+
186
+ Expected tuple unchanged.
187
+ """
188
+ src = """\
189
+ #include <vector>
190
+ #include "config.h"
191
+
192
+ /** Execution engine for the pipeline. */
193
+ class Engine {
194
+ public:
195
+ void start() { }
196
+ void stop() { }
197
+ };
198
+
199
+ /** Options passed to `Engine::start`. */
200
+ struct Options {
201
+ int timeout;
202
+ };
203
+
204
+ /** Execution mode — `Fast` skips integrity checks. */
205
+ enum class Mode { Fast, Safe };
206
+
207
+ /** Drive the engine through a single cycle. */
208
+ void run(Engine& e) {
209
+ e.start();
210
+ }
211
+ """
212
+ return (
213
+ "cpp",
214
+ src,
215
+ {"import", "class", "method", "function"},
216
+ [("start", "Engine"), ("stop", "Engine")],
217
+ )
218
+
219
+
220
+ @case(tags=["symbol"])
221
+ def case_cpp_global() -> SymbolCase:
222
+ """File-scope global with initialiser."""
223
+ return "cpp", "int g = 5;\n", [("variable", "g", "")]
@@ -0,0 +1,63 @@
1
+ // Greeter — format greetings for named recipients.
2
+ //
3
+ // The C++ plugin extracts free functions, prototypes, classes/structs/
4
+ // unions, type aliases, namespaces (as classes and scopes), member
5
+ // functions and operator overloads (as methods), namespace/global
6
+ // variables (including pointer globals), macros, and #include imports.
7
+
8
+ #include <string>
9
+
10
+ #include "greeter.hpp"
11
+
12
+ #define GREET_VERSION 2
13
+
14
+ const char *kAppName = "greeter"; // trailing comment: its own chunk
15
+
16
+ // Standalone note, separated by blank lines from any definition.
17
+ // Second line of the same block.
18
+
19
+ namespace greet {
20
+
21
+ const std::string DEFAULT_PREFIX = "Hello";
22
+
23
+ // A short alias for a recipient name.
24
+ using Name = std::string;
25
+
26
+ // Anything convertible to a std::string can name a recipient.
27
+ template <typename T>
28
+ concept Nameable = std::convertible_to<T, std::string>;
29
+
30
+ // A tagged greeting payload.
31
+ union Payload {
32
+ int code;
33
+ const char *text;
34
+ };
35
+
36
+ // Build a greeter with the default prefix (declared here).
37
+ Greeter make_default();
38
+
39
+ // A greeter holding a prefix.
40
+ class Greeter {
41
+ public:
42
+ explicit Greeter(std::string prefix) : prefix_(std::move(prefix)) {}
43
+
44
+ // Greet a single recipient.
45
+ std::string greet(const std::string &name) const {
46
+ return prefix_ + ", " + name;
47
+ }
48
+
49
+ // Two greeters are equal when their prefixes match.
50
+ bool operator==(const Greeter &other) const {
51
+ return prefix_ == other.prefix_;
52
+ }
53
+
54
+ private:
55
+ std::string prefix_;
56
+ };
57
+
58
+ // Format a greeting using a greeter.
59
+ std::string format_greeting(const Greeter &g, const std::string &name) {
60
+ return g.greet(name);
61
+ }
62
+
63
+ } // namespace greet
@@ -0,0 +1,22 @@
1
+ // Header companion for cpp.cpp — declares the greeter API.
2
+ //
3
+ // Exercises in-class *declared-only* methods (no inline body): the
4
+ // constructor and member functions are captured as methods scoped to
5
+ // their class, just like inline definitions.
6
+ #pragma once
7
+
8
+ #include <string>
9
+
10
+ namespace greet {
11
+
12
+ class Greeting {
13
+ public:
14
+ Greeting(std::string prefix);
15
+ std::string render(const std::string &name) const;
16
+ static Greeting withDefault();
17
+
18
+ private:
19
+ std::string prefix_;
20
+ };
21
+
22
+ } // namespace greet
@@ -0,0 +1,43 @@
1
+ """C++ doc-comment extraction (C++ 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,43 @@
1
+ """C++ 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)
@@ -0,0 +1,74 @@
1
+ """C++ sample extraction: the `samples/cpp/` 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" / "cpp"
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 "cpp"
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.FUNCTION,
51
+ ChunkKind.CLASS,
52
+ ChunkKind.METHOD,
53
+ ChunkKind.VARIABLE,
54
+ ChunkKind.IMPORT,
55
+ ChunkKind.COMMENT,
56
+ } <= kinds
57
+
58
+
59
+ def test_parses_cleanly(project: list[tuple[str, str]]) -> None:
60
+ manager = get_manager()
61
+ for path, text in project:
62
+ grammar = manager.grammar(manager.detect_language(path) or "cpp")
63
+ assert grammar is not None
64
+ assert not Parser(grammar).parse(text.encode()).root_node.has_error, path
65
+
66
+
67
+ def test_extraction_matches_snapshot(chunks: list[Chunk], snapshot_json: SnapshotAssertion) -> None:
68
+ assert chunks == snapshot_json
69
+
70
+
71
+ def test_edges_match_snapshot(
72
+ chunks: list[Chunk], edges: list[Edge], snapshot_json: SnapshotAssertion
73
+ ) -> None:
74
+ assert render_edges(edges, chunks) == snapshot_json
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: rbtr-lang-cpp
3
+ Version: 2026.7.0.dev0
4
+ Summary: rbtr — C++ language plugin
5
+ License-Expression: MIT
6
+ Requires-Dist: rbtr==2026.7.0.dev0
7
+ Requires-Dist: tree-sitter-cpp
8
+ Requires-Python: >=3.13
@@ -0,0 +1,18 @@
1
+ rbtr_lang_cpp/__init__.py,sha256=Hef94LjfHmbWBqOEJbyDkpSzWjYqxPXuslegwfHMD5s,35
2
+ rbtr_lang_cpp/cpp.scm,sha256=QrhrdROxX6W8uUUon8WWU6C0zkXy_Bd-qOSxMfyvD5k,2729
3
+ rbtr_lang_cpp/plugin.py,sha256=-fVX3jbkUGFrglJiMJ0_6vXNq4pDFh49ACKLbbxkqX0,1703
4
+ rbtr_lang_cpp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ rbtr_lang_cpp/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ rbtr_lang_cpp/tests/__snapshots__/test_samples/test_edges_match_snapshot.json,sha256=Hlo6vUD_r6RMxcV313kVRo4xFwJ7MjbNGcYL8-ri3IM,371
7
+ rbtr_lang_cpp/tests/__snapshots__/test_samples/test_extraction_matches_snapshot.json,sha256=qrZFR70cajx5lmPUAfBUmV3STHbuA_3A_8JxPObaQuU,13369
8
+ rbtr_lang_cpp/tests/cases_docstrings.py,sha256=QZhOC6DCNjsv_0FXVsHxEPZYyZFtdwbNCELCPFlboTM,1489
9
+ rbtr_lang_cpp/tests/cases_extraction.py,sha256=ZoSMv2NQ2hVwCmnZ2yKyBkWv6BnnGiBMYGpyP83gbCE,5338
10
+ rbtr_lang_cpp/tests/samples/cpp/cpp.cpp,sha256=x-TqDyDrLWWDQGAsH7YEMPHhYU1X0oIadFbBiSR85Hw,1642
11
+ rbtr_lang_cpp/tests/samples/cpp/greeter.hpp,sha256=19ZQl0dce_aYBhdP4dLjlu8rWcjOuaOqjPD-NhKdd5U,511
12
+ rbtr_lang_cpp/tests/test_docstrings.py,sha256=X5x3rMCC4o3AemYDVgq5N-97j_MSKe9CL5DBGCobSbo,1750
13
+ rbtr_lang_cpp/tests/test_extraction.py,sha256=tPmLyWaP6WdscoGkEK3CH2OZWSnpp5Z_YI2-AhTKjrs,2057
14
+ rbtr_lang_cpp/tests/test_samples.py,sha256=V4VTbjenW3F49RqInFy0PSHgh4cCyXBZRClkCYLRt6k,2295
15
+ rbtr_lang_cpp-2026.7.0.dev0.dist-info/WHEEL,sha256=CoDSoyhtC_eO_tlxRYzsTraPv1fPJRXFx91k6ISeAvA,81
16
+ rbtr_lang_cpp-2026.7.0.dev0.dist-info/entry_points.txt,sha256=b1-9T3HIJxCYvLPXjcVZpVbHUZFSNTFlgAfUK9AbR2w,49
17
+ rbtr_lang_cpp-2026.7.0.dev0.dist-info/METADATA,sha256=hkahgj7lhDyaqwC5z8m5wKuB0cIWQq_n6NOGLxZouKk,217
18
+ rbtr_lang_cpp-2026.7.0.dev0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.11.28
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [rbtr.languages]
2
+ cpp = rbtr_lang_cpp.plugin:cpp
3
+