comparison vendor/psy/psysh/src/Util/Docblock.php @ 13:5fb285c0d0e3

Update Drupal core to 8.4.7 via Composer. Security update; I *think* we've been lucky to get away with this so far, as we don't support self-registration which seems to be used by the so-called "drupalgeddon 2" attack that 8.4.5 was vulnerable to.
author Chris Cannam
date Mon, 23 Apr 2018 09:33:26 +0100
parents
children 129ea1e6d783
comparison
equal deleted inserted replaced
12:7a779792577d 13:5fb285c0d0e3
1 <?php
2
3 /*
4 * This file is part of Psy Shell.
5 *
6 * (c) 2012-2018 Justin Hileman
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Psy\Util;
13
14 /**
15 * A docblock representation.
16 *
17 * Based on PHP-DocBlock-Parser by Paul Scott:
18 *
19 * {@link http://www.github.com/icio/PHP-DocBlock-Parser}
20 *
21 * @author Paul Scott <paul@duedil.com>
22 * @author Justin Hileman <justin@justinhileman.info>
23 */
24 class Docblock
25 {
26 /**
27 * Tags in the docblock that have a whitespace-delimited number of parameters
28 * (such as `@param type var desc` and `@return type desc`) and the names of
29 * those parameters.
30 *
31 * @var array
32 */
33 public static $vectors = [
34 'throws' => ['type', 'desc'],
35 'param' => ['type', 'var', 'desc'],
36 'return' => ['type', 'desc'],
37 ];
38
39 protected $reflector;
40
41 /**
42 * The description of the symbol.
43 *
44 * @var string
45 */
46 public $desc;
47
48 /**
49 * The tags defined in the docblock.
50 *
51 * The array has keys which are the tag names (excluding the @) and values
52 * that are arrays, each of which is an entry for the tag.
53 *
54 * In the case where the tag name is defined in {@see DocBlock::$vectors} the
55 * value within the tag-value array is an array in itself with keys as
56 * described by {@see DocBlock::$vectors}.
57 *
58 * @var array
59 */
60 public $tags;
61
62 /**
63 * The entire DocBlock comment that was parsed.
64 *
65 * @var string
66 */
67 public $comment;
68
69 /**
70 * Docblock constructor.
71 *
72 * @param \Reflector $reflector
73 */
74 public function __construct(\Reflector $reflector)
75 {
76 $this->reflector = $reflector;
77 $this->setComment($reflector->getDocComment());
78 }
79
80 /**
81 * Set and parse the docblock comment.
82 *
83 * @param string $comment The docblock
84 */
85 protected function setComment($comment)
86 {
87 $this->desc = '';
88 $this->tags = [];
89 $this->comment = $comment;
90
91 $this->parseComment($comment);
92 }
93
94 /**
95 * Find the length of the docblock prefix.
96 *
97 * @param array $lines
98 *
99 * @return int Prefix length
100 */
101 protected static function prefixLength(array $lines)
102 {
103 // find only lines with interesting things
104 $lines = array_filter($lines, function ($line) {
105 return substr($line, strspn($line, "* \t\n\r\0\x0B"));
106 });
107
108 // if we sort the lines, we only have to compare two items
109 sort($lines);
110
111 $first = reset($lines);
112 $last = end($lines);
113
114 // find the longest common substring
115 $count = min(strlen($first), strlen($last));
116 for ($i = 0; $i < $count; $i++) {
117 if ($first[$i] !== $last[$i]) {
118 return $i;
119 }
120 }
121
122 return $count;
123 }
124
125 /**
126 * Parse the comment into the component parts and set the state of the object.
127 *
128 * @param string $comment The docblock
129 */
130 protected function parseComment($comment)
131 {
132 // Strip the opening and closing tags of the docblock
133 $comment = substr($comment, 3, -2);
134
135 // Split into arrays of lines
136 $comment = array_filter(preg_split('/\r?\n\r?/', $comment));
137
138 // Trim asterisks and whitespace from the beginning and whitespace from the end of lines
139 $prefixLength = self::prefixLength($comment);
140 $comment = array_map(function ($line) use ($prefixLength) {
141 return rtrim(substr($line, $prefixLength));
142 }, $comment);
143
144 // Group the lines together by @tags
145 $blocks = [];
146 $b = -1;
147 foreach ($comment as $line) {
148 if (self::isTagged($line)) {
149 $b++;
150 $blocks[] = [];
151 } elseif ($b === -1) {
152 $b = 0;
153 $blocks[] = [];
154 }
155 $blocks[$b][] = $line;
156 }
157
158 // Parse the blocks
159 foreach ($blocks as $block => $body) {
160 $body = trim(implode("\n", $body));
161
162 if ($block === 0 && !self::isTagged($body)) {
163 // This is the description block
164 $this->desc = $body;
165 } else {
166 // This block is tagged
167 $tag = substr(self::strTag($body), 1);
168 $body = ltrim(substr($body, strlen($tag) + 2));
169
170 if (isset(self::$vectors[$tag])) {
171 // The tagged block is a vector
172 $count = count(self::$vectors[$tag]);
173 if ($body) {
174 $parts = preg_split('/\s+/', $body, $count);
175 } else {
176 $parts = [];
177 }
178
179 // Default the trailing values
180 $parts = array_pad($parts, $count, null);
181
182 // Store as a mapped array
183 $this->tags[$tag][] = array_combine(self::$vectors[$tag], $parts);
184 } else {
185 // The tagged block is only text
186 $this->tags[$tag][] = $body;
187 }
188 }
189 }
190 }
191
192 /**
193 * Whether or not a docblock contains a given @tag.
194 *
195 * @param string $tag The name of the @tag to check for
196 *
197 * @return bool
198 */
199 public function hasTag($tag)
200 {
201 return is_array($this->tags) && array_key_exists($tag, $this->tags);
202 }
203
204 /**
205 * The value of a tag.
206 *
207 * @param string $tag
208 *
209 * @return array
210 */
211 public function tag($tag)
212 {
213 return $this->hasTag($tag) ? $this->tags[$tag] : null;
214 }
215
216 /**
217 * Whether or not a string begins with a @tag.
218 *
219 * @param string $str
220 *
221 * @return bool
222 */
223 public static function isTagged($str)
224 {
225 return isset($str[1]) && $str[0] === '@' && ctype_alpha($str[1]);
226 }
227
228 /**
229 * The tag at the beginning of a string.
230 *
231 * @param string $str
232 *
233 * @return string|null
234 */
235 public static function strTag($str)
236 {
237 if (preg_match('/^@[a-z0-9_]+/', $str, $matches)) {
238 return $matches[0];
239 }
240 }
241 }