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

PHPCSStandards / PHPCSExtra / 5307357493

pending completion
5307357493

Pull #247

github

web-flow
Merge 4fbf9d4ad into fbd0b1fd4
Pull Request #247: :sparkles: New `Universal.UseStatements.KeywordSpacing` sniff

58 of 59 new or added lines in 1 file covered. (98.31%)

2245 of 2252 relevant lines covered (99.69%)

3.98 hits per line

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

98.31
/Universal/Sniffs/UseStatements/KeywordSpacingSniff.php
1
<?php
2
/**
3
 * PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
4
 *
5
 * @package   PHPCSExtra
6
 * @copyright 2023 PHPCSExtra Contributors
7
 * @license   https://opensource.org/licenses/LGPL-3.0 LGPL3
8
 * @link      https://github.com/PHPCSStandards/PHPCSExtra
9
 */
10

11
namespace PHPCSExtra\Universal\Sniffs\UseStatements;
12

13
use PHP_CodeSniffer\Sniffs\Sniff;
14
use PHP_CodeSniffer\Files\File;
15
use PHP_CodeSniffer\Util\Tokens;
16
use PHPCSUtils\Fixers\SpacesFixer;
17
use PHPCSUtils\Utils\UseStatements;
18

19
/**
20
 * Enforce a single space after the keywords in import `use` statements.
21
 *
22
 * The keywords this sniff check are `use`, `function`, `const` and `as`.
23
 * For `as`, the space *before* the keyword is also checked to be a single space.
24
 *
25
 * @since 1.1.0
26
 */
27
final class KeywordSpacingSniff implements Sniff
28
{
29

30
    /**
31
     * Name of the metric for spacing before.
32
     *
33
     * @since 1.1.0
34
     *
35
     * @var string
36
     */
37
    const METRIC_NAME_BEFORE = 'Space before "%s" keyword in import use statement';
38

39
    /**
40
     * Name of the metric for spacing after.
41
     *
42
     * @since 1.1.0
43
     *
44
     * @var string
45
     */
46
    const METRIC_NAME_AFTER = 'Space after "%s" keyword in import use statement';
47

48
    /**
49
     * A list of keywords that are tokenized as `T_STRING` in import `use` statements.
50
     *
51
     * @since 1.1.0
52
     *
53
     * @var array(string => string)
54
     */
55
    protected $keywords = [
56
        'const'    => true,
57
        'function' => true,
58
    ];
59

60
    /**
61
     * Returns an array of tokens this sniff wants to listen for.
62
     *
63
     * @since 1.1.0
64
     *
65
     * @return array
66
     */
67
    public function register()
68
    {
69
        return [\T_USE];
4✔
70
    }
71

72
    /**
73
     * Processes this test, when one of its tokens is encountered.
74
     *
75
     * @since 1.1.0
76
     *
77
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
78
     * @param int                         $stackPtr  The position of the current token in the
79
     *                                               stack passed in $tokens.
80
     *
81
     * @return void
82
     */
83
    public function process(File $phpcsFile, $stackPtr)
84
    {
85
        if (UseStatements::isImportUse($phpcsFile, $stackPtr) === false) {
4✔
86
            // Trait or closure use statement.
87
            return;
4✔
88
        }
89

90
        $tokens         = $phpcsFile->getTokens();
4✔
91
        $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG], ($stackPtr + 1));
4✔
92
        if ($endOfStatement === false) {
4✔
93
            // Live coding or parse error.
94
            return;
4✔
95
        }
96

97
        // Check the spacing after the `use` keyword.
98
        $this->checkSpacingAfterKeyword($phpcsFile, $stackPtr, $tokens[$stackPtr]['content']);
4✔
99

100
        // Check the spacing before and after each `as` keyword.
101
        $current = $stackPtr;
4✔
102
        do {
103
            $current = $phpcsFile->findNext(\T_AS, ($current + 1), $endOfStatement);
4✔
104
            if ($current === false) {
4✔
105
                break;
4✔
106
            }
107

108
            // Prevent false positives when "as" is used within a "name".
109
            if (isset(Tokens::$emptyTokens[$tokens[($current - 1)]['code']]) === true) {
4✔
110
                $this->checkSpacingBeforeKeyword($phpcsFile, $current, $tokens[$current]['content']);
4✔
111
                $this->checkSpacingAfterKeyword($phpcsFile, $current, $tokens[$current]['content']);
4✔
112
            }
2✔
113
        } while (true);
4✔
114

115
        /*
116
         * Check the spacing after `function` and `const` keywords.
117
         */
118
        $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
4✔
119
        if (isset($this->keywords[\strtolower($tokens[$nextNonEmpty]['content'])]) === true) {
4✔
120
            // Keyword found at start of statement, applies to whole statement.
121
            $this->checkSpacingAfterKeyword($phpcsFile, $nextNonEmpty, $tokens[$nextNonEmpty]['content']);
4✔
122
            return;
4✔
123
        }
124

125
        // This may still be a group use statement with function/const substatements.
126
        $openGroup = $phpcsFile->findNext(\T_OPEN_USE_GROUP, ($stackPtr + 1), $endOfStatement);
4✔
127
        if ($openGroup === false) {
4✔
128
            // Not a group use statement.
129
            return;
4✔
130
        }
131

132
        $closeGroup = $phpcsFile->findNext(\T_CLOSE_USE_GROUP, ($openGroup + 1), $endOfStatement);
4✔
133

134
        $current = $openGroup;
4✔
135
        do {
136
            $current = $phpcsFile->findNext(Tokens::$emptyTokens, ($current + 1), $closeGroup, true);
4✔
137
            if ($current === false) {
4✔
138
                return;
4✔
139
            }
140

141
            if (isset($this->keywords[\strtolower($tokens[$current]['content'])]) === true) {
4✔
142
                $this->checkSpacingAfterKeyword($phpcsFile, $current, $tokens[$current]['content']);
4✔
143
            }
2✔
144

145
            // We're within the use group, so find the next comma.
146
            $current = $phpcsFile->findNext(\T_COMMA, ($current + 1), $closeGroup);
4✔
147
        } while ($current !== false);
4✔
NEW
148
    }
×
149

150
    /**
151
     * Check the spacing before a found keyword.
152
     *
153
     * @since 1.1.0
154
     *
155
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
156
     * @param int                         $stackPtr  The position of the keyword in the token stack.
157
     * @param string                      $content   The keyword as found.
158
     *
159
     * @return void
160
     */
161
    public function checkSpacingBeforeKeyword(File $phpcsFile, $stackPtr, $content)
162
    {
163
        $contentLC    = \strtolower($content);
4✔
164
        $prevNonEmpty = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($stackPtr - 1), null, true);
4✔
165

166
        SpacesFixer::checkAndFix(
4✔
167
            $phpcsFile,
4✔
168
            $stackPtr,
4✔
169
            $prevNonEmpty,
4✔
170
            1, // Expected spaces.
4✔
171
            'Expected %s before the "' . $contentLC . '" keyword. Found: %s',
4✔
172
            'SpaceBefore' . \ucfirst($contentLC),
4✔
173
            'error',
4✔
174
            0, // Severity.
4✔
175
            \sprintf(self::METRIC_NAME_BEFORE, $contentLC)
4✔
176
        );
2✔
177
    }
4✔
178

179
    /**
180
     * Check the spacing after a found keyword.
181
     *
182
     * @since 1.1.0
183
     *
184
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
185
     * @param int                         $stackPtr  The position of the keyword in the token stack.
186
     * @param string                      $content   The keyword as found.
187
     *
188
     * @return void
189
     */
190
    public function checkSpacingAfterKeyword(File $phpcsFile, $stackPtr, $content)
191
    {
192
        $contentLC    = \strtolower($content);
4✔
193
        $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), null, true);
4✔
194

195
        SpacesFixer::checkAndFix(
4✔
196
            $phpcsFile,
4✔
197
            $stackPtr,
4✔
198
            $nextNonEmpty,
4✔
199
            1, // Expected spaces.
4✔
200
            'Expected %s after the "' . $contentLC . '" keyword. Found: %s',
4✔
201
            'SpaceAfter' . \ucfirst($contentLC),
4✔
202
            'error',
4✔
203
            0, // Severity.
4✔
204
            \sprintf(self::METRIC_NAME_AFTER, $contentLC)
4✔
205
        );
2✔
206
    }
4✔
207
}
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