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

« back to all changes in this revision

Viewing changes to PHP_CodeSniffer-1.5.4/CodeSniffer/Standards/AbstractPatternSniff.php

  • Committer: Package Import Robot
  • Author(s): David Prévot, Greg Sherwood, Dawid Nowak, Weston Ruter, David Prévot
  • Date: 2014-08-09 12:28:47 UTC
  • mfrom: (1.1.5)
  • Revision ID: package-import@ubuntu.com-20140809122847-giuxi3ezmeajasvy
Tags: 1.5.4-1
* Team upload

[ Greg Sherwood ]
* Removed use of sys_get_temp_dir() as this is not supported by the min PHP
  version
* Fixed bug #20268 : Incorrect documentation titles in PEAR documentation
* Generic ScopeIndentSniff now accounts for different open tag indents
* Forgot that short array token are detected by the tokenizer even on
  unsupported PHP versions
* Fixed bug #20296 : new array notion in function comma check fails
* Fixed bug #20310 : PSR2 does not check for space after function name
* Fixed bug #20309 : Use "member variable" term in sniff "processMemberVar"
  method
* Fixed bug #20307 : PHP_CodeSniffer_Standards_AbstractVariableSniff analyze
  traits
* Fixed bug #20308 : Squiz.ValidVariableNameSniff - wrong variable usage
* Squiz InlineCommentSniff no longer requires a blank line after
  post-statement comments (request #20299)
* Fixed bug #20322 : Display rules set to type=error even when suppressing
  warnings
* Invalid sniff codes passed to --sniffs now show a friendly error message
  (request #20313)
* Generic LineLengthSniff now shows a warning if the iconv module is disabled
  (request #20314)
* Fixed bug #20323 : PHPCS tries to load sniffs from hidden directories
* Squiz SelfMemberReferenceSniff now works correctly with namespaces
* Prepare for 1.5.4 release

[ Dawid Nowak ]
* Correct multiline call detection: PSR2_FunctionCallSignatureSniff (string
  with multiple lines doesn't  necessarily mean multiple lines call)

[ Weston Ruter ]
* Allow installed_paths to be relative to the phpcs root directory

[ David Prévot ]
* Update phpcs.1
* Add XS-Testsuite still needed for ci.d.n

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-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: 1.5.4
 
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
 
 
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
 
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
                                && ($this->ignoreComments === true && in_array($tokens[($stackPtr - 1)]['code'], PHP_CodeSniffer_Tokens::$commentTokens) === 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
                    }
 
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 ($pattern[$i]['type'] === 'token') {
 
444
                if ($pattern[$i]['token'] === T_WHITESPACE) {
 
445
                    if ($this->ignoreComments === true) {
 
446
                        // If we are ignoring comments, check to see if this current
 
447
                        // token is a comment. If so skip it.
 
448
                        if (in_array($tokens[$stackPtr]['code'], PHP_CodeSniffer_Tokens::$commentTokens) === true) {
 
449
                            continue;
 
450
                        }
 
451
 
 
452
                        // If the next token is a comment, the we need to skip the
 
453
                        // current token as we should allow a space before a
 
454
                        // comment for readability.
 
455
                        if (in_array($tokens[($stackPtr + 1)]['code'], PHP_CodeSniffer_Tokens::$commentTokens) === true) {
 
456
                            continue;
 
457
                        }
 
458
                    }
 
459
 
 
460
                    $tokenContent = '';
 
461
                    if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
 
462
                        if (isset($pattern[($i + 1)]) === false) {
 
463
                            // This is the last token in the pattern, so just compare
 
464
                            // the next token of content.
 
465
                            $tokenContent = $tokens[$stackPtr]['content'];
 
466
                        } else {
 
467
                            // Get all the whitespace to the next token.
 
468
                            $next = $phpcsFile->findNext(
 
469
                                PHP_CodeSniffer_Tokens::$emptyTokens,
 
470
                                $stackPtr,
 
471
                                null,
 
472
                                true
 
473
                            );
 
474
 
 
475
                            $tokenContent = $phpcsFile->getTokensAsString(
 
476
                                $stackPtr,
 
477
                                ($next - $stackPtr)
 
478
                            );
 
479
 
 
480
                            $lastAddedStackPtr = $stackPtr;
 
481
                            $stackPtr          = $next;
 
482
                        }
 
483
 
 
484
                        if ($stackPtr !== $lastAddedStackPtr) {
 
485
                            $found .= $tokenContent;
 
486
                        }
 
487
                    } else {
 
488
                        if ($stackPtr !== $lastAddedStackPtr) {
 
489
                            $found            .= $tokens[$stackPtr]['content'];
 
490
                            $lastAddedStackPtr = $stackPtr;
 
491
                        }
 
492
                    }//end if
 
493
 
 
494
                    if (isset($pattern[($i + 1)]) === true
 
495
                        && $pattern[($i + 1)]['type'] === 'skip'
 
496
                    ) {
 
497
                        // The next token is a skip token, so we just need to make
 
498
                        // sure the whitespace we found has *at least* the
 
499
                        // whitespace required.
 
500
                        if (strpos($tokenContent, $pattern[$i]['value']) !== 0) {
 
501
                            $hasError = true;
 
502
                        }
 
503
                    } else {
 
504
                        if ($tokenContent !== $pattern[$i]['value']) {
 
505
                            $hasError = true;
 
506
                        }
 
507
                    }
 
508
                } else {
 
509
                    // Check to see if this important token is the same as the
 
510
                    // next important token in the pattern. If it is not, then
 
511
                    // the pattern cannot be for this piece of code.
 
512
                    $next = $phpcsFile->findNext(
 
513
                        $ignoreTokens,
 
514
                        $stackPtr,
 
515
                        null,
 
516
                        true
 
517
                    );
 
518
 
 
519
                    if ($next === false
 
520
                        || $tokens[$next]['code'] !== $pattern[$i]['token']
 
521
                    ) {
 
522
                        // The next important token did not match the pattern.
 
523
                        return false;
 
524
                    }
 
525
 
 
526
                    if ($lastAddedStackPtr !== null) {
 
527
                        if (($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET
 
528
                            || $tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET)
 
529
                            && isset($tokens[$next]['scope_condition']) === true
 
530
                            && $tokens[$next]['scope_condition'] > $lastAddedStackPtr
 
531
                        ) {
 
532
                            // This is a brace, but the owner of it is after the current
 
533
                            // token, which means it does not belong to any token in
 
534
                            // our pattern. This means the pattern is not for us.
 
535
                            return false;
 
536
                        }
 
537
 
 
538
                        if (($tokens[$next]['code'] === T_OPEN_PARENTHESIS
 
539
                            || $tokens[$next]['code'] === T_CLOSE_PARENTHESIS)
 
540
                            && isset($tokens[$next]['parenthesis_owner']) === true
 
541
                            && $tokens[$next]['parenthesis_owner'] > $lastAddedStackPtr
 
542
                        ) {
 
543
                            // This is a bracket, but the owner of it is after the current
 
544
                            // token, which means it does not belong to any token in
 
545
                            // our pattern. This means the pattern is not for us.
 
546
                            return false;
 
547
                        }
 
548
                    }//end if
 
549
 
 
550
                    // If we skipped past some whitespace tokens, then add them
 
551
                    // to the found string.
 
552
                    if (($next - $stackPtr) > 0) {
 
553
                        $hasComment = false;
 
554
                        for ($j = $stackPtr; $j < $next; $j++) {
 
555
                            $found .= $tokens[$j]['content'];
 
556
                            if (in_array($tokens[$j]['code'], PHP_CodeSniffer_Tokens::$commentTokens) === true) {
 
557
                                $hasComment = true;
 
558
                            }
 
559
                        }
 
560
 
 
561
                        // If we are not ignoring comments, this additional
 
562
                        // whitespace or comment is not allowed. If we are
 
563
                        // ignoring comments, there needs to be at least one
 
564
                        // comment for this to be allowed.
 
565
                        if ($this->ignoreComments === false
 
566
                            || ($this->ignoreComments === true
 
567
                            && $hasComment === false)
 
568
                        ) {
 
569
                            $hasError = true;
 
570
                        }
 
571
 
 
572
                        // Even when ignoring comments, we are not allowed to include
 
573
                        // newlines without the pattern specifying them, so
 
574
                        // everything should be on the same line.
 
575
                        if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) {
 
576
                            $hasError = true;
 
577
                        }
 
578
                    }//end if
 
579
 
 
580
                    if ($next !== $lastAddedStackPtr) {
 
581
                        $found            .= $tokens[$next]['content'];
 
582
                        $lastAddedStackPtr = $next;
 
583
                    }
 
584
 
 
585
                    if (isset($pattern[($i + 1)]) === true
 
586
                        && $pattern[($i + 1)]['type'] === 'skip'
 
587
                    ) {
 
588
                        $stackPtr = $next;
 
589
                    } else {
 
590
                        $stackPtr = ($next + 1);
 
591
                    }
 
592
                }//end if
 
593
            } else if ($pattern[$i]['type'] === 'skip') {
 
594
                if ($pattern[$i]['to'] === 'unknown') {
 
595
                    $next = $phpcsFile->findNext(
 
596
                        $pattern[($i + 1)]['token'],
 
597
                        $stackPtr
 
598
                    );
 
599
 
 
600
                    if ($next === false) {
 
601
                        // Couldn't find the next token, sowe we must
 
602
                        // be using the wrong pattern.
 
603
                        return false;
 
604
                    }
 
605
 
 
606
                    $found   .= '...';
 
607
                    $stackPtr = $next;
 
608
                } else {
 
609
                    // Find the previous opener.
 
610
                    $next = $phpcsFile->findPrevious(
 
611
                        PHP_CodeSniffer_Tokens::$blockOpeners,
 
612
                        $stackPtr
 
613
                    );
 
614
 
 
615
                    if ($next === false
 
616
                        || isset($tokens[$next][$pattern[$i]['to']]) === false
 
617
                    ) {
 
618
                        // If there was not opener, then we must
 
619
                        // be using the wrong pattern.
 
620
                        return false;
 
621
                    }
 
622
 
 
623
                    $found .= '...';
 
624
                    if ($pattern[$i]['to'] === 'parenthesis_closer') {
 
625
                        $found .= ')';
 
626
                    } else {
 
627
                        $found .= '}';
 
628
                    }
 
629
 
 
630
                    // Skip to the closing token.
 
631
                    $stackPtr = ($tokens[$next][$pattern[$i]['to']] + 1);
 
632
                }//end if
 
633
            } else if ($pattern[$i]['type'] === 'string') {
 
634
                if ($tokens[$stackPtr]['code'] !== T_STRING) {
 
635
                    $hasError = true;
 
636
                }
 
637
 
 
638
                if ($stackPtr !== $lastAddedStackPtr) {
 
639
                    $found            .= 'abc';
 
640
                    $lastAddedStackPtr = $stackPtr;
 
641
                }
 
642
 
 
643
                $stackPtr++;
 
644
            } else if ($pattern[$i]['type'] === 'newline') {
 
645
                // Find the next token that contains a newline character.
 
646
                $newline = 0;
 
647
                for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) {
 
648
                    if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) {
 
649
                        $newline = $j;
 
650
                        break;
 
651
                    }
 
652
                }
 
653
 
 
654
                if ($newline === 0) {
 
655
                    // We didn't find a newline character in the rest of the file.
 
656
                    $next     = ($phpcsFile->numTokens - 1);
 
657
                    $hasError = true;
 
658
                } else {
 
659
                    if ($this->ignoreComments === false) {
 
660
                        // The newline character cannot be part of a comment.
 
661
                        if (in_array($tokens[$newline]['code'], PHP_CodeSniffer_Tokens::$commentTokens) === true) {
 
662
                            $hasError = true;
 
663
                        }
 
664
                    }
 
665
 
 
666
                    if ($newline === $stackPtr) {
 
667
                        $next = ($stackPtr + 1);
 
668
                    } else {
 
669
                        // Check that there were no significant tokens that we
 
670
                        // skipped over to find our newline character.
 
671
                        $next = $phpcsFile->findNext(
 
672
                            $ignoreTokens,
 
673
                            $stackPtr,
 
674
                            null,
 
675
                            true
 
676
                        );
 
677
 
 
678
                        if ($next < $newline) {
 
679
                            // We skipped a non-ignored token.
 
680
                            $hasError = true;
 
681
                        } else {
 
682
                            $next = ($newline + 1);
 
683
                        }
 
684
                    }
 
685
                }//end if
 
686
 
 
687
                if ($stackPtr !== $lastAddedStackPtr) {
 
688
                    $found .= $phpcsFile->getTokensAsString(
 
689
                        $stackPtr,
 
690
                        ($next - $stackPtr)
 
691
                    );
 
692
 
 
693
                    $diff = ($next - $stackPtr);
 
694
                    $lastAddedStackPtr = ($next - 1);
 
695
                }
 
696
 
 
697
                $stackPtr = $next;
 
698
            }//end if
 
