Failed Conditions
Push — phpcs-3-upgrade ( d91341 )
by Alexander
02:06
created

ControlStructureSpacingSniff::isTry()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * CodingStandard_Sniffs_WhiteSpace_ControlStructureSpacingSniff.
4
 *
5
 * PHP version 5
6
 *
7
 * @category PHP
8
 * @package  PHP_CodeSniffer
9
 * @author   Greg Sherwood <[email protected]>
10
 * @author   Marc McIntyre <[email protected]>
11
 * @author   Alexander Obuhovich <[email protected]>
12
 * @license  https://github.com/aik099/CodingStandard/blob/master/LICENSE BSD 3-Clause
13
 * @link     https://github.com/aik099/CodingStandard
14
 */
15
16
namespace CodingStandard\Sniffs\WhiteSpace;
17
18
use PHP_CodeSniffer\Files\File;
19
use PHP_CodeSniffer\Sniffs\Sniff;
20
use PHP_CodeSniffer\Util\Tokens;
21
22
/**
23
 * CodingStandard_Sniffs_WhiteSpace_ControlStructureSpacingSniff.
24
 *
25
 * Checks that control structures have the correct spacing around brackets.
26
 *
27
 * @category PHP
28
 * @package  PHP_CodeSniffer
29
 * @author   Greg Sherwood <[email protected]>
30
 * @author   Marc McIntyre <[email protected]>
31
 * @author   Alexander Obuhovich <[email protected]>
32
 * @license  https://github.com/aik099/CodingStandard/blob/master/LICENSE BSD 3-Clause
33
 * @link     https://github.com/aik099/CodingStandard
34
 */
35
class ControlStructureSpacingSniff implements Sniff
36
{
37
38
    /**
39
     * A list of tokenizers this sniff supports.
40
     *
41
     * @var array
42
     */
43
    public $supportedTokenizers = array(
44
                                   'PHP',
45
                                   'JS',
46
                                  );
47
48
    /**
49
     * How many spaces should follow the opening bracket.
50
     *
51
     * @var int
52
     */
53
    public $requiredSpacesAfterOpen = 1;
54
55
    /**
56
     * How many spaces should precede the closing bracket.
57
     *
58
     * @var int
59
     */
60
    public $requiredSpacesBeforeClose = 1;
61
62
63
    /**
64
     * Returns an array of tokens this test wants to listen for.
65
     *
66
     * @return integer[]
67
     */
68
    public function register()
69
    {
70
        return array(
71
                T_IF,
72
                T_WHILE,
73
                T_FOREACH,
74
                T_FOR,
75
                T_SWITCH,
76
                T_DO,
77
                T_ELSE,
78
                T_ELSEIF,
79
                T_TRY,
80
                T_CATCH,
81
               );
82
    }//end register()
83
84
85
    /**
86
     * Processes this test, when one of its tokens is encountered.
87
     *
88
     * @param File $phpcsFile The file being scanned.
89
     * @param int  $stackPtr  The position of the current token in the
90
     *                        stack passed in $tokens.
91
     *
92
     * @return void
93
     */
94
    public function process(File $phpcsFile, $stackPtr)
95
    {
96
        $this->requiredSpacesAfterOpen   = (int) $this->requiredSpacesAfterOpen;
97
        $this->requiredSpacesBeforeClose = (int) $this->requiredSpacesBeforeClose;
98
        $tokens = $phpcsFile->getTokens();
99
100
        $this->checkBracketSpacing($phpcsFile, $stackPtr);
101
102
        if (isset($tokens[$stackPtr]['scope_closer']) === false) {
103
            return;
104
        }
105
106
        $this->checkContentInside($phpcsFile, $stackPtr);
107
        $this->checkLeadingContent($phpcsFile, $stackPtr);
108
        $this->checkTrailingContent($phpcsFile, $stackPtr);
109
    }//end process()
110
111
112
    /**
113
     * Checks bracket spacing.
114
     *
115
     * @param File $phpcsFile The file being scanned.
116
     * @param int  $stackPtr  The position of the current token
117
     *                        in the stack passed in $tokens.
118
     *
119
     * @return void
120
     */
121
    protected function checkBracketSpacing(File $phpcsFile, $stackPtr)
122
    {
123
        $tokens = $phpcsFile->getTokens();
124
125
        if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) {
126
            return;
127
        }
128
129
        $parenOpener    = $tokens[$stackPtr]['parenthesis_opener'];
130
        $parenCloser    = $tokens[$stackPtr]['parenthesis_closer'];
131
        $spaceAfterOpen = 0;
132
        if ($tokens[($parenOpener + 1)]['code'] === T_WHITESPACE) {
133
            $spaceAfterOpen = $tokens[($parenOpener + 1)]['length'];
134
        }
135
136
        if ($spaceAfterOpen !== $this->requiredSpacesAfterOpen) {
137
            $error = 'Expected %s spaces after "%s" opening bracket; %s found';
138
            $data  = array(
139
                      $this->requiredSpacesAfterOpen,
140
                      $tokens[$stackPtr]['content'],
141
                      $spaceAfterOpen,
142
                     );
143
            $fix   = $phpcsFile->addFixableError($error, ($parenOpener + 1), 'SpacingAfterOpenBrace', $data);
144
145
            if ($fix === true) {
146
                $phpcsFile->fixer->beginChangeset();
147
148
                for ($i = $spaceAfterOpen; $i < $this->requiredSpacesAfterOpen; $i++) {
149
                    $phpcsFile->fixer->addContent($parenOpener, ' ');
150
                }
151
152
                $phpcsFile->fixer->endChangeset();
153
            }
154
        }
155
156
        if ($tokens[$parenOpener]['line'] === $tokens[$parenCloser]['line']) {
157
            $spaceBeforeClose = 0;
158
            if ($tokens[($parenCloser - 1)]['code'] === T_WHITESPACE) {
159
                $spaceBeforeClose = $tokens[($parenCloser - 1)]['length'];
160
            }
161
162
            if ($spaceBeforeClose !== $this->requiredSpacesBeforeClose) {
163
                $error = 'Expected %s spaces before "%s" closing bracket; %s found';
164
                $data  = array(
165
                          $this->requiredSpacesBeforeClose,
166
                          $tokens[$stackPtr]['content'],
167
                          $spaceBeforeClose,
168
                         );
169
                $fix   = $phpcsFile->addFixableError($error, ($parenCloser - 1), 'SpaceBeforeCloseBrace', $data);
170
171
                if ($fix === true) {
172
                    $phpcsFile->fixer->beginChangeset();
173
174
                    for ($i = $spaceBeforeClose; $i < $this->requiredSpacesBeforeClose; $i++) {
175
                        $phpcsFile->fixer->addContentBefore($parenCloser, ' ');
176
                    }
177
178
                    $phpcsFile->fixer->endChangeset();
179
                }
180
            }
181
        }//end if
182
    }//end checkBracketSpacing()
183
184
185
    /**
186
     * Checks content inside.
187
     *
188
     * @param File $phpcsFile The file being scanned.
189
     * @param int  $stackPtr  The position of the current token
190
     *                        in the stack passed in $tokens.
191
     *
192
     * @return void
193
     */
194
    protected function checkContentInside(File $phpcsFile, $stackPtr)
195
    {
196
        $tokens      = $phpcsFile->getTokens();
197
        $scopeOpener = $tokens[$stackPtr]['scope_opener'];
198
        $scopeCloser = $tokens[$stackPtr]['scope_closer'];
199
200
        $firstContent = $phpcsFile->findNext(
201
            T_WHITESPACE,
202
            ($scopeOpener + 1),
203
            null,
204
            true
205
        );
206
207
        if ($tokens[$firstContent]['line'] !== ($tokens[$scopeOpener]['line'] + 1)) {
208
            $data = array($tokens[$stackPtr]['content']);
209
            $diff = $tokens[$firstContent]['line'] - ($tokens[$scopeOpener]['line'] + 1);
210
            if ($diff < 0) {
211
                $error = 'Opening brace of the "%s" control structure must be last content on the line';
212
                $fix   = $phpcsFile->addFixableError($error, $scopeOpener, 'ContentAfterOpen', $data);
213
            } else {
214
                $data[] = $diff;
215
                $error  = 'Expected 0 blank lines at start of "%s" control structure; %s found';
216
                $fix    = $phpcsFile->addFixableError($error, $scopeOpener, 'SpacingBeforeOpen', $data);
217
            }
218
219
            if ($fix === true) {
220
                $phpcsFile->fixer->beginChangeset();
221
222
                for ($i = ($firstContent - 1); $i > $scopeOpener; $i--) {
223
                    if ($tokens[$i]['line'] === $tokens[$firstContent]['line']
224
                        || $tokens[$i]['line'] === $tokens[$scopeOpener]['line']
225
                    ) {
226
                        // Keep existing indentation.
227
                        continue;
228
                    }
229
230
                    $phpcsFile->fixer->replaceToken($i, '');
231
                }
232
233
                if ($diff < 0) {
234
                    $phpcsFile->fixer->addNewline($scopeOpener);
235
                }
236
237
                $phpcsFile->fixer->endChangeset();
238
            }
239
        }//end if
240
241
        if ($firstContent !== $scopeCloser) {
242
            // Not an empty control structure.
243
            $lastContent = $phpcsFile->findPrevious(
244
                T_WHITESPACE,
245
                ($scopeCloser - 1),
246
                null,
247
                true
248
            );
249
250
            if ($tokens[$lastContent]['line'] !== ($tokens[$scopeCloser]['line'] - 1)) {
251
                $data = array($tokens[$stackPtr]['content']);
252
                $diff = (($tokens[$scopeCloser]['line'] - 1) - $tokens[$lastContent]['line']);
253
254
                if ($diff < 0) {
255
                    $error = 'Closing brace of the "%s" control structure must be first content on the line';
256
                    $fix   = $phpcsFile->addFixableError($error, $scopeCloser, 'SpacingAfterClose', $data);
257
                } else {
258
                    $data[] = $diff;
259
                    $error  = 'Expected 0 blank lines at end of "%s" control structure; %s found';
260
                    $fix    = $phpcsFile->addFixableError($error, $scopeCloser, 'SpacingAfterClose', $data);
261
                }
262
263
                if ($fix === true) {
264
                    $phpcsFile->fixer->beginChangeset();
265
266
                    for ($i = ($lastContent + 1); $i < $scopeCloser; $i++) {
267
                        if ($tokens[$i]['line'] === $tokens[$scopeCloser]['line']
268
                            || $tokens[$i]['line'] === $tokens[$lastContent]['line']
269
                        ) {
270
                            // Keep existing indentation.
271
                            continue;
272
                        }
273
274
                        $phpcsFile->fixer->replaceToken($i, '');
275
                    }
276
277
                    if ($diff < 0) {
278
                        $phpcsFile->fixer->addNewline($lastContent);
279
                    }
280
281
                    $phpcsFile->fixer->endChangeset();
282
                }
283
            }//end if
284
        }//end if
285
    }//end checkContentInside()
