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

PHPCSStandards / PHP_CodeSniffer / 15253296250

26 May 2025 11:55AM UTC coverage: 78.632% (+0.3%) from 78.375%
15253296250

Pull #1105

github

web-flow
Merge d9441d98f into caf806050
Pull Request #1105: Skip tests when 'git' command is not available

19665 of 25009 relevant lines covered (78.63%)

88.67 hits per line

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

95.41
/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_SEMICOLON;
3✔
66
        $find[] = T_OPEN_CURLY_BRACKET;
3✔
67

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

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

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

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

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

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

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

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

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

147
        if ($propertyInfo['scope_specified'] === true && $propertyInfo['is_final'] === true) {
3✔
148
            $scopePtr = $phpcsFile->findPrevious(Tokens::SCOPE_MODIFIERS, ($stackPtr - 1));
3✔
149
            $finalPtr = $phpcsFile->findPrevious(T_FINAL, ($stackPtr - 1));
3✔
150
            if ($finalPtr > $scopePtr) {
3✔
151
                $error = 'The final declaration must come before the visibility declaration';
3✔
152
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'FinalAfterVisibility');
3✔
153
                if ($fix === true) {
3✔
154
                    $phpcsFile->fixer->beginChangeset();
3✔
155

156
                    for ($i = ($finalPtr + 1); $finalPtr < $stackPtr; $i++) {
3✔
157
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
158
                            break;
3✔
159
                        }
160

161
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
162
                    }
163

164
                    $phpcsFile->fixer->replaceToken($finalPtr, '');
3✔
165
                    $phpcsFile->fixer->addContentBefore($scopePtr, $tokens[$finalPtr]['content'].' ');
3✔
166

167
                    $phpcsFile->fixer->endChangeset();
3✔
168
                }
169
            }
170
        }//end if
171

172
        if ($propertyInfo['scope_specified'] === true && $propertyInfo['is_static'] === true) {
3✔
173
            $scopePtr  = $phpcsFile->findPrevious(Tokens::SCOPE_MODIFIERS, ($stackPtr - 1));
3✔
174
            $staticPtr = $phpcsFile->findPrevious(T_STATIC, ($stackPtr - 1));
3✔
175
            if ($scopePtr > $staticPtr) {
3✔
176
                $error = 'The static declaration must come after the visibility declaration';
3✔
177
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'StaticBeforeVisibility');
3✔
178
                if ($fix === true) {
3✔
179
                    $phpcsFile->fixer->beginChangeset();
3✔
180

181
                    for ($i = ($scopePtr + 1); $scopePtr < $stackPtr; $i++) {
3✔
182
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
183
                            break;
3✔
184
                        }
185

186
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
187
                    }
188

189
                    $phpcsFile->fixer->replaceToken($scopePtr, '');
3✔
190
                    $phpcsFile->fixer->addContentBefore($staticPtr, $propertyInfo['scope'].' ');
3✔
191

192
                    $phpcsFile->fixer->endChangeset();
3✔
193
                }
194
            }
195
        }//end if
196

197
        if ($propertyInfo['scope_specified'] === true && $propertyInfo['is_readonly'] === true) {
3✔
198
            $scopePtr    = $phpcsFile->findPrevious(Tokens::SCOPE_MODIFIERS, ($stackPtr - 1));
3✔
199
            $readonlyPtr = $phpcsFile->findPrevious(T_READONLY, ($stackPtr - 1));
3✔
200
            if ($scopePtr > $readonlyPtr) {
3✔
201
                $error = 'The readonly declaration must come after the visibility declaration';
3✔
202
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'ReadonlyBeforeVisibility');
3✔
203
                if ($fix === true) {
3✔
204
                    $phpcsFile->fixer->beginChangeset();
3✔
205

206
                    for ($i = ($scopePtr + 1); $scopePtr < $stackPtr; $i++) {
3✔
207
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
208
                            break;
3✔
209
                        }
210

211
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
212
                    }
213

214
                    $phpcsFile->fixer->replaceToken($scopePtr, '');
3✔
215
                    $phpcsFile->fixer->addContentBefore($readonlyPtr, $propertyInfo['scope'].' ');
3✔
216

217
                    $phpcsFile->fixer->endChangeset();
3✔
218
                }
219
            }
220
        }//end if
221

222
    }//end processMemberVar()
1✔
223

224

225
    /**
226
     * Processes normal variables.
227
     *
228
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
229
     * @param int                         $stackPtr  The position where the token was found.
230
     *
231
     * @return void
232
     */
233
    protected function processVariable(File $phpcsFile, $stackPtr)
×
234
    {
235
        /*
236
            We don't care about normal variables.
237
        */
238

239
    }//end processVariable()
×
240

241

242
    /**
243
     * Processes variables in double quoted strings.
244
     *
245
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
246
     * @param int                         $stackPtr  The position where the token was found.
247
     *
248
     * @return void
249
     */
250
    protected function processVariableInString(File $phpcsFile, $stackPtr)
×
251
    {
252
        /*
253
            We don't care about normal variables.
254
        */
255

256
    }//end processVariableInString()
×
257

258

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

© 2025 Coveralls, Inc