Completed
Pull Request — master (#271)
by Juliette
02:44
created

isEndOfUseStatement()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 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
        'bool' => '7.0',
93
        'int' => '7.0',
94
        'float' => '7.0',
95
        'string' => '7.0',
96
        'null' => '7.0',
97
        'true' => '7.0',
98
        'false' => '7.0',
99
        'resource' => '7.0',
100
        'object' => '7.0',
101
        'mixed' => '7.0',
102
        'numeric' => '7.0'
103
    );
104
105
    /**
106
     * A list of keywords that can follow use statements.
107
     *
108
     * @var array(string => string)
109
     */
110
    protected $validUseNames = array(
111
        'const'    => true,
112
        'function' => true,
113
    );
114
115
    /**
116
     * targetedTokens
117
     *
118
     * @var array
119
     */
120
    protected $targetedTokens = array(
121
        T_CLASS,
122
        T_FUNCTION,
123
        T_NAMESPACE,
124
        T_STRING,
125
        T_CONST,
126
        T_USE,
127
        T_AS,
128
        T_EXTENDS,
129
        T_TRAIT,
130
        T_INTERFACE,
131
    );
132
133
    /**
134
     * Returns an array of tokens this test wants to listen for.
135
     *
136
     * @return array
137
     */
138
    public function register()
139
    {
140
        $tokens = $this->targetedTokens;
141
        if (defined('T_ANON_CLASS')) {
142
            $tokens[] = constant('T_ANON_CLASS');
143
        }
144
        return $tokens;
145
    }//end register()
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 the
152
     *                                        stack passed in $tokens.
153
     *
154
     * @return void
155
     */
156
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
157
    {
158
        $tokens = $phpcsFile->getTokens();
159
160
        /**
161
         * We distinguish between the class, function and namespace names or the define statements.
162
         */
163
        if ($tokens[$stackPtr]['type'] === 'T_STRING') {
164
            $this->processString($phpcsFile, $stackPtr, $tokens);
165
        } else {
166
            $this->processNonString($phpcsFile, $stackPtr, $tokens);
167
        }
168
    }
169
170
    /**
171
     * Processes this test, when one of its tokens is encountered.
172
     *
173
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
174
     * @param int                  $stackPtr  The position of the current token in the
175
     *                                        stack passed in $tokens.
176
     * @param array                $tokens    The stack of tokens that make up
177
     *                                        the file.
178
     *
179
     * @return void
180
     */
181
    public function processNonString(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $tokens)
182
    {
183
        $nextNonEmpty = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true);
184
        if ($nextNonEmpty === false) {
185
            return;
186
        }
187
188
        /*
189
         * PHP 5.6 allows for use const and use function, but only if followed by the function/constant name.
190
         * - `use function HelloWorld` => move to the next token to verify.
191
         * - `use const HelloWorld` => move to the next token to verify.
192
         */
193
        if ($tokens[$stackPtr]['type'] === 'T_USE'
194
            && isset($this->validUseNames[strtolower($tokens[$nextNonEmpty]['content'])]) === true
195
            && $this->supportsAbove('5.6')
196
        ) {
197
            $maybeUseNext = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($nextNonEmpty + 1), null, true, null, true);
198
            if ($maybeUseNext !== false && $this->isEndOfUseStatement($tokens[$maybeUseNext]) === false) {
199
                $nextNonEmpty = $maybeUseNext;
200
            }
201
        }
202
203
        /*
204
         * Deal with visibility modifiers.
205
         * - `use HelloWorld { sayHello as protected; }` => valid, bow out.
206
         * - `use HelloWorld { sayHello as private myPrivateHello; }` => move to the next token to verify.
207
         */
208
        else if ($tokens[$stackPtr]['type'] === 'T_AS'
209
            && in_array($tokens[$nextNonEmpty]['code'], PHP_CodeSniffer_Tokens::$scopeModifiers, true) === true
210
            && $this->tokenHasScope($phpcsFile, $nextNonEmpty, T_USE) === true
211
        ) {
212
            $maybeUseNext = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($nextNonEmpty + 1), null, true, null, true);
213
            if ($maybeUseNext === false || $this->isEndOfUseStatement($tokens[$maybeUseNext]) === true) {
214
                return;
215
            }
216
217
            $nextNonEmpty = $maybeUseNext;
218
        }
219
220
        $nextContentLc = strtolower($tokens[$nextNonEmpty]['content']);
221
        if (isset($this->invalidNames[$nextContentLc]) === false) {
222
            return;
223
        }
224
225
        // Deal with anonymous classes.
226
        $prevNonEmpty = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true);
227
        if ($prevNonEmpty !== false
228
            && $tokens[$prevNonEmpty]['type'] === 'T_NEW'
229
            && $tokens[$stackPtr]['type'] === 'T_ANON_CLASS'
230
        ) {
231
            return;
232
        }
233
234 View Code Duplication
        if ($this->supportsAbove($this->invalidNames[$nextContentLc])) {
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...
235
            $error = "Function name, class name, namespace name or constant name can not be reserved keyword '%s' (since version %s)";
236
            $data  = array(
237
                $tokens[$nextNonEmpty]['content'],
238
                $this->invalidNames[$nextContentLc],
239
            );
240
            $phpcsFile->addError($error, $stackPtr, 'Found', $data);
241
        }
242
243
    }//end processNonString()
244
245
    /**
246
     * Processes this test, when one of its tokens is encountered.
247
     *
248
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
249
     * @param int                  $stackPtr  The position of the current token in the
250
     *                                        stack passed in $tokens.
251
     * @param array                $tokens    The stack of tokens that make up
252
     *                                        the file.
253
     *
254
     * @return void
255
     */
256
    public function processString(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $tokens)
257
    {
258
        $tokenContentLc = strtolower($tokens[$stackPtr]['content']);
259
260
        // Special case for 5.3 where we want to find usage of traits, but
261
        // trait is not a token.
262
        if ($tokenContentLc === 'trait') {
263
            return $this->processNonString($phpcsFile, $stackPtr, $tokens);
264
        }
265
266
        // Look for any define/defined tokens (both T_STRING ones, blame Tokenizer).
267
        if ($tokenContentLc !== 'define' && $tokenContentLc !== 'defined') {
268
            return;
269
        }
270
271
        // Retrieve the define(d) constant name.
272
        $firstParam = $this->getFunctionCallParameter($phpcsFile, $stackPtr, 1);
273
        if ($firstParam === false) {
274
            return;
275
        }
276
277
        $defineName = strtolower($firstParam['raw']);
278
        $defineName = $this->stripQuotes($defineName);
279
280 View Code Duplication
        if (isset($this->invalidNames[$defineName]) && $this->supportsAbove($this->invalidNames[$defineName])) {
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...
281
            $error = "Function name, class name, namespace name or constant name can not be reserved keyword '%s' (since PHP version %s)";
282
            $data  = array(
283
                $defineName,
284
                $this->invalidNames[$defineName],
285
            );
286
            $phpcsFile->addError($error, $stackPtr, 'Found', $data);
287
        }
288
    }//end processString()
289
290
291
    /**
292
     * Check if the current token code is for a token which can be considered
293
     * the end of a (partial) use statement.
294
     *
295
     * @param int $token The current token information.
296
     *
297
     * @return bool
298
     */
299
    protected function isEndOfUseStatement($token)
300
    {
301
        return in_array($token['code'], array(T_CLOSE_CURLY_BRACKET, T_SEMICOLON, T_COMMA), true);
302
    }
303
}//end class
304