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

PHPCSStandards / PHPCSExtra / 14563837141

20 Apr 2025 10:20PM UTC coverage: 99.762% (-0.03%) from 99.789%
14563837141

Pull #329

github

web-flow
Merge f2c15de2e into c3c574154
Pull Request #329: Universal/TypeSeparatorSpacing: add support for PHP 8.2 DNF types

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

1 existing line in 1 file now uncovered.

3352 of 3360 relevant lines covered (99.76%)

3.67 hits per line

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

98.33
/Universal/Sniffs/WhiteSpace/DisallowInlineTabsSniff.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\WhiteSpace;
12

13
use PHP_CodeSniffer\Files\File;
14
use PHP_CodeSniffer\Sniffs\Sniff;
15
use PHP_CodeSniffer\Util\Tokens;
16
use PHPCSExtra\Universal\Helpers\DummyTokenizer;
17
use PHPCSUtils\BackCompat\Helper;
18
use PHPCSUtils\Tokens\Collections;
19

20
/**
21
 * Enforces using spaces for mid-line alignment.
22
 *
23
 * While tab versus space based indentation is a question of preference, for mid-line
24
 * alignment, spaces should always be preferred, as using tabs will result in inconsistent
25
 * formatting depending on the dev-user's chosen tab width.
26
 *
27
 * This sniff is especially useful for tab-indentation based standards which use the
28
 * `Generic.Whitespace.DisallowSpaceIndent` sniff to enforce this.
29
 *
30
 * **DO** make sure to set the PHPCS native `tab-width` configuration for the best results.
31
 * <code>
32
 *   <arg name="tab-width" value="4"/>
33
 * </code>
34
 *
35
 * The PHPCS native `Generic.Whitespace.DisallowTabIndent` sniff oversteps its reach and silently
36
 * does mid-line tab to space replacements as well.
37
 * However, the sister-sniff `Generic.Whitespace.DisallowSpaceIndent` leaves mid-line tabs/spaces alone.
38
 * This sniff fills that gap.
39
 *
40
 * @since 1.0.0
41
 */
42
final class DisallowInlineTabsSniff implements Sniff
43
{
44

45
    /**
46
     * The --tab-width CLI value that is being used.
47
     *
48
     * @since 1.0.0
49
     *
50
     * @var int
51
     */
52
    private $tabWidth;
53

54
    /**
55
     * Tokens to check for mid-line tabs.
56
     *
57
     * @since 1.0.0
58
     *
59
     * @var array<int|string, true>
60
     */
61
    private $find = [
62
        \T_WHITESPACE             => true,
63
        \T_DOC_COMMENT_WHITESPACE => true,
64
        \T_DOC_COMMENT_STRING     => true,
65
        \T_COMMENT                => true,
66
        \T_START_HEREDOC          => true,
67
        \T_START_NOWDOC           => true,
68
        \T_YIELD_FROM             => true,
69
    ];
70

71
    /**
72
     * Registers the tokens that this sniff wants to listen for.
73
     *
74
     * @since 1.0.0
75
     *
76
     * @return array<int|string>
77
     */
78
    public function register()
4✔
79
    {
80
        return Collections::phpOpenTags();
4✔
81
    }
82

83
    /**
84
     * Processes this test, when one of its tokens is encountered.
85
     *
86
     * @since 1.0.0
87
     *
88
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
89
     * @param int                         $stackPtr  The position of the current token
90
     *                                               in the stack passed in $tokens.
91
     *
92
     * @return int Integer stack pointer to skip the rest of the file.
93
     */
94
    public function process(File $phpcsFile, $stackPtr)
4✔
95
    {
96
        if (isset($this->tabWidth) === false) {
4✔
97
            $this->tabWidth = (int) Helper::getTabWidth($phpcsFile);
4✔
98
        }
2✔
99

100
        if (\defined('PHP_CODESNIFFER_IN_TESTS')) {
4✔
101
            $this->tabWidth = (int) Helper::getCommandLineData($phpcsFile, 'tabWidth');
4✔
102
        }
2✔
103

104
        $tokens = $phpcsFile->getTokens();
4✔
105
        $dummy  = new DummyTokenizer('', $phpcsFile->config);
4✔
106

107
        for ($i = 0; $i < $phpcsFile->numTokens; $i++) {
4✔
108
            // Skip all non-target tokens and skip whitespace at the start of a new line.
109
            if (isset($this->find[$tokens[$i]['code']]) === false
4✔
110
                || (($tokens[$i]['code'] === \T_WHITESPACE
4✔
111
                    || $tokens[$i]['code'] === \T_DOC_COMMENT_WHITESPACE)
4✔
112
                    && $tokens[$i]['column'] === 1)
4✔
113
            ) {
2✔
114
                continue;
4✔
115
            }
2✔
116

117
            // If tabs haven't been converted to spaces by the tokenizer, do so now.
118
            $token = $tokens[$i];
4✔
119
            if (isset($token['orig_content']) === false) {
4✔
120
                if ($token['content'] === '' || \strpos($token['content'], "\t") === false) {
4✔
121
                    // If there are no tabs, we can continue, no matter what.
122
                    continue;
4✔
123
                }
124

125
                $dummy->replaceTabsInToken($token);
4✔
126
            }
2✔
127

128
            /*
129
             * Tokens only have the 'orig_content' key if they contain tabs,
130
             * so from here on out, we **know** there will be tabs in the content.
131
             */
132
            $origContent = $token['orig_content'];
4✔
133
            $commentOnly = '';
4✔
134

135
            $multiLineComment = false;
4✔
136
            if (($tokens[$i]['code'] === \T_COMMENT
4✔
137
                || isset(Tokens::$phpcsCommentTokens[$tokens[$i]['code']]))
4✔
138
                 && $tokens[$i]['column'] === 1
4✔
139
                 && ($tokens[($i - 1)]['code'] === \T_COMMENT
4✔
140
                 || isset(Tokens::$phpcsCommentTokens[$tokens[($i - 1)]['code']]))
4✔
141
            ) {
2✔
142
                $multiLineComment = true;
4✔
143
            }
2✔
144

145
            if ($multiLineComment === true) {
4✔
146
                // This is the subsequent line of a multi-line comment. Account for indentation.
147
                $commentOnly = \ltrim($origContent);
4✔
148
                if ($commentOnly === '' || \strpos($commentOnly, "\t") === false) {
4✔
149
                    continue;
4✔
150
                }
151
            }
2✔
152

153
            /*
154
             * For "yield from", we should only handle tabs _between_ the keywords (single token),
155
             * not indentation for those situations where the keyword is split in multiple tokens.
156
             */
157
            if ($tokens[$i]['code'] === \T_YIELD_FROM
4✔
158
                && \preg_match('`^yield.+from$`i', $tokens[$i]['content']) !== 1
4✔
159
            ) {
2✔
UNCOV
160
                continue;
×
161
            }
162

163
            $fix = $phpcsFile->addFixableError(
4✔
164
                'Spaces must be used for mid-line alignment; tabs are not allowed',
4✔
165
                $i,
4✔
166
                'NonIndentTabsUsed'
2✔
167
            );
4✔
168

169
            if ($fix === false) {
4✔
170
                continue;
4✔
171
            }
172

173
            $indent = '';
4✔
174
            if ($multiLineComment === true) {
4✔
175
                // Take the original indent (tabs/spaces) and combine with the tab-replaced comment content.
176
                $indent           = \str_replace($commentOnly, '', $origContent);
4✔
177
                $token['content'] = \ltrim($token['content']);
4✔
178
            }
2✔
179

180
            $phpcsFile->fixer->replaceToken($i, $indent . $token['content']);
4✔
181
        }
2✔
182

183
        // Scanned the whole file in one go. Don't scan this file again.
184
        return $phpcsFile->numTokens;
4✔
185
    }
186
}
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