[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 docstring_tag,
8 doxygen_tag,
9 jsdoc_tag,
10 oneline_parser,
11)
12from sphinx_codelinks.config import ESCAPE, UNIX_NEWLINE, OneLineCommentStyle
13
14from .conftest import ONELINE_COMMENT_STYLE, ONELINE_COMMENT_STYLE_DEFAULT
15
16
17@pytest.mark.parametrize(
18 "oneline, result",
19 [
20 (
21 f"@title 1, IMPL_1 {UNIX_NEWLINE}",
22 {
23 "title": "title 1",
24 "id": "IMPL_1",
25 "type": "impl",
26 "links": [],
27 "start_column": 1,
28 "end_column": 17,
29 },
30 ),
31 # Test case for leading space after start sequence (bug fix)
32 (
33 f"@ title 1, IMPL_1 {UNIX_NEWLINE}",
34 {
35 "title": "title 1",
36 "id": "IMPL_1",
37 "type": "impl",
38 "links": [],
39 "start_column": 1,
40 "end_column": 18,
41 },
42 ),
43 # Test case for multiple leading spaces after start sequence
44 (
45 f"@ title 1, IMPL_1 {UNIX_NEWLINE}",
46 {
47 "title": "title 1",
48 "id": "IMPL_1",
49 "type": "impl",
50 "links": [],
51 "start_column": 1,
52 "end_column": 20,
53 },
54 ),
55 # Test case for trailing space before end sequence
56 (
57 f"@title 1, IMPL_1 {UNIX_NEWLINE}",
58 {
59 "title": "title 1",
60 "id": "IMPL_1",
61 "type": "impl",
62 "links": [],
63 "start_column": 1,
64 "end_column": 18,
65 },
66 ),
67 # Test case for both leading and trailing spaces
68 (
69 f"@ title 1, IMPL_1 {UNIX_NEWLINE}",
70 {
71 "title": "title 1",
72 "id": "IMPL_1",
73 "type": "impl",
74 "links": [],
75 "start_column": 1,
76 "end_column": 21,
77 },
78 ),
79 ],
80)
81def test_oneline_parser_default_config_positive(
82 oneline: str, result: dict[str, str | list[str]]
83) -> None:
84 oneline_need = oneline_parser(oneline, ONELINE_COMMENT_STYLE_DEFAULT)
85 assert oneline_need == result
86
87
88# Test case for space as field separator (as mentioned by Kilian)
89# Example: @Implementation <field1> <field2>
90ONELINE_COMMENT_STYLE_SPACE_SEPARATOR = OneLineCommentStyle(
91 start_sequence="@",
92 end_sequence=UNIX_NEWLINE,
93 field_split_char=" ",
94 needs_fields=[
95 {"name": "title"},
96 {"name": "id"},
97 {"name": "type", "default": "impl"},
98 ],
99)
100
101
102@pytest.mark.parametrize(
103 "oneline, result",
104 [
105 # Basic space-separated fields
106 (
107 f"@Implementation IMPL_1{UNIX_NEWLINE}",
108 {
109 "title": "Implementation",
110 "id": "IMPL_1",
111 "type": "impl",
112 "start_column": 1,
113 "end_column": 22,
114 },
115 ),
116 # Space separator with explicit type
117 (
118 f"@MyFeature FEAT_001 feature{UNIX_NEWLINE}",
119 {
120 "title": "MyFeature",
121 "id": "FEAT_001",
122 "type": "feature",
123 "start_column": 1,
124 "end_column": 27,
125 },
126 ),
127 # Leading space after @ (the bug Kilian reported)
128 (
129 f"@ Implementation IMPL_2{UNIX_NEWLINE}",
130 {
131 "title": "Implementation",
132 "id": "IMPL_2",
133 "type": "impl",
134 "start_column": 1,
135 "end_column": 23,
136 },
137 ),
138 # Trailing space before newline
139 (
140 f"@Implementation IMPL_3 {UNIX_NEWLINE}",
141 {
142 "title": "Implementation",
143 "id": "IMPL_3",
144 "type": "impl",
145 "start_column": 1,
146 "end_column": 23,
147 },
148 ),
149 # Multiple leading spaces after @
150 (
151 f"@ Title ID_456{UNIX_NEWLINE}",
152 {
153 "title": "Title",
154 "id": "ID_456",
155 "type": "impl",
156 "start_column": 1,
157 "end_column": 15,
158 },
159 ),
160 # Title contain spaces
161 (
162 f"@ Title\ escape\ space ID_456{UNIX_NEWLINE}",
163 {
164 "title": "Title escape space",
165 "id": "ID_456",
166 "type": "impl",
167 "start_column": 1,
168 "end_column": 30,
169 },
170 ),
171 ],
172)
173def test_oneline_parser_space_separator(
174 oneline: str, result: dict[str, str | list[str]]
175) -> None:
176 """Test oneline parser with space as field separator."""
177 oneline_need = oneline_parser(oneline, ONELINE_COMMENT_STYLE_SPACE_SEPARATOR)
178 assert oneline_need == result
179
180
181@pytest.mark.parametrize(
182 "oneline, result",
183 [
184 (
185 "[[IMPL_1, title 1]]",
186 {
187 "id": "IMPL_1",
188 "title": "title 1",
189 "type": "impl",
190 "links": [],
191 "status": "open",
192 "priority": "low",
193 "start_column": 2,
194 "end_column": 17,
195 },
196 ),
197 (
198 "[[IMPL_2, title 2, impl, [], closed]]",
199 {
200 "id": "IMPL_2",
201 "title": "title 2",
202 "type": "impl",
203 "links": [],
204 "status": "closed",
205 "priority": "low",
206 "start_column": 2,
207 "end_column": 35,
208 },
209 ),
210 (
211 "[[IMPL_3, title\, 3, impl, [], closed]]",
212 {
213 "id": "IMPL_3",
214 "title": "title, 3",
215 "type": "impl",
216 "links": [],
217 "status": "closed",
218 "priority": "low",
219 "start_column": 2,
220 "end_column": 37,
221 },
222 ),
223 (
224 "[[IMPL_5, title 5, impl, [SPEC_1, SPEC_2], open]]",
225 {
226 "id": "IMPL_5",
227 "title": "title 5",
228 "type": "impl",
229 "links": ["SPEC_1", "SPEC_2"],
230 "status": "open",
231 "priority": "low",
232 "start_column": 2,
233 "end_column": 47,
234 },
235 ),
236 (
237 "[[IMPL_7, Function has a, in the title]]",
238 {
239 "id": "IMPL_7",
240 "title": "Function has a",
241 "type": "in the title",
242 "links": [],
243 "status": "open",
244 "priority": "low",
245 "start_column": 2,
246 "end_column": 38,
247 },
248 ),
249 (
250 "[[IMPL_8, [Title starts with a bracket], impl]]",
251 {
252 "id": "IMPL_8",
253 "title": "[Title starts with a bracket]",
254 "type": "impl",
255 "links": [],
256 "status": "open",
257 "priority": "low",
258 "start_column": 2,
259 "end_column": 45,
260 },
261 ),
262 (
263 "[[IMPL_9, Function Baz, impl, [SPEC_1, SPEC_2[text], SPEC_3], open]]",
264 {
265 "id": "IMPL_9",
266 "title": "Function Baz",
267 "type": "impl",
268 "links": ["SPEC_1", "SPEC_2[text"],
269 "status": "SPEC_3]",
270 "priority": "open",
271 "start_column": 2,
272 "end_column": 66,
273 },
274 ),
275 (
276 "[[IMPL_10, title 10, impl, [SPEC_1], open]]",
277 {
278 "id": "IMPL_10",
279 "title": "title 10",
280 "type": "impl",
281 "links": ["SPEC_1"],
282 "status": "open",
283 "priority": "low",
284 "start_column": 2,
285 "end_column": 41,
286 },
287 ),
288 (
289 "[[IMPL_11, title 11, impl, [SPEC\,_1], open]]",
290 {
291 "id": "IMPL_11",
292 "title": "title 11",
293 "type": "impl",
294 "links": ["SPEC,_1"],
295 "status": "open",
296 "priority": "low",
297 "start_column": 2,
298 "end_column": 43,
299 },
300 ),
301 (
302 "[[IMPL_12, title 12, impl, [\[SPEC\,_1\]], open]]",
303 {
304 "id": "IMPL_12",
305 "title": "title 12",
306 "type": "impl",
307 "links": ["[SPEC,_1]"],
308 "status": "open",
309 "priority": "low",
310 "start_column": 2,
311 "end_column": 47,
312 },
313 ),
314 (
315 "[[IMPL_13, title\\ 13, impl, [\[SPEC\,_1\]], open]]",
316 {
317 "id": "IMPL_13",
318 "title": "title\ 13",
319 "type": "impl",
320 "links": ["[SPEC,_1]"],
321 "status": "open",
322 "priority": "low",
323 "start_column": 2,
324 "end_column": 48,
325 },
326 ),
327 ],
328)
329def test_oneline_parser_custom_config_positive(
330 oneline: str, result: dict[str, str | list[str]]
331) -> None:
332 oneline_need = oneline_parser(oneline, ONELINE_COMMENT_STYLE)
333 assert oneline_need == result
334
335
336@pytest.mark.parametrize(
337 "oneline, result",
338 [
339 (
340 f"[[IMPL_4, title{ESCAPE}{ESCAPE}, 4, impl, [], closed]]",
341 OnelineParserInvalidWarning(
342 sub_type=WarningSubTypeEnum.missing_square_brackets,
343 msg="Field 'links' with 'type': 'list[str]' must be given with '[]' brackets",
344 ),
345 ),
346 (
347 "[[IMPL_2, Function Bar, impl, [SPEC_1, SPEC_2, open]]",
348 OnelineParserInvalidWarning(
349 sub_type=WarningSubTypeEnum.missing_square_brackets,
350 msg="Field 'links' with 'type': 'list[str]' must be given with '[]' brackets",
351 ),
352 ),
353 (
354 "[[IMPL_13, title 13, impl, 13[\[SPEC\,_1\]], open]]",
355 OnelineParserInvalidWarning(
356 sub_type=WarningSubTypeEnum.not_start_or_end_with_square_brackets,
357 msg="Field 'links' with 'type': 'list[str]' must start with '[' and end with ']'",
358 ),
359 ),
360 (
361 "[[IMPL_14, title 13, impl, 13[\[SPEC\,_1\]], open, low, high]]",
362 OnelineParserInvalidWarning(
363 sub_type=WarningSubTypeEnum.too_many_fields,
364 msg="7 given fields, maximum is 6",
365 ),
366 ),
367 (
368 "[[IMPL_15]]",
369 OnelineParserInvalidWarning(
370 sub_type=WarningSubTypeEnum.too_few_fields,
371 msg="1 given fields, minimum is 2",
372 ),
373 ),
374 (
375 f"[[IMPL_16]]{UNIX_NEWLINE}, title 16]]",
376 OnelineParserInvalidWarning(
377 sub_type=WarningSubTypeEnum.newline_in_field,
378 msg="Field 'id' contains a newline character",
379 ),
380 ),
381 ],
382)
383def test_oneline_parser_custom_config_negative(
384 oneline: str, result: OnelineParserInvalidWarning
385) -> None:
386 res = oneline_parser(oneline, ONELINE_COMMENT_STYLE)
387 assert res == result
388
389
390@pytest.mark.parametrize(
391 "oneline, result",
392 [
393 (
394 f"@title 17]]{UNIX_NEWLINE}, IMPL_17 {UNIX_NEWLINE}",
395 OnelineParserInvalidWarning(
396 sub_type=WarningSubTypeEnum.newline_in_field,
397 msg="Field 'title' contains a newline character",
398 ),
399 ),
400 (
401 f"@title 17]], IMPL_17, impl, [SPEC_3, SPEC_4{UNIX_NEWLINE} ] {UNIX_NEWLINE}",
402 OnelineParserInvalidWarning(
403 sub_type=WarningSubTypeEnum.newline_in_field,
404 msg="Field 'links' contains a newline character",
405 ),
406 ),
407 ],
408)
409def test_oneline_parser_default_config_negative(
410 oneline: str, result: OnelineParserInvalidWarning
411) -> None:
412 assert oneline_parser(oneline, ONELINE_COMMENT_STYLE_DEFAULT) == result
413
414
415@pytest.mark.parametrize(
416 "oneline_config, result",
417 [
418 (
419 OneLineCommentStyle(
420 start_sequence="[[",
421 end_sequence="]]",
422 field_split_char=",",
423 needs_fields=[
424 {"name": "title"},
425 {"name": "id"},
426 {"name": "type", "default": "impl"},
427 {"name": "links", "type": "list[]", "default": []}, # wrong type
428 ],
429 ),
430 [
431 "Schema validation error in need_fields 'links': 'list[]' is not one of ['str', 'list[str]']"
432 ],
433 ),
434 (
435 OneLineCommentStyle(
436 start_sequence="[[",
437 end_sequence="]]",
438 field_split_char=",",
439 needs_fields=[
440 {"name": "title"},
441 {"name": "id"},
442 {"name": "type", "default": 123}, # int is invalid
443 {"name": "links", "type": "list[str]", "default": []},
444 ],
445 ),
446 [
447 "Schema validation error in need_fields 'type': 123 is not of type 'string'"
448 ],
449 ),
450 (
451 OneLineCommentStyle(
452 start_sequence="[[",
453 end_sequence="]]",
454 field_split_char=",",
455 needs_fields=[
456 {"name": "title", "qwe": "qwe"}, # invalid qwe filed
457 {"name": "id"},
458 {"name": "type", "default": "impl"},
459 {"name": "links", "type": "list[str]", "default": []},
460 ],
461 ),
462 [
463 "Schema validation error in need_fields 'title': Additional properties are not allowed ('qwe' was unexpected)"
464 ],
465 ),
466 (
467 OneLineCommentStyle(
468 start_sequence="[[",
469 end_sequence="]]",
470 field_split_char=",",
471 needs_fields=[
472 {"name": "title"},
473 {"name": "id"},
474 {
475 "name": "type",
476 "type: ": "list[str]",
477 "default": "impl",
478 }, # wring combination of type and default
479 {"name": "links", "type": "list[str]", "default": []},
480 ],
481 ),
482 [
483 "Schema validation error in need_fields 'type': Additional properties are not allowed ('type: ' was unexpected)"
484 ],
485 ),
486 (
487 OneLineCommentStyle(
488 start_sequence="[[",
489 end_sequence="]]",
490 field_split_char=",",
491 needs_fields=[
492 {"name": "id"} # "title" and "type" are not given
493 ],
494 ),
495 ["Missing required fields: ['title', 'type']"],
496 ),
497 (
498 OneLineCommentStyle(
499 start_sequence="[[",
500 end_sequence="]]",
501 field_split_char=",",
502 needs_fields=[
503 {"name": "id"},
504 {"name": "id"}, # duplicate
505 ],
506 ),
507 [
508 "Missing required fields: ['title', 'type']",
509 "Field 'id' is defined multiple times.",
510 ],
511 ),
512 (
513 OneLineCommentStyle(
514 start_sequence=1234, # wrong type
515 end_sequence=5678,
516 field_split_char=2222,
517 needs_fields=[
518 {"name": "id"},
519 ],
520 ),
521 [
522 "Schema validation error in field 'field_split_char': 2222 is not of type 'string'",
523 "Schema validation error in field 'end_sequence': 5678 is not of type 'string'",
524 "Schema validation error in field 'start_sequence': 1234 is not of type 'string'",
525 "Missing required fields: ['title', 'type']",
526 ],
527 ),
528 ],
529)
530def test_oneline_schema_validator_negative(oneline_config, result):
531 errors = oneline_config.check_fields_configuration()
532 assert sorted(errors) == sorted(result)
533
534
535@pytest.mark.parametrize(
536 "oneline",
537 [
538 # A start sequence embedded in free-form prose (word chars before it)
539 # must not be treated as a one-line marker (issue #88).
540 f"// Some prose that mentions @@another-tag(item_1, item_2): more text{UNIX_NEWLINE}",
541 f"// See @author, check the example{UNIX_NEWLINE}",
542 f"// We parse things matching @pattern, then act{UNIX_NEWLINE}",
543 ],
544)
545def test_oneline_parser_ignores_start_sequence_in_prose(oneline: str) -> None:
546 """Issue #88: only anchor a marker at the start of the comment content."""
547 assert oneline_parser(oneline, ONELINE_COMMENT_STYLE_DEFAULT) is None
548
549
550@pytest.mark.parametrize(
551 "oneline, expected_id",
552 [
553 (f"// @My Title, IMPL_1{UNIX_NEWLINE}", "IMPL_1"), # line-comment leader
554 (f" // @My Title, IMPL_2{UNIX_NEWLINE}", "IMPL_2"), # indented
555 (f"* @My Title, IMPL_3{UNIX_NEWLINE}", "IMPL_3"), # block-comment continuation
556 (f"/// @My Title, IMPL_4{UNIX_NEWLINE}", "IMPL_4"), # doc comment
557 (f"//! @My Title, IMPL_5{UNIX_NEWLINE}", "IMPL_5"), # inner doc comment
558 ],
559)
560def test_oneline_parser_marker_preceded_only_by_comment_decoration(
561 oneline: str, expected_id: str
562) -> None:
563 """A marker preceded only by comment syntax/whitespace is still recognized."""
564 res = oneline_parser(oneline, ONELINE_COMMENT_STYLE_DEFAULT)
565 assert isinstance(res, dict)
566 assert res["id"] == expected_id
567
568
569def test_oneline_parser_bounded_marker_allowed_after_prose() -> None:
570 """Explicitly-bounded markers ([[ ... ]]) are self-delimiting, so they may
571 be embedded after prose; only newline-terminated markers are anchored."""
572 oneline = "// one-line comment style: [[IMPL_1, title 1]]"
573 res = oneline_parser(oneline, ONELINE_COMMENT_STYLE)
574 assert isinstance(res, dict)
575 assert res["id"] == "IMPL_1"
576
577
578#: one required field (``title``): a line without the separator is a whole marker
579ONE_REQUIRED_FIELD = OneLineCommentStyle(
580 needs_fields=[{"name": "title"}, {"name": "type", "default": "impl"}]
581)
582
583
584@pytest.mark.parametrize(
585 "oneline",
586 [
587 f"// @param x the value{UNIX_NEWLINE}",
588 f"/// @return nothing{UNIX_NEWLINE}",
589 f" * @only{UNIX_NEWLINE}",
590 ],
591)
592def test_one_character_start_without_separator_is_not_a_marker(oneline: str) -> None:
593 """A one-character start sequence, no field separator in the content and more than
594 one required field: documentation tags such as ``@param`` are not markers, and not
595 warnings either."""
596 assert oneline_parser(oneline, ONELINE_COMMENT_STYLE_DEFAULT) is None
597
598
599@pytest.mark.parametrize(
600 "oneline, sub_type",
601 [
602 (
603 f"// @see A, B, C, D, E{UNIX_NEWLINE}",
604 WarningSubTypeEnum.too_many_fields,
605 ),
606 # the fourth field is ``links``, a ``list[str]``
607 (
608 f"// @brief a, b, c, d{UNIX_NEWLINE}",
609 WarningSubTypeEnum.missing_square_brackets,
610 ),
611 ],
612)
613def test_one_character_start_with_separator_still_warns(
614 oneline: str, sub_type: WarningSubTypeEnum
615) -> None:
616 res = oneline_parser(oneline, ONELINE_COMMENT_STYLE_DEFAULT)
617 assert isinstance(res, OnelineParserInvalidWarning)
618 assert res.sub_type == sub_type
619
620
621def test_one_character_start_with_two_fields_is_still_a_need() -> None:
622 """A line with the separator, ``@brief Does a, b``, is the need it always was."""
623 assert oneline_parser(
624 f"// @brief Does a, b{UNIX_NEWLINE}", ONELINE_COMMENT_STYLE_DEFAULT
625 ) == {
626 "title": "brief Does a",
627 "id": "b",
628 "type": "impl",
629 "links": [],
630 "start_column": 4,
631 "end_column": 19,
632 }
633
634
635def test_multi_character_start_without_separator_still_warns() -> None:
636 """``[[`` is specific enough: a marker with too few fields is a warning."""
637 assert oneline_parser(
638 "// [[ only-title ]]", ONELINE_COMMENT_STYLE
639 ) == OnelineParserInvalidWarning(
640 sub_type=WarningSubTypeEnum.too_few_fields,
641 msg="1 given fields, minimum is 2",
642 )
643
644
645def test_one_character_start_with_one_required_field_is_a_need() -> None:
646 """With one required field, a line without the separator is a whole marker."""
647 assert oneline_parser(f"// @x{UNIX_NEWLINE}", ONE_REQUIRED_FIELD) == {
648 "title": "x",
649 "type": "impl",
650 "start_column": 4,
651 "end_column": 5,
652 }
653
654
655@pytest.mark.parametrize(
656 "oneline, style",
657 [
658 ("// [[ ]]", ONELINE_COMMENT_STYLE),
659 (f"// @{UNIX_NEWLINE}", ONE_REQUIRED_FIELD),
660 (f"// @{UNIX_NEWLINE}", ONELINE_COMMENT_STYLE_DEFAULT),
661 (f"// @ {UNIX_NEWLINE}", ONELINE_COMMENT_STYLE_DEFAULT),
662 ],
663)
664def test_empty_content_is_not_a_marker(
665 oneline: str, style: OneLineCommentStyle
666) -> None:
667 """Nothing between the start and end sequences: not a marker, whatever the start
668 sequence and however many fields are required."""
669 assert oneline_parser(oneline, style) is None
670
671
672def test_an_escaped_separator_counts_as_present() -> None:
673 """``\\,`` is a separator in the content, so the line is a marker, and one field
674 is too few."""
675 res = oneline_parser(f"// @a\\,b{UNIX_NEWLINE}", ONELINE_COMMENT_STYLE_DEFAULT)
676 assert isinstance(res, OnelineParserInvalidWarning)
677 assert res.sub_type == WarningSubTypeEnum.too_few_fields
678
679
680def test_a_non_ascii_start_sequence_of_one_character_is_one_character() -> None:
681 """One character is one code point: ``§`` is a one-character start sequence."""
682 style = OneLineCommentStyle(start_sequence="§")
683 assert oneline_parser(f"// §only{UNIX_NEWLINE}", style) is None
684 res = oneline_parser(f"// §a title, IMPL_SECT{UNIX_NEWLINE}", style)
685 assert isinstance(res, dict)
686 assert res["id"] == "IMPL_SECT"
687
688
689@pytest.mark.parametrize(
690 "line, expected",
691 [
692 pytest.param("@param a: the first, thing", "param", id="param_field"),
693 pytest.param("@return: nothing", "return", id="return_field"),
694 pytest.param("@raise ValueError: on bad input", "raise", id="raise_field"),
695 pytest.param("@type x_1: int", "type", id="type_field"),
696 pytest.param("@param a:", "param", id="field_empty_text"),
697 pytest.param('"""@return: nothing', "return", id="opening_quotes"),
698 pytest.param("@todo fix the parser, IMPL_DS, impl", None, id="todo_title"),
699 pytest.param(
700 '"""@todo fix the parser, IMPL_DS, impl"""',
701 None,
702 id="todo_title_opening_quotes",
703 ),
704 pytest.param("@param a the first, thing", None, id="doxygen_comma_form"),
705 pytest.param("@return", None, id="bare_tag"),
706 pytest.param("@param a b: text", None, id="two_words_before_colon"),
707 pytest.param("@param a : text", None, id="space_before_colon"),
708 pytest.param(
709 "@Todo fix the parser, IMPL_TODO2, impl", None, id="case_sensitive"
710 ),
711 pytest.param("@params a: text", None, id="not_a_tag"),
712 pytest.param("see @param a: text", None, id="text_before_start"),
713 # the field word is any run of characters other than space, tab and ``:``,
714 # after spaces or tabs only: ``:|[ \t]+[^ \t:]+:``, locale-free
715 pytest.param("@param नाम: text", "param", id="non_ascii_word_devanagari"),
716 pytest.param("@param ä: text", "param", id="non_ascii_word_latin"),
717 pytest.param("@param *args: text", "param", id="star_args_word"),
718 pytest.param("@type a.b: int", "type", id="dotted_word"),
719 pytest.param("@param\ta: text", "param", id="tab_separator"),
720 pytest.param("@param\x1ca: text", None, id="unit_separator_is_not_a_space"),
721 pytest.param(
722 "@param\u3000a: text", None, id="ideographic_space_is_not_a_space"
723 ),
724 pytest.param("@return : text", None, id="empty_word_before_colon"),
725 pytest.param("@param : text", None, id="empty_word_after_param"),
726 pytest.param("@param1: text", None, id="digit_glued_to_tag"),
727 pytest.param("@return_x: text", None, id="underscore_glued_to_tag"),
728 ],
729)
730def test_docstring_tag_field_shape(line: str, expected: str | None) -> None:
731 """A docstring line is a ``docstring_tag`` only when a listed tag right after the
732 start sequence has the Epydoc field shape: ``:`` directly, or spaces or tabs, one
733 word without space, tab or ``:``, and ``:``. The same table as ubCode's."""
734 assert docstring_tag(line, "@") == expected
735
736
737@pytest.mark.parametrize(
738 ("line", "expected"),
739 [
740 pytest.param(" * @param a the first, thing", "param", id="param"),
741 pytest.param(" * @param {number} a text", "param", id="param_type"),
742 pytest.param(" * @param{number} a text", "param", id="brace_glued_to_tag"),
743 pytest.param(" * @returns", "returns", id="bare_tag"),
744 pytest.param(" * @returns\n", "returns", id="bare_tag_terminated"),
745 pytest.param(" * @deprecated\tUse x, it is faster", "deprecated", id="tab"),
746 pytest.param(" * {@link Foo}, the original", "link", id="inline_tag"),
747 pytest.param(" * {@inheritDoc}", "inheritDoc", id="inline_tag_closed"),
748 pytest.param("@typeParam T the type, of it", "typeParam", id="camel_case_tag"),
749 pytest.param(" * @Param check, IMPL_1, impl", None, id="case_sensitive"),
750 pytest.param(" * @parameters Check, IMPL_1, impl", None, id="longer_word"),
751 pytest.param(" * @param1 text", None, id="digit_glued_to_tag"),
752 pytest.param(" * @param-a text", None, id="dash_glued_to_tag"),
753 pytest.param(" * @param: text", None, id="colon_glued_to_tag"),
754 pytest.param(" * see @param a text", None, id="text_before_start"),
755 pytest.param(" * @Real Need, IMPL_1, impl", None, id="not_a_tag"),
756 pytest.param(
757 " * @param\u3000a text", None, id="ideographic_space_ends_nothing"
758 ),
759 ],
760)
761def test_jsdoc_tag(line: str, expected: str | None) -> None:
762 """A JSDoc comment line is a ``jsdoc_tag`` only when a listed tag right after the
763 start sequence ends at a space, a tab, a ``{``, a ``}`` or the end of the line. The
764 same table as ubCode's."""
765 assert jsdoc_tag(line, "@") == expected
766
767
768@pytest.mark.parametrize(
769 ("line", "expected"),
770 [
771 pytest.param("--! @param clk Clock input, rising edge", "param", id="param"),
772 pytest.param("--! @param[in] rst Reset, active high", "param", id="bracket"),
773 pytest.param("--! @code{.vhd} x, y", "code", id="brace"),
774 pytest.param("--! @brief", "brief", id="bare_command"),
775 pytest.param("--! @brief\n", "brief", id="bare_command_terminated"),
776 pytest.param("--!\t@note\tOne, two", "note", id="tab"),
777 pytest.param("--! @Param check, IMPL_1, impl", None, id="case_sensitive"),
778 pytest.param("--! @parameters Check, IMPL_1, impl", None, id="longer_word"),
779 pytest.param("--! @param1 text", None, id="digit_glued_to_command"),
780 pytest.param("--! @param: text", None, id="colon_glued_to_command"),
781 pytest.param("--! see @param a text", None, id="text_before_start"),
782 pytest.param("--! @Real Need, IMPL_1, impl", None, id="not_a_command"),
783 ],
784)
785def test_doxygen_tag(line: str, expected: str | None) -> None:
786 """A Doxygen comment line is a ``doxygen_tag`` only when a listed command right after
787 the start sequence ends at a space, a tab, a ``[``, a ``{`` or the end of the line.
788 The same table as ubCode's."""
789 assert doxygen_tag(line, "@") == expected