Failed Conditions
Branch test-scrutinizer-coverage (7e25b2)
by Wim
13:51 queued 10:07
created

PregReplaceEModifierSniff::process()   D

Complexity

Conditions 9
Paths 7

Size

Total Lines 40
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 9.0076

Importance

Changes 0
Metric Value
dl 0
loc 40
ccs 21
cts 22
cp 0.9545
rs 4.909
c 0
b 0
f 0
cc 9
eloc 22
nc 7
nop 2
crap 9.0076
1
<?php
2
/**
3
 * PHPCompatibility_Sniffs_PHP_PregReplaceEModifierSniff.
4
 *
5
 * PHP version 5.5
6
 *
7
 * @category  PHP
8
 * @package   PHPCompatibility
9
 * @author    Wim Godden <[email protected]>
10
 * @copyright 2014 Cu.be Solutions bvba
11
 */
12
13
/**
14
 * PHPCompatibility_Sniffs_PHP_PregReplaceEModifierSniff.
15
 *
16
 * Check for usage of the `e` modifier with PCRE functions which is deprecated since PHP 5.5
17
 * and removed as of PHP 7.0.
18
 *
19
 * PHP version 5.5
20
 *
21
 * @category  PHP
22
 * @package   PHPCompatibility
23
 * @author    Wim Godden <[email protected]>
24
 * @copyright 2014 Cu.be Solutions bvba
25
 */
26
class PHPCompatibility_Sniffs_PHP_PregReplaceEModifierSniff extends PHPCompatibility_Sniff
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
27
{
28
29
    /**
30
     * Functions to check for.
31
     *
32
     * @var array
33
     */
34
    protected $functions = array(
35
        'preg_replace' => true,
36
        'preg_filter'  => true,
37
    );
38
39
    /**
40
     * Regex bracket delimiters.
41
     *
42
     * @var array
43
     */
44
    protected $doublesSeparators = array(
45
        '{' => '}',
46
        '[' => ']',
47
        '(' => ')',
48
        '<' => '>',
49
    );
50
51
    /**
52
     * Returns an array of tokens this test wants to listen for.
53
     *
54
     * @return array
55
     */
56 66
    public function register()
57
    {
58 66
        return array(T_STRING);
59
    }//end register()
60
61
    /**
62
     * Processes this test, when one of its tokens is encountered.
63
     *
64
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
65
     * @param int                  $stackPtr  The position of the current token in the
66
     *                                        stack passed in $tokens.
67
     *
68
     * @return void
69
     */
70 2
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
71
    {
72 2
        if ($this->supportsAbove('5.5') === false) {
73 1
            return;
74
        }
75
76 1
        $tokens         = $phpcsFile->getTokens();
77 1
        $functionName   = $tokens[$stackPtr]['content'];
78 1
        $functionNameLc = strtolower($functionName);
79
80
        // Bow out if not one of the functions we're targetting.
81 1
        if (isset($this->functions[$functionNameLc]) === false) {
82 1
            return;
83
        }
84
85
        // Get the first parameter in the function call as that should contain the regex(es).
86 1
        $firstParam = $this->getFunctionCallParameter($phpcsFile, $stackPtr, 1);
87 1
        if ($firstParam === false) {
88
            return;
89
        }
90
91
        // Differentiate between an array of patterns passed and a single pattern.
92 1
        $nextNonEmpty = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, $firstParam['start'], ($firstParam['end'] +1), true);
93 1
        if ($nextNonEmpty !== false && ($tokens[$nextNonEmpty]['code'] === T_ARRAY || $tokens[$nextNonEmpty]['code'] === T_OPEN_SHORT_ARRAY)) {
94 1
            $arrayValues = $this->getFunctionCallParameters($phpcsFile, $nextNonEmpty);
95 1
            foreach ($arrayValues as $value) {
96 1
                $hasKey = $phpcsFile->findNext(T_DOUBLE_ARROW, $value['start'], ($value['end'] + 1));
97 1
                if ($hasKey !== false) {
98 1
                    $value['start'] = ($hasKey + 1);
99 1
                    $value['raw']   = trim($phpcsFile->getTokensAsString($value['start'], (($value['end'] + 1) - $value['start'])));
100
                }
101
102 1
                $this->processRegexPattern($value, $phpcsFile, $value['end'], $functionName);
103
            }
104
105
        } else {
106 1
            $this->processRegexPattern($firstParam, $phpcsFile, $stackPtr, $functionName);
107
        }
108
109 1
    }//end process()
