• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHP_CodeSniffer / 8532137691

03 Apr 2024 02:02AM UTC coverage: 72.352% (+0.02%) from 72.328%
8532137691

push

github

jrfnl
File::getMethodProperties(): skip over closure use statements

This PR improves performance of the `File::getMethodProperties()` method and prevents incorrect return type information for closures `use` clauses containing invalid variable imports in the `use` clause (defensive coding).

Closure `use` statements can only import plain variables, not properties or other more complex variables.

As things were, when such "illegal" variables were imported in a closure `use`, the information for the return type could get mangled.
While this would be a parse error, for the purposes of static analysis, the `File::getMethodProperties()` method should still handle this correctly.

This commit updates the `File::getMethodProperties()` method to always skip over the complete `use` clause, which prevents the issue and improves performance as the same time (less token walking).

Includes unit tests.

6 of 7 new or added lines in 1 file covered. (85.71%)

4 existing lines in 3 files now uncovered.

17358 of 23991 relevant lines covered (72.35%)

55.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.68
/src/Standards/PEAR/Sniffs/WhiteSpace/ScopeClosingBraceSniff.php
1
<?php
2
/**
3
 * Checks that the closing braces of scopes are aligned correctly.
4
 *
5
 * @author    Greg Sherwood <gsherwood@squiz.net>
6
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
7
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8
 */
9

10
namespace PHP_CodeSniffer\Standards\PEAR\Sniffs\WhiteSpace;
11

12
use PHP_CodeSniffer\Files\File;
13
use PHP_CodeSniffer\Sniffs\Sniff;
14
use PHP_CodeSniffer\Util\Tokens;
15

