Completed
Push — master ( 8fe12d...0e006e )
by Wim
01:56
created

PregReplaceEModifierSniff::process()   C

Complexity

Conditions 12
Paths 10

Size

Total Lines 56
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 56
rs 6.7092
c 1
b 0
f 0
cc 12
eloc 31
nc 10
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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->bowOutEarly() === true) {
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
            if ($functionName === 'preg_replace_callback_array') {
100
                // For preg_replace_callback_array(), the patterns will be in the array keys.
101
                foreach ($arrayValues as $value) {
102
                    $hasKey = $phpcsFile->findNext(T_DOUBLE_ARROW, $value['start'], ($value['end'] + 1));
103
                    if ($hasKey === false) {
104
                        continue;
105
                    }
106
107
                    $value['end'] = ($hasKey - 1);
108
                    $value['raw'] = trim($phpcsFile->getTokensAsString($value['start'], ($hasKey - $value['start'])));
109
                    $this->processRegexPattern($value, $phpcsFile, $value['end'], $functionName);
110
                }
111
112
            } else {
113
                // Otherwise, the patterns will be in the array values.
114
                foreach ($arrayValues as $value) {
115
                    $hasKey = $phpcsFile->findNext(T_DOUBLE_ARROW, $value['start'], ($value['end'] + 1));
116
                    if ($hasKey !== false) {
117
                        $value['start'] = ($hasKey + 1);
118
                        $value['raw']   = trim($phpcsFile->getTokensAsString($value['start'], (($value['end'] + 1) - $value['start'])));
119
                    }
120
121
                    $this->processRegexPattern($value, $phpcsFile, $value['end'], $functionName);
122
                }
123
            }
124
125
        } else {
126
            $this->processRegexPattern($firstParam, $phpcsFile, $stackPtr, $functionName);
127
        }
128
129
    }//end process()
130
131
132
    /**
133
     * Do a version check to determine if this sniff needs to run at all.
134
     *
135
     * @return bool
136
     */
137
    protected function bowOutEarly()
138
    {
139
        return ($this->supportsAbove('5.5') === false);
140
    }
141
142
143
    /**
144
     * Analyse a potential regex pattern for usage of the /e modifier.
145
     *
146
     * @param array                 $pattern      Array containing the start and end token
147
     *                                            pointer of the potential regex pattern and
148
     *                                            the raw string value of the pattern.
149
     * @param \PHP_CodeSniffer_File $phpcsFile    The file being scanned.
150
     * @param int                   $stackPtr     The position of the current token in the
151
     *                                            stack passed in $tokens.
152
     * @param string                $functionName The function which contained the pattern.
153
     *
154
     * @return void
155
     */
156
    protected function processRegexPattern($pattern, \PHP_CodeSniffer_File $phpcsFile, $stackPtr, $functionName)
157
    {
158
        $tokens = $phpcsFile->getTokens();
159
160
        /*
161
         * The pattern might be build up of a combination of strings, variables
162
         * and function calls. We are only concerned with the strings.
163
         */
164
        $regex = '';
165
        for ($i = $pattern['start']; $i <= $pattern['end']; $i++) {
166
            if (in_array($tokens[$i]['code'], \PHP_CodeSniffer_Tokens::$stringTokens, true) === true) {
167
                $content = $this->stripQuotes($tokens[$i]['content']);
168
                if ($tokens[$i]['code'] === T_DOUBLE_QUOTED_STRING) {
169
                    $content = $this->stripVariables($content);
170
                }
171
172
                $regex .= trim($content);
173
            }
174
        }
175
176
        // Deal with multi-line regexes which were broken up in several string tokens.
177
        if ($tokens[$pattern['start']]['line'] !== $tokens[$pattern['end']]['line']) {
178
            $regex = $this->stripQuotes($regex);
179
        }
180
181
        if ($regex === '') {
182
            // No string token found in the first parameter, so skip it (e.g. if variable passed in).
183
            return;
184
        }
185
186
        $regexFirstChar = substr($regex, 0, 1);
187
188
        // Make sure that the character identified as the delimiter is valid.
189
        // Otherwise, it is a false positive caused by the string concatenation.
190
        if (preg_match('`[a-z0-9\\\\ ]`i', $regexFirstChar) === 1) {
191
            return;
192
        }
193
194
        if (isset($this->doublesSeparators[$regexFirstChar])) {
195
            $regexEndPos = strrpos($regex, $this->doublesSeparators[$regexFirstChar]);
196
        } else {
197
            $regexEndPos = strrpos($regex, $regexFirstChar);
198
        }
199
200
        if ($regexEndPos !== false) {
201
            $modifiers = substr($regex, $regexEndPos + 1);
202
            $this->examineModifiers($phpcsFile, $stackPtr, $functionName, $modifiers);
203
        }
204
    }//end processRegexPattern()
205
206
207
    /**
208
     * Examine the regex modifier string.
209
     *
210
     * @param \PHP_CodeSniffer_File $phpcsFile    The file being scanned.
211
     * @param int                   $stackPtr     The position of the current token in the
212
     *                                            stack passed in $tokens.
213
     * @param string                $functionName The function which contained the pattern.
214
     * @param string                $modifiers    The regex modifiers found.
215
     *
216
     * @return void
217
     */
218
    protected function examineModifiers(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, $functionName, $modifiers)
219
    {
220
        if (strpos($modifiers, 'e') !== false) {
221
            $error     = '%s() - /e modifier is deprecated since PHP 5.5';
222
            $isError   = false;
223
            $errorCode = 'Deprecated';
224
            $data      = array($functionName);
225
226
            if ($this->supportsAbove('7.0')) {
227
                $error    .= ' and removed since PHP 7.0';
228
                $isError   = true;
229
                $errorCode = 'Removed';
230
            }
231
232
            $this->addMessage($phpcsFile, $error, $stackPtr, $isError, $errorCode, $data);
233
        }
234
    }
235
236
}//end class
237