1414
1515use phpDocumentor \Reflection \Types \Context ;
1616
17+ /**
18+ * Creates a new Description object given a body of text.
19+ *
20+ * Descriptions in phpDocumentor are somewhat complex entities as they can contain one or more tags inside their
21+ * body that can be replaced with a readable output. The replacing is done by passing a Formatter object to the
22+ * Description object's `render` method.
23+ *
24+ * In addition to the above does a Description support two types of escape sequences:
25+ *
26+ * 1. `{@}` to escape the `@` character to prevent it from being interpreted as part of a tag, i.e. `{{@}link}`
27+ * 2. `{}` to escape the `}` character, this can be used if you want to use the `}` character in the description
28+ * of an inline tag.
29+ *
30+ * If a body consists of multiple lines then this factory will also remove any superfluous whitespace at the beginning
31+ * of each line while maintaining any indentation that is used. This will prevent formatting parsers from tripping
32+ * over unexpected spaces as can be observed with tag descriptions.
33+ */
1734class DescriptionFactory
1835{
1936 /** @var TagFactory */
@@ -45,11 +62,16 @@ public function create($contents, Context $context = null)
4562 }
4663
4764 /**
48- * @param $contents
49- * @return array
65+ * Strips the contents from superfluous whitespace and splits the description into a series of tokens.
66+ *
67+ * @param string $contents
68+ *
69+ * @return string[] A series of tokens of which the description text is composed.
5070 */
5171 private function lex ($ contents )
5272 {
73+ $ contents = $ this ->removeSuperfluousStartingWhitespace ($ contents );
74+
5375 // performance optimalization; if there is no inline tag, don't bother splitting it up.
5476 if (strpos ($ contents , '{@ ' ) === false ) {
5577 return [$ contents ];
@@ -98,7 +120,8 @@ private function parse($tokens, Context $context)
98120 {
99121 $ count = count ($ tokens );
100122 $ tagCount = 0 ;
101- $ tags = [];
123+ $ tags = [];
124+
102125 for ($ i = 1 ; $ i < $ count ; $ i += 2 ) {
103126 $ tags [] = $ this ->tagFactory ->create ($ tokens [$ i ], $ context );
104127 $ tokens [$ i ] = '% ' . ++$ tagCount . '$s ' ;
@@ -114,4 +137,55 @@ private function parse($tokens, Context $context)
114137 return [implode ('' , $ tokens ), $ tags ];
115138 }
116139
140+ /**
141+ * Removes the superfluous from a multi-line description.
142+ *
143+ * When a description has more than one line then it can happen that the second and subsequent lines have an
144+ * additional indentation. This is commonly in use with tags like this:
145+ *
146+ * {@}since 1.1.0 This is an example
147+ * description where we have an
148+ * indentation in the second and
149+ * subsequent lines.
150+ *
151+ * If we do not normalize the indentation then we have superfluous whitespace on the second and subsequent
152+ * lines and this may cause rendering issues when, for example, using a Markdown converter.
153+ *
154+ * @param string $contents
155+ *
156+ * @return string
157+ */
158+ private function removeSuperfluousStartingWhitespace ($ contents )
159+ {
160+ $ lines = explode ("\n" , $ contents );
161+
162+ // if there is only one line then we don't have lines with superfluous whitespace and
163+ // can use the contents as-is
164+ if (count ($ lines ) <= 1 ) {
165+ return $ contents ;
166+ }
167+
168+ // determine how many whitespace characters need to be stripped
169+ $ startingSpaceCount = 9999999 ;
170+ for ($ i = 1 ; $ i < count ($ lines ); $ i ++) {
171+ // lines with a no length do not count as they are not indented at all
172+ if (strlen (trim ($ lines [$ i ])) === 0 ) {
173+ continue ;
174+ }
175+
176+ // determine the number of prefixing spaces by checking the difference in line length before and after
177+ // an ltrim
178+ $ startingSpaceCount = min ($ startingSpaceCount , strlen ($ lines [$ i ]) - strlen (ltrim ($ lines [$ i ])));
179+ }
180+
181+ // strip the number of spaces from each line
182+ if ($ startingSpaceCount > 0 ) {
183+ for ($ i = 1 ; $ i < count ($ lines ); $ i ++) {
184+ $ lines [$ i ] = substr ($ lines [$ i ], $ startingSpaceCount );
185+ }
186+ }
187+
188+ return implode ("\n" , $ lines );
189+ }
190+
117191}
0 commit comments