ForbiddenNamesSniff::processNonString()   F
last analyzed

Complexity

Conditions 34
Paths 35

Size

Total Lines 131

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 131
rs 3.3333
cc 34
nc 35
nop 3

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\ForbiddenNamesSniff.
4
 *
5
 * @category  PHP
6
 * @package   PHPCompatibility
7
 * @author    Wim Godden <[email protected]>
8
 * @copyright 2012 Cu.be Solutions bvba
9
 */
10
11
namespace PHPCompatibility\Sniffs\PHP;
12
13
use PHPCompatibility\Sniff;
14
use PHPCompatibility\PHPCSHelper;
15
16
/**
17
 * \PHPCompatibility\Sniffs\PHP\ForbiddenNamesSniff.
18
 *
19
 * Prohibits the use of reserved keywords as class, function, namespace or constant names.
20
 *
21
 * @category  PHP
22
 * @package   PHPCompatibility
23
 * @author    Wim Godden <[email protected]>
24
 * @copyright 2012 Cu.be Solutions bvba
25
 */
26
class ForbiddenNamesSniff extends Sniff
27
{
28
29
    /**
30
     * A list of keywords that can not be used as function, class and namespace name or constant name.
31
     * Mentions since which version it's not allowed.
32
     *
33
     * @var array(string => string)
34
     */
35
    protected $invalidNames = array(
36
        'abstract'      => '5.0',
37
        'and'           => 'all',
38
        'array'         => 'all',
39
        'as'            => 'all',
40
        'break'         => 'all',
41
        'callable'      => '5.4',
42
        'case'          => 'all',
43
        'catch'         => '5.0',
44
        'class'         => 'all',
45
        'clone'         => '5.0',
46
        'const'         => 'all',
47
        'continue'      => 'all',
48
        'declare'       => 'all',
49
        'default'       => 'all',
50
        'do'            => 'all',
51
        'else'          => 'all',
52
        'elseif'        => 'all',
53
        'enddeclare'    => 'all',
54
        'endfor'        => 'all',
55
        'endforeach'    => 'all',
56
        'endif'         => 'all',
57
        'endswitch'     => 'all',
58
        'endwhile'      => 'all',
59
        'extends'       => 'all',
60
        'final'         => '5.0',
61
        'finally'       => '5.5',
62
        'for'           => 'all',
63
        'foreach'       => 'all',
64
        'function'      => 'all',
65
        'global'        => 'all',
66
        'goto'          => '5.3',
67
        'if'            => 'all',
68
        'implements'    => '5.0',
69
        'interface'     => '5.0',
70
        'instanceof'    => '5.0',
71
        'insteadof'     => '5.4',
72
        'namespace'     => '5.3',
73
        'new'           => 'all',
74
        'or'            => 'all',
75
        'private'       => '5.0',
76
        'protected'     => '5.0',
77
        'public'        => '5.0',
78
        'static'        => 'all',
79
        'switch'        => 'all',
80
        'throw'         => '5.0',
81
        'trait'         => '5.4',
82
        'try'           => '5.0',
83
        'use'           => 'all',
84
        'var'           => 'all',
85
        'while'         => 'all',
86
        'xor'           => 'all',
87
        '__class__'     => 'all',
88
        '__dir__'       => '5.3',
89
        '__file__'      => 'all',
90
        '__function__'  => 'all',
91
        '__method__'    => 'all',
92
        '__namespace__' => '5.3',
93
    );
94
95
    /**
96
     * A list of keywords that can follow use statements.
97
     *
98
     * @var array(string => string)
99
     */
100
    protected $validUseNames = array(
101
        'const'    => true,
102
        'function' => true,
103
    );
104
105
    /**
106
     * Whether PHPCS 1.x is used or not.
107
     *
108
     * @var bool
109
     */
110
    protected $isLowPHPCS = false;
111
112
    /**
113
     * Scope modifiers and other keywords allowed in trait use statements.
114
     *
115
     * @var array
116
     */
117
    private $allowedModifiers = array();
118
119
    /**
120
     * Targeted tokens.
121
     *
122
     * @var array
123
     */
124
    protected $targetedTokens = array(
125
        T_CLASS,
126
        T_FUNCTION,
127
        T_NAMESPACE,
128
        T_STRING,
129
        T_CONST,
130
        T_USE,
131
        T_AS,
132
        T_EXTENDS,
133
        T_INTERFACE,
134
    );
135
136
    /**
137
     * Returns an array of tokens this test wants to listen for.
138
     *
139
     * @return array
140
     */
141
    public function register()
142
    {
143
        $this->isLowPHPCS = version_compare(PHPCSHelper::getVersion(), '2.0', '<');
144
145
        $this->allowedModifiers          = array_combine(
146
            \PHP_CodeSniffer_Tokens::$scopeModifiers,
147
            \PHP_CodeSniffer_Tokens::$scopeModifiers
148
        );
149
        $this->allowedModifiers[T_FINAL] = T_FINAL;
150
151
        $tokens = $this->targetedTokens;
152
153
        if (defined('T_TRAIT')) {
154
            // phpcs:ignore PHPCompatibility.PHP.NewConstants.t_traitFound
155
            $tokens[] = T_TRAIT;
156
        }
157
158
        if (defined('T_ANON_CLASS')) {
159
            // phpcs:ignore PHPCompatibility.PHP.NewConstants.t_anon_classFound
160
            $tokens[] = T_ANON_CLASS;
161
        }
162
163
        return $tokens;
164
    }//end register()
165
166
    /**
167
     * Processes this test, when one of its tokens is encountered.
168
     *
169
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
170
     * @param int                   $stackPtr  The position of the current token in the
171
     *                                         stack passed in $tokens.
172
     *
173
     * @return void
174
     */
175
    public function process(\PHP_CodeSniffer_File $phpcsFile, $stackPtr)
176
    {
177
        $tokens = $phpcsFile->getTokens();
178
179
        /*
180
         * We distinguish between the class, function and namespace names vs the define statements.
181
         */
182
        if ($tokens[$stackPtr]['type'] === 'T_STRING') {
183
            $this->processString($phpcsFile, $stackPtr, $tokens);
184
        } else {
185
            $this->processNonString($phpcsFile, $stackPtr, $tokens);
186
        }
187
    }
188
189
    /**
190
     * Processes this test, when one of its tokens is encountered.
191
     *
192
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
193
     * @param int                   $stackPtr  The position of the current token in the
194
     *                                         stack passed in $tokens.
195
     * @param array                 $tokens    The stack of tokens that make up
196
     *                                         the file.
197
     *
198
     * @return void
199
     */
200
    public function processNonString(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, $tokens)
201
    {
202
        $nextNonEmpty = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true);
203
        if ($nextNonEmpty === false) {
204
            return;
205
        }
206
207
        /*
208
         * Deal with anonymous classes - `class` before a reserved keyword is sometimes
209
         * misidentified as `T_ANON_CLASS`.
210
         * In PHPCS < 2.3.4 these were tokenized as T_CLASS no matter what.
211
         */
212
        if ($tokens[$stackPtr]['type'] === 'T_ANON_CLASS' || $tokens[$stackPtr]['type'] === 'T_CLASS') {
213
            $prevNonEmpty = $phpcsFile->findPrevious(\PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true);
214
            if ($prevNonEmpty !== false && $tokens[$prevNonEmpty]['type'] === 'T_NEW') {
215
                return;
216
            }
217
        }
218
219
        /*
220
         * PHP 5.6 allows for use const and use function, but only if followed by the function/constant name.
221
         * - `use function HelloWorld` => move to the next token (HelloWorld) to verify.
222
         * - `use const HelloWorld` => move to the next token (HelloWorld) to verify.
223
         */
224
        elseif ($tokens[$stackPtr]['type'] === 'T_USE'
225
            && isset($this->validUseNames[strtolower($tokens[$nextNonEmpty]['content'])]) === true
226
        ) {
227
            $maybeUseNext = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($nextNonEmpty + 1), null, true, null, true);
228
            if ($maybeUseNext !== false && $this->isEndOfUseStatement($tokens[$maybeUseNext]) === false) {
229
                // Prevent duplicate messages: `const` is T_CONST in PHPCS 1.x and T_STRING in PHPCS 2.x.
230
                if ($this->isLowPHPCS === true) {
231
                    return;
232
                }
233
                $nextNonEmpty = $maybeUseNext;
234
            }
235
        }
