Completed
Push — master ( a32632...c637b7 )
by Juliette
9s
created

addError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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