286
287
288
    /**
289
     * Checks leading content.
290
     *
291
     * @param File $phpcsFile The file being scanned.
292
     * @param int  $stackPtr  The position of the current token
293
     *                        in the stack passed in $tokens.
294
     *
295
     * @return void
296
     */
297
    protected function checkLeadingContent(File $phpcsFile, $stackPtr)
298
    {
299
        $tokens                   = $phpcsFile->getTokens();
300
        $leadingContent           = $this->getLeadingContent($phpcsFile, $stackPtr);
301
        $controlStructureStartPtr = $this->getLeadingCommentOrSelf($phpcsFile, $stackPtr);
302
303
        if ($tokens[$leadingContent]['code'] === T_OPEN_TAG) {
304
            // At the beginning of the script or embedded code.
305
            return;
306
        }
307
308
        $firstNonWhitespace = $phpcsFile->findPrevious(
309
            T_WHITESPACE,
310
            ($controlStructureStartPtr - 1),
311
            $leadingContent,
0 ignored issues
show
Bug introduced by
It seems like $leadingContent defined by $this->getLeadingContent($phpcsFile, $stackPtr) on line 300 can also be of type boolean; however, PHP_CodeSniffer\Files\File::findPrevious() does only seem to accept integer|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
312
            true
313
        );
314
        $firstNonWhitespace = $firstNonWhitespace ?: $leadingContent;
315
        $leadingLineNumber  = $tokens[$firstNonWhitespace]['line'];
316
317
        if ($tokens[$leadingContent]['code'] === T_OPEN_CURLY_BRACKET
318
            || $this->insideSwitchCase($phpcsFile, $leadingContent) === true
0 ignored issues
show
Bug introduced by
It seems like $leadingContent defined by $this->getLeadingContent($phpcsFile, $stackPtr) on line 300 can also be of type boolean; however, CodingStandard\Sniffs\Wh...iff::insideSwitchCase() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
319
            || ($this->elseOrElseIf($phpcsFile, $stackPtr) === true && $this->ifOrElseIf($phpcsFile, $leadingContent) === true)
0 ignored issues
show
Bug introduced by
It seems like $leadingContent defined by $this->getLeadingContent($phpcsFile, $stackPtr) on line 300 can also be of type boolean; however, CodingStandard\Sniffs\Wh...cingSniff::ifOrElseIf() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
320
            || ($this->isCatch($phpcsFile, $stackPtr) === true && $this->isTryOrCatch($phpcsFile, $leadingContent) === true)
0 ignored issues
show
Bug introduced by
It seems like $leadingContent defined by $this->getLeadingContent($phpcsFile, $stackPtr) on line 300 can also be of type boolean; however, CodingStandard\Sniffs\Wh...ngSniff::isTryOrCatch() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
321
        ) {
322
            if ($this->isFunction($phpcsFile, $leadingContent) === true) {
0 ignored issues
show
Bug introduced by
It seems like $leadingContent defined by $this->getLeadingContent($phpcsFile, $stackPtr) on line 300 can also be of type boolean; however, CodingStandard\Sniffs\Wh...cingSniff::isFunction() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
323
                // The previous content is the opening brace of a function
324
                // so normal function rules apply and we can ignore it.
325
                return;
326
            }
327
328
            if ($this->isClosure($phpcsFile, $stackPtr, $leadingContent) === true) {
0 ignored issues
show
Bug introduced by
It seems like $leadingContent defined by $this->getLeadingContent($phpcsFile, $stackPtr) on line 300 can also be of type boolean; however, CodingStandard\Sniffs\Wh...acingSniff::isClosure() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
329
                return;
330
            }
331
332
            if ($tokens[$controlStructureStartPtr]['line'] !== ($leadingLineNumber + 1)) {
333
                $data = array($tokens[$stackPtr]['content']);
334
                $diff = $tokens[$controlStructureStartPtr]['line'] - ($leadingLineNumber + 1);
335
                if ($diff < 0) {
336
                    $error = 'Beginning of the "%s" control structure must be first content on the line';
337
                    $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'ContentBeforeStart', $data);
338
                } else {
339
                    $data[] = $diff;
340
                    $error  = 'Expected 0 blank lines before "%s" control structure; %s found';
341
                    $fix    = $phpcsFile->addFixableError($error, $stackPtr, 'LineBeforeOpen', $data);
342
                }
343
344
                if ($fix === true) {
345
                    $phpcsFile->fixer->beginChangeset();
346
347
                    for ($i = ($firstNonWhitespace + 1); $i < $controlStructureStartPtr; $i++) {
348
                        if ($tokens[$i]['line'] === $tokens[$controlStructureStartPtr]['line']) {
349
                            // Keep existing indentation.
350
                            break;
351
                        }
352
353
                        $phpcsFile->fixer->replaceToken($i, '');
354
                    }
355
356
                    $phpcsFile->fixer->addNewline($firstNonWhitespace);
357
                    $phpcsFile->fixer->endChangeset();
358
                }
359
            }//end if
360
        } elseif ($tokens[$controlStructureStartPtr]['line'] === ($leadingLineNumber + 1)) {
361
            // Code on the previous line before control structure start.
362
            $data  = array($tokens[$stackPtr]['content']);
363
            $error = 'No blank line found before "%s" control structure';
364
            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'NoLineBeforeOpen', $data);
