Subversion Repositories Applications.framework

Rev

Rev 5 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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