NewLanguageConstructsSniff   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 284
Duplicated Lines 3.87 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 32
c 0
b 0
f 0
lcom 1
cbo 1
dl 11
loc 284
rs 9.84

8 Methods

Rating   Name   Duplication   Size   Complexity  
A register() 0 12 4
B process() 0 34 11
A getItemArray() 0 4 1
A getNonVersionArrayKeys() 0 4 1
A getErrorInfo() 0 8 1
A filterErrorData() 0 5 1
B isTCoalesceEqual() 4 18 7
A isTCoalesce() 7 16 6

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * \PHPCompatibility\Sniffs\PHP\NewLanguageConstructsSniff.
4
 *
5
 * @category  PHP
6
 * @package   PHPCompatibility
7
 * @author    Wim Godden <[email protected]>
8
 * @copyright 2013 Cu.be Solutions bvba
9
 */
10
11
namespace PHPCompatibility\Sniffs\PHP;
12
13
use PHPCompatibility\AbstractNewFeatureSniff;
14
15
/**
16
 * \PHPCompatibility\Sniffs\PHP\NewLanguageConstructsSniff.
17
 *
18
 * @category  PHP
19
 * @package   PHPCompatibility
20
 * @author    Wim Godden <[email protected]>
21
 * @copyright 2013 Cu.be Solutions bvba
22
 */
