Failed Conditions
Push — master ( c35fd9...2f93af )
by Alexander
02:57
created

getTrailingCommentOrSelf()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
nc 2
nop 2
dl 0
loc 25
ccs 15
cts 15
cp 1
crap 4
rs 9.52
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 1
    public function register()
69
    {
70
        return array(
71 1
                T_IF,
72 1
                T_WHILE,
73 1
                T_FOREACH,
74 1
                T_FOR,
75 1
                T_SWITCH,
76 1
                T_DO,
77 1
                T_ELSE,
78 1
                T_ELSEIF,
79 1
                T_TRY,
80 1
                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 1
    public function process(File $phpcsFile, $stackPtr)
95
    {
96 1
        $this->requiredSpacesAfterOpen   = (int) $this->requiredSpacesAfterOpen;
97 1
        $this->requiredSpacesBeforeClose = (int) $this->requiredSpacesBeforeClose;
98 1
        $tokens = $phpcsFile->getTokens();
99
100 1
        $this->checkBracketSpacing($phpcsFile, $stackPtr);
101
102 1
        if (isset($tokens[$stackPtr]['scope_closer']) === false) {
103 1
            return;
104
        }
105
106 1
        $this->checkContentInside($phpcsFile, $stackPtr);
107 1
        $this->checkLeadingContent($phpcsFile, $stackPtr);
108 1
        $this->checkTrailingContent($phpcsFile, $stackPtr);
109 1
    }//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 1
    protected function checkBracketSpacing(File $phpcsFile, $stackPtr)
122
    {
123 1
        $tokens = $phpcsFile->getTokens();
124
125 1
        if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) {
126 1
            return;
127
        }
128
129 1
        $parenOpener    = $tokens[$stackPtr]['parenthesis_opener'];
130 1
        $parenCloser    = $tokens[$stackPtr]['parenthesis_closer'];
131 1
        $spaceAfterOpen = 0;
132 1
        if ($tokens[($parenOpener + 1)]['code'] === T_WHITESPACE) {
133 1
            $spaceAfterOpen = $tokens[($parenOpener + 1)]['length'];
134
        }
135
136 1
        if ($spaceAfterOpen !== $this->requiredSpacesAfterOpen) {
137 1
            $error = 'Expected %s spaces after "%s" opening bracket; %s found';
138
            $data  = array(
139 1
                      $this->requiredSpacesAfterOpen,
140 1
                      $tokens[$stackPtr]['content'],
141 1
                      $spaceAfterOpen,
142
                     );
143 1
            $fix   = $phpcsFile->addFixableError($error, ($parenOpener + 1), 'SpacingAfterOpenBrace', $data);
144
145 1
            if ($fix === true) {
146 1
                $phpcsFile->fixer->beginChangeset();
147
148 1
                for ($i = $spaceAfterOpen; $i < $this->requiredSpacesAfterOpen; $i++) {
149 1
                    $phpcsFile->fixer->addContent($parenOpener, ' ');
150
                }
151
152 1
                $phpcsFile->fixer->endChangeset();
153
            }
154
        }
155
156 1
        if ($tokens[$parenOpener]['line'] === $tokens[$parenCloser]['line']) {
157 1
            $spaceBeforeClose = 0;
158 1
            if ($tokens[($parenCloser - 1)]['code'] === T_WHITESPACE) {
159 1
                $spaceBeforeClose = $tokens[($parenCloser - 1)]['length'];
160
            }
161
162 1
            if ($spaceBeforeClose !== $this->requiredSpacesBeforeClose) {
163 1
                $error = 'Expected %s spaces before "%s" closing bracket; %s found';
164
                $data  = array(
165 1
                          $this->requiredSpacesBeforeClose,
166 1
                          $tokens[$stackPtr]['content'],
167 1
                          $spaceBeforeClose,
168
                         );
169 1
                $fix   = $phpcsFile->addFixableError($error, ($parenCloser - 1), 'SpaceBeforeCloseBrace', $data);
170
171 1
                if ($fix === true) {
172 1
                    $phpcsFile->fixer->beginChangeset();
173
174 1
                    for ($i = $spaceBeforeClose; $i < $this->requiredSpacesBeforeClose; $i++) {
175 1
                        $phpcsFile->fixer->addContentBefore($parenCloser, ' ');
176
                    }
177
178 1
                    $phpcsFile->fixer->endChangeset();
179
                }
180
            }
181
        }//end if
182 1
    }//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 1
    protected function checkContentInside(File $phpcsFile, $stackPtr)
195
    {
196 1
        $tokens      = $phpcsFile->getTokens();
197 1
        $scopeOpener = $tokens[$stackPtr]['scope_opener'];
198 1
        $scopeCloser = $tokens[$stackPtr]['scope_closer'];
199
200 1
        $firstContent = $phpcsFile->findNext(
201 1
            T_WHITESPACE,
202 1
            ($scopeOpener + 1),
203 1
            null,
204 1
            true
205
        );
206
207 1
        if ($tokens[$firstContent]['line'] !== ($tokens[$scopeOpener]['line'] + 1)) {
208 1
            $data = array($tokens[$stackPtr]['content']);
209 1
            $diff = $tokens[$firstContent]['line'] - ($tokens[$scopeOpener]['line'] + 1);
210 1
            if ($diff < 0) {
211 1
                $error = 'Opening brace of the "%s" control structure must be last content on the line';
212 1
                $fix   = $phpcsFile->addFixableError($error, $scopeOpener, 'ContentAfterOpen', $data);
213
            } else {
214 1
                $data[] = $diff;
215 1
                $error  = 'Expected 0 blank lines at start of "%s" control structure; %s found';
216 1
                $fix    = $phpcsFile->addFixableError($error, $scopeOpener, 'SpacingBeforeOpen', $data);
217
            }
218
219 1
            if ($fix === true) {
220 1
                $phpcsFile->fixer->beginChangeset();
221
222 1
                for ($i = ($firstContent - 1); $i > $scopeOpener; $i--) {
223 1
                    if ($tokens[$i]['line'] === $tokens[$firstContent]['line']
224 1
                        || $tokens[$i]['line'] === $tokens[$scopeOpener]['line']
225
                    ) {
226
                        // Keep existing indentation.
227 1
                        continue;
228
                    }
229
230 1
                    $phpcsFile->fixer->replaceToken($i, '');
231
                }
232
233 1
                if ($diff < 0) {
234 1
                    $phpcsFile->fixer->addNewline($scopeOpener);
235
                }
236
237 1
                $phpcsFile->fixer->endChangeset();
238
            }
239
        }//end if
240
241 1
        if ($firstContent !== $scopeCloser) {
242
            // Not an empty control structure.
243 1
            $lastContent = $phpcsFile->findPrevious(
244 1
                T_WHITESPACE,
245 1
                ($scopeCloser - 1),
246 1
                null,
247 1
                true
248
            );
249
250 1
            if ($tokens[$lastContent]['line'] !== ($tokens[$scopeCloser]['line'] - 1)) {
251 1
                $data = array($tokens[$stackPtr]['content']);
252 1
                $diff = (($tokens[$scopeCloser]['line'] - 1) - $tokens[$lastContent]['line']);
253
254 1
                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 1
                    $data[] = $diff;
259 1
                    $error  = 'Expected 0 blank lines at end of "%s" control structure; %s found';
260 1
                    $fix    = $phpcsFile->addFixableError($error, $scopeCloser, 'SpacingAfterClose', $data);
261
                }
262
263 1
                if ($fix === true) {
264 1
                    $phpcsFile->fixer->beginChangeset();
265
266 1
                    for ($i = ($lastContent + 1); $i < $scopeCloser; $i++) {
267 1
                        if ($tokens[$i]['line'] === $tokens[$scopeCloser]['line']
268 1
                            || $tokens[$i]['line'] === $tokens[$lastContent]['line']
269
                        ) {
270
                            // Keep existing indentation.
271 1
                            continue;
272
                        }
273
274 1
                        $phpcsFile->fixer->replaceToken($i, '');
275
                    }
276
277 1
                    if ($diff < 0) {
278
                        $phpcsFile->fixer->addNewline($lastContent);
279
                    }
280
281 1
                    $phpcsFile->fixer->endChangeset();
282
                }
283
            }//end if
284
        }//end if
285 1
    }//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 1
    protected function checkLeadingContent(File $phpcsFile, $stackPtr)
