Completed
Push — feature/travis-add-interim-php... ( 354a19...d1bcd9 )
by Juliette
05:29 queued 03:15
created

PHPCompatibility_Sniffs_PHP_ForbiddenNamesSniff   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 332
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 38
lcom 1
cbo 3
dl 0
loc 332
rs 8.3999
c 0
b 0
f 0

6 Methods

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