Completed
Pull Request — master (#189)
by Juliette
03:23
created

addError()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
c 4
b 1
f 0
dl 0
loc 23
rs 8.5906
cc 6
eloc 13
nc 8
nop 3
1
<?php
2
/**
3
 * PHPCompatibility_Sniffs_PHP_NewKeywordsSniff.
4
 *
5
 * PHP version 5.5
6
 *
7
 * @category  PHP
8
 * @package   PHPCompatibility
9
 * @author    Wim Godden <[email protected]>
10
 * @copyright 2013 Cu.be Solutions bvba
11
 */
12
13
/**
14
 * PHPCompatibility_Sniffs_PHP_NewClassesSniff.
15
 *
16
 * @category  PHP
17
 * @package   PHPCompatibility
18
 * @author    Wim Godden <[email protected]>
19
 * @version   1.0.0
20
 * @copyright 2013 Cu.be Solutions bvba
21
 */
22
class PHPCompatibility_Sniffs_PHP_NewKeywordsSniff 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...
23
{
24
25
    /**
26
     * A list of new keywords, not present in older versions.
27
     *
28
     * The array lists : version number with false (not present) or true (present).
29
     * If's sufficient to list the last version which did not contain the keyword.
30
     *
31
     * Description will be used as part of the error message.
32
     * Condition is an array of valid scope conditions to check for.
33
     * If you need a condition of a different type, make sure to add the appropriate
34
     * logic for it as well as this will not resolve itself automatically.
35
     *
36
     * @var array(string => array(string => int|string|null))
37
     */
38
    protected $newKeywords = array(
39
                                        'T_HALT_COMPILER' => array(
40
                                            '5.0' => false,
41
                                            '5.1' => true,
42
                                            'description' => '"__halt_compiler" keyword'
43
                                        ),
44
                                        'T_CONST' => array(
45
                                            '5.2' => false,
46
                                            '5.3' => true,
47
                                            'description' => '"const" keyword',
48
                                            'condition' => array(T_CLASS), // Keyword is only new when not in class context.
49
                                        ),
50
                                        'T_CALLABLE' => array(
51
                                            '5.3' => false,
52
                                            '5.4' => true,
53
                                            'description' => '"callable" keyword',
54
                                            'content' => 'callable',
55
                                        ),
56
                                        'T_DIR' => array(
57
                                            '5.2' => false,
58
                                            '5.3' => true,
59
                                            'description' => '__DIR__ magic constant'
60
                                        ),
61
                                        'T_GOTO' => array(
62
                                            '5.2' => false,
63
                                            '5.3' => true,
64
                                            'description' => '"goto" keyword'
65
                                        ),
66
                                        'T_INSTEADOF' => array(
67
                                            '5.3' => false,
68
                                            '5.4' => true,
69
                                            'description' => '"insteadof" keyword (for traits)',
70
                                            'content' => 'insteadof',
71
                                        ),
72
                                        'T_NAMESPACE' => array(
73
                                            '5.2' => false,
74
                                            '5.3' => true,
75
                                            'description' => '"namespace" keyword'
76
                                        ),
77
                                        'T_NS_C' => array(
78
                                            '5.2' => false,
79
                                            '5.3' => true,
80
                                            'description' => '__NAMESPACE__ magic constant'
81
                                        ),
82
                                        'T_USE' => array(
83
                                            '5.2' => false,
84
                                            '5.3' => true,
85
                                            'description' => '"use" keyword (for traits/namespaces/anonymous functions)'
86
                                        ),
87
                                        'T_TRAIT' => array(
88
                                            '5.3' => false,
89
                                            '5.4' => true,
90
                                            'description' => '"trait" keyword',
91
                                            'content' => 'trait',
92
                                        ),
93
                                        'T_TRAIT_C' => array(
94
                                            '5.3' => false,
95
                                            '5.4' => true,
96
                                            'description' => '__TRAIT__ magic constant',
97
                                            'content' => '__TRAIT__',
98
                                        ),
99
                                        'T_YIELD' => array(
100
                                            '5.4' => false,
101
                                            '5.5' => true,
102
                                            'description' => '"yield" keyword (for generators)',
103
                                            'content' => 'yield',
104
                                        ),
105
                                        'T_FINALLY' => array(
106
                                            '5.4' => false,
107
                                            '5.5' => true,
108
                                            'description' => '"finally" keyword (in exception handling)',
109
                                            'content' => 'finally',
110
                                        ),
111
                                        'T_START_NOWDOC' => array(
112
                                            '5.2' => false,
113
                                            '5.3' => true,
114
                                            'description' => 'nowdoc functionality',
115
                                            'content' => "<<<'EOD'\n",
116
                                        ),
117
                                        'T_END_NOWDOC' => array(
118
                                            '5.2' => false,
119
                                            '5.3' => true,
120
                                            'description' => 'nowdoc functionality',
121
                                            'content' => 'EOD;',
122
                                        ),
123
                                    );
124
125
    /**
126
     * Translation table for T_STRING tokens.
127
     *
128
     * Will be set up from the register() method.
129
     *
130
     * @var array(string => string)
131
     */
132
    protected $translateContentToToken = array();
133
134
135
    /**
136
     * Returns an array of tokens this test wants to listen for.
137
     *
138
     * @return array
139
     */
140
    public function register()
141
    {
142
        $tokens    = array();
143
        $translate = array();
144
        foreach ($this->newKeywords as $token => $versions) {
145
            if (defined($token)) {
146
                $tokens[] = constant($token);
147
            }
148
            if (isset($versions['content'])) {
149
                $translate[$versions['content']] = $token;
150
            }
151
        }
152
153
        /*
154
         * Deal with tokens not recognized by the PHP version the sniffer is run
155
         * under and (not correctly) compensated for by PHPCS.
156
         */
157
        if (empty($translate) === false) {
158
            $this->translateContentToToken = $translate;
159
            $tokens[] = T_STRING;
160
        }
161
162
        return $tokens;
163
164
    }//end register()
165
166
167
    /**
168
     * Processes this test, when one of its tokens is encountered.
169
     *
170
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
171
     * @param int                  $stackPtr  The position of the current token in
172
     *                                        the stack passed in $tokens.
173
     *
174
     * @return void
175
     */
176
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
177
    {
178
        $tokens    = $phpcsFile->getTokens();
179
        $tokenType = $tokens[$stackPtr]['type'];
180
181
        // Translate T_STRING token if necessary.
182
        if ($tokens[$stackPtr]['type'] === 'T_STRING') {
183
            $content = $tokens[$stackPtr]['content'];
184
            if (isset($this->translateContentToToken[$content]) === false) {
185
                // Not one of the tokens we're looking for.
186
                return;
187
            }
188
189
            $tokenType = $this->translateContentToToken[$content];
190
        }
191
192
        $nextToken = $phpcsFile->findNext(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr + 1), null, true);
193
        $prevToken = $phpcsFile->findPrevious(PHP_CodeSniffer_Tokens::$emptyTokens, ($stackPtr - 1), null, true);
194
195
196
        // Skip attempts to use keywords as functions or class names - the former
197
        // will be reported by ForbiddenNamesAsInvokedFunctionsSniff, whilst the
198
        // will be (partially) reported by the ForbiddenNames sniff.
199
        // Either type will result in false-positives when targetting lower versions
200
        // of PHP where the name was not reserved, unless we explicitly check for
201
        // them.
202
        if (
203
            ($nextToken === false || $tokens[$nextToken]['type'] !== 'T_OPEN_PARENTHESIS')
204
            &&
205
            ($prevToken === false || $tokens[$prevToken]['type'] !== 'T_CLASS' || $tokens[$prevToken]['type'] !== 'T_INTERFACE')
206
        ) {
207
            // Skip based on token scope condition.
208
            if (isset($this->newKeywords[$tokenType]['condition'])) {
209
                $condition = $this->newKeywords[$tokenType]['condition'];
210
                if ($this->tokenHasScope($phpcsFile, $stackPtr, $condition) === true) {
211
                    return;
212
                }
213
            }
214
215
            $this->addError($phpcsFile, $stackPtr, $tokenType);
216
        }
217
    }//end process()
218
219
220
    /**
221
     * Generates the error or warning for this sniff.
222
     *
223
     * @param PHP_CodeSniffer_File $phpcsFile   The file being scanned.
224
     * @param int                  $stackPtr    The position of the function
225
     *                                          in the token array.
226
     * @param string               $keywordName The name of the keyword.
227
     *
228
     * @return void
229
     */
230
    protected function addError($phpcsFile, $stackPtr, $keywordName)
231
    {
232
        $notInVersion = '';
233
        foreach ($this->newKeywords[$keywordName] as $version => $present) {
234
            if (in_array($version, array('condition', 'description', 'content'), true)) {
235
                continue;
236
            }
237
238
            if ($present === false && $this->supportsBelow($version)) {
239
                $notInVersion = $version;
240
            }
241
        }
242
243
        if ($notInVersion !== '') {
244
            $error = '%s is not present in PHP version %s or earlier';
245
            $data  = array(
246
                $this->newKeywords[$keywordName]['description'],
247
                $notInVersion,
248
            );
249
            $phpcsFile->addError($error, $stackPtr, 'Found', $data);
250
        }
251
252
    }//end addError()
253
254
}//end class
255