16
class ScopeClosingBraceSniff implements Sniff
17
{
18

19
    /**
20
     * The number of spaces code should be indented.
21
     *
22
     * @var integer
23
     */
24
    public $indent = 4;
25

26

27
    /**
28
     * Returns an array of tokens this test wants to listen for.
29
     *
30
     * @return array<int|string>
31
     */
32
    public function register()
3✔
33
    {
34
        return Tokens::$scopeOpeners;
3✔
35

36
    }//end register()
37

38

39
    /**
40
     * Processes this test, when one of its tokens is encountered.
41
     *
42
     * @param \PHP_CodeSniffer\Files\File $phpcsFile All the tokens found in the document.
43
     * @param int                         $stackPtr  The position of the current token
44
     *                                               in the stack passed in $tokens.
45
     *
46
     * @return void
47
     */
48
    public function process(File $phpcsFile, $stackPtr)
3✔
49
    {
50
        $tokens = $phpcsFile->getTokens();
3✔
51

52
        // If this is an inline condition (ie. there is no scope opener), then
53
        // return, as this is not a new scope.
54
        if (isset($tokens[$stackPtr]['scope_closer']) === false) {
3✔
55
            return;
3✔
56
        }
57

58
        $scopeStart = $tokens[$stackPtr]['scope_opener'];
3✔
59
        $scopeEnd   = $tokens[$stackPtr]['scope_closer'];
3✔
60

61
        // If the scope closer doesn't think it belongs to this scope opener
62
        // then the opener is sharing its closer with other tokens. We only
63
        // want to process the closer once, so skip this one.
64
        if (isset($tokens[$scopeEnd]['scope_condition']) === false
3✔
65
            || $tokens[$scopeEnd]['scope_condition'] !== $stackPtr
3✔
66
        ) {
67
            return;
3✔
68
        }
69

70
        // We need to actually find the first piece of content on this line,
71
        // because if this is a method with tokens before it (public, static etc)
72
        // or an if with an else before it, then we need to start the scope
73
        // checking from there, rather than the current token.
74
        $lineStart = ($stackPtr - 1);
3✔
75
        for ($lineStart; $lineStart > 0; $lineStart--) {
3✔
76
            if (strpos($tokens[$lineStart]['content'], $phpcsFile->eolChar) !== false) {
3✔
77
                break;
3✔
78
            }
79
        }
80

81
        $lineStart++;
3✔
82

83
        $startColumn = 1;
3✔
84
        if ($tokens[$lineStart]['code'] === T_WHITESPACE) {
3✔
85
            $startColumn = $tokens[($lineStart + 1)]['column'];
3✔
86
        } else if ($tokens[$lineStart]['code'] === T_INLINE_HTML) {
3✔
87
            $trimmed = ltrim($tokens[$lineStart]['content']);
3✔
88
            if ($trimmed === '') {
3✔
89
                $startColumn = $tokens[($lineStart + 1)]['column'];
3✔
90
            } else {
91
                $startColumn = (strlen($tokens[$lineStart]['content']) - strlen($trimmed));
3✔
92
            }
93
        }
94

95
        // Check that the closing brace is on it's own line.
96
        for ($lastContent = ($scopeEnd - 1); $lastContent > $scopeStart; $lastContent--) {
3✔
97
            if ($tokens[$lastContent]['code'] === T_WHITESPACE || $tokens[$lastContent]['code'] === T_OPEN_TAG) {
3✔
98
                continue;
3✔
99
            }
100

101
            if ($tokens[$lastContent]['code'] === T_INLINE_HTML
3✔
102
                && ltrim($tokens[$lastContent]['content']) === ''
3✔
103
            ) {
104
                continue;
3✔
105
            }
106

107
            break;
3✔
108
        }
109

110
        if ($tokens[$lastContent]['line'] === $tokens[$scopeEnd]['line']) {
3✔
111
            $error = 'Closing brace must be on a line by itself';
3✔
112
            $fix   = $phpcsFile->addFixableError($error, $scopeEnd, 'Line');
3✔
113
            if ($fix === true) {
3✔
114
                $phpcsFile->fixer->addNewlineBefore($scopeEnd);
3✔
115
            }
116

117
            return;
3✔
118
        }
119

120
        // Check now that the closing brace is lined up correctly.
121
        $lineStart = ($scopeEnd - 1);
3✔
122
        for ($lineStart; $lineStart > 0; $lineStart--) {
3✔
123
            if (strpos($tokens[$lineStart]['content'], $phpcsFile->eolChar) !== false) {
3✔
124
                break;
3✔
125
            }
126
        }
127

128
        $lineStart++;
3✔
129

130
        $braceIndent = 0;
3✔
131
        if ($tokens[$lineStart]['code'] === T_WHITESPACE) {
3✔
132
            $braceIndent = ($tokens[($lineStart + 1)]['column'] - 1);
3✔
133
        } else if ($tokens[$lineStart]['code'] === T_INLINE_HTML) {
3✔
134
            $trimmed = ltrim($tokens[$lineStart]['content']);
3✔
135
            if ($trimmed === '') {
3✔
136
                $braceIndent = ($tokens[($lineStart + 1)]['column'] - 1);
3✔
137
            } else {
UNCOV
138
                $braceIndent = (strlen($tokens[$lineStart]['content']) - strlen($trimmed) - 1);
×
139
            }
140
        }
141

142
        $fix = false;
3✔
143
        if ($tokens[$stackPtr]['code'] === T_CASE
3✔
144
            || $tokens[$stackPtr]['code'] === T_DEFAULT
3✔
145
        ) {
146
            // BREAK statements should be indented n spaces from the
147
            // CASE or DEFAULT statement.
148
            $expectedIndent = ($startColumn + $this->indent - 1);
3✔
149
            if ($braceIndent !== $expectedIndent) {
3✔
150
                $error = 'Case breaking statement indented incorrectly; expected %s spaces, found %s';
3✔
151
                $data  = [
2✔
152
                    $expectedIndent,
3✔
153
                    $braceIndent,
3✔
154
                ];
2✔
155
                $fix   = $phpcsFile->addFixableError($error, $scopeEnd, 'BreakIndent', $data);
3✔
156
            }
157
        } else {
158
            $expectedIndent = max(0, ($startColumn - 1));
3✔
159
            if ($braceIndent !== $expectedIndent) {
3✔
160
                $error = 'Closing brace indented incorrectly; expected %s spaces, found %s';
3✔
161
                $data  = [
2✔
162
                    $expectedIndent,
3✔
163
                    $braceIndent,
3✔
164
                ];
2✔
165
                $fix   = $phpcsFile->addFixableError($error, $scopeEnd, 'Indent', $data);
3✔
166
            }
167
        }//end if
168

169
        if ($fix === true) {
3✔
170
            $spaces = str_repeat(' ', $expectedIndent);
3✔
171
            if ($braceIndent === 0) {
3✔
172
                $phpcsFile->fixer->addContentBefore($lineStart, $spaces);
3✔
173
            } else {
174
                $phpcsFile->fixer->replaceToken($lineStart, ltrim($tokens[$lineStart]['content']));
3✔
175
                $phpcsFile->fixer->addContentBefore($lineStart, $spaces);
3✔
176
            }
177
        }
178

179
    }//end process()
1✔
180

181

182
}//end class
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc