[docs]  1# @Test suite for one-line comment parser functionality, TEST_OLP_1, test, [IMPL_OLP_1]
  2import pytest
  3
  4from sphinx_codelinks.analyse.oneline_parser import (
  5    OnelineParserInvalidWarning,
  6    WarningSubTypeEnum,
  7    oneline_parser,
  8)
  9from sphinx_codelinks.config import ESCAPE, UNIX_NEWLINE, OneLineCommentStyle
 10
 11from .conftest import ONELINE_COMMENT_STYLE, ONELINE_COMMENT_STYLE_DEFAULT
 12
 13
 14@pytest.mark.parametrize(
 15    "oneline, result",
 16    [
 17        (
 18            f"@title 1, IMPL_1 {UNIX_NEWLINE}",
 19            {
 20                "title": "title 1",
 21                "id": "IMPL_1",
 22                "type": "impl",
 23                "links": [],
 24                "start_column": 1,
 25                "end_column": 17,
 26            },
 27        ),
 28        # Test case for leading space after start sequence (bug fix)
 29        (
 30            f"@ title 1, IMPL_1 {UNIX_NEWLINE}",
 31            {
 32                "title": "title 1",
 33                "id": "IMPL_1",
 34                "type": "impl",
 35                "links": [],
 36                "start_column": 1,
 37                "end_column": 18,
 38            },
 39        ),
 40        # Test case for multiple leading spaces after start sequence
 41        (
 42            f"@   title 1, IMPL_1 {UNIX_NEWLINE}",
 43            {
 44                "title": "title 1",
 45                "id": "IMPL_1",
 46                "type": "impl",
 47                "links": [],
 48                "start_column": 1,
 49                "end_column": 20,
 50            },
 51        ),
 52        # Test case for trailing space before end sequence
 53        (
 54            f"@title 1, IMPL_1  {UNIX_NEWLINE}",
 55            {
 56                "title": "title 1",
 57                "id": "IMPL_1",
 58                "type": "impl",
 59                "links": [],
 60                "start_column": 1,
 61                "end_column": 18,
 62            },
 63        ),
 64        # Test case for both leading and trailing spaces
 65        (
 66            f"@  title 1, IMPL_1   {UNIX_NEWLINE}",
 67            {
 68                "title": "title 1",
 69                "id": "IMPL_1",
 70                "type": "impl",
 71                "links": [],
 72                "start_column": 1,
 73                "end_column": 21,
 74            },
 75        ),
 76    ],
 77)
 78def test_oneline_parser_default_config_positive(
 79    oneline: str, result: dict[str, str | list[str]]
 80) -> None:
 81    oneline_need = oneline_parser(oneline, ONELINE_COMMENT_STYLE_DEFAULT)
 82    assert oneline_need == result
 83
 84
 85# Test case for space as field separator (as mentioned by Kilian)
 86# Example: @Implementation <field1> <field2>
 87ONELINE_COMMENT_STYLE_SPACE_SEPARATOR = OneLineCommentStyle(
 88    start_sequence="@",
 89    end_sequence=UNIX_NEWLINE,
 90    field_split_char=" ",
 91    needs_fields=[
 92        {"name": "title"},
 93        {"name": "id"},
 94        {"name": "type", "default": "impl"},
 95    ],
 96)
 97
 98
 99@pytest.mark.parametrize(
100    "oneline, result",
101    [
102        # Basic space-separated fields
103        (
104            f"@Implementation IMPL_1{UNIX_NEWLINE}",
105            {
106                "title": "Implementation",
107                "id": "IMPL_1",
108                "type": "impl",
109                "start_column": 1,
110                "end_column": 22,
111            },
112        ),
113        # Space separator with explicit type
114        (
115            f"@MyFeature FEAT_001 feature{UNIX_NEWLINE}",
116            {
117                "title": "MyFeature",
118                "id": "FEAT_001",
119                "type": "feature",
120                "start_column": 1,
121                "end_column": 27,
122            },
123        ),
124        # Leading space after @ (the bug Kilian reported)
125        (
126            f"@ Implementation IMPL_2{UNIX_NEWLINE}",
127            {
128                "title": "Implementation",
129                "id": "IMPL_2",
130                "type": "impl",
131                "start_column": 1,
132                "end_column": 23,
133            },
134        ),
135        # Trailing space before newline
136        (
137            f"@Implementation IMPL_3 {UNIX_NEWLINE}",
138            {
139                "title": "Implementation",
140                "id": "IMPL_3",
141                "type": "impl",
142                "start_column": 1,
143                "end_column": 23,
144            },
145        ),
146        # Multiple leading spaces after @
147        (
148            f"@  Title ID_456{UNIX_NEWLINE}",
149            {
150                "title": "Title",
151                "id": "ID_456",
152                "type": "impl",
153                "start_column": 1,
154                "end_column": 15,
155            },
156        ),
157        # Title contain spaces
158        (
159            f"@  Title\ escape\ space ID_456{UNIX_NEWLINE}",
160            {
161                "title": "Title escape space",
162                "id": "ID_456",
163                "type": "impl",
164                "start_column": 1,
165                "end_column": 30,
166            },
167        ),
168    ],
169)
170def test_oneline_parser_space_separator(
171    oneline: str, result: dict[str, str | list[str]]
172) -> None:
173    """Test oneline parser with space as field separator."""
174    oneline_need = oneline_parser(oneline, ONELINE_COMMENT_STYLE_SPACE_SEPARATOR)
175    assert oneline_need == result
176
177
178@pytest.mark.parametrize(
179    "oneline, result",
180    [
181        (
182            "[[IMPL_1, title 1]]",
183            {
184                "id": "IMPL_1",
185                "title": "title 1",
186                "type": "impl",
187                "links": [],
188                "status": "open",
189                "priority": "low",
190                "start_column": 2,
191                "end_column": 17,
192            },
193        ),
194        (
195            "[[IMPL_2, title 2, impl, [], closed]]",
196            {
197                "id": "IMPL_2",
198                "title": "title 2",
199                "type": "impl",
200                "links": [],
201                "status": "closed",
202                "priority": "low",
203                "start_column": 2,
204                "end_column": 35,
205            },
206        ),
207        (
208            "[[IMPL_3, title\, 3, impl, [], closed]]",
209            {
210                "id": "IMPL_3",
211                "title": "title, 3",
212                "type": "impl",
213                "links": [],
214                "status": "closed",
215                "priority": "low",
216                "start_column": 2,
217                "end_column": 37,
218            },
219        ),
220        (
221            "[[IMPL_5, title 5, impl, [SPEC_1, SPEC_2], open]]",
222            {
223                "id": "IMPL_5",
224                "title": "title 5",
225                "type": "impl",
226                "links": ["SPEC_1", "SPEC_2"],
227                "status": "open",
228                "priority": "low",
229                "start_column": 2,
230                "end_column": 47,
231            },
232        ),
233        (
234            "[[IMPL_7, Function has a, in the title]]",
235            {
236                "id": "IMPL_7",
237                "title": "Function has a",
238                "type": "in the title",
239                "links": [],
240                "status": "open",
241                "priority": "low",
242                "start_column": 2,
243                "end_column": 38,
244            },
245        ),
246        (
247            "[[IMPL_8, [Title starts with a bracket], impl]]",
248            {
249                "id": "IMPL_8",
250                "title": "[Title starts with a bracket]",
251                "type": "impl",
252                "links": [],
253                "status": "open",
254                "priority": "low",
255                "start_column": 2,
256                "end_column": 45,
257            },
258        ),
259        (
260            "[[IMPL_9, Function Baz, impl, [SPEC_1, SPEC_2[text], SPEC_3], open]]",
261            {
262                "id": "IMPL_9",
263                "title": "Function Baz",
264                "type": "impl",
265                "links": ["SPEC_1", "SPEC_2[text"],
266                "status": "SPEC_3]",
267                "priority": "open",
268                "start_column": 2,
269                "end_column": 66,
270            },
271        ),
272        (
273            "[[IMPL_10, title 10, impl, [SPEC_1], open]]",
274            {
275                "id": "IMPL_10",
276                "title": "title 10",
277                "type": "impl",
278                "links": ["SPEC_1"],
279                "status": "open",
280                "priority": "low",
281                "start_column": 2,
282                "end_column": 41,
283            },
284        ),
285        (
286            "[[IMPL_11, title 11, impl, [SPEC\,_1], open]]",
287            {
288                "id": "IMPL_11",
289                "title": "title 11",
290                "type": "impl",
291                "links": ["SPEC,_1"],
292                "status": "open",
293                "priority": "low",
294                "start_column": 2,
295                "end_column": 43,
296            },
297        ),
298        (
299            "[[IMPL_12, title 12, impl, [\[SPEC\,_1\]], open]]",
300            {
301                "id": "IMPL_12",
302                "title": "title 12",
303                "type": "impl",
304                "links": ["[SPEC,_1]"],
305                "status": "open",
306                "priority": "low",
307                "start_column": 2,
308                "end_column": 47,
309            },
310        ),
311        (
312            "[[IMPL_13, title\\ 13, impl, [\[SPEC\,_1\]], open]]",
313            {
314                "id": "IMPL_13",
315                "title": "title\ 13",
316                "type": "impl",
317                "links": ["[SPEC,_1]"],
318                "status": "open",
319                "priority": "low",
320                "start_column": 2,
321                "end_column": 48,
322            },
323        ),
324    ],
325)
326def test_oneline_parser_custom_config_positive(
327    oneline: str, result: dict[str, str | list[str]]
328) -> None:
329    oneline_need = oneline_parser(oneline, ONELINE_COMMENT_STYLE)
330    assert oneline_need == result
331
332
333@pytest.mark.parametrize(
334    "oneline, result",
335    [
336        (
337            f"[[IMPL_4, title{ESCAPE}{ESCAPE}, 4, impl, [], closed]]",
338            OnelineParserInvalidWarning(
339                sub_type=WarningSubTypeEnum.missing_square_brackets,
340                msg="Field links with 'type': 'list[str]' must be given with '[]' brackets",
341            ),
342        ),
343        (
344            "[[IMPL_2, Function Bar, impl, [SPEC_1, SPEC_2, open]]",
345            OnelineParserInvalidWarning(
346                sub_type=WarningSubTypeEnum.missing_square_brackets,
347                msg="Field links with 'type': 'list[str]' must be given with '[]' brackets",
348            ),
349        ),
350        (
351            "[[IMPL_13, title 13, impl, 13[\[SPEC\,_1\]], open]]",
352            OnelineParserInvalidWarning(
353                sub_type=WarningSubTypeEnum.not_start_or_end_with_square_brackets,
354                msg="Field links with 'type': 'list[str]' must start with '[' and end with ']'",
355            ),
356        ),
357        (
358            "[[IMPL_14, title 13, impl, 13[\[SPEC\,_1\]], open, low, high]]",
359            OnelineParserInvalidWarning(
360                sub_type=WarningSubTypeEnum.too_many_fields,
361                msg="7 given fields. They shall be less than 6",
362            ),
363        ),
364        (
365            "[[IMPL_15]]",
366            OnelineParserInvalidWarning(
367                sub_type=WarningSubTypeEnum.too_few_fields,
368                msg="1 given fields. They shall be more than 2",
369            ),
370        ),
371        (
372            f"[[IMPL_16]]{UNIX_NEWLINE}, title 16]]",
373            OnelineParserInvalidWarning(
374                sub_type=WarningSubTypeEnum.newline_in_field,
375                msg="Field id has newline character. It is not allowed",
376            ),
377        ),
378    ],
379)
380def test_oneline_parser_custom_config_negative(
381    oneline: str, result: OnelineParserInvalidWarning
382) -> None:
383    res = oneline_parser(oneline, ONELINE_COMMENT_STYLE)
384    assert res == result
385
386
387@pytest.mark.parametrize(
388    "oneline, result",
389    [
390        (
391            f"@title 17]]{UNIX_NEWLINE}, IMPL_17 {UNIX_NEWLINE}",
392            OnelineParserInvalidWarning(
393                sub_type=WarningSubTypeEnum.newline_in_field,
394                msg="Field title has newline character. It is not allowed",
395            ),
396        ),
397        (
398            f"@title 17]], IMPL_17, impl, [SPEC_3, SPEC_4{UNIX_NEWLINE} ] {UNIX_NEWLINE}",
399            OnelineParserInvalidWarning(
400                sub_type=WarningSubTypeEnum.newline_in_field,
401                msg="Field links has newline character. It is not allowed",
402            ),
403        ),
404    ],
405)
406def test_oneline_parser_default_config_negative(
407    oneline: str, result: OnelineParserInvalidWarning
408) -> None:
409    assert oneline_parser(oneline, ONELINE_COMMENT_STYLE_DEFAULT) == result
410
411
412@pytest.mark.parametrize(
413    "oneline_config, result",
414    [
415        (
416            OneLineCommentStyle(
417                start_sequence="[[",
418                end_sequence="]]",
419                field_split_char=",",
420                needs_fields=[
421                    {"name": "title"},
422                    {"name": "id"},
423                    {"name": "type", "default": "impl"},
424                    {"name": "links", "type": "list[]", "default": []},  # wrong type
425                ],
426            ),
427            [
428                "Schema validation error in need_fields 'links': 'list[]' is not one of ['str', 'list[str]']"
429            ],
430        ),
431        (
432            OneLineCommentStyle(
433                start_sequence="[[",
434                end_sequence="]]",
435                field_split_char=",",
436                needs_fields=[
437                    {"name": "title"},
438                    {"name": "id"},
439                    {"name": "type", "default": 123},  # int is invalid
440                    {"name": "links", "type": "list[str]", "default": []},
441                ],
442            ),
443            [
444                "Schema validation error in need_fields 'type': 123 is not of type 'string'"
445            ],
446        ),
447        (
448            OneLineCommentStyle(
449                start_sequence="[[",
450                end_sequence="]]",
451                field_split_char=",",
452                needs_fields=[
453                    {"name": "title", "qwe": "qwe"},  # invalid qwe filed
454                    {"name": "id"},
455                    {"name": "type", "default": "impl"},
456                    {"name": "links", "type": "list[str]", "default": []},
457                ],
458            ),
459            [
460                "Schema validation error in need_fields 'title': Additional properties are not allowed ('qwe' was unexpected)"
461            ],
462        ),
463        (
464            OneLineCommentStyle(
465                start_sequence="[[",
466                end_sequence="]]",
467                field_split_char=",",
468                needs_fields=[
469                    {"name": "title"},
470                    {"name": "id"},
471                    {
472                        "name": "type",
473                        "type: ": "list[str]",
474                        "default": "impl",
475                    },  # wring combination of type and default
476                    {"name": "links", "type": "list[str]", "default": []},
477                ],
478            ),
479            [
480                "Schema validation error in need_fields 'type': Additional properties are not allowed ('type: ' was unexpected)"
481            ],
482        ),
483        (
484            OneLineCommentStyle(
485                start_sequence="[[",
486                end_sequence="]]",
487                field_split_char=",",
488                needs_fields=[
489                    {"name": "id"}  # "title" and "type" are not given
490                ],
491            ),
492            ["Missing required fields: ['title', 'type']"],
493        ),
494        (
495            OneLineCommentStyle(
496                start_sequence="[[",
497                end_sequence="]]",
498                field_split_char=",",
499                needs_fields=[
500                    {"name": "id"},
501                    {"name": "id"},  # duplicate
502                ],
503            ),
504            [
505                "Missing required fields: ['title', 'type']",
506                "Field 'id' is defined multiple times.",
507            ],
508        ),
509        (
510            OneLineCommentStyle(
511                start_sequence=1234,  # wrong type
512                end_sequence=5678,
513                field_split_char=2222,
514                needs_fields=[
515                    {"name": "id"},
516                ],
517            ),
518            [
519                "Schema validation error in field 'field_split_char': 2222 is not of type 'string'",
520                "Schema validation error in field 'end_sequence': 5678 is not of type 'string'",
521                "Schema validation error in field 'start_sequence': 1234 is not of type 'string'",
522                "Missing required fields: ['title', 'type']",
523            ],
524        ),
525    ],
526)
527def test_oneline_schema_validator_negative(oneline_config, result):
528    errors = oneline_config.check_fields_configuration()
529    assert sorted(errors) == sorted(result)