365
366
            if ($fix === true) {
367
                $phpcsFile->fixer->beginChangeset();
368
                $phpcsFile->fixer->addNewline($firstNonWhitespace);
0 ignored issues
show
Bug introduced by
It seems like $firstNonWhitespace defined by $firstNonWhitespace ?: $leadingContent on line 314 can also be of type boolean; however, PHP_CodeSniffer\Fixer::addNewline() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
369
                $phpcsFile->fixer->endChangeset();
370
            }
371
        }//end if
372
    }//end checkLeadingContent()
373
374
375
    /**
376
     * Returns leading non-whitespace/comment token.
377
     *
378
     * @param File $phpcsFile All the tokens found in the document.
379
     * @param int  $stackPtr  The position of the current token
380
     *                        in the stack passed in $tokens.
381
     *
382
     * @return int|bool
383
     */
384
    protected function getLeadingContent(File $phpcsFile, $stackPtr)
385
    {
386
        $prevNonWhitespace = $phpcsFile->findPrevious(
387
            array(
388
             T_WHITESPACE,
389
             T_COMMENT,
390
            ),
391
            ($stackPtr - 1),
392
            null,
393
            true
394
        );
395
396
        return $prevNonWhitespace;
397
    }//end getLeadingContent()
398
399
    /**
400
     * Returns leading comment or self.
401
     *
402
     * @param File $phpcsFile All the tokens found in the document.
403
     * @param int  $stackPtr  The position of the current token
404
     *                        in the stack passed in $tokens.
405
     *
406
     * @return bool|int
407
     */
