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

NewKeywordsSniff::process()   D

Complexity

Conditions 16
Paths 31

Size

Total Lines 82
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 82
rs 4.9422
c 0
b 0
f 0
cc 16
eloc 33
nc 31
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\NewKeywordsSniff.
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\NewKeywordsSniff.
17
 *
18
 * @category  PHP
19
 * @package   PHPCompatibility
20
 * @author    Wim Godden <[email protected]>
21
 * @copyright 2013 Cu.be Solutions bvba
22
 */
23
class NewKeywordsSniff extends AbstractNewFeatureSniff
24
{
25
26
    /**
27
     * A list of new keywords, 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 last version which did not contain the keyword.
31
     *
32
     * Description will be used as part of the error message.
33
     * Condition is the name of a callback method within this class or the parent class
34
     * which checks whether the token complies with a certain condition.
35
     * The callback function will be passed the $phpcsFile and the $stackPtr.
36
     * The callback function should return `true` if the condition is met and the
37
     * error should *not* be thrown.
38
     *
39
     * @var array(string => array(string => int|string|null))
40
     */
41
    protected $newKeywords = array(
42
        'T_HALT_COMPILER' => array(
43
            '5.0' => false,
44
            '5.1' => true,
45
            'description' => '"__halt_compiler" keyword',
46
        ),
47
        'T_CONST' => array(
48
            '5.2' => false,
49
            '5.3' => true,
50
            'description' => '"const" keyword',
51
            'condition' => 'isClassConstant', // Keyword is only new when not in class context.
52
        ),
53
        'T_CALLABLE' => array(
54
            '5.3' => false,
55
            '5.4' => true,
56
            'description' => '"callable" keyword',
57
            'content' => 'callable',
58
        ),
59
        'T_DIR' => array(
60
            '5.2' => false,
61
            '5.3' => true,
62
            'description' => '__DIR__ magic constant',
63
            'content' => '__DIR__',
64
        ),
65
        'T_GOTO' => array(
66
            '5.2' => false,
67
            '5.3' => true,
68
            'description' => '"goto" keyword',
69
            'content' => 'goto',
70
        ),
71
        'T_INSTEADOF' => array(
72
            '5.3' => false,
73
            '5.4' => true,
74
            'description' => '"insteadof" keyword (for traits)',
75
            'content' => 'insteadof',
76
        ),
77
        'T_NAMESPACE' => array(
78
            '5.2' => false,
79
            '5.3' => true,
80
            'description' => '"namespace" keyword',
81
            'content' => 'namespace',
82
        ),
83
        'T_NS_C' => array(
84
            '5.2' => false,
85
            '5.3' => true,
86
            'description' => '__NAMESPACE__ magic constant',
87
            'content' => '__NAMESPACE__',
88
        ),
89
        'T_USE' => array(
90
            '5.2' => false,
91
            '5.3' => true,
92
            'description' => '"use" keyword (for traits/namespaces/anonymous functions)',
93
        ),
94
        'T_TRAIT' => array(
95
            '5.3' => false,
96
            '5.4' => true,
97
            'description' => '"trait" keyword',
98
            'content' => 'trait',
99
        ),
100
        'T_TRAIT_C' => array(
101
            '5.3' => false,
102
            '5.4' => true,
103
            'description' => '__TRAIT__ magic constant',
104
            'content' => '__TRAIT__',
105
        ),
106
        // The specifics for distinguishing between 'yield' and 'yield from' are dealt
107
        // with in the translation logic.
108
        // This token has to be placed above the `T_YIELD` token in this array to allow for this.
109
        'T_YIELD_FROM' => array(
110
            '5.6' => false,
111
            '7.0' => true,
112
            'description' => '"yield from" keyword (for generators)',
113
            'content' => 'yield',
114
        ),
115
        'T_YIELD' => array(
116
            '5.4' => false,
117
            '5.5' => true,
118
            'description' => '"yield" keyword (for generators)',
119
            'content' => 'yield',
120
        ),
121
        'T_FINALLY' => array(
122
            '5.4' => false,
123
            '5.5' => true,
124
            'description' => '"finally" keyword (in exception handling)',
125
            'content' => 'finally',
126
        ),
127
    );
128
129
    /**
130
     * Translation table for T_STRING tokens.
131
     *
132
     * Will be set up from the register() method.
133
     *
134
     * @var array(string => string)
135
     */
136
    protected $translateContentToToken = array();
137
138
139
    /**
140
     * Returns an array of tokens this test wants to listen for.
141
     *
142
     * @return array
143
     */
144
    public function register()
145
    {
146
        $tokens    = array();
147
        $translate = array();
148
        foreach ($this->newKeywords as $token => $versions) {
149
            if (defined($token)) {
150
                $tokens[] = constant($token);
151
            }
152
            if (isset($versions['content'])) {
153
                $translate[$versions['content']] = $token;
154
            }
155
        }
156
157
        /*
158
         * Deal with tokens not recognized by the PHP version the sniffer is run
159
         * under and (not correctly) compensated for by PHPCS.
160
         */
161
        if (empty($translate) === false) {
162
            $this->translateContentToToken = $translate;
163
            $tokens[] = T_STRING;
164
        }
165
166
        return $tokens;
167
168
    }//end register()
169
170
171
    /**
172
     * Processes this test, when one of its tokens is encountered.
173
     *
174
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
175
     * @param int                   $stackPtr  The position of the current token in
176
     *                                         the stack passed in $tokens.
177
     *
178
     * @return void
179
     */
180
    public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
181
    {
182
        $tokens    = $phpcsFile->getTokens();
183
        $tokenType = $tokens[$stackPtr]['type'];
184
185
        // Allow for dealing with multi-token keywords, like "yield from".
186
        $end = $stackPtr;
187
188
        // Translate T_STRING token if necessary.
189
        if ($tokens[$stackPtr]['type'] === 'T_STRING') {
190
            $content = $tokens[$stackPtr]['content'];
191
            if (isset($this->translateContentToToken[$content]) === false) {
192
                // Not one of the tokens we're looking for.
193
                return;
194
            }
195
196
            $tokenType = $this->translateContentToToken[$content];
197
        }
198
199
        /*
200
         * Special case: distinguish between `yield` and `yield from`.
201
         *
202
         * PHPCS currently (at least up to v 3.0.1) does not backfill for the
203
         * `yield` nor the `yield from` keywords.
204
         * See: https://github.com/squizlabs/PHP_CodeSniffer/issues/1524
205
         *
206
         * In PHP < 5.5, both `yield` as well as `from` are tokenized as T_STRING.
207
         * In PHP 5.5 - 5.6, `yield` is tokenized as T_YIELD and `from` as T_STRING,
208
         * but the `T_YIELD_FROM` token *is* defined in PHP.
209
         * In PHP 7.0+ both are tokenized as their respective token, however,
210
         * a multi-line "yield from" is tokenized as two tokens.
211
         */
212
        if ($tokenType === 'T_YIELD') {
213
            $nextToken = $phpcsFile->findNext(T_WHITESPACE, ($end + 1), null, true);
214
            if ($tokens[$nextToken]['code'] === T_STRING
215
                && $tokens[$nextToken]['content'] === 'from'
216
            ) {
217
                $tokenType = 'T_YIELD_FROM';
218
                $end       = $nextToken;
219
            }
220
            unset($nextToken);
221
        }
222
223
        if ($tokenType === 'T_YIELD_FROM' && $tokens[($stackPtr - 1)]['type'] === 'T_YIELD_FROM') {
224
            // Multi-line "yield from", no need to report it twice.
225
            return;
226
        }
227
228
        if (isset($this->newKeywords[$tokenType]) === false) {
229
            return;
230
        }
231
232
        $nextToken = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($end + 1), null, true);
233
        $prevToken = $phpcsFile->findPrevious(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true);
234
235
236
        // Skip attempts to use keywords as functions or class names - the former
237
        // will be reported by ForbiddenNamesAsInvokedFunctionsSniff, whilst the
238
        // latter will be (partially) reported by the ForbiddenNames sniff.
239
        // Either type will result in false-positives when targetting lower versions
240
        // of PHP where the name was not reserved, unless we explicitly check for
241
        // them.
242
        if (($nextToken === false
243
                || $tokens[$nextToken]['type'] !== 'T_OPEN_PARENTHESIS')
244
            && ($prevToken === false
245
                || $tokens[$prevToken]['type'] !== 'T_CLASS'
246
                || $tokens[$prevToken]['type'] !== 'T_INTERFACE')
247
        ) {
248
            // Skip based on token scope condition.
249
            if (isset($this->newKeywords[$tokenType]['condition'])
250
                && call_user_func(array($this, $this->newKeywords[$tokenType]['condition']), $phpcsFile, $stackPtr) === true
251
            ) {
252
                return;
253
            }
254
255
            $itemInfo = array(
256
                'name'   => $tokenType,
257
            );
258
            $this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
259
        }
260
261
    }//end process()
