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

PHPCSStandards / PHP_CodeSniffer / 17663621563

12 Sep 2025 03:25AM UTC coverage: 78.786%. Remained the same
17663621563

push

github

web-flow
Merge pull request #1243 from PHPCSStandards/phpcs-4.x/feature/155-normalize-some-code-style-rules-5

CS: normalize code style rules [5]

294 of 308 new or added lines in 191 files covered. (95.45%)

2354 existing lines in 130 files now uncovered.

19732 of 25045 relevant lines covered (78.79%)

96.47 hits per line

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

96.84
/src/Standards/PSR12/Sniffs/ControlStructures/BooleanOperatorPlacementSniff.php
1
<?php
2
/**
3
 * Checks that control structures have boolean operators in the correct place.
4
 *
5
 * @author    Greg Sherwood <gsherwood@squiz.net>
6
 * @copyright 2006-2019 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\PSR12\Sniffs\ControlStructures;
11

12
use PHP_CodeSniffer\Files\File;
13
use PHP_CodeSniffer\Sniffs\Sniff;
14

15
class BooleanOperatorPlacementSniff implements Sniff
16
{
17

18
    /**
19
     * Boolean operator tokens.
20
     *
21
     * @var array<int|string>
22
     */
23
    private const BOOLEAN_OPERATORS = [
24
        T_BOOLEAN_AND,
25
        T_BOOLEAN_OR,
26
    ];
27

28
    /**
29
     * Used to restrict the placement of the boolean operator.
30
     *
31
     * Allowed value are "first" or "last".
32
     *
33
     * @var string|null
34
     */
35
    public $allowOnly = null;
36

37

38
    /**
39
     * Returns an array of tokens this test wants to listen for.
40
     *
41
     * @return array<int|string>
42
     */
43
    public function register()
3✔
44
    {
45
        return [
2✔
46
            T_IF,
3✔
47
            T_WHILE,
3✔
48
            T_SWITCH,
3✔
49
            T_ELSEIF,
3✔
50
            T_MATCH,
3✔
51
        ];
2✔
52
    }
53

54

55
    /**
56
     * Processes this test, when one of its tokens is encountered.
57
     *
58
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
59
     * @param int                         $stackPtr  The position of the current token
60
     *                                               in the stack passed in $tokens.
61
     *
62
     * @return void
63
     */
64
    public function process(File $phpcsFile, int $stackPtr)
3✔
65
    {
66
        $tokens = $phpcsFile->getTokens();
3✔
67

68
        if (isset($tokens[$stackPtr]['parenthesis_opener']) === false
3✔
69
            || isset($tokens[$stackPtr]['parenthesis_closer']) === false
3✔
70
        ) {
UNCOV
71
            return;
×
72
        }
73

74
        $parenOpener = $tokens[$stackPtr]['parenthesis_opener'];
3✔
75
        $parenCloser = $tokens[$stackPtr]['parenthesis_closer'];
3✔
76

77
        if ($tokens[$parenOpener]['line'] === $tokens[$parenCloser]['line']) {
3✔
78
            // Conditions are all on the same line.
79
            return;
3✔
80
        }
81

82
        if ($this->allowOnly === 'first' || $this->allowOnly === 'last') {
3✔
83
            $position = $this->allowOnly;
3✔
84
        } else {
85
            $position = null;
3✔
86
        }
87

88
        $operator  = $parenOpener;
3✔
89
        $error     = false;
3✔
90
        $operators = [];
3✔
91

92
        do {
93
            $operator = $phpcsFile->findNext(self::BOOLEAN_OPERATORS, ($operator + 1), $parenCloser);
3✔
94
            if ($operator === false) {
3✔
95
                break;
3✔
96
            }
97

98
            $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($operator - 1), $parenOpener, true);
3✔
99
            if ($prev === false) {
3✔
100
                // Parse error.
UNCOV
101
                return;
×
102
            }
103

104
            $next = $phpcsFile->findNext(T_WHITESPACE, ($operator + 1), $parenCloser, true);
3✔
105
            if ($next === false) {
3✔
106
                // Parse error.
UNCOV
107
                return;
×
108
            }
109

110
            $firstOnLine = false;
3✔
111
            $lastOnLine  = false;
3✔
112

113
            if ($tokens[$prev]['line'] < $tokens[$operator]['line']) {
3✔
114
                // The boolean operator is the first content on the line.
115
                $firstOnLine = true;
3✔
116
            }
117

118
            if ($tokens[$next]['line'] > $tokens[$operator]['line']) {
3✔
119
                // The boolean operator is the last content on the line.
120
                $lastOnLine = true;
3✔
121
            }
122

123
            if ($firstOnLine === true && $lastOnLine === true) {
3✔
124
                // The operator is the only content on the line.
125
                // Don't record it because we can't determine
126
                // placement information from looking at it.
127
                continue;
3✔
128
            }
129

130
            $operators[] = $operator;
3✔
131

132
            if ($firstOnLine === false && $lastOnLine === false) {
3✔
133
                // It's in the middle of content, so we can't determine
134
                // placement information from looking at it, but we may
135
                // still need to process it.
136
                continue;
3✔
137
            }
138

139
            if ($firstOnLine === true) {
3✔
140
                if ($position === null) {
3✔
141
                    $position = 'first';
3✔
142
                }
143

144
                if ($position !== 'first') {
3✔
145
                    $error = true;
3✔
146
                }
147
            } else {
148
                if ($position === null) {
3✔
149
                    $position = 'last';
3✔
150
                }
151

152
                if ($position !== 'last') {
3✔
153
                    $error = true;
3✔
154
                }
155
            }
156
        } while ($operator !== false);
3✔
157

158
        if ($error === false) {
3✔
159
            return;
3✔
160
        }
161

162
        switch ($this->allowOnly) {
3✔
163
        case 'first':
3✔
164
            $error = 'Boolean operators between conditions must be at the beginning of the line';
3✔
165
            break;
3✔
166
        case 'last':
3✔
167
            $error = 'Boolean operators between conditions must be at the end of the line';
3✔
168
            break;
3✔
169
        default:
170
            $error = 'Boolean operators between conditions must be at the beginning or end of the line, but not both';
3✔
171
        }
172

173
        $fix = $phpcsFile->addFixableError($error, $stackPtr, 'FoundMixed');
3✔
174
        if ($fix === false) {
3✔
175
            return;
3✔
176
        }
177

178
        $phpcsFile->fixer->beginChangeset();
3✔
179
        foreach ($operators as $operator) {
3✔
180
            $prev = $phpcsFile->findPrevious(T_WHITESPACE, ($operator - 1), $parenOpener, true);
3✔
181
            $next = $phpcsFile->findNext(T_WHITESPACE, ($operator + 1), $parenCloser, true);
3✔
182

183
            if ($position === 'last') {
3✔
184
                if ($tokens[$next]['line'] === $tokens[$operator]['line']) {
3✔
185
                    if ($tokens[$prev]['line'] === $tokens[$operator]['line']) {
3✔
186
                        // Move the content after the operator to the next line.
187
                        if ($tokens[($operator + 1)]['code'] === T_WHITESPACE) {
3✔
188
                            $phpcsFile->fixer->replaceToken(($operator + 1), '');
3✔
189
                        }
190

191
                        $first   = $phpcsFile->findFirstOnLine(T_WHITESPACE, $operator, true);
3✔
192
                        $padding = str_repeat(' ', ($tokens[$first]['column'] - 1));
3✔
193
                        $phpcsFile->fixer->addContent($operator, $phpcsFile->eolChar . $padding);
3✔
194
                    } else {
195
                        // Move the operator to the end of the previous line.
196
                        if ($tokens[($operator + 1)]['code'] === T_WHITESPACE) {
3✔
197
                            $phpcsFile->fixer->replaceToken(($operator + 1), '');
3✔
198
                        }
199

200
                        $phpcsFile->fixer->addContent($prev, ' ' . $tokens[$operator]['content']);
3✔
201
                        $phpcsFile->fixer->replaceToken($operator, '');
3✔
202
                    }
203
                }//end if
204
            } else {
205
                if ($tokens[$prev]['line'] === $tokens[$operator]['line']) {
3✔
206
                    if ($tokens[$next]['line'] === $tokens[$operator]['line']) {
3✔
207
                        // Move the operator, and the rest of the expression, to the next line.
208
                        if ($tokens[($operator - 1)]['code'] === T_WHITESPACE) {
3✔
209
                            $phpcsFile->fixer->replaceToken(($operator - 1), '');
3✔
210
                        }
211

212
                        $first   = $phpcsFile->findFirstOnLine(T_WHITESPACE, $operator, true);
3✔
213
                        $padding = str_repeat(' ', ($tokens[$first]['column'] - 1));
3✔
214
                        $phpcsFile->fixer->addContentBefore($operator, $phpcsFile->eolChar . $padding);
3✔
215
                    } else {
216
                        // Move the operator to the start of the next line.
217
                        if ($tokens[($operator - 1)]['code'] === T_WHITESPACE) {
3✔
218
                            $phpcsFile->fixer->replaceToken(($operator - 1), '');
3✔
219
                        }
220

221
                        $phpcsFile->fixer->addContentBefore($next, $tokens[$operator]['content'] . ' ');
3✔
222
                        $phpcsFile->fixer->replaceToken($operator, '');
3✔
223
                    }
224
                }//end if
225
            }//end if
226
        }//end foreach
227

228
        $phpcsFile->fixer->endChangeset();
3✔
229
    }
1✔
230
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc