~ubuntu-branches/ubuntu/wily/php-codesniffer/wily

« back to all changes in this revision

Viewing changes to PHP_CodeSniffer-1.5.0RC2/CodeSniffer/Standards/AbstractPatternSniff.php

  • Committer: Package Import Robot
  • Author(s): David Prévot
  • Date: 2014-07-21 14:42:41 UTC
  • mfrom: (1.1.4)
  • Revision ID: package-import@ubuntu.com-20140721144241-g4orlcuk4jzn9mhs
Tags: 1.5.3-1
* Team upload
* Focus on stable release
* Update copyright
* Bump standards version to 3.9.5
* Update Homepage
* Use ${phppear:…} instead of hardcoding them
* Run tests in dh_auto_test
* Update patch with gbp pq
* Simplify configuration installation
* Edit package.xml to move script
* Add DEP-8 tests

Show diffs side-by-side

added added

removed removed

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