• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHP_CodeSniffer / 14448228954

14 Apr 2025 02:28PM UTC coverage: 75.31% (+0.006%) from 75.304%
14448228954

push

github

web-flow
Merge pull request #974 from PHPCSStandards/phpcs-4.0/feature/6-abstractpatternsniff-remove-deprecated-param

AbstractPatternSniff::__construct(): remove the $ignoreComments property

0 of 1 new or added line in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

21107 of 28027 relevant lines covered (75.31%)

70.43 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Sniffs/AbstractPatternSniff.php
1
<?php
2
/**
3
 * Processes pattern strings and checks that the code conforms to the pattern.
4
 *
5
 * @author    Greg Sherwood <gsherwood@squiz.net>
6
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
7
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8
 */
9

10
namespace PHP_CodeSniffer\Sniffs;
11

12
use PHP_CodeSniffer\Exceptions\RuntimeException;
13
use PHP_CodeSniffer\Files\File;
14
use PHP_CodeSniffer\Tokenizers\PHP;
15
use PHP_CodeSniffer\Util\Tokens;
16

17
abstract class AbstractPatternSniff implements Sniff
18
{
19

20
    /**
21
     * If true, comments will be ignored if they are found in the code.
22
     *
23
     * @var boolean
24
     */
25
    public $ignoreComments = false;
26

27
    /**
28
     * The current file being checked.
29
     *
30
     * @var string
31
     */
32
    protected $currFile = '';
33

34
    /**
35
     * The parsed patterns array.
36
     *
37
     * @var array
38
     */
39
    private $parsedPatterns = [];
40

41
    /**
42
     * Tokens that this sniff wishes to process outside of the patterns.
43
     *
44
     * @var int[]
45
     * @see registerSupplementary()
46
     * @see processSupplementary()
47
     */
48
    private $supplementaryTokens = [];
49

50
    /**
51
     * Positions in the stack where errors have occurred.
52
     *
53
     * @var array<int, bool>
54
     */
55
    private $errorPos = [];
56

57

58
    /**
59
     * Constructs a AbstractPatternSniff.
60
     */
NEW
61
    public function __construct()
×
62
    {
UNCOV
63
        $this->supplementaryTokens = $this->registerSupplementary();
×
64

65
    }//end __construct()
66

67

68
    /**
69
     * Registers the tokens to listen to.
70
     *
71
     * Classes extending <i>AbstractPatternTest</i> should implement the
72
     * <i>getPatterns()</i> method to register the patterns they wish to test.
73
     *
74
     * @return array<int|string>
75
     * @see    process()
76
     */
77
    final public function register()
×
78
    {
79
        $listenTypes = [];
×
80
        $patterns    = $this->getPatterns();
×
81

82
        foreach ($patterns as $pattern) {
×
83
            $parsedPattern = $this->parse($pattern);
×
84

85
            // Find a token position in the pattern that we can use
86
            // for a listener token.
87
            $pos           = $this->getListenerTokenPos($parsedPattern);
×
88
            $tokenType     = $parsedPattern[$pos]['token'];
×
89
            $listenTypes[] = $tokenType;
×
90

91
            $patternArray = [
92
                'listen_pos'   => $pos,
×
93
                'pattern'      => $parsedPattern,
×
94
                'pattern_code' => $pattern,
×
95
            ];
96

97
            if (isset($this->parsedPatterns[$tokenType]) === false) {
×
98
                $this->parsedPatterns[$tokenType] = [];
×
99
            }
100

101
            $this->parsedPatterns[$tokenType][] = $patternArray;
×
102
        }//end foreach
103

104
        return array_unique(array_merge($listenTypes, $this->supplementaryTokens));
×
105

106
    }//end register()
107

108

109
    /**
110
     * Returns the token types that the specified pattern is checking for.
111
     *
112
     * Returned array is in the format:
113
     * <code>
114
     *   array(
115
     *      T_WHITESPACE => 0, // 0 is the position where the T_WHITESPACE token
116
     *                         // should occur in the pattern.
117
     *   );
118
     * </code>
119
     *
120
     * @param array $pattern The parsed pattern to find the acquire the token
121
     *                       types from.
122
     *
123
     * @return array<int, int>
124
     */
125
    private function getPatternTokenTypes($pattern)
×
126
    {
127
        $tokenTypes = [];
×
128
        foreach ($pattern as $pos => $patternInfo) {
×
129
            if ($patternInfo['type'] === 'token') {
×
130
                if (isset($tokenTypes[$patternInfo['token']]) === false) {
×
131
                    $tokenTypes[$patternInfo['token']] = $pos;
×
132
                }
133
            }
134
        }
135

136
        return $tokenTypes;
×
137

138
    }//end getPatternTokenTypes()
139

140

141
    /**
142
     * Returns the position in the pattern that this test should register as
143
     * a listener for the pattern.
144
     *
145
     * @param array $pattern The pattern to acquire the listener for.
146
     *
147
     * @return int The position in the pattern that this test should register
148
     *             as the listener.
149
     * @throws \PHP_CodeSniffer\Exceptions\RuntimeException If we could not determine a token to listen for.
150
     */
151
    private function getListenerTokenPos($pattern)
×
152
    {
153
        $tokenTypes = $this->getPatternTokenTypes($pattern);
×
154
        $tokenCodes = array_keys($tokenTypes);
×
155
        $token      = Tokens::getHighestWeightedToken($tokenCodes);
×
156

157
        // If we could not get a token.
158
        if ($token === false) {
×
159
            $error = 'Could not determine a token to listen for';
×
160
            throw new RuntimeException($error);
×
161
        }
162

163
        return $tokenTypes[$token];
×
164

165
    }//end getListenerTokenPos()
166

167

168
    /**
169
     * Processes the test.
170
     *
171
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where the
172
     *                                               token occurred.
173
     * @param int                         $stackPtr  The position in the tokens stack
174
     *                                               where the listening token type
175
     *                                               was found.
176
     *
177
     * @return void
178
     * @see    register()
179
     */
180
    final public function process(File $phpcsFile, $stackPtr)
×
181
    {
182
        $file = $phpcsFile->getFilename();
×
183
        if ($this->currFile !== $file) {
×
184
            // We have changed files, so clean up.
185
            $this->errorPos = [];
×
186
            $this->currFile = $file;
×
187
        }
188

189
        $tokens = $phpcsFile->getTokens();
×
190

191
        if (in_array($tokens[$stackPtr]['code'], $this->supplementaryTokens, true) === true) {
×
192
            $this->processSupplementary($phpcsFile, $stackPtr);
×
193
        }
194

195
        $type = $tokens[$stackPtr]['code'];
×
196

197
        // If the type is not set, then it must have been a token registered
198
        // with registerSupplementary().
199
        if (isset($this->parsedPatterns[$type]) === false) {
×
200
            return;
×
201
        }
202

203
        $allErrors = [];
×
204

205
        // Loop over each pattern that is listening to the current token type
206
        // that we are processing.
207
        foreach ($this->parsedPatterns[$type] as $patternInfo) {
×
208
            // If processPattern returns false, then the pattern that we are
209
            // checking the code with must not be designed to check that code.
210
            $errors = $this->processPattern($patternInfo, $phpcsFile, $stackPtr);
×
211
            if ($errors === false) {
×
212
                // The pattern didn't match.
213
                continue;
×
214
            } else if (empty($errors) === true) {
×
215
                // The pattern matched, but there were no errors.
216
                break;
×
217
            }
218

219
            foreach ($errors as $stackPtr => $error) {
×
220
                if (isset($this->errorPos[$stackPtr]) === false) {
×
221
                    $this->errorPos[$stackPtr] = true;
×
222
                    $allErrors[$stackPtr]      = $error;
×
223
                }
224
            }
225
        }
226

227
        foreach ($allErrors as $stackPtr => $error) {
×
228
            $phpcsFile->addError($error, $stackPtr, 'Found');
×
229
        }
230

231
    }//end process()
232

233

234
    /**
235
     * Processes the pattern and verifies the code at $stackPtr.
236
     *
237
     * @param array                       $patternInfo Information about the pattern used
238
     *                                                 for checking, which includes are
239
     *                                                 parsed token representation of the
240
     *                                                 pattern.
241
     * @param \PHP_CodeSniffer\Files\File $phpcsFile   The PHP_CodeSniffer file where the
242
     *                                                 token occurred.
243
     * @param int                         $stackPtr    The position in the tokens stack where
244
     *                                                 the listening token type was found.
245
     *
246
     * @return array|false
247
     */
248
    protected function processPattern($patternInfo, File $phpcsFile, $stackPtr)
×
249
    {
250
        $tokens      = $phpcsFile->getTokens();
×
251
        $pattern     = $patternInfo['pattern'];
×
252
        $patternCode = $patternInfo['pattern_code'];
×
253
        $errors      = [];
×
254
        $found       = '';
×
255

256
        $ignoreTokens = [T_WHITESPACE => T_WHITESPACE];
×
257
        if ($this->ignoreComments === true) {
×
258
            $ignoreTokens += Tokens::$commentTokens;
×
259
        }
260

261
        $origStackPtr = $stackPtr;
×
262
        $hasError     = false;
×
263

264
        if ($patternInfo['listen_pos'] > 0) {
×
265
            $stackPtr--;
×
266

267
            for ($i = ($patternInfo['listen_pos'] - 1); $i >= 0; $i--) {
×
268
                if ($pattern[$i]['type'] === 'token') {
×
269
                    if ($pattern[$i]['token'] === T_WHITESPACE) {
×
270
                        if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
×
271
                            $found = $tokens[$stackPtr]['content'].$found;
×
272
                        }
273

274
                        // Only check the size of the whitespace if this is not
275
                        // the first token. We don't care about the size of
276
                        // leading whitespace, just that there is some.
277
                        if ($i !== 0) {
×
278
                            if ($tokens[$stackPtr]['content'] !== $pattern[$i]['value']) {
×
279
                                $hasError = true;
×
280
                            }
281
                        }
282
                    } else {
283
                        // Check to see if this important token is the same as the
284
                        // previous important token in the pattern. If it is not,
285
                        // then the pattern cannot be for this piece of code.
286
                        $prev = $phpcsFile->findPrevious(
×
287
                            $ignoreTokens,
×
288
                            $stackPtr,
×
289
                            null,
×
290
                            true
×
291
                        );
292

293
                        if ($prev === false
×
294
                            || $tokens[$prev]['code'] !== $pattern[$i]['token']
×
295
                        ) {
296
                            return false;
×
297
                        }
298

299
                        // If we skipped past some whitespace tokens, then add them
300
                        // to the found string.
301
                        $tokenContent = $phpcsFile->getTokensAsString(
×
302
                            ($prev + 1),
×
303
                            ($stackPtr - $prev - 1)
×
304
                        );
305

306
                        $found = $tokens[$prev]['content'].$tokenContent.$found;
×
307

308
                        if (isset($pattern[($i - 1)]) === true
×
309
                            && $pattern[($i - 1)]['type'] === 'skip'
×
310
                        ) {
311
                            $stackPtr = $prev;
×
312
                        } else {
313
                            $stackPtr = ($prev - 1);
×
314
                        }
315
                    }//end if
316
                } else if ($pattern[$i]['type'] === 'skip') {
×
317
                    // Skip to next piece of relevant code.
318
                    if ($pattern[$i]['to'] === 'parenthesis_closer') {
×
319
                        $to = 'parenthesis_opener';
×
320
                    } else {
321
                        $to = 'scope_opener';
×
322
                    }
323

324
                    // Find the previous opener.
325
                    $next = $phpcsFile->findPrevious(
×
326
                        $ignoreTokens,
×
327
                        $stackPtr,
×
328
                        null,
×
329
                        true
×
330
                    );
331

332
                    if ($next === false || isset($tokens[$next][$to]) === false) {
×
333
                        // If there was not opener, then we must be
334
                        // using the wrong pattern.
335
                        return false;
×
336
                    }
337

338
                    if ($to === 'parenthesis_opener') {
×
339
                        $found = '{'.$found;
×
340
                    } else {
341
                        $found = '('.$found;
×
342
                    }
343

344
                    $found = '...'.$found;
×
345

346
                    // Skip to the opening token.
347
                    $stackPtr = ($tokens[$next][$to] - 1);
×
348
                } else if ($pattern[$i]['type'] === 'string') {
×
349
                    $found = 'abc';
×
350
                } else if ($pattern[$i]['type'] === 'newline') {
×
351
                    if ($this->ignoreComments === true
×
352
                        && isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true
×
353
                    ) {
354
                        $startComment = $phpcsFile->findPrevious(
×
355
                            Tokens::$commentTokens,
×
356
                            ($stackPtr - 1),
×
357
                            null,
×
358
                            true
×
359
                        );
360

361
                        if ($tokens[$startComment]['line'] !== $tokens[($startComment + 1)]['line']) {
×
362
                            $startComment++;
×
363
                        }
364

365
                        $tokenContent = $phpcsFile->getTokensAsString(
×
366
                            $startComment,
×
367
                            ($stackPtr - $startComment + 1)
×
368
                        );
369

370
                        $found    = $tokenContent.$found;
×
371
                        $stackPtr = ($startComment - 1);
×
372
                    }
373

374
                    if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
×
375
                        if ($tokens[$stackPtr]['content'] !== $phpcsFile->eolChar) {
×
376
                            $found = $tokens[$stackPtr]['content'].$found;
×
377

378
                            // This may just be an indent that comes after a newline
379
                            // so check the token before to make sure. If it is a newline, we
380
                            // can ignore the error here.
381
                            if (($tokens[($stackPtr - 1)]['content'] !== $phpcsFile->eolChar)
×
382
                                && ($this->ignoreComments === true
×
383
                                && isset(Tokens::$commentTokens[$tokens[($stackPtr - 1)]['code']]) === false)
×
384
                            ) {
385
                                $hasError = true;
×
386
                            } else {
387
                                $stackPtr--;
×
388
                            }
389
                        } else {
390
                            $found = 'EOL'.$found;
×
391
                        }
392
                    } else {
393
                        $found    = $tokens[$stackPtr]['content'].$found;
×
394
                        $hasError = true;
×
395
                    }//end if
396

397
                    if ($hasError === false && $pattern[($i - 1)]['type'] !== 'newline') {
×
398
                        // Make sure they only have 1 newline.
399
                        $prev = $phpcsFile->findPrevious($ignoreTokens, ($stackPtr - 1), null, true);
×
400
                        if ($prev !== false && $tokens[$prev]['line'] !== $tokens[$stackPtr]['line']) {
×
401
                            $hasError = true;
×
402
                        }
403
                    }
404
                }//end if
405
            }//end for
406
        }//end if