699
        }//end for
 
700
 
 
701
        if ($hasError === true) {
 
702
            $error = $this->prepareError($found, $patternCode);
 
703
            $errors[$origStackPtr] = $error;
 
704
        }
 
705
 
 
706
        return $errors;
 
707
 
 
708
    }//end processPattern()
 
709
 
 
710
 
 
711
    /**
 
712
     * Prepares an error for the specified patternCode.
 
713
     *
 
714
     * @param string $found       The actual found string in the code.
 
715
     * @param string $patternCode The expected pattern code.
 
716
     *
 
717
     * @return string The error message.
 
718
     */
 
719
    protected function prepareError($found, $patternCode)
 
720
    {
 
721
        $found    = str_replace("\r\n", '\n', $found);
 
722
        $found    = str_replace("\n", '\n', $found);
 
723
        $found    = str_replace("\r", '\n', $found);
 
724
        $found    = str_replace("\t", '\t', $found);
 
725
        $found    = str_replace('EOL', '\n', $found);
 
726
        $expected = str_replace('EOL', '\n', $patternCode);
 
727
 
 
728
        $error = "Expected \"$expected\"; found \"$found\"";
 
729
 
 
730
        return $error;
 
731
 
 
732
    }//end prepareError()
 
733
 
 
734
 
 
735
    /**
 
736
     * Returns the patterns that should be checked.
 
737
     *
 
738
     * @return string[]
 
739
     */
 
