Failed Conditions
Push — master ( ad3046...3455e4 )
by Alexander
02:18
created

ControlStructureSpacingSniff::getScopeCloser()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 27
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 3
eloc 14
c 3
b 0
f 0
nc 3
nop 2
dl 0
loc 27
rs 8.8571
ccs 12
cts 12
cp 1
crap 3
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
/**
17
 * CodingStandard_Sniffs_WhiteSpace_ControlStructureSpacingSniff.
18
 *
19
 * Checks that control structures have the correct spacing around brackets.
20
 *
21
 * @category PHP
22
 * @package  PHP_CodeSniffer
23
 * @author   Greg Sherwood <[email protected]>
24
 * @author   Marc McIntyre <[email protected]>
25
 * @author   Alexander Obuhovich <[email protected]>
26
 * @license  https://github.com/aik099/CodingStandard/blob/master/LICENSE BSD 3-Clause
27
 * @link     https://github.com/aik099/CodingStandard
28
 */
29
class CodingStandard_Sniffs_WhiteSpace_ControlStructureSpacingSniff implements PHP_CodeSniffer_Sniff
0 ignored issues
show
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
30
{
31
32
    /**
33
     * A list of tokenizers this sniff supports.
34
     *
35
     * @var array
36
     */
37
    public $supportedTokenizers = array(
38
                                   'PHP',
39
                                   'JS',
40
                                  );
41
42
    /**
43
     * How many spaces should follow the opening bracket.
44
     *
45
     * @var int
46
     */
47
    public $requiredSpacesAfterOpen = 1;
48
49
    /**
50
     * How many spaces should precede the closing bracket.
51
     *
52
     * @var int
53
     */
54
    public $requiredSpacesBeforeClose = 1;
55
56
57
    /**
58
     * Returns an array of tokens this test wants to listen for.
59
     *
60
     * @return integer[]
61
     */
62 1
    public function register()
63
    {
64
        return array(
65 1
                T_IF,
66 1
                T_WHILE,
67 1
                T_FOREACH,
68 1
                T_FOR,
69 1
                T_SWITCH,
70 1
                T_DO,
71 1
                T_ELSE,
72 1
                T_ELSEIF,
73 1
                T_TRY,
74 1
                T_CATCH,
75 1
               );
76
77
    }//end register()
78
79
80
    /**
81
     * Processes this test, when one of its tokens is encountered.
82
     *
83
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
84
     * @param int                  $stackPtr  The position of the current token
85
     *                                        in the stack passed in $tokens.
86
     *
87
     * @return void
88
     */
89 1
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
90
    {
91 1
        $this->requiredSpacesAfterOpen   = (int) $this->requiredSpacesAfterOpen;
92 1
        $this->requiredSpacesBeforeClose = (int) $this->requiredSpacesBeforeClose;
93 1
        $tokens = $phpcsFile->getTokens();
94
95 1
        $this->checkBracketSpacing($phpcsFile, $stackPtr);
96
97 1
        if (isset($tokens[$stackPtr]['scope_closer']) === false) {
98 1
            return;
99
        }
100
101 1
        $this->checkContentInside($phpcsFile, $stackPtr);
102 1
        $this->checkLeadingContent($phpcsFile, $stackPtr);
103 1
        $this->checkTrailingContent($phpcsFile, $stackPtr);
104
105 1
    }//end process()
106
107
108
    /**
109
     * Checks bracket spacing.
110
     *
111
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
112
     * @param int                  $stackPtr  The position of the current token
113
     *                                        in the stack passed in $tokens.
114
     *
115
     * @return void
116
     */
117 1
    protected function checkBracketSpacing(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
118
    {
119 1
        $tokens = $phpcsFile->getTokens();
120
121 1
        if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) {
122 1
            return;
123
        }
124
125 1
        $parenOpener    = $tokens[$stackPtr]['parenthesis_opener'];
126 1
        $parenCloser    = $tokens[$stackPtr]['parenthesis_closer'];
127 1
        $spaceAfterOpen = 0;
128 1
        if ($tokens[($parenOpener + 1)]['code'] === T_WHITESPACE) {
129 1
            $spaceAfterOpen = $tokens[($parenOpener + 1)]['length'];
130 1
        }
131
132 1
        if ($spaceAfterOpen !== $this->requiredSpacesAfterOpen) {
133 1
            $error = 'Expected %s spaces after "%s" opening bracket; %s found';
134
            $data  = array(
135 1
                      $this->requiredSpacesAfterOpen,
136 1
                      $tokens[$stackPtr]['content'],
137 1
                      $spaceAfterOpen,
138 1
                     );
139 1
            $fix   = $phpcsFile->addFixableError($error, ($parenOpener + 1), 'SpacingAfterOpenBrace', $data);
140
141 1
            if ($fix === true) {
142 1
                $phpcsFile->fixer->beginChangeset();
143
144 1
                for ($i = $spaceAfterOpen; $i < $this->requiredSpacesAfterOpen; $i++) {
145 1
                    $phpcsFile->fixer->addContent($parenOpener, ' ');
146 1
                }
147
148 1
                $phpcsFile->fixer->endChangeset();
149 1
            }
150 1
        }
151
152 1
        if ($tokens[$parenOpener]['line'] === $tokens[$parenCloser]['line']) {
153 1
            $spaceBeforeClose = 0;
154 1
            if ($tokens[($parenCloser - 1)]['code'] === T_WHITESPACE) {
155 1
                $spaceBeforeClose = $tokens[($parenCloser - 1)]['length'];
156 1
            }
157
158 1
            if ($spaceBeforeClose !== $this->requiredSpacesBeforeClose) {
159 1
                $error = 'Expected %s spaces before "%s" closing bracket; %s found';
160
                $data  = array(
161 1
                          $this->requiredSpacesBeforeClose,
162 1
                          $tokens[$stackPtr]['content'],
163 1
                          $spaceBeforeClose,
164 1
                         );
165 1
                $fix   = $phpcsFile->addFixableError($error, ($parenCloser - 1), 'SpaceBeforeCloseBrace', $data);
166
167 1
                if ($fix === true) {
168 1
                    $phpcsFile->fixer->beginChangeset();
169
170 1
                    for ($i = $spaceBeforeClose; $i < $this->requiredSpacesBeforeClose; $i++) {
171 1
                        $phpcsFile->fixer->addContentBefore($parenCloser, ' ');
172 1
                    }
173
174 1
                    $phpcsFile->fixer->endChangeset();
175 1
                }
176 1
            }
177 1
        }//end if
178
179 1
    }//end checkBracketSpacing()
180
181
182
    /**
183
     * Checks content inside.
184
     *
185
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
186
     * @param int                  $stackPtr  The position of the current token
187
     *                                        in the stack passed in $tokens.
188
     *
189
     * @return void
190
     */
191 1
    protected function checkContentInside(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
192
    {
193 1
        $tokens      = $phpcsFile->getTokens();
194 1
        $scopeOpener = $tokens[$stackPtr]['scope_opener'];
195 1
        $scopeCloser = $tokens[$stackPtr]['scope_closer'];
196
197 1
        $firstContent = $phpcsFile->findNext(
198 1
            T_WHITESPACE,
199 1
            ($scopeOpener + 1),
200 1
            null,
201
            true
202 1
        );
203
204 1
        if ($tokens[$firstContent]['line'] !== ($tokens[$scopeOpener]['line'] + 1)) {
205 1
            $data  = array($tokens[$stackPtr]['content']);
206 1
            $diff  = $tokens[$firstContent]['line'] - ($tokens[$scopeOpener]['line'] + 1);
207 1
            if ($diff < 0) {
208 1
                $error  = 'Opening brace of the "%s" control structure must be last content on the line';
209 1
                $fix    = $phpcsFile->addFixableError($error, $scopeOpener, 'ContentAfterOpen', $data);
210 1
            } else {
211 1
                $data[] = $diff;
212 1
                $error  = 'Expected 0 blank lines at start of "%s" control structure; %s found';
213 1
                $fix    = $phpcsFile->addFixableError($error, $scopeOpener, 'SpacingBeforeOpen', $data);
214
            }
215
216 1
            if ($fix === true) {
217 1
                $phpcsFile->fixer->beginChangeset();
218
219 1
                for ($i = ($firstContent - 1); $i > $scopeOpener; $i--) {
220 1
                    if ($tokens[$i]['line'] === $tokens[$firstContent]['line']
221 1
                        || $tokens[$i]['line'] === $tokens[$scopeOpener]['line']
222 1
                    ) {
223
                        // Keep existing indentation.
224 1
                        continue;
225
                    }
226
227 1
                    $phpcsFile->fixer->replaceToken($i, '');
228 1
                }
229
230 1
                if ($diff < 0) {
231 1
                    $phpcsFile->fixer->addNewline($scopeOpener);
232 1
                }
233
234 1
                $phpcsFile->fixer->endChangeset();
235 1
            }
236 1
        }//end if
237
238 1
        if ($firstContent !== $scopeCloser) {
239
            // Not an empty control structure.
240 1
            $lastContent = $phpcsFile->findPrevious(
241 1
                T_WHITESPACE,
242 1
                ($scopeCloser - 1),
243 1
                null,
244
                true
245 1
            );
246
247 1
            if ($tokens[$lastContent]['line'] !== ($tokens[$scopeCloser]['line'] - 1)) {
248 1
                $data  = array($tokens[$stackPtr]['content']);
249 1
                $diff = (($tokens[$scopeCloser]['line'] - 1) - $tokens[$lastContent]['line']);
250
251 1
                if ($diff < 0) {
252
                    $error  = 'Closing brace of the "%s" control structure must be first content on the line';
253
                    $fix    = $phpcsFile->addFixableError($error, $scopeCloser, 'SpacingAfterClose', $data);
254
                } else {
255 1
                    $data[] = $diff;
256 1
                    $error  = 'Expected 0 blank lines at end of "%s" control structure; %s found';
257 1
                    $fix    = $phpcsFile->addFixableError($error, $scopeCloser, 'SpacingAfterClose', $data);
258
                }
259
260 1
                if ($fix === true) {
261 1
                    $phpcsFile->fixer->beginChangeset();
262
263 1
                    for ($i = ($lastContent + 1); $i < $scopeCloser; $i++) {
264 1
                        if ($tokens[$i]['line'] === $tokens[$scopeCloser]['line']
265 1
                            || $tokens[$i]['line'] === $tokens[$lastContent]['line']
266 1
                        ) {
267
                            // Keep existing indentation.
268 1
                            continue;
269
                        }
270
271 1
                        $phpcsFile->fixer->replaceToken($i, '');
272 1
                    }
273
274 1
                    if ($diff < 0) {
275
                        $phpcsFile->fixer->addNewline($lastContent);
276
                    }
277
278 1
                    $phpcsFile->fixer->endChangeset();
279 1
                }
280 1
            }//end if
281 1
        }//end if
282
283 1
    }//end checkContentInside()
284
285
286
    /**
287
     * Checks leading content.
288
     *
289
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
290
     * @param int                  $stackPtr  The position of the current token
291
     *                                        in the stack passed in $tokens.
292
     *
293
     * @return void
294
     */
295 1
    protected function checkLeadingContent(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
296
    {
297 1
        $tokens                   = $phpcsFile->getTokens();
298 1
        $leadingContent           = $this->getLeadingContent($phpcsFile, $stackPtr);
299 1
        $controlStructureStartPtr = $this->getLeadingCommentOrSelf($phpcsFile, $stackPtr);
300
301 1
        if ($tokens[$leadingContent]['code'] === T_OPEN_TAG) {
302
            // At the beginning of the script or embedded code.
303 1
            return;
304
        }
305
306 1
        $firstNonWhitespace = $phpcsFile->findPrevious(
307 1
            T_WHITESPACE,
308 1
            ($controlStructureStartPtr - 1),
309 1
            $leadingContent,
0 ignored issues
show
Bug introduced by
It seems like $leadingContent defined by $this->getLeadingContent($phpcsFile, $stackPtr) on line 298 can also be of type boolean; however, PHP_CodeSniffer_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...
310
            true
311 1
        );
312 1
        $firstNonWhitespace = $firstNonWhitespace ?: $leadingContent;
313 1
        $leadingLineNumber  = $tokens[$firstNonWhitespace]['line'];
314
315 1
        if ($tokens[$leadingContent]['code'] === T_OPEN_CURLY_BRACKET
316 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 298 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...
317 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 298 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...
318 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 298 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...
319 1
        ) {
320 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 298 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...
321
                // The previous content is the opening brace of a function
322
                // so normal function rules apply and we can ignore it.
323 1
                return;
324
            }
325
326 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 298 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...
327
                return;
328
            }
329
330 1
            if ($tokens[$controlStructureStartPtr]['line'] !== ($leadingLineNumber + 1)) {
331 1
                $data = array($tokens[$stackPtr]['content']);
332 1
                $diff = $tokens[$controlStructureStartPtr]['line'] - ($leadingLineNumber + 1);
333 1
                if ($diff < 0) {
334 1
                    $error = 'Beginning of the "%s" control structure must be first content on the line';
335 1
                    $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'ContentBeforeStart', $data);
336 1
                } else {
337 1
                    $data[] = $diff;
338 1
                    $error  = 'Expected 0 blank lines before "%s" control structure; %s found';
339 1
                    $fix    = $phpcsFile->addFixableError($error, $stackPtr, 'LineBeforeOpen', $data);
340
                }
341
342 1
                if ($fix === true) {
343 1
                    $phpcsFile->fixer->beginChangeset();
344
345 1
                    for ($i = ($firstNonWhitespace + 1); $i < $controlStructureStartPtr; $i++) {
346 1
                        if ($tokens[$i]['line'] === $tokens[$controlStructureStartPtr]['line']) {
347
                            // Keep existing indentation.
348 1
                            break;
349
                        }
350
351 1
                        $phpcsFile->fixer->replaceToken($i, '');
352 1
                    }
353
354 1
                    $phpcsFile->fixer->addNewline($firstNonWhitespace);
355 1
                    $phpcsFile->fixer->endChangeset();
356 1
                }
357 1
            }//end if
358 1
        } else if ($tokens[$controlStructureStartPtr]['line'] === ($leadingLineNumber + 1)) {
359
            // Code on the previous line before control structure start.
360 1
            $data  = array($tokens[$stackPtr]['content']);
361 1
            $error = 'No blank line found before "%s" control structure';
362 1
            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'NoLineBeforeOpen', $data);
363
364 1
            if ($fix === true) {
365 1
                $phpcsFile->fixer->beginChangeset();
366 1
                $phpcsFile->fixer->addNewline($firstNonWhitespace);
0 ignored issues
show
Bug introduced by
It seems like $firstNonWhitespace defined by $firstNonWhitespace ?: $leadingContent on line 312 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...
367 1
                $phpcsFile->fixer->endChangeset();
368 1
            }
369 1
        }//end if