298
    {
299 1
        $tokens                   = $phpcsFile->getTokens();
300 1
        $leadingContent           = $this->getLeadingContent($phpcsFile, $stackPtr);
301 1
        $controlStructureStartPtr = $this->getLeadingCommentOrSelf($phpcsFile, $stackPtr);
302
303 1
        if ($tokens[$leadingContent]['code'] === T_OPEN_TAG) {
304
            // At the beginning of the script or embedded code.
305 1
            return;
306
        }
307
308 1
        $firstNonWhitespace = $phpcsFile->findPrevious(
309 1
            T_WHITESPACE,
310 1
            ($controlStructureStartPtr - 1),
311 1
            $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 1
            true
313
        );
314 1
        $firstNonWhitespace = $firstNonWhitespace ?: $leadingContent;
315 1
        $leadingLineNumber  = $tokens[$firstNonWhitespace]['line'];
316
317 1
        if ($tokens[$leadingContent]['code'] === T_OPEN_CURLY_BRACKET
318 1
            || $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 1
            || ($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 1
            || ($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 1
            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 1
                return;
326
            }
327
328 1
            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 1
            if ($tokens[$controlStructureStartPtr]['line'] !== ($leadingLineNumber + 1)) {
333 1
                $data = array($tokens[$stackPtr]['content']);
334 1
                $diff = $tokens[$controlStructureStartPtr]['line'] - ($leadingLineNumber + 1);
335 1
                if ($diff < 0) {
336 1
                    $error = 'Beginning of the "%s" control structure must be first content on the line';
337 1
                    $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'ContentBeforeStart', $data);
338
                } else {
339 1
                    $data[] = $diff;
340 1
                    $error  = 'Expected 0 blank lines before "%s" control structure; %s found';
341 1
                    $fix    = $phpcsFile->addFixableError($error, $stackPtr, 'LineBeforeOpen', $data);
342
                }
343
344 1
                if ($fix === true) {
345 1
                    $phpcsFile->fixer->beginChangeset();
346
347 1
                    for ($i = ($firstNonWhitespace + 1); $i < $controlStructureStartPtr; $i++) {
348 1
                        if ($tokens[$i]['line'] === $tokens[$controlStructureStartPtr]['line']) {
349
                            // Keep existing indentation.
350 1
                            break;
351
                        }
352
353 1
                        $phpcsFile->fixer->replaceToken($i, '');
354
                    }
355
356 1
                    $phpcsFile->fixer->addNewline($firstNonWhitespace);
357 1
                    $phpcsFile->fixer->endChangeset();
358
                }
359
            }//end if
360 1
        } elseif ($tokens[$controlStructureStartPtr]['line'] === ($leadingLineNumber + 1)) {
361
            // Code on the previous line before control structure start.
362 1
            $data  = array($tokens[$stackPtr]['content']);
363 1
            $error = 'No blank line found before "%s" control structure';
364 1
            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'NoLineBeforeOpen', $data);
365
366 1
            if ($fix === true) {
367 1
                $phpcsFile->fixer->beginChangeset();
368 1
                $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 1
                $phpcsFile->fixer->endChangeset();
370
            }
371
        }//end if