407

408
        $stackPtr          = $origStackPtr;
×
409
        $lastAddedStackPtr = null;
×
410
        $patternLen        = count($pattern);
×
411

412
        if (($stackPtr + $patternLen - $patternInfo['listen_pos']) > $phpcsFile->numTokens) {
×
413
            // Pattern can never match as there are not enough tokens left in the file.
414
            return false;
×
415
        }
416

417
        for ($i = $patternInfo['listen_pos']; $i < $patternLen; $i++) {
×
418
            if (isset($tokens[$stackPtr]) === false) {
×
419
                break;
×
420
            }
421

422
            if ($pattern[$i]['type'] === 'token') {
×
423
                if ($pattern[$i]['token'] === T_WHITESPACE) {
×
424
                    if ($this->ignoreComments === true) {
×
425
                        // If we are ignoring comments, check to see if this current
426
                        // token is a comment. If so skip it.
427
                        if (isset(Tokens::$commentTokens[$tokens[$stackPtr]['code']]) === true) {
×
428
                            continue;
×
429
                        }
430

431
                        // If the next token is a comment, the we need to skip the
432
                        // current token as we should allow a space before a
433
                        // comment for readability.
434
                        if (isset($tokens[($stackPtr + 1)]) === true
×
435
                            && isset(Tokens::$commentTokens[$tokens[($stackPtr + 1)]['code']]) === true
×
436
                        ) {
437
                            continue;
×
438
                        }
439
                    }
440

441
                    $tokenContent = '';
×
442
                    if ($tokens[$stackPtr]['code'] === T_WHITESPACE) {
×
443
                        if (isset($pattern[($i + 1)]) === false) {
×
444
                            // This is the last token in the pattern, so just compare
445
                            // the next token of content.
446
                            $tokenContent = $tokens[$stackPtr]['content'];
×
447
                        } else {
448
                            // Get all the whitespace to the next token.
449
                            $next = $phpcsFile->findNext(
×
450
                                Tokens::$emptyTokens,
×
451
                                $stackPtr,
×
452
                                null,
×
453
                                true
×
454
                            );
455

456
                            $tokenContent = $phpcsFile->getTokensAsString(
×
457
                                $stackPtr,
×
458
                                ($next - $stackPtr)
×
459
                            );
460

461
                            $lastAddedStackPtr = $stackPtr;
×
462
                            $stackPtr          = $next;
×
463
                        }//end if
464

465
                        if ($stackPtr !== $lastAddedStackPtr) {
×
466
                            $found .= $tokenContent;
×
467
                        }
468
                    } else {
469
                        if ($stackPtr !== $lastAddedStackPtr) {
×
470
                            $found            .= $tokens[$stackPtr]['content'];
×
471
                            $lastAddedStackPtr = $stackPtr;
×
472
                        }
473
                    }//end if
474

475
                    if (isset($pattern[($i + 1)]) === true
×
476
                        && $pattern[($i + 1)]['type'] === 'skip'
×
477
                    ) {
478
                        // The next token is a skip token, so we just need to make
479
                        // sure the whitespace we found has *at least* the
480
                        // whitespace required.
481
                        if (strpos($tokenContent, $pattern[$i]['value']) !== 0) {
×
482
                            $hasError = true;
×
483
                        }
484
                    } else {
485
                        if ($tokenContent !== $pattern[$i]['value']) {
×
486
                            $hasError = true;
×
487
                        }
488
                    }
489
                } else {
490
                    // Check to see if this important token is the same as the
491
                    // next important token in the pattern. If it is not, then
492
                    // the pattern cannot be for this piece of code.
493
                    $next = $phpcsFile->findNext(
×
494
                        $ignoreTokens,
×
495
                        $stackPtr,
×
496
                        null,
×
497
                        true
×
498
                    );
499

500
                    if ($next === false
×
501
                        || $tokens[$next]['code'] !== $pattern[$i]['token']
×
502
                    ) {
503
                        // The next important token did not match the pattern.
504
                        return false;
×
505
                    }
506

507
                    if ($lastAddedStackPtr !== null) {
×
508
                        if (($tokens[$next]['code'] === T_OPEN_CURLY_BRACKET
×
509
                            || $tokens[$next]['code'] === T_CLOSE_CURLY_BRACKET)
×
510
                            && isset($tokens[$next]['scope_condition']) === true
×
511
                            && $tokens[$next]['scope_condition'] > $lastAddedStackPtr
×
512
                        ) {
513
                            // This is a brace, but the owner of it is after the current
514
                            // token, which means it does not belong to any token in
515
                            // our pattern. This means the pattern is not for us.
516
                            return false;
×
517
                        }
518

519
                        if (($tokens[$next]['code'] === T_OPEN_PARENTHESIS
×
520
                            || $tokens[$next]['code'] === T_CLOSE_PARENTHESIS)
×
521
                            && isset($tokens[$next]['parenthesis_owner']) === true
×
522
                            && $tokens[$next]['parenthesis_owner'] > $lastAddedStackPtr
×
523
                        ) {
524
                            // This is a bracket, but the owner of it is after the current
525
                            // token, which means it does not belong to any token in
526
                            // our pattern. This means the pattern is not for us.
527
                            return false;
×
528
                        }
529
                    }//end if
530

531
                    // If we skipped past some whitespace tokens, then add them
532
                    // to the found string.
533
                    if (($next - $stackPtr) > 0) {
×
534
                        $hasComment = false;
×
535
                        for ($j = $stackPtr; $j < $next; $j++) {
×
536
                            $found .= $tokens[$j]['content'];
×
537
                            if (isset(Tokens::$commentTokens[$tokens[$j]['code']]) === true) {
×
538
                                $hasComment = true;
×
539
                            }
540
                        }
541

542
                        // If we are not ignoring comments, this additional
543
                        // whitespace or comment is not allowed. If we are
544
                        // ignoring comments, there needs to be at least one
545
                        // comment for this to be allowed.
546
                        if ($this->ignoreComments === false
×
547
                            || ($this->ignoreComments === true
×
548
                            && $hasComment === false)
×
549
                        ) {
550
                            $hasError = true;
×
551
                        }
552

553
                        // Even when ignoring comments, we are not allowed to include
554
                        // newlines without the pattern specifying them, so
555
                        // everything should be on the same line.
556
                        if ($tokens[$next]['line'] !== $tokens[$stackPtr]['line']) {
×
557
                            $hasError = true;
×
558
                        }
559
                    }//end if
560

561
                    if ($next !== $lastAddedStackPtr) {
×
562
                        $found            .= $tokens[$next]['content'];
×
563
                        $lastAddedStackPtr = $next;
×
564
                    }
565

566
                    if (isset($pattern[($i + 1)]) === true
×
567
                        && $pattern[($i + 1)]['type'] === 'skip'
×
568
                    ) {
569
                        $stackPtr = $next;
×
570
                    } else {
571
                        $stackPtr = ($next + 1);
×
572
                    }
573
                }//end if
574
            } else if ($pattern[$i]['type'] === 'skip') {
×
575
                if ($pattern[$i]['to'] === 'unknown') {
×
576
                    $next = $phpcsFile->findNext(
×
577
                        $pattern[($i + 1)]['token'],
×
578
                        $stackPtr
×
579
                    );
580

581
                    if ($next === false) {
×
582
                        // Couldn't find the next token, so we must
583
                        // be using the wrong pattern.
584
                        return false;
×
585
                    }
586

587
                    $found   .= '...';
×
588
                    $stackPtr = $next;
×
589
                } else {
590
                    // Find the previous opener.
591
                    $next = $phpcsFile->findPrevious(
×
592
                        Tokens::$blockOpeners,
×
593
                        $stackPtr
×
594
                    );
595

596
                    if ($next === false
×
597
                        || isset($tokens[$next][$pattern[$i]['to']]) === false
×
598
                    ) {
599
                        // If there was not opener, then we must
600
                        // be using the wrong pattern.
601
                        return false;
×
602
                    }
603

604
                    $found .= '...';
×
605
                    if ($pattern[$i]['to'] === 'parenthesis_closer') {
×
606
                        $found .= ')';
×
607
                    } else {
608
                        $found .= '}';
×
609
                    }
610

611
                    // Skip to the closing token.
612
                    $stackPtr = ($tokens[$next][$pattern[$i]['to']] + 1);
×
613
                }//end if
614
            } else if ($pattern[$i]['type'] === 'string') {
×
615
                if ($tokens[$stackPtr]['code'] !== T_STRING) {
×
616
                    $hasError = true;
×
617
                }
618

619
                if ($stackPtr !== $lastAddedStackPtr) {
×
620
                    $found            .= 'abc';
×
621
                    $lastAddedStackPtr = $stackPtr;
×
622
                }
623

624
                $stackPtr++;
×
625
            } else if ($pattern[$i]['type'] === 'newline') {
×
626
                // Find the next token that contains a newline character.
627
                $newline = 0;
×
628
                for ($j = $stackPtr; $j < $phpcsFile->numTokens; $j++) {
×
629
                    if (strpos($tokens[$j]['content'], $phpcsFile->eolChar) !== false) {
×
630
                        $newline = $j;
×
631
                        break;
×
632
                    }
633
                }
634

635
                if ($newline === 0) {
×
636
                    // We didn't find a newline character in the rest of the file.
637
                    $next     = ($phpcsFile->numTokens - 1);
×
638
                    $hasError = true;
×
639
                } else {
640
                    if ($this->ignoreComments === false) {
×
641
                        // The newline character cannot be part of a comment.
642
                        if (isset(Tokens::$commentTokens[$tokens[$newline]['code']]) === true) {
×
643
                            $hasError = true;
×
644
                        }
645
                    }
646

647
                    if ($newline === $stackPtr) {
×
648
                        $next = ($stackPtr + 1);
×
649
                    } else {
650
                        // Check that there were no significant tokens that we
651
                        // skipped over to find our newline character.
652
                        $next = $phpcsFile->findNext(
×
653
                            $ignoreTokens,
×
654
                            $stackPtr,
×
655
                            null,
×
656
                            true
×
657
                        );
658

659
                        if ($next < $newline) {
×
660
                            // We skipped a non-ignored token.
661
                            $hasError = true;
×
662
                        } else {
663
                            $next = ($newline + 1);
×
664
                        }
665
                    }
666
                }//end if
667

668
                if ($stackPtr !== $lastAddedStackPtr) {
×
669
                    $found .= $phpcsFile->getTokensAsString(
×
670
                        $stackPtr,
×
671
                        ($next - $stackPtr)
×
672
                    );
673

674
                    $lastAddedStackPtr = ($next - 1);
×
675
                }
676

677
                $stackPtr = $next;
×
678
            }//end if
679
        }//end for
