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

PHPCSStandards / PHP_CodeSniffer / 17137405110

21 Aug 2025 07:48PM UTC coverage: 78.884% (-0.03%) from 78.912%
17137405110

Pull #1175

github

web-flow
Merge 4fed0943c into 038abb652
Pull Request #1175: [4.0] Fix exit code when phpcbf is processing STDIN

0 of 23 new or added lines in 2 files covered. (0.0%)

90 existing lines in 6 files now uncovered.

19848 of 25161 relevant lines covered (78.88%)

89.71 hits per line

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

96.53
/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php
1
<?php
2
/**
3
 * Verifies that properties are declared 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\PSR2\Sniffs\Classes;
11

12
use Exception;
13
use PHP_CodeSniffer\Files\File;
14
use PHP_CodeSniffer\Sniffs\AbstractScopeSniff;
15
use PHP_CodeSniffer\Sniffs\AbstractVariableSniff;
16
use PHP_CodeSniffer\Util\Tokens;
17

18
class PropertyDeclarationSniff extends AbstractVariableSniff
19
{
20

21

22
    /**
23
     * Only listen to variables within OO scopes.
24
     */
25
    public function __construct()
3✔
26
    {
27
        AbstractScopeSniff::__construct(Tokens::OO_SCOPE_TOKENS, [T_VARIABLE], false);
3✔
28

29
    }//end __construct()
1✔
30

31

32
    /**
33
     * Processes the function tokens within the class.
34
     *
35
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
36
     * @param int                         $stackPtr  The position where the token was found.
37
     *
38
     * @return void
39
     */
40
    protected function processMemberVar(File $phpcsFile, $stackPtr)
