88use TypeLang \PhpDoc \Parser \Splitter \SplitterInterface ;
99
1010/**
11- * Groups the significant segments of a DocBlock comment into sections (a
12- * description followed by tags) and builds the {@see SourceMap} for them .
11+ * Groups the significant segments of a DocBlock comment into a leading
12+ * description followed by tags, joining each tag with its continuation lines .
1313 *
14- * It only slices and maps the comment: parsing the description and tag
15- * contents is left to the caller .
14+ * A line opening with "@" starts a new tag; any line before the first tag
15+ * belongs to the description, and any non-tag line after a tag continues it .
1616 */
1717final readonly class DocBlockAnalyzer
1818{
@@ -22,35 +22,51 @@ public function __construct(
2222
2323 public function analyze (string $ docblock ): RawDocBlock
2424 {
25- $ buffer = '' ;
26- $ currentOffset = 0 ;
27- $ hasOffset = false ;
25+ /** @phpstan-ignore-next-line : Pre-allocate (invalid) segment in order to use it as a
26+ * template in the future (speeding up object instantiation) */
27+ $ prototype = new Segment ('' );
28+
29+ /** @var list<Segment> $groups */
30+ $ groups = [];
2831
29- /** @var list<Segment> $computedSegments */
30- $ computedSegments = [] ;
32+ $ buffer = '' ;
33+ $ offset = 0 ;
3134
3235 foreach ($ this ->splitter ->split ($ docblock ) as $ segment ) {
33- $ segmentText = $ segment ->text ;
36+ // A tag opens a new group; the previous one is finished first.
37+ if ($ buffer !== '' && $ segment ->text [0 ] === '@ ' ) {
38+ /** @phpstan-ignore-next-line : Allow external mutation */
39+ $ prototype ->text = $ buffer ;
40+ /** @phpstan-ignore-next-line : Allow external mutation */
41+ $ prototype ->offset = $ offset ;
3442
35- if ($ segmentText [0 ] === '@ ' ) {
36- $ computedSegments [] = new Segment ($ buffer , $ currentOffset );
43+ $ groups [] = clone $ prototype ;
3744 $ buffer = '' ;
38- $ currentOffset = $ segment ->offset ;
39- $ hasOffset = true ;
40- } elseif (!$ hasOffset ) {
41- // Anchor the leading description at its first significant line.
42- $ currentOffset = $ segment ->offset ;
43- $ hasOffset = true ;
45+ }
46+
47+ if ($ buffer === '' ) {
48+ $ offset = $ segment ->offset ;
4449 }
4550
4651 $ buffer .= $ segment ->text ;
4752 }
4853
49- $ computedSegments [] = new Segment ($ buffer , $ currentOffset );
54+ if ($ buffer !== '' ) {
55+ /** @phpstan-ignore-next-line : Allow external mutation */
56+ $ prototype ->text = $ buffer ;
57+ /** @phpstan-ignore-next-line : Allow external mutation */
58+ $ prototype ->offset = $ offset ;
59+
60+ $ groups [] = clone $ prototype ;
61+ }
62+
63+ // The leading group is the description unless it already is a tag.
64+ $ description = null ;
65+
66+ if ($ groups !== [] && $ groups [0 ]->text [0 ] !== '@ ' ) {
67+ $ description = \array_shift ($ groups );
68+ }
5069
51- return new RawDocBlock (
52- description: \array_shift ($ computedSegments ),
53- tags: $ computedSegments ,
54- );
70+ return new RawDocBlock ($ description , $ groups );
5571 }
5672}
0 commit comments