680

681
        if ($hasError === true) {
×
682
            $error = $this->prepareError($found, $patternCode);
×
683
            $errors[$origStackPtr] = $error;
×
684
        }
685

686
        return $errors;
×
687

688
    }//end processPattern()
689

690

691
    /**
692
     * Prepares an error for the specified patternCode.
693
     *
694
     * @param string $found       The actual found string in the code.
695
     * @param string $patternCode The expected pattern code.
696
     *
697
     * @return string The error message.
698
     */
699
    protected function prepareError($found, $patternCode)
×
700
    {
701
        $found    = str_replace("\r\n", '\n', $found);
×
702
        $found    = str_replace("\n", '\n', $found);
×
703
        $found    = str_replace("\r", '\n', $found);
×
704
        $found    = str_replace("\t", '\t', $found);
×
705
        $found    = str_replace('EOL', '\n', $found);
×
706
        $expected = str_replace('EOL', '\n', $patternCode);
×
707

708
        $error = "Expected \"$expected\"; found \"$found\"";
×
709

710
        return $error;
×
711

712
    }//end prepareError()
713

714

715
    /**
716
     * Returns the patterns that should be checked.
717
     *
718
     * @return string[]
719
     */
720
    abstract protected function getPatterns();
721

722

723
    /**
724
     * Registers any supplementary tokens that this test might wish to process.
725
     *
726
     * A sniff may wish to register supplementary tests when it wishes to group
727
     * an arbitrary validation that cannot be performed using a pattern, with
728
     * other pattern tests.
729
     *
730
     * @return int[]
731
     * @see    processSupplementary()
732
     */
733
    protected function registerSupplementary()
×
734
    {
735
        return [];
×
736

737
    }//end registerSupplementary()
738

739

740
     /**
741
      * Processes any tokens registered with registerSupplementary().
742
      *
743
      * @param \PHP_CodeSniffer\Files\File $phpcsFile The PHP_CodeSniffer file where to
744
      *                                               process the skip.
745
      * @param int                         $stackPtr  The position in the tokens stack to
746
      *                                               process.
747
      *
748
      * @return void
749
      * @see    registerSupplementary()
750
      */
751
    protected function processSupplementary(File $phpcsFile, $stackPtr)
×
752
    {
753

754
    }//end processSupplementary()
×
755

756

757
    /**
758
     * Parses a pattern string into an array of pattern steps.
759
     *
760
     * @param string $pattern The pattern to parse.
761
     *
762
     * @return array The parsed pattern array.
763
     * @see    createSkipPattern()
764
     * @see    createTokenPattern()
765
     */
766
    private function parse($pattern)
×
767
    {
768
        $patterns   = [];
×
769
        $length     = strlen($pattern);
×
770
        $lastToken  = 0;
×
771
        $firstToken = 0;
×
772

773
        for ($i = 0; $i < $length; $i++) {
×
774
            $specialPattern = false;
×
775
            $isLastChar     = ($i === ($length - 1));
×
776
            $oldFirstToken  = $firstToken;
×
777

778
            if (substr($pattern, $i, 3) === '...') {
×
779
                // It's a skip pattern. The skip pattern requires the
780
                // content of the token in the "from" position and the token
781
                // to skip to.
782
                $specialPattern = $this->createSkipPattern($pattern, ($i - 1));
×
783
                $lastToken      = ($i - $firstToken);
×
784
                $firstToken     = ($i + 3);
×
785
                $i += 2;
×
786

787
                if ($specialPattern['to'] !== 'unknown') {
×
788
                    $firstToken++;
×
789
                }
790
            } else if (substr($pattern, $i, 3) === 'abc') {
×
791
                $specialPattern = ['type' => 'string'];
×
792
                $lastToken      = ($i - $firstToken);
×
793
                $firstToken     = ($i + 3);
×
794
                $i += 2;
×
795
            } else if (substr($pattern, $i, 3) === 'EOL') {
×
796
                $specialPattern = ['type' => 'newline'];
×
797
                $lastToken      = ($i - $firstToken);
×
798
                $firstToken     = ($i + 3);
×
799
                $i += 2;
×
800
            }//end if
801

802
            if ($specialPattern !== false || $isLastChar === true) {
×
803
                // If we are at the end of the string, don't worry about a limit.
804
                if ($isLastChar === true) {
×
805
                    // Get the string from the end of the last skip pattern, if any,
806
                    // to the end of the pattern string.
807
                    $str = substr($pattern, $oldFirstToken);
×
808
                } else {
809
                    // Get the string from the end of the last special pattern,
810
                    // if any, to the start of this special pattern.
811
                    if ($lastToken === 0) {
×
812
                        // Note that if the last special token was zero characters ago,
813
                        // there will be nothing to process so we can skip this bit.
814
                        // This happens if you have something like: EOL... in your pattern.
815
                        $str = '';
×
816
                    } else {
817
                        $str = substr($pattern, $oldFirstToken, $lastToken);
×
818
                    }
819
                }
820

821
                if ($str !== '') {
×
822
                    $tokenPatterns = $this->createTokenPattern($str);
×
823
                    foreach ($tokenPatterns as $tokenPattern) {
×
824
                        $patterns[] = $tokenPattern;
×
825
                    }
826
                }
827

828
                // Make sure we don't skip the last token.
829
                if ($isLastChar === false && $i === ($length - 1)) {
×
830
                    $i--;
×
831
                }
832
            }//end if
833

834
            // Add the skip pattern *after* we have processed
835
            // all the tokens from the end of the last skip pattern
836
            // to the start of this skip pattern.
837
            if ($specialPattern !== false) {
×
838
                $patterns[] = $specialPattern;
×
839
            }
840
        }//end for
841

842
        return $patterns;
×
843

844
    }//end parse()
