Completed
Pull Request — master (#291)
by Juliette
02:26
created

NewExecutionDirectivesSniff::getErrorMsgTemplate()   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 0
1
<?php
2
/**
3
 * PHPCompatibility_Sniffs_PHP_NewExecutionDirectivesSniff.
4
 *
5
 * @category  PHP
6
 * @package   PHPCompatibility
7
 * @author    Juliette Reinders Folmer <[email protected]>
8
 */
9
10
/**
11
 * PHPCompatibility_Sniffs_PHP_NewExecutionDirectivesSniff.
12
 *
13
 * @category  PHP
14
 * @package   PHPCompatibility
15
 * @author    Juliette Reinders Folmer <[email protected]>
16
 */
17
class PHPCompatibility_Sniffs_PHP_NewExecutionDirectivesSniff
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...
18
    extends PHPCompatibility_AbstractNewFeatureSniff
0 ignored issues
show
Coding Style introduced by
The extends keyword must be on the same line as the class name
Loading history...
19
{
20
21
    /**
22
     * A list of new execution directives
23
     *
24
     * The array lists : version number with false (not present) or true (present).
25
     * If the execution order is conditional, add the condition as a string to the version nr.
26
     * If's sufficient to list the first version where the execution directive appears.
27
     *
28
     * @var array(string => array(string => int|string|null))
29
     */
30
    protected $newDirectives = array (
31
                                'ticks' => array(
32
                                    '3.1' => false,
33
                                    '4.0' => true,
34
                                    'valid_value_callback' => 'isNumeric',
35
                                ),
36
                                'encoding' => array(
37
                                    '5.2' => false,
38
                                    '5.3' => '--enable-zend-multibyte', // Directive ignored unless.
39
                                    '5.4' => true,
40
                                    'valid_value_callback' => 'validEncoding',
41
                                ),
42
                                'strict_types' => array(
43
                                    '5.6' => false,
44
                                    '7.0' => true,
45
                                    'valid_values' => array(1),
46
                                ),
47
                               );
48
49
50
    /**
51
     * Tokens to ignore when trying to find the value for the directive.
52
     *
53
     * @var array
54
     */
55
    protected $ignoreTokens = array();
56
57
58
    /**
59
     * Returns an array of tokens this test wants to listen for.
60
     *
61
     * @return array
62
     */
63
    public function register()
64
    {
65
        $this->ignoreTokens          = PHP_CodeSniffer_Tokens::$emptyTokens;
66
        $this->ignoreTokens[T_EQUAL] = T_EQUAL;
67
68
        return array(T_DECLARE);
69
    }//end register()
70
71
72
    /**
73
     * Processes this test, when one of its tokens is encountered.
74
     *
75
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
76
     * @param int                  $stackPtr  The position of the current token in
77
     *                                        the stack passed in $tokens.
78
     *
79
     * @return void
80
     */
81
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
82
    {
83
        $tokens = $phpcsFile->getTokens();
84
85
        if (isset($tokens[$stackPtr]['parenthesis_opener'], $tokens[$stackPtr]['parenthesis_closer']) === true) {
86
	        $openParenthesis  = $tokens[$stackPtr]['parenthesis_opener'];
87
	        $closeParenthesis = $tokens[$stackPtr]['parenthesis_closer'];
88
        }
89
        else {
90
	        if (version_compare(PHP_CodeSniffer::VERSION, '2.0', '>=')) {
91
				return;
92
			}
93
94
			// Deal with PHPCS 1.x which does not set the parenthesis properly for declare statements.
95
			$openParenthesis = $phpcsFile->findNext(T_OPEN_PARENTHESIS, ($stackPtr + 1), null, false, null, true);
96 View Code Duplication
			if ($openParenthesis === false || isset($tokens[$openParenthesis]['parenthesis_closer']) === false) {
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...
97
				return;
98
			}
99
			$closeParenthesis = $tokens[$openParenthesis]['parenthesis_closer'];
100
		}
101
102
        $directivePtr = $phpcsFile->findNext(T_STRING, ($openParenthesis + 1), $closeParenthesis, false);
103
        if ($directivePtr === false) {
104
            return;
105
        }
106
107
        $directiveContent = $tokens[$directivePtr]['content'];
108
109
        if (isset($this->newDirectives[$directiveContent]) === false) {
110
            $error = 'Declare can only be used with the directives %s. Found: %s';
111
            $data  = array(
112
                implode(', ', array_keys($this->newDirectives)),
113
                $directiveContent,
114
            );
115
116
            $phpcsFile->addError($error, $stackPtr, 'InvalidDirectiveFound', $data);
117
        }
118
        else {
119
            // Check for valid directive for version.
120
            $itemInfo = array(
121
                'name'   => $directiveContent,
122
            );
123
            $this->handleFeature($phpcsFile, $stackPtr, $itemInfo);
124
125
            // Check for valid directive value.
126
            $valuePtr = $phpcsFile->findNext($this->ignoreTokens, $directivePtr + 1, $closeParenthesis, true);
127
            if ($valuePtr === false) {
128
                return;
129
            }
130
131
            $this->addWarningOnInvalidValue($phpcsFile, $valuePtr, $directiveContent);
132
        }
133
134
    }//end process()
135
136
137
    /**
138
     * Determine whether an error/warning should be thrown for an item based on collected information.
139
     *
140
     * @param array $errorInfo Detail information about an item.
141
     *
142
     * @return bool
143
     */
144
    protected function shouldThrowError(array $errorInfo)
145
    {
146
        return ($errorInfo['not_in_version'] !== '' || $errorInfo['conditional_version'] !== '');
147
    }
148
149
150
    /**
151
     * Get the relevant sub-array for a specific item from a multi-dimensional array.
152
     *
153
     * @param array $itemInfo Base information about the item.
154
     *
155
     * @return array Version and other information about the item.
156
     */
157
    public function getItemArray(array $itemInfo)
158
    {
159
        return $this->newDirectives[$itemInfo['name']];
160
    }
161
162
163
    /**
164
     * Get an array of the non-PHP-version array keys used in a sub-array.
165
     *
166
     * @return array
167
     */
168
    protected function getNonVersionArrayKeys()
169
    {
170
        return array(
171
            'valid_value_callback',
172
            'valid_values',
173
        );
174
    }
175
176
177
    /**
178
     * Retrieve the relevant detail (version) information for use in an error message.
179
     *
180
     * @param array $itemArray Version and other information about the item.
181
     * @param array $itemInfo  Base information about the item.
182
     *
183
     * @return array
184
     */
185
    public function getErrorInfo(array $itemArray, array $itemInfo)
186
    {
187
        $errorInfo = parent::getErrorInfo($itemArray, $itemInfo);
188
        $errorInfo['conditional_version'] = '';
189
        $errorInfo['condition']           = '';
190
191
        $versionArray = $this->getVersionArray($itemArray);
192
193
        if (empty($versionArray) === false) {
194 View Code Duplication
            foreach ($versionArray as $version => $present) {
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...
195
                if (is_string($present) === true && $this->supportsBelow($version) === true) {
196
                    // We cannot test for compilation option (ok, except by scraping the output of phpinfo...).
197
                    $errorInfo['conditional_version'] = $version;
198
                    $errorInfo['condition']           = $present;
199
                }
200
            }
201
        }
202
203
        return $errorInfo;
204
    }
205
206
207
    /**
208
     * Get the error message template for this sniff.
209
     *
210
     * @return string
211
     */
212
    protected function getErrorMsgTemplate()
213
    {
214
        return 'Directive '.parent::getErrorMsgTemplate();
215
    }
216
217
218
    /**
219
     * Generates the error or warning for this item.
220
     *
221
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
222
     * @param int                  $stackPtr  The position of the relevant token in
223
     *                                        the stack.
224
     * @param array                $itemInfo  Base information about the item.
225
     * @param array                $errorInfo Array with detail (version) information
226
     *                                        relevant to the item.
227
     *
228
     * @return void
229
     */
230
    public function addError(PHP_CodeSniffer_File $phpcsFile, $stackPtr, array $itemInfo, array $errorInfo)
231
    {
232
        if ($errorInfo['not_in_version'] !== '') {
233
            parent::addError($phpcsFile, $stackPtr, $itemInfo, $errorInfo);
234
        } else if ($errorInfo['conditional_version'] !== '') {
235
            $error     = 'Directive %s is present in PHP version %s but will be disregarded unless PHP is compiled with %s';
236
            $errorCode = $this->stringToErrorCode($itemInfo['name']).'WithConditionFound';
237
            $data      = array(
238
                $itemInfo['name'],
239
                $errorInfo['conditional_version'],
240
                $errorInfo['condition'],
241
            );
242
243
            $phpcsFile->addWarning($error, $stackPtr, $errorCode, $data);
244
        }
245
246
    }//end addError()
247
248
249
    /**
250
     * Generates a error or warning for this sniff.
251
     *
252
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
253
     * @param int                  $stackPtr  The position of the execution directive value
254
     *                                        in the token array.
255
     * @param string               $directive The directive.
256
     *
257
     * @return void
258
     */
259
    protected function addWarningOnInvalidValue($phpcsFile, $stackPtr, $directive)
260
    {
261
        $tokens = $phpcsFile->getTokens();
262
263
        $value = $tokens[$stackPtr]['content'];
264
        if ($tokens[$stackPtr]['code'] === T_CONSTANT_ENCAPSED_STRING) {
265
            $value = $this->stripQuotes($value);
266
        }
267
268
        $isError = false;
269
        if (isset($this->newDirectives[$directive]['valid_values'])) {
270
            if (in_array($value, $this->newDirectives[$directive]['valid_values']) === false) {
271
                $isError = true;
272
            }
273
        }
274
        else if (isset($this->newDirectives[$directive]['valid_value_callback'])) {
275
            $valid = call_user_func(array($this, $this->newDirectives[$directive]['valid_value_callback']), $value);
276
            if ($valid === false) {
277
                $isError = true;
278
            }
279
        }
280
281
        if ($isError === true) {
282
            $error     = 'The execution directive %s does not seem to have a valid value. Please review. Found: %s';
283
            $errorCode = $this->stringToErrorCode($directive).'InvalidValueFound';
284
            $data      = array(
285
                $directive,
286
                $value,
287
            );
288
289
            $phpcsFile->addWarning($error, $stackPtr, $errorCode, $data);
290
        }
291
    }// addErrorOnInvalidValue()
292
293
294
    /**
295
     * Check whether a value is numeric.
296
     *
297
     * Callback function to test whether the value for an execution directive is valid.
298
     *
299
     * @param mixed $value The value to test.
300
     *
301
     * @return bool
302
     */
303
    protected function isNumeric($value)
304
    {
305
        return is_numeric($value);
306
    }
307
308
309
    /**
310
     * Check whether a value is valid encoding.
311
     *
312
     * Callback function to test whether the value for an execution directive is valid.
313
     *
314
     * @param mixed $value The value to test.
315
     *
316
     * @return bool
317
     */
318
    protected function validEncoding($value)
319
    {
320
        $encodings = array();
321
        if (function_exists('mb_list_encodings')) {
322
            $encodings = mb_list_encodings();
323
        }
324
325
        if (empty($encodings)) {
326
            // If we can't test the encoding, let it pass through.
327
            return true;
328
        }
329
330
        if (in_array($value, $encodings, true)) {
331
            return true;
332
        }
333
        else {
334
            return false;
335
        }
336
    }
337
338
339
}//end class
340