rbtr-lang-c 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_c/c.scm ADDED
@@ -0,0 +1,63 @@
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
+ (preproc_include
9
+ path: (system_lib_string) @_import_module) @import
10
+
11
+ (preproc_include
12
+ path: (string_literal) @_import_module) @import
13
+
14
+ (struct_specifier
15
+ name: (type_identifier) @_cls_name
16
+ body: (field_declaration_list)) @class
17
+
18
+ (union_specifier
19
+ name: (type_identifier) @_cls_name
20
+ body: (field_declaration_list)) @class
21
+
22
+ (enum_specifier
23
+ name: (type_identifier) @_cls_name
24
+ body: (enumerator_list)) @class
25
+
26
+ (enumerator
27
+ name: (identifier) @_var_name) @variable
28
+
29
+ (type_definition
30
+ declarator: (type_identifier) @_cls_name) @class
31
+
32
+ (type_definition
33
+ declarator: (function_declarator
34
+ declarator: (parenthesized_declarator
35
+ (pointer_declarator
36
+ declarator: (type_identifier) @_cls_name)))) @class
37
+
38
+ (preproc_function_def
39
+ name: (identifier) @_fn_name) @function
40
+
41
+ (preproc_def
42
+ name: (identifier) @_var_name) @variable
43
+
44
+ (translation_unit
45
+ (declaration
46
+ declarator: (function_declarator
47
+ declarator: (identifier) @_fn_name)) @function)
48
+
49
+ (translation_unit
50
+ (declaration
51
+ declarator: (init_declarator
52
+ declarator: (identifier) @_var_name)) @variable)
53
+
54
+ (translation_unit
55
+ (declaration
56
+ declarator: (pointer_declarator
57
+ declarator: (identifier) @_var_name)) @variable)
58
+
59
+ (translation_unit
60
+ (declaration
61
+ declarator: (init_declarator
62
+ declarator: (pointer_declarator
63
+ declarator: (identifier) @_var_name))) @variable)
rbtr_lang_c/plugin.py ADDED
@@ -0,0 +1,41 @@
1
+ """C language plugin.
2
+
3
+ Provides symbol extraction (functions, function prototypes, structs,
4
+ unions, enums, typedefs, global variables, and function/object-like
5
+ macros) and include directive capture. Object-like macros and
6
+ pointer-declared globals are variables; function-like macros and
7
+ prototypes are functions.
8
+
9
+ Extracted chunks::
10
+
11
+ int add(int a, int b) { ... } → function "add", scope ""
12
+ struct Node { int value; }; → class "Node", scope ""
13
+ enum Color { RED, GREEN }; → class "Color", scope ""
14
+ typedef struct { ... } Point; → class "Point", scope ""
15
+
16
+ #include <stdio.h>
17
+ → import, metadata {module: "stdio.h"}
18
+ #include "mylib.h"
19
+ → import, metadata {module: "mylib.h"}
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
+ c = LanguageRegistration(
33
+ id="c",
34
+ extensions=frozenset({".c", ".h"}),
35
+ grammar_module="tree_sitter_c",
36
+ extraction=QueryExtraction(
37
+ query=load_query(__package__, "c"),
38
+ ),
39
+ source_roots=("", "include", "src"),
40
+ extraction_serial=4,
41
+ )
rbtr_lang_c/py.typed ADDED
File without changes
File without changes
@@ -0,0 +1,6 @@
1
+ [
2
+ "c.c::#include \"greeter.h\" -> greeter.h::GREETER_H [imports]",
3
+ "c.c::#include \"greeter.h\" -> greeter.h::Greeter [imports]",
4
+ "c.c::#include \"greeter.h\" -> greeter.h::Greeter [imports]",
5
+ "c.c::#include \"greeter.h\" -> greeter.h::MAX_NAME [imports]"
6
+ ]
@@ -0,0 +1,452 @@
1
+ [
2
+ {
3
+ "id": "4bbfada66e2e2dde",
4
+ "blob_sha": "sha1",
5
+ "file_path": "c.c",
6
+ "kind": "comment",
7
+ "name": "<anonymous>",
8
+ "scope": "",
9
+ "language": "c",
10
+ "content": "/* Greeter — format greetings for named recipients.\n *\n * The C plugin extracts functions, function prototypes, struct/union/\n * enum/typedef type definitions (as classes), top-level variables\n * (including pointer-declared globals), function/object-like macros,\n * and #include imports (system and local). */",
11
+ "line_start": 1,
12
+ "line_end": 6,
13
+ "metadata": {
14
+ "module": "",
15
+ "names": "",
16
+ "dots": "",
17
+ "language_hint": ""
18
+ }
19
+ },
20
+ {
21
+ "id": "ed19bac9e8771016",
22
+ "blob_sha": "sha1",
23
+ "file_path": "c.c",
24
+ "kind": "import",
25
+ "name": "#include <stdio.h>",
26
+ "scope": "",
27
+ "language": "c",
28
+ "content": "#include <stdio.h>\n",
29
+ "line_start": 8,
30
+ "line_end": 9,
31
+ "metadata": {
32
+ "module": "stdio.h",
33
+ "names": "",
34
+ "dots": "",
35
+ "language_hint": ""
36
+ }
37
+ },
38
+ {
39
+ "id": "f3bd03d43786cbdd",
40
+ "blob_sha": "sha1",
41
+ "file_path": "c.c",
42
+ "kind": "import",
43
+ "name": "#include \"greeter.h\"",
44
+ "scope": "",
45
+ "language": "c",
46
+ "content": "#include \"greeter.h\"\n",
47
+ "line_start": 9,
48
+ "line_end": 10,
49
+ "metadata": {
50
+ "module": "greeter.h",
51
+ "names": "",
52
+ "dots": "",
53
+ "language_hint": ""
54
+ }
55
+ },
56
+ {
57
+ "id": "66295f4469f588b6",
58
+ "blob_sha": "sha1",
59
+ "file_path": "c.c",
60
+ "kind": "variable",
61
+ "name": "MAX_NAME",
62
+ "scope": "",
63
+ "language": "c",
64
+ "content": "#define MAX_NAME 64\n",
65
+ "line_start": 11,
66
+ "line_end": 12,
67
+ "metadata": {
68
+ "module": "",
69
+ "names": "",
70
+ "dots": "",
71
+ "language_hint": ""
72
+ }
73
+ },
74
+ {
75
+ "id": "94c70ffbbc25dd51",
76
+ "blob_sha": "sha1",
77
+ "file_path": "c.c",
78
+ "kind": "function",
79
+ "name": "SQUARE",
80
+ "scope": "",
81
+ "language": "c",
82
+ "content": "#define SQUARE(x) ((x) * (x))\n",
83
+ "line_start": 12,
84
+ "line_end": 13,
85
+ "metadata": {
86
+ "module": "",
87
+ "names": "",
88
+ "dots": "",
89
+ "language_hint": ""
90
+ }
91
+ },
92
+ {
93
+ "id": "93f2130c32943142",
94
+ "blob_sha": "sha1",
95
+ "file_path": "c.c",
96
+ "kind": "variable",
97
+ "name": "greeter_count",
98
+ "scope": "",
99
+ "language": "c",
100
+ "content": "int greeter_count = 0;",
101
+ "line_start": 14,
102
+ "line_end": 14,
103
+ "metadata": {
104
+ "module": "",
105
+ "names": "",
106
+ "dots": "",
107
+ "language_hint": ""
108
+ }
109
+ },
110
+ {
111
+ "id": "e3bef78ffe8ef8d8",
112
+ "blob_sha": "sha1",
113
+ "file_path": "c.c",
114
+ "kind": "comment",
115
+ "name": "<anonymous>",
116
+ "scope": "",
117
+ "language": "c",
118
+ "content": "// trailing comment: its own chunk, not folded",
119
+ "line_start": 14,
120
+ "line_end": 14,
121
+ "metadata": {
122
+ "module": "",
123
+ "names": "",
124
+ "dots": "",
125
+ "language_hint": ""
126
+ }
127
+ },
128
+ {
129
+ "id": "0032d098456ec45a",
130
+ "blob_sha": "sha1",
131
+ "file_path": "c.c",
132
+ "kind": "variable",
133
+ "name": "default_prefix",
134
+ "scope": "",
135
+ "language": "c",
136
+ "content": "const char *default_prefix = \"Hello\";",
137
+ "line_start": 15,
138
+ "line_end": 15,
139
+ "metadata": {
140
+ "module": "",
141
+ "names": "",
142
+ "dots": "",
143
+ "language_hint": ""
144
+ }
145
+ },
146
+ {
147
+ "id": "416366101cbeff9a",
148
+ "blob_sha": "sha1",
149
+ "file_path": "c.c",
150
+ "kind": "comment",
151
+ "name": "<anonymous>",
152
+ "scope": "",
153
+ "language": "c",
154
+ "content": "/* Standalone note, separated by blank lines from any definition. */\n/* Second line of the same block. */",
155
+ "line_start": 17,
156
+ "line_end": 18,
157
+ "metadata": {
158
+ "module": "",
159
+ "names": "",
160
+ "dots": "",
161
+ "language_hint": ""
162
+ }
163
+ },
164
+ {
165
+ "id": "86c46ea843464bb6",
166
+ "blob_sha": "sha1",
167
+ "file_path": "c.c",
168
+ "kind": "class",
169
+ "name": "Greeter",
170
+ "scope": "",
171
+ "language": "c",
172
+ "content": "/* A greeter holding a prefix string. */\nstruct Greeter {\n const char *prefix;\n}",
173
+ "line_start": 20,
174
+ "line_end": 23,
175
+ "metadata": {
176
+ "module": "",
177
+ "names": "",
178
+ "dots": "",
179
+ "language_hint": ""
180
+ }
181
+ },
182
+ {
183
+ "id": "50ad4d624ef7d80b",
184
+ "blob_sha": "sha1",
185
+ "file_path": "c.c",
186
+ "kind": "class",
187
+ "name": "Greeter",
188
+ "scope": "",
189
+ "language": "c",
190
+ "content": "typedef struct Greeter Greeter;",
191
+ "line_start": 25,
192
+ "line_end": 25,
193
+ "metadata": {
194
+ "module": "",
195
+ "names": "",
196
+ "dots": "",
197
+ "language_hint": ""
198
+ }
199
+ },
200
+ {
201
+ "id": "7341787e58337d04",
202
+ "blob_sha": "sha1",
203
+ "file_path": "c.c",
204
+ "kind": "class",
205
+ "name": "GreetCallback",
206
+ "scope": "",
207
+ "language": "c",
208
+ "content": "/* A callback invoked for each formatted greeting. */\ntypedef void (*GreetCallback)(const char *line);",
209
+ "line_start": 27,
210
+ "line_end": 28,
211
+ "metadata": {
212
+ "module": "",
213
+ "names": "",
214
+ "dots": "",
215
+ "language_hint": ""
216
+ }
217
+ },
218
+ {
219
+ "id": "e93571296c8a693c",
220
+ "blob_sha": "sha1",
221
+ "file_path": "c.c",
222
+ "kind": "class",
223
+ "name": "Payload",
224
+ "scope": "",
225
+ "language": "c",
226
+ "content": "/* A tagged greeting payload. */\nunion Payload {\n int code;\n const char *text;\n}",
227
+ "line_start": 30,
228
+ "line_end": 34,
229
+ "metadata": {
230
+ "module": "",
231
+ "names": "",
232
+ "dots": "",
233
+ "language_hint": ""
234
+ }
235
+ },
236
+ {
237
+ "id": "41a8e4ce665cd2b7",
238
+ "blob_sha": "sha1",
239
+ "file_path": "c.c",
240
+ "kind": "class",
241
+ "name": "Locale",
242
+ "scope": "",
243
+ "language": "c",
244
+ "content": "enum Locale { LOCALE_EN, LOCALE_FR }",
245
+ "line_start": 36,
246
+ "line_end": 36,
247
+ "metadata": {
248
+ "module": "",
249
+ "names": "",
250
+ "dots": "",
251
+ "language_hint": ""
252
+ }
253
+ },
254
+ {
255
+ "id": "47db2d32016d125b",
256
+ "blob_sha": "sha1",
257
+ "file_path": "c.c",
258
+ "kind": "variable",
259
+ "name": "LOCALE_EN",
260
+ "scope": "",
261
+ "language": "c",
262
+ "content": "LOCALE_EN",
263
+ "line_start": 36,
264
+ "line_end": 36,
265
+ "metadata": {
266
+ "module": "",
267
+ "names": "",
268
+ "dots": "",
269
+ "language_hint": ""
270
+ }
271
+ },
272
+ {
273
+ "id": "bf9111abaa92b1f7",
274
+ "blob_sha": "sha1",
275
+ "file_path": "c.c",
276
+ "kind": "variable",
277
+ "name": "LOCALE_FR",
278
+ "scope": "",
279
+ "language": "c",
280
+ "content": "LOCALE_FR",
281
+ "line_start": 36,
282
+ "line_end": 36,
283
+ "metadata": {
284
+ "module": "",
285
+ "names": "",
286
+ "dots": "",
287
+ "language_hint": ""
288
+ }
289
+ },
290
+ {
291
+ "id": "4e716afb57b39ec5",
292
+ "blob_sha": "sha1",
293
+ "file_path": "c.c",
294
+ "kind": "function",
295
+ "name": "greeter_default",
296
+ "scope": "",
297
+ "language": "c",
298
+ "content": "/* Build a greeter with the default prefix. */\nGreeter greeter_default(void);",
299
+ "line_start": 38,
300
+ "line_end": 39,
301
+ "metadata": {
302
+ "module": "",
303
+ "names": "",
304
+ "dots": "",
305
+ "language_hint": ""
306
+ }
307
+ },
308
+ {
309
+ "id": "dc9b641d87a81ebc",
310
+ "blob_sha": "sha1",
311
+ "file_path": "c.c",
312
+ "kind": "function",
313
+ "name": "format_greeting",
314
+ "scope": "",
315
+ "language": "c",
316
+ "content": "/* Format a greeting for `name` into `buf`. */\nint format_greeting(const Greeter *g, const char *name, char *buf, int n) {\n return snprintf(buf, (size_t)n, \"%s, %s\", g->prefix, name);\n}",
317
+ "line_start": 41,
318
+ "line_end": 44,
319
+ "metadata": {
320
+ "module": "",
321
+ "names": "",
322
+ "dots": "",
323
+ "language_hint": ""
324
+ }
325
+ },
326
+ {
327
+ "id": "02a12b65facd7a62",
328
+ "blob_sha": "sha1",
329
+ "file_path": "greeter.h",
330
+ "kind": "comment",
331
+ "name": "<anonymous>",
332
+ "scope": "",
333
+ "language": "c",
334
+ "content": "/* Header companion for c.c — declares the greeter API.\n *\n * Resolves the `#include \"greeter.h\"` edge and exercises C prototype and\n * type-definition capture (declared, not defined, here). */",
335
+ "line_start": 1,
336
+ "line_end": 4,
337
+ "metadata": {
338
+ "module": "",
339
+ "names": "",
340
+ "dots": "",
341
+ "language_hint": ""
342
+ }
343
+ },
344
+ {
345
+ "id": "ab72e4cd48b2ee81",
346
+ "blob_sha": "sha1",
347
+ "file_path": "greeter.h",
348
+ "kind": "variable",
349
+ "name": "GREETER_H",
350
+ "scope": "",
351
+ "language": "c",
352
+ "content": "#define GREETER_H\n",
353
+ "line_start": 6,
354
+ "line_end": 7,
355
+ "metadata": {
356
+ "module": "",
357
+ "names": "",
358
+ "dots": "",
359
+ "language_hint": ""
360
+ }
361
+ },
362
+ {
363
+ "id": "c1960555fd4c603c",
364
+ "blob_sha": "sha1",
365
+ "file_path": "greeter.h",
366
+ "kind": "variable",
367
+ "name": "MAX_NAME",
368
+ "scope": "",
369
+ "language": "c",
370
+ "content": "#define MAX_NAME 64\n",
371
+ "line_start": 8,
372
+ "line_end": 9,
373
+ "metadata": {
374
+ "module": "",
375
+ "names": "",
376
+ "dots": "",
377
+ "language_hint": ""
378
+ }
379
+ },
380
+ {
381
+ "id": "7d79f841f33998db",
382
+ "blob_sha": "sha1",
383
+ "file_path": "greeter.h",
384
+ "kind": "class",
385
+ "name": "Greeter",
386
+ "scope": "",
387
+ "language": "c",
388
+ "content": "/* A greeter holding a prefix string. */\nstruct Greeter {\n const char *prefix;\n}",
389
+ "line_start": 10,
390
+ "line_end": 13,
391
+ "metadata": {
392
+ "module": "",
393
+ "names": "",
394
+ "dots": "",
395
+ "language_hint": ""
396
+ }
397
+ },
398
+ {
399
+ "id": "b262681e99ffd26f",
400
+ "blob_sha": "sha1",
401
+ "file_path": "greeter.h",
402
+ "kind": "class",
403
+ "name": "Greeter",
404
+ "scope": "",
405
+ "language": "c",
406
+ "content": "typedef struct Greeter Greeter;",
407
+ "line_start": 15,
408
+ "line_end": 15,
409
+ "metadata": {
410
+ "module": "",
411
+ "names": "",
412
+ "dots": "",
413
+ "language_hint": ""
414
+ }
415
+ },
416
+ {
417
+ "id": "8ff9df9b9b12f62c",
418
+ "blob_sha": "sha1",
419
+ "file_path": "greeter.h",
420
+ "kind": "comment",
421
+ "name": "<anonymous>",
422
+ "scope": "",
423
+ "language": "c",
424
+ "content": "/* Build a greeter with the default prefix. */",
425
+ "line_start": 17,
426
+ "line_end": 17,
427
+ "metadata": {
428
+ "module": "",
429
+ "names": "",
430
+ "dots": "",
431
+ "language_hint": ""
432
+ }
433
+ },
434
+ {
435
+ "id": "7232d67445ea06ec",
436
+ "blob_sha": "sha1",
437
+ "file_path": "greeter.h",
438
+ "kind": "comment",
439
+ "name": "<anonymous>",
440
+ "scope": "",
441
+ "language": "c",
442
+ "content": "/* Format a greeting for `name` into `buf`. */",
443
+ "line_start": 20,
444
+ "line_end": 20,
445
+ "metadata": {
446
+ "module": "",
447
+ "names": "",
448
+ "dots": "",
449
+ "language_hint": ""
450
+ }
451
+ }
452
+ ]
@@ -0,0 +1,88 @@
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_c_doxygen_on_function() -> DocstringCase:
15
+ """Canonical Doxygen `/** */` above a function."""
16
+ src = """\
17
+ /** Compute the sum. */
18
+ int add(int a, int b) { return a + b; }
19
+ """
20
+ return "c", src, "add", "Compute the sum"
21
+
22
+
23
+ @case(tags=["documented", "canonical", "exterior_doc"])
24
+ def case_c_doxygen_on_struct() -> DocstringCase:
25
+ """Doxygen above a struct."""
26
+ src = """\
27
+ /** Point in 2D space. */
28
+ struct Point { int x; int y; };
29
+ """
30
+ return "c", src, "Point", "Point in 2D space"
31
+
32
+
33
+ @case(tags=["documented", "edge_case", "exterior_doc"])
34
+ def case_c_multi_line_doxygen() -> DocstringCase:
35
+ r"""Multi-line Doxygen with `\param` / `\return` tags."""
36
+ src = """\
37
+ /**
38
+ * Hash a buffer.
39
+ * \\param data the buffer
40
+ * \\return the hash
41
+ */
42
+ int hash(const char *data) { return 0; }
43
+ """
44
+ return "c", src, "hash", r"\return the hash"
45
+
46
+
47
+ @case(tags=["documented", "unconventional", "exterior_doc"])
48
+ def case_c_line_comment_run() -> DocstringCase:
49
+ """Plain `//` comment run — common in embedded code where
50
+ Doxygen style is heavier than needed.
51
+ """
52
+ src = """\
53
+ // Simple comment.
54
+ // Second line.
55
+ int foo(void) { return 0; }
56
+ """
57
+ return "c", src, "foo", "Simple comment"
58
+
59
+
60
+ @case(tags=["undocumented", "no_docs"])
61
+ def case_c_fn_without_doc() -> DocstringCase:
62
+ src = """\
63
+ int bare(void) { return 0; }
64
+ """
65
+ return "c", src, "bare", "PHANTOM_DOC_TEXT_SHOULD_NEVER_APPEAR"
66
+
67
+
68
+ @case(tags=["undocumented", "boundary_not_attached"])
69
+ def case_c_doc_detached_by_blank_line() -> DocstringCase:
70
+ """Blank line breaks attachment."""
71
+ src = """\
72
+ /** Orphan. */
73
+
74
+ int later(void) { return 0; }
75
+ """
76
+ return "c", src, "later", "Orphan"
77
+
78
+
79
+ @case(tags=["undocumented", "invalid"])
80
+ def case_c_doc_between_two_functions() -> DocstringCase:
81
+ """Comment between two functions belongs to the later one."""
82
+ src = """\
83
+ int first(void) { return 0; }
84
+
85
+ /** Doc for second. */
86
+ int second(void) { return 0; }
87
+ """
88
+ return "c", src, "first", "Doc for second"
@@ -0,0 +1,137 @@
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 MultiImportCase = tuple[str, str, int, list[dict[str, str]]]
10
+ type MixedCase = tuple[str, str, set[str], list[tuple[str, str]]]
11
+
12
+
13
+ @case(tags=["symbol"])
14
+ def case_c_function_basic() -> SymbolCase:
15
+ """int add(int a, int b)."""
16
+ return "c", "int add(int a, int b) { return a + b; }\n", [("function", "add", "")]
17
+
18
+
19
+ @case(tags=["symbol"])
20
+ def case_c_function_void() -> SymbolCase:
21
+ """void do_stuff(void)."""
22
+ return "c", "void do_stuff(void) { }\n", [("function", "do_stuff", "")]
23
+
24
+
25
+ @case(tags=["symbol"])
26
+ def case_c_function_static() -> SymbolCase:
27
+ """static int helper(void)."""
28
+ return "c", "static int helper(void) { return 1; }\n", [("function", "helper", "")]
29
+
30
+
31
+ @case(tags=["symbol"])
32
+ def case_c_multiple_functions() -> SymbolCase:
33
+ """Multiple C functions."""
34
+ src = """\
35
+ int foo(void) { return 0; }
36
+ void bar(void) { }
37
+ """
38
+ return "c", src, [("function", "foo", ""), ("function", "bar", "")]
39
+
40
+
41
+ @case(tags=["symbol"])
42
+ def case_c_struct() -> SymbolCase:
43
+ """struct Node."""
44
+ return "c", "struct Node { int value; };\n", [("class", "Node", "")]
45
+
46
+
47
+ @case(tags=["symbol"])
48
+ def case_c_enum() -> SymbolCase:
49
+ """enum Color."""
50
+ return "c", "enum Color { RED, GREEN, BLUE };\n", [("class", "Color", "")]
51
+
52
+
53
+ @case(tags=["symbol"])
54
+ def case_c_typedef_struct() -> SymbolCase:
55
+ """typedef struct { ... } Point."""
56
+ return "c", "typedef struct { int x; int y; } Point;\n", [("class", "Point", "")]
57
+
58
+
59
+ @case(tags=["symbol"])
60
+ def case_c_no_scope() -> SymbolCase:
61
+ """C functions are never scoped — no classes."""
62
+ src = """\
63
+ struct S { int x; };
64
+ int func(void) { return 0; }
65
+ """
66
+ return "c", src, [("function", "func", "")]
67
+
68
+
69
+ @case(tags=["import"])
70
+ def case_c_include_system() -> ImportCase:
71
+ """#include <stdio.h>."""
72
+ return "c", "#include <stdio.h>\n", {"module": "stdio.h"}
73
+
74
+
75
+ @case(tags=["import"])
76
+ def case_c_include_local() -> ImportCase:
77
+ """#include "mylib.h"."""
78
+ return "c", '#include "mylib.h"\n', {"module": "mylib.h"}
79
+
80
+
81
+ @case(tags=["import"])
82
+ def case_c_include_nested_path() -> ImportCase:
83
+ """#include "utils/helpers.h"."""
84
+ return "c", '#include "utils/helpers.h"\n', {"module": "utils/helpers.h"}
85
+
86
+
87
+ @case(tags=["import"])
88
+ def case_c_include_system_nested() -> ImportCase:
89
+ """#include <sys/types.h>."""
90
+ return "c", "#include <sys/types.h>\n", {"module": "sys/types.h"}
91
+
92
+
93
+ @case(tags=["multi_import"])
94
+ def case_c_multiple_includes() -> MultiImportCase:
95
+ """Two include directives."""
96
+ src = """\
97
+ #include <stdlib.h>
98
+ #include "local.h"
99
+ """
100
+ return "c", src, 2, [{"module": "stdlib.h"}, {"module": "local.h"}]
101
+
102
+
103
+ @case(tags=["mixed"])
104
+ def case_c_full_file() -> MixedCase:
105
+ """Realistic C file with Doxygen comments on every symbol.
106
+
107
+ Expected-kinds tuple unchanged.
108
+ """
109
+ src = """\
110
+ #include <stdio.h>
111
+ #include "utils.h"
112
+
113
+ /** Runtime configuration for the parser. */
114
+ struct Config {
115
+ int timeout;
116
+ int retries;
117
+ };
118
+
119
+ /** Return status for parse operations. */
120
+ enum Status { OK, ERR };
121
+
122
+ /** Parse a config file from disk. */
123
+ int parse_config(const char *path) {
124
+ return 0;
125
+ }
126
+
127
+ /** Release parser-owned resources. */
128
+ static void cleanup(void) {
129
+ }
130
+ """
131
+ return "c", src, {"import", "class", "function"}, []
132
+
133
+
134
+ @case(tags=["symbol"])
135
+ def case_c_global() -> SymbolCase:
136
+ """File-scope global with initialiser."""
137
+ return "c", "int g = 5;\n", [("variable", "g", "")]
@@ -0,0 +1,44 @@
1
+ /* Greeter — format greetings for named recipients.
2
+ *
3
+ * The C plugin extracts functions, function prototypes, struct/union/
4
+ * enum/typedef type definitions (as classes), top-level variables
5
+ * (including pointer-declared globals), function/object-like macros,
6
+ * and #include imports (system and local). */
7
+
8
+ #include <stdio.h>
9
+ #include "greeter.h"
10
+
11
+ #define MAX_NAME 64
12
+ #define SQUARE(x) ((x) * (x))
13
+
14
+ int greeter_count = 0; // trailing comment: its own chunk, not folded
15
+ const char *default_prefix = "Hello";
16
+
17
+ /* Standalone note, separated by blank lines from any definition. */
18
+ /* Second line of the same block. */
19
+
20
+ /* A greeter holding a prefix string. */
21
+ struct Greeter {
22
+ const char *prefix;
23
+ };
24
+
25
+ typedef struct Greeter Greeter;
26
+
27
+ /* A callback invoked for each formatted greeting. */
28
+ typedef void (*GreetCallback)(const char *line);
29
+
30
+ /* A tagged greeting payload. */
31
+ union Payload {
32
+ int code;
33
+ const char *text;
34
+ };
35
+
36
+ enum Locale { LOCALE_EN, LOCALE_FR };
37
+
38
+ /* Build a greeter with the default prefix. */
39
+ Greeter greeter_default(void);
40
+
41
+ /* Format a greeting for `name` into `buf`. */
42
+ int format_greeting(const Greeter *g, const char *name, char *buf, int n) {
43
+ return snprintf(buf, (size_t)n, "%s, %s", g->prefix, name);
44
+ }
@@ -0,0 +1,23 @@
1
+ /* Header companion for c.c — declares the greeter API.
2
+ *
3
+ * Resolves the `#include "greeter.h"` edge and exercises C prototype and
4
+ * type-definition capture (declared, not defined, here). */
5
+ #ifndef GREETER_H
6
+ #define GREETER_H
7
+
8
+ #define MAX_NAME 64
9
+
10
+ /* A greeter holding a prefix string. */
11
+ struct Greeter {
12
+ const char *prefix;
13
+ };
14
+
15
+ typedef struct Greeter Greeter;
16
+
17
+ /* Build a greeter with the default prefix. */
18
+ Greeter greeter_default(void);
19
+
20
+ /* Format a greeting for `name` into `buf`. */
21
+ int format_greeting(const Greeter *g, const char *name, char *buf, int n);
22
+
23
+ #endif
@@ -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,57 @@
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)
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
+ """C sample extraction: the `samples/c/` 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" / "c"
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 "c"
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.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 "c")
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,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: rbtr-lang-c
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-c
8
+ Requires-Python: >=3.13
@@ -0,0 +1,18 @@
1
+ rbtr_lang_c/__init__.py,sha256=W3rb2Hb3BOLyp3wleR_VPsXyJGMQblamkeX7hbqoRlo,33
2
+ rbtr_lang_c/c.scm,sha256=AJ1PhPeS3Uec-wNBY3AcHjURJHhPQoI_4Zs05sdI9Po,1581
3
+ rbtr_lang_c/plugin.py,sha256=a0sSTlDv_7D2InnxHteDBUA0euL_3yTLrg8JPU8-imA,1527
4
+ rbtr_lang_c/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ rbtr_lang_c/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ rbtr_lang_c/tests/__snapshots__/test_samples/test_edges_match_snapshot.json,sha256=l8QD-x3nsCmTkqN6G3hcZZPac-zVk5gQFhKYo7TtfHw,266
7
+ rbtr_lang_c/tests/__snapshots__/test_samples/test_extraction_matches_snapshot.json,sha256=zSFlCZqJ1NVo5C6tI_CXkxagbYDMSjaJJpn-YokLWYs,10319
8
+ rbtr_lang_c/tests/cases_docstrings.py,sha256=-JGathdyZlZURDl6iI5CumMufpZ7Y6SUouQVtMshyRw,2296
9
+ rbtr_lang_c/tests/cases_extraction.py,sha256=1AkfnBFoJR71WY-rc-S81yX8IOMiZJWNx73e7Qc0m7c,3590
10
+ rbtr_lang_c/tests/samples/c/c.c,sha256=XBE6ciJZCeSlFVz6hXuJE0o9CNROGJkn5ORMUtbjVMk,1240
11
+ rbtr_lang_c/tests/samples/c/greeter.h,sha256=KVCbgHbINca9FWposImUiESk1CfE7hiC9f7TqHFn_AE,582
12
+ rbtr_lang_c/tests/test_docstrings.py,sha256=Sr69qUSWgy4Jb_-4dmi9iNzy2dSTX69nOr9P8rE0urU,1746
13
+ rbtr_lang_c/tests/test_extraction.py,sha256=oBQPoZSXfeIZ0EWHR8_-TYASKaso7hiV9s1eZsTPzaA,2651
14
+ rbtr_lang_c/tests/test_samples.py,sha256=PNvepW1j9oBL4pYtD-T4mJt0ACJP36EUDyCNGeoW7sw,2259
15
+ rbtr_lang_c-2026.7.0.dev0.dist-info/WHEEL,sha256=CoDSoyhtC_eO_tlxRYzsTraPv1fPJRXFx91k6ISeAvA,81
16
+ rbtr_lang_c-2026.7.0.dev0.dist-info/entry_points.txt,sha256=9LThxwGT0btaR93-FSKuhnw1Lxlt8DE1qgN2mz-znOw,43
17
+ rbtr_lang_c-2026.7.0.dev0.dist-info/METADATA,sha256=Uy5NlqnJ4tH-vxEHOHeGGP9_8PYo0U_LJ3Zktjenuac,211
18
+ rbtr_lang_c-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
+ c = rbtr_lang_c.plugin:c
3
+