Navegador

navegador / tests / test_new_language_parsers.py
Blame History Raw 1034 lines
1
"""
2
Tests for the 7 new language parsers:
3
KotlinParser, CSharpParser, PHPParser, RubyParser, SwiftParser, CParser, CppParser
4
5
All tree-sitter grammar imports are mocked so no grammars need to be installed.
6
"""
7
8
import tempfile
9
from pathlib import Path
10
from unittest.mock import MagicMock, patch
11
12
import pytest
13
14
from navegador.graph.schema import NodeLabel
15
from navegador.ingestion.parser import LANGUAGE_MAP, RepoIngester
16
17
18
# ── Shared helpers ────────────────────────────────────────────────────────────
19
20
21
class MockNode:
22
_id_counter = 0
23
24
def __init__(
25
self,
26
type_: str,
27
text: bytes = b"",
28
children: list = None,
29
start_byte: int = 0,
30
end_byte: int = 0,
31
start_point: tuple = (0, 0),
32
end_point: tuple = (0, 0),
33
parent=None,
34
):
35
MockNode._id_counter += 1
36
self.id = MockNode._id_counter
37
self.type = type_
38
self.children = children or []
39
self.start_byte = start_byte
40
self.end_byte = end_byte
41
self.start_point = start_point
42
self.end_point = end_point
43
self.parent = parent
44
self._fields: dict = {}
45
for child in self.children:
46
child.parent = self
47
48
def child_by_field_name(self, name: str):
49
return self._fields.get(name)
50
51
def set_field(self, name: str, node):
52
self._fields[name] = node
53
node.parent = self
54
return self
55
56
57
def _text_node(text: bytes, type_: str = "identifier") -> MockNode:
58
return MockNode(type_, text, start_byte=0, end_byte=len(text))
59
60
61
def _make_store():
62
store = MagicMock()
63
store.query.return_value = MagicMock(result_set=[])
64
return store
65
66
67
def _make_mock_tree(root_node: MockNode):
68
tree = MagicMock()
69
tree.root_node = root_node
70
return tree
71
72
73
def _mock_ts_modules(lang_module_name: str):
74
"""Return a patch.dict context that mocks tree_sitter and the given grammar module."""
75
mock_lang_module = MagicMock()
76
mock_ts = MagicMock()
77
return patch.dict("sys.modules", {lang_module_name: mock_lang_module, "tree_sitter": mock_ts})
78
79
80
# ── LANGUAGE_MAP coverage ─────────────────────────────────────────────────────
81
82
83
class TestLanguageMapExtensions:
84
def test_kotlin_kt(self):
85
assert LANGUAGE_MAP[".kt"] == "kotlin"
86
87
def test_kotlin_kts(self):
88
assert LANGUAGE_MAP[".kts"] == "kotlin"
89
90
def test_csharp_cs(self):
91
assert LANGUAGE_MAP[".cs"] == "csharp"
92
93
def test_php(self):
94
assert LANGUAGE_MAP[".php"] == "php"
95
96
def test_ruby_rb(self):
97
assert LANGUAGE_MAP[".rb"] == "ruby"
98
99
def test_swift(self):
100
assert LANGUAGE_MAP[".swift"] == "swift"
101
102
def test_c_c(self):
103
assert LANGUAGE_MAP[".c"] == "c"
104
105
def test_c_h(self):
106
assert LANGUAGE_MAP[".h"] == "c"
107
108
def test_cpp_cpp(self):
109
assert LANGUAGE_MAP[".cpp"] == "cpp"
110
111
def test_cpp_hpp(self):
112
assert LANGUAGE_MAP[".hpp"] == "cpp"
113
114
def test_cpp_cc(self):
115
assert LANGUAGE_MAP[".cc"] == "cpp"
116
117
def test_cpp_cxx(self):
118
assert LANGUAGE_MAP[".cxx"] == "cpp"
119
120
121
# ── _get_parser dispatch ──────────────────────────────────────────────────────
122
123
124
class TestGetParserDispatch:
125
def _make_ingester(self):
126
store = _make_store()
127
ingester = RepoIngester.__new__(RepoIngester)
128
ingester.store = store
129
ingester.redact = False
130
ingester._detector = None
131
ingester._parsers = {}
132
return ingester
133
134
def _test_parser_type(self, language: str, grammar_module: str, parser_cls_name: str):
135
ingester = self._make_ingester()
136
with _mock_ts_modules(grammar_module):
137
# Also need to force re-import of the parser module
138
import sys
139
mod_name = f"navegador.ingestion.{language}"
140
if mod_name in sys.modules:
141
del sys.modules[mod_name]
142
parser = ingester._get_parser(language)
143
assert type(parser).__name__ == parser_cls_name
144
145
def test_kotlin_parser(self):
146
self._test_parser_type("kotlin", "tree_sitter_kotlin", "KotlinParser")
147
148
def test_csharp_parser(self):
149
self._test_parser_type("csharp", "tree_sitter_c_sharp", "CSharpParser")
150
151
def test_php_parser(self):
152
self._test_parser_type("php", "tree_sitter_php", "PHPParser")
153
154
def test_ruby_parser(self):
155
self._test_parser_type("ruby", "tree_sitter_ruby", "RubyParser")
156
157
def test_swift_parser(self):
158
self._test_parser_type("swift", "tree_sitter_swift", "SwiftParser")
159
160
def test_c_parser(self):
161
self._test_parser_type("c", "tree_sitter_c", "CParser")
162
163
def test_cpp_parser(self):
164
self._test_parser_type("cpp", "tree_sitter_cpp", "CppParser")
165
166
167
# ── _get_*_language ImportError ────────────────────────────────────────────────
168
169
170
class TestMissingGrammars:
171
def _assert_import_error(self, module_path: str, fn_name: str, grammar_pkg: str, grammar_module: str):
172
import importlib
173
import sys
174
# Remove cached module if present
175
if module_path in sys.modules:
176
del sys.modules[module_path]
177
with patch.dict("sys.modules", {grammar_module: None, "tree_sitter": None}):
178
mod = importlib.import_module(module_path)
179
fn = getattr(mod, fn_name)
180
with pytest.raises(ImportError, match=grammar_pkg):
181
fn()
182
183
def test_kotlin_missing(self):
184
self._assert_import_error(
185
"navegador.ingestion.kotlin", "_get_kotlin_language",
186
"tree-sitter-kotlin", "tree_sitter_kotlin",
187
)
188
189
def test_csharp_missing(self):
190
self._assert_import_error(
191
"navegador.ingestion.csharp", "_get_csharp_language",
192
"tree-sitter-c-sharp", "tree_sitter_c_sharp",
193
)
194
195
def test_php_missing(self):
196
self._assert_import_error(
197
"navegador.ingestion.php", "_get_php_language",
198
"tree-sitter-php", "tree_sitter_php",
199
)
200
201
def test_ruby_missing(self):
202
self._assert_import_error(
203
"navegador.ingestion.ruby", "_get_ruby_language",
204
"tree-sitter-ruby", "tree_sitter_ruby",
205
)
206
207
def test_swift_missing(self):
208
self._assert_import_error(
209
"navegador.ingestion.swift", "_get_swift_language",
210
"tree-sitter-swift", "tree_sitter_swift",
211
)
212
213
def test_c_missing(self):
214
self._assert_import_error(
215
"navegador.ingestion.c", "_get_c_language",
216
"tree-sitter-c", "tree_sitter_c",
217
)
218
219
def test_cpp_missing(self):
220
self._assert_import_error(
221
"navegador.ingestion.cpp", "_get_cpp_language",
222
"tree-sitter-cpp", "tree_sitter_cpp",
223
)
224
225
226
# ── KotlinParser ──────────────────────────────────────────────────────────────
227
228
229
def _make_kotlin_parser():
230
from navegador.ingestion.kotlin import KotlinParser
231
p = KotlinParser.__new__(KotlinParser)
232
p._parser = MagicMock()
233
return p
234
235
236
class TestKotlinParserFileNode:
237
def test_parse_file_creates_file_node(self):
238
parser = _make_kotlin_parser()
239
store = _make_store()
240
root = MockNode("source_file")
241
parser._parser.parse.return_value = _make_mock_tree(root)
242
with tempfile.NamedTemporaryFile(suffix=".kt", delete=False) as f:
243
f.write(b"fun main() {}\n")
244
fpath = Path(f.name)
245
try:
246
parser.parse_file(fpath, fpath.parent, store)
247
assert store.create_node.call_args[0][0] == NodeLabel.File
248
assert store.create_node.call_args[0][1]["language"] == "kotlin"
249
finally:
250
fpath.unlink()
251
252
253
class TestKotlinHandleClass:
254
def test_creates_class_node(self):
255
parser = _make_kotlin_parser()
256
store = _make_store()
257
source = b"class Foo {}"
258
name_node = _text_node(b"Foo", "simple_identifier")
259
body = MockNode("class_body")
260
node = MockNode("class_declaration", start_point=(0, 0), end_point=(0, 11))
261
node.set_field("name", name_node)
262
node.set_field("body", body)
263
stats = {"functions": 0, "classes": 0, "edges": 0}
264
parser._handle_class(node, source, "Foo.kt", store, stats)
265
assert stats["classes"] == 1
266
assert store.create_node.call_args[0][0] == NodeLabel.Class
267
268
def test_skips_if_no_name(self):
269
parser = _make_kotlin_parser()
270
store = _make_store()
271
node = MockNode("class_declaration")
272
stats = {"functions": 0, "classes": 0, "edges": 0}
273
parser._handle_class(node, b"", "Foo.kt", store, stats)
274
assert stats["classes"] == 0
275
276
def test_walks_member_functions(self):
277
parser = _make_kotlin_parser()
278
store = _make_store()
279
source = b"class Foo { fun bar() {} }"
280
class_name = _text_node(b"Foo", "simple_identifier")
281
fn_name = _text_node(b"bar", "simple_identifier")
282
fn_node = MockNode("function_declaration", start_point=(0, 12), end_point=(0, 24))
283
fn_node.set_field("name", fn_name)
284
body = MockNode("class_body", children=[fn_node])
285
node = MockNode("class_declaration", start_point=(0, 0), end_point=(0, 25))
286
node.set_field("name", class_name)
287
node.set_field("body", body)
288
stats = {"functions": 0, "classes": 0, "edges": 0}
289
parser._handle_class(node, source, "Foo.kt", store, stats)
290
assert stats["classes"] == 1
291
assert stats["functions"] == 1
292
293
294
class TestKotlinHandleFunction:
295
def test_creates_function_node(self):
296
parser = _make_kotlin_parser()
297
store = _make_store()
298
source = b"fun greet() {}"
299
name_node = _text_node(b"greet", "simple_identifier")
300
node = MockNode("function_declaration", start_point=(0, 0), end_point=(0, 13))
301
node.set_field("name", name_node)
302
stats = {"functions": 0, "classes": 0, "edges": 0}
303
parser._handle_function(node, source, "Foo.kt", store, stats, class_name=None)
304
assert stats["functions"] == 1
305
assert store.create_node.call_args[0][0] == NodeLabel.Function
306
307
def test_creates_method_node_in_class(self):
308
parser = _make_kotlin_parser()
309
store = _make_store()
310
source = b"fun run() {}"
311
name_node = _text_node(b"run", "simple_identifier")
312
node = MockNode("function_declaration", start_point=(0, 0), end_point=(0, 11))
313
node.set_field("name", name_node)
314
stats = {"functions": 0, "classes": 0, "edges": 0}
315
parser._handle_function(node, source, "Foo.kt", store, stats, class_name="Foo")
316
assert store.create_node.call_args[0][0] == NodeLabel.Method
317
318
def test_skips_if_no_name(self):
319
parser = _make_kotlin_parser()
320
store = _make_store()
321
node = MockNode("function_declaration")
322
stats = {"functions": 0, "classes": 0, "edges": 0}
323
parser._handle_function(node, b"", "Foo.kt", store, stats, class_name=None)
324
assert stats["functions"] == 0
325
326
327
class TestKotlinHandleImport:
328
def test_creates_import_node(self):
329
parser = _make_kotlin_parser()
330
store = _make_store()
331
source = b"import kotlin.collections.List"
332
node = MockNode("import_header", start_byte=0, end_byte=len(source), start_point=(0, 0))
333
stats = {"functions": 0, "classes": 0, "edges": 0}
334
parser._handle_import(node, source, "Foo.kt", store, stats)
335
assert stats["edges"] == 1
336
props = store.create_node.call_args[0][1]
337
assert "kotlin.collections.List" in props["name"]
338
339
340
# ── CSharpParser ───────────────────────────────────────────────────────────────
341
342
343
def _make_csharp_parser():
344
from navegador.ingestion.csharp import CSharpParser
345
p = CSharpParser.__new__(CSharpParser)
346
p._parser = MagicMock()
347
return p
348
349
350
class TestCSharpParserFileNode:
351
def test_parse_file_creates_file_node(self):
352
parser = _make_csharp_parser()
353
store = _make_store()
354
root = MockNode("compilation_unit")
355
parser._parser.parse.return_value = _make_mock_tree(root)
356
with tempfile.NamedTemporaryFile(suffix=".cs", delete=False) as f:
357
f.write(b"class Foo {}\n")
358
fpath = Path(f.name)
359
try:
360
parser.parse_file(fpath, fpath.parent, store)
361
assert store.create_node.call_args[0][0] == NodeLabel.File
362
assert store.create_node.call_args[0][1]["language"] == "csharp"
363
finally:
364
fpath.unlink()
365
366
367
class TestCSharpHandleClass:
368
def test_creates_class_node(self):
369
parser = _make_csharp_parser()
370
store = _make_store()
371
source = b"class Foo {}"
372
name_node = _text_node(b"Foo")
373
body = MockNode("declaration_list")
374
node = MockNode("class_declaration", start_point=(0, 0), end_point=(0, 11))
375
node.set_field("name", name_node)
376
node.set_field("body", body)
377
stats = {"functions": 0, "classes": 0, "edges": 0}
378
parser._handle_class(node, source, "Foo.cs", store, stats)
379
assert stats["classes"] == 1
380
assert store.create_node.call_args[0][0] == NodeLabel.Class
381
382
def test_skips_if_no_name(self):
383
parser = _make_csharp_parser()
384
store = _make_store()
385
node = MockNode("class_declaration")
386
stats = {"functions": 0, "classes": 0, "edges": 0}
387
parser._handle_class(node, b"", "Foo.cs", store, stats)
388
assert stats["classes"] == 0
389
390
def test_creates_inherits_edge(self):
391
parser = _make_csharp_parser()
392
store = _make_store()
393
source = b"class Child : Parent {}"
394
name_node = _text_node(b"Child")
395
parent_id = _text_node(b"Parent")
396
bases = MockNode("base_list", children=[parent_id])
397
body = MockNode("declaration_list")
398
node = MockNode("class_declaration", start_point=(0, 0), end_point=(0, 22))
399
node.set_field("name", name_node)
400
node.set_field("bases", bases)
401
node.set_field("body", body)
402
stats = {"functions": 0, "classes": 0, "edges": 0}
403
parser._handle_class(node, source, "Child.cs", store, stats)
404
assert stats["edges"] == 2 # CONTAINS + INHERITS
405
406
def test_walks_methods(self):
407
parser = _make_csharp_parser()
408
store = _make_store()
409
source = b"class Foo { void Save() {} }"
410
class_name_node = _text_node(b"Foo")
411
method_name_node = _text_node(b"Save")
412
method = MockNode("method_declaration", start_point=(0, 12), end_point=(0, 25))
413
method.set_field("name", method_name_node)
414
body = MockNode("declaration_list", children=[method])
415
node = MockNode("class_declaration", start_point=(0, 0), end_point=(0, 27))
416
node.set_field("name", class_name_node)
417
node.set_field("body", body)
418
stats = {"functions": 0, "classes": 0, "edges": 0}
419
parser._handle_class(node, source, "Foo.cs", store, stats)
420
assert stats["functions"] == 1
421
422
423
class TestCSharpHandleUsing:
424
def test_creates_import_node(self):
425
parser = _make_csharp_parser()
426
store = _make_store()
427
source = b"using System.Collections.Generic;"
428
node = MockNode("using_directive", start_byte=0, end_byte=len(source), start_point=(0, 0))
429
stats = {"functions": 0, "classes": 0, "edges": 0}
430
parser._handle_using(node, source, "Foo.cs", store, stats)
431
assert stats["edges"] == 1
432
props = store.create_node.call_args[0][1]
433
assert "System.Collections.Generic" in props["name"]
434
435
436
# ── PHPParser ─────────────────────────────────────────────────────────────────
437
438
439
def _make_php_parser():
440
from navegador.ingestion.php import PHPParser
441
p = PHPParser.__new__(PHPParser)
442
p._parser = MagicMock()
443
return p
444
445
446
class TestPHPParserFileNode:
447
def test_parse_file_creates_file_node(self):
448
parser = _make_php_parser()
449
store = _make_store()
450
root = MockNode("program")
451
parser._parser.parse.return_value = _make_mock_tree(root)
452
with tempfile.NamedTemporaryFile(suffix=".php", delete=False) as f:
453
f.write(b"<?php class Foo {} ?>\n")
454
fpath = Path(f.name)
455
try:
456
parser.parse_file(fpath, fpath.parent, store)
457
assert store.create_node.call_args[0][0] == NodeLabel.File
458
assert store.create_node.call_args[0][1]["language"] == "php"
459
finally:
460
fpath.unlink()
461
462
463
class TestPHPHandleClass:
464
def test_creates_class_node(self):
465
parser = _make_php_parser()
466
store = _make_store()
467
source = b"class Foo {}"
468
name_node = _text_node(b"Foo", "name")
469
body = MockNode("declaration_list")
470
node = MockNode("class_declaration", start_point=(0, 0), end_point=(0, 11))
471
node.set_field("name", name_node)
472
node.set_field("body", body)
473
stats = {"functions": 0, "classes": 0, "edges": 0}
474
parser._handle_class(node, source, "Foo.php", store, stats)
475
assert stats["classes"] == 1
476
477
def test_skips_if_no_name(self):
478
parser = _make_php_parser()
479
store = _make_store()
480
node = MockNode("class_declaration")
481
stats = {"functions": 0, "classes": 0, "edges": 0}
482
parser._handle_class(node, b"", "Foo.php", store, stats)
483
assert stats["classes"] == 0
484
485
def test_creates_inherits_edge(self):
486
parser = _make_php_parser()
487
store = _make_store()
488
source = b"class Child extends Parent {}"
489
name_node = _text_node(b"Child", "name")
490
parent_name_node = _text_node(b"Parent", "qualified_name")
491
base_clause = MockNode("base_clause", children=[parent_name_node])
492
body = MockNode("declaration_list")
493
node = MockNode("class_declaration", start_point=(0, 0), end_point=(0, 28))
494
node.set_field("name", name_node)
495
node.set_field("base_clause", base_clause)
496
node.set_field("body", body)
497
stats = {"functions": 0, "classes": 0, "edges": 0}
498
parser._handle_class(node, source, "Child.php", store, stats)
499
assert stats["edges"] == 2 # CONTAINS + INHERITS
500
501
502
class TestPHPHandleFunction:
503
def test_creates_function_node(self):
504
parser = _make_php_parser()
505
store = _make_store()
506
source = b"function save() {}"
507
name_node = _text_node(b"save", "name")
508
node = MockNode("function_definition", start_point=(0, 0), end_point=(0, 17))
509
node.set_field("name", name_node)
510
stats = {"functions": 0, "classes": 0, "edges": 0}
511
parser._handle_function(node, source, "Foo.php", store, stats, class_name=None)
512
assert stats["functions"] == 1
513
assert store.create_node.call_args[0][0] == NodeLabel.Function
514
515
def test_skips_if_no_name(self):
516
parser = _make_php_parser()
517
store = _make_store()
518
node = MockNode("function_definition")
519
stats = {"functions": 0, "classes": 0, "edges": 0}
520
parser._handle_function(node, b"", "Foo.php", store, stats, class_name=None)
521
assert stats["functions"] == 0
522
523
524
class TestPHPHandleUse:
525
def test_creates_import_node(self):
526
parser = _make_php_parser()
527
store = _make_store()
528
source = b"use App\\Http\\Controllers\\Controller;"
529
node = MockNode("use_declaration", start_byte=0, end_byte=len(source), start_point=(0, 0))
530
stats = {"functions": 0, "classes": 0, "edges": 0}
531
parser._handle_use(node, source, "Foo.php", store, stats)
532
assert stats["edges"] == 1
533
props = store.create_node.call_args[0][1]
534
assert "Controller" in props["name"]
535
536
537
# ── RubyParser ────────────────────────────────────────────────────────────────
538
539
540
def _make_ruby_parser():
541
from navegador.ingestion.ruby import RubyParser
542
p = RubyParser.__new__(RubyParser)
543
p._parser = MagicMock()
544
return p
545
546
547
class TestRubyParserFileNode:
548
def test_parse_file_creates_file_node(self):
549
parser = _make_ruby_parser()
550
store = _make_store()
551
root = MockNode("program")
552
parser._parser.parse.return_value = _make_mock_tree(root)
553
with tempfile.NamedTemporaryFile(suffix=".rb", delete=False) as f:
554
f.write(b"class Foo; end\n")
555
fpath = Path(f.name)
556
try:
557
parser.parse_file(fpath, fpath.parent, store)
558
assert store.create_node.call_args[0][0] == NodeLabel.File
559
assert store.create_node.call_args[0][1]["language"] == "ruby"
560
finally:
561
fpath.unlink()
562
563
564
class TestRubyHandleClass:
565
def test_creates_class_node(self):
566
parser = _make_ruby_parser()
567
store = _make_store()
568
source = b"class Foo; end"
569
name_node = _text_node(b"Foo", "constant")
570
body = MockNode("body_statement")
571
node = MockNode("class", start_point=(0, 0), end_point=(0, 13))
572
node.set_field("name", name_node)
573
node.set_field("body", body)
574
stats = {"functions": 0, "classes": 0, "edges": 0}
575
parser._handle_class(node, source, "foo.rb", store, stats)
576
assert stats["classes"] == 1
577
578
def test_skips_if_no_name(self):
579
parser = _make_ruby_parser()
580
store = _make_store()
581
node = MockNode("class")
582
stats = {"functions": 0, "classes": 0, "edges": 0}
583
parser._handle_class(node, b"", "foo.rb", store, stats)
584
assert stats["classes"] == 0
585
586
def test_creates_inherits_edge(self):
587
parser = _make_ruby_parser()
588
store = _make_store()
589
source = b"class Child < Parent; end"
590
name_node = _text_node(b"Child", "constant")
591
superclass_node = _text_node(b"Parent", "constant")
592
body = MockNode("body_statement")
593
node = MockNode("class", start_point=(0, 0), end_point=(0, 24))
594
node.set_field("name", name_node)
595
node.set_field("superclass", superclass_node)
596
node.set_field("body", body)
597
stats = {"functions": 0, "classes": 0, "edges": 0}
598
parser._handle_class(node, source, "child.rb", store, stats)
599
assert stats["edges"] >= 2 # CONTAINS + INHERITS
600
601
def test_walks_body_methods(self):
602
parser = _make_ruby_parser()
603
store = _make_store()
604
source = b"class Foo; def run; end; end"
605
class_name_node = _text_node(b"Foo", "constant")
606
method_name_node = _text_node(b"run")
607
method_node = MockNode("method", start_point=(0, 11), end_point=(0, 22))
608
method_node.set_field("name", method_name_node)
609
body = MockNode("body_statement", children=[method_node])
610
node = MockNode("class", start_point=(0, 0), end_point=(0, 26))
611
node.set_field("name", class_name_node)
612
node.set_field("body", body)
613
stats = {"functions": 0, "classes": 0, "edges": 0}
614
parser._handle_class(node, source, "foo.rb", store, stats)
615
assert stats["functions"] == 1
616
617
618
class TestRubyHandleMethod:
619
def test_creates_function_node(self):
620
parser = _make_ruby_parser()
621
store = _make_store()
622
source = b"def run; end"
623
name_node = _text_node(b"run")
624
node = MockNode("method", start_point=(0, 0), end_point=(0, 11))
625
node.set_field("name", name_node)
626
stats = {"functions": 0, "classes": 0, "edges": 0}
627
parser._handle_method(node, source, "foo.rb", store, stats, class_name=None)
628
assert stats["functions"] == 1
629
assert store.create_node.call_args[0][0] == NodeLabel.Function
630
631
def test_creates_method_node_in_class(self):
632
parser = _make_ruby_parser()
633
store = _make_store()
634
source = b"def run; end"
635
name_node = _text_node(b"run")
636
node = MockNode("method", start_point=(0, 0), end_point=(0, 11))
637
node.set_field("name", name_node)
638
stats = {"functions": 0, "classes": 0, "edges": 0}
639
parser._handle_method(node, source, "foo.rb", store, stats, class_name="Foo")
640
assert store.create_node.call_args[0][0] == NodeLabel.Method
641
642
def test_skips_if_no_name(self):
643
parser = _make_ruby_parser()
644
store = _make_store()
645
node = MockNode("method")
646
stats = {"functions": 0, "classes": 0, "edges": 0}
647
parser._handle_method(node, b"", "foo.rb", store, stats, class_name=None)
648
assert stats["functions"] == 0
649
650
651
class TestRubyHandleModule:
652
def test_creates_module_node(self):
653
parser = _make_ruby_parser()
654
store = _make_store()
655
source = b"module Concerns; end"
656
name_node = _text_node(b"Concerns", "constant")
657
body = MockNode("body_statement")
658
node = MockNode("module", start_point=(0, 0), end_point=(0, 19))
659
node.set_field("name", name_node)
660
node.set_field("body", body)
661
stats = {"functions": 0, "classes": 0, "edges": 0}
662
parser._handle_module(node, source, "concerns.rb", store, stats)
663
assert stats["classes"] == 1
664
props = store.create_node.call_args[0][1]
665
assert props.get("docstring") == "module"
666
667
668
# ── SwiftParser ───────────────────────────────────────────────────────────────
669
670
671
def _make_swift_parser():
672
from navegador.ingestion.swift import SwiftParser
673
p = SwiftParser.__new__(SwiftParser)
674
p._parser = MagicMock()
675
return p
676
677
678
class TestSwiftParserFileNode:
679
def test_parse_file_creates_file_node(self):
680
parser = _make_swift_parser()
681
store = _make_store()
682
root = MockNode("source_file")
683
parser._parser.parse.return_value = _make_mock_tree(root)
684
with tempfile.NamedTemporaryFile(suffix=".swift", delete=False) as f:
685
f.write(b"class Foo {}\n")
686
fpath = Path(f.name)
687
try:
688
parser.parse_file(fpath, fpath.parent, store)
689
assert store.create_node.call_args[0][0] == NodeLabel.File
690
assert store.create_node.call_args[0][1]["language"] == "swift"
691
finally:
692
fpath.unlink()
693
694
695
class TestSwiftHandleClass:
696
def test_creates_class_node(self):
697
parser = _make_swift_parser()
698
store = _make_store()
699
source = b"class Foo {}"
700
name_node = _text_node(b"Foo", "type_identifier")
701
body = MockNode("class_body")
702
node = MockNode("class_declaration", start_point=(0, 0), end_point=(0, 11))
703
node.set_field("name", name_node)
704
node.set_field("body", body)
705
stats = {"functions": 0, "classes": 0, "edges": 0}
706
parser._handle_class(node, source, "Foo.swift", store, stats)
707
assert stats["classes"] == 1
708
709
def test_skips_if_no_name(self):
710
parser = _make_swift_parser()
711
store = _make_store()
712
node = MockNode("class_declaration")
713
stats = {"functions": 0, "classes": 0, "edges": 0}
714
parser._handle_class(node, b"", "Foo.swift", store, stats)
715
assert stats["classes"] == 0
716
717
def test_creates_inherits_edge(self):
718
parser = _make_swift_parser()
719
store = _make_store()
720
source = b"class Child: Parent {}"
721
name_node = _text_node(b"Child", "type_identifier")
722
parent_id = _text_node(b"Parent", "type_identifier")
723
inheritance = MockNode("type_inheritance_clause", children=[parent_id])
724
body = MockNode("class_body")
725
node = MockNode("class_declaration", start_point=(0, 0), end_point=(0, 21))
726
node.set_field("name", name_node)
727
node.set_field("type_inheritance_clause", inheritance)
728
node.set_field("body", body)
729
stats = {"functions": 0, "classes": 0, "edges": 0}
730
parser._handle_class(node, source, "Child.swift", store, stats)
731
assert stats["edges"] == 2 # CONTAINS + INHERITS
732
733
def test_walks_body_functions(self):
734
parser = _make_swift_parser()
735
store = _make_store()
736
source = b"class Foo { func run() {} }"
737
class_name = _text_node(b"Foo", "type_identifier")
738
fn_name = _text_node(b"run", "simple_identifier")
739
fn_node = MockNode("function_declaration", start_point=(0, 12), end_point=(0, 24))
740
fn_node.set_field("name", fn_name)
741
body = MockNode("class_body", children=[fn_node])
742
node = MockNode("class_declaration", start_point=(0, 0), end_point=(0, 26))
743
node.set_field("name", class_name)
744
node.set_field("body", body)
745
stats = {"functions": 0, "classes": 0, "edges": 0}
746
parser._handle_class(node, source, "Foo.swift", store, stats)
747
assert stats["functions"] == 1
748
749
750
class TestSwiftHandleImport:
751
def test_creates_import_node(self):
752
parser = _make_swift_parser()
753
store = _make_store()
754
source = b"import Foundation"
755
node = MockNode("import_declaration", start_byte=0, end_byte=len(source), start_point=(0, 0))
756
stats = {"functions": 0, "classes": 0, "edges": 0}
757
parser._handle_import(node, source, "Foo.swift", store, stats)
758
assert stats["edges"] == 1
759
props = store.create_node.call_args[0][1]
760
assert "Foundation" in props["name"]
761
762
763
# ── CParser ───────────────────────────────────────────────────────────────────
764
765
766
def _make_c_parser():
767
from navegador.ingestion.c import CParser
768
p = CParser.__new__(CParser)
769
p._parser = MagicMock()
770
return p
771
772
773
class TestCParserFileNode:
774
def test_parse_file_creates_file_node(self):
775
parser = _make_c_parser()
776
store = _make_store()
777
root = MockNode("translation_unit")
778
parser._parser.parse.return_value = _make_mock_tree(root)
779
with tempfile.NamedTemporaryFile(suffix=".c", delete=False) as f:
780
f.write(b"void foo() {}\n")
781
fpath = Path(f.name)
782
try:
783
parser.parse_file(fpath, fpath.parent, store)
784
assert store.create_node.call_args[0][0] == NodeLabel.File
785
assert store.create_node.call_args[0][1]["language"] == "c"
786
finally:
787
fpath.unlink()
788
789
790
class TestCHandleFunction:
791
def _make_function_node(self, fn_name: bytes) -> tuple[MockNode, bytes]:
792
source = fn_name + b"(void) {}"
793
fn_id = _text_node(fn_name)
794
fn_decl = MockNode("function_declarator")
795
fn_decl.set_field("declarator", fn_id)
796
body = MockNode("compound_statement")
797
node = MockNode("function_definition", start_point=(0, 0), end_point=(0, len(source)))
798
node.set_field("declarator", fn_decl)
799
node.set_field("body", body)
800
return node, source
801
802
def test_creates_function_node(self):
803
parser = _make_c_parser()
804
store = _make_store()
805
node, source = self._make_function_node(b"main")
806
stats = {"functions": 0, "classes": 0, "edges": 0}
807
parser._handle_function(node, source, "main.c", store, stats)
808
assert stats["functions"] == 1
809
assert store.create_node.call_args[0][0] == NodeLabel.Function
810
811
def test_skips_if_no_name(self):
812
parser = _make_c_parser()
813
store = _make_store()
814
node = MockNode("function_definition")
815
stats = {"functions": 0, "classes": 0, "edges": 0}
816
parser._handle_function(node, b"", "main.c", store, stats)
817
assert stats["functions"] == 0
818
819
820
class TestCHandleStruct:
821
def test_creates_struct_node(self):
822
parser = _make_c_parser()
823
store = _make_store()
824
source = b"struct Point { int x; int y; };"
825
name_node = _text_node(b"Point", "type_identifier")
826
node = MockNode("struct_specifier", start_point=(0, 0), end_point=(0, 30))
827
node.set_field("name", name_node)
828
stats = {"functions": 0, "classes": 0, "edges": 0}
829
parser._handle_struct(node, source, "point.c", store, stats)
830
assert stats["classes"] == 1
831
props = store.create_node.call_args[0][1]
832
assert props["docstring"] == "struct"
833
834
def test_skips_if_no_name(self):
835
parser = _make_c_parser()
836
store = _make_store()
837
node = MockNode("struct_specifier")
838
stats = {"functions": 0, "classes": 0, "edges": 0}
839
parser._handle_struct(node, b"", "point.c", store, stats)
840
assert stats["classes"] == 0
841
842
843
class TestCHandleInclude:
844
def test_creates_import_node_angle_bracket(self):
845
parser = _make_c_parser()
846
store = _make_store()
847
source = b"#include <stdio.h>"
848
path_node = MockNode("system_lib_string", start_byte=9, end_byte=18)
849
node = MockNode("preproc_include", start_byte=0, end_byte=18, start_point=(0, 0))
850
node.set_field("path", path_node)
851
stats = {"functions": 0, "classes": 0, "edges": 0}
852
parser._handle_include(node, source, "main.c", store, stats)
853
assert stats["edges"] == 1
854
props = store.create_node.call_args[0][1]
855
assert "stdio.h" in props["name"]
856
857
def test_creates_import_node_quoted(self):
858
parser = _make_c_parser()
859
store = _make_store()
860
source = b'#include "utils.h"'
861
path_node = MockNode("string_literal", start_byte=9, end_byte=18)
862
node = MockNode("preproc_include", start_byte=0, end_byte=18, start_point=(0, 0))
863
node.set_field("path", path_node)
864
stats = {"functions": 0, "classes": 0, "edges": 0}
865
parser._handle_include(node, source, "main.c", store, stats)
866
assert stats["edges"] == 1
867
props = store.create_node.call_args[0][1]
868
assert "utils.h" in props["name"]
869
870
871
# ── CppParser ─────────────────────────────────────────────────────────────────
872
873
874
def _make_cpp_parser():
875
from navegador.ingestion.cpp import CppParser
876
p = CppParser.__new__(CppParser)
877
p._parser = MagicMock()
878
return p
879
880
881
class TestCppParserFileNode:
882
def test_parse_file_creates_file_node(self):
883
parser = _make_cpp_parser()
884
store = _make_store()
885
root = MockNode("translation_unit")
886
parser._parser.parse.return_value = _make_mock_tree(root)
887
with tempfile.NamedTemporaryFile(suffix=".cpp", delete=False) as f:
888
f.write(b"class Foo {};\n")
889
fpath = Path(f.name)
890
try:
891
parser.parse_file(fpath, fpath.parent, store)
892
assert store.create_node.call_args[0][0] == NodeLabel.File
893
assert store.create_node.call_args[0][1]["language"] == "cpp"
894
finally:
895
fpath.unlink()
896
897
898
class TestCppHandleClass:
899
def test_creates_class_node(self):
900
parser = _make_cpp_parser()
901
store = _make_store()
902
source = b"class Foo {};"
903
name_node = _text_node(b"Foo", "type_identifier")
904
body = MockNode("field_declaration_list")
905
node = MockNode("class_specifier", start_point=(0, 0), end_point=(0, 12))
906
node.set_field("name", name_node)
907
node.set_field("body", body)
908
stats = {"functions": 0, "classes": 0, "edges": 0}
909
parser._handle_class(node, source, "Foo.cpp", store, stats)
910
assert stats["classes"] == 1
911
912
def test_skips_if_no_name(self):
913
parser = _make_cpp_parser()
914
store = _make_store()
915
node = MockNode("class_specifier")
916
stats = {"functions": 0, "classes": 0, "edges": 0}
917
parser._handle_class(node, b"", "Foo.cpp", store, stats)
918
assert stats["classes"] == 0
919
920
def test_creates_inherits_edge(self):
921
parser = _make_cpp_parser()
922
store = _make_store()
923
source = b"class Child : public Parent {};"
924
name_node = _text_node(b"Child", "type_identifier")
925
parent_id = _text_node(b"Parent", "type_identifier")
926
base_clause = MockNode("base_class_clause", children=[parent_id])
927
body = MockNode("field_declaration_list")
928
node = MockNode("class_specifier", start_point=(0, 0), end_point=(0, 30))
929
node.set_field("name", name_node)
930
node.set_field("base_clause", base_clause)
931
node.set_field("body", body)
932
stats = {"functions": 0, "classes": 0, "edges": 0}
933
parser._handle_class(node, source, "Child.cpp", store, stats)
934
assert stats["edges"] == 2 # CONTAINS + INHERITS
935
936
def test_walks_member_functions(self):
937
parser = _make_cpp_parser()
938
store = _make_store()
939
source = b"class Foo { void run() {} };"
940
class_name = _text_node(b"Foo", "type_identifier")
941
fn_id = _text_node(b"run")
942
fn_decl = MockNode("function_declarator")
943
fn_decl.set_field("declarator", fn_id)
944
fn_body = MockNode("compound_statement")
945
fn_node = MockNode("function_definition", start_point=(0, 12), end_point=(0, 24))
946
fn_node.set_field("declarator", fn_decl)
947
fn_node.set_field("body", fn_body)
948
body = MockNode("field_declaration_list", children=[fn_node])
949
node = MockNode("class_specifier", start_point=(0, 0), end_point=(0, 27))
950
node.set_field("name", class_name)
951
node.set_field("body", body)
952
stats = {"functions": 0, "classes": 0, "edges": 0}
953
parser._handle_class(node, source, "Foo.cpp", store, stats)
954
assert stats["functions"] == 1
955
956
957
class TestCppHandleFunction:
958
def test_creates_function_node(self):
959
parser = _make_cpp_parser()
960
store = _make_store()
961
source = b"void main() {}"
962
fn_id = _text_node(b"main")
963
fn_decl = MockNode("function_declarator")
964
fn_decl.set_field("declarator", fn_id)
965
body = MockNode("compound_statement")
966
node = MockNode("function_definition", start_point=(0, 0), end_point=(0, 13))
967
node.set_field("declarator", fn_decl)
968
node.set_field("body", body)
969
stats = {"functions": 0, "classes": 0, "edges": 0}
970
parser._handle_function(node, source, "main.cpp", store, stats, class_name=None)
971
assert stats["functions"] == 1
972
assert store.create_node.call_args[0][0] == NodeLabel.Function
973
974
def test_creates_method_node_in_class(self):
975
parser = _make_cpp_parser()
976
store = _make_store()
977
source = b"void run() {}"
978
fn_id = _text_node(b"run")
979
fn_decl = MockNode("function_declarator")
980
fn_decl.set_field("declarator", fn_id)
981
body = MockNode("compound_statement")
982
node = MockNode("function_definition", start_point=(0, 0), end_point=(0, 12))
983
node.set_field("declarator", fn_decl)
984
node.set_field("body", body)
985
stats = {"functions": 0, "classes": 0, "edges": 0}
986
parser._handle_function(node, source, "Foo.cpp", store, stats, class_name="Foo")
987
assert store.create_node.call_args[0][0] == NodeLabel.Method
988
989
def test_skips_if_no_name(self):
990
parser = _make_cpp_parser()
991
store = _make_store()
992
node = MockNode("function_definition")
993
stats = {"functions": 0, "classes": 0, "edges": 0}
994
parser._handle_function(node, b"", "main.cpp", store, stats, class_name=None)
995
assert stats["functions"] == 0
996
997
998
class TestCppHandleInclude:
999
def test_creates_import_node(self):
1000
parser = _make_cpp_parser()
1001
store = _make_store()
1002
source = b"#include <vector>"
1003
path_node = MockNode("system_lib_string", start_byte=9, end_byte=17)
1004
node = MockNode("preproc_include", start_byte=0, end_byte=17, start_point=(0, 0))
1005
node.set_field("path", path_node)
1006
stats = {"functions": 0, "classes": 0, "edges": 0}
1007
parser._handle_include(node, source, "Foo.cpp", store, stats)
1008
assert stats["edges"] == 1
1009
props = store.create_node.call_args[0][1]
1010
assert "vector" in props["name"]
1011
1012
1013
class TestCppExtractFunctionName:
1014
def test_simple_identifier(self):
1015
from navegador.ingestion.cpp import CppParser
1016
parser = CppParser.__new__(CppParser)
1017
source = b"foo"
1018
node = _text_node(b"foo")
1019
assert parser._extract_function_name(node, source) == "foo"
1020
1021
def test_function_declarator(self):
1022
from navegador.ingestion.cpp import CppParser
1023
parser = CppParser.__new__(CppParser)
1024
source = b"foo"
1025
fn_id = _text_node(b"foo")
1026
fn_decl = MockNode("function_declarator")
1027
fn_decl.set_field("declarator", fn_id)
1028
assert parser._extract_function_name(fn_decl, source) == "foo"
1029
1030
def test_none_input(self):
1031
from navegador.ingestion.cpp import CppParser
1032
parser = CppParser.__new__(CppParser)
1033
assert parser._extract_function_name(None, b"") is None
1034

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button