Completed
Push — feature/rename-phpunit-config ( 357a24...3649e1 )
by Juliette
03:31 queued 01:25
created

NewExecutionDirectivesSniff   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 315
Duplicated Lines 3.17 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 2
dl 10
loc 315
rs 9
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A register() 0 7 1
B process() 3 53 8
A shouldThrowError() 0 4 2
A getItemArray() 0 4 1
A getNonVersionArrayKeys() 0 7 1
B getErrorInfo() 7 20 5
A getErrorMsgTemplate() 0 4 1
A addError() 0 17 3
C addWarningOnInvalidValue() 0 32 7
A isNumeric() 0 4 1
B validEncoding() 0 14 5

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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