370
371 1
    }//end checkLeadingContent()
372
373
374
    /**
375
     * Returns leading non-whitespace/comment token.
376
     *
377
     * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document.
378
     * @param int                  $stackPtr  The position of the current token
379
     *                                        in the stack passed in $tokens.
380
     *
381
     * @return int|bool
382
     */
383 1
    protected function getLeadingContent(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
384
    {
385 1
        $prevNonWhitespace = $phpcsFile->findPrevious(
386
            array(
387 1
             T_WHITESPACE,
388
             T_COMMENT
389 1
            ),
390 1
            ($stackPtr - 1),
391 1
            null,
392
            true
393 1
        );
394
395 1
        return $prevNonWhitespace;
396
397
    }//end getLeadingContent()
398
399
    /**
400
     * Returns leading comment or self.
401
     *
402
     * @param PHP_CodeSniffer_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(PHP_CodeSniffer_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
                true
420 1
            );
421
422 1
            if ($tokens[$newPrev]['code'] === T_COMMENT
423 1
                && $tokens[$newPrev]['line'] === ($tokens[$prev]['line'] - 1)
424 1
            ) {
425 1
                $prevTokens[] = $newPrev;
426 1
            } else {
427 1
                break;
428
            }
429 1
        } while (true);
430
431 1
        return end($prevTokens);
432
433
    }//end getLeadingCommentOrSelf()
434
435
    /**
436
     * Checks trailing content.
437
     *
438
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
439
     * @param int                  $stackPtr  The position of the current token
440
     *                                        in the stack passed in $tokens.
441
     *
442
     * @return void
443
     */
444 1
    protected function checkTrailingContent(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
445
    {
446 1
        $tokens                 = $phpcsFile->getTokens();
447 1
        $scopeCloser            = $this->getScopeCloser($phpcsFile, $stackPtr);
448 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 447 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...
449 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 447 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...
450
451 1
        if ($tokens[$trailingContent]['code'] === T_CLOSE_TAG) {
452
            // At the end of the script or embedded code.
453 1
            return;
454
        }
455
456 1
        $lastNonWhitespace = $phpcsFile->findNext(
457 1
            T_WHITESPACE,
458 1
            ($controlStructureEndPtr + 1),
459 1
            $trailingContent,
0 ignored issues
show
Security Bug introduced by
It seems like $trailingContent defined by $this->getTrailingConten...hpcsFile, $scopeCloser) on line 448 can also be of type false; however, PHP_CodeSniffer_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...
460
            true
461 1
        );
462 1
        $lastNonWhitespace  = $lastNonWhitespace ?: $trailingContent;
463 1
        $trailingLineNumber = $tokens[$lastNonWhitespace]['line'];
464
465 1
        if ($tokens[$trailingContent]['code'] === T_CLOSE_CURLY_BRACKET
466 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 448 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...
467 1
        ) {
468 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 448 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...
469
                // The next content is the closing brace of a function
470
                // so normal function rules apply and we can ignore it.
471 1
                return;
472
            }
473
474 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 448 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...
475
                return;
476
            }
477
478 1
            if ($tokens[$controlStructureEndPtr]['line'] !== ($trailingLineNumber - 1)) {
479 1
                $diff  = ($trailingLineNumber - 1) - $tokens[$controlStructureEndPtr]['line'];
480
                $data  = array(
481 1
                          $tokens[$stackPtr]['content'],
482 1
                          $diff,
483 1
                         );
484 1
                $error = 'Expected 0 blank lines after "%s" control structure; %s found';
485 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 447 can also be of type boolean; however, PHP_CodeSniffer_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...
486
487 1
                if ($fix === true) {
488 1
                    $phpcsFile->fixer->beginChangeset();
489
490 1
                    for ($i = ($controlStructureEndPtr + 1); $i < $lastNonWhitespace; $i++) {
491 1
                        if ($tokens[$i]['line'] === $tokens[$lastNonWhitespace]['line']) {
492
                            // Keep existing indentation.
493 1
                            break;
494
                        }
495
496 1
                        $phpcsFile->fixer->replaceToken($i, '');
497 1
                    }
498
499 1
                    $phpcsFile->fixer->addNewline($controlStructureEndPtr);
500 1
                    $phpcsFile->fixer->endChangeset();
501 1
                }
502 1
            }//end if
503 1
        } else if ($tokens[$controlStructureEndPtr]['line'] === ($trailingLineNumber - 1)) {
504
            // Code on the next line after control structure scope closer.
505 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 448 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...
506 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 448 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...
507 1
            ) {
508 1
                return;
509
            }