740
    protected abstract function getPatterns();
 
741
 
 
742
 
 
743
    /**
 
744
     * Registers any supplementary tokens that this test might wish to process.
 
745
     *
 
746
     * A sniff may wish to register supplementary tests when it wishes to group
 
747
     * an arbitary validation that cannot be performed using a pattern, with
 
748
     * other pattern tests.
 
749
     *
 
750
     * @return int[]
 
751
     * @see processSupplementary()
 
752
     */
 
753
    protected function registerSupplementary()
 
754
    {
 
755
        return array();
 
756
 
 
757
    }//end registerSupplementary()
 
758
 
 
759
 
 
760
     /**
 
761
      * Processes any tokens registered with registerSupplementary().
 
762
      *
 
763
      * @param PHP_CodeSniffer_File $phpcsFile The PHP_CodeSniffer file where to
 
764
      *                                        process the skip.
 
765
      * @param int                  $stackPtr  The position in the tokens stack to
 
766
      *                                        process.
 
767
      *
 
768
      * @return void
 
769
      * @see registerSupplementary()
 
770
      */
 
771
    protected function processSupplementary(
 
772
        PHP_CodeSniffer_File $phpcsFile,
 
773
        $stackPtr
 
774
    ) {
 
775
 
 
776
    }//end processSupplementary()
 
777
 
 
778
 
 
779
    /**
 
780
     * Parses a pattern string into an array of pattern steps.
 
781
     *
 
782
     * @param string $pattern The pattern to parse.
 
783
     *
 
784
     * @return array The parsed pattern array.
 
785
     * @see _createSkipPattern()
 
786
     * @see _createTokenPattern()
 
787
     */
 
788
    private function _parse($pattern)
 
789
    {
 
790
        $patterns   = array();
 
791
        $length     = strlen($pattern);
 
792
        $lastToken  = 0;
 
793
        $firstToken = 0;
 
794
 
 
795
        for ($i = 0; $i < $length; $i++) {
 
796
 
 
797
            $specialPattern = false;
 
798
            $isLastChar     = ($i === ($length - 1));
 
799
            $oldFirstToken  = $firstToken;
 
800
 
 
801
            if (substr($pattern, $i, 3) === '...') {
 
802
                // It's a skip pattern. The skip pattern requires the
 
803
                // content of the token in the "from" position and the token
 
804
                // to skip to.
 
805
                $specialPattern = $this->_createSkipPattern($pattern, ($i - 1));
 
806
                $lastToken      = ($i - $firstToken);
 
807
                $firstToken     = ($i + 3);
 
808
                $i              = ($i + 2);
 
809
 
 
810
                if ($specialPattern['to'] !== 'unknown') {
 
811
                    $firstToken++;
 
812
                }
 
813
            } else if (substr($pattern, $i, 3) === 'abc') {
 
814
                $specialPattern = array('type' => 'string');
 
815
                $lastToken      = ($i - $firstToken);
 
816
                $firstToken     = ($i + 3);
 
817
                $i              = ($i + 2);
 
818
            } else if (substr($pattern, $i, 3) === 'EOL') {
 
819
                $specialPattern = array('type' => 'newline');
 
820
                $lastToken      = ($i - $firstToken);
 
821
                $firstToken     = ($i + 3);
 
822
                $i              = ($i + 2);
 
823
            }
 
824
 
 
825
            if ($specialPattern !== false || $isLastChar === true) {
 
826
                // If we are at the end of the string, don't worry about a limit.
 
827
                if ($isLastChar === true) {
 
828
                    // Get the string from the end of the last skip pattern, if any,
 
829
                    // to the end of the pattern string.
 
830
                    $str = substr($pattern, $oldFirstToken);
 
831
                } else {
 
832
                    // Get the string from the end of the last special pattern,
 
833
                    // if any, to the start of this special pattern.
 
834
                    if ($lastToken === 0) {
 
835
                        // Note that if the last special token was zero characters ago,
 
836
                        // there will be nothing to process so we can skip this bit.
 
837
                        // This happens if you have something like: EOL... in your pattern.
 
838
                        $str = '';
 
839
                    } else {
 
840
                        $str = substr($pattern, $oldFirstToken, $lastToken);
 
841
                    }
 
842
                }
 
843
 
 
844
                if ($str !== '') {
 
845
                    $tokenPatterns = $this->_createTokenPattern($str);
 
846
                    foreach ($tokenPatterns as $tokenPattern) {
 
847
                        $patterns[] = $tokenPattern;
 
848
                    }
 
849
                }
 
850
 
 
851
                // Make sure we don't skip the last token.
 
852
                if ($isLastChar === false && $i === ($length - 1)) {
 
853
                    $i--;
 
854
                }
 
855
            }//end if
 
856
 
 
857
            // Add the skip pattern *after* we have processed
 
858
            // all the tokens from the end of the last skip pattern
 
859
            // to the start of this skip pattern.
 
860
            if ($specialPattern !== false) {
 
861
                $patterns[] = $specialPattern;
 
862
            }
 
863
        }//end for
 
864
 
 
865
        return $patterns;
 
866
 
 
867
    }//end _parse()
 