408
    protected function getLeadingCommentOrSelf(File $phpcsFile, $stackPtr)
409
    {
410
        $prevTokens = array($stackPtr);
411
        $tokens     = $phpcsFile->getTokens();
412
413
        do {
414
            $prev    = end($prevTokens);
415
            $newPrev = $phpcsFile->findPrevious(
416
                T_WHITESPACE,
417
                ($prev - 1),
418
                null,
419
                true
420
            );
421
422
            if ($tokens[$newPrev]['code'] === T_COMMENT
423
                && $tokens[$newPrev]['line'] === ($tokens[$prev]['line'] - 1)
424
            ) {
425
                $prevTokens[] = $newPrev;
426
            } else {
427
                break;
428
            }
429
        } while (true);
430
431
        return end($prevTokens);
432
    }//end getLeadingCommentOrSelf()
433
434
    /**
435
     * Checks trailing content.
436
     *
437
     * @param File $phpcsFile The file being scanned.
438
     * @param int  $stackPtr  The position of the current token
439
     *                        in the stack passed in $tokens.
440
     *
441
     * @return void
442
     */
443
    protected function checkTrailingContent(File $phpcsFile, $stackPtr)
444
    {
445
        $tokens                 = $phpcsFile->getTokens();
446
        $scopeCloser            = $this->getScopeCloser($phpcsFile, $stackPtr);
447
        $trailingContent        = $this->getTrailingContent($phpcsFile, $scopeCloser);
0 ignored issues
show
Bug introduced by
It seems like $scopeCloser defined by $this->getScopeCloser($phpcsFile, $stackPtr) on line 446 can also be of type boolean; however, CodingStandard\Sniffs\Wh...f::getTrailingContent() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
448
        $controlStructureEndPtr = $this->getTrailingCommentOrSelf($phpcsFile, $scopeCloser);
0 ignored issues
show
Bug introduced by
It seems like $scopeCloser defined by $this->getScopeCloser($phpcsFile, $stackPtr) on line 446 can also be of type boolean; however, CodingStandard\Sniffs\Wh...TrailingCommentOrSelf() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
449
450
        if ($tokens[$trailingContent]['code'] === T_CLOSE_TAG) {
451
            // At the end of the script or embedded code.
452
            return;
453
        }
454
455
        $lastNonWhitespace = $phpcsFile->findNext(
456
            T_WHITESPACE,
457
            ($controlStructureEndPtr + 1),
458
            $trailingContent,
0 ignored issues
show
Security Bug introduced by
It seems like $trailingContent defined by $this->getTrailingConten...hpcsFile, $scopeCloser) on line 447 can also be of type false; however, PHP_CodeSniffer\Files\File::findNext() does only seem to accept integer|null, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
459
            true
460
        );
461
        $lastNonWhitespace  = $lastNonWhitespace ?: $trailingContent;
462
        $trailingLineNumber = $tokens[$lastNonWhitespace]['line'];
463
464
        if ($tokens[$trailingContent]['code'] === T_CLOSE_CURLY_BRACKET
465
            || $this->insideSwitchCase($phpcsFile, $trailingContent) === true
0 ignored issues
show
Security Bug introduced by
It seems like $trailingContent defined by $this->getTrailingConten...hpcsFile, $scopeCloser) on line 447 can also be of type false; however, CodingStandard\Sniffs\Wh...iff::insideSwitchCase() does only seem to accept integer, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
466
        ) {
467
            if ($this->isFunction($phpcsFile, $trailingContent) === true) {
0 ignored issues
show
Security Bug introduced by
It seems like $trailingContent defined by $this->getTrailingConten...hpcsFile, $scopeCloser) on line 447 can also be of type false; however, CodingStandard\Sniffs\Wh...cingSniff::isFunction() does only seem to accept integer, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
468
                // The next content is the closing brace of a function
469
                // so normal function rules apply and we can ignore it.
470
                return;
471
            }
