[docs]   1# @Test suite for tree-sitter parsing utilities and language support, TEST_LANG_1, test, [IMPL_LANG_1, IMPL_EXTR_1]
   2import shutil
   3import subprocess
   4from pathlib import Path
   5
   6import pytest
   7import tree_sitter_bash
   8import tree_sitter_c_sharp
   9import tree_sitter_cpp
  10import tree_sitter_go
  11import tree_sitter_json
  12import tree_sitter_python
  13import tree_sitter_rust
  14import tree_sitter_yaml
  15from tree_sitter import Language, Parser, Query
  16from tree_sitter import Node as TreeSitterNode
  17
  18from sphinx_codelinks.analyse import utils
  19from sphinx_codelinks.source_discover.config import CommentType
  20
  21
  22@pytest.fixture(scope="session")
  23def init_cpp_tree_sitter() -> tuple[Parser, Query]:
  24    parsed_language = Language(tree_sitter_cpp.language())
  25    query = Query(parsed_language, utils.CPP_QUERY)
  26    parser = Parser(parsed_language)
  27    return parser, query
  28
  29
  30@pytest.fixture(scope="session")
  31def init_python_tree_sitter() -> tuple[Parser, Query]:
  32    parsed_language = Language(tree_sitter_python.language())
  33    query = Query(parsed_language, utils.PYTHON_QUERY)
  34    parser = Parser(parsed_language)
  35    return parser, query
  36
  37
  38@pytest.fixture(scope="session")
  39def init_csharp_tree_sitter() -> tuple[Parser, Query]:
  40    parsed_language = Language(tree_sitter_c_sharp.language())
  41    query = Query(parsed_language, utils.C_SHARP_QUERY)
  42    parser = Parser(parsed_language)
  43    return parser, query
  44
  45
  46@pytest.fixture(scope="session")
  47def init_yaml_tree_sitter() -> tuple[Parser, Query]:
  48    parsed_language = Language(tree_sitter_yaml.language())
  49    query = Query(parsed_language, utils.YAML_QUERY)
  50    parser = Parser(parsed_language)
  51    return parser, query
  52
  53
  54@pytest.fixture(scope="session")
  55def init_rust_tree_sitter() -> tuple[Parser, Query]:
  56    parsed_language = Language(tree_sitter_rust.language())
  57    query = Query(parsed_language, utils.RUST_QUERY)
  58    parser = Parser(parsed_language)
  59    return parser, query
  60
  61
  62@pytest.fixture(scope="session")
  63def init_go_tree_sitter() -> tuple[Parser, Query]:
  64    parsed_language = Language(tree_sitter_go.language())
  65    query = Query(parsed_language, utils.GO_QUERY)
  66    parser = Parser(parsed_language)
  67    return parser, query
  68
  69
  70@pytest.fixture(scope="session")
  71def init_jsonc_tree_sitter() -> tuple[Parser, Query]:
  72    parsed_language = Language(tree_sitter_json.language())
  73    query = Query(parsed_language, utils.JSONC_QUERY)
  74    parser = Parser(parsed_language)
  75    return parser, query
  76
  77
  78@pytest.fixture(scope="session")
  79def init_bash_tree_sitter() -> tuple[Parser, Query]:
  80    parsed_language = Language(tree_sitter_bash.language())
  81    query = Query(parsed_language, utils.BASH_QUERY)
  82    parser = Parser(parsed_language)
  83    return parser, query
  84
  85
  86@pytest.mark.parametrize(
  87    ("code", "result"),
  88    [
  89        (
  90            b"""
  91                // @req-id: need_001
  92                void dummy_func1(){
  93                }
  94            """,
  95            "void dummy_func1()",
  96        ),
  97        (
  98            b"""
  99                void dummy_func2(){
 100                }
 101                // @req-id: need_001
 102                void dummy_func1(){
 103                }
 104            """,
 105            "void dummy_func1()",
 106        ),
 107        (
 108            b"""
 109                void dummy_func1(){
 110                    a = 1;
 111                    /* @req-id: need_001 */
 112                }
 113            """,
 114            "void dummy_func1()",
 115        ),
 116        (
 117            b"""
 118                void dummy_func1(){
 119                    // @req-id: need_001
 120                    a = 1;
 121                }
 122                void dummy_func2(){
 123                }
 124            """,
 125            "void dummy_func1()",
 126        ),
 127    ],
 128)
 129def test_find_associated_scope_cpp(code, result, init_cpp_tree_sitter):
 130    parser, query = init_cpp_tree_sitter
 131    comments = utils.extract_comments(code, parser, query)
 132    node: TreeSitterNode | None = utils.find_associated_scope(
 133        comments[0], CommentType.cpp
 134    )
 135    assert node
 136    assert node.text
 137    func_def = node.text.decode("utf-8")
 138    assert result in func_def
 139
 140
 141@pytest.mark.parametrize(
 142    ("code", "result"),
 143    [
 144        (
 145            b"""
 146                def dummy_func1():
 147                    # @req-id: need_001
 148                    pass
 149            """,
 150            "def dummy_func1()",
 151        ),
 152        (
 153            b"""
 154                def dummy_func1():
 155                    # @req-id: need_002
 156                    def dummy_func2():
 157                        pass
 158                    pass
 159            """,
 160            "def dummy_func2()",
 161        ),
 162        (
 163            b"""
 164                def dummy_func1():
 165                '''@req-id: need_002'''
 166                    def nested_dummy_func():
 167                        pass
 168                    pass
 169            """,
 170            "def dummy_func1()",
 171        ),
 172        (
 173            b"""
 174                def dummy_func1():
 175                    def nested_dummy_func():
 176                        '''@req-id: need_002'''
 177                        pass
 178                    pass
 179            """,
 180            "def nested_dummy_func()",
 181        ),
 182        (
 183            b"""
 184                def dummy_func1():
 185                    def nested_dummy_func():
 186                        # @req-id: need_002
 187                        pass
 188                    pass
 189            """,
 190            "def nested_dummy_func()",
 191        ),
 192        (
 193            b"""
 194                def dummy_func1():
 195                    def nested_dummy_func():
 196                        pass
 197                    # @req-id: need_002
 198                    pass
 199            """,
 200            "def dummy_func1()",
 201        ),
 202    ],
 203)
 204def test_find_associated_scope_python(code, result, init_python_tree_sitter):
 205    parser, query = init_python_tree_sitter
 206    comments = utils.extract_comments(code, parser, query)
 207    node: TreeSitterNode | None = utils.find_associated_scope(
 208        comments[0], CommentType.python
 209    )
 210    assert node
 211    assert node.text
 212    func_def = node.text.decode("utf-8")
 213    assert func_def.startswith(result)
 214
 215
 216@pytest.mark.parametrize(
 217    ("code", "result"),
 218    [
 219        (
 220            b"""
 221                // @req-id: need_001
 222                public class DummyClass1
 223                {
 224                }
 225            """,
 226            "public class DummyClass1",
 227        ),
 228        (
 229            b"""
 230                public class DummyClass2
 231                {
 232                    // @req-id: need_001
 233                    public void DummyFunc2()
 234                    {
 235                    }
 236                }
 237            """,
 238            "public void DummyFunc2",
 239        ),
 240        (
 241            b"""
 242                public class DummyClass3
 243                {
 244                    // @req-id: need_001
 245                    public string Property1 { get; set; }
 246                }
 247            """,
 248            "public string Property1",
 249        ),
 250    ],
 251)
 252def test_find_associated_scope_csharp(code, result, init_csharp_tree_sitter):
 253    parser, query = init_csharp_tree_sitter
 254    comments = utils.extract_comments(code, parser, query)
 255    node: TreeSitterNode | None = utils.find_associated_scope(
 256        comments[0], CommentType.cs
 257    )
 258    assert node
 259    assert node.text
 260    func_def = node.text.decode("utf-8")
 261    assert func_def.startswith(result)
 262
 263
 264@pytest.mark.parametrize(
 265    ("code", "result"),
 266    [
 267        (
 268            b"""
 269                # @req-id: need_001
 270                database:
 271                  host: localhost
 272                  port: 5432
 273            """,
 274            "database:",
 275        ),
 276        (
 277            b"""
 278                services:
 279                  web:
 280                    # @req-id: need_002
 281                    image: nginx:latest
 282                    ports:
 283                      - "80:80"
 284            """,
 285            "image: nginx:latest",
 286        ),
 287        (
 288            b"""
 289                # @req-id: need_003
 290                version: "3.8"
 291                services:
 292                  app:
 293                    build: .
 294            """,
 295            "version:",
 296        ),
 297        (
 298            b"""
 299                items:
 300                  # @req-id: need_004
 301                  - name: item1
 302                    value: test
 303                  - name: item2
 304                    value: test2
 305            """,
 306            "- name: item1",
 307        ),
 308    ],
 309)
 310def test_find_associated_scope_yaml(code, result, init_yaml_tree_sitter):
 311    parser, query = init_yaml_tree_sitter
 312    comments = utils.extract_comments(code, parser, query)
 313    node: TreeSitterNode | None = utils.find_associated_scope(
 314        comments[0], CommentType.yaml
 315    )
 316    assert node
 317    assert node.text
 318    yaml_structure = node.text.decode("utf-8")
 319    assert result in yaml_structure
 320
 321
 322@pytest.mark.parametrize(
 323    ("code", "result"),
 324    [
 325        (
 326            b"""
 327                // @req-id: need_001
 328                fn dummy_func1() {
 329                }
 330            """,
 331            "fn dummy_func1()",
 332        ),
 333        (
 334            b"""
 335                fn dummy_func2() {
 336                }
 337                // @req-id: need_001
 338                fn dummy_func1() {
 339                }
 340            """,
 341            "fn dummy_func1()",
 342        ),
 343        (
 344            b"""
 345                fn dummy_func1() {
 346                    let a = 1;
 347                    /* @req-id: need_001 */
 348                }
 349            """,
 350            "fn dummy_func1()",
 351        ),
 352        (
 353            b"""
 354                fn dummy_func1() {
 355                    // @req-id: need_001
 356                    let a = 1;
 357                }
 358                fn dummy_func2() {
 359                }
 360            """,
 361            "fn dummy_func1()",
 362        ),
 363        (
 364            b"""
 365                /// @req-id: need_001
 366                fn dummy_func1() {
 367                }
 368            """,
 369            "fn dummy_func1()",
 370        ),
 371        (
 372            b"""
 373                struct MyStruct {
 374                    // @req-id: need_001
 375                    field: i32,
 376                }
 377            """,
 378            "struct MyStruct",
 379        ),
 380    ],
 381)
 382def test_find_associated_scope_rust(code, result, init_rust_tree_sitter):
 383    parser, query = init_rust_tree_sitter
 384    comments = utils.extract_comments(code, parser, query)
 385    node: TreeSitterNode | None = utils.find_associated_scope(
 386        comments[0], CommentType.rust
 387    )
 388    assert node
 389    assert node.text
 390    rust_def = node.text.decode("utf-8")
 391    assert result in rust_def
 392
 393
 394@pytest.mark.parametrize(
 395    ("code", "result"),
 396    [
 397        # leading comment is associated with the following key/value pair
 398        (
 399            b'{\n  // @req-id: need_001\n  "alpha": 1\n}\n',
 400            '"alpha": 1',
 401        ),
 402        # inline comment is associated with the array item on the same row
 403        (
 404            b'{\n  "items": [\n    "first", // @req-id: need_001\n    "second"\n  ]\n}\n',
 405            '"first"',
 406        ),
 407        # inline comment is associated with the pair on the same row
 408        (
 409            b'{\n  "alpha": 1, // @req-id: need_001\n  "beta": 2\n}\n',
 410            '"alpha": 1',
 411        ),
 412        # block comment is associated with the following pair
 413        (
 414            b'{\n  /* @req-id: need_001 */\n  "beta": 2\n}\n',
 415            '"beta": 2',
 416        ),
 417        # trailing comment falls back to the enclosing object
 418        (
 419            b'{\n  "alpha": 1\n  // @req-id: need_001\n}\n',
 420            '"alpha"',
 421        ),
 422    ],
 423)
 424def test_find_associated_scope_jsonc(code, result, init_jsonc_tree_sitter):
 425    parser, query = init_jsonc_tree_sitter
 426    comments = utils.extract_comments(code, parser, query)
 427    node: TreeSitterNode | None = utils.find_associated_scope(
 428        comments[0], CommentType.jsonc
 429    )
 430    assert node
 431    assert node.text
 432    jsonc_structure = node.text.decode("utf-8")
 433    assert result in jsonc_structure
 434
 435
 436@pytest.mark.parametrize(
 437    ("code", "result"),
 438    [
 439        # comment above a POSIX-style function definition
 440        (
 441            b"""
 442                # @req-id: need_001
 443                greet() {
 444                    echo hi
 445                }
 446            """,
 447            "greet()",
 448        ),
 449        # comment above the `function` keyword form
 450        (
 451            b"""
 452                # @req-id: need_002
 453                function greet {
 454                    echo hi
 455                }
 456            """,
 457            "function greet",
 458        ),
 459        # comment inside a function body falls back to the enclosing function
 460        (
 461            b"""
 462                greet() {
 463                    # @req-id: need_003
 464                    echo hi
 465                }
 466            """,
 467            "greet()",
 468        ),
 469    ],
 470)
 471def test_find_associated_scope_bash(code, result, init_bash_tree_sitter):
 472    parser, query = init_bash_tree_sitter
 473    comments = utils.extract_comments(code, parser, query)
 474    node: TreeSitterNode | None = utils.find_associated_scope(
 475        comments[0], CommentType.bash
 476    )
 477    assert node
 478    assert node.text
 479    func_def = node.text.decode("utf-8")
 480    assert func_def.startswith(result)
 481
 482
 483@pytest.mark.parametrize(
 484    ("code", "result"),
 485    [
 486        (
 487            b"""
 488                def dummy_func1():
 489                    # @req-id: need_001
 490                    pass
 491            """,
 492            "def dummy_func1()",
 493        ),
 494        (
 495            b"""
 496                def dummy_func1():
 497                '''@req-id: need_001'''
 498                    pass
 499            """,
 500            "def dummy_func1()",
 501        ),
 502        (
 503            b"""
 504                def dummy_func1():
 505                    def nested_dummy_func1():
 506                        '''@req-id: need_001'''
 507                        pass
 508                    pass
 509            """,
 510            "def nested_dummy_func1()",
 511        ),
 512        (
 513            b"""
 514                def dummy_func1():
 515                    '''@req-id: need_001'''
 516                    def nested_dummy_func1():
 517                        pass
 518                    pass
 519            """,
 520            "def dummy_func1()",
 521        ),
 522    ],
 523)
 524def test_find_enclosing_scope_python(code, result, init_python_tree_sitter):
 525    parser, query = init_python_tree_sitter
 526    comments = utils.extract_comments(code, parser, query)
 527    node: TreeSitterNode | None = utils.find_enclosing_scope(
 528        comments[0], CommentType.python
 529    )
 530    assert node
 531    assert node.text
 532    func_def = node.text.decode("utf-8")
 533    assert result in func_def
 534
 535
 536@pytest.mark.parametrize(
 537    ("code", "result"),
 538    [
 539        (
 540            b"""
 541                # @req-id: need_001
 542                def dummy_func1():
 543                    pass
 544            """,
 545            "def dummy_func1()",
 546        ),
 547        (
 548            b"""
 549                # @req-id: need_001
 550                # @req-id: need_002
 551                def dummy_func1():
 552                    pass
 553            """,
 554            "def dummy_func1()",
 555        ),
 556    ],
 557)
 558def test_find_next_scope_python(code, result, init_python_tree_sitter):
 559    parser, query = init_python_tree_sitter
 560    comments = utils.extract_comments(code, parser, query)
 561    node: TreeSitterNode | None = utils.find_next_scope(comments[0], CommentType.python)
 562    assert node
 563    assert node.text
 564    func_def = node.text.decode("utf-8")
 565    assert result in func_def
 566
 567
 568@pytest.mark.parametrize(
 569    ("code", "result"),
 570    [
 571        (
 572            b"""
 573                // @req-id: need_001
 574                void dummy_func1(){
 575                }
 576            """,
 577            "void dummy_func1()",
 578        ),
 579        (
 580            b"""
 581                /* @req-id: need_001 */
 582                void dummy_func1(){
 583                }
 584            """,
 585            "void dummy_func1()",
 586        ),
 587    ],
 588)
 589def test_find_next_scope_cpp(code, result, init_cpp_tree_sitter):
 590    parser, query = init_cpp_tree_sitter
 591    comments = utils.extract_comments(code, parser, query)
 592    node: TreeSitterNode | None = utils.find_next_scope(comments[0], CommentType.cpp)
 593    assert node
 594    assert node.text
 595    func_def = node.text.decode("utf-8")
 596    assert result in func_def
 597
 598
 599@pytest.mark.parametrize(
 600    ("code", "result"),
 601    [
 602        (
 603            b"""
 604                // @req-id: need_001
 605                public class DummyClass1
 606                {
 607                }
 608            """,
 609            "public class DummyClass1",
 610        ),
 611        (
 612            b"""
 613
 614                public class DummyClass1
 615                {
 616                    /* @req-id: need_001 */
 617                    /* @req-id: need_002 */
 618                    public void DummyFunc1()
 619                    {
 620                    }
 621                }
 622            """,
 623            "public void DummyFunc1",
 624        ),
 625    ],
 626)
 627def test_find_next_scope_csharp(code, result, init_csharp_tree_sitter):
 628    parser, query = init_csharp_tree_sitter
 629    comments = utils.extract_comments(code, parser, query)
 630    node: TreeSitterNode | None = utils.find_next_scope(comments[0], CommentType.cs)
 631    assert node
 632    assert node.text
 633    func_def = node.text.decode("utf-8")
 634    assert result in func_def
 635
 636
 637@pytest.mark.parametrize(
 638    ("code", "result"),
 639    [
 640        (
 641            b"""
 642                void dummy_func1(){
 643                // @req-id: need_001
 644                }
 645            """,
 646            "void dummy_func1()",
 647        ),
 648        (
 649            b"""
 650                void dummy_func1(){
 651                /* @req-id: need_001 */
 652                }
 653            """,
 654            "void dummy_func1()",
 655        ),
 656    ],
 657)
 658def test_find_enclosing_scope_cpp(code, result, init_cpp_tree_sitter):
 659    parser, query = init_cpp_tree_sitter
 660    comments = utils.extract_comments(code, parser, query)
 661    node: TreeSitterNode | None = utils.find_enclosing_scope(
 662        comments[0], CommentType.cpp
 663    )
 664    assert node
 665    assert node.text
 666    func_def = node.text.decode("utf-8")
 667    assert result in func_def
 668
 669
 670@pytest.mark.parametrize(
 671    ("code", "result"),
 672    [
 673        (
 674            b"""
 675                public class DummyClass1
 676                {
 677                    // @req-id: need_001
 678                }
 679            """,
 680            "public class DummyClass1",
 681        ),
 682        (
 683            b"""
 684                public class DummyClass1
 685                {
 686                    public void DummyFunc1()
 687                    {
 688                        /* @req-id: need_001 */
 689                    }
 690                }
 691            """,
 692            "public void DummyFunc1()",
 693        ),
 694        (
 695            b"""
 696                public class DummyClass1
 697                {
 698                    public string DummyProperty1
 699                    {
 700                        get
 701                        {
 702                            /* @req-id: need_001 */
 703                            return "dummy";
 704                        }
 705                    }
 706                }
 707            """,
 708            "public string DummyProperty1",
 709        ),
 710    ],
 711)
 712def test_find_enclosing_scope_csharp(code, result, init_csharp_tree_sitter):
 713    parser, query = init_csharp_tree_sitter
 714    comments = utils.extract_comments(code, parser, query)
 715    node: TreeSitterNode | None = utils.find_enclosing_scope(
 716        comments[0], CommentType.cs
 717    )
 718    assert node
 719    assert node.text
 720    func_def = node.text.decode("utf-8")
 721    assert result in func_def
 722
 723
 724@pytest.mark.parametrize(
 725    ("code", "num_comments", "result"),
 726    [
 727        (
 728            b"""
 729                // @req-id: need_001
 730                void dummy_func1(){
 731                }
 732            """,
 733            1,
 734            "// @req-id: need_001",
 735        ),
 736        (
 737            b"""
 738                void dummy_func1(){
 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 dummy_func1(){
 749                }
 750            """,
 751            1,
 752            "/* @req-id: need_001 */",
 753        ),
 754        (
 755            b"""
 756                //  @req-id: need_001
 757                //
 758                //
 759                void dummy_func1(){
 760                }
 761            """,
 762            3,
 763            "//  @req-id: need_001",
 764        ),
 765    ],
 766)
 767def test_cpp_comment(code, num_comments, result, init_cpp_tree_sitter):
 768    parser, query = init_cpp_tree_sitter
 769    comments = utils.extract_comments(code, parser, query)
 770    assert len(comments) == num_comments
 771    comments.sort(key=lambda x: x.start_point.row)
 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                def dummy_func1():
 783                    pass
 784            """,
 785            1,
 786            "# @req-id: need_001",
 787        ),
 788        (
 789            b"""
 790                def dummy_func1():
 791                    # @req-id: need_001
 792                    pass
 793            """,
 794            1,
 795            "# @req-id: need_001",
 796        ),
 797        (
 798            b"""
 799                # single line comment
 800                # @req-id: need_001
 801                def dummy_func1():
 802                    pass
 803            """,
 804            2,
 805            "# single line comment",
 806        ),
 807        (
 808            b"""
 809                def dummy_func1():
 810                    '''
 811                    @req-id: need_001
 812                    '''
 813                    pass
 814            """,
 815            1,
 816            "'''\n                    @req-id: need_001\n                    '''",
 817        ),
 818        (
 819            b"""
 820                def dummy_func1():
 821                    text = '''@req-id: need_001, need_002, this docstring shall not be extracted as comment'''
 822                    # @req-id: need_001
 823                    pass
 824            """,
 825            1,
 826            "# @req-id: need_001",
 827        ),
 828    ],
 829)
 830def test_python_comment(code, num_comments, result, init_python_tree_sitter):
 831    parser, query = init_python_tree_sitter
 832    comments: list[TreeSitterNode] = utils.extract_comments(code, parser, query)
 833    comments.sort(key=lambda x: x.start_point.row)
 834    assert len(comments) == num_comments
 835    assert comments[0].text
 836    assert comments[0].text.decode("utf-8") == result
 837
 838
 839@pytest.mark.parametrize(
 840    ("code", "num_comments", "result"),
 841    [
 842        (
 843            b"""
 844                // @req-id: need_001
 845                void DummyFunc1(){
 846                }
 847            """,
 848            1,
 849            "// @req-id: need_001",
 850        ),
 851        (
 852            b"""
 853                void DummyFunc1(){
 854                // @req-id: need_001
 855                }
 856            """,
 857            1,
 858            "// @req-id: need_001",
 859        ),
 860        (
 861            b"""
 862                /* @req-id: need_001 */
 863                void DummyFunc1(){
 864                }
 865            """,
 866            1,
 867            "/* @req-id: need_001 */",
 868        ),
 869        (
 870            b"""
 871                //  @req-id: need_001
 872                //
 873                //
 874                void DummyFunc1(){
 875                }
 876            """,
 877            3,
 878            "//  @req-id: need_001",
 879        ),
 880    ],
 881)
 882def test_csharp_comment(code, num_comments, result, init_csharp_tree_sitter):
 883    parser, query = init_csharp_tree_sitter
 884    comments: list[TreeSitterNode] = utils.extract_comments(code, parser, query)
 885    comments.sort(key=lambda x: x.start_point.row)
 886    assert len(comments) == num_comments
 887    assert comments[0].text
 888    assert comments[0].text.decode("utf-8") == result
 889
 890
 891@pytest.mark.parametrize(
 892    ("code", "num_comments", "result"),
 893    [
 894        (
 895            b"""
 896                # @req-id: need_001
 897                database:
 898                  host: localhost
 899            """,
 900            1,
 901            "# @req-id: need_001",
 902        ),
 903        (
 904            b"""
 905                services:
 906                  web:
 907                    # @req-id: need_001
 908                    image: nginx:latest
 909            """,
 910            1,
 911            "# @req-id: need_001",
 912        ),
 913        (
 914            b"""
 915                # Top level comment
 916                # @req-id: need_001
 917                version: "3.8"
 918            """,
 919            2,
 920            "# Top level comment",
 921        ),
 922    ],
 923)
 924def test_yaml_comment(code, num_comments, result, init_yaml_tree_sitter):
 925    parser, query = init_yaml_tree_sitter
 926    comments: list[TreeSitterNode] = utils.extract_comments(code, parser, query)
 927    comments.sort(key=lambda x: x.start_point.row)
 928    assert len(comments) == num_comments
 929    assert comments[0].text
 930    assert comments[0].text.decode("utf-8") == result
 931
 932
 933@pytest.mark.parametrize(
 934    ("code", "num_comments", "result"),
 935    [
 936        (
 937            b"""
 938                // @req-id: need_001
 939                func dummyFunc1() {
 940                }
 941            """,
 942            1,
 943            "// @req-id: need_001",
 944        ),
 945        (
 946            b"""
 947                func dummyFunc1() {
 948                // @req-id: need_001
 949                }
 950            """,
 951            1,
 952            "// @req-id: need_001",
 953        ),
 954        (
 955            b"""
 956                /* @req-id: need_001 */
 957                func dummyFunc1() {
 958                }
 959            """,
 960            1,
 961            "/* @req-id: need_001 */",
 962        ),
 963        (
 964            b"""
 965                //  @req-id: need_001
 966                //
 967                //
 968                func dummyFunc1() {
 969                }
 970            """,
 971            3,
 972            "//  @req-id: need_001",
 973        ),
 974    ],
 975)
 976def test_go_comment(code, num_comments, result, init_go_tree_sitter):
 977    parser, query = init_go_tree_sitter
 978    comments: list[TreeSitterNode] = utils.extract_comments(code, parser, query)
 979    comments.sort(key=lambda x: x.start_point.row)
 980    assert len(comments) == num_comments
 981    assert comments[0].text
 982    assert comments[0].text.decode("utf-8") == result
 983
 984
 985@pytest.mark.parametrize(
 986    ("code", "result"),
 987    [
 988        (
 989            b"""
 990                // @req-id: need_001
 991                func dummyFunc1() {
 992                }
 993            """,
 994            "func dummyFunc1()",
 995        ),
 996        (
 997            b"""
 998                func dummyFunc2() {
 999                }
