Mercurial > hg > isophonics-drupal-site
comparison vendor/squizlabs/php_codesniffer/CodeSniffer/Standards/AbstractPatternSniff.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children | 7a779792577d |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 /** | |
3 * Processes pattern strings and checks that the code conforms to the pattern. | |
4 * | |
5 * PHP version 5 | |
6 * | |
7 * @category PHP | |
8 * @package PHP_CodeSniffer | |
9 * @author Greg Sherwood <gsherwood@squiz.net> | |
10 * @author Marc McIntyre <mmcintyre@squiz.net> | |
11 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) | |
12 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence | |
13 * @link http://pear.php.net/package/PHP_CodeSniffer | |
14 */ | |
15 | |
16 if (class_exists('PHP_CodeSniffer_Standards_IncorrectPatternException', true) === false) { | |
17 $error = 'Class PHP_CodeSniffer_Standards_IncorrectPatternException not found'; | |
18 throw new PHP_CodeSniffer_Exception($error); | |
19 } | |
20 | |
21 /** | |
22 * Processes pattern strings and checks that the code conforms to the pattern. | |
23 * | |
24 * This test essentially checks that code is correctly formatted with whitespace. | |
25 * | |
26 * @category PHP | |
27 * @package PHP_CodeSniffer | |
28 * @author Greg Sherwood <gsherwood@squiz.net> | |
29 * @author Marc McIntyre <mmcintyre@squiz.net> | |
30 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) | |
31 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence | |
32 * @version Release: @package_version@ | |
33 * @link http://pear.php.net/package/PHP_CodeSniffer | |
34 */ | |
35 abstract class PHP_CodeSniffer_Standards_AbstractPatternSniff implements PHP_CodeSniffer_Sniff | |
36 { | |
37 | |
38 /** | |
39 * If true, comments will be ignored if they are found in the code. | |
40 * | |
41 * @var boolean | |
42 */ | |
43 public $ignoreComments = false; | |
44 | |
45 /** | |
46 * The current file being checked. | |
47 * | |
48 * @var string | |
49 */ | |
50 protected $currFile = ''; | |
51 | |
52 /** | |
53 * The parsed patterns array. | |
54 * | |
55 * @var array | |
56 */ | |
57 private $_parsedPatterns = array(); | |
58 | |
59 /** | |
60 * Tokens that this sniff wishes to process outside of the patterns. | |
61 * | |
62 * @var array(int) | |
63 * @see registerSupplementary() | |
64 * @see processSupplementary() | |
65 */ | |
66 private $_supplementaryTokens = array(); | |
67 | |
68 /** | |
69 * Positions in the stack where errors have occurred. | |
70 * | |
71 * @var array() | |
72 */ | |
73 private $_errorPos = array(); | |
74 | |
75 | |
76 /** | |
77 * Constructs a PHP_CodeSniffer_Standards_AbstractPatternSniff. | |
78 * | |
79 * @param boolean $ignoreComments If true, comments will be ignored. | |
80 */ | |
81 public function __construct($ignoreComments=null) | |
82 { | |
83 // This is here for backwards compatibility. | |
84 if ($ignoreComments !== null) { | |
85 $this->ignoreComments = $ignoreComments; | |
86 } | |
87 | |
88 $this->_supplementaryTokens = $this->registerSupplementary(); | |
89 | |
90 }//end __construct() | |
91 | |
92 | |
93 /** | |
94 * Registers the tokens to listen to. | |
95 * | |
96 * Classes extending <i>AbstractPatternTest</i> should implement the | |
97 * <i>getPatterns()</i> method to register the patterns they wish to test. | |
98 * | |
99 * @return int[] | |
100 * @see process() | |
101 */ | |
102 public final function register() | |
103 { | |
104 $listenTypes = array(); | |
105 $patterns = $this->getPatterns(); | |
106 | |
107 foreach ($patterns as $pattern) { | |
108 $parsedPattern = $this->_parse($pattern); | |
109 | |
110 // Find a token position in the pattern that we can use | |
111 // for a listener token. | |
112 $pos = $this->_getListenerTokenPos($parsedPattern); | |
113 $tokenType = $parsedPattern[$pos]['token']; | |
114 $listenTypes[] = $tokenType; | |
115 | |
116 $patternArray = array( | |
117 'listen_pos' => $pos, | |
118 'pattern' => $parsedPattern, | |
119 'pattern_code' => $pattern, | |
120 ); | |
121 | |
122 if (isset($this->_parsedPatterns[$tokenType]) === false) { | |
123 $this->_parsedPatterns[$tokenType] = array(); | |
124 } | |
125 | |
126 $this->_parsedPatterns[$tokenType][] = $patternArray; | |
127 }//end foreach | |
128 | |
129 return array_unique(array_merge($listenTypes, $this->_supplementaryTokens)); | |
130 | |
131 }//end register() | |
132 | |
133 | |
134 /** | |
135 * Returns the token types that the specified pattern is checking for. | |
136 * | |
137 * Returned array is in the format: | |
138 * <code> | |
139 * array( | |
140 * T_WHITESPACE => 0, // 0 is the position where the T_WHITESPACE token | |
141 * // should occur in the pattern. | |
142 * ); | |
143 * </code> | |
144 * | |
145 * @param array $pattern The parsed pattern to find the acquire the token | |
146 * types from. | |
147 * | |
148 * @return array<int, int> | |
149 */ | |
150 private function _getPatternTokenTypes($pattern) | |
151 { | |
152 $tokenTypes = array(); | |
153 foreach ($pattern as $pos => $patternInfo) { | |
154 if ($patternInfo['type'] === 'token') { | |
155 if (isset($tokenTypes[$patternInfo['token']]) === false) { | |
156 $tokenTypes[$patternInfo['token']] = $pos; | |
157 } | |
158 } | |
159 } | |
160 | |
161 return $tokenTypes; | |
162 | |
163 }//end _getPatternTokenTypes() | |
164 | |
165 | |
166 /** | |
167 * Returns the position in the pattern that this test should register as | |
168 * a listener for the pattern. | |
169 * | |
170 * @param array $pattern The pattern to acquire the listener for. | |
171 * | |
172 * @return int The position in the pattern that this test should register | |
173 * as the listener. | |
174 * @throws PHP_CodeSniffer_Exception If we could not determine a token | |
175 * to listen for. | |
176 */ | |
177 private function _getListenerTokenPos($pattern) | |
178 { | |
179 $tokenTypes = $this->_getPatternTokenTypes($pattern); | |
180 $tokenCodes = array_keys($tokenTypes); | |
181 $token = PHP_CodeSniffer_Tokens::getHighestWeightedToken($tokenCodes); | |
182 | |
183 // If we could not get a token. | |
184 if ($token === false) { | |
185 $error = 'Could not determine a token to listen for'; | |
186 throw new PHP_CodeSniffer_Exception($error); | |
187 } | |
188 | |
189 return $tokenTypes[$token]; | |
190 | |
191 }//end _getListenerTokenPos() | |
192 | |
193 | |
194 /** | |
195 * Processes the test. | |
196 * | |
197 * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where the | |
198 * token occurred. | |
199 * @param int $stackPtr The position in the tokens stack | |
200 * where the listening token type was | |
201 * found. | |
202 * | |
203 * @return void | |
204 * @see register() | |
205 */ | |
206 public final function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr) | |
207 { | |
208 $file = $phpcsFile->getFilename(); | |
209 if ($this->currFile !== $file) { | |
210 // We have changed files, so clean up. | |
211 $this->_errorPos = array(); | |
212 $this->currFile = $file; | |
213 } | |
214 | |
215 $tokens = $phpcsFile->getTokens(); | |
216 | |
217 if (in_array($tokens[$stackPtr]['code'], $this->_supplementaryTokens) === true) { | |
218 $this->processSupplementary($phpcsFile, $stackPtr); | |
219 } | |
220 | |
221 $type = $tokens[$stackPtr]['code']; | |
222 | |
223 // If the type is not set, then it must have been a token registered | |
224 // with registerSupplementary(). | |
225 if (isset($this->_parsedPatterns[$type]) === false) { | |
226 return; | |
227 } | |
228 | |
229 $allErrors = array(); | |
230 | |
231 // Loop over each pattern that is listening to the current token type | |
232 // that we are processing. | |
233 foreach ($this->_parsedPatterns[$type] as $patternInfo) { | |
234 // If processPattern returns false, then the pattern that we are | |
235 // checking the code with must not be designed to check that code. | |
236 $errors = $this->processPattern($patternInfo, $phpcsFile, $stackPtr); | |
237 if ($errors === false) { | |
238 // The pattern didn't match. | |
239 continue; | |
240 } else if (empty($errors) === true) { | |
241 // The pattern matched, but there were no errors. | |
242 break; | |
243 } | |
244 | |
245 foreach ($errors as $stackPtr => $error) { | |
246 if (isset($this->_errorPos[$stackPtr]) === false) { | |
247 $this->_errorPos[$stackPtr] = true; | |
248 $allErrors[$stackPtr] = $error; | |
249 } | |
250 } | |
251 } | |
252 | |
253 foreach ($allErrors as $stackPtr => $error) { | |
254 $phpcsFile->addError($error, $stackPtr); | |
255 } | |
256 | |
257 }//end process() | |
258 | |
259 | |
260 /** | |
261 * Processes the pattern and verifies the code at $stackPtr. | |
262 * | |
263 * @param array $patternInfo Information about the pattern used | |
264 * for checking, which includes are | |
265 * parsed token representation of the | |
266 * pattern. | |
267 * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where the | |
268 * token occurred. | |
269 * @param int $stackPtr The position in the tokens stack where | |
270 * the listening token type was found. | |
271 * | |
272 * @return array | |
273 */ | |
274 protected function processPattern( | |
275 $patternInfo, | |
276 PHP_CodeSniffer_File $phpcsFile, | |
277 $stackPtr | |
278 ) { | |
279 $tokens = $phpcsFile->getTokens(); | |
280 $pattern = $patternInfo['pattern']; | |
281 $patternCode = $patternInfo['pattern_code']; | |
282 $errors = array(); | |
283 $found = ''; | |
284 | |
285 $ignoreTokens = array(T_WHITESPACE); | |
286 if ($this->ignoreComments === true) { | |
287 $ignoreTokens | |
288 = array_merge($ignoreTokens, PHP_CodeSniffer_Tokens::$commentTokens); | |
289 } | |
290 | |
291 $origStackPtr = $stackPtr; | |
292 $hasError = false; | |
293 | |
294 if ($patternInfo['listen_pos'] > 0) { | |
295 $stackPtr--; | |
296 | |
297 for ($i = ($patternInfo['listen_pos'] - 1); $i >= 0; $i--) { | |
298 if ($pattern[$i]['type'] === 'token') { | |
299 if ($pattern[$i]['token'] === T_WHITESPACE) { | |
300 if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { | |
301 $found = $tokens[$stackPtr]['content'].$found; | |
302 } | |
303 | |
304 // Only check the size of the whitespace if this is not | |
305 // the first token. We don't care about the size of | |
306 // leading whitespace, just that there is some. | |
307 if ($i !== 0) { | |
308 if ($tokens[$stackPtr]['content'] !== $pattern[$i]['value']) { | |
309 $hasError = true; | |
310 } | |
311 } | |
312 } else { | |
313 // Check to see if this important token is the same as the | |
314 // previous important token in the pattern. If it is not, | |
315 // then the pattern cannot be for this piece of code. | |
316 $prev = $phpcsFile->findPrevious( | |
317 $ignoreTokens, | |
318 $stackPtr, | |
319 null, | |
320 true | |
321 ); | |
322 | |
323 if ($prev === false | |
324 || $tokens[$prev]['code'] !== $pattern[$i]['token'] | |
325 ) { | |
326 return false; | |
327 } | |
328 | |
329 // If we skipped past some whitespace tokens, then add them | |
330 // to the found string. | |
331 $tokenContent = $phpcsFile->getTokensAsString( | |
332 ($prev + 1), | |
333 ($stackPtr - $prev - 1) | |
334 ); | |
335 | |
336 $found = $tokens[$prev]['content'].$tokenContent.$found; | |
337 | |
338 if (isset($pattern[($i - 1)]) === true | |
339 && $pattern[($i - 1)]['type'] === 'skip' | |
340 ) { | |
341 $stackPtr = $prev; | |
342 } else { | |
343 $stackPtr = ($prev - 1); | |
344 } | |
345 }//end if | |
346 } else if ($pattern[$i]['type'] === 'skip') { | |
347 // Skip to next piece of relevant code. | |
348 if ($pattern[$i]['to'] === 'parenthesis_closer') { | |
349 $to = 'parenthesis_opener'; | |
350 } else { | |
351 $to = 'scope_opener'; | |
352 } | |
353 | |
354 // Find the previous opener. | |
355 $next = $phpcsFile->findPrevious( | |
356 $ignoreTokens, | |
357 $stackPtr, | |
358 null, | |
359 true | |
360 ); | |
361 | |
362 if ($next === false || isset($tokens[$next][$to]) === false) { | |
363 // If there was not opener, then we must be | |
364 // using the wrong pattern. | |
365 return false; | |
366 } | |
367 | |
368 if ($to === 'parenthesis_opener') { | |
369 $found = '{'.$found; | |
370 } else { | |
371 $found = '('.$found; | |
372 } | |
373 | |
374 $found = '...'.$found; | |
375 | |
376 // Skip to the opening token. | |
377 $stackPtr = ($tokens[$next][$to] - 1); | |
378 } else if ($pattern[$i]['type'] === 'string') { | |
379 $found = 'abc'; | |
380 } else if ($pattern[$i]['type'] === 'newline') { | |
381 if ($this->ignoreComments === true | |
382 && isset(PHP_CodeSniffer_Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true | |
383 ) { | |
384 $startComment = $phpcsFile->findPrevious( | |
385 PHP_CodeSniffer_Tokens::$commentTokens, | |
386 ($stackPtr - 1), | |
387 null, | |
388 true | |
389 ); | |
390 | |
391 if ($tokens[$startComment]['line'] !== $tokens[($startComment + 1)]['line']) { | |
392 $startComment++; | |
393 } | |
394 | |
395 $tokenContent = $phpcsFile->getTokensAsString( | |
396 $startComment, | |
397 ($stackPtr - $startComment + 1) | |
398 ); | |
399 | |
400 $found = $tokenContent.$found; | |
401 $stackPtr = ($startComment - 1); | |
402 } | |
403 | |
404 if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { | |
405 if ($tokens[$stackPtr]['content'] !== $phpcsFile->eolChar) { | |
406 $found = $tokens[$stackPtr]['content'].$found; | |
407 | |
408 // This may just be an indent that comes after a newline | |
409 // so check the token before to make sure. If it is a newline, we | |
410 // can ignore the error here. | |
411 if (($tokens[($stackPtr - 1)]['content'] !== $phpcsFile->eolChar) | |
412 && ($this->ignoreComments === true | |
413 && isset(PHP_CodeSniffer_Tokens::$commentTokens[$tokens[($stackPtr - 1)]['code']]) === false) | |
414 ) { | |
415 $hasError = true; | |
416 } else { | |
417 $stackPtr--; | |
418 } | |
419 } else { | |
420 $found = 'EOL'.$found; | |
421 } | |
422 } else { | |
423 $found = $tokens[$stackPtr]['content'].$found; | |
424 $hasError = true; | |
425 }//end if | |
426 | |
427 if ($hasError === false && $pattern[($i - 1)]['type'] !== 'newline') { | |
428 // Make sure they only have 1 newline. | |
429 $prev = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true); | |
430 if ($prev !== false && $tokens[$prev]['line'] !== $tokens[$stackPtr]['line']) { | |
431 $hasError = true; | |
432 } | |
433 } | |
434 }//end if | |
435 }//end for | |
436 }//end if | |
437 | |
438 $stackPtr = $origStackPtr; | |
439 $lastAddedStackPtr = null; | |
440 $patternLen = count($pattern); | |
441 | |
442 for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) { | |
443 if (isset($tokens[$stackPtr]) === false) { | |
444 break; | |
445 } | |
446 | |
447 if ($pattern[$i]['type'] === 'token') { | |
448 if ($pattern[$i]['token'] === T_WHITESPACE) { | |
449 if ($this->ignoreComments === true) { | |
450 // If we are ignoring comments, check to see if this current | |
451 // token is a comment. If so skip it. | |
452 if (isset(PHP_CodeSniffer_Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true) { | |
453 continue; | |
454 } | |
455 | |
456 // If the next token is a comment, the we need to skip the | |
457 // current token as we should allow a space before a | |
458 // comment for readability. | |
459 if (isset($tokens[($stackPtr + 1)]) === true | |
460 && isset(PHP_CodeSniffer_Tokens::$commentTokens[$tokens[($stackPtr + 1)]['code']]) === true | |
461 ) { | |
462 continue; | |
463 } | |
464 } | |
465 | |
466 $tokenContent = ''; | |
467 if ($tokens[$stackPtr]['code'] === T_WHITESPACE) { | |
468 if (isset($pattern[($i + 1)]) === false) { | |
469 // This is the last token in the pattern, so just compare | |
470 // the next token of content. | |
471 $tokenContent = $tokens[$stackPtr]['content']; | |
472 } else { | |
473 // Get all the whitespace to the next token. | |
474 $next = $phpcsFile->findNext( | |
475 PHP_CodeSniffer_Tokens::$emptyTokens, | |
476 $stackPtr, | |
477 null, | |
478 true | |
479 ); | |
480 | |
481 $tokenContent = $phpcsFile->getTokensAsString( | |
482 $stackPtr, | |
483 ($next - $stackPtr) | |
484 ); | |
485 | |
486 $lastAddedStackPtr = $stackPtr; | |
487 $stackPtr = $next; | |
488 }//end if | |
489 | |
490 if ($stackPtr !== $lastAddedStackPtr) { | |
491 $found .= $tokenContent; | |
492 } | |
493 } else { | |
494 if ($stackPtr !== $lastAddedStackPtr) { | |
495 $found .= $tokens[$stackPtr]['content']; | |
496 $lastAddedStackPtr = $stackPtr; | |
497 } | |
498 }//end if | |
499 | |
500 if (isset($pattern[($i + 1)]) === true | |
501 && $pattern[($i + 1)]['type'] === 'skip' | |
502 ) { | |
503 // The next token is a skip token, so we just need to make | |
504 // sure the whitespace we found has *at least* the | |
505 // whitespace required. | |
506 if (strpos($tokenContent, $pattern[$i]['value']) !== 0) { | |
507 $hasError = true; | |
508 } | |
509 } else { | |
510 if ($tokenContent !== $pattern[$i]['value']) { | |
511 $hasError = true; | |
512 } | |
513 } | |
514 } else { | |
515 // Check to see if this important token is the same as the | |
516 // next important token in the pattern. If it is not, then | |
517 // the pattern cannot be for this piece of code. | |
518 $next = $phpcsFile->findNext( | |
519 $ignoreTokens, | |
520 $stackPtr, | |
521 null, | |
522 true | |
523 ); | |
524 | |
525 if ($next === false | |
526 || $tokens[$next]['code'] !== $pattern[$i]['token'] | |
527 ) { | |
528 // The next important token did not match the pattern. | |
529 return false; | |
530 } | |
531 | |
532 if ($lastAddedStackPtr !== null) { | |
533 if (($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET | |
534 || $tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET) | |
535 && isset($tokens[$next]['scope_condition']) === true | |
536 && $tokens[$next]['scope_condition'] > $lastAddedStackPtr | |
537 ) { | |
538 // This is a brace, but the owner of it is after the current | |
539 // token, which means it does not belong to any token in | |
540 // our pattern. This means the pattern is not for us. | |
541 return false; | |
542 } | |
543 | |
544 if (($tokens[$next]['code'] === T_OPEN_PARENTHESIS | |
545 || $tokens[$next]['code'] === T_CLOSE_PARENTHESIS) | |
546 && isset($tokens[$next]['parenthesis_owner']) === true | |
547 && $tokens[$next]['parenthesis_owner'] > $lastAddedStackPtr | |
548 ) { | |
549 // This is a bracket, but the owner of it is after the current | |
550 // token, which means it does not belong to any token in | |
551 // our pattern. This means the pattern is not for us. | |
552 return false; | |
553 } | |
554 }//end if | |
555 | |
556 // If we skipped past some whitespace tokens, then add them | |
557 // to the found string. | |
558 if (($next - $stackPtr) > 0) { | |
559 $hasComment = false; | |
560 for ($j = $stackPtr; $j < $next; $j++) { | |
561 $found .= $tokens[$j]['content']; | |
562 if (isset(PHP_CodeSniffer_Tokens::$commentTokens[$tokens[$j]['code']]) === true) { | |
563 $hasComment = true; | |
564 } | |
565 } | |
566 | |
567 // If we are not ignoring comments, this additional | |
568 // whitespace or comment is not allowed. If we are | |
569 // ignoring comments, there needs to be at least one | |
570 // comment for this to be allowed. | |
571 if ($this->ignoreComments === false | |
572 || ($this->ignoreComments === true | |
573 && $hasComment === false) | |
574 ) { | |
575 $hasError = true; | |
576 } | |
577 | |
578 // Even when ignoring comments, we are not allowed to include | |
579 // newlines without the pattern specifying them, so | |
580 // everything should be on the same line. | |
581 if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) { | |
582 $hasError = true; | |
583 } | |
584 }//end if | |
585 | |
586 if ($next !== $lastAddedStackPtr) { | |
587 $found .= $tokens[$next]['content']; | |
588 $lastAddedStackPtr = $next; | |
589 } | |
590 | |
591 if (isset($pattern[($i + 1)]) === true | |
592 && $pattern[($i + 1)]['type'] === 'skip' | |
593 ) { | |
594 $stackPtr = $next; | |
595 } else { | |
596 $stackPtr = ($next + 1); | |
597 } | |
598 }//end if | |
599 } else if ($pattern[$i]['type'] === 'skip') { | |
600 if ($pattern[$i]['to'] === 'unknown') { | |
601 $next = $phpcsFile->findNext( | |
602 $pattern[($i + 1)]['token'], | |
603 $stackPtr | |
604 ); | |
605 | |
606 if ($next === false) { | |
607 // Couldn't find the next token, so we must | |
608 // be using the wrong pattern. | |
609 return false; | |
610 } | |
611 | |
612 $found .= '...'; | |
613 $stackPtr = $next; | |
614 } else { | |
615 // Find the previous opener. | |
616 $next = $phpcsFile->findPrevious( | |
617 PHP_CodeSniffer_Tokens::$blockOpeners, | |
618 $stackPtr | |
619 ); | |
620 | |
621 if ($next === false | |
622 || isset($tokens[$next][$pattern[$i]['to']]) === false | |
623 ) { | |
624 // If there was not opener, then we must | |
625 // be using the wrong pattern. | |
626 return false; | |
627 } | |
628 | |
629 $found .= '...'; | |
630 if ($pattern[$i]['to'] === 'parenthesis_closer') { | |
631 $found .= ')'; | |
632 } else { | |
633 $found .= '}'; | |
634 } | |
635 | |
636 // Skip to the closing token. | |
637 $stackPtr = ($tokens[$next][$pattern[$i]['to']] + 1); | |
638 }//end if | |
639 } else if ($pattern[$i]['type'] === 'string') { | |
640 if ($tokens[$stackPtr]['code'] !== T_STRING) { | |
641 $hasError = true; | |
642 } | |
643 | |
644 if ($stackPtr !== $lastAddedStackPtr) { | |
645 $found .= 'abc'; | |
646 $lastAddedStackPtr = $stackPtr; | |
647 } | |
648 | |
649 $stackPtr++; | |
650 } else if ($pattern[$i]['type'] === 'newline') { | |
651 // Find the next token that contains a newline character. | |
652 $newline = 0; | |
653 for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) { | |
654 if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) { | |
655 $newline = $j; | |
656 break; | |
657 } | |
658 } | |
659 | |
660 if ($newline === 0) { | |
661 // We didn't find a newline character in the rest of the file. | |
662 $next = ($phpcsFile->numTokens - 1); | |
663 $hasError = true; | |
664 } else { | |
665 if ($this->ignoreComments === false) { | |
666 // The newline character cannot be part of a comment. | |
667 if (isset(PHP_CodeSniffer_Tokens::$commentTokens[$tokens[$newline]['code']]) === true) { | |
668 $hasError = true; | |
669 } | |
670 } | |
671 | |
672 if ($newline === $stackPtr) { | |
673 $next = ($stackPtr + 1); | |
674 } else { | |
675 // Check that there were no significant tokens that we | |
676 // skipped over to find our newline character. | |
677 $next = $phpcsFile->findNext( | |
678 $ignoreTokens, | |
679 $stackPtr, | |
680 null, | |
681 true | |
682 ); | |
683 | |
684 if ($next < $newline) { | |
685 // We skipped a non-ignored token. | |
686 $hasError = true; | |
687 } else { | |
688 $next = ($newline + 1); | |
689 } | |
690 } | |
691 }//end if | |
692 | |
693 if ($stackPtr !== $lastAddedStackPtr) { | |
694 $found .= $phpcsFile->getTokensAsString( | |
695 $stackPtr, | |
696 ($next - $stackPtr) | |
697 ); | |
698 | |
699 $diff = ($next - $stackPtr); | |
700 $lastAddedStackPtr = ($next - 1); | |
701 } | |
702 | |
703 $stackPtr = $next; | |
704 }//end if | |
705 }//end for | |
706 | |
707 if ($hasError === true) { | |
708 $error = $this->prepareError($found, $patternCode); | |
709 $errors[$origStackPtr] = $error; | |
710 } | |
711 | |
712 return $errors; | |
713 | |
714 }//end processPattern() | |
715 | |
716 | |
717 /** | |
718 * Prepares an error for the specified patternCode. | |
719 * | |
720 * @param string $found The actual found string in the code. | |
721 * @param string $patternCode The expected pattern code. | |
722 * | |
723 * @return string The error message. | |
724 */ | |
725 protected function prepareError($found, $patternCode) | |
726 { | |
727 $found = str_replace("\r\n", '\n', $found); | |
728 $found = str_replace("\n", '\n', $found); | |
729 $found = str_replace("\r", '\n', $found); | |
730 $found = str_replace("\t", '\t', $found); | |
731 $found = str_replace('EOL', '\n', $found); | |
732 $expected = str_replace('EOL', '\n', $patternCode); | |
733 | |
734 $error = "Expected \"$expected\"; found \"$found\""; | |
735 | |
736 return $error; | |
737 | |
738 }//end prepareError() | |
739 | |
740 | |
741 /** | |
742 * Returns the patterns that should be checked. | |
743 * | |
744 * @return string[] | |
745 */ | |
746 protected abstract function getPatterns(); | |
747 | |
748 | |
749 /** | |
750 * Registers any supplementary tokens that this test might wish to process. | |
751 * | |
752 * A sniff may wish to register supplementary tests when it wishes to group | |
753 * an arbitrary validation that cannot be performed using a pattern, with | |
754 * other pattern tests. | |
755 * | |
756 * @return int[] | |
757 * @see processSupplementary() | |
758 */ | |
759 protected function registerSupplementary() | |
760 { | |
761 return array(); | |
762 | |
763 }//end registerSupplementary() | |
764 | |
765 | |
766 /** | |
767 * Processes any tokens registered with registerSupplementary(). | |
768 * | |
769 * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where to | |
770 * process the skip. | |
771 * @param int $stackPtr The position in the tokens stack to | |
772 * process. | |
773 * | |
774 * @return void | |
775 * @see registerSupplementary() | |
776 */ | |
777 protected function processSupplementary( | |
778 PHP_CodeSniffer_File $phpcsFile, | |
779 $stackPtr | |
780 ) { | |
781 | |
782 }//end processSupplementary() | |
783 | |
784 | |
785 /** | |
786 * Parses a pattern string into an array of pattern steps. | |
787 * | |
788 * @param string $pattern The pattern to parse. | |
789 * | |
790 * @return array The parsed pattern array. | |
791 * @see _createSkipPattern() | |
792 * @see _createTokenPattern() | |
793 */ | |
794 private function _parse($pattern) | |
795 { | |
796 $patterns = array(); | |
797 $length = strlen($pattern); | |
798 $lastToken = 0; | |
799 $firstToken = 0; | |
800 | |
801 for ($i = 0; $i < $length; $i++) { | |
802 $specialPattern = false; | |
803 $isLastChar = ($i === ($length - 1)); | |
804 $oldFirstToken = $firstToken; | |
805 | |
806 if (substr($pattern, $i, 3) === '...') { | |
807 // It's a skip pattern. The skip pattern requires the | |
808 // content of the token in the "from" position and the token | |
809 // to skip to. | |
810 $specialPattern = $this->_createSkipPattern($pattern, ($i - 1)); | |
811 $lastToken = ($i - $firstToken); | |
812 $firstToken = ($i + 3); | |
813 $i = ($i + 2); | |
814 | |
815 if ($specialPattern['to'] !== 'unknown') { | |
816 $firstToken++; | |
817 } | |
818 } else if (substr($pattern, $i, 3) === 'abc') { | |
819 $specialPattern = array('type' => 'string'); | |
820 $lastToken = ($i - $firstToken); | |
821 $firstToken = ($i + 3); | |
822 $i = ($i + 2); | |
823 } else if (substr($pattern, $i, 3) === 'EOL') { | |
824 $specialPattern = array('type' => 'newline'); | |
825 $lastToken = ($i - $firstToken); | |
826 $firstToken = ($i + 3); | |
827 $i = ($i + 2); | |
828 }//end if | |
829 | |
830 if ($specialPattern !== false || $isLastChar === true) { | |
831 // If we are at the end of the string, don't worry about a limit. | |
832 if ($isLastChar === true) { | |
833 // Get the string from the end of the last skip pattern, if any, | |
834 // to the end of the pattern string. | |
835 $str = substr($pattern, $oldFirstToken); | |
836 } else { | |
837 // Get the string from the end of the last special pattern, | |
838 // if any, to the start of this special pattern. | |
839 if ($lastToken === 0) { | |
840 // Note that if the last special token was zero characters ago, | |
841 // there will be nothing to process so we can skip this bit. | |
842 // This happens if you have something like: EOL... in your pattern. | |
843 $str = ''; | |
844 } else { | |
845 $str = substr($pattern, $oldFirstToken, $lastToken); | |
846 } | |
847 } | |
848 | |
849 if ($str !== '') { | |
850 $tokenPatterns = $this->_createTokenPattern($str); | |
851 foreach ($tokenPatterns as $tokenPattern) { | |
852 $patterns[] = $tokenPattern; | |
853 } | |
854 } | |
855 | |
856 // Make sure we don't skip the last token. | |
857 if ($isLastChar === false && $i === ($length - 1)) { | |
858 $i--; | |
859 } | |
860 }//end if | |
861 | |
862 // Add the skip pattern *after* we have processed | |
863 // all the tokens from the end of the last skip pattern | |
864 // to the start of this skip pattern. | |
865 if ($specialPattern !== false) { | |
866 $patterns[] = $specialPattern; | |
867 } | |
868 }//end for | |
869 | |
870 return $patterns; | |
871 | |
872 }//end _parse() | |
873 | |
874 | |
875 /** | |
876 * Creates a skip pattern. | |
877 * | |
878 * @param string $pattern The pattern being parsed. | |
879 * @param string $from The token content that the skip pattern starts from. | |
880 * | |
881 * @return array The pattern step. | |
882 * @see _createTokenPattern() | |
883 * @see _parse() | |
884 */ | |
885 private function _createSkipPattern($pattern, $from) | |
886 { | |
887 $skip = array('type' => 'skip'); | |
888 | |
889 $nestedParenthesis = 0; | |
890 $nestedBraces = 0; | |
891 for ($start = $from; $start >= 0; $start--) { | |
892 switch ($pattern[$start]) { | |
893 case '(': | |
894 if ($nestedParenthesis === 0) { | |
895 $skip['to'] = 'parenthesis_closer'; | |
896 } | |
897 | |
898 $nestedParenthesis--; | |
899 break; | |
900 case '{': | |
901 if ($nestedBraces === 0) { | |
902 $skip['to'] = 'scope_closer'; | |
903 } | |
904 | |
905 $nestedBraces--; | |
906 break; | |
907 case '}': | |
908 $nestedBraces++; | |
909 break; | |
910 case ')': | |
911 $nestedParenthesis++; | |
912 break; | |
913 }//end switch | |
914 | |
915 if (isset($skip['to']) === true) { | |
916 break; | |
917 } | |
918 }//end for | |
919 | |
920 if (isset($skip['to']) === false) { | |
921 $skip['to'] = 'unknown'; | |
922 } | |
923 | |
924 return $skip; | |
925 | |
926 }//end _createSkipPattern() | |
927 | |
928 | |
929 /** | |
930 * Creates a token pattern. | |
931 * | |
932 * @param string $str The tokens string that the pattern should match. | |
933 * | |
934 * @return array The pattern step. | |
935 * @see _createSkipPattern() | |
936 * @see _parse() | |
937 */ | |
938 private function _createTokenPattern($str) | |
939 { | |
940 // Don't add a space after the closing php tag as it will add a new | |
941 // whitespace token. | |
942 $tokenizer = new PHP_CodeSniffer_Tokenizers_PHP(); | |
943 $tokens = $tokenizer->tokenizeString('<?php '.$str.'?>'); | |
944 | |
945 // Remove the <?php tag from the front and the end php tag from the back. | |
946 $tokens = array_slice($tokens, 1, (count($tokens) - 2)); | |
947 | |
948 $patterns = array(); | |
949 foreach ($tokens as $patternInfo) { | |
950 $patterns[] = array( | |
951 'type' => 'token', | |
952 'token' => $patternInfo['code'], | |
953 'value' => $patternInfo['content'], | |
954 ); | |
955 } | |
956 | |
957 return $patterns; | |
958 | |
959 }//end _createTokenPattern() | |
960 | |
961 | |
962 }//end class |