868
 
 
869
 
 
870
    /**
 
871
     * Creates a skip pattern.
 
872
     *
 
873
     * @param string $pattern The pattern being parsed.
 
874
     * @param string $from    The token content that the skip pattern starts from.
 
875
     *
 
876
     * @return array The pattern step.
 
877
     * @see _createTokenPattern()
 
878
     * @see _parse()
 
879
     */
 
880
    private function _createSkipPattern($pattern, $from)
 
881
    {
 
882
        $skip = array('type' => 'skip');
 
883
 
 
884
        $nestedParenthesis = 0;
 
885
        $nestedBraces      = 0;
 
886
        for ($start = $from; $start >= 0; $start--) {
 
887
            switch ($pattern[$start]) {
 
888
            case '(':
 
889
                if ($nestedParenthesis === 0) {
 
890
                    $skip['to'] = 'parenthesis_closer';
 
891
                }
 
892
 
 
893
                $nestedParenthesis--;
 
894
                break;
 
895
            case '{':
 
896
                if ($nestedBraces === 0) {
 
897
                    $skip['to'] = 'scope_closer';
 
898
                }
 
899
 
 
900
                $nestedBraces--;
 
901
                break;
 
902
            case '}':
 
903
                $nestedBraces++;
 
904
                break;
 
905
            case ')':
 
906
                $nestedParenthesis++;
 
907
                break;
 
908
            }
 
909
 
 
910
            if (isset($skip['to']) === true) {
 
911
                break;
 
912
            }
 
913
        }
 
914
 
 
915
        if (isset($skip['to']) === false) {
 
916
            $skip['to'] = 'unknown';
 
917
        }
 
918
 
 
919
        return $skip;
 
920
 
 
921
    }//end _createSkipPattern()
 
922
 
 
923
 
 
924
    /**
 
925
     * Creates a token pattern.
 
926
     *
 
927
     * @param string $str The tokens string that the pattern should match.
 
928
     *
 
929
     * @return array The pattern step.
 
930
     * @see _createSkipPattern()
 
931
     * @see _parse()
 
932
     */
 
933
    private function _createTokenPattern($str)
 
934
    {
 
935
        // Don't add a space after the closing php tag as it will add a new
 
936
        // whitespace token.
 
937
        $tokens = token_get_all('<?php '.$str.'?>');
 
938
 
 
939
        // Remove the <?php tag from the front and the end php tag from the back.
 
940
        $tokens = array_slice($tokens, 1, (count($tokens) - 2));
 
941
 
 
942
        foreach ($tokens as &$token) {
 
943
            $token = PHP_CodeSniffer::standardiseToken($token);
 
944
        }
 
945
 
 
946
        $patterns = array();
 
947
        foreach ($tokens as $patternInfo) {
 
948
            $patterns[] = array(
 
949
                           'type'  => 'token',
 
950
                           'token' => $patternInfo['code'],
 
951
                           'value' => $patternInfo['content'],
 
952
                          );
 
953
        }
 
954
 
 
955
        return $patterns;
 
956
 
 
957
    }//end _createTokenPattern()
 
958
 
 
959
 
 
960
}//end class
 
961
 
 
962
?>