262
263
264
    /**
265
     * Get the relevant sub-array for a specific item from a multi-dimensional array.
266
     *
267
     * @param array $itemInfo Base information about the item.
268
     *
269
     * @return array Version and other information about the item.
270
     */
271
    public function getItemArray(array $itemInfo)
272
    {
273
        return $this->newKeywords[$itemInfo['name']];
274
    }
275
276
277
    /**
278
     * Get an array of the non-PHP-version array keys used in a sub-array.
279
     *
280
     * @return array
281
     */
282
    protected function getNonVersionArrayKeys()
283
    {
284
        return array(
285
            'description',
286
            'condition',
287
            'content',
288
        );
289
    }
290
291
292
    /**
293
     * Retrieve the relevant detail (version) information for use in an error message.
294
     *
295
     * @param array $itemArray Version and other information about the item.
296
     * @param array $itemInfo  Base information about the item.
297
     *
298
     * @return array
299
     */
300
    public function getErrorInfo(array $itemArray, array $itemInfo)
301
    {
302
        $errorInfo = parent::getErrorInfo($itemArray, $itemInfo);
303
        $errorInfo['description'] = $itemArray['description'];
304
305
        return $errorInfo;
306
307
    }
308
309
310
    /**
311
     * Allow for concrete child classes to filter the error data before it's passed to PHPCS.
312
     *
313
     * @param array $data      The error data array which was created.
314
     * @param array $itemInfo  Base information about the item this error message applied to.
315
     * @param array $errorInfo Detail information about an item this error message applied to.
316
     *
317
     * @return array
318
     */
319
    protected function filterErrorData(array $data, array $itemInfo, array $errorInfo)
320
    {
321
        $data[0] = $errorInfo['description'];
322
        return $data;
323
    }
324
325
326
}//end class
327