Completed
Push — master ( 401561...db947c )
by Wim
12s
created

NewLanguageConstructsSniff::isTCoalesceEqual()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 18
Code Lines 9

Duplication

Lines 4
Ratio 22.22 %

Importance

Changes 0
Metric Value
dl 4
loc 18
rs 8.2222
c 0
b 0
f 0
cc 7
eloc 9
nc 4
nop 2
1
<?php
2
/**
3
 * PHPCompatibility_Sniffs_PHP_NewLanguageConstructsSniff.
4
 *
5
 * PHP version 5.5
6
 *
7
 * @category  PHP
8
 * @package   PHPCompatibility
9
 * @author    Wim Godden <[email protected]>
10
 * @copyright 2013 Cu.be Solutions bvba
11
 */
12
13
/**
14
 * PHPCompatibility_Sniffs_PHP_NewLanguageConstructsSniff.
15
 *
16
 * @category  PHP
17
 * @package   PHPCompatibility
18
 * @author    Wim Godden <[email protected]>
19
 * @version   1.0.0
20
 * @copyright 2013 Cu.be Solutions bvba
21
 */
22
class PHPCompatibility_Sniffs_PHP_NewLanguageConstructsSniff extends PHPCompatibility_AbstractNewFeatureSniff
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...
23
{
24
25
    /**
26
     * A list of new language constructs, not present in older versions.
27
     *
28
     * The array lists : version number with false (not present) or true (present).
29
     * If's sufficient to list the first version where the keyword appears.
30
     *
31
     * @var array(string => array(string => int|string|null))
32
     */
33
    protected $newConstructs = array(
34
                                        'T_NS_SEPARATOR' => array(
35
                                            '5.2' => false,
36
                                            '5.3' => true,
37
                                            'description' => 'the \ operator (for namespaces)'
38
                                        ),
39
                                        'T_POW' => array(
40
                                            '5.5' => false,
41
                                            '5.6' => true,
42
                                            'description' => 'power operator (**)'
43
                                        ), // identified in PHPCS 1.5 as T_MULTIPLY + T_MULTIPLY
44
                                        'T_POW_EQUAL' => array(
45
                                            '5.5' => false,
46
                                            '5.6' => true,
47
                                            'description' => 'power assignment operator (**=)'
48
                                        ), // identified in PHPCS 1.5 as T_MULTIPLY + T_MUL_EQUAL
49
                                        'T_ELLIPSIS' => array(
50
                                            '5.5' => false,
51
                                            '5.6' => true,
52
                                            'description' => 'variadic functions using ...'
53
                                        ),
54
                                        'T_SPACESHIP' => array(
55
                                            '5.6' => false,
56
                                            '7.0' => true,
57
                                            'description' => 'spaceship operator (<=>)'
58
                                        ), // identified in PHPCS 1.5 as T_IS_SMALLER_OR_EQUAL + T_GREATER_THAN
59
                                        'T_COALESCE' => array(
60
                                            '5.6' => false,
61
                                            '7.0' => true,
62
                                            'description' => 'null coalescing operator (??)'
63
                                        ), // identified in PHPCS 1.5 as T_INLINE_THEN + T_INLINE_THEN
64
                                        'T_COALESCE_EQUAL' => array(
65
                                            '7.1' => false,
66
                                            '7.2' => true,
67
                                            'description' => 'null coalesce equal operator (??=)'
68
                                        ), // 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
69
                                    );
70
71
72
    /**
73
     * A list of new language constructs which are not recognized in PHPCS 1.x.
74
     *
75
     * The array lists an alternative token to listen for.
76
     *
77
     * @var array(string => int)
78
     */
79
    protected $newConstructsPHPCSCompat = array(
80
                                        'T_POW'            => T_MULTIPLY,
81
                                        'T_POW_EQUAL'      => T_MUL_EQUAL,
82
                                        'T_SPACESHIP'      => T_GREATER_THAN,
83
                                        'T_COALESCE'       => T_INLINE_THEN,
84
                                        'T_COALESCE_EQUAL' => T_EQUAL,
85
                                    );
86
87
    /**
88
     * Translation table for PHPCS 1.x and older 2.x tokens.
89
     *
90
     * The 'before' index lists the token which would have to be directly before the
91
     * token found for it to be one of the new language constructs.
92
     * The 'real_token' index indicates which language construct was found in that case.
93
     *
94
     * If the token combination has multi-layer complexity, such as is the case
95
     * with T_COALESCE(_EQUAL), a 'callback' index is added instead pointing to a
96
	 * separate function which can determine whether this is the targetted token across
97
	 * PHP and PHPCS versions.
98
     *
99
     * {@internal 'before' was chosen rather than 'after' as that allowed for a 1-on-1
100
     * translation list with the current tokens.}}
101
     *
102
     * @var array(string => array(string => string))
103
     */
104
    protected $PHPCSCompatTranslate = array(
105
                                        'T_MULTIPLY' => array(
106
                                            'before' => 'T_MULTIPLY',
107
                                            'real_token' => 'T_POW',
108
                                        ),
109
                                        'T_MUL_EQUAL' => array(
110
                                            'before' => 'T_MULTIPLY',
111
                                            'real_token' => 'T_POW_EQUAL',
112
                                        ),
113
                                        'T_GREATER_THAN' => array(
114
                                            'before' => 'T_IS_SMALLER_OR_EQUAL',
115
                                            'real_token' => 'T_SPACESHIP',
116
                                        ),
117
                                        'T_INLINE_THEN' => array(
118
                                            'callback' => 'isTCoalesce',
119
                                            'real_token' => 'T_COALESCE',
120
                                        ),
121
                                        'T_EQUAL' => array(
122
                                            'callback' => 'isTCoalesceEqual',
123
                                            'real_token' => 'T_COALESCE_EQUAL',
124
                                        ),
125
                                    );
126
127
    /**
128
     * Returns an array of tokens this test wants to listen for.
129
     *
130
     * @return array
131
     */
132
    public function register()
133
    {
134
        $tokens = array();
135
        foreach ($this->newConstructs as $token => $versions) {
136
            if (defined($token)) {
137
                $tokens[] = constant($token);
138
            }
139
            else if(isset($this->newConstructsPHPCSCompat[$token])) {
140
                $tokens[] = $this->newConstructsPHPCSCompat[$token];
141
            }
142
        }
143
        return $tokens;
144
    }//end register()
145
146
147
    /**
148
     * Processes this test, when one of its tokens is encountered.
149
     *
150
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
151
     * @param int                  $stackPtr  The position of the current token in
152
     *                                        the stack passed in $tokens.
153
     *
154
     * @return void
155
     */
156
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
157
    {
158
        $tokens    = $phpcsFile->getTokens();
159
        $tokenType = $tokens[$stackPtr]['type'];
160
161
        // Translate older PHPCS token combis for new constructs to the actual construct.
162
        if (isset($this->newConstructs[$tokenType]) === false) {
163
            if (
164
                isset($this->PHPCSCompatTranslate[$tokenType])
165
                &&
166
                ((isset($this->PHPCSCompatTranslate[$tokenType]['before'])
167
                    && $tokens[$stackPtr - 1]['type'] === $this->PHPCSCompatTranslate[$tokenType]['before'])
168
                ||
169
                (isset($this->PHPCSCompatTranslate[$tokenType]['callback'])
170
                    && call_user_func(array($this, $this->PHPCSCompatTranslate[$tokenType]['callback']), $tokens, $stackPtr) === true))
171
            ) {
172
                $tokenType = $this->PHPCSCompatTranslate[$tokenType]['real_token'];
173
            }
174
        } elseif ($tokenType === 'T_COALESCE') {
175
            // Make sure that T_COALESCE is not confused with T_COALESCE_EQUAL.
176
            if (isset($tokens[($stackPtr + 1)]) !== false && $tokens[($stackPtr + 1)]['code'] === T_EQUAL) {
177
                // Ignore as will be dealt with via the T_EQUAL token.
178
                return;
179
            }
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) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
299
                return true;
300
            }
301
        }
302
303
        return false;
304
    }
305
306
}//end class
307