472
473
            if ($this->isClosure($phpcsFile, $stackPtr, $trailingContent) === true) {
0 ignored issues
show
Security Bug introduced by
It seems like $trailingContent defined by $this->getTrailingConten...hpcsFile, $scopeCloser) on line 447 can also be of type false; however, CodingStandard\Sniffs\Wh...acingSniff::isClosure() does only seem to accept integer, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
474
                return;
475
            }
476
477
            if ($tokens[$controlStructureEndPtr]['line'] !== ($trailingLineNumber - 1)) {
478
                $diff  = ($trailingLineNumber - 1) - $tokens[$controlStructureEndPtr]['line'];
479
                $data  = array(
480
                          $tokens[$stackPtr]['content'],
481
                          $diff,
482
                         );
483
                $error = 'Expected 0 blank lines after "%s" control structure; %s found';
484
                $fix   = $phpcsFile->addFixableError($error, $scopeCloser, 'LineAfterClose', $data);
0 ignored issues
show
Bug introduced by
It seems like $scopeCloser defined by $this->getScopeCloser($phpcsFile, $stackPtr) on line 446 can also be of type boolean; however, PHP_CodeSniffer\Files\File::addFixableError() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
485
486
                if ($fix === true) {
487
                    $phpcsFile->fixer->beginChangeset();
488
489
                    for ($i = ($controlStructureEndPtr + 1); $i < $lastNonWhitespace; $i++) {
490
                        if ($tokens[$i]['line'] === $tokens[$lastNonWhitespace]['line']) {
491
                            // Keep existing indentation.
492
                            break;
493
                        }
494
495
                        $phpcsFile->fixer->replaceToken($i, '');
496
                    }
497
498
                    $phpcsFile->fixer->addNewline($controlStructureEndPtr);
499
                    $phpcsFile->fixer->endChangeset();
500
                }
501
            }//end if
502
        } elseif ($tokens[$controlStructureEndPtr]['line'] === ($trailingLineNumber - 1)) {
503
            // Code on the next line after control structure scope closer.
504
            if ($this->elseOrElseIf($phpcsFile, $trailingContent) === true
0 ignored issues
show
Security Bug introduced by
It seems like $trailingContent defined by $this->getTrailingConten...hpcsFile, $scopeCloser) on line 447 can also be of type false; however, CodingStandard\Sniffs\Wh...ngSniff::elseOrElseIf() does only seem to accept integer, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
505
                || $this->isCatch($phpcsFile, $trailingContent) === true
0 ignored issues
show
Security Bug introduced by
It seems like $trailingContent defined by $this->getTrailingConten...hpcsFile, $scopeCloser) on line 447 can also be of type false; however, CodingStandard\Sniffs\Wh...SpacingSniff::isCatch() does only seem to accept integer, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
506
            ) {
507
                return;
508
            }
509
510
            $error = 'No blank line found after "%s" control structure';
511
            $data  = array($tokens[$stackPtr]['content']);
512
            $fix   = $phpcsFile->addFixableError($error, $scopeCloser, 'NoLineAfterClose', $data);
0 ignored issues
show
Bug introduced by
It seems like $scopeCloser defined by $this->getScopeCloser($phpcsFile, $stackPtr) on line 446 can also be of type boolean; however, PHP_CodeSniffer\Files\File::addFixableError() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
513
514
            if ($fix === true) {
515
                $phpcsFile->fixer->beginChangeset();
516
                $phpcsFile->fixer->addNewline($controlStructureEndPtr);
517
                $phpcsFile->fixer->endChangeset();
518
            }
519
        }//end if
520
    }//end checkTrailingContent()
521
522
523
    /**
524
     * Returns scope closer  with special check for "do...while" statements.
525
     *
526
     * @param File $phpcsFile All the tokens found in the document.
527
     * @param int  $stackPtr  The position of the current token
528
     *                        in the stack passed in $tokens.
529
     *
530
     * @return int|bool
531
     */
532
    protected function getScopeCloser(File $phpcsFile, $stackPtr)
533
    {
534
        $tokens      = $phpcsFile->getTokens();
535
        $scopeCloser = $tokens[$stackPtr]['scope_closer'];
536
537
        if ($tokens[$stackPtr]['code'] !== T_DO) {
538
            return $scopeCloser;
539
        }
540
541
        $trailingContent = $phpcsFile->findNext(
542
            Tokens::$emptyTokens,
543
            ($scopeCloser + 1),
544
            null,
545
            true
546
        );
547
548
        if ($tokens[$trailingContent]['code'] === T_WHILE) {
549
            return ($tokens[$trailingContent]['parenthesis_closer'] + 1);
550
        }
551
552
        // @codeCoverageIgnoreStart
553
        $phpcsFile->addError('Expected "while" not found after "do"', $stackPtr, 'InvalidDo');
554
555
        return $scopeCloser;
556
        // @codeCoverageIgnoreEnd
557
    }//end getScopeCloser()
