Completed
Push — feature/rename-phpunit-config ( 357a24...3649e1 )
by Juliette
03:31 queued 01:25
created

PregReplaceEModifierSniff::process()   D

Complexity

Conditions 9
Paths 7

Size

Total Lines 40
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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