845

846

847
    /**
848
     * Creates a skip pattern.
849
     *
850
     * @param string $pattern The pattern being parsed.
851
     * @param int    $from    The token position that the skip pattern starts from.
852
     *
853
     * @return array The pattern step.
854
     * @see    createTokenPattern()
855
     * @see    parse()
856
     */
857
    private function createSkipPattern($pattern, $from)
×
858
    {
859
        $skip = ['type' => 'skip'];
×
860

861
        $nestedParenthesis = 0;
×
862
        $nestedBraces      = 0;
×
863
        for ($start = $from; $start >= 0; $start--) {
×
864
            switch ($pattern[$start]) {
×
865
            case '(':
×
866
                if ($nestedParenthesis === 0) {
×
867
                    $skip['to'] = 'parenthesis_closer';
×
868
                }
869

870
                $nestedParenthesis--;
×
871
                break;
×
872
            case '{':
×
873
                if ($nestedBraces === 0) {
×
874
                    $skip['to'] = 'scope_closer';
×
875
                }
876

877
                $nestedBraces--;
×
878
                break;
×
879
            case '}':
×
880
                $nestedBraces++;
×
881
                break;
×
882
            case ')':
×
883
                $nestedParenthesis++;
×
884
                break;
×
885
            }//end switch
886

887
            if (isset($skip['to']) === true) {
×
888
                break;
×
889
            }
890
        }//end for
891

892
        if (isset($skip['to']) === false) {
×
893
            $skip['to'] = 'unknown';
×
894
        }
895

896
        return $skip;
×
897

898
    }//end createSkipPattern()
899

900

901
    /**
902
     * Creates a token pattern.
903
     *
904
     * @param string $str The tokens string that the pattern should match.
905
     *
906
     * @return array The pattern step.
907
     * @see    createSkipPattern()
908
     * @see    parse()
909
     */
910
    private function createTokenPattern($str)
×
911
    {
912
        // Don't add a space after the closing php tag as it will add a new
913
        // whitespace token.
914
        $tokenizer = new PHP('<?php '.$str.'?>', null);
×
915

916
        // Remove the <?php tag from the front and the end php tag from the back.
917
        $tokens = $tokenizer->getTokens();
×
918
        $tokens = array_slice($tokens, 1, (count($tokens) - 2));
×
919

920
        $patterns = [];
×
921
        foreach ($tokens as $patternInfo) {
×
922
            $patterns[] = [
×
923
                'type'  => 'token',
×
924
                'token' => $patternInfo['code'],
×
925
                'value' => $patternInfo['content'],
×
926
            ];
927
        }
928

929
        return $patterns;
×
930

931
    }//end createTokenPattern()
932

933

934
}//end class
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc