Completed
Push — in-portal ( b7a6d3...d7c658 )
by Alexander
02:38
created

ControlStructureSpacingSniff::getTrailingContent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 2
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 13
cp 0
crap 2
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
    public function register()
63
    {
64
        return array(
65
                T_IF,
66
                T_WHILE,
67
                T_FOREACH,
68
                T_FOR,
69
                T_SWITCH,
70
                T_DO,
71
                T_ELSE,
72
                T_ELSEIF,
73
                T_TRY,
74
                T_CATCH,
75
               );
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
    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
90
    {
91
        $this->requiredSpacesAfterOpen   = (int) $this->requiredSpacesAfterOpen;
92
        $this->requiredSpacesBeforeClose = (int) $this->requiredSpacesBeforeClose;
93
        $tokens = $phpcsFile->getTokens();
94
95
        $this->checkBracketSpacing($phpcsFile, $stackPtr);
96
97
        if (isset($tokens[$stackPtr]['scope_closer']) === false) {
98
            return;
99
        }
100
101
        $this->checkContentInside($phpcsFile, $stackPtr);
102
        $this->checkLeadingContent($phpcsFile, $stackPtr);
103
        $this->checkTrailingContent($phpcsFile, $stackPtr);
104
105
    }//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
    protected function checkBracketSpacing(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
118
    {
119
        $tokens = $phpcsFile->getTokens();
120
121
        if (isset($tokens[$stackPtr]['parenthesis_opener']) === false) {
122
            return;
123
        }
124
125
        $parenOpener    = $tokens[$stackPtr]['parenthesis_opener'];
126
        $parenCloser    = $tokens[$stackPtr]['parenthesis_closer'];
127
        $spaceAfterOpen = 0;
128
        if ($tokens[($parenOpener + 1)]['code'] === T_WHITESPACE) {
129
            $spaceAfterOpen = $tokens[($parenOpener + 1)]['length'];
130
        }
131
132
        if ($spaceAfterOpen !== $this->requiredSpacesAfterOpen) {
133
            $error = 'Expected %s spaces after "%s" opening bracket; %s found';
134
            $data  = array(
135
                      $this->requiredSpacesAfterOpen,
136
                      $tokens[$stackPtr]['content'],
137
                      $spaceAfterOpen,
138
                     );
139
            $fix   = $phpcsFile->addFixableError($error, ($parenOpener + 1), 'SpacingAfterOpenBrace', $data);
140
141
            if ($fix === true) {
142
                $phpcsFile->fixer->beginChangeset();
143
144
                for ($i = $spaceAfterOpen; $i < $this->requiredSpacesAfterOpen; $i++) {
145
                    $phpcsFile->fixer->addContent($parenOpener, ' ');
146
                }
147
148
                $phpcsFile->fixer->endChangeset();
149
            }
150
        }
151
152
        if ($tokens[$parenOpener]['line'] === $tokens[$parenCloser]['line']) {
153
            $spaceBeforeClose = 0;
154
            if ($tokens[($parenCloser - 1)]['code'] === T_WHITESPACE) {
155
                $spaceBeforeClose = $tokens[($parenCloser - 1)]['length'];
156
            }
157
158
            if ($spaceBeforeClose !== $this->requiredSpacesBeforeClose) {
159
                $error = 'Expected %s spaces before "%s" closing bracket; %s found';
160
                $data  = array(
161
                          $this->requiredSpacesBeforeClose,
162
                          $tokens[$stackPtr]['content'],
163
                          $spaceBeforeClose,
164
                         );
165
                $fix   = $phpcsFile->addFixableError($error, ($parenCloser - 1), 'SpaceBeforeCloseBrace', $data);
166
167
                if ($fix === true) {
168
                    $phpcsFile->fixer->beginChangeset();
169
170
                    for ($i = $spaceBeforeClose; $i < $this->requiredSpacesBeforeClose; $i++) {
171
                        $phpcsFile->fixer->addContentBefore($parenCloser, ' ');
172
                    }
173
174
                    $phpcsFile->fixer->endChangeset();
175
                }
176
            }
177
        }//end if
178
179
    }//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
    protected function checkContentInside(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
192
    {
193
        $tokens      = $phpcsFile->getTokens();
194
        $scopeOpener = $tokens[$stackPtr]['scope_opener'];
195
        $scopeCloser = $tokens[$stackPtr]['scope_closer'];
196
197
        $firstContent = $phpcsFile->findNext(
198
            T_WHITESPACE,
199
            ($scopeOpener + 1),
200
            null,
201
            true
202
        );
203
204
        if ($tokens[$firstContent]['line'] !== ($tokens[$scopeOpener]['line'] + 1)) {
205
            $data  = array($tokens[$stackPtr]['content']);
206
            $diff  = $tokens[$firstContent]['line'] - ($tokens[$scopeOpener]['line'] + 1);
207
            if ($diff < 0) {
208
                $error  = 'Opening brace of the "%s" control structure must be last content on the line';
209
                $fix    = $phpcsFile->addFixableError($error, $scopeOpener, 'ContentAfterOpen', $data);
210
            } else {
211
                $data[] = $diff;
212
                $error  = 'Expected 0 blank lines at start of "%s" control structure; %s found';
213
                $fix    = $phpcsFile->addFixableError($error, $scopeOpener, 'SpacingBeforeOpen', $data);
214
            }
215
216
            if ($fix === true) {
217
                $phpcsFile->fixer->beginChangeset();
218
219
                for ($i = ($firstContent - 1); $i > $scopeOpener; $i--) {
220
                    if ($tokens[$i]['line'] === $tokens[$firstContent]['line']
221
                        || $tokens[$i]['line'] === $tokens[$scopeOpener]['line']
222
                    ) {
223
                        // Keep existing indentation.
224
                        continue;
225
                    }
226
227
                    $phpcsFile->fixer->replaceToken($i, '');
228
                }
229
230
                if ($diff < 0) {
231
                    $phpcsFile->fixer->addNewline($scopeOpener);
232
                }
233
234
                $phpcsFile->fixer->endChangeset();
235
            }
236
        }//end if
237
238
        if ($firstContent !== $scopeCloser) {
239
            // Not an empty control structure.
240
            $lastContent = $phpcsFile->findPrevious(
241
                T_WHITESPACE,
242
                ($scopeCloser - 1),
243
                null,
244
                true
245
            );
246
247
            if ($tokens[$lastContent]['line'] !== ($tokens[$scopeCloser]['line'] - 1)) {
248
                $data  = array($tokens[$stackPtr]['content']);
249
                $diff = (($tokens[$scopeCloser]['line'] - 1) - $tokens[$lastContent]['line']);
250
251
                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
                    $data[] = $diff;
256
                    $error  = 'Expected 0 blank lines at end of "%s" control structure; %s found';
257
                    $fix    = $phpcsFile->addFixableError($error, $scopeCloser, 'SpacingAfterClose', $data);
258
                }
259
260
                if ($fix === true) {
261
                    $phpcsFile->fixer->beginChangeset();
262
263
                    for ($i = ($lastContent + 1); $i < $scopeCloser; $i++) {
264
                        if ($tokens[$i]['line'] === $tokens[$scopeCloser]['line']
265
                            || $tokens[$i]['line'] === $tokens[$lastContent]['line']
266
                        ) {
267
                            // Keep existing indentation.
268
                            continue;
269
                        }
270
271
                        $phpcsFile->fixer->replaceToken($i, '');
272
                    }
273
274
                    if ($diff < 0) {
275
                        $phpcsFile->fixer->addNewline($lastContent);
276
                    }
277
278
                    $phpcsFile->fixer->endChangeset();
279
                }
280
            }//end if
281
        }//end if
282
283
    }//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
    protected function checkLeadingContent(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
296
    {
297
        $tokens                   = $phpcsFile->getTokens();
298
        $leadingContent           = $this->getLeadingContent($phpcsFile, $stackPtr);
299
        $controlStructureStartPtr = $this->getLeadingCommentOrSelf($phpcsFile, $stackPtr);
300
301
        if ($tokens[$leadingContent]['code'] === T_OPEN_TAG) {
302
            // At the beginning of the script or embedded code.
303
            return;
304
        }
305
306
        $firstNonWhitespace = $phpcsFile->findPrevious(
307
            T_WHITESPACE,
308
            ($controlStructureStartPtr - 1),
309
            $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
        );
312
        $firstNonWhitespace = $firstNonWhitespace ?: $leadingContent;
313
        $leadingLineNumber  = $tokens[$firstNonWhitespace]['line'];
314
315
        if ($tokens[$leadingContent]['code'] === T_OPEN_CURLY_BRACKET
316
            || $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
            || ($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
            || ($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
        ) {
320
            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
                return;
324
            }
325
326
            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
            if ($tokens[$controlStructureStartPtr]['line'] !== ($leadingLineNumber + 1)) {
331
                $data = array($tokens[$stackPtr]['content']);
332
                $diff = $tokens[$controlStructureStartPtr]['line'] - ($leadingLineNumber + 1);
333
                if ($diff < 0) {
334
                    $error = 'Beginning of the "%s" control structure must be first content on the line';
335
                    $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'ContentBeforeStart', $data);
336
                } else {
337
                    $data[] = $diff;
338
                    $error  = 'Expected 0 blank lines before "%s" control structure; %s found';
339
                    $fix    = $phpcsFile->addFixableError($error, $stackPtr, 'LineBeforeOpen', $data);
340
                }
341
342
                if ($fix === true) {
343
                    $phpcsFile->fixer->beginChangeset();
344
345
                    for ($i = ($firstNonWhitespace + 1); $i < $controlStructureStartPtr; $i++) {
346
                        if ($tokens[$i]['line'] === $tokens[$controlStructureStartPtr]['line']) {
347
                            // Keep existing indentation.
348
                            break;
349
                        }
350
351
                        $phpcsFile->fixer->replaceToken($i, '');
352
                    }
353
354
                    $phpcsFile->fixer->addNewline($firstNonWhitespace);
355
                    $phpcsFile->fixer->endChangeset();
356
                }
357
            }//end if
358
        } else if ($tokens[$controlStructureStartPtr]['line'] === ($leadingLineNumber + 1)) {
359
            // Code on the previous line before control structure start.
360
            $data  = array($tokens[$stackPtr]['content']);
361
            $error = 'No blank line found before "%s" control structure';
362
            $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'NoLineBeforeOpen', $data);
363
364
            if ($fix === true) {
365
                $phpcsFile->fixer->beginChangeset();
366
                $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
                $phpcsFile->fixer->endChangeset();
368
            }
369
        }//end if
370
371
    }//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
    protected function getLeadingContent(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
384
    {
385
        $prevNonWhitespace = $phpcsFile->findPrevious(
386
            array(
387
             T_WHITESPACE,
388
             T_COMMENT
389
            ),
390
            ($stackPtr - 1),
391
            null,
392
            true
393
        );
394
395
        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
    protected function getLeadingCommentOrSelf(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
409
    {
410
        $prevTokens = array($stackPtr);
411
        $tokens = $phpcsFile->getTokens();
412
413
        do {
414
            $prev    = end($prevTokens);
415
            $newPrev = $phpcsFile->findPrevious(
416
                T_WHITESPACE,
417
                ($prev - 1),
418
                null,
419
                true
420
            );
421
422
            if ($tokens[$newPrev]['code'] === T_COMMENT
423
                && $tokens[$newPrev]['line'] === ($tokens[$prev]['line'] - 1)
424
            ) {
425
                $prevTokens[] = $newPrev;
426
            } else {
427
                break;
428
            }
429
        } while (true);
430
431
        return end($prevTokens);
432
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
    protected function checkTrailingContent(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
445
    {
446
        $tokens                 = $phpcsFile->getTokens();
447
        $scopeCloser            = $this->getScopeCloser($phpcsFile, $stackPtr);
448
        $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
        $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
        if ($tokens[$trailingContent]['code'] === T_CLOSE_TAG) {
452
            // At the end of the script or embedded code.
453
            return;
454
        }
455
456
        $lastNonWhitespace = $phpcsFile->findNext(
457
            T_WHITESPACE,
458
            ($controlStructureEndPtr + 1),
459
            $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
        );
462
        $lastNonWhitespace  = $lastNonWhitespace ?: $trailingContent;
463
        $trailingLineNumber = $tokens[$lastNonWhitespace]['line'];
464
465
        if ($tokens[$trailingContent]['code'] === T_CLOSE_CURLY_BRACKET
466
            || $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
        ) {
468
            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
                return;
472
            }
473
474
            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
            if ($tokens[$controlStructureEndPtr]['line'] !== ($trailingLineNumber - 1)) {
479
                $diff  = ($trailingLineNumber - 1) - $tokens[$controlStructureEndPtr]['line'];
480
                $data  = array(
481
                          $tokens[$stackPtr]['content'],
482
                          $diff,
483
                         );
484
                $error = 'Expected 0 blank lines after "%s" control structure; %s found';
485
                $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
                if ($fix === true) {
488
                    $phpcsFile->fixer->beginChangeset();
489
490
                    for ($i = ($controlStructureEndPtr + 1); $i < $lastNonWhitespace; $i++) {
491
                        if ($tokens[$i]['line'] === $tokens[$lastNonWhitespace]['line']) {
492
                            // Keep existing indentation.
493
                            break;
494
                        }
495
496
                        $phpcsFile->fixer->replaceToken($i, '');
497
                    }
498
499
                    $phpcsFile->fixer->addNewline($controlStructureEndPtr);
500
                    $phpcsFile->fixer->endChangeset();
501
                }
502
            }//end if
503
        } else if ($tokens[$controlStructureEndPtr]['line'] === ($trailingLineNumber - 1)) {
504
            // Code on the next line after control structure scope closer.
505
            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
                || $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
            ) {
508
                return;
509
            }
510
511
            $error = 'No blank line found after "%s" control structure';
512
            $data  = array($tokens[$stackPtr]['content']);
513
            $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
            if ($fix === true) {
516
                $phpcsFile->fixer->beginChangeset();
517
                $phpcsFile->fixer->addNewline($controlStructureEndPtr);
518
                $phpcsFile->fixer->endChangeset();
519
            }
520
        }//end if
521
522
    }//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
    protected function getScopeCloser(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
535
    {
536
        $tokens      = $phpcsFile->getTokens();
537
        $scopeCloser = $tokens[$stackPtr]['scope_closer'];
538
539
        if ($tokens[$stackPtr]['code'] !== T_DO) {
540
            return $scopeCloser;
541
        }
542
543
        $trailingContent = $phpcsFile->findNext(
544
            PHP_CodeSniffer_Tokens::$emptyTokens,
545
            ($scopeCloser + 1),
546
            null,
547
            true
548
        );
549
550
        if ($tokens[$trailingContent]['code'] === T_WHILE) {
551
            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
    protected function getTrailingContent(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
573
    {
574
        $nextNonWhitespace = $phpcsFile->findNext(
575
            array(
576
             T_WHITESPACE,
577
             T_COMMENT,
578
            ),
579
            ($stackPtr + 1),
580
            null,
581
            true
582
        );
583
584
        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
    protected function getTrailingCommentOrSelf(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
599
    {
600
        $nextTokens = array($stackPtr);
601
        $tokens = $phpcsFile->getTokens();
602
603
        do {
604
            $next    = end($nextTokens);
605
            $newNext = $phpcsFile->findNext(
606
                T_WHITESPACE,
607
                ($next + 1),
608
                null,
609
                true
610
            );
611
612
            if ($tokens[$newNext]['code'] === T_COMMENT
613
                && $tokens[$newNext]['line'] === ($tokens[$next]['line'] + 1)
614
            ) {
615
                $nextTokens[] = $newNext;
616
            } else {
617
                break;
618
            }
619
        } while (true);
620
621
        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
    protected function insideSwitchCase(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
661
    {
662
        if ($this->isScopeCondition($phpcsFile, $stackPtr, array(T_CASE, T_DEFAULT)) === true) {
663
            $tokens = $phpcsFile->getTokens();
664
665
            // Consider "return" instead of "break" as function ending to enforce empty line before it.
666
            return $tokens[$stackPtr]['code'] !== T_RETURN;
667
        }
668
669
        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
    protected function ifOrElseIf(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
684
    {
685
        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
    protected function elseOrElseIf(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
700
    {
701
        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
    protected function isTryOrCatch(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
716
    {
717
        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
    protected function isCatch(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
748
    {
749
        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
    protected function isFunction(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
764
    {
765
        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
    protected function isClosure(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $scopeConditionPtr)
781
    {
782
        $tokens = $phpcsFile->getTokens();
783
784
        if ($this->isScopeCondition($phpcsFile, $scopeConditionPtr, T_CLOSURE) === true
785
            && ($phpcsFile->hasCondition($stackPtr, T_FUNCTION) === true
786
            || $phpcsFile->hasCondition($stackPtr, T_CLOSURE) === true
787
            || isset($tokens[$stackPtr]['nested_parenthesis']) === true)
788
        ) {
789
            return true;
790
        }
791
792
        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
    protected function isScopeCondition(PHP_CodeSniffer_File $phpcsFile, $stackPtr, $types)
808
    {
809
        $tokens = $phpcsFile->getTokens();
810
811
        if (isset($tokens[$stackPtr]['scope_condition']) === true) {
812
            $owner = $tokens[$stackPtr]['scope_condition'];
813
814
            if (in_array($tokens[$owner]['code'], (array)$types) === true) {
815
                return true;
816
            }
817
        }
818
819
        return false;
820
821
    }//end isScopeCondition()
822
823
824
}//end class
825