3✔
41
    {
42
        try {
43
            $propertyInfo = $phpcsFile->getMemberProperties($stackPtr);
3✔
44
        } catch (Exception $e) {
3✔
45
            // Parse error: property in enum. Ignore.
46
            return;
3✔
47
        }
48

49
        $tokens = $phpcsFile->getTokens();
3✔
50

51
        if ($tokens[$stackPtr]['content'][1] === '_') {
3✔
52
            $error = 'Property name "%s" should not be prefixed with an underscore to indicate visibility';
3✔
53
            $data  = [$tokens[$stackPtr]['content']];
3✔
54
            $phpcsFile->addWarning($error, $stackPtr, 'Underscore', $data);
3✔
55
        }
56

57
        // Detect multiple properties defined at the same time. Throw an error
58
        // for this, but also only process the first property in the list so we don't
59
        // repeat errors.
60
        $find   = Tokens::SCOPE_MODIFIERS;
3✔
61
        $find[] = T_VARIABLE;
3✔
62
        $find[] = T_VAR;
3✔
63
        $find[] = T_READONLY;
3✔
64
        $find[] = T_FINAL;
3✔
65
        $find[] = T_ABSTRACT;
3✔
66
        $find[] = T_SEMICOLON;
3✔
67
        $find[] = T_OPEN_CURLY_BRACKET;
3✔
68

69
        $prev = $phpcsFile->findPrevious($find, ($stackPtr - 1));
3✔
70
        if ($tokens[$prev]['code'] === T_VARIABLE) {
3✔
71
            return;
3✔
72
        }
73

74
        if ($tokens[$prev]['code'] === T_VAR) {
3✔
75
            $error = 'The var keyword must not be used to declare a property';
3✔
76
            $phpcsFile->addError($error, $stackPtr, 'VarUsed');
3✔
77
        }
78

79
        $next = $phpcsFile->findNext([T_VARIABLE, T_SEMICOLON], ($stackPtr + 1));
3✔
80
        if ($next !== false && $tokens[$next]['code'] === T_VARIABLE) {
3✔
81
            $error = 'There must not be more than one property declared per statement';
3✔
82
            $phpcsFile->addError($error, $stackPtr, 'Multiple');
3✔
83
        }
84

85
        if ($propertyInfo['type'] !== '') {
3✔
86
            $typeToken = $propertyInfo['type_end_token'];
3✔
87
            $error     = 'There must be 1 space after the property type declaration; %s found';
3✔
88
            if ($tokens[($typeToken + 1)]['code'] !== T_WHITESPACE) {
3✔
89
                $data = ['0'];
3✔
90
                $fix  = $phpcsFile->addFixableError($error, $typeToken, 'SpacingAfterType', $data);
3✔
91
                if ($fix === true) {
3✔
92
                    $phpcsFile->fixer->addContent($typeToken, ' ');
3✔
93
                }
94
            } else if ($tokens[($typeToken + 1)]['content'] !== ' ') {
3✔
95
                $next = $phpcsFile->findNext(T_WHITESPACE, ($typeToken + 1), null, true);
3✔
96
                if ($tokens[$next]['line'] !== $tokens[$typeToken]['line']) {
3✔
97
                    $found = 'newline';
3✔
98
                } else {
99
                    $found = $tokens[($typeToken + 1)]['length'];
3✔
100
                }
101

102
                $data = [$found];
3✔
103

104
                $nextNonWs = $phpcsFile->findNext(Tokens::EMPTY_TOKENS, ($typeToken + 1), null, true);
3✔
105
                if ($nextNonWs !== $next) {
3✔
UNCOV
106
                    $phpcsFile->addError($error, $typeToken, 'SpacingAfterType', $data);
×
107
                } else {
108
                    $fix = $phpcsFile->addFixableError($error, $typeToken, 'SpacingAfterType', $data);
3✔
109
                    if ($fix === true) {
3✔
110
                        if ($found === 'newline') {
3✔
111
                            $phpcsFile->fixer->beginChangeset();
3✔
112
                            for ($x = ($typeToken + 1); $x < $next; $x++) {
3✔
113
                                $phpcsFile->fixer->replaceToken($x, '');
3✔
114
                            }
115

116
                            $phpcsFile->fixer->addContent($typeToken, ' ');
3✔
117
                            $phpcsFile->fixer->endChangeset();
3✔
118
                        } else {
119
                            $phpcsFile->fixer->replaceToken(($typeToken + 1), ' ');
3✔
120
                        }
121
                    }
122
                }
123
            }//end if
124
        }//end if
125

126
        if ($propertyInfo['scope_specified'] === false && $propertyInfo['set_scope'] === false) {
3✔
127
            $error = 'Visibility must be declared on property "%s"';
3✔
128
            $data  = [$tokens[$stackPtr]['content']];
3✔
129
            $phpcsFile->addError($error, $stackPtr, 'ScopeMissing', $data);
3✔
130
        }
131

132
        /*
133
         * Note: per PSR-PER section 4.6 v 2.1/3.0, the order should be:
134
         * - Inheritance modifier: `abstract` or `final`.
135
         * - Visibility modifier: `public`, `protected`, or `private`.
136
         * - Set-visibility modifier: `public(set)`, `protected(set)`, or `private(set)`
137
         * - Scope modifier: `static`.
138
         * - Mutation modifier: `readonly`.
139
         * - Type declaration.
140
         * - Name.
141
         *
142
         * Ref:
143
         * - https://www.php-fig.org/per/coding-style/#46-modifier-keywords
144
         * - https://github.com/php-fig/per-coding-style/pull/99
145
         *
146
         * The `static` and `readonly` modifiers are mutually exclusive and cannot be used together.
147
         *
148
         * Based on that, the below modifier keyword order checks are sufficient (for now).
149
         */
150

151
        $hasVisibilityModifier   = ($propertyInfo['scope_specified'] === true || $propertyInfo['set_scope'] !== false);
3✔
152
        $lastVisibilityModifier  = $phpcsFile->findPrevious(Tokens::SCOPE_MODIFIERS, ($stackPtr - 1));
3✔
153
        $firstVisibilityModifier = $lastVisibilityModifier;
3✔
154

155
        if ($propertyInfo['scope_specified'] === true && $propertyInfo['set_scope'] !== false) {
3✔
156
            $scopePtr    = $phpcsFile->findPrevious([T_PUBLIC, T_PROTECTED, T_PRIVATE], ($stackPtr - 1));
3✔
157
            $setScopePtr = $phpcsFile->findPrevious([T_PUBLIC_SET, T_PROTECTED_SET, T_PRIVATE_SET], ($stackPtr - 1));
3✔
158
            if ($scopePtr > $setScopePtr) {
3✔
159
                $error = 'The "read"-visibility must come before the "write"-visibility';
3✔
160
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'AvizKeywordOrder');
3✔
161
                if ($fix === true) {
3✔
162
                    $phpcsFile->fixer->beginChangeset();
3✔
163

164
                    for ($i = ($scopePtr + 1); $scopePtr < $stackPtr; $i++) {
3✔
165
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
166
                            break;
3✔
167
                        }
168

169
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
170
                    }
171

172
                    $phpcsFile->fixer->replaceToken($scopePtr, '');
3✔
173
                    $phpcsFile->fixer->addContentBefore($setScopePtr, $tokens[$scopePtr]['content'].' ');
3✔
174

175
                    $phpcsFile->fixer->endChangeset();
3✔
176
                }
177
            }
178

179
            $firstVisibilityModifier = min($scopePtr, $setScopePtr);
3✔
180
        }//end if
181

182
        if ($hasVisibilityModifier === true && $propertyInfo['is_final'] === true) {
3✔
183
            $scopePtr = $firstVisibilityModifier;
3✔
184
            $finalPtr = $phpcsFile->findPrevious(T_FINAL, ($stackPtr - 1));
3✔
185
            if ($finalPtr > $scopePtr) {
3✔
186
                $error = 'The final declaration must come before the visibility declaration';
3✔
187
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'FinalAfterVisibility');
3✔
188
                if ($fix === true) {
3✔
189
                    $phpcsFile->fixer->beginChangeset();
3✔
190

191
                    for ($i = ($finalPtr + 1); $finalPtr < $stackPtr; $i++) {
3✔
192
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
193
                            break;
3✔
194
                        }
195

196
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
197
                    }
198

199
                    $phpcsFile->fixer->replaceToken($finalPtr, '');
3✔
200
                    $phpcsFile->fixer->addContentBefore($scopePtr, $tokens[$finalPtr]['content'].' ');
3✔
201

202
                    $phpcsFile->fixer->endChangeset();
3✔
203
                }