236
237
        /*
238
         * Deal with visibility modifiers.
239
         * - `use HelloWorld { sayHello as protected; }` => valid, bow out.
240
         * - `use HelloWorld { sayHello as private myPrivateHello; }` => move to the next token to verify.
241
         */
242
        elseif ($tokens[$stackPtr]['type'] === 'T_AS'
243
            && isset($this->allowedModifiers[$tokens[$nextNonEmpty]['code']]) === true
244
            && $this->inUseScope($phpcsFile, $stackPtr) === true
245
        ) {
246
            $maybeUseNext = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($nextNonEmpty + 1), null, true, null, true);
247
            if ($maybeUseNext === false || $this->isEndOfUseStatement($tokens[$maybeUseNext]) === true) {
248
                return;
249
            }
250
251
            $nextNonEmpty = $maybeUseNext;
252
        }
253
254
        /*
255
         * Deal with functions declared to return by reference.
256
         */
257
        elseif ($tokens[$stackPtr]['type'] === 'T_FUNCTION'
258
            && $tokens[$nextNonEmpty]['type'] === 'T_BITWISE_AND'
259
        ) {
260
            $maybeUseNext = $phpcsFile->findNext(\PHP_CodeSniffer_Tokens::$emptyTokens, ($nextNonEmpty + 1), null, true, null, true);
261
            if ($maybeUseNext === false) {
262
                // Live coding.
263
                return;
264
            }
265
266
            $nextNonEmpty = $maybeUseNext;
267
        }
