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

PHPCSStandards / PHPCSExtra / 15639493787

13 Jun 2025 04:35PM UTC coverage: 97.39% (-2.5%) from 99.85%
15639493787

Pull #367

github

web-flow
Merge 844746ea8 into 10343591c
Pull Request #367: Update for compatibility with PHPCS 4.0

55 of 140 new or added lines in 8 files covered. (39.29%)

1 existing line in 1 file now uncovered.

3358 of 3448 relevant lines covered (97.39%)

3.58 hits per line

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

98.41
/Universal/Sniffs/UseStatements/NoLeadingBackslashSniff.php
1
<?php
2
/**
3
 * PHPCSExtra, a collection of sniffs and standards for use with PHP_CodeSniffer.
4
 *
5
 * @package   PHPCSExtra
6
 * @copyright 2020 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\Files\File;
14
use PHP_CodeSniffer\Sniffs\Sniff;
15
use PHP_CodeSniffer\Util\Tokens;
16
use PHPCSUtils\Utils\UseStatements;
17

18
/**
19
 * Verifies that names being imported in import use statements do not start with a leading backslash.
20
 *
21
 * This sniff handles all types of import use statements supported by PHP, in contrast to any
22
 * of the other sniffs for the same in, for instance, the PSR12 or the Slevomat standard,
23
 * all of which are incomplete.
24
 *
25
 * @since 1.0.0
26
 */
27
final class NoLeadingBackslashSniff implements Sniff
28
{
29

30
    /**
31
     * Name of the metric.
32
     *
33
     * @since 1.0.0
34
     *
35
     * @var string
36
     */
37
    const METRIC_NAME = 'Import use with leading backslash';
38

39
    /**
40
     * Returns an array of tokens this test wants to listen for.
41
     *
42
     * @since 1.0.0
43
     *
44
     * @return array<int|string>
45
     */
46
    public function register()
4✔
47
    {
48
        return [\T_USE];
4✔
49
    }
50

51
    /**
52
     * Processes this test, when one of its tokens is encountered.
53
     *
54
     * @since 1.0.0
55
     *
56
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
57
     * @param int                         $stackPtr  The position of the current token in the
58
     *                                               stack passed in $tokens.
59
     *
60
     * @return void
61
     */
62
    public function process(File $phpcsFile, $stackPtr)
4✔
63
    {
64
        if (UseStatements::isImportUse($phpcsFile, $stackPtr) === false) {
4✔
65
            // Trait or closure use statement.
66
            return;
4✔
67
        }
68

69
        $endOfStatement = $phpcsFile->findNext([\T_SEMICOLON, \T_CLOSE_TAG, \T_OPEN_USE_GROUP], ($stackPtr + 1));
4✔
70
        if ($endOfStatement === false) {
4✔
71
            // Live coding or parse error.
72
            return;
4✔
73
        }
74

75
        $tokens  = $phpcsFile->getTokens();
4✔
76
        $current = $stackPtr;
4✔
77

78
        do {
79
            $continue = $this->processImport($phpcsFile, $current, $endOfStatement);
4✔
80
            if ($continue === false) {
4✔
81
                break;
4✔
82
            }
83

84
            // Move the stackPtr forward to the next part of the use statement, if any.
85
            $current = $phpcsFile->findNext(\T_COMMA, ($current + 1), $endOfStatement);
4✔
86
        } while ($current !== false);
4✔
87

88
        if ($tokens[$endOfStatement]['code'] !== \T_OPEN_USE_GROUP) {
4✔
89
            // Finished the statement.
90
            return;
4✔
91
        }
92

93
        $current        = $endOfStatement; // Group open brace.
4✔
94
        $endOfStatement = $phpcsFile->findNext([\T_CLOSE_USE_GROUP], ($endOfStatement + 1));
4✔
95
        if ($endOfStatement === false) {
4✔
96
            // Live coding or parse error.
97
            return;
4✔
98
        }
99

100
        do {
101
            $continue = $this->processImport($phpcsFile, $current, $endOfStatement, true);
4✔
102
            if ($continue === false) {
4✔
103
                break;
4✔
104
            }
105

106
            // Move the stackPtr forward to the next part of the use statement, if any.
107
            $current = $phpcsFile->findNext(\T_COMMA, ($current + 1), $endOfStatement);
4✔
108
        } while ($current !== false);
4✔
109
    }
2✔
110

111
    /**
112
     * Examine an individual import statement.
113
     *
114
     * @param \PHP_CodeSniffer\Files\File $phpcsFile      The file being scanned.
115
     * @param int                         $stackPtr       The position of the current token.
116
     * @param int                         $endOfStatement End token for the current import statement.
117
     * @param bool                        $groupUse       Whether the current statement is a partial one
118
     *                                                    within a group use statement.
119
     *
120
     * @return bool Whether or not to continue examining this import use statement.
121
     */
122
    private function processImport(File $phpcsFile, $stackPtr, $endOfStatement, $groupUse = false)
4✔
123
    {
124
        $tokens = $phpcsFile->getTokens();
4✔
125

126
        $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($stackPtr + 1), $endOfStatement, true);
4✔
127
        if ($nextNonEmpty === false) {
4✔
128
            // Reached the end of the statement.
129
            return false;
4✔
130
        }
131

132
        // Skip past 'function'/'const' keyword.
133
        $contentLC = \strtolower($tokens[$nextNonEmpty]['content']);
4✔
134
        if ($tokens[$nextNonEmpty]['code'] === \T_STRING
4✔
135
            && ($contentLC === 'function' || $contentLC === 'const')
4✔
136
        ) {
2✔
137
            $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($nextNonEmpty + 1), $endOfStatement, true);
4✔
138
            if ($nextNonEmpty === false) {
4✔
139
                // Reached the end of the statement.
140
                return false;
4✔
141
            }
142
        }
2✔
143

144
        if ($tokens[$nextNonEmpty]['code'] === \T_NS_SEPARATOR
4✔
145
            || $tokens[$nextNonEmpty]['code'] === \T_NAME_FULLY_QUALIFIED
4✔
146
        ) {
2✔
147
            $phpcsFile->recordMetric($nextNonEmpty, self::METRIC_NAME, 'yes');
4✔
148

149
            $error = 'An import use statement should never start with a leading backslash';
4✔
150
            $code  = 'LeadingBackslashFound';
4✔
151

152
            if ($groupUse === true) {
4✔
153
                $error = 'Parse error: partial import use statement in a use group starting with a leading backslash';
4✔
154
                $code  = 'LeadingBackslashFoundInGroup';
4✔
155
            }
2✔
156

157
            $fix = $phpcsFile->addFixableError($error, $nextNonEmpty, $code);
4✔
158

159
            if ($fix === true) {
4✔
160
                if ($tokens[$nextNonEmpty]['code'] === \T_NS_SEPARATOR) {
4✔
161
                    // PHPCS 3.x.
162
                    if ($tokens[$nextNonEmpty - 1]['code'] !== \T_WHITESPACE) {
4✔
163
                        $phpcsFile->fixer->replaceToken($nextNonEmpty, ' ');
4✔
164
                    } else {
2✔
165
                        $phpcsFile->fixer->replaceToken($nextNonEmpty, '');
4✔
166
                    }
167
                } else {
2✔
168
                    // PHPCS 4.x / T_NAME_FULLY_QUALIFIED.
NEW
169
                    $phpcsFile->fixer->replaceToken($nextNonEmpty, \ltrim($tokens[$nextNonEmpty]['content'], '\\'));
×
170
                }
171
            }
2✔
172
        } else {
2✔
173
            $phpcsFile->recordMetric($nextNonEmpty, self::METRIC_NAME, 'no');
4✔
174
        }
175

176
        return true;
4✔
177
    }
178
}
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