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