110
111
112
    /**
113
     * Analyse a potential regex pattern for usage of the /e modifier.
114
     *
115
     * @param array                $pattern      Array containing the start and end token
116
     *                                           pointer of the potential regex pattern and
117
     *                                           the raw string value of the pattern.
118
     * @param PHP_CodeSniffer_File $phpcsFile    The file being scanned.
119
     * @param int                  $stackPtr     The position of the current token in the
120
     *                                           stack passed in $tokens.
121
     * @param string               $functionName The function which contained the pattern.
122
     *
123
     * @return void
124
     */
125 1
    protected function processRegexPattern($pattern, $phpcsFile, $stackPtr, $functionName)
126
    {
127 1
        $tokens = $phpcsFile->getTokens();
128
129
        /*
130
         * The pattern might be build up of a combination of strings, variables
131
         * and function calls. We are only concerned with the strings.
132
         */
133 1
        $regex = '';
134 1
        for ($i = $pattern['start']; $i <= $pattern['end']; $i++) {
135 1
            if (in_array($tokens[$i]['code'], PHP_CodeSniffer_Tokens::$stringTokens, true) === true) {
136 1
                $content = $this->stripQuotes($tokens[$i]['content']);
137 1
                if ($tokens[$i]['code'] === T_DOUBLE_QUOTED_STRING) {
138 1
                    $content = $this->stripVariables($content);
139
                }
140
141 1
                $regex .= trim($content);
142
            }
143
        }
144
145
        // Deal with multi-line regexes which were broken up in several string tokens.
146 1
        if ($tokens[$pattern['start']]['line'] !== $tokens[$pattern['end']]['line']) {
147 1
            $regex = $this->stripQuotes($regex);
148
        }
149
150 1
        if ($regex === '') {
151
            // No string token found in the first parameter, so skip it (e.g. if variable passed in).
152 1
            return;
153
        }
154
155 1
        $regexFirstChar = substr($regex, 0, 1);
156
157
        // Make sure that the character identified as the delimiter is valid.
158
        // Otherwise, it is a false positive caused by the string concatenation.
159 1
        if (preg_match('`[a-z0-9\\\\ ]`i', $regexFirstChar) === 1) {
160 1
            return;
161
        }
162
163 1
        if (isset($this->doublesSeparators[$regexFirstChar])) {
164 1
            $regexEndPos = strrpos($regex, $this->doublesSeparators[$regexFirstChar]);
165
        } else {
166 1
            $regexEndPos = strrpos($regex, $regexFirstChar);
167
        }
168
169 1
        if ($regexEndPos) {
170 1
            $modifiers = substr($regex, $regexEndPos + 1);
171
172 1
            if (strpos($modifiers, 'e') !== false) {
173 1
                $error     = '%s() - /e modifier is deprecated since PHP 5.5';
174 1
                $isError   = false;
175 1
                $errorCode = 'Deprecated';
176 1
                $data      = array($functionName);
177
178 1
                if ($this->supportsAbove('7.0')) {
179 1
                    $error    .= ' and removed since PHP 7.0';
180 1
                    $isError   = true;
181 1
                    $errorCode = 'Removed';
182
                }
183
184 1
                $this->addMessage($phpcsFile, $error, $stackPtr, $isError, $errorCode, $data);
185
            }
186
        }
187 1
    }//end processRegexPattern()
188
189
}//end class
190