23
class NewLanguageConstructsSniff extends AbstractNewFeatureSniff
24
{
25
26
    /**
27
     * A list of new language constructs, not present in older versions.
28
     *
29
     * The array lists : version number with false (not present) or true (present).
30
     * If's sufficient to list the first version where the keyword appears.
31
     *
32
     * @var array(string => array(string => int|string|null))
33
     */
34
    protected $newConstructs = array(
35
        'T_NS_SEPARATOR' => array(
36
            '5.2' => false,
37
            '5.3' => true,
38
            'description' => 'the \ operator (for namespaces)',
39
        ),
40
        'T_POW' => array(
41
            '5.5' => false,
42
            '5.6' => true,
43
            'description' => 'power operator (**)',
44
        ), // Identified in PHPCS 1.5 as T_MULTIPLY + T_MULTIPLY.
45
        'T_POW_EQUAL' => array(
46
            '5.5' => false,
47
            '5.6' => true,
48
            'description' => 'power assignment operator (**=)',
49
        ), // Identified in PHPCS 1.5 as T_MULTIPLY + T_MUL_EQUAL.
50
        'T_ELLIPSIS' => array(
51
            '5.5' => false,
52
            '5.6' => true,
53
            'description' => 'variadic functions using ...',
54
        ),
55
        'T_SPACESHIP' => array(
56
            '5.6' => false,
57
            '7.0' => true,
58
            'description' => 'spaceship operator (<=>)',
59
        ), // Identified in PHPCS 1.5 as T_IS_SMALLER_OR_EQUAL + T_GREATER_THAN.
60
        'T_COALESCE' => array(
61
            '5.6' => false,
62
            '7.0' => true,
63
            'description' => 'null coalescing operator (??)',
64
        ), // Identified in PHPCS 1.5 as T_INLINE_THEN + T_INLINE_THEN.
65
        /*
66
         * Was slated for 7.2, but still not implemented. PHPCS however does already tokenize it.
67
         * @link https://wiki.php.net/rfc/null_coalesce_equal_operator
68
         */
69
        'T_COALESCE_EQUAL' => array(
70
            '7.2' => false,
71
            '7.3' => true,
72
            'description' => 'null coalesce equal operator (??=)',
73
        ), // Identified in PHPCS 1.5 as T_INLINE_THEN + T_INLINE_THEN + T_EQUAL and pre-PHPCS 2.8.1 as T_COALESCE + T_EQUAL.
74
    );
75
76
77
    /**
78
     * A list of new language constructs which are not recognized in PHPCS 1.x.
79
     *
80
     * The array lists an alternative token to listen for.
81
     *
82
     * @var array(string => int)
83
     */
84
    protected $newConstructsPHPCSCompat = array(
85
        'T_POW'            => T_MULTIPLY,
86
        'T_POW_EQUAL'      => T_MUL_EQUAL,
87
        'T_SPACESHIP'      => T_GREATER_THAN,
88
        'T_COALESCE'       => T_INLINE_THEN,
89
        'T_COALESCE_EQUAL' => T_EQUAL,
90
    );
91
92
    /**
93
     * Translation table for PHPCS 1.x and older 2.x tokens.
94
     *
95
     * The 'before' index lists the token which would have to be directly before the
96
     * token found for it to be one of the new language constructs.
97
     * The 'real_token' index indicates which language construct was found in that case.
98
     *
99
     * If the token combination has multi-layer complexity, such as is the case
100
     * with T_COALESCE(_EQUAL), a 'callback' index is added instead pointing to a
101
     * separate function which can determine whether this is the targetted token across
102
     * PHP and PHPCS versions.
103
     *
104
     * {@internal 'before' was chosen rather than 'after' as that allowed for a 1-on-1
105
     * translation list with the current tokens.}}
106
     *
107
     * @var array(string => array(string => string))
108
     */
109
    protected $PHPCSCompatTranslate = array(
110
        'T_MULTIPLY' => array(
111
            'before'     => 'T_MULTIPLY',
112
            'real_token' => 'T_POW',
113
        ),
114
        'T_MUL_EQUAL' => array(
115
            'before'     => 'T_MULTIPLY',
116
            'real_token' => 'T_POW_EQUAL',
117
        ),
118
        'T_GREATER_THAN' => array(
119
            'before'     => 'T_IS_SMALLER_OR_EQUAL',
120
            'real_token' => 'T_SPACESHIP',
121
        ),
122
        'T_INLINE_THEN' => array(
123
            'callback'   => 'isTCoalesce',
124
            'real_token' => 'T_COALESCE',
125
        ),
126
        'T_EQUAL' => array(
127
            'callback'   => 'isTCoalesceEqual',
128
            'real_token' => 'T_COALESCE_EQUAL',
129
        ),
130
    );
131
132
    /**
133
     * Returns an array of tokens this test wants to listen for.
134
     *
135
     * @return array
136
     */
137
    public function register()
138
    {
139
        $tokens = array();
140
        foreach ($this->newConstructs as $token => $versions) {
141
            if (defined($token)) {
142
                $tokens[] = constant($token);
143
            } elseif (isset($this->newConstructsPHPCSCompat[$token])) {
144
                $tokens[] = $this->newConstructsPHPCSCompat[$token];
145
            }
146
        }
147
        return $tokens;
148
    }//end register()
149
150
151
    /**
152
     * Processes this test, when one of its tokens is encountered.
153
     *
154
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
155
     * @param int                   $stackPtr  The position of the current token in
156
     *                                         the stack passed in $tokens.
157
     *
158
     * @return void
159
     */
160
    public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
161
    {
162
        $tokens    = $phpcsFile->getTokens();
163
        $tokenType = $tokens[$stackPtr]['type'];
164
165
        // Translate older PHPCS token combis for new constructs to the actual construct.
166
        if (isset($this->newConstructs[$tokenType]) === false) {
167
            if (isset($this->PHPCSCompatTranslate[$tokenType])
168
                && ((isset($this->PHPCSCompatTranslate[$tokenType]['before'], $tokens[$stackPtr - 1]) === true
169
                    && $tokens[$stackPtr - 1]['type'] === $this->PHPCSCompatTranslate[$tokenType]['before'])
170
                || (isset($this->PHPCSCompatTranslate[$tokenType]['callback']) === true
171
                    && call_user_func(array($this, $this->PHPCSCompatTranslate[$tokenType]['callback']), $tokens, $stackPtr) === true))
172
            ) {
173
                $tokenType = $this->PHPCSCompatTranslate[$tokenType]['real_token'];
174
            }
175
        } elseif ($tokenType === 'T_COALESCE') {
176
            // Make sure that T_COALESCE is not confused with T_COALESCE_EQUAL.
177
            if (isset($tokens[($stackPtr + 1)]) !== false && $tokens[($stackPtr + 1)]['code'] === T_EQUAL) {
178
                // Ignore as will be dealt with via the T_EQUAL token.
179
                return;
180
            }
181
        }
182
183
        // If the translation did not yield one of the tokens we are looking for, bow out.
184
        if (isset($this->newConstructs[$tokenType]) === false) {
185
            return;
186
        }
187
188
        $itemInfo = array(
189
            'name'   => $tokenType,
190
        );
191
        $this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
192
193
    }//end process()
194
195
196
    /**
197
     * Get the relevant sub-array for a specific item from a multi-dimensional array.
198
     *
199
     * @param array $itemInfo Base information about the item.
200
     *
201
     * @return array Version and other information about the item.
202
     */
203
    public function getItemArray(array $itemInfo)
204
    {
205
        return $this->newConstructs[$itemInfo['name']];
206
    }
207
208
209
    /**
210
     * Get an array of the non-PHP-version array keys used in a sub-array.
211
     *
212
     * @return array
213
     */
214
    protected function getNonVersionArrayKeys()
215
    {
216
        return array('description');
217
    }
218
219
220
    /**
221
     * Retrieve the relevant detail (version) information for use in an error message.
222
     *
223
     * @param array $itemArray Version and other information about the item.
224
     * @param array $itemInfo  Base information about the item.
225
     *
226
     * @return array
227
     */
228
    public function getErrorInfo(array $itemArray, array $itemInfo)
229
    {
230
        $errorInfo = parent::getErrorInfo($itemArray, $itemInfo);
231
        $errorInfo['description'] = $itemArray['description'];
232
233
        return $errorInfo;
234
235
    }
236
237
238
    /**
239
     * Allow for concrete child classes to filter the error data before it's passed to PHPCS.
240
     *
241
     * @param array $data      The error data array which was created.
242
     * @param array $itemInfo  Base information about the item this error message applied to.
243
     * @param array $errorInfo Detail information about an item this error message applied to.
244
     *
245
     * @return array
246
     */
247
    protected function filterErrorData(array $data, array $itemInfo, array $errorInfo)
248
    {
249
        $data[0] = $errorInfo['description'];
250
        return $data;
251
    }
252
253
254
    /**
255
     * Callback function to determine whether a T_EQUAL token is really a T_COALESCE_EQUAL token.
256
     *
257
     * @param array $tokens   The token stack.
258
     * @param int   $stackPtr The current position in the token stack.
259
     *
260
     * @return bool
261
     */
262
    private function isTCoalesceEqual($tokens, $stackPtr)
263
    {
264 View Code Duplication
        if ($tokens[$stackPtr]['code'] !== T_EQUAL || isset($tokens[($stackPtr - 1)]) === false) {
265
            // Function called for wrong token or token has no predecesor.
266
            return false;
267
        }
268
269
        if ($tokens[($stackPtr - 1)]['type'] === 'T_COALESCE') {
270
            return true;
271
        }
272
        if ($tokens[($stackPtr - 1)]['type'] === 'T_INLINE_THEN'
273
            && (isset($tokens[($stackPtr - 2)]) && $tokens[($stackPtr - 2)]['type'] === 'T_INLINE_THEN')
274
        ) {
275
            return true;
276
        }
277
278
        return false;
279
    }
280
281
    /**
282
     * Callback function to determine whether a T_INLINE_THEN token is really a T_COALESCE token.
283
     *
284
     * @param array $tokens   The token stack.
285
     * @param int   $stackPtr The current position in the token stack.
286
     *
287
     * @return bool
288
     */
289
    private function isTCoalesce($tokens, $stackPtr)
290
    {
291 View Code Duplication
        if ($tokens[$stackPtr]['code'] !== T_INLINE_THEN || isset($tokens[($stackPtr - 1)]) === false) {
292
            // Function called for wrong token or token has no predecesor.
293
            return false;
294
        }
295
296
        if ($tokens[($stackPtr - 1)]['code'] === T_INLINE_THEN) {
297
            // Make sure not to confuse it with the T_COALESCE_EQUAL token.
298 View Code Duplication
            if (isset($tokens[($stackPtr + 1)]) === false || $tokens[($stackPtr + 1)]['code'] !== T_EQUAL) {
299
                return true;
300
            }
301
        }
302
303
        return false;
304
    }
305
306
}//end class
307