[docs] 1# @Test suite for tree-sitter parsing utilities and language support, TEST_LANG_1, test, [IMPL_LANG_1, IMPL_EXTR_1, IMPL_RST_1]
2from pathlib import Path
3import shutil
4import subprocess
5
6import pytest
7from tree_sitter import Language, Parser, Query
8from tree_sitter import Node as TreeSitterNode
9import tree_sitter_c_sharp
10import tree_sitter_cpp
11import tree_sitter_python
12import tree_sitter_rust
13import tree_sitter_yaml
14
15from sphinx_codelinks.analyse import utils
16from sphinx_codelinks.config import UNIX_NEWLINE
17from sphinx_codelinks.source_discover.config import CommentType
18
19
20@pytest.fixture(scope="session")
21def init_cpp_tree_sitter() -> tuple[Parser, Query]:
22 parsed_language = Language(tree_sitter_cpp.language())
23 query = Query(parsed_language, utils.CPP_QUERY)
24 parser = Parser(parsed_language)
25 return parser, query
26
27
28@pytest.fixture(scope="session")
29def init_python_tree_sitter() -> tuple[Parser, Query]:
30 parsed_language = Language(tree_sitter_python.language())
31 query = Query(parsed_language, utils.PYTHON_QUERY)
32 parser = Parser(parsed_language)
33 return parser, query
34
35
36@pytest.fixture(scope="session")
37def init_csharp_tree_sitter() -> tuple[Parser, Query]:
38 parsed_language = Language(tree_sitter_c_sharp.language())
39 query = Query(parsed_language, utils.C_SHARP_QUERY)
40 parser = Parser(parsed_language)
41 return parser, query
42
43
44@pytest.fixture(scope="session")
45def init_yaml_tree_sitter() -> tuple[Parser, Query]:
46 parsed_language = Language(tree_sitter_yaml.language())
47 query = Query(parsed_language, utils.YAML_QUERY)
48 parser = Parser(parsed_language)
49 return parser, query
50
51
52@pytest.fixture(scope="session")
53def init_rust_tree_sitter() -> tuple[Parser, Query]:
54 parsed_language = Language(tree_sitter_rust.language())
55 query = Query(parsed_language, utils.RUST_QUERY)
56 parser = Parser(parsed_language)
57 return parser, query
58
59
60@pytest.mark.parametrize(
61 ("code", "result"),
62 [
63 (
64 b"""
65 // @req-id: need_001
66 void dummy_func1(){
67 }
68 """,
69 "void dummy_func1()",
70 ),
71 (
72 b"""
73 void dummy_func2(){
74 }
75 // @req-id: need_001
76 void dummy_func1(){
77 }
78 """,
79 "void dummy_func1()",
80 ),
81 (
82 b"""
83 void dummy_func1(){
84 a = 1;
85 /* @req-id: need_001 */
86 }
87 """,
88 "void dummy_func1()",
89 ),
90 (
91 b"""
92 void dummy_func1(){
93 // @req-id: need_001
94 a = 1;
95 }
96 void dummy_func2(){
97 }
98 """,
99 "void dummy_func1()",
100 ),
101 ],
102)
103def test_find_associated_scope_cpp(code, result, init_cpp_tree_sitter):
104 parser, query = init_cpp_tree_sitter
105 comments = utils.extract_comments(code, parser, query)
106 node: TreeSitterNode | None = utils.find_associated_scope(
107 comments[0], CommentType.cpp
108 )
109 assert node
110 assert node.text
111 func_def = node.text.decode("utf-8")
112 assert result in func_def
113
114
115@pytest.mark.parametrize(
116 ("code", "result"),
117 [
118 (
119 b"""
120 def dummy_func1():
121 # @req-id: need_001
122 pass
123 """,
124 "def dummy_func1()",
125 ),
126 (
127 b"""
128 def dummy_func1():
129 # @req-id: need_002
130 def dummy_func2():
131 pass
132 pass
133 """,
134 "def dummy_func2()",
135 ),
136 (
137 b"""
138 def dummy_func1():
139 '''@req-id: need_002'''
140 def nested_dummy_func():
141 pass
142 pass
143 """,
144 "def dummy_func1()",
145 ),
146 (
147 b"""
148 def dummy_func1():
149 def nested_dummy_func():
150 '''@req-id: need_002'''
151 pass
152 pass
153 """,
154 "def nested_dummy_func()",
155 ),
156 (
157 b"""
158 def dummy_func1():
159 def nested_dummy_func():
160 # @req-id: need_002
161 pass
162 pass
163 """,
164 "def nested_dummy_func()",
165 ),
166 (
167 b"""
168 def dummy_func1():
169 def nested_dummy_func():
170 pass
171 # @req-id: need_002
172 pass
173 """,
174 "def dummy_func1()",
175 ),
176 ],
177)
178def test_find_associated_scope_python(code, result, init_python_tree_sitter):
179 parser, query = init_python_tree_sitter
180 comments = utils.extract_comments(code, parser, query)
181 node: TreeSitterNode | None = utils.find_associated_scope(
182 comments[0], CommentType.python
183 )
184 assert node
185 assert node.text
186 func_def = node.text.decode("utf-8")
187 assert func_def.startswith(result)
188
189
190@pytest.mark.parametrize(
191 ("code", "result"),
192 [
193 (
194 b"""
195 // @req-id: need_001
196 public class DummyClass1
197 {
198 }
199 """,
200 "public class DummyClass1",
201 ),
202 (
203 b"""
204 public class DummyClass2
205 {
206 // @req-id: need_001
207 public void DummyFunc2()
208 {
209 }
210 }
211 """,
212 "public void DummyFunc2",
213 ),
214 (
215 b"""
216 public class DummyClass3
217 {
218 // @req-id: need_001
219 public string Property1 { get; set; }
220 }
221 """,
222 "public string Property1",
223 ),
224 ],
225)
226def test_find_associated_scope_csharp(code, result, init_csharp_tree_sitter):
227 parser, query = init_csharp_tree_sitter
228 comments = utils.extract_comments(code, parser, query)
229 node: TreeSitterNode | None = utils.find_associated_scope(
230 comments[0], CommentType.cs
231 )
232 assert node
233 assert node.text
234 func_def = node.text.decode("utf-8")
235 assert func_def.startswith(result)
236
237
238@pytest.mark.parametrize(
239 ("code", "result"),
240 [
241 (
242 b"""
243 # @req-id: need_001
244 database:
245 host: localhost
246 port: 5432
247 """,
248 "database:",
249 ),
250 (
251 b"""
252 services:
253 web:
254 # @req-id: need_002
255 image: nginx:latest
256 ports:
257 - "80:80"
258 """,
259 "image: nginx:latest",
260 ),
261 (
262 b"""
263 # @req-id: need_003
264 version: "3.8"
265 services:
266 app:
267 build: .
268 """,
269 "version:",
270 ),
271 (
272 b"""
273 items:
274 # @req-id: need_004
275 - name: item1
276 value: test
277 - name: item2
278 value: test2
279 """,
280 "- name: item1",
281 ),
282 ],
283)
284def test_find_associated_scope_yaml(code, result, init_yaml_tree_sitter):
285 parser, query = init_yaml_tree_sitter
286 comments = utils.extract_comments(code, parser, query)
287 node: TreeSitterNode | None = utils.find_associated_scope(
288 comments[0], CommentType.yaml
289 )
290 assert node
291 assert node.text
292 yaml_structure = node.text.decode("utf-8")
293 assert result in yaml_structure
294
295
296@pytest.mark.parametrize(
297 ("code", "result"),
298 [
299 (
300 b"""
301 // @req-id: need_001
302 fn dummy_func1() {
303 }
304 """,
305 "fn dummy_func1()",
306 ),
307 (
308 b"""
309 fn dummy_func2() {
310 }
311 // @req-id: need_001
312 fn dummy_func1() {
313 }
314 """,
315 "fn dummy_func1()",
316 ),
317 (
318 b"""
319 fn dummy_func1() {
320 let a = 1;
321 /* @req-id: need_001 */
322 }
323 """,
324 "fn dummy_func1()",
325 ),
326 (
327 b"""
328 fn dummy_func1() {
329 // @req-id: need_001
330 let a = 1;
331 }
332 fn dummy_func2() {
333 }
334 """,
335 "fn dummy_func1()",
336 ),
337 (
338 b"""
339 /// @req-id: need_001
340 fn dummy_func1() {
341 }
342 """,
343 "fn dummy_func1()",
344 ),
345 (
346 b"""
347 struct MyStruct {
348 // @req-id: need_001
349 field: i32,
350 }
351 """,
352 "struct MyStruct",
353 ),
354 ],
355)
356def test_find_associated_scope_rust(code, result, init_rust_tree_sitter):
357 parser, query = init_rust_tree_sitter
358 comments = utils.extract_comments(code, parser, query)
359 node: TreeSitterNode | None = utils.find_associated_scope(
360 comments[0], CommentType.rust
361 )
362 assert node
363 assert node.text
364 rust_def = node.text.decode("utf-8")
365 assert result in rust_def
366
367
368@pytest.mark.parametrize(
369 ("code", "result"),
370 [
371 (
372 b"""
373 def dummy_func1():
374 # @req-id: need_001
375 pass
376 """,
377 "def dummy_func1()",
378 ),
379 (
380 b"""
381 def dummy_func1():
382 '''@req-id: need_001'''
383 pass
384 """,
385 "def dummy_func1()",
386 ),
387 (
388 b"""
389 def dummy_func1():
390 def nested_dummy_func1():
391 '''@req-id: need_001'''
392 pass
393 pass
394 """,
395 "def nested_dummy_func1()",
396 ),
397 (
398 b"""
399 def dummy_func1():
400 '''@req-id: need_001'''
401 def nested_dummy_func1():
402 pass
403 pass
404 """,
405 "def dummy_func1()",
406 ),
407 ],
408)
409def test_find_enclosing_scope_python(code, result, init_python_tree_sitter):
410 parser, query = init_python_tree_sitter
411 comments = utils.extract_comments(code, parser, query)
412 node: TreeSitterNode | None = utils.find_enclosing_scope(
413 comments[0], CommentType.python
414 )
415 assert node
416 assert node.text
417 func_def = node.text.decode("utf-8")
418 assert result in func_def
419
420
421@pytest.mark.parametrize(
422 ("code", "result"),
423 [
424 (
425 b"""
426 # @req-id: need_001
427 def dummy_func1():
428 pass
429 """,
430 "def dummy_func1()",
431 ),
432 (
433 b"""
434 # @req-id: need_001
435 # @req-id: need_002
436 def dummy_func1():
437 pass
438 """,
439 "def dummy_func1()",
440 ),
441 ],
442)
443def test_find_next_scope_python(code, result, init_python_tree_sitter):
444 parser, query = init_python_tree_sitter
445 comments = utils.extract_comments(code, parser, query)
446 node: TreeSitterNode | None = utils.find_next_scope(comments[0], CommentType.python)
447 assert node
448 assert node.text
449 func_def = node.text.decode("utf-8")
450 assert result in func_def
451
452
453@pytest.mark.parametrize(
454 ("code", "result"),
455 [
456 (
457 b"""
458 // @req-id: need_001
459 void dummy_func1(){
460 }
461 """,
462 "void dummy_func1()",
463 ),
464 (
465 b"""
466 /* @req-id: need_001 */
467 void dummy_func1(){
468 }
469 """,
470 "void dummy_func1()",
471 ),
472 ],
473)
474def test_find_next_scope_cpp(code, result, init_cpp_tree_sitter):
475 parser, query = init_cpp_tree_sitter
476 comments = utils.extract_comments(code, parser, query)
477 node: TreeSitterNode | None = utils.find_next_scope(comments[0], CommentType.cpp)
478 assert node
479 assert node.text
480 func_def = node.text.decode("utf-8")
481 assert result in func_def
482
483
484@pytest.mark.parametrize(
485 ("code", "result"),
486 [
487 (
488 b"""
489 // @req-id: need_001
490 public class DummyClass1
491 {
492 }
493 """,
494 "public class DummyClass1",
495 ),
496 (
497 b"""
498
499 public class DummyClass1
500 {
501 /* @req-id: need_001 */
502 /* @req-id: need_002 */
503 public void DummyFunc1()
504 {
505 }
506 }
507 """,
508 "public void DummyFunc1",
509 ),
510 ],
511)
512def test_find_next_scope_csharp(code, result, init_csharp_tree_sitter):
513 parser, query = init_csharp_tree_sitter
514 comments = utils.extract_comments(code, parser, query)
515 node: TreeSitterNode | None = utils.find_next_scope(comments[0], CommentType.cs)
516 assert node
517 assert node.text
518 func_def = node.text.decode("utf-8")
519 assert result in func_def
520
521
522@pytest.mark.parametrize(
523 ("code", "result"),
524 [
525 (
526 b"""
527 void dummy_func1(){
528 // @req-id: need_001
529 }
530 """,
531 "void dummy_func1()",
532 ),
533 (
534 b"""
535 void dummy_func1(){
536 /* @req-id: need_001 */
537 }
538 """,
539 "void dummy_func1()",
540 ),
541 ],
542)
543def test_find_enclosing_scope_cpp(code, result, init_cpp_tree_sitter):
544 parser, query = init_cpp_tree_sitter
545 comments = utils.extract_comments(code, parser, query)
546 node: TreeSitterNode | None = utils.find_enclosing_scope(
547 comments[0], CommentType.cpp
548 )
549 assert node
550 assert node.text
551 func_def = node.text.decode("utf-8")
552 assert result in func_def
553
554
555@pytest.mark.parametrize(
556 ("code", "result"),
557 [
558 (
559 b"""
560 public class DummyClass1
561 {
562 // @req-id: need_001
563 }
564 """,
565 "public class DummyClass1",
566 ),
567 (
568 b"""
569 public class DummyClass1
570 {
571 public void DummyFunc1()
572 {
573 /* @req-id: need_001 */
574 }
575 }
576 """,
577 "public void DummyFunc1()",
578 ),
579 (
580 b"""
581 public class DummyClass1
582 {
583 public string DummyProperty1
584 {
585 get
586 {
587 /* @req-id: need_001 */
588 return "dummy";
589 }
590 }
591 }
592 """,
593 "public string DummyProperty1",
594 ),
595 ],
596)
597def test_find_enclosing_scope_csharp(code, result, init_csharp_tree_sitter):
598 parser, query = init_csharp_tree_sitter
599 comments = utils.extract_comments(code, parser, query)
600 node: TreeSitterNode | None = utils.find_enclosing_scope(
601 comments[0], CommentType.cs
602 )
603 assert node
604 assert node.text
605 func_def = node.text.decode("utf-8")
606 assert result in func_def
607
608
609@pytest.mark.parametrize(
610 ("code", "num_comments", "result"),
611 [
612 (
613 b"""
614 // @req-id: need_001
615 void dummy_func1(){
616 }
617 """,
618 1,
619 "// @req-id: need_001",
620 ),
621 (
622 b"""
623 void dummy_func1(){
624 // @req-id: need_001
625 }
626 """,
627 1,
628 "// @req-id: need_001",
629 ),
630 (
631 b"""
632 /* @req-id: need_001 */
633 void dummy_func1(){
634 }
635 """,
636 1,
637 "/* @req-id: need_001 */",
638 ),
639 (
640 b"""
641 // @req-id: need_001
642 //
643 //
644 void dummy_func1(){
645 }
646 """,
647 3,
648 "// @req-id: need_001",
649 ),
650 ],
651)
652def test_cpp_comment(code, num_comments, result, init_cpp_tree_sitter):
653 parser, query = init_cpp_tree_sitter
654 comments = utils.extract_comments(code, parser, query)
655 assert len(comments) == num_comments
656 comments.sort(key=lambda x: x.start_point.row)
657 assert comments[0].text
658 assert comments[0].text.decode("utf-8") == result
659
660
661@pytest.mark.parametrize(
662 ("code", "num_comments", "result"),
663 [
664 (
665 b"""
666 # @req-id: need_001
667 def dummy_func1():
668 pass
669 """,
670 1,
671 "# @req-id: need_001",
672 ),
673 (
674 b"""
675 def dummy_func1():
676 # @req-id: need_001
677 pass
678 """,
679 1,
680 "# @req-id: need_001",
681 ),
682 (
683 b"""
684 # single line comment
685 # @req-id: need_001
686 def dummy_func1():
687 pass
688 """,
689 2,
690 "# single line comment",
691 ),
692 (
693 b"""
694 def dummy_func1():
695 '''
696 @req-id: need_001
697 '''
698 pass
699 """,
700 1,
701 "'''\n @req-id: need_001\n '''",
702 ),
703 (
704 b"""
705 def dummy_func1():
706 text = '''@req-id: need_001, need_002, this docstring shall not be extracted as comment'''
707 # @req-id: need_001
708 pass
709 """,
710 1,
711 "# @req-id: need_001",
712 ),
713 ],
714)
715def test_python_comment(code, num_comments, result, init_python_tree_sitter):
716 parser, query = init_python_tree_sitter
717 comments: list[TreeSitterNode] = utils.extract_comments(code, parser, query)
718 comments.sort(key=lambda x: x.start_point.row)
719 assert len(comments) == num_comments
720 assert comments[0].text
721 assert comments[0].text.decode("utf-8") == result
722
723
724@pytest.mark.parametrize(
725 ("code", "num_comments", "result"),
726 [
727 (
728 b"""
729 // @req-id: need_001
730 void DummyFunc1(){
731 }
732 """,
733 1,
734 "// @req-id: need_001",
735 ),
736 (
737 b"""
738 void DummyFunc1(){
739 // @req-id: need_001
740 }
741 """,
742 1,
743 "// @req-id: need_001",
744 ),
745 (
746 b"""
747 /* @req-id: need_001 */
748 void DummyFunc1(){
749 }
750 """,
751 1,
752 "/* @req-id: need_001 */",
753 ),
754 (
755 b"""
756 // @req-id: need_001
757 //
758 //
759 void DummyFunc1(){
760 }
761 """,
762 3,
763 "// @req-id: need_001",
764 ),
765 ],
766)
767def test_csharp_comment(code, num_comments, result, init_csharp_tree_sitter):
768 parser, query = init_csharp_tree_sitter
769 comments: list[TreeSitterNode] = utils.extract_comments(code, parser, query)
770 comments.sort(key=lambda x: x.start_point.row)
771 assert len(comments) == num_comments
772 assert comments[0].text
773 assert comments[0].text.decode("utf-8") == result
774
775
776@pytest.mark.parametrize(
777 ("code", "num_comments", "result"),
778 [
779 (
780 b"""
781 # @req-id: need_001
782 database:
783 host: localhost
784 """,
785 1,
786 "# @req-id: need_001",
787 ),
788 (
789 b"""
790 services:
791 web:
792 # @req-id: need_001
793 image: nginx:latest
794 """,
795 1,
796 "# @req-id: need_001",
797 ),
798 (
799 b"""
800 # Top level comment
801 # @req-id: need_001
802 version: "3.8"
803 """,
804 2,
805 "# Top level comment",
806 ),
807 ],
808)
809def test_yaml_comment(code, num_comments, result, init_yaml_tree_sitter):
810 parser, query = init_yaml_tree_sitter
811 comments: list[TreeSitterNode] = utils.extract_comments(code, parser, query)
812 comments.sort(key=lambda x: x.start_point.row)
813 assert len(comments) == num_comments
814 assert comments[0].text
815 assert comments[0].text.decode("utf-8") == result
816
817
818@pytest.mark.parametrize(
819 ("git_url", "rev", "project_path", "filepath", "lineno", "result"),
820 [
821 (
822 "git@github.com:useblocks/sphinx-codelinks.git",
823 "beef1234",
824 Path(__file__).parent.parent,
825 Path("example") / "to" / "here",
826 3,
827 "https://github.com/useblocks/sphinx-codelinks/blob/beef1234/example/to/here#L3",
828 )
829 ],
830)
831def test_form_https_url(git_url, rev, project_path, filepath, lineno, result): # noqa: PLR0913 # need to have these args
832 url = utils.form_https_url(git_url, rev, project_path, filepath, lineno=lineno)
833 assert url == result
834
835
836def get_git_path() -> str:
837 """Get the path to the git executable."""
838 git_path = shutil.which("git")
839 if not git_path:
840 raise FileNotFoundError("Git executable not found")
841 if not Path(git_path).is_file():
842 raise FileNotFoundError("Git executable path is invalid")
843 return git_path
844
845
846def init_git_repo(repo_path: Path, remote_url: str) -> Path:
847 """Initialize a git repository for testing."""
848 git_dir = repo_path / "test_repo"
849 src_dir = git_dir / "src"
850 src_dir.mkdir(parents=True)
851
852 git_path = get_git_path()
853 if not git_path:
854 raise FileNotFoundError("Git executable not found")
855 if not Path(git_path).is_file():
856 raise FileNotFoundError("Git executable path is invalid")
857
858 # Initialize git repo
859 subprocess.run([git_path, "init"], cwd=git_dir, check=True, capture_output=True) # noqa: S603
860 subprocess.run( # noqa: S603
861 [git_path, "config", "user.email", "test@example.com"], cwd=git_dir, check=True
862 )
863 subprocess.run( # noqa: S603
864 [git_path, "config", "user.name", "Test User"], cwd=git_dir, check=True
865 )
866
867 # Create a test file and commit
868 test_file = src_dir / "test_file.py"
869 test_file.write_text("# Test file\nprint('hello')\n")
870 subprocess.run([git_path, "add", "."], cwd=git_dir, check=True) # noqa: S603
871 subprocess.run( # noqa: S603
872 [git_path, "commit", "-m", "Initial commit"], cwd=git_dir, check=True
873 )
874
875 # Add a remote
876 subprocess.run( # noqa: S603
877 [git_path, "remote", "add", "origin", remote_url],
878 cwd=git_dir,
879 check=True,
880 )
881
882 return git_dir
883
884
885@pytest.fixture(
886 params=[
887 ("test_repo_git", "git@github.com:test-user/test-repo.git"),
888 ("test_repo_https", "https://github.com/test-user/test-repo.git"),
889 ]
890)
891def git_repo(tmp_path: str, request: pytest.FixtureRequest) -> tuple[Path, str]:
892 """Create git repos for testing."""
893 repo_name, remote_url = request.param
894 repo_path = Path(tmp_path) / repo_name
895 repo_path = init_git_repo(repo_path, remote_url)
896 return repo_path, remote_url
897
898
899def get_current_commit_hash(git_dir: Path) -> str:
900 """Get the current commit hash of the git repository."""
901 git_path = get_git_path()
902 result = subprocess.run( # noqa: S603
903 [git_path, "rev-parse", "HEAD"],
904 cwd=git_dir,
905 check=True,
906 capture_output=True,
907 text=True,
908 )
909
910 return str(result.stdout.strip())
911
912
913def test_locate_git_root(git_repo: tuple[Path, str]) -> None:
914 repo_path = git_repo[0]
915 src_dir = repo_path / "src"
916 git_root = utils.locate_git_root(src_dir)
917 assert git_root == repo_path
918
919
920def test_get_remote_url(git_repo: tuple[Path, str]) -> None:
921 repo_path, expected_url = git_repo
922 remote_url = utils.get_remote_url(repo_path)
923 assert remote_url == expected_url
924
925
926def test_get_current_rev(git_repo: tuple[Path, str]) -> None:
927 repo_path, _ = git_repo
928 current_rev = get_current_commit_hash(repo_path)
929 assert current_rev == utils.get_current_rev(repo_path)
930
931
932@pytest.mark.parametrize(
933 ("text", "leading_sequences", "result"),
934 [
935 (
936 """
937* some text in a comment
938* some text in a comment
939*
940""",
941 ["*"],
942 """
943 some text in a comment
944 some text in a comment
945
946""",
947 ),
948 ],
949)
950def test_remove_leading_sequences(text, leading_sequences, result):
951 clean_text = utils.remove_leading_sequences(text, leading_sequences)
952 assert clean_text == result
953
954
955@pytest.mark.parametrize(
956 ("text", "rst_markers", "rst_text", "positions"),
957 [
958 (
959 """
960@rst
961.. impl:: multiline rst text
962 :id: IMPL_71
963@endrst
964""",
965 ["@rst", "@endrst"],
966 f""".. impl:: multiline rst text{UNIX_NEWLINE} :id: IMPL_71{UNIX_NEWLINE}""",
967 {"row_offset": 1, "start_idx": 6, "end_idx": 51},
968 ),
969 (
970 """
971@rst.. impl:: oneline rst text@endrst
972""",
973 ["@rst", "@endrst"],
974 """.. impl:: oneline rst text""",
975 {"row_offset": 0, "start_idx": 5, "end_idx": 31},
976 ),
977 ],
978)
979def test_extract_rst(text, rst_markers, rst_text, positions):
980 extracted_rst = utils.extract_rst(text, rst_markers[0], rst_markers[1])
981 assert extracted_rst is not None
982 assert extracted_rst["rst_text"] == rst_text
983 assert extracted_rst["start_idx"] == positions["start_idx"]
984 assert extracted_rst["end_idx"] == positions["end_idx"]
985
986
987# ========== YAML-specific tests ==========
988
989
990@pytest.mark.parametrize(
991 ("code", "expected_structure"),
992 [
993 # Basic key-value pair
994 (
995 b"""
996 # Comment before key
997 database:
998 host: localhost
999 """,
1000 "database:",
1001 ),
1002 # Comment in nested structure
1003 (
1004 b"""
1005 services:
1006 web:
1007 # Comment before image
1008 image: nginx:latest
1009 """,
1010 "image: nginx:latest",
1011 ),
1012 # Comment before list item
1013 (
1014 b"""
1015 items:
1016 # Comment before list item
1017 - name: item1
1018 value: test
1019 """,
1020 "- name: item1",
1021 ),
1022 # Comment in document structure
1023 (
1024 b"""---
1025# Comment in document
1026version: "3.8"
1027services:
1028 app:
1029 build: .
1030 """,
1031 "version:",
1032 ),
1033 # Flow mapping structure
1034 (
1035 b"""
1036 # Comment before flow mapping
1037 config: {debug: true, port: 8080}
1038 """,
1039 "config:",
1040 ),
1041 ],
1042)
1043def test_find_yaml_next_structure(code, expected_structure, init_yaml_tree_sitter):
1044 """Test the find_yaml_next_structure function."""
1045 parser, query = init_yaml_tree_sitter
1046 comments = utils.extract_comments(code, parser, query)
1047 assert comments, "No comments found in the code"
1048
1049 next_structure = utils.find_yaml_next_structure(comments[0])
1050 assert next_structure, "No next structure found"
1051 structure_text = next_structure.text.decode("utf-8")
1052 assert expected_structure in structure_text
1053
1054
1055@pytest.mark.parametrize(
1056 ("code", "expected_structure"),
1057 [
1058 # Comment associated with key-value pair
1059 (
1060 b"""
1061 # Database configuration
1062 database:
1063 host: localhost
1064 port: 5432
1065 """,
1066 "database:",
1067 ),
1068 # Comment associated with nested structure
1069 (
1070 b"""
1071 services:
1072 web:
1073 # Web service image
1074 image: nginx:latest
1075 ports:
1076 - "80:80"
1077 """,
1078 "image: nginx:latest",
1079 ),
1080 # Comment associated with list item
1081 (
1082 b"""
1083 dependencies:
1084 # First dependency
1085 - name: redis
1086 version: "6.0"
1087 - name: postgres
1088 version: "13"
1089 """,
1090 "- name: redis",
1091 ),
1092 # Comment inside parent structure
1093 (
1094 b"""
1095 app:
1096 # Internal comment
1097 name: myapp
1098 version: "1.0"
1099 """,
1100 "name: myapp",
1101 ),
1102 ],
1103)
1104def test_find_yaml_associated_structure(
1105 code, expected_structure, init_yaml_tree_sitter
1106):
1107 """Test the find_yaml_associated_structure function."""
1108 parser, query = init_yaml_tree_sitter
1109 comments = utils.extract_comments(code, parser, query)
1110 assert comments, "No comments found in the code"
1111
1112 associated_structure = utils.find_yaml_associated_structure(comments[0])
1113 assert associated_structure, "No associated structure found"
1114 structure_text = associated_structure.text.decode("utf-8")
1115 assert expected_structure in structure_text
1116
1117
1118@pytest.mark.parametrize(
1119 ("code", "expected_results"),
1120 [
1121 # Multiple comments in sequence
1122 (
1123 b"""
1124 # First comment
1125 # Second comment
1126 database:
1127 host: localhost
1128 """,
1129 ["database:", "database:"], # Both comments should associate with database
1130 ),
1131 # Comments at different nesting levels
1132 (
1133 b"""
1134 # Top level comment
1135 services:
1136 web:
1137 # Nested comment
1138 image: nginx:latest
1139 """,
1140 ["services:", "image: nginx:latest"],
1141 ),
1142 ],
1143)
1144def test_multiple_yaml_comments(code, expected_results, init_yaml_tree_sitter):
1145 """Test handling of multiple YAML comments in the same file."""
1146 parser, query = init_yaml_tree_sitter
1147 comments = utils.extract_comments(code, parser, query)
1148 comments.sort(key=lambda x: x.start_point.row)
1149
1150 assert len(comments) == len(expected_results), (
1151 f"Expected {len(expected_results)} comments, found {len(comments)}"
1152 )
1153
1154 for i, comment in enumerate(comments):
1155 associated_structure = utils.find_yaml_associated_structure(comment)
1156 assert associated_structure, f"No associated structure found for comment {i}"
1157 structure_text = associated_structure.text.decode("utf-8")
1158 assert expected_results[i] in structure_text
1159
1160
1161@pytest.mark.parametrize(
1162 ("code", "has_structure"),
1163 [
1164 # Comment at end of file with no following structure
1165 (
1166 b"""
1167database:
1168 host: localhost
1169# End of file comment
1170 """,
1171 True, # This will actually find the parent database structure
1172 ),
1173 # Comment with only whitespace after
1174 (
1175 b"""
1176 # Lonely comment
1177
1178
1179 """,
1180 False,
1181 ),
1182 # Comment before valid structure
1183 (
1184 b"""
1185 # Valid comment
1186 key: value
1187 """,
1188 True,
1189 ),
1190 ],
1191)
1192def test_yaml_edge_cases(code, has_structure, init_yaml_tree_sitter):
1193 """Test edge cases in YAML comment processing."""
1194 parser, query = init_yaml_tree_sitter
1195 comments = utils.extract_comments(code, parser, query)
1196
1197 if comments:
1198 structure = utils.find_yaml_associated_structure(comments[0])
1199 if has_structure:
1200 assert structure, "Expected to find associated structure"
1201 else:
1202 assert structure is None, "Expected no associated structure"
1203 else:
1204 assert not has_structure, "No comments found but structure was expected"
1205
1206
1207@pytest.mark.parametrize(
1208 ("code", "expected_structures"),
1209 [
1210 # Simpler nested YAML structure
1211 (
1212 b"""# Global configuration
1213version: "3.8"
1214
1215# Services section
1216services:
1217 web:
1218 image: nginx:latest
1219 # Port configuration
1220 ports:
1221 - "80:80"
1222 """,
1223 [
1224 "version:", # Global configuration
1225 "services:", # Services section
1226 '- "80:80"', # Port configuration
1227 ],
1228 ),
1229 ],
1230)
1231def test_complex_yaml_structure(code, expected_structures, init_yaml_tree_sitter):
1232 """Test complex nested YAML structures with multiple comments."""
1233 parser, query = init_yaml_tree_sitter
1234 comments = utils.extract_comments(code, parser, query)
1235 comments.sort(key=lambda x: x.start_point.row)
1236
1237 assert len(comments) == len(expected_structures), (
1238 f"Expected {len(expected_structures)} comments, found {len(comments)}"
1239 )
1240
1241 for i, comment in enumerate(comments):
1242 associated_structure = utils.find_yaml_associated_structure(comment)
1243 assert associated_structure, f"No associated structure found for comment {i}"
1244 structure_text = associated_structure.text.decode("utf-8")
1245 assert expected_structures[i] in structure_text, (
1246 f"Expected '{expected_structures[i]}' in structure text: '{structure_text}'"
1247 )
1248
1249
1250@pytest.mark.parametrize(
1251 ("code", "expected_type"),
1252 [
1253 # Block mapping pair
1254 (
1255 b"""
1256 # Comment
1257 key: value
1258 """,
1259 "block_mapping_pair",
1260 ),
1261 # Block sequence item
1262 (
1263 b"""
1264 items:
1265 # Comment
1266 - item1
1267 """,
1268 "block_sequence_item",
1269 ),
1270 # Nested block mapping
1271 (
1272 b"""
1273 services:
1274 # Comment
1275 web:
1276 image: nginx
1277 """,
1278 "block_mapping_pair",
1279 ),
1280 ],
1281)
1282def test_yaml_structure_types(code, expected_type, init_yaml_tree_sitter):
1283 """Test that YAML structures return the correct node types."""
1284 parser, query = init_yaml_tree_sitter
1285 comments = utils.extract_comments(code, parser, query)
1286 assert comments, "No comments found"
1287
1288 structure = utils.find_yaml_associated_structure(comments[0])
1289 assert structure, "No associated structure found"
1290 assert structure.type == expected_type, (
1291 f"Expected type {expected_type}, got {structure.type}"
1292 )
1293
1294
1295def test_yaml_document_structure(init_yaml_tree_sitter):
1296 """Test YAML document structure handling."""
1297 code = b"""---
1298# Document comment
1299apiVersion: v1
1300kind: ConfigMap
1301metadata:
1302 name: my-config
1303data:
1304 # Data comment
1305 config.yml: |
1306 setting: value
1307 """
1308
1309 parser, query = init_yaml_tree_sitter
1310 comments = utils.extract_comments(code, parser, query)
1311 comments.sort(key=lambda x: x.start_point.row)
1312
1313 # Should find both comments
1314 assert len(comments) >= 2, f"Expected at least 2 comments, found {len(comments)}"
1315
1316 # First comment should associate with apiVersion
1317 first_structure = utils.find_yaml_associated_structure(comments[0])
1318 assert first_structure, "No structure found for first comment"
1319 first_text = first_structure.text.decode("utf-8")
1320 assert "apiVersion:" in first_text
1321
1322 # Second comment should associate with config.yml
1323 second_structure = utils.find_yaml_associated_structure(comments[1])
1324 assert second_structure, "No structure found for second comment"
1325 second_text = second_structure.text.decode("utf-8")
1326 assert "config.yml:" in second_text
1327
1328
1329def test_yaml_inline_comments_current_behavior(init_yaml_tree_sitter):
1330 """Test improved behavior of inline comments in YAML after the fix."""
1331 code = b"""key1: value1 # inline comment about key1
1332key2: value2
1333key3: value3 # inline comment about key3
1334"""
1335
1336 parser, query = init_yaml_tree_sitter
1337 comments = utils.extract_comments(code, parser, query)
1338 comments.sort(key=lambda x: x.start_point.row)
1339
1340 assert len(comments) == 2, f"Expected 2 comments, found {len(comments)}"
1341
1342 # Fixed behavior: inline comment about key1 now correctly associates with key1
1343 first_structure = utils.find_yaml_associated_structure(comments[0])
1344 assert first_structure, "No structure found for first comment"
1345 first_text = first_structure.text.decode("utf-8")
1346 assert "key1:" in first_text, f"Expected 'key1:' in '{first_text}'"
1347
1348 # Fixed behavior: inline comment about key3 now correctly associates with key3
1349 second_structure = utils.find_yaml_associated_structure(comments[1])
1350 assert second_structure, "No structure found for second comment"
1351 second_text = second_structure.text.decode("utf-8")
1352 assert "key3:" in second_text, f"Expected 'key3:' in '{second_text}'"
1353
1354
1355@pytest.mark.parametrize(
1356 ("code", "expected_associations"),
1357 [
1358 # Basic inline comment case
1359 (
1360 b"""key1: value1 # comment about key1
1361key2: value2
1362 """,
1363 ["key1:"], # Now correctly associates with key1
1364 ),
1365 # Multiple inline comments
1366 (
1367 b"""database:
1368 host: localhost # production server
1369 port: 5432 # default postgres port
1370 user: admin
1371 """,
1372 [
1373 "host: localhost",
1374 "port: 5432",
1375 ], # Now correctly associates with the right structures
1376 ),
1377 ],
1378)
1379def test_yaml_inline_comments_fixed_behavior(
1380 code, expected_associations, init_yaml_tree_sitter
1381):
1382 """Test that inline comments now correctly associate with the structure they comment on."""
1383 parser, query = init_yaml_tree_sitter
1384 comments = utils.extract_comments(code, parser, query)
1385 comments.sort(key=lambda x: x.start_point.row)
1386
1387 assert len(comments) == len(expected_associations), (
1388 f"Expected {len(expected_associations)} comments, found {len(comments)}"
1389 )
1390
1391 for i, comment in enumerate(comments):
1392 structure = utils.find_yaml_associated_structure(comment)
1393 assert structure, f"No structure found for comment {i}"
1394 structure_text = structure.text.decode("utf-8")
1395 assert expected_associations[i] in structure_text, (
1396 f"Expected '{expected_associations[i]}' in structure text: '{structure_text}'"
1397 )
1398
1399
1400@pytest.mark.parametrize(
1401 ("code", "expected_associations"),
1402 [
1403 # Inline comments with list items
1404 (
1405 b"""items:
1406 - name: item1 # first item
1407 - name: item2 # second item
1408 """,
1409 [
1410 "name: item1",
1411 "name: item2",
1412 ], # The inline comment finds the key-value pair within the list item
1413 ),
1414 # Mixed inline and block comments
1415 (
1416 b"""# Block comment for database
1417database:
1418 host: localhost # inline comment for host
1419 port: 5432
1420 # Block comment for user
1421 user: admin
1422 """,
1423 ["database:", "host: localhost", "user: admin"],
1424 ),
1425 # Inline comments in nested structures
1426 (
1427 b"""services:
1428 web:
1429 image: nginx # web server image
1430 ports:
1431 - "80:80" # http port
1432 """,
1433 ["image: nginx", '- "80:80"'],
1434 ),
1435 ],
1436)
1437def test_yaml_inline_comments_comprehensive(
1438 code, expected_associations, init_yaml_tree_sitter
1439):
1440 """Comprehensive test for inline comment behavior in various YAML structures."""
1441 parser, query = init_yaml_tree_sitter
1442 comments = utils.extract_comments(code, parser, query)
1443 comments.sort(key=lambda x: x.start_point.row)
1444
1445 assert len(comments) == len(expected_associations), (
1446 f"Expected {len(expected_associations)} comments, found {len(comments)}"
1447 )
1448
1449 for i, comment in enumerate(comments):
1450 structure = utils.find_yaml_associated_structure(comment)
1451 assert structure, (
1452 f"No structure found for comment {i}: '{comment.text.decode('utf-8')}'"
1453 )
1454 structure_text = structure.text.decode("utf-8")
1455 assert expected_associations[i] in structure_text, (
1456 f"Comment {i} '{comment.text.decode('utf-8')}' -> Expected '{expected_associations[i]}' in '{structure_text}'"
1457 )