372 1
    }//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 1
    protected function getLeadingContent(File $phpcsFile, $stackPtr)
385
    {
386 1
        $prevNonWhitespace = $phpcsFile->findPrevious(
387
            array(
388 1
             T_WHITESPACE,
389 1
             T_COMMENT,
390
            ),
391 1
            ($stackPtr - 1),
392 1
            null,
393 1
            true
394
        );
395
396 1
        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 1
    protected function getLeadingCommentOrSelf(File $phpcsFile, $stackPtr)
409
    {
410 1
        $prevTokens = array($stackPtr);
411 1
        $tokens     = $phpcsFile->getTokens();
412
413
        do {
414 1
            $prev    = end($prevTokens);
415 1
            $newPrev = $phpcsFile->findPrevious(
416 1
                T_WHITESPACE,
417 1
                ($prev - 1),
418 1
                null,
419 1
                true
420
            );
421
422 1
            if ($tokens[$newPrev]['code'] === T_COMMENT
423 1
                && $tokens[$newPrev]['line'] === ($tokens[$prev]['line'] - 1)
424
            ) {
425 1
                $prevTokens[] = $newPrev;
426
            } else {
427 1
                break;
428
            }
429 1
        } while (true);
430
431 1
        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 1
    protected function checkTrailingContent(File $phpcsFile, $stackPtr)
444
    {
445 1
        $tokens                 = $phpcsFile->getTokens();
446 1
        $scopeCloser            = $this->getScopeCloser($phpcsFile, $stackPtr);
447 1
        $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 1
        $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 1
        if ($tokens[$trailingContent]['code'] === T_CLOSE_TAG) {
451
            // At the end of the script or embedded code.
452 1
            return;
453
        }
454
455 1
        $lastNonWhitespace = $phpcsFile->findNext(
456 1
            T_WHITESPACE,
457 1
            ($controlStructureEndPtr + 1),
458 1
            $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 1
            true
460
        );
461 1
        $lastNonWhitespace  = $lastNonWhitespace ?: $trailingContent;
462 1
        $trailingLineNumber = $tokens[$lastNonWhitespace]['line'];
463
464 1
        if ($tokens[$trailingContent]['code'] === T_CLOSE_CURLY_BRACKET
465 1
            || $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 1
            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 1
                return;
471
            }
472
473 1
            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 1
            if ($tokens[$controlStructureEndPtr]['line'] !== ($trailingLineNumber - 1)) {
478 1
                $diff  = ($trailingLineNumber - 1) - $tokens[$controlStructureEndPtr]['line'];
479
                $data  = array(
480 1
                          $tokens[$stackPtr]['content'],
481 1
                          $diff,
482
                         );
483 1
                $error = 'Expected 0 blank lines after "%s" control structure; %s found';
484 1
                $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 1
                if ($fix === true) {
487 1
                    $phpcsFile->fixer->beginChangeset();
488
489 1
                    for ($i = ($controlStructureEndPtr + 1); $i < $lastNonWhitespace; $i++) {
490 1
                        if ($tokens[$i]['line'] === $tokens[$lastNonWhitespace]['line']) {
491
                            // Keep existing indentation.
492 1
                            break;
493
                        }
494
495 1
                        $phpcsFile->fixer->replaceToken($i, '');
496
                    }
497
498 1
                    $phpcsFile->fixer->addNewline($controlStructureEndPtr);
499 1
                    $phpcsFile->fixer->endChangeset();
500
                }
501
            }//end if
502 1
        } elseif ($tokens[$controlStructureEndPtr]['line'] === ($trailingLineNumber - 1)) {
503
            // Code on the next line after control structure scope closer.
504 1
            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 1
                || $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 1
                return;
508
            }
509
510 1
            $error = 'No blank line found after "%s" control structure';
511 1
            $data  = array($tokens[$stackPtr]['content']);
512 1
            $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 1
            if ($fix === true) {
515 1
                $phpcsFile->fixer->beginChangeset();
516 1
                $phpcsFile->fixer->addNewline($controlStructureEndPtr);
517 1
                $phpcsFile->fixer->endChangeset();
518
            }
519
        }//end if
520 1
    }//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 1
    protected function getScopeCloser(File $phpcsFile, $stackPtr)