558
559
560
    /**
561
     * Returns trailing content token.
562
     *
563
     * @param File $phpcsFile All the tokens found in the document.
564
     * @param int  $stackPtr  The position of the current token
565
     *                        in the stack passed in $tokens.
566
     *
567
     * @return int|bool
568
     */
569
    protected function getTrailingContent(File $phpcsFile, $stackPtr)
570
    {
571
        $nextNonWhitespace = $phpcsFile->findNext(
572
            array(
573
             T_WHITESPACE,
574
             T_COMMENT,
575
            ),
576
            ($stackPtr + 1),
577
            null,
578
            true
579
        );
580
581
        return $nextNonWhitespace;
582
    }//end getTrailingContent()
583
584
585
    /**
586
     * Returns trailing comment or self.
587
     *
588
     * @param File $phpcsFile All the tokens found in the document.
589
     * @param int  $stackPtr  The position of the current token
590
     *                        in the stack passed in $tokens.
591
     *
592
     * @return bool|int
593
     */
594
    protected function getTrailingCommentOrSelf(File $phpcsFile, $stackPtr)
595
    {
596
        $nextTokens = array($stackPtr);
597
        $tokens = $phpcsFile->getTokens();
598
599
        do {
600
            $next    = end($nextTokens);
601
            $newNext = $phpcsFile->findNext(
602
                T_WHITESPACE,
603
                ($next + 1),
604
                null,
605
                true
606
            );
607
608
            if ($tokens[$newNext]['code'] === T_COMMENT
609
                && $tokens[$newNext]['line'] === ($tokens[$next]['line'] + 1)
610
            ) {
611
                $nextTokens[] = $newNext;
612
            } else {
613
                break;
614
            }
615
        } while (true);
616
617
        return end($nextTokens);
618
    }//end getTrailingCommentOrSelf()
619
620
621
    /**
622
     * Finds first token on a line.
623
     *
624
     * @param File $phpcsFile All the tokens found in the document.
625
     * @param int  $start     Start from token.
626
     *
627
     * @return int | bool
628
     */
629
    public function findFirstOnLine(File $phpcsFile, $start)
630
    {
631
        $tokens = $phpcsFile->getTokens();
632
633
        for ($i = $start; $i >= 0; $i--) {
634
            if ($tokens[$i]['line'] === $tokens[$start]['line']) {
635
                continue;
636
            }
637
638
            return ($i + 1);
639
        }
640
641
        return false;
642
    }//end findFirstOnLine()
643
644
645
    /**
646
     * Detects, that we're at the edge (beginning or ending) of CASE/DEFAULT with SWITCH statement.
647
     *
648
     * @param File $phpcsFile The file being scanned.
649
     * @param int  $stackPtr  The position of the current token
650
     *                        in the stack passed in $tokens.
651
     *
652
     * @return bool
653
     */
654
    protected function insideSwitchCase(File $phpcsFile, $stackPtr)
655
    {
656
        if ($this->isScopeCondition($phpcsFile, $stackPtr, array(T_CASE, T_DEFAULT)) === true) {
657
            $tokens = $phpcsFile->getTokens();
658
659
            // Consider "return" instead of "break" as function ending to enforce empty line before it.
660
            return $tokens[$stackPtr]['code'] !== T_RETURN;
661
        }
662
663
        return false;
664
    }//end insideSwitchCase()
665
666
667
    /**
668
     * Detects, that it is a closing brace of IF/ELSEIF.
669
     *
670
     * @param File $phpcsFile The file being scanned.
671
     * @param int  $stackPtr  The position of the current token
672
     *                        in the stack passed in $tokens.
673
     *
674
     * @return bool
675
     */
676
    protected function ifOrElseIf(File $phpcsFile, $stackPtr)
677
    {
678
        return $this->isScopeCondition($phpcsFile, $stackPtr, array(T_IF, T_ELSEIF));
679
    }//end ifOrElseIf()
680
681
682
    /**
683
     * Detects, that it is a closing brace of ELSE/ELSEIF.
684
     *
685
     * @param File $phpcsFile The file being scanned.
686
     * @param int  $stackPtr  The position of the current token
687
     *                        in the stack passed in $tokens.
688
     *
689
     * @return bool
690
     */
691
    protected function elseOrElseIf(File $phpcsFile, $stackPtr)
692
    {
693
        return $this->isScopeCondition($phpcsFile, $stackPtr, array(T_ELSE, T_ELSEIF));
694
    }//end elseOrElseIf()
695
696
697
    /**
698
     * Detects, that it is a closing brace of TRY/CATCH.
699
     *
700
     * @param File $phpcsFile The file being scanned.
701
     * @param int  $stackPtr  The position of the current token
702
     *                        in the stack passed in $tokens.
703
     *
704
     * @return bool
705
     */
706
    protected function isTryOrCatch(File $phpcsFile, $stackPtr)
707
    {
708
        return $this->isScopeCondition($phpcsFile, $stackPtr, array(T_TRY, T_CATCH));
709
    }//end isTryOrCatch()
710
711
712
    /**
713
     * Detects, that it is a closing brace of TRY.
714
     *
715
     * @param File $phpcsFile The file being scanned.
716
     * @param int  $stackPtr  The position of the current token
717
     *                        in the stack passed in $tokens.
718
     *
719
     * @return bool
720
     */
721
    protected function isTry(File $phpcsFile, $stackPtr)
722
    {
723
        return $this->isScopeCondition($phpcsFile, $stackPtr, T_TRY);
724
    }//end isTry()
725
726
727
    /**
728
     * Detects, that it is a closing brace of CATCH.
729
     *
730
     * @param File $phpcsFile The file being scanned.
731
     * @param int  $stackPtr  The position of the current token
732
     *                        in the stack passed in $tokens.
733
     *
734
     * @return bool
735
     */
736
    protected function isCatch(File $phpcsFile, $stackPtr)
737
    {
738
        return $this->isScopeCondition($phpcsFile, $stackPtr, T_CATCH);
739
    }//end isCatch()
740
741
742
    /**
743
     * Determines that a function is located at given position.
744
     *
745
     * @param File $phpcsFile The file being scanned.
746
     * @param int  $stackPtr  The position of the current token
747
     *                        in the stack passed in $tokens.
748
     *
749
     * @return bool
750
     */
751
    protected function isFunction(File $phpcsFile, $stackPtr)
752
    {
753
        return $this->isScopeCondition($phpcsFile, $stackPtr, T_FUNCTION);
754
    }//end isFunction()
755
756
757
    /**
758
     * Determines that a closure is located at given position.
759
     *
760
     * @param File $phpcsFile         The file being scanned.
761
     * @param int  $stackPtr          The position of the current token.
762
     *                                in the stack passed in $tokens.
763
     * @param int  $scopeConditionPtr Position of scope condition.
764
     *
765
     * @return bool
766
     */
767
    protected function isClosure(File $phpcsFile, $stackPtr, $scopeConditionPtr)
768
    {
769
        $tokens = $phpcsFile->getTokens();
770
771
        if ($this->isScopeCondition($phpcsFile, $scopeConditionPtr, T_CLOSURE) === true
772
            && ($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true
773
            || $phpcsFile->hasCondition($stackPtr, T_CLOSURE) === true
774
            || isset($tokens[$stackPtr]['nested_parenthesis']) === true)
775
        ) {
776
            return true;
777
        }
778
779
        return false;
780
    }//end isClosure()
781
782
783
    /**
784
     * Detects, that it is a closing brace of ELSE/ELSEIF.
785
     *
786
     * @param File      $phpcsFile The file being scanned.
787
     * @param int       $stackPtr  The position of the current token
788
     *                             in the stack passed in $tokens.
789
     * @param int|array $types     The type(s) of tokens to search for.
790
     *
791
     * @return bool
792
     */
793
    protected function isScopeCondition(File $phpcsFile, $stackPtr, $types)
794
    {
795
        $tokens = $phpcsFile->getTokens();
796
797
        if (isset($tokens[$stackPtr]['scope_condition']) === true) {
798
            $owner = $tokens[$stackPtr]['scope_condition'];
799
800
            if (in_array($tokens[$owner]['code'], (array)$types) === true) {
801
                return true;
802
            }
803
        }
804
805
        return false;
806
    }//end isScopeCondition()
807
}//end class
808