1000                // @req-id: need_001
1001                func dummyFunc1() {
1002                }
1003            """,
1004            "func dummyFunc1()",
1005        ),
1006        (
1007            b"""
1008                /* @req-id: need_001 */
1009                func dummyFunc1() {
1010                }
1011            """,
1012            "func dummyFunc1()",
1013        ),
1014    ],
1015)
1016def test_find_associated_scope_go(code, result, init_go_tree_sitter):
1017    parser, query = init_go_tree_sitter
1018    comments = utils.extract_comments(code, parser, query)
1019    node: TreeSitterNode | None = utils.find_associated_scope(
1020        comments[0], CommentType.go
1021    )
1022    assert node
1023    assert node.text
1024    go_def = node.text.decode("utf-8")
1025    assert result in go_def
1026
1027
1028@pytest.mark.parametrize(
1029    ("code", "result"),
1030    [
1031        (
1032            b"""
1033                // @req-id: need_001
1034                func dummyFunc1() {
1035                }
1036            """,
1037            "func dummyFunc1()",
1038        ),
1039        (
1040            b"""
1041                // @req-id: need_001
1042                type DummyStruct struct {
1043                    Field int
1044                }
1045            """,
1046            "type DummyStruct struct",
1047        ),
1048    ],
1049)
1050def test_find_next_scope_go(code, result, init_go_tree_sitter):
1051    parser, query = init_go_tree_sitter
1052    comments = utils.extract_comments(code, parser, query)
1053    node: TreeSitterNode | None = utils.find_next_scope(comments[0], CommentType.go)
1054    assert node
1055    assert node.text
1056    go_def = node.text.decode("utf-8")
1057    assert result in go_def
1058
1059
1060@pytest.mark.parametrize(
1061    ("code", "result"),
1062    [
1063        (
1064            b"""
1065                func dummyFunc1() {
1066                    // @req-id: need_001
1067                }
1068            """,
1069            "func dummyFunc1()",
1070        ),
1071        (
1072            b"""
1073                func dummyFunc1() {
1074                    /* @req-id: need_001 */
1075                }
1076            """,
1077            "func dummyFunc1()",
1078        ),
1079    ],
1080)
1081def test_find_enclosing_scope_go(code, result, init_go_tree_sitter):
1082    parser, query = init_go_tree_sitter
1083    comments = utils.extract_comments(code, parser, query)
1084    node: TreeSitterNode | None = utils.find_enclosing_scope(
1085        comments[0], CommentType.go
1086    )
1087    assert node
1088    assert node.text
1089    go_def = node.text.decode("utf-8")
1090    assert result in go_def
1091
1092
1093@pytest.mark.parametrize(
1094    ("git_url", "rev", "project_path", "filepath", "lineno", "result"),
1095    [
1096        (
1097            "git@github.com:useblocks/sphinx-codelinks.git",
1098            "beef1234",
1099            Path(__file__).parent.parent,
1100            # ABSOLUTE, and under `project_path`. `form_https_url` resolves the
1101            # filepath with `.absolute()`, i.e. against the CWD, so a relative one
1102            # here only agrees with `project_path` when pytest happens to run from
1103            # the package directory -- which it does not when the whole workspace's
1104            # suite is invoked from the repository root
1105            Path(__file__).parent.parent / "example" / "to" / "here",
1106            3,
1107            "https://github.com/useblocks/sphinx-codelinks/blob/beef1234/example/to/here#L3",
1108        )
1109    ],
1110)
1111def test_form_https_url(
1112    git_url, rev, project_path, filepath, lineno, result
1113):  # need to have these args
1114    url = utils.form_https_url(git_url, rev, project_path, filepath, lineno=lineno)
1115    assert url == result
1116
1117
1118def get_git_path() -> str:
1119    """Get the path to the git executable."""
1120    git_path = shutil.which("git")
1121    if not git_path:
1122        raise FileNotFoundError("Git executable not found")
1123    if not Path(git_path).is_file():
1124        raise FileNotFoundError("Git executable path is invalid")
1125    return git_path
1126
1127
1128def init_git_repo(repo_path: Path, remote_url: str) -> Path:
1129    """Initialize a git repository for testing."""
1130    git_dir = repo_path / "test_repo"
1131    src_dir = git_dir / "src"
1132    src_dir.mkdir(parents=True)
1133
1134    git_path = get_git_path()
1135    if not git_path:
1136        raise FileNotFoundError("Git executable not found")
1137    if not Path(git_path).is_file():
1138        raise FileNotFoundError("Git executable path is invalid")
1139
1140    # Initialize git repo
1141    subprocess.run([git_path, "init"], cwd=git_dir, check=True, capture_output=True)
1142    subprocess.run(
1143        [git_path, "config", "user.email", "test@example.com"], cwd=git_dir, check=True
1144    )
1145    subprocess.run(
1146        [git_path, "config", "user.name", "Test User"], cwd=git_dir, check=True
1147    )
1148
1149    # Create a test file and commit
1150    test_file = src_dir / "test_file.py"
1151    test_file.write_text("# Test file\nprint('hello')\n")
1152    subprocess.run([git_path, "add", "."], cwd=git_dir, check=True)
1153    subprocess.run(
1154        [git_path, "commit", "-m", "Initial commit"], cwd=git_dir, check=True
1155    )
1156
1157    # Add a remote
1158    subprocess.run(
1159        [git_path, "remote", "add", "origin", remote_url],
1160        cwd=git_dir,
1161        check=True,
1162    )
1163
1164    return git_dir
1165
1166
1167@pytest.fixture(
1168    params=[
1169        ("test_repo_git", "git@github.com:test-user/test-repo.git"),
1170        ("test_repo_https", "https://github.com/test-user/test-repo.git"),
1171    ]
1172)
1173def git_repo(tmp_path: str, request: pytest.FixtureRequest) -> tuple[Path, str]:
1174    """Create git repos for testing."""
1175    repo_name, remote_url = request.param
1176    repo_path = Path(tmp_path) / repo_name
1177    repo_path = init_git_repo(repo_path, remote_url)
1178    return repo_path, remote_url
1179
1180
1181def get_current_commit_hash(git_dir: Path) -> str:
1182    """Get the current commit hash of the git repository."""
1183    git_path = get_git_path()
1184    result = subprocess.run(
1185        [git_path, "rev-parse", "HEAD"],
1186        cwd=git_dir,
1187        check=True,
1188        capture_output=True,
1189        text=True,
1190    )
1191
1192    return str(result.stdout.strip())
1193
1194
1195def test_locate_git_root(git_repo: tuple[Path, str]) -> None:
1196    repo_path = git_repo[0]
1197    src_dir = repo_path / "src"
1198    git_root = utils.locate_git_root(src_dir)
1199    assert git_root == repo_path
1200
1201
1202def test_get_remote_url(git_repo: tuple[Path, str]) -> None:
1203    repo_path, expected_url = git_repo
1204    remote_url = utils.get_remote_url(repo_path)
1205    assert remote_url == expected_url
1206
1207
1208def test_get_current_rev(git_repo: tuple[Path, str]) -> None:
1209    repo_path, _ = git_repo
1210    current_rev = get_current_commit_hash(repo_path)
1211    assert current_rev == utils.get_current_rev(repo_path)
1212
1213
1214@pytest.fixture(params=["detached", "branch"])
1215def git_worktree(
1216    tmp_path: Path, request: pytest.FixtureRequest
1217) -> tuple[Path, str, str]:
1218    """A LINKED WORKTREE of a scratch repository, in both shapes git makes.
1219
1220    A worktree is not a checkout with a `.git` directory: `.git` is a FILE holding
1221    `gitdir: <path>`, `config` lives in the main repository's git directory (named by
1222    the worktree's `commondir`), and only per-worktree state -- `HEAD` among it -- is in
1223    the worktree's own. `detached` puts a SHA in `HEAD`; `branch` puts `ref:
1224    refs/heads/...`, whose target is a shared ref in the common directory.
1225
1226    This repository is worked in worktrees as a matter of routine, so a helper that
1227    demands a `.git` directory fails for every developer while staying green in CI
1228    (useblocks/sphinx-codelinks#106).
1229    """
1230    remote_url = "https://github.com/test-user/test-repo.git"
1231    repo_path = init_git_repo(tmp_path / "main", remote_url)
1232    git_path = get_git_path()
1233    worktree_path = tmp_path / "worktree"
1234    add = [git_path, "worktree", "add"]
1235    add += (
1236        ["--detach", str(worktree_path), "HEAD"]
1237        if request.param == "detached"
1238        else [str(worktree_path), "-b", "a-worktree-branch"]
1239    )
1240    subprocess.run(add, cwd=repo_path, check=True, capture_output=True)
1241    assert (worktree_path / ".git").is_file(), "a linked worktree's .git is a file"
1242    return worktree_path, remote_url, get_current_commit_hash(repo_path)
1243
1244
1245def test_locate_git_root_worktree(git_worktree: tuple[Path, str, str]) -> None:
1246    worktree_path, _, _ = git_worktree
1247    assert utils.locate_git_root(worktree_path / "src") == worktree_path
1248
1249
1250def test_get_remote_url_worktree(git_worktree: tuple[Path, str, str]) -> None:
1251    worktree_path, remote_url, _ = git_worktree
1252    assert utils.get_remote_url(worktree_path) == remote_url
1253
1254
1255def test_get_current_rev_worktree(git_worktree: tuple[Path, str, str]) -> None:
1256    worktree_path, _, rev = git_worktree
1257    assert utils.get_current_rev(worktree_path) == rev
1258
1259
1260def test_get_current_rev_packed_refs(git_worktree: tuple[Path, str, str]) -> None:
1261    """`git gc` packs the loose refs away, and the branch ref is then in `packed-refs`.
1262
1263    Nothing unusual has to happen for this: `git gc` runs unattended on any long-lived
1264    checkout, and afterwards there is no `refs/heads/<branch>` FILE in either the
1265    worktree's git directory or the common one. It is the same failure shape as
1266    useblocks/sphinx-codelinks#106 reached by a different route -- and CI never sees it,
1267    because `actions/checkout` and Read the Docs both leave a detached HEAD.
1268    """
1269    worktree_path, _, rev = git_worktree
1270    git_path = get_git_path()
1271    subprocess.run(
1272        [git_path, "pack-refs", "--all"],
1273        cwd=worktree_path,
1274        check=True,
1275        capture_output=True,
1276    )
1277    git_dir = utils._git_dir(worktree_path)
1278    assert git_dir is not None
1279    common = utils._git_common_dir(git_dir)
1280    assert not list((common / "refs" / "heads").glob("*")), (
1281        "the loose refs should be gone"
1282    )
1283
1284    assert utils.get_current_rev(worktree_path) == rev
1285
1286
1287def test_get_current_rev_detached_head(tmp_path: Path) -> None:
1288    """In a detached HEAD (e.g. CI checkouts) .git/HEAD holds the commit SHA
1289    directly; get_current_rev returns it rather than warning and giving up."""
1290    git_root = tmp_path / "repo"
1291    (git_root / ".git").mkdir(parents=True)
1292    sha = "a1b2c3d4e5f60718293a4b5c6d7e8f9012345678"
1293    (git_root / ".git" / "HEAD").write_text(f"{sha}\n")
1294
1295    assert utils.get_current_rev(git_root) == sha
1296
1297
1298# ========== YAML-specific tests ==========
1299
1300
1301@pytest.mark.parametrize(
1302    ("code", "expected_structure"),
1303    [
1304        # Basic key-value pair
1305        (
1306            b"""
1307                # Comment before key
1308                database:
1309                  host: localhost
1310            """,
1311            "database:",
1312        ),
1313        # Comment in nested structure
1314        (
1315            b"""
1316                services:
1317                  web:
1318                    # Comment before image
1319                    image: nginx:latest
1320            """,
1321            "image: nginx:latest",
1322        ),
1323        # Comment before list item
1324        (
1325            b"""
1326                items:
1327                  # Comment before list item
1328                  - name: item1
1329                    value: test
1330            """,
1331            "- name: item1",
1332        ),
1333        # Comment in document structure
1334        (
1335            b"""---
1336# Comment in document
1337version: "3.8"
1338services:
1339  app:
1340    build: .
1341            """,
1342            "version:",
1343        ),
1344        # Flow mapping structure
1345        (
1346            b"""
1347                # Comment before flow mapping
1348                config: {debug: true, port: 8080}
1349            """,
1350            "config:",
1351        ),
1352    ],
1353)
1354def test_find_yaml_next_structure(code, expected_structure, init_yaml_tree_sitter):
1355    """Test the find_yaml_next_structure function."""
1356    parser, query = init_yaml_tree_sitter
1357    comments = utils.extract_comments(code, parser, query)
1358    assert comments, "No comments found in the code"
1359
1360    next_structure = utils.find_yaml_next_structure(comments[0])
1361    assert next_structure, "No next structure found"
1362    structure_text = next_structure.text.decode("utf-8")
1363    assert expected_structure in structure_text
1364
1365
1366@pytest.mark.parametrize(
1367    ("code", "expected_structure"),
1368    [
1369        # Comment associated with key-value pair
1370        (
1371            b"""
1372                # Database configuration
1373                database:
1374                  host: localhost
1375                  port: 5432
1376            """,
1377            "database:",
1378        ),
1379        # Comment associated with nested structure
1380        (
1381            b"""
1382                services:
1383                  web:
1384                    # Web service image
1385                    image: nginx:latest
1386                    ports:
1387                      - "80:80"
1388            """,
1389            "image: nginx:latest",
1390        ),
1391        # Comment associated with list item
1392        (
1393            b"""
1394                dependencies:
1395                  # First dependency
1396                  - name: redis
1397                    version: "6.0"
1398                  - name: postgres
1399                    version: "13"
1400            """,
1401            "- name: redis",
1402        ),
1403        # Comment inside parent structure
1404        (
1405            b"""
1406                app:
1407                  # Internal comment
1408                  name: myapp
1409                  version: "1.0"
1410            """,
1411            "name: myapp",
1412        ),
1413    ],
1414)
1415def test_find_yaml_associated_structure(
1416    code, expected_structure, init_yaml_tree_sitter
1417):
1418    """Test the find_yaml_associated_structure function."""
1419    parser, query = init_yaml_tree_sitter
1420    comments = utils.extract_comments(code, parser, query)
1421    assert comments, "No comments found in the code"
1422
1423    associated_structure = utils.find_yaml_associated_structure(comments[0])
1424    assert associated_structure, "No associated structure found"
1425    structure_text = associated_structure.text.decode("utf-8")
1426    assert expected_structure in structure_text
1427
1428
1429@pytest.mark.parametrize(
1430    ("code", "expected_results"),
1431    [
1432        # Multiple comments in sequence
1433        (
1434            b"""
1435                # First comment
1436                # Second comment
1437                database:
1438                  host: localhost
1439            """,
1440            ["database:", "database:"],  # Both comments should associate with database
1441        ),
1442        # Comments at different nesting levels
1443        (
1444            b"""
1445                # Top level comment
1446                services:
1447                  web:
1448                    # Nested comment
1449                    image: nginx:latest
1450            """,
1451            ["services:", "image: nginx:latest"],
1452        ),
1453    ],
1454)
1455def test_multiple_yaml_comments(code, expected_results, init_yaml_tree_sitter):
1456    """Test handling of multiple YAML comments in the same file."""
1457    parser, query = init_yaml_tree_sitter
1458    comments = utils.extract_comments(code, parser, query)
1459    comments.sort(key=lambda x: x.start_point.row)
1460
1461    assert len(comments) == len(expected_results), (
1462        f"Expected {len(expected_results)} comments, found {len(comments)}"
1463    )
1464
1465    for i, comment in enumerate(comments):
1466        associated_structure = utils.find_yaml_associated_structure(comment)
1467        assert associated_structure, f"No associated structure found for comment {i}"
1468        structure_text = associated_structure.text.decode("utf-8")
1469        assert expected_results[i] in structure_text
1470
1471
1472@pytest.mark.parametrize(
1473    ("code", "has_structure"),
1474    [
1475        # Comment at end of file with no following structure
1476        (
1477            b"""
1478database:
1479  host: localhost
1480# End of file comment
1481            """,
1482            True,  # This will actually find the parent database structure
1483        ),
1484        # Comment with only whitespace after
1485        (
1486            b"""
1487                # Lonely comment
1488
1489
1490            """,
1491            False,
1492        ),
1493        # Comment before valid structure
1494        (
1495            b"""
1496                # Valid comment
1497                key: value
1498            """,
1499            True,
1500        ),
1501    ],
1502)
1503def test_yaml_edge_cases(code, has_structure, init_yaml_tree_sitter):
1504    """Test edge cases in YAML comment processing."""
1505    parser, query = init_yaml_tree_sitter
1506    comments = utils.extract_comments(code, parser, query)
1507
1508    if comments:
1509        structure = utils.find_yaml_associated_structure(comments[0])
1510        if has_structure:
1511            assert structure, "Expected to find associated structure"
1512        else:
1513            assert structure is None, "Expected no associated structure"
1514    else:
1515        assert not has_structure, "No comments found but structure was expected"
1516
1517
1518@pytest.mark.parametrize(
1519    ("code", "expected_structures"),
1520    [
1521        # Simpler nested YAML structure
1522        (
1523            b"""# Global configuration
1524version: "3.8"
1525
1526# Services section
1527services:
1528  web:
1529    image: nginx:latest
1530    # Port configuration
1531    ports:
1532      - "80:80"
1533            """,
1534            [
1535                "version:",  # Global configuration
1536                "services:",  # Services section
1537                '- "80:80"',  # Port configuration
1538            ],
1539        ),
1540    ],
1541)
1542def test_complex_yaml_structure(code, expected_structures, init_yaml_tree_sitter):
1543    """Test complex nested YAML structures with multiple comments."""
1544    parser, query = init_yaml_tree_sitter
1545    comments = utils.extract_comments(code, parser, query)
1546    comments.sort(key=lambda x: x.start_point.row)
1547
1548    assert len(comments) == len(expected_structures), (
1549        f"Expected {len(expected_structures)} comments, found {len(comments)}"
1550    )
1551
1552    for i, comment in enumerate(comments):
1553        associated_structure = utils.find_yaml_associated_structure(comment)
1554        assert associated_structure, f"No associated structure found for comment {i}"
1555        structure_text = associated_structure.text.decode("utf-8")
1556        assert expected_structures[i] in structure_text, (
1557            f"Expected '{expected_structures[i]}' in structure text: '{structure_text}'"
1558        )
1559
1560
1561@pytest.mark.parametrize(
1562    ("code", "expected_type"),
1563    [
1564        # Block mapping pair
1565        (
1566            b"""
1567                # Comment
1568                key: value
1569            """,
1570            "block_mapping_pair",
1571        ),
1572        # Block sequence item
1573        (
1574            b"""
1575                items:
1576                  # Comment
1577                  - item1
1578            """,
1579            "block_sequence_item",
1580        ),
1581        # Nested block mapping
1582        (
1583            b"""
1584                services:
1585                  # Comment
1586                  web:
1587                    image: nginx
1588            """,
1589            "block_mapping_pair",
1590        ),
1591    ],
1592)
1593def test_yaml_structure_types(code, expected_type, init_yaml_tree_sitter):
1594    """Test that YAML structures return the correct node types."""
1595    parser, query = init_yaml_tree_sitter
1596    comments = utils.extract_comments(code, parser, query)
1597    assert comments, "No comments found"
1598
1599    structure = utils.find_yaml_associated_structure(comments[0])
1600    assert structure, "No associated structure found"
1601    assert structure.type == expected_type, (
1602        f"Expected type {expected_type}, got {structure.type}"
1603    )
1604
1605
1606def test_yaml_document_structure(init_yaml_tree_sitter):
1607    """Test YAML document structure handling."""
1608    code = b"""---
1609# Document comment
1610apiVersion: v1
1611kind: ConfigMap
1612metadata:
1613  name: my-config
1614data:
1615  # Data comment
1616  config.yml: |
1617    setting: value
1618    """
1619
1620    parser, query = init_yaml_tree_sitter
1621    comments = utils.extract_comments(code, parser, query)
1622    comments.sort(key=lambda x: x.start_point.row)
1623
1624    # Should find both comments
1625    assert len(comments) >= 2, f"Expected at least 2 comments, found {len(comments)}"
1626
1627    # First comment should associate with apiVersion
1628    first_structure = utils.find_yaml_associated_structure(comments[0])
1629    assert first_structure, "No structure found for first comment"
1630    first_text = first_structure.text.decode("utf-8")
1631    assert "apiVersion:" in first_text
1632
1633    # Second comment should associate with config.yml
1634    second_structure = utils.find_yaml_associated_structure(comments[1])
1635    assert second_structure, "No structure found for second comment"
1636    second_text = second_structure.text.decode("utf-8")
1637    assert "config.yml:" in second_text
1638
1639
1640def test_yaml_inline_comments_current_behavior(init_yaml_tree_sitter):
1641    """Test improved behavior of inline comments in YAML after the fix."""
1642    code = b"""key1: value1  # inline comment about key1
1643key2: value2
1644key3: value3  # inline comment about key3
1645"""
1646
1647    parser, query = init_yaml_tree_sitter
1648    comments = utils.extract_comments(code, parser, query)
1649    comments.sort(key=lambda x: x.start_point.row)
1650
1651    assert len(comments) == 2, f"Expected 2 comments, found {len(comments)}"
1652
1653    # Fixed behavior: inline comment about key1 now correctly associates with key1
1654    first_structure = utils.find_yaml_associated_structure(comments[0])
1655    assert first_structure, "No structure found for first comment"
1656    first_text = first_structure.text.decode("utf-8")
1657    assert "key1:" in first_text, f"Expected 'key1:' in '{first_text}'"
1658
1659    # Fixed behavior: inline comment about key3 now correctly associates with key3
1660    second_structure = utils.find_yaml_associated_structure(comments[1])
1661    assert second_structure, "No structure found for second comment"
1662    second_text = second_structure.text.decode("utf-8")
1663    assert "key3:" in second_text, f"Expected 'key3:' in '{second_text}'"
1664
1665
1666@pytest.mark.parametrize(
1667    ("code", "expected_associations"),
1668    [
1669        # Basic inline comment case
1670        (
1671            b"""key1: value1  # comment about key1
1672key2: value2
1673            """,
1674            ["key1:"],  # Now correctly associates with key1
1675        ),
1676        # Multiple inline comments
1677        (
1678            b"""database:
1679  host: localhost  # production server
1680  port: 5432       # default postgres port
1681  user: admin
1682            """,
1683            [
1684                "host: localhost",
1685                "port: 5432",
1686            ],  # Now correctly associates with the right structures
1687        ),
1688    ],
1689)
1690def test_yaml_inline_comments_fixed_behavior(
1691    code, expected_associations, init_yaml_tree_sitter
1692):
1693    """Test that inline comments now correctly associate with the structure they comment on."""
1694    parser, query = init_yaml_tree_sitter
1695    comments = utils.extract_comments(code, parser, query)
1696    comments.sort(key=lambda x: x.start_point.row)
1697
1698    assert len(comments) == len(expected_associations), (
1699        f"Expected {len(expected_associations)} comments, found {len(comments)}"
1700    )
1701
1702    for i, comment in enumerate(comments):
1703        structure = utils.find_yaml_associated_structure(comment)
1704        assert structure, f"No structure found for comment {i}"
1705        structure_text = structure.text.decode("utf-8")
1706        assert expected_associations[i] in structure_text, (
1707            f"Expected '{expected_associations[i]}' in structure text: '{structure_text}'"
1708        )
1709
1710
1711@pytest.mark.parametrize(
1712    ("code", "expected_associations"),
1713    [
1714        # Inline comments with list items
1715        (
1716            b"""items:
1717  - name: item1  # first item
1718  - name: item2  # second item
1719            """,
1720            [
1721                "name: item1",
1722                "name: item2",
1723            ],  # The inline comment finds the key-value pair within the list item
1724        ),
1725        # Mixed inline and block comments
1726        (
1727            b"""# Block comment for database
1728database:
1729  host: localhost  # inline comment for host
1730  port: 5432
1731  # Block comment for user
1732  user: admin
1733            """,
1734            ["database:", "host: localhost", "user: admin"],
1735        ),
1736        # Inline comments in nested structures
1737        (
1738            b"""services:
1739  web:
1740    image: nginx  # web server image
1741    ports:
1742      - "80:80"   # http port
1743            """,
1744            ["image: nginx", '- "80:80"'],
1745        ),
1746    ],
1747)
1748def test_yaml_inline_comments_comprehensive(
1749    code, expected_associations, init_yaml_tree_sitter
1750):
1751    """Comprehensive test for inline comment behavior in various YAML structures."""
1752    parser, query = init_yaml_tree_sitter
1753    comments = utils.extract_comments(code, parser, query)
1754    comments.sort(key=lambda x: x.start_point.row)
1755
1756    assert len(comments) == len(expected_associations), (
1757        f"Expected {len(expected_associations)} comments, found {len(comments)}"
1758    )
1759
1760    for i, comment in enumerate(comments):
1761        structure = utils.find_yaml_associated_structure(comment)
1762        assert structure, (
1763            f"No structure found for comment {i}: '{comment.text.decode('utf-8')}'"
1764        )
1765        structure_text = structure.text.decode("utf-8")
1766        assert expected_associations[i] in structure_text, (
1767            f"Comment {i} '{comment.text.decode('utf-8')}' -> Expected '{expected_associations[i]}' in '{structure_text}'"
1768        )