Chris@17
|
1 <?php
|
Chris@17
|
2 /**
|
Chris@17
|
3 * Represents a piece of content being checked during the run.
|
Chris@17
|
4 *
|
Chris@17
|
5 * @author Greg Sherwood <gsherwood@squiz.net>
|
Chris@17
|
6 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
|
Chris@17
|
7 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
|
Chris@17
|
8 */
|
Chris@17
|
9
|
Chris@17
|
10 namespace PHP_CodeSniffer\Files;
|
Chris@17
|
11
|
Chris@17
|
12 use PHP_CodeSniffer\Ruleset;
|
Chris@17
|
13 use PHP_CodeSniffer\Config;
|
Chris@17
|
14 use PHP_CodeSniffer\Fixer;
|
Chris@17
|
15 use PHP_CodeSniffer\Util;
|
Chris@17
|
16 use PHP_CodeSniffer\Exceptions\RuntimeException;
|
Chris@17
|
17 use PHP_CodeSniffer\Exceptions\TokenizerException;
|
Chris@17
|
18
|
Chris@17
|
19 class File
|
Chris@17
|
20 {
|
Chris@17
|
21
|
Chris@17
|
22 /**
|
Chris@17
|
23 * The absolute path to the file associated with this object.
|
Chris@17
|
24 *
|
Chris@17
|
25 * @var string
|
Chris@17
|
26 */
|
Chris@17
|
27 public $path = '';
|
Chris@17
|
28
|
Chris@17
|
29 /**
|
Chris@17
|
30 * The absolute path to the file associated with this object.
|
Chris@17
|
31 *
|
Chris@17
|
32 * @var string
|
Chris@17
|
33 */
|
Chris@17
|
34 protected $content = '';
|
Chris@17
|
35
|
Chris@17
|
36 /**
|
Chris@17
|
37 * The config data for the run.
|
Chris@17
|
38 *
|
Chris@17
|
39 * @var \PHP_CodeSniffer\Config
|
Chris@17
|
40 */
|
Chris@17
|
41 public $config = null;
|
Chris@17
|
42
|
Chris@17
|
43 /**
|
Chris@17
|
44 * The ruleset used for the run.
|
Chris@17
|
45 *
|
Chris@17
|
46 * @var \PHP_CodeSniffer\Ruleset
|
Chris@17
|
47 */
|
Chris@17
|
48 public $ruleset = null;
|
Chris@17
|
49
|
Chris@17
|
50 /**
|
Chris@17
|
51 * If TRUE, the entire file is being ignored.
|
Chris@17
|
52 *
|
Chris@17
|
53 * @var boolean
|
Chris@17
|
54 */
|
Chris@17
|
55 public $ignored = false;
|
Chris@17
|
56
|
Chris@17
|
57 /**
|
Chris@17
|
58 * The EOL character this file uses.
|
Chris@17
|
59 *
|
Chris@17
|
60 * @var string
|
Chris@17
|
61 */
|
Chris@17
|
62 public $eolChar = '';
|
Chris@17
|
63
|
Chris@17
|
64 /**
|
Chris@17
|
65 * The Fixer object to control fixing errors.
|
Chris@17
|
66 *
|
Chris@17
|
67 * @var \PHP_CodeSniffer\Fixer
|
Chris@17
|
68 */
|
Chris@17
|
69 public $fixer = null;
|
Chris@17
|
70
|
Chris@17
|
71 /**
|
Chris@17
|
72 * The tokenizer being used for this file.
|
Chris@17
|
73 *
|
Chris@17
|
74 * @var \PHP_CodeSniffer\Tokenizers\Tokenizer
|
Chris@17
|
75 */
|
Chris@17
|
76 public $tokenizer = null;
|
Chris@17
|
77
|
Chris@17
|
78 /**
|
Chris@17
|
79 * The name of the tokenizer being used for this file.
|
Chris@17
|
80 *
|
Chris@17
|
81 * @var string
|
Chris@17
|
82 */
|
Chris@17
|
83 public $tokenizerType = 'PHP';
|
Chris@17
|
84
|
Chris@17
|
85 /**
|
Chris@17
|
86 * Was the file loaded from cache?
|
Chris@17
|
87 *
|
Chris@17
|
88 * If TRUE, the file was loaded from a local cache.
|
Chris@17
|
89 * If FALSE, the file was tokenized and processed fully.
|
Chris@17
|
90 *
|
Chris@17
|
91 * @var boolean
|
Chris@17
|
92 */
|
Chris@17
|
93 public $fromCache = false;
|
Chris@17
|
94
|
Chris@17
|
95 /**
|
Chris@17
|
96 * The number of tokens in this file.
|
Chris@17
|
97 *
|
Chris@17
|
98 * Stored here to save calling count() everywhere.
|
Chris@17
|
99 *
|
Chris@17
|
100 * @var integer
|
Chris@17
|
101 */
|
Chris@17
|
102 public $numTokens = 0;
|
Chris@17
|
103
|
Chris@17
|
104 /**
|
Chris@17
|
105 * The tokens stack map.
|
Chris@17
|
106 *
|
Chris@17
|
107 * @var array
|
Chris@17
|
108 */
|
Chris@17
|
109 protected $tokens = [];
|
Chris@17
|
110
|
Chris@17
|
111 /**
|
Chris@17
|
112 * The errors raised from sniffs.
|
Chris@17
|
113 *
|
Chris@17
|
114 * @var array
|
Chris@17
|
115 * @see getErrors()
|
Chris@17
|
116 */
|
Chris@17
|
117 protected $errors = [];
|
Chris@17
|
118
|
Chris@17
|
119 /**
|
Chris@17
|
120 * The warnings raised from sniffs.
|
Chris@17
|
121 *
|
Chris@17
|
122 * @var array
|
Chris@17
|
123 * @see getWarnings()
|
Chris@17
|
124 */
|
Chris@17
|
125 protected $warnings = [];
|
Chris@17
|
126
|
Chris@17
|
127 /**
|
Chris@17
|
128 * The metrics recorded by sniffs.
|
Chris@17
|
129 *
|
Chris@17
|
130 * @var array
|
Chris@17
|
131 * @see getMetrics()
|
Chris@17
|
132 */
|
Chris@17
|
133 protected $metrics = [];
|
Chris@17
|
134
|
Chris@17
|
135 /**
|
Chris@17
|
136 * The metrics recorded for each token.
|
Chris@17
|
137 *
|
Chris@17
|
138 * Stops the same metric being recorded for the same token twice.
|
Chris@17
|
139 *
|
Chris@17
|
140 * @var array
|
Chris@17
|
141 * @see getMetrics()
|
Chris@17
|
142 */
|
Chris@17
|
143 private $metricTokens = [];
|
Chris@17
|
144
|
Chris@17
|
145 /**
|
Chris@17
|
146 * The total number of errors raised.
|
Chris@17
|
147 *
|
Chris@17
|
148 * @var integer
|
Chris@17
|
149 */
|
Chris@17
|
150 protected $errorCount = 0;
|
Chris@17
|
151
|
Chris@17
|
152 /**
|
Chris@17
|
153 * The total number of warnings raised.
|
Chris@17
|
154 *
|
Chris@17
|
155 * @var integer
|
Chris@17
|
156 */
|
Chris@17
|
157 protected $warningCount = 0;
|
Chris@17
|
158
|
Chris@17
|
159 /**
|
Chris@17
|
160 * The total number of errors and warnings that can be fixed.
|
Chris@17
|
161 *
|
Chris@17
|
162 * @var integer
|
Chris@17
|
163 */
|
Chris@17
|
164 protected $fixableCount = 0;
|
Chris@17
|
165
|
Chris@17
|
166 /**
|
Chris@17
|
167 * The total number of errors and warnings that were fixed.
|
Chris@17
|
168 *
|
Chris@17
|
169 * @var integer
|
Chris@17
|
170 */
|
Chris@17
|
171 protected $fixedCount = 0;
|
Chris@17
|
172
|
Chris@17
|
173 /**
|
Chris@17
|
174 * An array of sniffs that are being ignored.
|
Chris@17
|
175 *
|
Chris@17
|
176 * @var array
|
Chris@17
|
177 */
|
Chris@17
|
178 protected $ignoredListeners = [];
|
Chris@17
|
179
|
Chris@17
|
180 /**
|
Chris@17
|
181 * An array of message codes that are being ignored.
|
Chris@17
|
182 *
|
Chris@17
|
183 * @var array
|
Chris@17
|
184 */
|
Chris@17
|
185 protected $ignoredCodes = [];
|
Chris@17
|
186
|
Chris@17
|
187 /**
|
Chris@17
|
188 * An array of sniffs listening to this file's processing.
|
Chris@17
|
189 *
|
Chris@17
|
190 * @var \PHP_CodeSniffer\Sniffs\Sniff[]
|
Chris@17
|
191 */
|
Chris@17
|
192 protected $listeners = [];
|
Chris@17
|
193
|
Chris@17
|
194 /**
|
Chris@17
|
195 * The class name of the sniff currently processing the file.
|
Chris@17
|
196 *
|
Chris@17
|
197 * @var string
|
Chris@17
|
198 */
|
Chris@17
|
199 protected $activeListener = '';
|
Chris@17
|
200
|
Chris@17
|
201 /**
|
Chris@17
|
202 * An array of sniffs being processed and how long they took.
|
Chris@17
|
203 *
|
Chris@17
|
204 * @var array
|
Chris@17
|
205 */
|
Chris@17
|
206 protected $listenerTimes = [];
|
Chris@17
|
207
|
Chris@17
|
208 /**
|
Chris@17
|
209 * A cache of often used config settings to improve performance.
|
Chris@17
|
210 *
|
Chris@17
|
211 * Storing them here saves 10k+ calls to __get() in the Config class.
|
Chris@17
|
212 *
|
Chris@17
|
213 * @var array
|
Chris@17
|
214 */
|
Chris@17
|
215 protected $configCache = [];
|
Chris@17
|
216
|
Chris@17
|
217
|
Chris@17
|
218 /**
|
Chris@17
|
219 * Constructs a file.
|
Chris@17
|
220 *
|
Chris@17
|
221 * @param string $path The absolute path to the file to process.
|
Chris@17
|
222 * @param \PHP_CodeSniffer\Ruleset $ruleset The ruleset used for the run.
|
Chris@17
|
223 * @param \PHP_CodeSniffer\Config $config The config data for the run.
|
Chris@17
|
224 *
|
Chris@17
|
225 * @return void
|
Chris@17
|
226 */
|
Chris@17
|
227 public function __construct($path, Ruleset $ruleset, Config $config)
|
Chris@17
|
228 {
|
Chris@17
|
229 $this->path = $path;
|
Chris@17
|
230 $this->ruleset = $ruleset;
|
Chris@17
|
231 $this->config = $config;
|
Chris@17
|
232 $this->fixer = new Fixer();
|
Chris@17
|
233
|
Chris@17
|
234 $parts = explode('.', $path);
|
Chris@17
|
235 $extension = array_pop($parts);
|
Chris@17
|
236 if (isset($config->extensions[$extension]) === true) {
|
Chris@17
|
237 $this->tokenizerType = $config->extensions[$extension];
|
Chris@17
|
238 } else {
|
Chris@17
|
239 // Revert to default.
|
Chris@17
|
240 $this->tokenizerType = 'PHP';
|
Chris@17
|
241 }
|
Chris@17
|
242
|
Chris@17
|
243 $this->configCache['cache'] = $this->config->cache;
|
Chris@17
|
244 $this->configCache['sniffs'] = array_map('strtolower', $this->config->sniffs);
|
Chris@17
|
245 $this->configCache['exclude'] = array_map('strtolower', $this->config->exclude);
|
Chris@17
|
246 $this->configCache['errorSeverity'] = $this->config->errorSeverity;
|
Chris@17
|
247 $this->configCache['warningSeverity'] = $this->config->warningSeverity;
|
Chris@17
|
248 $this->configCache['recordErrors'] = $this->config->recordErrors;
|
Chris@17
|
249 $this->configCache['ignorePatterns'] = $this->ruleset->ignorePatterns;
|
Chris@17
|
250 $this->configCache['includePatterns'] = $this->ruleset->includePatterns;
|
Chris@17
|
251
|
Chris@17
|
252 }//end __construct()
|
Chris@17
|
253
|
Chris@17
|
254
|
Chris@17
|
255 /**
|
Chris@17
|
256 * Set the content of the file.
|
Chris@17
|
257 *
|
Chris@17
|
258 * Setting the content also calculates the EOL char being used.
|
Chris@17
|
259 *
|
Chris@17
|
260 * @param string $content The file content.
|
Chris@17
|
261 *
|
Chris@17
|
262 * @return void
|
Chris@17
|
263 */
|
Chris@17
|
264 public function setContent($content)
|
Chris@17
|
265 {
|
Chris@17
|
266 $this->content = $content;
|
Chris@17
|
267 $this->tokens = [];
|
Chris@17
|
268
|
Chris@17
|
269 try {
|
Chris@17
|
270 $this->eolChar = Util\Common::detectLineEndings($content);
|
Chris@17
|
271 } catch (RuntimeException $e) {
|
Chris@17
|
272 $this->addWarningOnLine($e->getMessage(), 1, 'Internal.DetectLineEndings');
|
Chris@17
|
273 return;
|
Chris@17
|
274 }
|
Chris@17
|
275
|
Chris@17
|
276 }//end setContent()
|
Chris@17
|
277
|
Chris@17
|
278
|
Chris@17
|
279 /**
|
Chris@17
|
280 * Reloads the content of the file.
|
Chris@17
|
281 *
|
Chris@17
|
282 * By default, we have no idea where our content comes from,
|
Chris@17
|
283 * so we can't do anything.
|
Chris@17
|
284 *
|
Chris@17
|
285 * @return void
|
Chris@17
|
286 */
|
Chris@17
|
287 public function reloadContent()
|
Chris@17
|
288 {
|
Chris@17
|
289
|
Chris@17
|
290 }//end reloadContent()
|
Chris@17
|
291
|
Chris@17
|
292
|
Chris@17
|
293 /**
|
Chris@17
|
294 * Disables caching of this file.
|
Chris@17
|
295 *
|
Chris@17
|
296 * @return void
|
Chris@17
|
297 */
|
Chris@17
|
298 public function disableCaching()
|
Chris@17
|
299 {
|
Chris@17
|
300 $this->configCache['cache'] = false;
|
Chris@17
|
301
|
Chris@17
|
302 }//end disableCaching()
|
Chris@17
|
303
|
Chris@17
|
304
|
Chris@17
|
305 /**
|
Chris@17
|
306 * Starts the stack traversal and tells listeners when tokens are found.
|
Chris@17
|
307 *
|
Chris@17
|
308 * @return void
|
Chris@17
|
309 */
|
Chris@17
|
310 public function process()
|
Chris@17
|
311 {
|
Chris@17
|
312 if ($this->ignored === true) {
|
Chris@17
|
313 return;
|
Chris@17
|
314 }
|
Chris@17
|
315
|
Chris@17
|
316 $this->errors = [];
|
Chris@17
|
317 $this->warnings = [];
|
Chris@17
|
318 $this->errorCount = 0;
|
Chris@17
|
319 $this->warningCount = 0;
|
Chris@17
|
320 $this->fixableCount = 0;
|
Chris@17
|
321
|
Chris@17
|
322 $this->parse();
|
Chris@17
|
323
|
Chris@17
|
324 // Check if tokenizer errors cause this file to be ignored.
|
Chris@17
|
325 if ($this->ignored === true) {
|
Chris@17
|
326 return;
|
Chris@17
|
327 }
|
Chris@17
|
328
|
Chris@17
|
329 $this->fixer->startFile($this);
|
Chris@17
|
330
|
Chris@17
|
331 if (PHP_CODESNIFFER_VERBOSITY > 2) {
|
Chris@17
|
332 echo "\t*** START TOKEN PROCESSING ***".PHP_EOL;
|
Chris@17
|
333 }
|
Chris@17
|
334
|
Chris@17
|
335 $foundCode = false;
|
Chris@17
|
336 $listenerIgnoreTo = [];
|
Chris@17
|
337 $inTests = defined('PHP_CODESNIFFER_IN_TESTS');
|
Chris@17
|
338 $checkAnnotations = $this->config->annotations;
|
Chris@17
|
339
|
Chris@17
|
340 // Foreach of the listeners that have registered to listen for this
|
Chris@17
|
341 // token, get them to process it.
|
Chris@17
|
342 foreach ($this->tokens as $stackPtr => $token) {
|
Chris@17
|
343 // Check for ignored lines.
|
Chris@17
|
344 if ($checkAnnotations === true
|
Chris@17
|
345 && ($token['code'] === T_COMMENT
|
Chris@17
|
346 || $token['code'] === T_PHPCS_IGNORE_FILE
|
Chris@17
|
347 || $token['code'] === T_PHPCS_SET
|
Chris@17
|
348 || $token['code'] === T_DOC_COMMENT_STRING
|
Chris@17
|
349 || $token['code'] === T_DOC_COMMENT_TAG
|
Chris@17
|
350 || ($inTests === true && $token['code'] === T_INLINE_HTML))
|
Chris@17
|
351 ) {
|
Chris@17
|
352 $commentText = ltrim($this->tokens[$stackPtr]['content'], ' /*');
|
Chris@17
|
353 $commentTextLower = strtolower($commentText);
|
Chris@17
|
354 if (strpos($commentText, '@codingStandards') !== false) {
|
Chris@17
|
355 if (strpos($commentText, '@codingStandardsIgnoreFile') !== false) {
|
Chris@17
|
356 // Ignoring the whole file, just a little late.
|
Chris@17
|
357 $this->errors = [];
|
Chris@17
|
358 $this->warnings = [];
|
Chris@17
|
359 $this->errorCount = 0;
|
Chris@17
|
360 $this->warningCount = 0;
|
Chris@17
|
361 $this->fixableCount = 0;
|
Chris@17
|
362 return;
|
Chris@17
|
363 } else if (strpos($commentText, '@codingStandardsChangeSetting') !== false) {
|
Chris@17
|
364 $start = strpos($commentText, '@codingStandardsChangeSetting');
|
Chris@17
|
365 $comment = substr($commentText, ($start + 30));
|
Chris@17
|
366 $parts = explode(' ', $comment);
|
Chris@17
|
367 if (count($parts) >= 2) {
|
Chris@17
|
368 $sniffParts = explode('.', $parts[0]);
|
Chris@17
|
369 if (count($sniffParts) >= 3) {
|
Chris@17
|
370 // If the sniff code is not known to us, it has not been registered in this run.
|
Chris@17
|
371 // But don't throw an error as it could be there for a different standard to use.
|
Chris@17
|
372 if (isset($this->ruleset->sniffCodes[$parts[0]]) === true) {
|
Chris@17
|
373 $listenerCode = array_shift($parts);
|
Chris@17
|
374 $propertyCode = array_shift($parts);
|
Chris@17
|
375 $propertyValue = rtrim(implode(' ', $parts), " */\r\n");
|
Chris@17
|
376 $listenerClass = $this->ruleset->sniffCodes[$listenerCode];
|
Chris@17
|
377 $this->ruleset->setSniffProperty($listenerClass, $propertyCode, $propertyValue);
|
Chris@17
|
378 }
|
Chris@17
|
379 }
|
Chris@17
|
380 }
|
Chris@17
|
381 }//end if
|
Chris@17
|
382 } else if (substr($commentTextLower, 0, 16) === 'phpcs:ignorefile'
|
Chris@17
|
383 || substr($commentTextLower, 0, 17) === '@phpcs:ignorefile'
|
Chris@17
|
384 ) {
|
Chris@17
|
385 // Ignoring the whole file, just a little late.
|
Chris@17
|
386 $this->errors = [];
|
Chris@17
|
387 $this->warnings = [];
|
Chris@17
|
388 $this->errorCount = 0;
|
Chris@17
|
389 $this->warningCount = 0;
|
Chris@17
|
390 $this->fixableCount = 0;
|
Chris@17
|
391 return;
|
Chris@17
|
392 } else if (substr($commentTextLower, 0, 9) === 'phpcs:set'
|
Chris@17
|
393 || substr($commentTextLower, 0, 10) === '@phpcs:set'
|
Chris@17
|
394 ) {
|
Chris@17
|
395 // If the @phpcs: syntax is being used, strip the @ to make
|
Chris@17
|
396 // comparisons easier.
|
Chris@17
|
397 if ($commentText[0] === '@') {
|
Chris@17
|
398 $commentText = substr($commentText, 1);
|
Chris@17
|
399 }
|
Chris@17
|
400
|
Chris@17
|
401 // Need to maintain case here, to get the correct sniff code.
|
Chris@17
|
402 $parts = explode(' ', substr($commentText, 10));
|
Chris@17
|
403 if (count($parts) >= 2) {
|
Chris@17
|
404 $sniffParts = explode('.', $parts[0]);
|
Chris@17
|
405 if (count($sniffParts) >= 3) {
|
Chris@17
|
406 // If the sniff code is not known to us, it has not been registered in this run.
|
Chris@17
|
407 // But don't throw an error as it could be there for a different standard to use.
|
Chris@17
|
408 if (isset($this->ruleset->sniffCodes[$parts[0]]) === true) {
|
Chris@17
|
409 $listenerCode = array_shift($parts);
|
Chris@17
|
410 $propertyCode = array_shift($parts);
|
Chris@17
|
411 $propertyValue = rtrim(implode(' ', $parts), " */\r\n");
|
Chris@17
|
412 $listenerClass = $this->ruleset->sniffCodes[$listenerCode];
|
Chris@17
|
413 $this->ruleset->setSniffProperty($listenerClass, $propertyCode, $propertyValue);
|
Chris@17
|
414 }
|
Chris@17
|
415 }
|
Chris@17
|
416 }
|
Chris@17
|
417 }//end if
|
Chris@17
|
418 }//end if
|
Chris@17
|
419
|
Chris@17
|
420 if (PHP_CODESNIFFER_VERBOSITY > 2) {
|
Chris@17
|
421 $type = $token['type'];
|
Chris@17
|
422 $content = Util\Common::prepareForOutput($token['content']);
|
Chris@17
|
423 echo "\t\tProcess token $stackPtr: $type => $content".PHP_EOL;
|
Chris@17
|
424 }
|
Chris@17
|
425
|
Chris@17
|
426 if ($token['code'] !== T_INLINE_HTML) {
|
Chris@17
|
427 $foundCode = true;
|
Chris@17
|
428 }
|
Chris@17
|
429
|
Chris@17
|
430 if (isset($this->ruleset->tokenListeners[$token['code']]) === false) {
|
Chris@17
|
431 continue;
|
Chris@17
|
432 }
|
Chris@17
|
433
|
Chris@17
|
434 foreach ($this->ruleset->tokenListeners[$token['code']] as $listenerData) {
|
Chris@17
|
435 if (isset($this->ignoredListeners[$listenerData['class']]) === true
|
Chris@17
|
436 || (isset($listenerIgnoreTo[$listenerData['class']]) === true
|
Chris@17
|
437 && $listenerIgnoreTo[$listenerData['class']] > $stackPtr)
|
Chris@17
|
438 ) {
|
Chris@17
|
439 // This sniff is ignoring past this token, or the whole file.
|
Chris@17
|
440 continue;
|
Chris@17
|
441 }
|
Chris@17
|
442
|
Chris@17
|
443 // Make sure this sniff supports the tokenizer
|
Chris@17
|
444 // we are currently using.
|
Chris@17
|
445 $class = $listenerData['class'];
|
Chris@17
|
446
|
Chris@17
|
447 if (isset($listenerData['tokenizers'][$this->tokenizerType]) === false) {
|
Chris@17
|
448 continue;
|
Chris@17
|
449 }
|
Chris@17
|
450
|
Chris@17
|
451 // If the file path matches one of our ignore patterns, skip it.
|
Chris@17
|
452 // While there is support for a type of each pattern
|
Chris@17
|
453 // (absolute or relative) we don't actually support it here.
|
Chris@17
|
454 foreach ($listenerData['ignore'] as $pattern) {
|
Chris@17
|
455 // We assume a / directory separator, as do the exclude rules
|
Chris@17
|
456 // most developers write, so we need a special case for any system
|
Chris@17
|
457 // that is different.
|
Chris@17
|
458 if (DIRECTORY_SEPARATOR === '\\') {
|
Chris@17
|
459 $pattern = str_replace('/', '\\\\', $pattern);
|
Chris@17
|
460 }
|
Chris@17
|
461
|
Chris@17
|
462 $pattern = '`'.$pattern.'`i';
|
Chris@17
|
463 if (preg_match($pattern, $this->path) === 1) {
|
Chris@17
|
464 $this->ignoredListeners[$class] = true;
|
Chris@17
|
465 continue(2);
|
Chris@17
|
466 }
|
Chris@17
|
467 }
|
Chris@17
|
468
|
Chris@17
|
469 // If the file path does not match one of our include patterns, skip it.
|
Chris@17
|
470 // While there is support for a type of each pattern
|
Chris@17
|
471 // (absolute or relative) we don't actually support it here.
|
Chris@17
|
472 if (empty($listenerData['include']) === false) {
|
Chris@17
|
473 $included = false;
|
Chris@17
|
474 foreach ($listenerData['include'] as $pattern) {
|
Chris@17
|
475 // We assume a / directory separator, as do the exclude rules
|
Chris@17
|
476 // most developers write, so we need a special case for any system
|
Chris@17
|
477 // that is different.
|
Chris@17
|
478 if (DIRECTORY_SEPARATOR === '\\') {
|
Chris@17
|
479 $pattern = str_replace('/', '\\\\', $pattern);
|
Chris@17
|
480 }
|
Chris@17
|
481
|
Chris@17
|
482 $pattern = '`'.$pattern.'`i';
|
Chris@17
|
483 if (preg_match($pattern, $this->path) === 1) {
|
Chris@17
|
484 $included = true;
|
Chris@17
|
485 break;
|
Chris@17
|
486 }
|
Chris@17
|
487 }
|
Chris@17
|
488
|
Chris@17
|
489 if ($included === false) {
|
Chris@17
|
490 $this->ignoredListeners[$class] = true;
|
Chris@17
|
491 continue;
|
Chris@17
|
492 }
|
Chris@17
|
493 }//end if
|
Chris@17
|
494
|
Chris@17
|
495 $this->activeListener = $class;
|
Chris@17
|
496
|
Chris@17
|
497 if (PHP_CODESNIFFER_VERBOSITY > 2) {
|
Chris@17
|
498 $startTime = microtime(true);
|
Chris@17
|
499 echo "\t\t\tProcessing ".$this->activeListener.'... ';
|
Chris@17
|
500 }
|
Chris@17
|
501
|
Chris@17
|
502 $ignoreTo = $this->ruleset->sniffs[$class]->process($this, $stackPtr);
|
Chris@17
|
503 if ($ignoreTo !== null) {
|
Chris@17
|
504 $listenerIgnoreTo[$this->activeListener] = $ignoreTo;
|
Chris@17
|
505 }
|
Chris@17
|
506
|
Chris@17
|
507 if (PHP_CODESNIFFER_VERBOSITY > 2) {
|
Chris@17
|
508 $timeTaken = (microtime(true) - $startTime);
|
Chris@17
|
509 if (isset($this->listenerTimes[$this->activeListener]) === false) {
|
Chris@17
|
510 $this->listenerTimes[$this->activeListener] = 0;
|
Chris@17
|
511 }
|
Chris@17
|
512
|
Chris@17
|
513 $this->listenerTimes[$this->activeListener] += $timeTaken;
|
Chris@17
|
514
|
Chris@17
|
515 $timeTaken = round(($timeTaken), 4);
|
Chris@17
|
516 echo "DONE in $timeTaken seconds".PHP_EOL;
|
Chris@17
|
517 }
|
Chris@17
|
518
|
Chris@17
|
519 $this->activeListener = '';
|
Chris@17
|
520 }//end foreach
|
Chris@17
|
521 }//end foreach
|
Chris@17
|
522
|
Chris@17
|
523 // If short open tags are off but the file being checked uses
|
Chris@17
|
524 // short open tags, the whole content will be inline HTML
|
Chris@17
|
525 // and nothing will be checked. So try and handle this case.
|
Chris@17
|
526 // We don't show this error for STDIN because we can't be sure the content
|
Chris@17
|
527 // actually came directly from the user. It could be something like
|
Chris@17
|
528 // refs from a Git pre-push hook.
|
Chris@17
|
529 if ($foundCode === false && $this->tokenizerType === 'PHP' && $this->path !== 'STDIN') {
|
Chris@17
|
530 $shortTags = (bool) ini_get('short_open_tag');
|
Chris@17
|
531 if ($shortTags === false) {
|
Chris@17
|
532 $error = 'No PHP code was found in this file and short open tags are not allowed by this install of PHP. This file may be using short open tags but PHP does not allow them.';
|
Chris@17
|
533 $this->addWarning($error, null, 'Internal.NoCodeFound');
|
Chris@17
|
534 }
|
Chris@17
|
535 }
|
Chris@17
|
536
|
Chris@17
|
537 if (PHP_CODESNIFFER_VERBOSITY > 2) {
|
Chris@17
|
538 echo "\t*** END TOKEN PROCESSING ***".PHP_EOL;
|
Chris@17
|
539 echo "\t*** START SNIFF PROCESSING REPORT ***".PHP_EOL;
|
Chris@17
|
540
|
Chris@17
|
541 asort($this->listenerTimes, SORT_NUMERIC);
|
Chris@17
|
542 $this->listenerTimes = array_reverse($this->listenerTimes, true);
|
Chris@17
|
543 foreach ($this->listenerTimes as $listener => $timeTaken) {
|
Chris@17
|
544 echo "\t$listener: ".round(($timeTaken), 4).' secs'.PHP_EOL;
|
Chris@17
|
545 }
|
Chris@17
|
546
|
Chris@17
|
547 echo "\t*** END SNIFF PROCESSING REPORT ***".PHP_EOL;
|
Chris@17
|
548 }
|
Chris@17
|
549
|
Chris@17
|
550 $this->fixedCount += $this->fixer->getFixCount();
|
Chris@17
|
551
|
Chris@17
|
552 }//end process()
|
Chris@17
|
553
|
Chris@17
|
554
|
Chris@17
|
555 /**
|
Chris@17
|
556 * Tokenizes the file and prepares it for the test run.
|
Chris@17
|
557 *
|
Chris@17
|
558 * @return void
|
Chris@17
|
559 */
|
Chris@17
|
560 public function parse()
|
Chris@17
|
561 {
|
Chris@17
|
562 if (empty($this->tokens) === false) {
|
Chris@17
|
563 // File has already been parsed.
|
Chris@17
|
564 return;
|
Chris@17
|
565 }
|
Chris@17
|
566
|
Chris@17
|
567 try {
|
Chris@17
|
568 $tokenizerClass = 'PHP_CodeSniffer\Tokenizers\\'.$this->tokenizerType;
|
Chris@17
|
569 $this->tokenizer = new $tokenizerClass($this->content, $this->config, $this->eolChar);
|
Chris@17
|
570 $this->tokens = $this->tokenizer->getTokens();
|
Chris@17
|
571 } catch (TokenizerException $e) {
|
Chris@17
|
572 $this->ignored = true;
|
Chris@17
|
573 $this->addWarning($e->getMessage(), null, 'Internal.Tokenizer.Exception');
|
Chris@17
|
574 if (PHP_CODESNIFFER_VERBOSITY > 0) {
|
Chris@17
|
575 echo "[$this->tokenizerType => tokenizer error]... ";
|
Chris@17
|
576 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
577 echo PHP_EOL;
|
Chris@17
|
578 }
|
Chris@17
|
579 }
|
Chris@17
|
580
|
Chris@17
|
581 return;
|
Chris@17
|
582 }
|
Chris@17
|
583
|
Chris@17
|
584 $this->numTokens = count($this->tokens);
|
Chris@17
|
585
|
Chris@17
|
586 // Check for mixed line endings as these can cause tokenizer errors and we
|
Chris@17
|
587 // should let the user know that the results they get may be incorrect.
|
Chris@17
|
588 // This is done by removing all backslashes, removing the newline char we
|
Chris@17
|
589 // detected, then converting newlines chars into text. If any backslashes
|
Chris@17
|
590 // are left at the end, we have additional newline chars in use.
|
Chris@17
|
591 $contents = str_replace('\\', '', $this->content);
|
Chris@17
|
592 $contents = str_replace($this->eolChar, '', $contents);
|
Chris@17
|
593 $contents = str_replace("\n", '\n', $contents);
|
Chris@17
|
594 $contents = str_replace("\r", '\r', $contents);
|
Chris@17
|
595 if (strpos($contents, '\\') !== false) {
|
Chris@17
|
596 $error = 'File has mixed line endings; this may cause incorrect results';
|
Chris@17
|
597 $this->addWarningOnLine($error, 1, 'Internal.LineEndings.Mixed');
|
Chris@17
|
598 }
|
Chris@17
|
599
|
Chris@17
|
600 if (PHP_CODESNIFFER_VERBOSITY > 0) {
|
Chris@17
|
601 if ($this->numTokens === 0) {
|
Chris@17
|
602 $numLines = 0;
|
Chris@17
|
603 } else {
|
Chris@17
|
604 $numLines = $this->tokens[($this->numTokens - 1)]['line'];
|
Chris@17
|
605 }
|
Chris@17
|
606
|
Chris@17
|
607 echo "[$this->tokenizerType => $this->numTokens tokens in $numLines lines]... ";
|
Chris@17
|
608 if (PHP_CODESNIFFER_VERBOSITY > 1) {
|
Chris@17
|
609 echo PHP_EOL;
|
Chris@17
|
610 }
|
Chris@17
|
611 }
|
Chris@17
|
612
|
Chris@17
|
613 }//end parse()
|
Chris@17
|
614
|
Chris@17
|
615
|
Chris@17
|
616 /**
|
Chris@17
|
617 * Returns the token stack for this file.
|
Chris@17
|
618 *
|
Chris@17
|
619 * @return array
|
Chris@17
|
620 */
|
Chris@17
|
621 public function getTokens()
|
Chris@17
|
622 {
|
Chris@17
|
623 return $this->tokens;
|
Chris@17
|
624
|
Chris@17
|
625 }//end getTokens()
|
Chris@17
|
626
|
Chris@17
|
627
|
Chris@17
|
628 /**
|
Chris@17
|
629 * Remove vars stored in this file that are no longer required.
|
Chris@17
|
630 *
|
Chris@17
|
631 * @return void
|
Chris@17
|
632 */
|
Chris@17
|
633 public function cleanUp()
|
Chris@17
|
634 {
|
Chris@17
|
635 $this->listenerTimes = null;
|
Chris@17
|
636 $this->content = null;
|
Chris@17
|
637 $this->tokens = null;
|
Chris@17
|
638 $this->metricTokens = null;
|
Chris@17
|
639 $this->tokenizer = null;
|
Chris@17
|
640 $this->fixer = null;
|
Chris@17
|
641 $this->config = null;
|
Chris@17
|
642 $this->ruleset = null;
|
Chris@17
|
643
|
Chris@17
|
644 }//end cleanUp()
|
Chris@17
|
645
|
Chris@17
|
646
|
Chris@17
|
647 /**
|
Chris@17
|
648 * Records an error against a specific token in the file.
|
Chris@17
|
649 *
|
Chris@17
|
650 * @param string $error The error message.
|
Chris@17
|
651 * @param int $stackPtr The stack position where the error occurred.
|
Chris@17
|
652 * @param string $code A violation code unique to the sniff message.
|
Chris@17
|
653 * @param array $data Replacements for the error message.
|
Chris@17
|
654 * @param int $severity The severity level for this error. A value of 0
|
Chris@17
|
655 * will be converted into the default severity level.
|
Chris@17
|
656 * @param boolean $fixable Can the error be fixed by the sniff?
|
Chris@17
|
657 *
|
Chris@17
|
658 * @return boolean
|
Chris@17
|
659 */
|
Chris@17
|
660 public function addError(
|
Chris@17
|
661 $error,
|
Chris@17
|
662 $stackPtr,
|
Chris@17
|
663 $code,
|
Chris@17
|
664 $data=[],
|
Chris@17
|
665 $severity=0,
|
Chris@17
|
666 $fixable=false
|
Chris@17
|
667 ) {
|
Chris@17
|
668 if ($stackPtr === null) {
|
Chris@17
|
669 $line = 1;
|
Chris@17
|
670 $column = 1;
|
Chris@17
|
671 } else {
|
Chris@17
|
672 $line = $this->tokens[$stackPtr]['line'];
|
Chris@17
|
673 $column = $this->tokens[$stackPtr]['column'];
|
Chris@17
|
674 }
|
Chris@17
|
675
|
Chris@17
|
676 return $this->addMessage(true, $error, $line, $column, $code, $data, $severity, $fixable);
|
Chris@17
|
677
|
Chris@17
|
678 }//end addError()
|
Chris@17
|
679
|
Chris@17
|
680
|
Chris@17
|
681 /**
|
Chris@17
|
682 * Records a warning against a specific token in the file.
|
Chris@17
|
683 *
|
Chris@17
|
684 * @param string $warning The error message.
|
Chris@17
|
685 * @param int $stackPtr The stack position where the error occurred.
|
Chris@17
|
686 * @param string $code A violation code unique to the sniff message.
|
Chris@17
|
687 * @param array $data Replacements for the warning message.
|
Chris@17
|
688 * @param int $severity The severity level for this warning. A value of 0
|
Chris@17
|
689 * will be converted into the default severity level.
|
Chris@17
|
690 * @param boolean $fixable Can the warning be fixed by the sniff?
|
Chris@17
|
691 *
|
Chris@17
|
692 * @return boolean
|
Chris@17
|
693 */
|
Chris@17
|
694 public function addWarning(
|
Chris@17
|
695 $warning,
|
Chris@17
|
696 $stackPtr,
|
Chris@17
|
697 $code,
|
Chris@17
|
698 $data=[],
|
Chris@17
|
699 $severity=0,
|
Chris@17
|
700 $fixable=false
|
Chris@17
|
701 ) {
|
Chris@17
|
702 if ($stackPtr === null) {
|
Chris@17
|
703 $line = 1;
|
Chris@17
|
704 $column = 1;
|
Chris@17
|
705 } else {
|
Chris@17
|
706 $line = $this->tokens[$stackPtr]['line'];
|
Chris@17
|
707 $column = $this->tokens[$stackPtr]['column'];
|
Chris@17
|
708 }
|
Chris@17
|
709
|
Chris@17
|
710 return $this->addMessage(false, $warning, $line, $column, $code, $data, $severity, $fixable);
|
Chris@17
|
711
|
Chris@17
|
712 }//end addWarning()
|
Chris@17
|
713
|
Chris@17
|
714
|
Chris@17
|
715 /**
|
Chris@17
|
716 * Records an error against a specific line in the file.
|
Chris@17
|
717 *
|
Chris@17
|
718 * @param string $error The error message.
|
Chris@17
|
719 * @param int $line The line on which the error occurred.
|
Chris@17
|
720 * @param string $code A violation code unique to the sniff message.
|
Chris@17
|
721 * @param array $data Replacements for the error message.
|
Chris@17
|
722 * @param int $severity The severity level for this error. A value of 0
|
Chris@17
|
723 * will be converted into the default severity level.
|
Chris@17
|
724 *
|
Chris@17
|
725 * @return boolean
|
Chris@17
|
726 */
|
Chris@17
|
727 public function addErrorOnLine(
|
Chris@17
|
728 $error,
|
Chris@17
|
729 $line,
|
Chris@17
|
730 $code,
|
Chris@17
|
731 $data=[],
|
Chris@17
|
732 $severity=0
|
Chris@17
|
733 ) {
|
Chris@17
|
734 return $this->addMessage(true, $error, $line, 1, $code, $data, $severity, false);
|
Chris@17
|
735
|
Chris@17
|
736 }//end addErrorOnLine()
|
Chris@17
|
737
|
Chris@17
|
738
|
Chris@17
|
739 /**
|
Chris@17
|
740 * Records a warning against a specific token in the file.
|
Chris@17
|
741 *
|
Chris@17
|
742 * @param string $warning The error message.
|
Chris@17
|
743 * @param int $line The line on which the warning occurred.
|
Chris@17
|
744 * @param string $code A violation code unique to the sniff message.
|
Chris@17
|
745 * @param array $data Replacements for the warning message.
|
Chris@17
|
746 * @param int $severity The severity level for this warning. A value of 0 will
|
Chris@17
|
747 * will be converted into the default severity level.
|
Chris@17
|
748 *
|
Chris@17
|
749 * @return boolean
|
Chris@17
|
750 */
|
Chris@17
|
751 public function addWarningOnLine(
|
Chris@17
|
752 $warning,
|
Chris@17
|
753 $line,
|
Chris@17
|
754 $code,
|
Chris@17
|
755 $data=[],
|
Chris@17
|
756 $severity=0
|
Chris@17
|
757 ) {
|
Chris@17
|
758 return $this->addMessage(false, $warning, $line, 1, $code, $data, $severity, false);
|
Chris@17
|
759
|
Chris@17
|
760 }//end addWarningOnLine()
|
Chris@17
|
761
|
Chris@17
|
762
|
Chris@17
|
763 /**
|
Chris@17
|
764 * Records a fixable error against a specific token in the file.
|
Chris@17
|
765 *
|
Chris@17
|
766 * Returns true if the error was recorded and should be fixed.
|
Chris@17
|
767 *
|
Chris@17
|
768 * @param string $error The error message.
|
Chris@17
|
769 * @param int $stackPtr The stack position where the error occurred.
|
Chris@17
|
770 * @param string $code A violation code unique to the sniff message.
|
Chris@17
|
771 * @param array $data Replacements for the error message.
|
Chris@17
|
772 * @param int $severity The severity level for this error. A value of 0
|
Chris@17
|
773 * will be converted into the default severity level.
|
Chris@17
|
774 *
|
Chris@17
|
775 * @return boolean
|
Chris@17
|
776 */
|
Chris@17
|
777 public function addFixableError(
|
Chris@17
|
778 $error,
|
Chris@17
|
779 $stackPtr,
|
Chris@17
|
780 $code,
|
Chris@17
|
781 $data=[],
|
Chris@17
|
782 $severity=0
|
Chris@17
|
783 ) {
|
Chris@17
|
784 $recorded = $this->addError($error, $stackPtr, $code, $data, $severity, true);
|
Chris@17
|
785 if ($recorded === true && $this->fixer->enabled === true) {
|
Chris@17
|
786 return true;
|
Chris@17
|
787 }
|
Chris@17
|
788
|
Chris@17
|
789 return false;
|
Chris@17
|
790
|
Chris@17
|
791 }//end addFixableError()
|
Chris@17
|
792
|
Chris@17
|
793
|
Chris@17
|
794 /**
|
Chris@17
|
795 * Records a fixable warning against a specific token in the file.
|
Chris@17
|
796 *
|
Chris@17
|
797 * Returns true if the warning was recorded and should be fixed.
|
Chris@17
|
798 *
|
Chris@17
|
799 * @param string $warning The error message.
|
Chris@17
|
800 * @param int $stackPtr The stack position where the error occurred.
|
Chris@17
|
801 * @param string $code A violation code unique to the sniff message.
|
Chris@17
|
802 * @param array $data Replacements for the warning message.
|
Chris@17
|
803 * @param int $severity The severity level for this warning. A value of 0
|
Chris@17
|
804 * will be converted into the default severity level.
|
Chris@17
|
805 *
|
Chris@17
|
806 * @return boolean
|
Chris@17
|
807 */
|
Chris@17
|
808 public function addFixableWarning(
|
Chris@17
|
809 $warning,
|
Chris@17
|
810 $stackPtr,
|
Chris@17
|
811 $code,
|
Chris@17
|
812 $data=[],
|
Chris@17
|
813 $severity=0
|
Chris@17
|
814 ) {
|
Chris@17
|
815 $recorded = $this->addWarning($warning, $stackPtr, $code, $data, $severity, true);
|
Chris@17
|
816 if ($recorded === true && $this->fixer->enabled === true) {
|
Chris@17
|
817 return true;
|
Chris@17
|
818 }
|
Chris@17
|
819
|
Chris@17
|
820 return false;
|
Chris@17
|
821
|
Chris@17
|
822 }//end addFixableWarning()
|
Chris@17
|
823
|
Chris@17
|
824
|
Chris@17
|
825 /**
|
Chris@17
|
826 * Adds an error to the error stack.
|
Chris@17
|
827 *
|
Chris@17
|
828 * @param boolean $error Is this an error message?
|
Chris@17
|
829 * @param string $message The text of the message.
|
Chris@17
|
830 * @param int $line The line on which the message occurred.
|
Chris@17
|
831 * @param int $column The column at which the message occurred.
|
Chris@17
|
832 * @param string $code A violation code unique to the sniff message.
|
Chris@17
|
833 * @param array $data Replacements for the message.
|
Chris@17
|
834 * @param int $severity The severity level for this message. A value of 0
|
Chris@17
|
835 * will be converted into the default severity level.
|
Chris@17
|
836 * @param boolean $fixable Can the problem be fixed by the sniff?
|
Chris@17
|
837 *
|
Chris@17
|
838 * @return boolean
|
Chris@17
|
839 */
|
Chris@17
|
840 protected function addMessage($error, $message, $line, $column, $code, $data, $severity, $fixable)
|
Chris@17
|
841 {
|
Chris@17
|
842 // Check if this line is ignoring all message codes.
|
Chris@17
|
843 if (isset($this->tokenizer->ignoredLines[$line]['.all']) === true) {
|
Chris@17
|
844 return false;
|
Chris@17
|
845 }
|
Chris@17
|
846
|
Chris@17
|
847 // Work out which sniff generated the message.
|
Chris@17
|
848 $parts = explode('.', $code);
|
Chris@17
|
849 if ($parts[0] === 'Internal') {
|
Chris@17
|
850 // An internal message.
|
Chris@17
|
851 $listenerCode = Util\Common::getSniffCode($this->activeListener);
|
Chris@17
|
852 $sniffCode = $code;
|
Chris@17
|
853 $checkCodes = [$sniffCode];
|
Chris@17
|
854 } else {
|
Chris@17
|
855 if ($parts[0] !== $code) {
|
Chris@17
|
856 // The full message code has been passed in.
|
Chris@17
|
857 $sniffCode = $code;
|
Chris@17
|
858 $listenerCode = substr($sniffCode, 0, strrpos($sniffCode, '.'));
|
Chris@17
|
859 } else {
|
Chris@17
|
860 $listenerCode = Util\Common::getSniffCode($this->activeListener);
|
Chris@17
|
861 $sniffCode = $listenerCode.'.'.$code;
|
Chris@17
|
862 $parts = explode('.', $sniffCode);
|
Chris@17
|
863 }
|
Chris@17
|
864
|
Chris@17
|
865 $checkCodes = [
|
Chris@17
|
866 $sniffCode,
|
Chris@17
|
867 $parts[0].'.'.$parts[1].'.'.$parts[2],
|
Chris@17
|
868 $parts[0].'.'.$parts[1],
|
Chris@17
|
869 $parts[0],
|
Chris@17
|
870 ];
|
Chris@17
|
871 }//end if
|
Chris@17
|
872
|
Chris@17
|
873 if (isset($this->tokenizer->ignoredLines[$line]) === true) {
|
Chris@17
|
874 // Check if this line is ignoring this specific message.
|
Chris@17
|
875 $ignored = false;
|
Chris@17
|
876 foreach ($checkCodes as $checkCode) {
|
Chris@17
|
877 if (isset($this->tokenizer->ignoredLines[$line][$checkCode]) === true) {
|
Chris@17
|
878 $ignored = true;
|
Chris@17
|
879 break;
|
Chris@17
|
880 }
|
Chris@17
|
881 }
|
Chris@17
|
882
|
Chris@17
|
883 // If it is ignored, make sure it's not whitelisted.
|
Chris@17
|
884 if ($ignored === true
|
Chris@17
|
885 && isset($this->tokenizer->ignoredLines[$line]['.except']) === true
|
Chris@17
|
886 ) {
|
Chris@17
|
887 foreach ($checkCodes as $checkCode) {
|
Chris@17
|
888 if (isset($this->tokenizer->ignoredLines[$line]['.except'][$checkCode]) === true) {
|
Chris@17
|
889 $ignored = false;
|
Chris@17
|
890 break;
|
Chris@17
|
891 }
|
Chris@17
|
892 }
|
Chris@17
|
893 }
|
Chris@17
|
894
|
Chris@17
|
895 if ($ignored === true) {
|
Chris@17
|
896 return false;
|
Chris@17
|
897 }
|
Chris@17
|
898 }//end if
|
Chris@17
|
899
|
Chris@17
|
900 $includeAll = true;
|
Chris@17
|
901 if ($this->configCache['cache'] === false
|
Chris@17
|
902 || $this->configCache['recordErrors'] === false
|
Chris@17
|
903 ) {
|
Chris@17
|
904 $includeAll = false;
|
Chris@17
|
905 }
|
Chris@17
|
906
|
Chris@17
|
907 // Filter out any messages for sniffs that shouldn't have run
|
Chris@17
|
908 // due to the use of the --sniffs command line argument.
|
Chris@17
|
909 if ($includeAll === false
|
Chris@17
|
910 && ((empty($this->configCache['sniffs']) === false
|
Chris@17
|
911 && in_array(strtolower($listenerCode), $this->configCache['sniffs']) === false)
|
Chris@17
|
912 || (empty($this->configCache['exclude']) === false
|
Chris@17
|
913 && in_array(strtolower($listenerCode), $this->configCache['exclude']) === true))
|
Chris@17
|
914 ) {
|
Chris@17
|
915 return false;
|
Chris@17
|
916 }
|
Chris@17
|
917
|
Chris@17
|
918 // If we know this sniff code is being ignored for this file, return early.
|
Chris@17
|
919 foreach ($checkCodes as $checkCode) {
|
Chris@17
|
920 if (isset($this->ignoredCodes[$checkCode]) === true) {
|
Chris@17
|
921 return false;
|
Chris@17
|
922 }
|
Chris@17
|
923 }
|
Chris@17
|
924
|
Chris@17
|
925 $oppositeType = 'warning';
|
Chris@17
|
926 if ($error === false) {
|
Chris@17
|
927 $oppositeType = 'error';
|
Chris@17
|
928 }
|
Chris@17
|
929
|
Chris@17
|
930 foreach ($checkCodes as $checkCode) {
|
Chris@17
|
931 // Make sure this message type has not been set to the opposite message type.
|
Chris@17
|
932 if (isset($this->ruleset->ruleset[$checkCode]['type']) === true
|
Chris@17
|
933 && $this->ruleset->ruleset[$checkCode]['type'] === $oppositeType
|
Chris@17
|
934 ) {
|
Chris@17
|
935 $error = !$error;
|
Chris@17
|
936 break;
|
Chris@17
|
937 }
|
Chris@17
|
938 }
|
Chris@17
|
939
|
Chris@17
|
940 if ($error === true) {
|
Chris@17
|
941 $configSeverity = $this->configCache['errorSeverity'];
|
Chris@17
|
942 $messageCount = &$this->errorCount;
|
Chris@17
|
943 $messages = &$this->errors;
|
Chris@17
|
944 } else {
|
Chris@17
|
945 $configSeverity = $this->configCache['warningSeverity'];
|
Chris@17
|
946 $messageCount = &$this->warningCount;
|
Chris@17
|
947 $messages = &$this->warnings;
|
Chris@17
|
948 }
|
Chris@17
|
949
|
Chris@17
|
950 if ($includeAll === false && $configSeverity === 0) {
|
Chris@17
|
951 // Don't bother doing any processing as these messages are just going to
|
Chris@17
|
952 // be hidden in the reports anyway.
|
Chris@17
|
953 return false;
|
Chris@17
|
954 }
|
Chris@17
|
955
|
Chris@17
|
956 if ($severity === 0) {
|
Chris@17
|
957 $severity = 5;
|
Chris@17
|
958 }
|
Chris@17
|
959
|
Chris@17
|
960 foreach ($checkCodes as $checkCode) {
|
Chris@17
|
961 // Make sure we are interested in this severity level.
|
Chris@17
|
962 if (isset($this->ruleset->ruleset[$checkCode]['severity']) === true) {
|
Chris@17
|
963 $severity = $this->ruleset->ruleset[$checkCode]['severity'];
|
Chris@17
|
964 break;
|
Chris@17
|
965 }
|
Chris@17
|
966 }
|
Chris@17
|
967
|
Chris@17
|
968 if ($includeAll === false && $configSeverity > $severity) {
|
Chris@17
|
969 return false;
|
Chris@17
|
970 }
|
Chris@17
|
971
|
Chris@17
|
972 // Make sure we are not ignoring this file.
|
Chris@17
|
973 $included = null;
|
Chris@17
|
974 foreach ($checkCodes as $checkCode) {
|
Chris@17
|
975 $patterns = null;
|
Chris@17
|
976
|
Chris@17
|
977 if (isset($this->configCache['includePatterns'][$checkCode]) === true) {
|
Chris@17
|
978 $patterns = $this->configCache['includePatterns'][$checkCode];
|
Chris@17
|
979 $excluding = false;
|
Chris@17
|
980 } else if (isset($this->configCache['ignorePatterns'][$checkCode]) === true) {
|
Chris@17
|
981 $patterns = $this->configCache['ignorePatterns'][$checkCode];
|
Chris@17
|
982 $excluding = true;
|
Chris@17
|
983 }
|
Chris@17
|
984
|
Chris@17
|
985 if ($patterns === null) {
|
Chris@17
|
986 continue;
|
Chris@17
|
987 }
|
Chris@17
|
988
|
Chris@17
|
989 foreach ($patterns as $pattern => $type) {
|
Chris@17
|
990 // While there is support for a type of each pattern
|
Chris@17
|
991 // (absolute or relative) we don't actually support it here.
|
Chris@17
|
992 $replacements = [
|
Chris@17
|
993 '\\,' => ',',
|
Chris@17
|
994 '*' => '.*',
|
Chris@17
|
995 ];
|
Chris@17
|
996
|
Chris@17
|
997 // We assume a / directory separator, as do the exclude rules
|
Chris@17
|
998 // most developers write, so we need a special case for any system
|
Chris@17
|
999 // that is different.
|
Chris@17
|
1000 if (DIRECTORY_SEPARATOR === '\\') {
|
Chris@17
|
1001 $replacements['/'] = '\\\\';
|
Chris@17
|
1002 }
|
Chris@17
|
1003
|
Chris@17
|
1004 $pattern = '`'.strtr($pattern, $replacements).'`i';
|
Chris@17
|
1005 $matched = preg_match($pattern, $this->path);
|
Chris@17
|
1006
|
Chris@17
|
1007 if ($matched === 0) {
|
Chris@17
|
1008 if ($excluding === false && $included === null) {
|
Chris@17
|
1009 // This file path is not being included.
|
Chris@17
|
1010 $included = false;
|
Chris@17
|
1011 }
|
Chris@17
|
1012
|
Chris@17
|
1013 continue;
|
Chris@17
|
1014 }
|
Chris@17
|
1015
|
Chris@17
|
1016 if ($excluding === true) {
|
Chris@17
|
1017 // This file path is being excluded.
|
Chris@17
|
1018 $this->ignoredCodes[$checkCode] = true;
|
Chris@17
|
1019 return false;
|
Chris@17
|
1020 }
|
Chris@17
|
1021
|
Chris@17
|
1022 // This file path is being included.
|
Chris@17
|
1023 $included = true;
|
Chris@17
|
1024 break;
|
Chris@17
|
1025 }//end foreach
|
Chris@17
|
1026 }//end foreach
|
Chris@17
|
1027
|
Chris@17
|
1028 if ($included === false) {
|
Chris@17
|
1029 // There were include rules set, but this file
|
Chris@17
|
1030 // path didn't match any of them.
|
Chris@17
|
1031 return false;
|
Chris@17
|
1032 }
|
Chris@17
|
1033
|
Chris@17
|
1034 $messageCount++;
|
Chris@17
|
1035 if ($fixable === true) {
|
Chris@17
|
1036 $this->fixableCount++;
|
Chris@17
|
1037 }
|
Chris@17
|
1038
|
Chris@17
|
1039 if ($this->configCache['recordErrors'] === false
|
Chris@17
|
1040 && $includeAll === false
|
Chris@17
|
1041 ) {
|
Chris@17
|
1042 return true;
|
Chris@17
|
1043 }
|
Chris@17
|
1044
|
Chris@17
|
1045 // Work out the error message.
|
Chris@17
|
1046 if (isset($this->ruleset->ruleset[$sniffCode]['message']) === true) {
|
Chris@17
|
1047 $message = $this->ruleset->ruleset[$sniffCode]['message'];
|
Chris@17
|
1048 }
|
Chris@17
|
1049
|
Chris@17
|
1050 if (empty($data) === false) {
|
Chris@17
|
1051 $message = vsprintf($message, $data);
|
Chris@17
|
1052 }
|
Chris@17
|
1053
|
Chris@17
|
1054 if (isset($messages[$line]) === false) {
|
Chris@17
|
1055 $messages[$line] = [];
|
Chris@17
|
1056 }
|
Chris@17
|
1057
|
Chris@17
|
1058 if (isset($messages[$line][$column]) === false) {
|
Chris@17
|
1059 $messages[$line][$column] = [];
|
Chris@17
|
1060 }
|
Chris@17
|
1061
|
Chris@17
|
1062 $messages[$line][$column][] = [
|
Chris@17
|
1063 'message' => $message,
|
Chris@17
|
1064 'source' => $sniffCode,
|
Chris@17
|
1065 'listener' => $this->activeListener,
|
Chris@17
|
1066 'severity' => $severity,
|
Chris@17
|
1067 'fixable' => $fixable,
|
Chris@17
|
1068 ];
|
Chris@17
|
1069
|
Chris@17
|
1070 if (PHP_CODESNIFFER_VERBOSITY > 1
|
Chris@17
|
1071 && $this->fixer->enabled === true
|
Chris@17
|
1072 && $fixable === true
|
Chris@17
|
1073 ) {
|
Chris@17
|
1074 @ob_end_clean();
|
Chris@17
|
1075 echo "\tE: [Line $line] $message ($sniffCode)".PHP_EOL;
|
Chris@17
|
1076 ob_start();
|
Chris@17
|
1077 }
|
Chris@17
|
1078
|
Chris@17
|
1079 return true;
|
Chris@17
|
1080
|
Chris@17
|
1081 }//end addMessage()
|
Chris@17
|
1082
|
Chris@17
|
1083
|
Chris@17
|
1084 /**
|
Chris@17
|
1085 * Record a metric about the file being examined.
|
Chris@17
|
1086 *
|
Chris@17
|
1087 * @param int $stackPtr The stack position where the metric was recorded.
|
Chris@17
|
1088 * @param string $metric The name of the metric being recorded.
|
Chris@17
|
1089 * @param string $value The value of the metric being recorded.
|
Chris@17
|
1090 *
|
Chris@17
|
1091 * @return boolean
|
Chris@17
|
1092 */
|
Chris@17
|
1093 public function recordMetric($stackPtr, $metric, $value)
|
Chris@17
|
1094 {
|
Chris@17
|
1095 if (isset($this->metrics[$metric]) === false) {
|
Chris@17
|
1096 $this->metrics[$metric] = ['values' => [$value => 1]];
|
Chris@17
|
1097 $this->metricTokens[$metric][$stackPtr] = true;
|
Chris@17
|
1098 } else if (isset($this->metricTokens[$metric][$stackPtr]) === false) {
|
Chris@17
|
1099 $this->metricTokens[$metric][$stackPtr] = true;
|
Chris@17
|
1100 if (isset($this->metrics[$metric]['values'][$value]) === false) {
|
Chris@17
|
1101 $this->metrics[$metric]['values'][$value] = 1;
|
Chris@17
|
1102 } else {
|
Chris@17
|
1103 $this->metrics[$metric]['values'][$value]++;
|
Chris@17
|
1104 }
|
Chris@17
|
1105 }
|
Chris@17
|
1106
|
Chris@17
|
1107 return true;
|
Chris@17
|
1108
|
Chris@17
|
1109 }//end recordMetric()
|
Chris@17
|
1110
|
Chris@17
|
1111
|
Chris@17
|
1112 /**
|
Chris@17
|
1113 * Returns the number of errors raised.
|
Chris@17
|
1114 *
|
Chris@17
|
1115 * @return int
|
Chris@17
|
1116 */
|
Chris@17
|
1117 public function getErrorCount()
|
Chris@17
|
1118 {
|
Chris@17
|
1119 return $this->errorCount;
|
Chris@17
|
1120
|
Chris@17
|
1121 }//end getErrorCount()
|
Chris@17
|
1122
|
Chris@17
|
1123
|
Chris@17
|
1124 /**
|
Chris@17
|
1125 * Returns the number of warnings raised.
|
Chris@17
|
1126 *
|
Chris@17
|
1127 * @return int
|
Chris@17
|
1128 */
|
Chris@17
|
1129 public function getWarningCount()
|
Chris@17
|
1130 {
|
Chris@17
|
1131 return $this->warningCount;
|
Chris@17
|
1132
|
Chris@17
|
1133 }//end getWarningCount()
|
Chris@17
|
1134
|
Chris@17
|
1135
|
Chris@17
|
1136 /**
|
Chris@17
|
1137 * Returns the number of successes recorded.
|
Chris@17
|
1138 *
|
Chris@17
|
1139 * @return int
|
Chris@17
|
1140 */
|
Chris@17
|
1141 public function getSuccessCount()
|
Chris@17
|
1142 {
|
Chris@17
|
1143 return $this->successCount;
|
Chris@17
|
1144
|
Chris@17
|
1145 }//end getSuccessCount()
|
Chris@17
|
1146
|
Chris@17
|
1147
|
Chris@17
|
1148 /**
|
Chris@17
|
1149 * Returns the number of fixable errors/warnings raised.
|
Chris@17
|
1150 *
|
Chris@17
|
1151 * @return int
|
Chris@17
|
1152 */
|
Chris@17
|
1153 public function getFixableCount()
|
Chris@17
|
1154 {
|
Chris@17
|
1155 return $this->fixableCount;
|
Chris@17
|
1156
|
Chris@17
|
1157 }//end getFixableCount()
|
Chris@17
|
1158
|
Chris@17
|
1159
|
Chris@17
|
1160 /**
|
Chris@17
|
1161 * Returns the number of fixed errors/warnings.
|
Chris@17
|
1162 *
|
Chris@17
|
1163 * @return int
|
Chris@17
|
1164 */
|
Chris@17
|
1165 public function getFixedCount()
|
Chris@17
|
1166 {
|
Chris@17
|
1167 return $this->fixedCount;
|
Chris@17
|
1168
|
Chris@17
|
1169 }//end getFixedCount()
|
Chris@17
|
1170
|
Chris@17
|
1171
|
Chris@17
|
1172 /**
|
Chris@17
|
1173 * Returns the list of ignored lines.
|
Chris@17
|
1174 *
|
Chris@17
|
1175 * @return array
|
Chris@17
|
1176 */
|
Chris@17
|
1177 public function getIgnoredLines()
|
Chris@17
|
1178 {
|
Chris@17
|
1179 return $this->tokenizer->ignoredLines;
|
Chris@17
|
1180
|
Chris@17
|
1181 }//end getIgnoredLines()
|
Chris@17
|
1182
|
Chris@17
|
1183
|
Chris@17
|
1184 /**
|
Chris@17
|
1185 * Returns the errors raised from processing this file.
|
Chris@17
|
1186 *
|
Chris@17
|
1187 * @return array
|
Chris@17
|
1188 */
|
Chris@17
|
1189 public function getErrors()
|
Chris@17
|
1190 {
|
Chris@17
|
1191 return $this->errors;
|
Chris@17
|
1192
|
Chris@17
|
1193 }//end getErrors()
|
Chris@17
|
1194
|
Chris@17
|
1195
|
Chris@17
|
1196 /**
|
Chris@17
|
1197 * Returns the warnings raised from processing this file.
|
Chris@17
|
1198 *
|
Chris@17
|
1199 * @return array
|
Chris@17
|
1200 */
|
Chris@17
|
1201 public function getWarnings()
|
Chris@17
|
1202 {
|
Chris@17
|
1203 return $this->warnings;
|
Chris@17
|
1204
|
Chris@17
|
1205 }//end getWarnings()
|
Chris@17
|
1206
|
Chris@17
|
1207
|
Chris@17
|
1208 /**
|
Chris@17
|
1209 * Returns the metrics found while processing this file.
|
Chris@17
|
1210 *
|
Chris@17
|
1211 * @return array
|
Chris@17
|
1212 */
|
Chris@17
|
1213 public function getMetrics()
|
Chris@17
|
1214 {
|
Chris@17
|
1215 return $this->metrics;
|
Chris@17
|
1216
|
Chris@17
|
1217 }//end getMetrics()
|
Chris@17
|
1218
|
Chris@17
|
1219
|
Chris@17
|
1220 /**
|
Chris@17
|
1221 * Returns the absolute filename of this file.
|
Chris@17
|
1222 *
|
Chris@17
|
1223 * @return string
|
Chris@17
|
1224 */
|
Chris@17
|
1225 public function getFilename()
|
Chris@17
|
1226 {
|
Chris@17
|
1227 return $this->path;
|
Chris@17
|
1228
|
Chris@17
|
1229 }//end getFilename()
|
Chris@17
|
1230
|
Chris@17
|
1231
|
Chris@17
|
1232 /**
|
Chris@17
|
1233 * Returns the declaration names for classes, interfaces, traits, and functions.
|
Chris@17
|
1234 *
|
Chris@17
|
1235 * @param int $stackPtr The position of the declaration token which
|
Chris@17
|
1236 * declared the class, interface, trait, or function.
|
Chris@17
|
1237 *
|
Chris@17
|
1238 * @return string|null The name of the class, interface, trait, or function;
|
Chris@17
|
1239 * or NULL if the function or class is anonymous.
|
Chris@17
|
1240 * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If the specified token is not of type
|
Chris@17
|
1241 * T_FUNCTION, T_CLASS, T_ANON_CLASS,
|
Chris@17
|
1242 * T_CLOSURE, T_TRAIT, or T_INTERFACE.
|
Chris@17
|
1243 */
|
Chris@17
|
1244 public function getDeclarationName($stackPtr)
|
Chris@17
|
1245 {
|
Chris@17
|
1246 $tokenCode = $this->tokens[$stackPtr]['code'];
|
Chris@17
|
1247
|
Chris@17
|
1248 if ($tokenCode === T_ANON_CLASS || $tokenCode === T_CLOSURE) {
|
Chris@17
|
1249 return null;
|
Chris@17
|
1250 }
|
Chris@17
|
1251
|
Chris@17
|
1252 if ($tokenCode !== T_FUNCTION
|
Chris@17
|
1253 && $tokenCode !== T_CLASS
|
Chris@17
|
1254 && $tokenCode !== T_INTERFACE
|
Chris@17
|
1255 && $tokenCode !== T_TRAIT
|
Chris@17
|
1256 ) {
|
Chris@17
|
1257 throw new RuntimeException('Token type "'.$this->tokens[$stackPtr]['type'].'" is not T_FUNCTION, T_CLASS, T_INTERFACE or T_TRAIT');
|
Chris@17
|
1258 }
|
Chris@17
|
1259
|
Chris@17
|
1260 if ($tokenCode === T_FUNCTION
|
Chris@17
|
1261 && strtolower($this->tokens[$stackPtr]['content']) !== 'function'
|
Chris@17
|
1262 ) {
|
Chris@17
|
1263 // This is a function declared without the "function" keyword.
|
Chris@17
|
1264 // So this token is the function name.
|
Chris@17
|
1265 return $this->tokens[$stackPtr]['content'];
|
Chris@17
|
1266 }
|
Chris@17
|
1267
|
Chris@17
|
1268 $content = null;
|
Chris@17
|
1269 for ($i = $stackPtr; $i < $this->numTokens; $i++) {
|
Chris@17
|
1270 if ($this->tokens[$i]['code'] === T_STRING) {
|
Chris@17
|
1271 $content = $this->tokens[$i]['content'];
|
Chris@17
|
1272 break;
|
Chris@17
|
1273 }
|
Chris@17
|
1274 }
|
Chris@17
|
1275
|
Chris@17
|
1276 return $content;
|
Chris@17
|
1277
|
Chris@17
|
1278 }//end getDeclarationName()
|
Chris@17
|
1279
|
Chris@17
|
1280
|
Chris@17
|
1281 /**
|
Chris@17
|
1282 * Returns the method parameters for the specified function token.
|
Chris@17
|
1283 *
|
Chris@17
|
1284 * Each parameter is in the following format:
|
Chris@17
|
1285 *
|
Chris@17
|
1286 * <code>
|
Chris@17
|
1287 * 0 => array(
|
Chris@17
|
1288 * 'name' => '$var', // The variable name.
|
Chris@17
|
1289 * 'token' => integer, // The stack pointer to the variable name.
|
Chris@17
|
1290 * 'content' => string, // The full content of the variable definition.
|
Chris@17
|
1291 * 'pass_by_reference' => boolean, // Is the variable passed by reference?
|
Chris@17
|
1292 * 'variable_length' => boolean, // Is the param of variable length through use of `...` ?
|
Chris@17
|
1293 * 'type_hint' => string, // The type hint for the variable.
|
Chris@17
|
1294 * 'type_hint_token' => integer, // The stack pointer to the type hint
|
Chris@17
|
1295 * // or false if there is no type hint.
|
Chris@17
|
1296 * 'nullable_type' => boolean, // Is the variable using a nullable type?
|
Chris@17
|
1297 * )
|
Chris@17
|
1298 * </code>
|
Chris@17
|
1299 *
|
Chris@17
|
1300 * Parameters with default values have an additional array index of
|
Chris@17
|
1301 * 'default' with the value of the default as a string.
|
Chris@17
|
1302 *
|
Chris@17
|
1303 * @param int $stackPtr The position in the stack of the function token
|
Chris@17
|
1304 * to acquire the parameters for.
|
Chris@17
|
1305 *
|
Chris@17
|
1306 * @return array
|
Chris@17
|
1307 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified $stackPtr is not of
|
Chris@17
|
1308 * type T_FUNCTION or T_CLOSURE.
|
Chris@17
|
1309 */
|
Chris@17
|
1310 public function getMethodParameters($stackPtr)
|
Chris@17
|
1311 {
|
Chris@17
|
1312 if ($this->tokens[$stackPtr]['code'] !== T_FUNCTION
|
Chris@17
|
1313 && $this->tokens[$stackPtr]['code'] !== T_CLOSURE
|
Chris@17
|
1314 ) {
|
Chris@17
|
1315 throw new TokenizerException('$stackPtr must be of type T_FUNCTION or T_CLOSURE');
|
Chris@17
|
1316 }
|
Chris@17
|
1317
|
Chris@17
|
1318 $opener = $this->tokens[$stackPtr]['parenthesis_opener'];
|
Chris@17
|
1319 $closer = $this->tokens[$stackPtr]['parenthesis_closer'];
|
Chris@17
|
1320
|
Chris@17
|
1321 $vars = [];
|
Chris@17
|
1322 $currVar = null;
|
Chris@17
|
1323 $paramStart = ($opener + 1);
|
Chris@17
|
1324 $defaultStart = null;
|
Chris@17
|
1325 $paramCount = 0;
|
Chris@17
|
1326 $passByReference = false;
|
Chris@17
|
1327 $variableLength = false;
|
Chris@17
|
1328 $typeHint = '';
|
Chris@17
|
1329 $typeHintToken = false;
|
Chris@17
|
1330 $nullableType = false;
|
Chris@17
|
1331
|
Chris@17
|
1332 for ($i = $paramStart; $i <= $closer; $i++) {
|
Chris@17
|
1333 // Check to see if this token has a parenthesis or bracket opener. If it does
|
Chris@17
|
1334 // it's likely to be an array which might have arguments in it. This
|
Chris@17
|
1335 // could cause problems in our parsing below, so lets just skip to the
|
Chris@17
|
1336 // end of it.
|
Chris@17
|
1337 if (isset($this->tokens[$i]['parenthesis_opener']) === true) {
|
Chris@17
|
1338 // Don't do this if it's the close parenthesis for the method.
|
Chris@17
|
1339 if ($i !== $this->tokens[$i]['parenthesis_closer']) {
|
Chris@17
|
1340 $i = ($this->tokens[$i]['parenthesis_closer'] + 1);
|
Chris@17
|
1341 }
|
Chris@17
|
1342 }
|
Chris@17
|
1343
|
Chris@17
|
1344 if (isset($this->tokens[$i]['bracket_opener']) === true) {
|
Chris@17
|
1345 // Don't do this if it's the close parenthesis for the method.
|
Chris@17
|
1346 if ($i !== $this->tokens[$i]['bracket_closer']) {
|
Chris@17
|
1347 $i = ($this->tokens[$i]['bracket_closer'] + 1);
|
Chris@17
|
1348 }
|
Chris@17
|
1349 }
|
Chris@17
|
1350
|
Chris@17
|
1351 switch ($this->tokens[$i]['code']) {
|
Chris@17
|
1352 case T_BITWISE_AND:
|
Chris@17
|
1353 if ($defaultStart === null) {
|
Chris@17
|
1354 $passByReference = true;
|
Chris@17
|
1355 }
|
Chris@17
|
1356 break;
|
Chris@17
|
1357 case T_VARIABLE:
|
Chris@17
|
1358 $currVar = $i;
|
Chris@17
|
1359 break;
|
Chris@17
|
1360 case T_ELLIPSIS:
|
Chris@17
|
1361 $variableLength = true;
|
Chris@17
|
1362 break;
|
Chris@17
|
1363 case T_CALLABLE:
|
Chris@17
|
1364 if ($typeHintToken === false) {
|
Chris@17
|
1365 $typeHintToken = $i;
|
Chris@17
|
1366 }
|
Chris@17
|
1367
|
Chris@17
|
1368 $typeHint .= $this->tokens[$i]['content'];
|
Chris@17
|
1369 break;
|
Chris@17
|
1370 case T_SELF:
|
Chris@17
|
1371 case T_PARENT:
|
Chris@17
|
1372 case T_STATIC:
|
Chris@17
|
1373 // Self and parent are valid, static invalid, but was probably intended as type hint.
|
Chris@17
|
1374 if (isset($defaultStart) === false) {
|
Chris@17
|
1375 if ($typeHintToken === false) {
|
Chris@17
|
1376 $typeHintToken = $i;
|
Chris@17
|
1377 }
|
Chris@17
|
1378
|
Chris@17
|
1379 $typeHint .= $this->tokens[$i]['content'];
|
Chris@17
|
1380 }
|
Chris@17
|
1381 break;
|
Chris@17
|
1382 case T_STRING:
|
Chris@17
|
1383 // This is a string, so it may be a type hint, but it could
|
Chris@17
|
1384 // also be a constant used as a default value.
|
Chris@17
|
1385 $prevComma = false;
|
Chris@17
|
1386 for ($t = $i; $t >= $opener; $t--) {
|
Chris@17
|
1387 if ($this->tokens[$t]['code'] === T_COMMA) {
|
Chris@17
|
1388 $prevComma = $t;
|
Chris@17
|
1389 break;
|
Chris@17
|
1390 }
|
Chris@17
|
1391 }
|
Chris@17
|
1392
|
Chris@17
|
1393 if ($prevComma !== false) {
|
Chris@17
|
1394 $nextEquals = false;
|
Chris@17
|
1395 for ($t = $prevComma; $t < $i; $t++) {
|
Chris@17
|
1396 if ($this->tokens[$t]['code'] === T_EQUAL) {
|
Chris@17
|
1397 $nextEquals = $t;
|
Chris@17
|
1398 break;
|
Chris@17
|
1399 }
|
Chris@17
|
1400 }
|
Chris@17
|
1401
|
Chris@17
|
1402 if ($nextEquals !== false) {
|
Chris@17
|
1403 break;
|
Chris@17
|
1404 }
|
Chris@17
|
1405 }
|
Chris@17
|
1406
|
Chris@17
|
1407 if ($defaultStart === null) {
|
Chris@17
|
1408 if ($typeHintToken === false) {
|
Chris@17
|
1409 $typeHintToken = $i;
|
Chris@17
|
1410 }
|
Chris@17
|
1411
|
Chris@17
|
1412 $typeHint .= $this->tokens[$i]['content'];
|
Chris@17
|
1413 }
|
Chris@17
|
1414 break;
|
Chris@17
|
1415 case T_NS_SEPARATOR:
|
Chris@17
|
1416 // Part of a type hint or default value.
|
Chris@17
|
1417 if ($defaultStart === null) {
|
Chris@17
|
1418 if ($typeHintToken === false) {
|
Chris@17
|
1419 $typeHintToken = $i;
|
Chris@17
|
1420 }
|
Chris@17
|
1421
|
Chris@17
|
1422 $typeHint .= $this->tokens[$i]['content'];
|
Chris@17
|
1423 }
|
Chris@17
|
1424 break;
|
Chris@17
|
1425 case T_NULLABLE:
|
Chris@17
|
1426 if ($defaultStart === null) {
|
Chris@17
|
1427 $nullableType = true;
|
Chris@17
|
1428 $typeHint .= $this->tokens[$i]['content'];
|
Chris@17
|
1429 }
|
Chris@17
|
1430 break;
|
Chris@17
|
1431 case T_CLOSE_PARENTHESIS:
|
Chris@17
|
1432 case T_COMMA:
|
Chris@17
|
1433 // If it's null, then there must be no parameters for this
|
Chris@17
|
1434 // method.
|
Chris@17
|
1435 if ($currVar === null) {
|
Chris@17
|
1436 continue 2;
|
Chris@17
|
1437 }
|
Chris@17
|
1438
|
Chris@17
|
1439 $vars[$paramCount] = [];
|
Chris@17
|
1440 $vars[$paramCount]['token'] = $currVar;
|
Chris@17
|
1441 $vars[$paramCount]['name'] = $this->tokens[$currVar]['content'];
|
Chris@17
|
1442 $vars[$paramCount]['content'] = trim($this->getTokensAsString($paramStart, ($i - $paramStart)));
|
Chris@17
|
1443
|
Chris@17
|
1444 if ($defaultStart !== null) {
|
Chris@17
|
1445 $vars[$paramCount]['default'] = trim($this->getTokensAsString($defaultStart, ($i - $defaultStart)));
|
Chris@17
|
1446 }
|
Chris@17
|
1447
|
Chris@17
|
1448 $vars[$paramCount]['pass_by_reference'] = $passByReference;
|
Chris@17
|
1449 $vars[$paramCount]['variable_length'] = $variableLength;
|
Chris@17
|
1450 $vars[$paramCount]['type_hint'] = $typeHint;
|
Chris@17
|
1451 $vars[$paramCount]['type_hint_token'] = $typeHintToken;
|
Chris@17
|
1452 $vars[$paramCount]['nullable_type'] = $nullableType;
|
Chris@17
|
1453
|
Chris@17
|
1454 // Reset the vars, as we are about to process the next parameter.
|
Chris@17
|
1455 $defaultStart = null;
|
Chris@17
|
1456 $paramStart = ($i + 1);
|
Chris@17
|
1457 $passByReference = false;
|
Chris@17
|
1458 $variableLength = false;
|
Chris@17
|
1459 $typeHint = '';
|
Chris@17
|
1460 $typeHintToken = false;
|
Chris@17
|
1461 $nullableType = false;
|
Chris@17
|
1462
|
Chris@17
|
1463 $paramCount++;
|
Chris@17
|
1464 break;
|
Chris@17
|
1465 case T_EQUAL:
|
Chris@17
|
1466 $defaultStart = ($i + 1);
|
Chris@17
|
1467 break;
|
Chris@17
|
1468 }//end switch
|
Chris@17
|
1469 }//end for
|
Chris@17
|
1470
|
Chris@17
|
1471 return $vars;
|
Chris@17
|
1472
|
Chris@17
|
1473 }//end getMethodParameters()
|
Chris@17
|
1474
|
Chris@17
|
1475
|
Chris@17
|
1476 /**
|
Chris@17
|
1477 * Returns the visibility and implementation properties of a method.
|
Chris@17
|
1478 *
|
Chris@17
|
1479 * The format of the array is:
|
Chris@17
|
1480 * <code>
|
Chris@17
|
1481 * array(
|
Chris@17
|
1482 * 'scope' => 'public', // public protected or protected
|
Chris@17
|
1483 * 'scope_specified' => true, // true is scope keyword was found.
|
Chris@17
|
1484 * 'return_type' => '', // the return type of the method.
|
Chris@17
|
1485 * 'return_type_token' => integer, // The stack pointer to the start of the return type
|
Chris@17
|
1486 * // or false if there is no return type.
|
Chris@17
|
1487 * 'nullable_return_type' => false, // true if the return type is nullable.
|
Chris@17
|
1488 * 'is_abstract' => false, // true if the abstract keyword was found.
|
Chris@17
|
1489 * 'is_final' => false, // true if the final keyword was found.
|
Chris@17
|
1490 * 'is_static' => false, // true if the static keyword was found.
|
Chris@17
|
1491 * 'has_body' => false, // true if the method has a body
|
Chris@17
|
1492 * );
|
Chris@17
|
1493 * </code>
|
Chris@17
|
1494 *
|
Chris@17
|
1495 * @param int $stackPtr The position in the stack of the function token to
|
Chris@17
|
1496 * acquire the properties for.
|
Chris@17
|
1497 *
|
Chris@17
|
1498 * @return array
|
Chris@17
|
1499 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a
|
Chris@17
|
1500 * T_FUNCTION token.
|
Chris@17
|
1501 */
|
Chris@17
|
1502 public function getMethodProperties($stackPtr)
|
Chris@17
|
1503 {
|
Chris@17
|
1504 if ($this->tokens[$stackPtr]['code'] !== T_FUNCTION
|
Chris@17
|
1505 && $this->tokens[$stackPtr]['code'] !== T_CLOSURE
|
Chris@17
|
1506 ) {
|
Chris@17
|
1507 throw new TokenizerException('$stackPtr must be of type T_FUNCTION or T_CLOSURE');
|
Chris@17
|
1508 }
|
Chris@17
|
1509
|
Chris@17
|
1510 if ($this->tokens[$stackPtr]['code'] === T_FUNCTION) {
|
Chris@17
|
1511 $valid = [
|
Chris@17
|
1512 T_PUBLIC => T_PUBLIC,
|
Chris@17
|
1513 T_PRIVATE => T_PRIVATE,
|
Chris@17
|
1514 T_PROTECTED => T_PROTECTED,
|
Chris@17
|
1515 T_STATIC => T_STATIC,
|
Chris@17
|
1516 T_FINAL => T_FINAL,
|
Chris@17
|
1517 T_ABSTRACT => T_ABSTRACT,
|
Chris@17
|
1518 T_WHITESPACE => T_WHITESPACE,
|
Chris@17
|
1519 T_COMMENT => T_COMMENT,
|
Chris@17
|
1520 T_DOC_COMMENT => T_DOC_COMMENT,
|
Chris@17
|
1521 ];
|
Chris@17
|
1522 } else {
|
Chris@17
|
1523 $valid = [
|
Chris@17
|
1524 T_STATIC => T_STATIC,
|
Chris@17
|
1525 T_WHITESPACE => T_WHITESPACE,
|
Chris@17
|
1526 T_COMMENT => T_COMMENT,
|
Chris@17
|
1527 T_DOC_COMMENT => T_DOC_COMMENT,
|
Chris@17
|
1528 ];
|
Chris@17
|
1529 }
|
Chris@17
|
1530
|
Chris@17
|
1531 $scope = 'public';
|
Chris@17
|
1532 $scopeSpecified = false;
|
Chris@17
|
1533 $isAbstract = false;
|
Chris@17
|
1534 $isFinal = false;
|
Chris@17
|
1535 $isStatic = false;
|
Chris@17
|
1536
|
Chris@17
|
1537 for ($i = ($stackPtr - 1); $i > 0; $i--) {
|
Chris@17
|
1538 if (isset($valid[$this->tokens[$i]['code']]) === false) {
|
Chris@17
|
1539 break;
|
Chris@17
|
1540 }
|
Chris@17
|
1541
|
Chris@17
|
1542 switch ($this->tokens[$i]['code']) {
|
Chris@17
|
1543 case T_PUBLIC:
|
Chris@17
|
1544 $scope = 'public';
|
Chris@17
|
1545 $scopeSpecified = true;
|
Chris@17
|
1546 break;
|
Chris@17
|
1547 case T_PRIVATE:
|
Chris@17
|
1548 $scope = 'private';
|
Chris@17
|
1549 $scopeSpecified = true;
|
Chris@17
|
1550 break;
|
Chris@17
|
1551 case T_PROTECTED:
|
Chris@17
|
1552 $scope = 'protected';
|
Chris@17
|
1553 $scopeSpecified = true;
|
Chris@17
|
1554 break;
|
Chris@17
|
1555 case T_ABSTRACT:
|
Chris@17
|
1556 $isAbstract = true;
|
Chris@17
|
1557 break;
|
Chris@17
|
1558 case T_FINAL:
|
Chris@17
|
1559 $isFinal = true;
|
Chris@17
|
1560 break;
|
Chris@17
|
1561 case T_STATIC:
|
Chris@17
|
1562 $isStatic = true;
|
Chris@17
|
1563 break;
|
Chris@17
|
1564 }//end switch
|
Chris@17
|
1565 }//end for
|
Chris@17
|
1566
|
Chris@17
|
1567 $returnType = '';
|
Chris@17
|
1568 $returnTypeToken = false;
|
Chris@17
|
1569 $nullableReturnType = false;
|
Chris@17
|
1570 $hasBody = true;
|
Chris@17
|
1571
|
Chris@17
|
1572 if (isset($this->tokens[$stackPtr]['parenthesis_closer']) === true) {
|
Chris@17
|
1573 $scopeOpener = null;
|
Chris@17
|
1574 if (isset($this->tokens[$stackPtr]['scope_opener']) === true) {
|
Chris@17
|
1575 $scopeOpener = $this->tokens[$stackPtr]['scope_opener'];
|
Chris@17
|
1576 }
|
Chris@17
|
1577
|
Chris@17
|
1578 $valid = [
|
Chris@17
|
1579 T_STRING => T_STRING,
|
Chris@17
|
1580 T_CALLABLE => T_CALLABLE,
|
Chris@17
|
1581 T_SELF => T_SELF,
|
Chris@17
|
1582 T_PARENT => T_PARENT,
|
Chris@17
|
1583 T_NS_SEPARATOR => T_NS_SEPARATOR,
|
Chris@17
|
1584 ];
|
Chris@17
|
1585
|
Chris@17
|
1586 for ($i = $this->tokens[$stackPtr]['parenthesis_closer']; $i < $this->numTokens; $i++) {
|
Chris@17
|
1587 if (($scopeOpener === null && $this->tokens[$i]['code'] === T_SEMICOLON)
|
Chris@17
|
1588 || ($scopeOpener !== null && $i === $scopeOpener)
|
Chris@17
|
1589 ) {
|
Chris@17
|
1590 // End of function definition.
|
Chris@17
|
1591 break;
|
Chris@17
|
1592 }
|
Chris@17
|
1593
|
Chris@17
|
1594 if ($this->tokens[$i]['code'] === T_NULLABLE) {
|
Chris@17
|
1595 $nullableReturnType = true;
|
Chris@17
|
1596 }
|
Chris@17
|
1597
|
Chris@17
|
1598 if (isset($valid[$this->tokens[$i]['code']]) === true) {
|
Chris@17
|
1599 if ($returnTypeToken === false) {
|
Chris@17
|
1600 $returnTypeToken = $i;
|
Chris@17
|
1601 }
|
Chris@17
|
1602
|
Chris@17
|
1603 $returnType .= $this->tokens[$i]['content'];
|
Chris@17
|
1604 }
|
Chris@17
|
1605 }
|
Chris@17
|
1606
|
Chris@17
|
1607 $end = $this->findNext([T_OPEN_CURLY_BRACKET, T_SEMICOLON], $this->tokens[$stackPtr]['parenthesis_closer']);
|
Chris@17
|
1608 $hasBody = $this->tokens[$end]['code'] === T_OPEN_CURLY_BRACKET;
|
Chris@17
|
1609 }//end if
|
Chris@17
|
1610
|
Chris@17
|
1611 if ($returnType !== '' && $nullableReturnType === true) {
|
Chris@17
|
1612 $returnType = '?'.$returnType;
|
Chris@17
|
1613 }
|
Chris@17
|
1614
|
Chris@17
|
1615 return [
|
Chris@17
|
1616 'scope' => $scope,
|
Chris@17
|
1617 'scope_specified' => $scopeSpecified,
|
Chris@17
|
1618 'return_type' => $returnType,
|
Chris@17
|
1619 'return_type_token' => $returnTypeToken,
|
Chris@17
|
1620 'nullable_return_type' => $nullableReturnType,
|
Chris@17
|
1621 'is_abstract' => $isAbstract,
|
Chris@17
|
1622 'is_final' => $isFinal,
|
Chris@17
|
1623 'is_static' => $isStatic,
|
Chris@17
|
1624 'has_body' => $hasBody,
|
Chris@17
|
1625 ];
|
Chris@17
|
1626
|
Chris@17
|
1627 }//end getMethodProperties()
|
Chris@17
|
1628
|
Chris@17
|
1629
|
Chris@17
|
1630 /**
|
Chris@17
|
1631 * Returns the visibility and implementation properties of the class member
|
Chris@17
|
1632 * variable found at the specified position in the stack.
|
Chris@17
|
1633 *
|
Chris@17
|
1634 * The format of the array is:
|
Chris@17
|
1635 *
|
Chris@17
|
1636 * <code>
|
Chris@17
|
1637 * array(
|
Chris@17
|
1638 * 'scope' => 'public', // public protected or protected.
|
Chris@17
|
1639 * 'scope_specified' => false, // true if the scope was explicitly specified.
|
Chris@17
|
1640 * 'is_static' => false, // true if the static keyword was found.
|
Chris@17
|
1641 * );
|
Chris@17
|
1642 * </code>
|
Chris@17
|
1643 *
|
Chris@17
|
1644 * @param int $stackPtr The position in the stack of the T_VARIABLE token to
|
Chris@17
|
1645 * acquire the properties for.
|
Chris@17
|
1646 *
|
Chris@17
|
1647 * @return array
|
Chris@17
|
1648 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a
|
Chris@17
|
1649 * T_VARIABLE token, or if the position is not
|
Chris@17
|
1650 * a class member variable.
|
Chris@17
|
1651 */
|
Chris@17
|
1652 public function getMemberProperties($stackPtr)
|
Chris@17
|
1653 {
|
Chris@17
|
1654 if ($this->tokens[$stackPtr]['code'] !== T_VARIABLE) {
|
Chris@17
|
1655 throw new TokenizerException('$stackPtr must be of type T_VARIABLE');
|
Chris@17
|
1656 }
|
Chris@17
|
1657
|
Chris@17
|
1658 $conditions = array_keys($this->tokens[$stackPtr]['conditions']);
|
Chris@17
|
1659 $ptr = array_pop($conditions);
|
Chris@17
|
1660 if (isset($this->tokens[$ptr]) === false
|
Chris@17
|
1661 || ($this->tokens[$ptr]['code'] !== T_CLASS
|
Chris@17
|
1662 && $this->tokens[$ptr]['code'] !== T_ANON_CLASS
|
Chris@17
|
1663 && $this->tokens[$ptr]['code'] !== T_TRAIT)
|
Chris@17
|
1664 ) {
|
Chris@17
|
1665 if (isset($this->tokens[$ptr]) === true
|
Chris@17
|
1666 && $this->tokens[$ptr]['code'] === T_INTERFACE
|
Chris@17
|
1667 ) {
|
Chris@17
|
1668 // T_VARIABLEs in interfaces can actually be method arguments
|
Chris@17
|
1669 // but they wont be seen as being inside the method because there
|
Chris@17
|
1670 // are no scope openers and closers for abstract methods. If it is in
|
Chris@17
|
1671 // parentheses, we can be pretty sure it is a method argument.
|
Chris@17
|
1672 if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === false
|
Chris@17
|
1673 || empty($this->tokens[$stackPtr]['nested_parenthesis']) === true
|
Chris@17
|
1674 ) {
|
Chris@17
|
1675 $error = 'Possible parse error: interfaces may not include member vars';
|
Chris@17
|
1676 $this->addWarning($error, $stackPtr, 'Internal.ParseError.InterfaceHasMemberVar');
|
Chris@17
|
1677 return [];
|
Chris@17
|
1678 }
|
Chris@17
|
1679 } else {
|
Chris@17
|
1680 throw new TokenizerException('$stackPtr is not a class member var');
|
Chris@17
|
1681 }
|
Chris@17
|
1682 }
|
Chris@17
|
1683
|
Chris@17
|
1684 // Make sure it's not a method parameter.
|
Chris@17
|
1685 if (empty($this->tokens[$stackPtr]['nested_parenthesis']) === false) {
|
Chris@17
|
1686 $parenthesis = array_keys($this->tokens[$stackPtr]['nested_parenthesis']);
|
Chris@17
|
1687 $deepestOpen = array_pop($parenthesis);
|
Chris@17
|
1688 if ($deepestOpen > $ptr
|
Chris@17
|
1689 && isset($this->tokens[$deepestOpen]['parenthesis_owner']) === true
|
Chris@17
|
1690 && $this->tokens[$this->tokens[$deepestOpen]['parenthesis_owner']]['code'] === T_FUNCTION
|
Chris@17
|
1691 ) {
|
Chris@17
|
1692 throw new TokenizerException('$stackPtr is not a class member var');
|
Chris@17
|
1693 }
|
Chris@17
|
1694 }
|
Chris@17
|
1695
|
Chris@17
|
1696 $valid = [
|
Chris@17
|
1697 T_PUBLIC => T_PUBLIC,
|
Chris@17
|
1698 T_PRIVATE => T_PRIVATE,
|
Chris@17
|
1699 T_PROTECTED => T_PROTECTED,
|
Chris@17
|
1700 T_STATIC => T_STATIC,
|
Chris@17
|
1701 T_VAR => T_VAR,
|
Chris@17
|
1702 ];
|
Chris@17
|
1703
|
Chris@17
|
1704 $valid += Util\Tokens::$emptyTokens;
|
Chris@17
|
1705
|
Chris@17
|
1706 $scope = 'public';
|
Chris@17
|
1707 $scopeSpecified = false;
|
Chris@17
|
1708 $isStatic = false;
|
Chris@17
|
1709
|
Chris@17
|
1710 $startOfStatement = $this->findPrevious(
|
Chris@17
|
1711 [
|
Chris@17
|
1712 T_SEMICOLON,
|
Chris@17
|
1713 T_OPEN_CURLY_BRACKET,
|
Chris@17
|
1714 T_CLOSE_CURLY_BRACKET,
|
Chris@17
|
1715 ],
|
Chris@17
|
1716 ($stackPtr - 1)
|
Chris@17
|
1717 );
|
Chris@17
|
1718
|
Chris@17
|
1719 for ($i = ($startOfStatement + 1); $i < $stackPtr; $i++) {
|
Chris@17
|
1720 if (isset($valid[$this->tokens[$i]['code']]) === false) {
|
Chris@17
|
1721 break;
|
Chris@17
|
1722 }
|
Chris@17
|
1723
|
Chris@17
|
1724 switch ($this->tokens[$i]['code']) {
|
Chris@17
|
1725 case T_PUBLIC:
|
Chris@17
|
1726 $scope = 'public';
|
Chris@17
|
1727 $scopeSpecified = true;
|
Chris@17
|
1728 break;
|
Chris@17
|
1729 case T_PRIVATE:
|
Chris@17
|
1730 $scope = 'private';
|
Chris@17
|
1731 $scopeSpecified = true;
|
Chris@17
|
1732 break;
|
Chris@17
|
1733 case T_PROTECTED:
|
Chris@17
|
1734 $scope = 'protected';
|
Chris@17
|
1735 $scopeSpecified = true;
|
Chris@17
|
1736 break;
|
Chris@17
|
1737 case T_STATIC:
|
Chris@17
|
1738 $isStatic = true;
|
Chris@17
|
1739 break;
|
Chris@17
|
1740 }
|
Chris@17
|
1741 }//end for
|
Chris@17
|
1742
|
Chris@17
|
1743 return [
|
Chris@17
|
1744 'scope' => $scope,
|
Chris@17
|
1745 'scope_specified' => $scopeSpecified,
|
Chris@17
|
1746 'is_static' => $isStatic,
|
Chris@17
|
1747 ];
|
Chris@17
|
1748
|
Chris@17
|
1749 }//end getMemberProperties()
|
Chris@17
|
1750
|
Chris@17
|
1751
|
Chris@17
|
1752 /**
|
Chris@17
|
1753 * Returns the visibility and implementation properties of a class.
|
Chris@17
|
1754 *
|
Chris@17
|
1755 * The format of the array is:
|
Chris@17
|
1756 * <code>
|
Chris@17
|
1757 * array(
|
Chris@17
|
1758 * 'is_abstract' => false, // true if the abstract keyword was found.
|
Chris@17
|
1759 * 'is_final' => false, // true if the final keyword was found.
|
Chris@17
|
1760 * );
|
Chris@17
|
1761 * </code>
|
Chris@17
|
1762 *
|
Chris@17
|
1763 * @param int $stackPtr The position in the stack of the T_CLASS token to
|
Chris@17
|
1764 * acquire the properties for.
|
Chris@17
|
1765 *
|
Chris@17
|
1766 * @return array
|
Chris@17
|
1767 * @throws \PHP_CodeSniffer\Exceptions\TokenizerException If the specified position is not a
|
Chris@17
|
1768 * T_CLASS token.
|
Chris@17
|
1769 */
|
Chris@17
|
1770 public function getClassProperties($stackPtr)
|
Chris@17
|
1771 {
|
Chris@17
|
1772 if ($this->tokens[$stackPtr]['code'] !== T_CLASS) {
|
Chris@17
|
1773 throw new TokenizerException('$stackPtr must be of type T_CLASS');
|
Chris@17
|
1774 }
|
Chris@17
|
1775
|
Chris@17
|
1776 $valid = [
|
Chris@17
|
1777 T_FINAL => T_FINAL,
|
Chris@17
|
1778 T_ABSTRACT => T_ABSTRACT,
|
Chris@17
|
1779 T_WHITESPACE => T_WHITESPACE,
|
Chris@17
|
1780 T_COMMENT => T_COMMENT,
|
Chris@17
|
1781 T_DOC_COMMENT => T_DOC_COMMENT,
|
Chris@17
|
1782 ];
|
Chris@17
|
1783
|
Chris@17
|
1784 $isAbstract = false;
|
Chris@17
|
1785 $isFinal = false;
|
Chris@17
|
1786
|
Chris@17
|
1787 for ($i = ($stackPtr - 1); $i > 0; $i--) {
|
Chris@17
|
1788 if (isset($valid[$this->tokens[$i]['code']]) === false) {
|
Chris@17
|
1789 break;
|
Chris@17
|
1790 }
|
Chris@17
|
1791
|
Chris@17
|
1792 switch ($this->tokens[$i]['code']) {
|
Chris@17
|
1793 case T_ABSTRACT:
|
Chris@17
|
1794 $isAbstract = true;
|
Chris@17
|
1795 break;
|
Chris@17
|
1796
|
Chris@17
|
1797 case T_FINAL:
|
Chris@17
|
1798 $isFinal = true;
|
Chris@17
|
1799 break;
|
Chris@17
|
1800 }
|
Chris@17
|
1801 }//end for
|
Chris@17
|
1802
|
Chris@17
|
1803 return [
|
Chris@17
|
1804 'is_abstract' => $isAbstract,
|
Chris@17
|
1805 'is_final' => $isFinal,
|
Chris@17
|
1806 ];
|
Chris@17
|
1807
|
Chris@17
|
1808 }//end getClassProperties()
|
Chris@17
|
1809
|
Chris@17
|
1810
|
Chris@17
|
1811 /**
|
Chris@17
|
1812 * Determine if the passed token is a reference operator.
|
Chris@17
|
1813 *
|
Chris@17
|
1814 * Returns true if the specified token position represents a reference.
|
Chris@17
|
1815 * Returns false if the token represents a bitwise operator.
|
Chris@17
|
1816 *
|
Chris@17
|
1817 * @param int $stackPtr The position of the T_BITWISE_AND token.
|
Chris@17
|
1818 *
|
Chris@17
|
1819 * @return boolean
|
Chris@17
|
1820 */
|
Chris@17
|
1821 public function isReference($stackPtr)
|
Chris@17
|
1822 {
|
Chris@17
|
1823 if ($this->tokens[$stackPtr]['code'] !== T_BITWISE_AND) {
|
Chris@17
|
1824 return false;
|
Chris@17
|
1825 }
|
Chris@17
|
1826
|
Chris@17
|
1827 $tokenBefore = $this->findPrevious(
|
Chris@17
|
1828 Util\Tokens::$emptyTokens,
|
Chris@17
|
1829 ($stackPtr - 1),
|
Chris@17
|
1830 null,
|
Chris@17
|
1831 true
|
Chris@17
|
1832 );
|
Chris@17
|
1833
|
Chris@17
|
1834 if ($this->tokens[$tokenBefore]['code'] === T_FUNCTION) {
|
Chris@17
|
1835 // Function returns a reference.
|
Chris@17
|
1836 return true;
|
Chris@17
|
1837 }
|
Chris@17
|
1838
|
Chris@17
|
1839 if ($this->tokens[$tokenBefore]['code'] === T_DOUBLE_ARROW) {
|
Chris@17
|
1840 // Inside a foreach loop or array assignment, this is a reference.
|
Chris@17
|
1841 return true;
|
Chris@17
|
1842 }
|
Chris@17
|
1843
|
Chris@17
|
1844 if ($this->tokens[$tokenBefore]['code'] === T_AS) {
|
Chris@17
|
1845 // Inside a foreach loop, this is a reference.
|
Chris@17
|
1846 return true;
|
Chris@17
|
1847 }
|
Chris@17
|
1848
|
Chris@17
|
1849 if (isset(Util\Tokens::$assignmentTokens[$this->tokens[$tokenBefore]['code']]) === true) {
|
Chris@17
|
1850 // This is directly after an assignment. It's a reference. Even if
|
Chris@17
|
1851 // it is part of an operation, the other tests will handle it.
|
Chris@17
|
1852 return true;
|
Chris@17
|
1853 }
|
Chris@17
|
1854
|
Chris@17
|
1855 $tokenAfter = $this->findNext(
|
Chris@17
|
1856 Util\Tokens::$emptyTokens,
|
Chris@17
|
1857 ($stackPtr + 1),
|
Chris@17
|
1858 null,
|
Chris@17
|
1859 true
|
Chris@17
|
1860 );
|
Chris@17
|
1861
|
Chris@17
|
1862 if ($this->tokens[$tokenAfter]['code'] === T_NEW) {
|
Chris@17
|
1863 return true;
|
Chris@17
|
1864 }
|
Chris@17
|
1865
|
Chris@17
|
1866 if (isset($this->tokens[$stackPtr]['nested_parenthesis']) === true) {
|
Chris@17
|
1867 $brackets = $this->tokens[$stackPtr]['nested_parenthesis'];
|
Chris@17
|
1868 $lastBracket = array_pop($brackets);
|
Chris@17
|
1869 if (isset($this->tokens[$lastBracket]['parenthesis_owner']) === true) {
|
Chris@17
|
1870 $owner = $this->tokens[$this->tokens[$lastBracket]['parenthesis_owner']];
|
Chris@17
|
1871 if ($owner['code'] === T_FUNCTION
|
Chris@17
|
1872 || $owner['code'] === T_CLOSURE
|
Chris@17
|
1873 ) {
|
Chris@17
|
1874 $params = $this->getMethodParameters($this->tokens[$lastBracket]['parenthesis_owner']);
|
Chris@17
|
1875 foreach ($params as $param) {
|
Chris@17
|
1876 $varToken = $tokenAfter;
|
Chris@17
|
1877 if ($param['variable_length'] === true) {
|
Chris@17
|
1878 $varToken = $this->findNext(
|
Chris@17
|
1879 (Util\Tokens::$emptyTokens + [T_ELLIPSIS]),
|
Chris@17
|
1880 ($stackPtr + 1),
|
Chris@17
|
1881 null,
|
Chris@17
|
1882 true
|
Chris@17
|
1883 );
|
Chris@17
|
1884 }
|
Chris@17
|
1885
|
Chris@17
|
1886 if ($param['token'] === $varToken
|
Chris@17
|
1887 && $param['pass_by_reference'] === true
|
Chris@17
|
1888 ) {
|
Chris@17
|
1889 // Function parameter declared to be passed by reference.
|
Chris@17
|
1890 return true;
|
Chris@17
|
1891 }
|
Chris@17
|
1892 }
|
Chris@17
|
1893 }//end if
|
Chris@17
|
1894 } else {
|
Chris@17
|
1895 $prev = false;
|
Chris@17
|
1896 for ($t = ($this->tokens[$lastBracket]['parenthesis_opener'] - 1); $t >= 0; $t--) {
|
Chris@17
|
1897 if ($this->tokens[$t]['code'] !== T_WHITESPACE) {
|
Chris@17
|
1898 $prev = $t;
|
Chris@17
|
1899 break;
|
Chris@17
|
1900 }
|
Chris@17
|
1901 }
|
Chris@17
|
1902
|
Chris@17
|
1903 if ($prev !== false && $this->tokens[$prev]['code'] === T_USE) {
|
Chris@17
|
1904 // Closure use by reference.
|
Chris@17
|
1905 return true;
|
Chris@17
|
1906 }
|
Chris@17
|
1907 }//end if
|
Chris@17
|
1908 }//end if
|
Chris@17
|
1909
|
Chris@17
|
1910 // Pass by reference in function calls and assign by reference in arrays.
|
Chris@17
|
1911 if ($this->tokens[$tokenBefore]['code'] === T_OPEN_PARENTHESIS
|
Chris@17
|
1912 || $this->tokens[$tokenBefore]['code'] === T_COMMA
|
Chris@17
|
1913 || $this->tokens[$tokenBefore]['code'] === T_OPEN_SHORT_ARRAY
|
Chris@17
|
1914 ) {
|
Chris@17
|
1915 if ($this->tokens[$tokenAfter]['code'] === T_VARIABLE) {
|
Chris@17
|
1916 return true;
|
Chris@17
|
1917 } else {
|
Chris@17
|
1918 $skip = Util\Tokens::$emptyTokens;
|
Chris@17
|
1919 $skip[] = T_NS_SEPARATOR;
|
Chris@17
|
1920 $skip[] = T_SELF;
|
Chris@17
|
1921 $skip[] = T_PARENT;
|
Chris@17
|
1922 $skip[] = T_STATIC;
|
Chris@17
|
1923 $skip[] = T_STRING;
|
Chris@17
|
1924 $skip[] = T_NAMESPACE;
|
Chris@17
|
1925 $skip[] = T_DOUBLE_COLON;
|
Chris@17
|
1926
|
Chris@17
|
1927 $nextSignificantAfter = $this->findNext(
|
Chris@17
|
1928 $skip,
|
Chris@17
|
1929 ($stackPtr + 1),
|
Chris@17
|
1930 null,
|
Chris@17
|
1931 true
|
Chris@17
|
1932 );
|
Chris@17
|
1933 if ($this->tokens[$nextSignificantAfter]['code'] === T_VARIABLE) {
|
Chris@17
|
1934 return true;
|
Chris@17
|
1935 }
|
Chris@17
|
1936 }//end if
|
Chris@17
|
1937 }//end if
|
Chris@17
|
1938
|
Chris@17
|
1939 return false;
|
Chris@17
|
1940
|
Chris@17
|
1941 }//end isReference()
|
Chris@17
|
1942
|
Chris@17
|
1943
|
Chris@17
|
1944 /**
|
Chris@17
|
1945 * Returns the content of the tokens from the specified start position in
|
Chris@17
|
1946 * the token stack for the specified length.
|
Chris@17
|
1947 *
|
Chris@17
|
1948 * @param int $start The position to start from in the token stack.
|
Chris@17
|
1949 * @param int $length The length of tokens to traverse from the start pos.
|
Chris@17
|
1950 * @param bool $origContent Whether the original content or the tab replaced
|
Chris@17
|
1951 * content should be used.
|
Chris@17
|
1952 *
|
Chris@17
|
1953 * @return string The token contents.
|
Chris@17
|
1954 */
|
Chris@17
|
1955 public function getTokensAsString($start, $length, $origContent=false)
|
Chris@17
|
1956 {
|
Chris@17
|
1957 if (is_int($start) === false || isset($this->tokens[$start]) === false) {
|
Chris@17
|
1958 throw new RuntimeException('The $start position for getTokensAsString() must exist in the token stack');
|
Chris@17
|
1959 }
|
Chris@17
|
1960
|
Chris@17
|
1961 if (is_int($length) === false || $length <= 0) {
|
Chris@17
|
1962 return '';
|
Chris@17
|
1963 }
|
Chris@17
|
1964
|
Chris@17
|
1965 $str = '';
|
Chris@17
|
1966 $end = ($start + $length);
|
Chris@17
|
1967 if ($end > $this->numTokens) {
|
Chris@17
|
1968 $end = $this->numTokens;
|
Chris@17
|
1969 }
|
Chris@17
|
1970
|
Chris@17
|
1971 for ($i = $start; $i < $end; $i++) {
|
Chris@17
|
1972 // If tabs are being converted to spaces by the tokeniser, the
|
Chris@17
|
1973 // original content should be used instead of the converted content.
|
Chris@17
|
1974 if ($origContent === true && isset($this->tokens[$i]['orig_content']) === true) {
|
Chris@17
|
1975 $str .= $this->tokens[$i]['orig_content'];
|
Chris@17
|
1976 } else {
|
Chris@17
|
1977 $str .= $this->tokens[$i]['content'];
|
Chris@17
|
1978 }
|
Chris@17
|
1979 }
|
Chris@17
|
1980
|
Chris@17
|
1981 return $str;
|
Chris@17
|
1982
|
Chris@17
|
1983 }//end getTokensAsString()
|
Chris@17
|
1984
|
Chris@17
|
1985
|
Chris@17
|
1986 /**
|
Chris@17
|
1987 * Returns the position of the previous specified token(s).
|
Chris@17
|
1988 *
|
Chris@17
|
1989 * If a value is specified, the previous token of the specified type(s)
|
Chris@17
|
1990 * containing the specified value will be returned.
|
Chris@17
|
1991 *
|
Chris@17
|
1992 * Returns false if no token can be found.
|
Chris@17
|
1993 *
|
Chris@17
|
1994 * @param int|array $types The type(s) of tokens to search for.
|
Chris@17
|
1995 * @param int $start The position to start searching from in the
|
Chris@17
|
1996 * token stack.
|
Chris@17
|
1997 * @param int $end The end position to fail if no token is found.
|
Chris@17
|
1998 * if not specified or null, end will default to
|
Chris@17
|
1999 * the start of the token stack.
|
Chris@17
|
2000 * @param bool $exclude If true, find the previous token that is NOT of
|
Chris@17
|
2001 * the types specified in $types.
|
Chris@17
|
2002 * @param string $value The value that the token(s) must be equal to.
|
Chris@17
|
2003 * If value is omitted, tokens with any value will
|
Chris@17
|
2004 * be returned.
|
Chris@17
|
2005 * @param bool $local If true, tokens outside the current statement
|
Chris@17
|
2006 * will not be checked. IE. checking will stop
|
Chris@17
|
2007 * at the previous semi-colon found.
|
Chris@17
|
2008 *
|
Chris@17
|
2009 * @return int|bool
|
Chris@17
|
2010 * @see findNext()
|
Chris@17
|
2011 */
|
Chris@17
|
2012 public function findPrevious(
|
Chris@17
|
2013 $types,
|
Chris@17
|
2014 $start,
|
Chris@17
|
2015 $end=null,
|
Chris@17
|
2016 $exclude=false,
|
Chris@17
|
2017 $value=null,
|
Chris@17
|
2018 $local=false
|
Chris@17
|
2019 ) {
|
Chris@17
|
2020 $types = (array) $types;
|
Chris@17
|
2021
|
Chris@17
|
2022 if ($end === null) {
|
Chris@17
|
2023 $end = 0;
|
Chris@17
|
2024 }
|
Chris@17
|
2025
|
Chris@17
|
2026 for ($i = $start; $i >= $end; $i--) {
|
Chris@17
|
2027 $found = (bool) $exclude;
|
Chris@17
|
2028 foreach ($types as $type) {
|
Chris@17
|
2029 if ($this->tokens[$i]['code'] === $type) {
|
Chris@17
|
2030 $found = !$exclude;
|
Chris@17
|
2031 break;
|
Chris@17
|
2032 }
|
Chris@17
|
2033 }
|
Chris@17
|
2034
|
Chris@17
|
2035 if ($found === true) {
|
Chris@17
|
2036 if ($value === null) {
|
Chris@17
|
2037 return $i;
|
Chris@17
|
2038 } else if ($this->tokens[$i]['content'] === $value) {
|
Chris@17
|
2039 return $i;
|
Chris@17
|
2040 }
|
Chris@17
|
2041 }
|
Chris@17
|
2042
|
Chris@17
|
2043 if ($local === true) {
|
Chris@17
|
2044 if (isset($this->tokens[$i]['scope_opener']) === true
|
Chris@17
|
2045 && $i === $this->tokens[$i]['scope_closer']
|
Chris@17
|
2046 ) {
|
Chris@17
|
2047 $i = $this->tokens[$i]['scope_opener'];
|
Chris@17
|
2048 } else if (isset($this->tokens[$i]['bracket_opener']) === true
|
Chris@17
|
2049 && $i === $this->tokens[$i]['bracket_closer']
|
Chris@17
|
2050 ) {
|
Chris@17
|
2051 $i = $this->tokens[$i]['bracket_opener'];
|
Chris@17
|
2052 } else if (isset($this->tokens[$i]['parenthesis_opener']) === true
|
Chris@17
|
2053 && $i === $this->tokens[$i]['parenthesis_closer']
|
Chris@17
|
2054 ) {
|
Chris@17
|
2055 $i = $this->tokens[$i]['parenthesis_opener'];
|
Chris@17
|
2056 } else if ($this->tokens[$i]['code'] === T_SEMICOLON) {
|
Chris@17
|
2057 break;
|
Chris@17
|
2058 }
|
Chris@17
|
2059 }
|
Chris@17
|
2060 }//end for
|
Chris@17
|
2061
|
Chris@17
|
2062 return false;
|
Chris@17
|
2063
|
Chris@17
|
2064 }//end findPrevious()
|
Chris@17
|
2065
|
Chris@17
|
2066
|
Chris@17
|
2067 /**
|
Chris@17
|
2068 * Returns the position of the next specified token(s).
|
Chris@17
|
2069 *
|
Chris@17
|
2070 * If a value is specified, the next token of the specified type(s)
|
Chris@17
|
2071 * containing the specified value will be returned.
|
Chris@17
|
2072 *
|
Chris@17
|
2073 * Returns false if no token can be found.
|
Chris@17
|
2074 *
|
Chris@17
|
2075 * @param int|array $types The type(s) of tokens to search for.
|
Chris@17
|
2076 * @param int $start The position to start searching from in the
|
Chris@17
|
2077 * token stack.
|
Chris@17
|
2078 * @param int $end The end position to fail if no token is found.
|
Chris@17
|
2079 * if not specified or null, end will default to
|
Chris@17
|
2080 * the end of the token stack.
|
Chris@17
|
2081 * @param bool $exclude If true, find the next token that is NOT of
|
Chris@17
|
2082 * a type specified in $types.
|
Chris@17
|
2083 * @param string $value The value that the token(s) must be equal to.
|
Chris@17
|
2084 * If value is omitted, tokens with any value will
|
Chris@17
|
2085 * be returned.
|
Chris@17
|
2086 * @param bool $local If true, tokens outside the current statement
|
Chris@17
|
2087 * will not be checked. i.e., checking will stop
|
Chris@17
|
2088 * at the next semi-colon found.
|
Chris@17
|
2089 *
|
Chris@17
|
2090 * @return int|bool
|
Chris@17
|
2091 * @see findPrevious()
|
Chris@17
|
2092 */
|
Chris@17
|
2093 public function findNext(
|
Chris@17
|
2094 $types,
|
Chris@17
|
2095 $start,
|
Chris@17
|
2096 $end=null,
|
Chris@17
|
2097 $exclude=false,
|
Chris@17
|
2098 $value=null,
|
Chris@17
|
2099 $local=false
|
Chris@17
|
2100 ) {
|
Chris@17
|
2101 $types = (array) $types;
|
Chris@17
|
2102
|
Chris@17
|
2103 if ($end === null || $end > $this->numTokens) {
|
Chris@17
|
2104 $end = $this->numTokens;
|
Chris@17
|
2105 }
|
Chris@17
|
2106
|
Chris@17
|
2107 for ($i = $start; $i < $end; $i++) {
|
Chris@17
|
2108 $found = (bool) $exclude;
|
Chris@17
|
2109 foreach ($types as $type) {
|
Chris@17
|
2110 if ($this->tokens[$i]['code'] === $type) {
|
Chris@17
|
2111 $found = !$exclude;
|
Chris@17
|
2112 break;
|
Chris@17
|
2113 }
|
Chris@17
|
2114 }
|
Chris@17
|
2115
|
Chris@17
|
2116 if ($found === true) {
|
Chris@17
|
2117 if ($value === null) {
|
Chris@17
|
2118 return $i;
|
Chris@17
|
2119 } else if ($this->tokens[$i]['content'] === $value) {
|
Chris@17
|
2120 return $i;
|
Chris@17
|
2121 }
|
Chris@17
|
2122 }
|
Chris@17
|
2123
|
Chris@17
|
2124 if ($local === true && $this->tokens[$i]['code'] === T_SEMICOLON) {
|
Chris@17
|
2125 break;
|
Chris@17
|
2126 }
|
Chris@17
|
2127 }//end for
|
Chris@17
|
2128
|
Chris@17
|
2129 return false;
|
Chris@17
|
2130
|
Chris@17
|
2131 }//end findNext()
|
Chris@17
|
2132
|
Chris@17
|
2133
|
Chris@17
|
2134 /**
|
Chris@17
|
2135 * Returns the position of the first non-whitespace token in a statement.
|
Chris@17
|
2136 *
|
Chris@17
|
2137 * @param int $start The position to start searching from in the token stack.
|
Chris@17
|
2138 * @param int|array $ignore Token types that should not be considered stop points.
|
Chris@17
|
2139 *
|
Chris@17
|
2140 * @return int
|
Chris@17
|
2141 */
|
Chris@17
|
2142 public function findStartOfStatement($start, $ignore=null)
|
Chris@17
|
2143 {
|
Chris@17
|
2144 $endTokens = Util\Tokens::$blockOpeners;
|
Chris@17
|
2145
|
Chris@17
|
2146 $endTokens[T_COLON] = true;
|
Chris@17
|
2147 $endTokens[T_COMMA] = true;
|
Chris@17
|
2148 $endTokens[T_DOUBLE_ARROW] = true;
|
Chris@17
|
2149 $endTokens[T_SEMICOLON] = true;
|
Chris@17
|
2150 $endTokens[T_OPEN_TAG] = true;
|
Chris@17
|
2151 $endTokens[T_CLOSE_TAG] = true;
|
Chris@17
|
2152 $endTokens[T_OPEN_SHORT_ARRAY] = true;
|
Chris@17
|
2153
|
Chris@17
|
2154 if ($ignore !== null) {
|
Chris@17
|
2155 $ignore = (array) $ignore;
|
Chris@17
|
2156 foreach ($ignore as $code) {
|
Chris@17
|
2157 if (isset($endTokens[$code]) === true) {
|
Chris@17
|
2158 unset($endTokens[$code]);
|
Chris@17
|
2159 }
|
Chris@17
|
2160 }
|
Chris@17
|
2161 }
|
Chris@17
|
2162
|
Chris@17
|
2163 $lastNotEmpty = $start;
|
Chris@17
|
2164
|
Chris@17
|
2165 for ($i = $start; $i >= 0; $i--) {
|
Chris@17
|
2166 if (isset($endTokens[$this->tokens[$i]['code']]) === true) {
|
Chris@17
|
2167 // Found the end of the previous statement.
|
Chris@17
|
2168 return $lastNotEmpty;
|
Chris@17
|
2169 }
|
Chris@17
|
2170
|
Chris@17
|
2171 if (isset($this->tokens[$i]['scope_opener']) === true
|
Chris@17
|
2172 && $i === $this->tokens[$i]['scope_closer']
|
Chris@17
|
2173 ) {
|
Chris@17
|
2174 // Found the end of the previous scope block.
|
Chris@17
|
2175 return $lastNotEmpty;
|
Chris@17
|
2176 }
|
Chris@17
|
2177
|
Chris@17
|
2178 // Skip nested statements.
|
Chris@17
|
2179 if (isset($this->tokens[$i]['bracket_opener']) === true
|
Chris@17
|
2180 && $i === $this->tokens[$i]['bracket_closer']
|
Chris@17
|
2181 ) {
|
Chris@17
|
2182 $i = $this->tokens[$i]['bracket_opener'];
|
Chris@17
|
2183 } else if (isset($this->tokens[$i]['parenthesis_opener']) === true
|
Chris@17
|
2184 && $i === $this->tokens[$i]['parenthesis_closer']
|
Chris@17
|
2185 ) {
|
Chris@17
|
2186 $i = $this->tokens[$i]['parenthesis_opener'];
|
Chris@17
|
2187 }
|
Chris@17
|
2188
|
Chris@17
|
2189 if (isset(Util\Tokens::$emptyTokens[$this->tokens[$i]['code']]) === false) {
|
Chris@17
|
2190 $lastNotEmpty = $i;
|
Chris@17
|
2191 }
|
Chris@17
|
2192 }//end for
|
Chris@17
|
2193
|
Chris@17
|
2194 return 0;
|
Chris@17
|
2195
|
Chris@17
|
2196 }//end findStartOfStatement()
|
Chris@17
|
2197
|
Chris@17
|
2198
|
Chris@17
|
2199 /**
|
Chris@17
|
2200 * Returns the position of the last non-whitespace token in a statement.
|
Chris@17
|
2201 *
|
Chris@17
|
2202 * @param int $start The position to start searching from in the token stack.
|
Chris@17
|
2203 * @param int|array $ignore Token types that should not be considered stop points.
|
Chris@17
|
2204 *
|
Chris@17
|
2205 * @return int
|
Chris@17
|
2206 */
|
Chris@17
|
2207 public function findEndOfStatement($start, $ignore=null)
|
Chris@17
|
2208 {
|
Chris@17
|
2209 $endTokens = [
|
Chris@17
|
2210 T_COLON => true,
|
Chris@17
|
2211 T_COMMA => true,
|
Chris@17
|
2212 T_DOUBLE_ARROW => true,
|
Chris@17
|
2213 T_SEMICOLON => true,
|
Chris@17
|
2214 T_CLOSE_PARENTHESIS => true,
|
Chris@17
|
2215 T_CLOSE_SQUARE_BRACKET => true,
|
Chris@17
|
2216 T_CLOSE_CURLY_BRACKET => true,
|
Chris@17
|
2217 T_CLOSE_SHORT_ARRAY => true,
|
Chris@17
|
2218 T_OPEN_TAG => true,
|
Chris@17
|
2219 T_CLOSE_TAG => true,
|
Chris@17
|
2220 ];
|
Chris@17
|
2221
|
Chris@17
|
2222 if ($ignore !== null) {
|
Chris@17
|
2223 $ignore = (array) $ignore;
|
Chris@17
|
2224 foreach ($ignore as $code) {
|
Chris@17
|
2225 if (isset($endTokens[$code]) === true) {
|
Chris@17
|
2226 unset($endTokens[$code]);
|
Chris@17
|
2227 }
|
Chris@17
|
2228 }
|
Chris@17
|
2229 }
|
Chris@17
|
2230
|
Chris@17
|
2231 $lastNotEmpty = $start;
|
Chris@17
|
2232
|
Chris@17
|
2233 for ($i = $start; $i < $this->numTokens; $i++) {
|
Chris@17
|
2234 if ($i !== $start && isset($endTokens[$this->tokens[$i]['code']]) === true) {
|
Chris@17
|
2235 // Found the end of the statement.
|
Chris@17
|
2236 if ($this->tokens[$i]['code'] === T_CLOSE_PARENTHESIS
|
Chris@17
|
2237 || $this->tokens[$i]['code'] === T_CLOSE_SQUARE_BRACKET
|
Chris@17
|
2238 || $this->tokens[$i]['code'] === T_CLOSE_CURLY_BRACKET
|
Chris@17
|
2239 || $this->tokens[$i]['code'] === T_CLOSE_SHORT_ARRAY
|
Chris@17
|
2240 || $this->tokens[$i]['code'] === T_OPEN_TAG
|
Chris@17
|
2241 || $this->tokens[$i]['code'] === T_CLOSE_TAG
|
Chris@17
|
2242 ) {
|
Chris@17
|
2243 return $lastNotEmpty;
|
Chris@17
|
2244 }
|
Chris@17
|
2245
|
Chris@17
|
2246 return $i;
|
Chris@17
|
2247 }
|
Chris@17
|
2248
|
Chris@17
|
2249 // Skip nested statements.
|
Chris@17
|
2250 if (isset($this->tokens[$i]['scope_closer']) === true
|
Chris@17
|
2251 && ($i === $this->tokens[$i]['scope_opener']
|
Chris@17
|
2252 || $i === $this->tokens[$i]['scope_condition'])
|
Chris@17
|
2253 ) {
|
Chris@17
|
2254 if ($i === $start && isset(Util\Tokens::$scopeOpeners[$this->tokens[$i]['code']]) === true) {
|
Chris@17
|
2255 return $this->tokens[$i]['scope_closer'];
|
Chris@17
|
2256 }
|
Chris@17
|
2257
|
Chris@17
|
2258 $i = $this->tokens[$i]['scope_closer'];
|
Chris@17
|
2259 } else if (isset($this->tokens[$i]['bracket_closer']) === true
|
Chris@17
|
2260 && $i === $this->tokens[$i]['bracket_opener']
|
Chris@17
|
2261 ) {
|
Chris@17
|
2262 $i = $this->tokens[$i]['bracket_closer'];
|
Chris@17
|
2263 } else if (isset($this->tokens[$i]['parenthesis_closer']) === true
|
Chris@17
|
2264 && $i === $this->tokens[$i]['parenthesis_opener']
|
Chris@17
|
2265 ) {
|
Chris@17
|
2266 $i = $this->tokens[$i]['parenthesis_closer'];
|
Chris@17
|
2267 }
|
Chris@17
|
2268
|
Chris@17
|
2269 if (isset(Util\Tokens::$emptyTokens[$this->tokens[$i]['code']]) === false) {
|
Chris@17
|
2270 $lastNotEmpty = $i;
|
Chris@17
|
2271 }
|
Chris@17
|
2272 }//end for
|
Chris@17
|
2273
|
Chris@17
|
2274 return ($this->numTokens - 1);
|
Chris@17
|
2275
|
Chris@17
|
2276 }//end findEndOfStatement()
|
Chris@17
|
2277
|
Chris@17
|
2278
|
Chris@17
|
2279 /**
|
Chris@17
|
2280 * Returns the position of the first token on a line, matching given type.
|
Chris@17
|
2281 *
|
Chris@17
|
2282 * Returns false if no token can be found.
|
Chris@17
|
2283 *
|
Chris@17
|
2284 * @param int|array $types The type(s) of tokens to search for.
|
Chris@17
|
2285 * @param int $start The position to start searching from in the
|
Chris@17
|
2286 * token stack. The first token matching on
|
Chris@17
|
2287 * this line before this token will be returned.
|
Chris@17
|
2288 * @param bool $exclude If true, find the token that is NOT of
|
Chris@17
|
2289 * the types specified in $types.
|
Chris@17
|
2290 * @param string $value The value that the token must be equal to.
|
Chris@17
|
2291 * If value is omitted, tokens with any value will
|
Chris@17
|
2292 * be returned.
|
Chris@17
|
2293 *
|
Chris@17
|
2294 * @return int | bool
|
Chris@17
|
2295 */
|
Chris@17
|
2296 public function findFirstOnLine($types, $start, $exclude=false, $value=null)
|
Chris@17
|
2297 {
|
Chris@17
|
2298 if (is_array($types) === false) {
|
Chris@17
|
2299 $types = [$types];
|
Chris@17
|
2300 }
|
Chris@17
|
2301
|
Chris@17
|
2302 $foundToken = false;
|
Chris@17
|
2303
|
Chris@17
|
2304 for ($i = $start; $i >= 0; $i--) {
|
Chris@17
|
2305 if ($this->tokens[$i]['line'] < $this->tokens[$start]['line']) {
|
Chris@17
|
2306 break;
|
Chris@17
|
2307 }
|
Chris@17
|
2308
|
Chris@17
|
2309 $found = $exclude;
|
Chris@17
|
2310 foreach ($types as $type) {
|
Chris@17
|
2311 if ($exclude === false) {
|
Chris@17
|
2312 if ($this->tokens[$i]['code'] === $type) {
|
Chris@17
|
2313 $found = true;
|
Chris@17
|
2314 break;
|
Chris@17
|
2315 }
|
Chris@17
|
2316 } else {
|
Chris@17
|
2317 if ($this->tokens[$i]['code'] === $type) {
|
Chris@17
|
2318 $found = false;
|
Chris@17
|
2319 break;
|
Chris@17
|
2320 }
|
Chris@17
|
2321 }
|
Chris@17
|
2322 }
|
Chris@17
|
2323
|
Chris@17
|
2324 if ($found === true) {
|
Chris@17
|
2325 if ($value === null) {
|
Chris@17
|
2326 $foundToken = $i;
|
Chris@17
|
2327 } else if ($this->tokens[$i]['content'] === $value) {
|
Chris@17
|
2328 $foundToken = $i;
|
Chris@17
|
2329 }
|
Chris@17
|
2330 }
|
Chris@17
|
2331 }//end for
|
Chris@17
|
2332
|
Chris@17
|
2333 return $foundToken;
|
Chris@17
|
2334
|
Chris@17
|
2335 }//end findFirstOnLine()
|
Chris@17
|
2336
|
Chris@17
|
2337
|
Chris@17
|
2338 /**
|
Chris@17
|
2339 * Determine if the passed token has a condition of one of the passed types.
|
Chris@17
|
2340 *
|
Chris@17
|
2341 * @param int $stackPtr The position of the token we are checking.
|
Chris@17
|
2342 * @param int|array $types The type(s) of tokens to search for.
|
Chris@17
|
2343 *
|
Chris@17
|
2344 * @return boolean
|
Chris@17
|
2345 */
|
Chris@17
|
2346 public function hasCondition($stackPtr, $types)
|
Chris@17
|
2347 {
|
Chris@17
|
2348 // Check for the existence of the token.
|
Chris@17
|
2349 if (isset($this->tokens[$stackPtr]) === false) {
|
Chris@17
|
2350 return false;
|
Chris@17
|
2351 }
|
Chris@17
|
2352
|
Chris@17
|
2353 // Make sure the token has conditions.
|
Chris@17
|
2354 if (isset($this->tokens[$stackPtr]['conditions']) === false) {
|
Chris@17
|
2355 return false;
|
Chris@17
|
2356 }
|
Chris@17
|
2357
|
Chris@17
|
2358 $types = (array) $types;
|
Chris@17
|
2359 $conditions = $this->tokens[$stackPtr]['conditions'];
|
Chris@17
|
2360
|
Chris@17
|
2361 foreach ($types as $type) {
|
Chris@17
|
2362 if (in_array($type, $conditions) === true) {
|
Chris@17
|
2363 // We found a token with the required type.
|
Chris@17
|
2364 return true;
|
Chris@17
|
2365 }
|
Chris@17
|
2366 }
|
Chris@17
|
2367
|
Chris@17
|
2368 return false;
|
Chris@17
|
2369
|
Chris@17
|
2370 }//end hasCondition()
|
Chris@17
|
2371
|
Chris@17
|
2372
|
Chris@17
|
2373 /**
|
Chris@17
|
2374 * Return the position of the condition for the passed token.
|
Chris@17
|
2375 *
|
Chris@17
|
2376 * Returns FALSE if the token does not have the condition.
|
Chris@17
|
2377 *
|
Chris@17
|
2378 * @param int $stackPtr The position of the token we are checking.
|
Chris@17
|
2379 * @param int $type The type of token to search for.
|
Chris@17
|
2380 *
|
Chris@17
|
2381 * @return int
|
Chris@17
|
2382 */
|
Chris@17
|
2383 public function getCondition($stackPtr, $type)
|
Chris@17
|
2384 {
|
Chris@17
|
2385 // Check for the existence of the token.
|
Chris@17
|
2386 if (isset($this->tokens[$stackPtr]) === false) {
|
Chris@17
|
2387 return false;
|
Chris@17
|
2388 }
|
Chris@17
|
2389
|
Chris@17
|
2390 // Make sure the token has conditions.
|
Chris@17
|
2391 if (isset($this->tokens[$stackPtr]['conditions']) === false) {
|
Chris@17
|
2392 return false;
|
Chris@17
|
2393 }
|
Chris@17
|
2394
|
Chris@17
|
2395 $conditions = $this->tokens[$stackPtr]['conditions'];
|
Chris@17
|
2396 foreach ($conditions as $token => $condition) {
|
Chris@17
|
2397 if ($condition === $type) {
|
Chris@17
|
2398 return $token;
|
Chris@17
|
2399 }
|
Chris@17
|
2400 }
|
Chris@17
|
2401
|
Chris@17
|
2402 return false;
|
Chris@17
|
2403
|
Chris@17
|
2404 }//end getCondition()
|
Chris@17
|
2405
|
Chris@17
|
2406
|
Chris@17
|
2407 /**
|
Chris@17
|
2408 * Returns the name of the class that the specified class extends.
|
Chris@17
|
2409 * (works for classes, anonymous classes and interfaces)
|
Chris@17
|
2410 *
|
Chris@17
|
2411 * Returns FALSE on error or if there is no extended class name.
|
Chris@17
|
2412 *
|
Chris@17
|
2413 * @param int $stackPtr The stack position of the class.
|
Chris@17
|
2414 *
|
Chris@17
|
2415 * @return string|false
|
Chris@17
|
2416 */
|
Chris@17
|
2417 public function findExtendedClassName($stackPtr)
|
Chris@17
|
2418 {
|
Chris@17
|
2419 // Check for the existence of the token.
|
Chris@17
|
2420 if (isset($this->tokens[$stackPtr]) === false) {
|
Chris@17
|
2421 return false;
|
Chris@17
|
2422 }
|
Chris@17
|
2423
|
Chris@17
|
2424 if ($this->tokens[$stackPtr]['code'] !== T_CLASS
|
Chris@17
|
2425 && $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS
|
Chris@17
|
2426 && $this->tokens[$stackPtr]['code'] !== T_INTERFACE
|
Chris@17
|
2427 ) {
|
Chris@17
|
2428 return false;
|
Chris@17
|
2429 }
|
Chris@17
|
2430
|
Chris@17
|
2431 if (isset($this->tokens[$stackPtr]['scope_opener']) === false) {
|
Chris@17
|
2432 return false;
|
Chris@17
|
2433 }
|
Chris@17
|
2434
|
Chris@17
|
2435 $classOpenerIndex = $this->tokens[$stackPtr]['scope_opener'];
|
Chris@17
|
2436 $extendsIndex = $this->findNext(T_EXTENDS, $stackPtr, $classOpenerIndex);
|
Chris@17
|
2437 if (false === $extendsIndex) {
|
Chris@17
|
2438 return false;
|
Chris@17
|
2439 }
|
Chris@17
|
2440
|
Chris@17
|
2441 $find = [
|
Chris@17
|
2442 T_NS_SEPARATOR,
|
Chris@17
|
2443 T_STRING,
|
Chris@17
|
2444 T_WHITESPACE,
|
Chris@17
|
2445 ];
|
Chris@17
|
2446
|
Chris@17
|
2447 $end = $this->findNext($find, ($extendsIndex + 1), ($classOpenerIndex + 1), true);
|
Chris@17
|
2448 $name = $this->getTokensAsString(($extendsIndex + 1), ($end - $extendsIndex - 1));
|
Chris@17
|
2449 $name = trim($name);
|
Chris@17
|
2450
|
Chris@17
|
2451 if ($name === '') {
|
Chris@17
|
2452 return false;
|
Chris@17
|
2453 }
|
Chris@17
|
2454
|
Chris@17
|
2455 return $name;
|
Chris@17
|
2456
|
Chris@17
|
2457 }//end findExtendedClassName()
|
Chris@17
|
2458
|
Chris@17
|
2459
|
Chris@17
|
2460 /**
|
Chris@17
|
2461 * Returns the names of the interfaces that the specified class implements.
|
Chris@17
|
2462 *
|
Chris@17
|
2463 * Returns FALSE on error or if there are no implemented interface names.
|
Chris@17
|
2464 *
|
Chris@17
|
2465 * @param int $stackPtr The stack position of the class.
|
Chris@17
|
2466 *
|
Chris@17
|
2467 * @return array|false
|
Chris@17
|
2468 */
|
Chris@17
|
2469 public function findImplementedInterfaceNames($stackPtr)
|
Chris@17
|
2470 {
|
Chris@17
|
2471 // Check for the existence of the token.
|
Chris@17
|
2472 if (isset($this->tokens[$stackPtr]) === false) {
|
Chris@17
|
2473 return false;
|
Chris@17
|
2474 }
|
Chris@17
|
2475
|
Chris@17
|
2476 if ($this->tokens[$stackPtr]['code'] !== T_CLASS
|
Chris@17
|
2477 && $this->tokens[$stackPtr]['code'] !== T_ANON_CLASS
|
Chris@17
|
2478 ) {
|
Chris@17
|
2479 return false;
|
Chris@17
|
2480 }
|
Chris@17
|
2481
|
Chris@17
|
2482 if (isset($this->tokens[$stackPtr]['scope_closer']) === false) {
|
Chris@17
|
2483 return false;
|
Chris@17
|
2484 }
|
Chris@17
|
2485
|
Chris@17
|
2486 $classOpenerIndex = $this->tokens[$stackPtr]['scope_opener'];
|
Chris@17
|
2487 $implementsIndex = $this->findNext(T_IMPLEMENTS, $stackPtr, $classOpenerIndex);
|
Chris@17
|
2488 if ($implementsIndex === false) {
|
Chris@17
|
2489 return false;
|
Chris@17
|
2490 }
|
Chris@17
|
2491
|
Chris@17
|
2492 $find = [
|
Chris@17
|
2493 T_NS_SEPARATOR,
|
Chris@17
|
2494 T_STRING,
|
Chris@17
|
2495 T_WHITESPACE,
|
Chris@17
|
2496 T_COMMA,
|
Chris@17
|
2497 ];
|
Chris@17
|
2498
|
Chris@17
|
2499 $end = $this->findNext($find, ($implementsIndex + 1), ($classOpenerIndex + 1), true);
|
Chris@17
|
2500 $name = $this->getTokensAsString(($implementsIndex + 1), ($end - $implementsIndex - 1));
|
Chris@17
|
2501 $name = trim($name);
|
Chris@17
|
2502
|
Chris@17
|
2503 if ($name === '') {
|
Chris@17
|
2504 return false;
|
Chris@17
|
2505 } else {
|
Chris@17
|
2506 $names = explode(',', $name);
|
Chris@17
|
2507 $names = array_map('trim', $names);
|
Chris@17
|
2508 return $names;
|
Chris@17
|
2509 }
|
Chris@17
|
2510
|
Chris@17
|
2511 }//end findImplementedInterfaceNames()
|
Chris@17
|
2512
|
Chris@17
|
2513
|
Chris@17
|
2514 }//end class
|