533
    {
534 1
        $tokens      = $phpcsFile->getTokens();
535 1
        $scopeCloser = $tokens[$stackPtr]['scope_closer'];
536
537 1
        if ($tokens[$stackPtr]['code'] !== T_DO) {
538 1
            return $scopeCloser;
539
        }
540
541 1
        $trailingContent = $phpcsFile->findNext(
542 1
            Tokens::$emptyTokens,
543 1
            ($scopeCloser + 1),
544 1
            null,
545 1
            true
546
        );
547
548 1
        if ($tokens[$trailingContent]['code'] === T_WHILE) {
549 1
            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 1
    protected function getTrailingContent(File $phpcsFile, $stackPtr)
570
    {
571 1
        $nextNonWhitespace = $phpcsFile->findNext(
572
            array(
573 1
             T_WHITESPACE,
574 1
             T_COMMENT,
575
            ),
576 1
            ($stackPtr + 1),
577 1
            null,
578 1
            true
579
        );
580
581 1
        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 1
    protected function getTrailingCommentOrSelf(File $phpcsFile, $stackPtr)
595
    {
596 1
        $nextTokens = array($stackPtr);
597 1
        $tokens = $phpcsFile->getTokens();
598
599
        do {
600 1
            $next    = end($nextTokens);
601 1
            $newNext = $phpcsFile->findNext(
602 1
                T_WHITESPACE,
603 1
                ($next + 1),
604 1
                null,
605 1
                true
606
            );
607
608 1
            if ($tokens[$newNext]['code'] === T_COMMENT
609 1
                && $tokens[$newNext]['line'] === ($tokens[$next]['line'] + 1)
610
            ) {
611 1
                $nextTokens[] = $newNext;
612
            } else {
613 1
                break;
614
            }
615 1
        } while (true);
616
617 1
        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 1
    protected function insideSwitchCase(File $phpcsFile, $stackPtr)
655
    {
656 1
        if ($this->isScopeCondition($phpcsFile, $stackPtr, array(T_CASE, T_DEFAULT)) === true) {
657 1
            $tokens = $phpcsFile->getTokens();
658
659
            // Consider "return" instead of "break" as function ending to enforce empty line before it.
660 1
            return $tokens[$stackPtr]['code'] !== T_RETURN;
661
        }
662
663 1
        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 1
    protected function ifOrElseIf(File $phpcsFile, $stackPtr)
677
    {
678 1
        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 1
    protected function elseOrElseIf(File $phpcsFile, $stackPtr)
692
    {
693 1
        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 1
    protected function isTryOrCatch(File $phpcsFile, $stackPtr)
707
    {
708 1
        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 1
    protected function isCatch(File $phpcsFile, $stackPtr)
737
    {
738 1
        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 1
    protected function isFunction(File $phpcsFile, $stackPtr)
752
    {
753 1
        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 1
    protected function isClosure(File $phpcsFile, $stackPtr, $scopeConditionPtr)
768
    {
769 1
        $tokens = $phpcsFile->getTokens();
770
771 1
        if ($this->isScopeCondition($phpcsFile, $scopeConditionPtr, T_CLOSURE) === true
772
            && ($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true
773
            || $phpcsFile->hasCondition($stackPtr, T_CLOSURE) === true
774 1
            || isset($tokens[$stackPtr]['nested_parenthesis']) === true)
775
        ) {
776
            return true;
777
        }
778
779 1
        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 1
    protected function isScopeCondition(File $phpcsFile, $stackPtr, $types)
794
    {
795 1
        $tokens = $phpcsFile->getTokens();
796
797 1
        if (isset($tokens[$stackPtr]['scope_condition']) === true) {
798 1
            $owner = $tokens[$stackPtr]['scope_condition'];
799
800 1
            if (in_array($tokens[$owner]['code'], (array)$types) === true) {
801 1
                return true;
802
            }
803
        }
804
805 1
        return false;
806
    }//end isScopeCondition()
807
}//end class
808