510
511 1
            $error = 'No blank line found after "%s" control structure';
512 1
            $data  = array($tokens[$stackPtr]['content']);
513 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 447 can also be of type boolean; however, PHP_CodeSniffer_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...
514
515 1
            if ($fix === true) {
516 1
                $phpcsFile->fixer->beginChangeset();
517 1
                $phpcsFile->fixer->addNewline($controlStructureEndPtr);
518 1
                $phpcsFile->fixer->endChangeset();
519 1
            }
520 1
        }//end if
521
522 1
    }//end checkTrailingContent()
523
524
525
    /**
526
     * Returns scope closer  with special check for "do...while" statements.
527
     *
528
     * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document.
529
     * @param int                  $stackPtr  The position of the current token
530
     *                                        in the stack passed in $tokens.
531
     *
532
     * @return int|bool
533
     */
534 1
    protected function getScopeCloser(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
535
    {
536 1
        $tokens      = $phpcsFile->getTokens();
537 1
        $scopeCloser = $tokens[$stackPtr]['scope_closer'];
538
539 1
        if ($tokens[$stackPtr]['code'] !== T_DO) {
540 1
            return $scopeCloser;
541
        }
542
543 1
        $trailingContent = $phpcsFile->findNext(
544 1
            PHP_CodeSniffer_Tokens::$emptyTokens,
545 1
            ($scopeCloser + 1),
546 1
            null,
547
            true
548 1
        );
549
550 1
        if ($tokens[$trailingContent]['code'] === T_WHILE) {
551 1
            return ($tokens[$trailingContent]['parenthesis_closer'] + 1);
552
        }
553
554
        // @codeCoverageIgnoreStart
555
        $phpcsFile->addError('Expected "while" not found after "do"', $stackPtr, 'InvalidDo');
556
557
        return $scopeCloser;
558
        // @codeCoverageIgnoreEnd
559
560
    }//end getScopeCloser()
561
562
563
    /**
564
     * Returns trailing content token.
565
     *
566
     * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document.
567
     * @param int                  $stackPtr  The position of the current token
568
     *                                        in the stack passed in $tokens.
569
     *
570
     * @return int|bool
571
     */
572 1
    protected function getTrailingContent(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
573
    {
574 1
        $nextNonWhitespace = $phpcsFile->findNext(
575
            array(
576 1
             T_WHITESPACE,
577 1
             T_COMMENT,
578 1
            ),
579 1
            ($stackPtr + 1),
580 1
            null,
581
            true
582 1
        );
583
584 1
        return $nextNonWhitespace;
585
586
    }//end getTrailingContent()
587
588
589
    /**
590
     * Returns trailing comment or self.
591
     *
592
     * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document.
593
     * @param int                  $stackPtr  The position of the current token
594
     *                                        in the stack passed in $tokens.
595
     *
596
     * @return bool|int
597
     */
598 1
    protected function getTrailingCommentOrSelf(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
599
    {
600 1
        $nextTokens = array($stackPtr);
601 1
        $tokens = $phpcsFile->getTokens();
602
603
        do {
604 1
            $next    = end($nextTokens);
605 1
            $newNext = $phpcsFile->findNext(
606 1
                T_WHITESPACE,
607 1
                ($next + 1),
608 1
                null,
609
                true
610 1
            );
611
612 1
            if ($tokens[$newNext]['code'] === T_COMMENT
613 1
                && $tokens[$newNext]['line'] === ($tokens[$next]['line'] + 1)
614 1
            ) {
615 1
                $nextTokens[] = $newNext;
616 1
            } else {
617 1
                break;
618
            }
619 1
        } while (true);
620
621 1
        return end($nextTokens);
622
623
    }//end getTrailingCommentOrSelf()
624
625
626
    /**
627
     * Finds first token on a line.
628
     *
629
     * @param PHP_CodeSniffer_File $phpcsFile All the tokens found in the document.
630
     * @param int                  $start     Start from token.
631
     *
632
     * @return int | bool
633
     */
634
    public function findFirstOnLine(PHP_CodeSniffer_File $phpcsFile, $start)
635
    {
636
        $tokens = $phpcsFile->getTokens();
637
638
        for ($i = $start; $i >= 0; $i--) {
639
            if ($tokens[$i]['line'] === $tokens[$start]['line']) {
640
                continue;
641
            }
642
643
            return ($i + 1);
644
        }
645
646
        return false;
647
648
    }//end findFirstOnLine()
649
650
651
    /**
652
     * Detects, that we're at the edge (beginning or ending) of CASE/DEFAULT with SWITCH statement.
653
     *
654
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
655
     * @param int                  $stackPtr  The position of the current token
656
     *                                        in the stack passed in $tokens.
657
     *
658
     * @return bool
659
     */
660 1
    protected function insideSwitchCase(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
661
    {
662 1
        if ($this->isScopeCondition($phpcsFile, $stackPtr, array(T_CASE, T_DEFAULT)) === true) {
663 1
            $tokens = $phpcsFile->getTokens();
664
665
            // Consider "return" instead of "break" as function ending to enforce empty line before it.
666 1
            return $tokens[$stackPtr]['code'] !== T_RETURN;
667
        }
668
669 1
        return false;
670
671
    }//end insideSwitchCase()
672
673
674
    /**
675
     * Detects, that it is a closing brace of IF/ELSEIF.
676
     *
677
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
678
     * @param int                  $stackPtr  The position of the current token
679
     *                                        in the stack passed in $tokens.
680
     *
681
     * @return bool
682
     */
683 1
    protected function ifOrElseIf(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
684
    {
685 1
        return $this->isScopeCondition($phpcsFile, $stackPtr, array(T_IF, T_ELSEIF));
686
687
    }//end ifOrElseIf()
688
689
690
    /**
691
     * Detects, that it is a closing brace of ELSE/ELSEIF.
692
     *
693
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
694
     * @param int                  $stackPtr  The position of the current token
695
     *                                        in the stack passed in $tokens.
696
     *
697
     * @return bool
698
     */
699 1
    protected function elseOrElseIf(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
700
    {
701 1
        return $this->isScopeCondition($phpcsFile, $stackPtr, array(T_ELSE, T_ELSEIF));
702
703
    }//end elseOrElseIf()
704
705
706
    /**
707
     * Detects, that it is a closing brace of TRY/CATCH.
708
     *
709
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
710
     * @param int                  $stackPtr  The position of the current token
711
     *                                        in the stack passed in $tokens.
712
     *
713
     * @return bool
714
     */
715 1
    protected function isTryOrCatch(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
716
    {
717 1
        return $this->isScopeCondition($phpcsFile, $stackPtr, array(T_TRY, T_CATCH));
718
719
    }//end isTryOrCatch()
720
721
722
    /**
723
     * Detects, that it is a closing brace of TRY.
724
     *
725
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
726
     * @param int                  $stackPtr  The position of the current token
727
     *                                        in the stack passed in $tokens.
728
     *
729
     * @return bool
730
     */
731
    protected function isTry(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
732
    {
733
        return $this->isScopeCondition($phpcsFile, $stackPtr, T_TRY);
734
735
    }//end isTry()
736
737
738
    /**
739
     * Detects, that it is a closing brace of CATCH.
740
     *
741
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
742
     * @param int                  $stackPtr  The position of the current token
743
     *                                        in the stack passed in $tokens.
744
     *
745
     * @return bool
746
     */
747 1
    protected function isCatch(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
748
    {
749 1
        return $this->isScopeCondition($phpcsFile, $stackPtr, T_CATCH);
750
751
    }//end isCatch()
752
753
754
    /**
755
     * Determines that a function is located at given position.
756
     *
757
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
758
     * @param int                  $stackPtr  The position of the current token
759
     *                                        in the stack passed in $tokens.
760
     *
761
     * @return bool
762
     */
763 1
    protected function isFunction(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
764
    {
765 1
        return $this->isScopeCondition($phpcsFile, $stackPtr, T_FUNCTION);
766
767
    }//end isFunction()
768
769
770
    /**
771
     * Determines that a closure is located at given position.
772
     *
773
     * @param PHP_CodeSniffer_File $phpcsFile         The file being scanned.
774
     * @param int                  $stackPtr          The position of the current token.
775
     *                                                in the stack passed in $tokens.
776
     * @param int                  $scopeConditionPtr Position of scope condition.
777
     *
778
     * @return bool
779
     */
780 1
    protected function isClosure(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $scopeConditionPtr)
781
    {
782 1
        $tokens = $phpcsFile->getTokens();
783
784 1
        if ($this->isScopeCondition($phpcsFile, $scopeConditionPtr, T_CLOSURE) === true
785 1
            && ($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true
786
            || $phpcsFile->hasCondition($stackPtr, T_CLOSURE) === true
787
            || isset($tokens[$stackPtr]['nested_parenthesis']) === true)
788 1
        ) {
789
            return true;
790
        }
791
792 1
        return false;
793
794
    }//end isClosure()
795
796
797
    /**
798
     * Detects, that it is a closing brace of ELSE/ELSEIF.
799
     *
800
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
801
     * @param int                  $stackPtr  The position of the current token
802
     *                                        in the stack passed in $tokens.
803
     * @param int|array            $types     The type(s) of tokens to search for.
804
     *
805
     * @return bool
806
     */
807 1
    protected function isScopeCondition(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $types)
808
    {
809 1
        $tokens = $phpcsFile->getTokens();
810
811 1
        if (isset($tokens[$stackPtr]['scope_condition']) === true) {
812 1
            $owner = $tokens[$stackPtr]['scope_condition'];
813
814 1
            if (in_array($tokens[$owner]['code'], (array)$types) === true) {
815 1
                return true;
816
            }
817 1
        }
818
819 1
        return false;
820
821
    }//end isScopeCondition()
822
823
824
}//end class
825