204
            }
205
        }//end if
206

207
        if ($hasVisibilityModifier === true && $propertyInfo['is_abstract'] === true) {
3✔
208
            $scopePtr    = $firstVisibilityModifier;
3✔
209
            $abstractPtr = $phpcsFile->findPrevious(T_ABSTRACT, ($stackPtr - 1));
3✔
210
            if ($abstractPtr > $scopePtr) {
3✔
211
                $error = 'The abstract declaration must come before the visibility declaration';
3✔
212
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'AbstractAfterVisibility');
3✔
213
                if ($fix === true) {
3✔
214
                    $phpcsFile->fixer->beginChangeset();
3✔
215

216
                    for ($i = ($abstractPtr + 1); $abstractPtr < $stackPtr; $i++) {
3✔
217
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
218
                            break;
3✔
219
                        }
220

221
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
222
                    }
223

224
                    $phpcsFile->fixer->replaceToken($abstractPtr, '');
3✔
225
                    $phpcsFile->fixer->addContentBefore($scopePtr, $tokens[$abstractPtr]['content'].' ');
3✔
226

227
                    $phpcsFile->fixer->endChangeset();
3✔
228
                }
229
            }
230
        }//end if
231

232
        if ($hasVisibilityModifier === true && $propertyInfo['is_static'] === true) {
3✔
233
            $scopePtr  = $lastVisibilityModifier;
3✔
234
            $staticPtr = $phpcsFile->findPrevious(T_STATIC, ($stackPtr - 1));
3✔
235
            if ($scopePtr > $staticPtr) {
3✔
236
                $error = 'The static declaration must come after the visibility declaration';
3✔
237
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'StaticBeforeVisibility');
3✔
238
                if ($fix === true) {
3✔
239
                    $phpcsFile->fixer->beginChangeset();
3✔
240

241
                    for ($i = ($staticPtr + 1); $staticPtr < $stackPtr; $i++) {
3✔
242
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
243
                            break;
3✔
244
                        }
245

246
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
247
                    }
248

249
                    $phpcsFile->fixer->replaceToken($staticPtr, '');
3✔
250
                    $phpcsFile->fixer->addContent($scopePtr, ' '.$tokens[$staticPtr]['content']);
3✔
251

252
                    $phpcsFile->fixer->endChangeset();
3✔
253
                }
254
            }
255
        }//end if
256

257
        if ($hasVisibilityModifier === true && $propertyInfo['is_readonly'] === true) {
3✔
258
            $scopePtr    = $lastVisibilityModifier;
3✔
259
            $readonlyPtr = $phpcsFile->findPrevious(T_READONLY, ($stackPtr - 1));
3✔
260
            if ($scopePtr > $readonlyPtr) {
3✔
261
                $error = 'The readonly declaration must come after the visibility declaration';
3✔
262
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'ReadonlyBeforeVisibility');
3✔
263
                if ($fix === true) {
3✔
264
                    $phpcsFile->fixer->beginChangeset();
3✔
265

266
                    for ($i = ($readonlyPtr + 1); $readonlyPtr < $stackPtr; $i++) {
3✔
267
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
268
                            break;
3✔
269
                        }
270

271
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
272
                    }
273

274
                    $phpcsFile->fixer->replaceToken($readonlyPtr, '');
3✔
275
                    $phpcsFile->fixer->addContent($scopePtr, ' '.$tokens[$readonlyPtr]['content']);
3✔
276

277
                    $phpcsFile->fixer->endChangeset();
3✔
278
                }
279
            }
280
        }//end if
281

282
    }//end processMemberVar()
1✔
283

284

285
    /**
286
     * Processes normal variables.
287
     *
288
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
289
     * @param int                         $stackPtr  The position where the token was found.
290
     *
291
     * @return void
292
     */
UNCOV
293
    protected function processVariable(File $phpcsFile, $stackPtr)
×
294
    {
295
        /*
296
            We don't care about normal variables.
297
        */
298

UNCOV
299
    }//end processVariable()
×
300

301

302
    /**
303
     * Processes variables in double quoted strings.
304
     *
305
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
306
     * @param int                         $stackPtr  The position where the token was found.
307
     *
308
     * @return void
309
     */
UNCOV
310
    protected function processVariableInString(File $phpcsFile, $stackPtr)
×
311
    {
312
        /*
313
            We don't care about normal variables.
314
        */
315

UNCOV
316
    }//end processVariableInString()
×
317

318

319
}//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

© 2026 Coveralls, Inc