268
269
        /*
270
         * Deal with nested namespaces.
271
         */
272
        elseif ($tokens[$stackPtr]['type'] === 'T_NAMESPACE') {
273
            if ($tokens[$stackPtr + 1]['code'] === T_NS_SEPARATOR) {
274
                // Not a namespace declaration, but use of, i.e. namespace\someFunction();
275
                return;
276
            }
277
278
            $endToken      = $phpcsFile->findNext(array(T_SEMICOLON, T_OPEN_CURLY_BRACKET), ($stackPtr + 1), null, false, null, true);
279
            $namespaceName = trim($phpcsFile->getTokensAsString(($stackPtr + 1), ($endToken - $stackPtr - 1)));
280
            if (empty($namespaceName) === true) {
281
                return;
282
            }
283
284
            $namespaceParts = explode('\\', $namespaceName);
285
            foreach ($namespaceParts as $namespacePart) {
286
                $partLc = strtolower($namespacePart);
287
                if (isset($this->invalidNames[$partLc]) === false) {
288
                    continue;
289
                }
290
291
                // Find the token position of the part which matched.
292
                for ($i = ($stackPtr + 1); $i < $endToken; $i++) {
293
                    if ($tokens[$i]['content'] === $namespacePart) {
294
                        $nextNonEmpty = $i;
295
                        break;
296
                    }
297
                }
298
            }
299
            unset($i, $namespacePart, $partLc);
300
        }
301
302
        $nextContentLc = strtolower($tokens[$nextNonEmpty]['content']);
303
        if (isset($this->invalidNames[$nextContentLc]) === false) {
304
            return;
305
        }
306
307
        /*
308
         * Deal with PHP 7 relaxing the rules.
309
         * "As of PHP 7.0.0 these keywords are allowed as property, constant, and method names
310
         * of classes, interfaces and traits, except that class may not be used as constant name."
311
         */
312
        if ((($tokens[$stackPtr]['type'] === 'T_FUNCTION'
313
                && $this->inClassScope($phpcsFile, $stackPtr, false) === true)
314
            || ($tokens[$stackPtr]['type'] === 'T_CONST'
315
                && $this->isClassConstant($phpcsFile, $stackPtr) === true
316
                && $nextContentLc !== 'class'))
317
            && $this->supportsBelow('5.6') === false
318
        ) {
319
            return;
320
        }
321
322
        if ($this->supportsAbove($this->invalidNames[$nextContentLc])) {
323
            $data = array(
324
                $tokens[$nextNonEmpty]['content'],
325
                $this->invalidNames[$nextContentLc],
326
            );
327
            $this->addError($phpcsFile, $stackPtr, $tokens[$nextNonEmpty]['content'], $data);
328
        }
329
330
    }//end processNonString()
331
332
    /**
333
     * Processes this test, when one of its tokens is encountered.
334
     *
335
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
336
     * @param int                   $stackPtr  The position of the current token in the
337
     *                                         stack passed in $tokens.
338
     * @param array                 $tokens    The stack of tokens that make up
339
     *                                         the file.
340
     *
341
     * @return void
342
     */
343
    public function processString(\PHP_CodeSniffer_File $phpcsFile, $stackPtr, $tokens)
344
    {
345
        $tokenContentLc = strtolower($tokens[$stackPtr]['content']);
346
347
        /*
348
         * Special case for PHP versions where the target is not yet identified as
349
         * its own token, but presents as T_STRING.
350
         * - namespace keyword in PHP < 5.3
351
         * - trait keyword in PHP < 5.4
352
         */
353
        if (version_compare(PHP_VERSION_ID, '50400', '<') && $tokenContentLc === 'trait') {
354
            $this->processNonString($phpcsFile, $stackPtr, $tokens);
355
            return;
356
        }
357
358
        // Look for any define/defined tokens (both T_STRING ones, blame Tokenizer).
359
        if ($tokenContentLc !== 'define' && $tokenContentLc !== 'defined') {
360
            return;
361
        }
362
363
        // Retrieve the define(d) constant name.
364
        $firstParam = $this->getFunctionCallParameter($phpcsFile, $stackPtr, 1);
365
        if ($firstParam === false) {
366
            return;
367
        }
368
369
        $defineName   = $this->stripQuotes($firstParam['raw']);
370
        $defineNameLc = strtolower($defineName);
371
372
        if (isset($this->invalidNames[$defineNameLc]) && $this->supportsAbove($this->invalidNames[$defineNameLc])) {
373
            $data = array(
374
                $defineName,
375
                $this->invalidNames[$defineNameLc],
376
            );
377
            $this->addError($phpcsFile, $stackPtr, $defineNameLc, $data);
378
        }
379
    }//end processString()
380
381
382
    /**
383
     * Add the error message.
384
     *
385
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
386
     * @param int                   $stackPtr  The position of the current token in the
387
     *                                         stack passed in $tokens.
388
     * @param string                $content   The token content found.
389
     * @param array                 $data      The data to pass into the error message.
390
     *
391
     * @return void
392
     */
393
    protected function addError($phpcsFile, $stackPtr, $content, $data)
394
    {
395
        $error     = "Function name, class name, namespace name or constant name can not be reserved keyword '%s' (since version %s)";
396
        $errorCode = $this->stringToErrorCode($content).'Found';
397
        $phpcsFile->addError($error, $stackPtr, $errorCode, $data);
398
    }
399
400
401
    /**
402
     * Check if the current token code is for a token which can be considered
403
     * the end of a (partial) use statement.
404
     *
405
     * @param int $token The current token information.
406
     *
407
     * @return bool
408
     */
409
    protected function isEndOfUseStatement($token)
410
    {
411
        return in_array($token['code'], array(T_CLOSE_CURLY_BRACKET, T_SEMICOLON, T_COMMA), true);
412
    }
413
}//end class
414