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