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

PHPCSStandards / PHP_CodeSniffer / 15036337869

15 May 2025 04:03AM UTC coverage: 78.375% (-0.2%) from 78.556%
15036337869

Pull #856

github

web-flow
Merge 93f570b46 into f5e7943d0
Pull Request #856: [Doc] Cover all errors of PEAR ClassDeclaration

25112 of 32041 relevant lines covered (78.37%)

69.4 hits per line

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

95.42
/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\AbstractVariableSniff;
15
use PHP_CodeSniffer\Util\Tokens;
16

17
class PropertyDeclarationSniff extends AbstractVariableSniff
18
{
19

20

21
    /**
22
     * Processes the function tokens within the class.
23
     *
24
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
25
     * @param int                         $stackPtr  The position where the token was found.
26
     *
27
     * @return void
28
     */
29
    protected function processMemberVar(File $phpcsFile, $stackPtr)
3✔
30
    {
31
        $tokens = $phpcsFile->getTokens();
3✔
32

33
        if ($tokens[$stackPtr]['content'][1] === '_') {
3✔
34
            $error = 'Property name "%s" should not be prefixed with an underscore to indicate visibility';
3✔
35
            $data  = [$tokens[$stackPtr]['content']];
3✔
36
            $phpcsFile->addWarning($error, $stackPtr, 'Underscore', $data);
3✔
37
        }
1✔
38

39
        // Detect multiple properties defined at the same time. Throw an error
40
        // for this, but also only process the first property in the list so we don't
41
        // repeat errors.
42
        $find   = Tokens::$scopeModifiers;
3✔
43
        $find[] = T_VARIABLE;
3✔
44
        $find[] = T_VAR;
3✔
45
        $find[] = T_READONLY;
3✔
46
        $find[] = T_FINAL;
3✔
47
        $find[] = T_SEMICOLON;
3✔
48
        $find[] = T_OPEN_CURLY_BRACKET;
3✔
49

50
        $prev = $phpcsFile->findPrevious($find, ($stackPtr - 1));
3✔
51
        if ($tokens[$prev]['code'] === T_VARIABLE) {
3✔
52
            return;
3✔
53
        }
54

55
        if ($tokens[$prev]['code'] === T_VAR) {
3✔
56
            $error = 'The var keyword must not be used to declare a property';
3✔
57
            $phpcsFile->addError($error, $stackPtr, 'VarUsed');
3✔
58
        }
1✔
59

60
        $next = $phpcsFile->findNext([T_VARIABLE, T_SEMICOLON], ($stackPtr + 1));
3✔
61
        if ($next !== false && $tokens[$next]['code'] === T_VARIABLE) {
3✔
62
            $error = 'There must not be more than one property declared per statement';
3✔
63
            $phpcsFile->addError($error, $stackPtr, 'Multiple');
3✔
64
        }
1✔
65

66
        try {
67
            $propertyInfo = $phpcsFile->getMemberProperties($stackPtr);
3✔
68
            if (empty($propertyInfo) === true) {
3✔
69
                return;
2✔
70
            }
71
        } catch (Exception $e) {
1✔
72
            // Turns out not to be a property after all.
73
            return;
×
74
        }
75

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

93
                $data = [$found];
3✔
94

95
                $nextNonWs = $phpcsFile->findNext(Tokens::$emptyTokens, ($typeToken + 1), null, true);
3✔
96
                if ($nextNonWs !== $next) {
3✔
97
                    $phpcsFile->addError($error, $typeToken, 'SpacingAfterType', $data);
×
98
                } else {
99
                    $fix = $phpcsFile->addFixableError($error, $typeToken, 'SpacingAfterType', $data);
3✔
100
                    if ($fix === true) {
3✔
101
                        if ($found === 'newline') {
3✔
102
                            $phpcsFile->fixer->beginChangeset();
3✔
103
                            for ($x = ($typeToken + 1); $x < $next; $x++) {
3✔
104
                                $phpcsFile->fixer->replaceToken($x, '');
3✔
105
                            }
1✔
106

107
                            $phpcsFile->fixer->addContent($typeToken, ' ');
3✔
108
                            $phpcsFile->fixer->endChangeset();
3✔
109
                        } else {
1✔
110
                            $phpcsFile->fixer->replaceToken(($typeToken + 1), ' ');
3✔
111
                        }
112
                    }
1✔
113
                }
114
            }//end if
1✔
115
        }//end if
1✔
116

117
        if ($propertyInfo['scope_specified'] === false) {
3✔
118
            $error = 'Visibility must be declared on property "%s"';
3✔
119
            $data  = [$tokens[$stackPtr]['content']];
3✔
120
            $phpcsFile->addError($error, $stackPtr, 'ScopeMissing', $data);
3✔
121
        }
1✔
122

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

139
        if ($propertyInfo['scope_specified'] === true && $propertyInfo['is_final'] === true) {
3✔
140
            $scopePtr = $phpcsFile->findPrevious(Tokens::$scopeModifiers, ($stackPtr - 1));
3✔
141
            $finalPtr = $phpcsFile->findPrevious(T_FINAL, ($stackPtr - 1));
3✔
142
            if ($finalPtr > $scopePtr) {
3✔
143
                $error = 'The final declaration must come before the visibility declaration';
3✔
144
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'FinalAfterVisibility');
3✔
145
                if ($fix === true) {
3✔
146
                    $phpcsFile->fixer->beginChangeset();
3✔
147

148
                    for ($i = ($finalPtr + 1); $finalPtr < $stackPtr; $i++) {
3✔
149
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
150
                            break;
3✔
151
                        }
152

153
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
154
                    }
1✔
155

156
                    $phpcsFile->fixer->replaceToken($finalPtr, '');
3✔
157
                    $phpcsFile->fixer->addContentBefore($scopePtr, $tokens[$finalPtr]['content'].' ');
3✔
158

159
                    $phpcsFile->fixer->endChangeset();
3✔
160
                }
1✔
161
            }
1✔
162
        }//end if
1✔
163

164
        if ($propertyInfo['scope_specified'] === true && $propertyInfo['is_static'] === true) {
3✔
165
            $scopePtr  = $phpcsFile->findPrevious(Tokens::$scopeModifiers, ($stackPtr - 1));
3✔
166
            $staticPtr = $phpcsFile->findPrevious(T_STATIC, ($stackPtr - 1));
3✔
167
            if ($scopePtr > $staticPtr) {
3✔
168
                $error = 'The static declaration must come after the visibility declaration';
3✔
169
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'StaticBeforeVisibility');
3✔
170
                if ($fix === true) {
3✔
171
                    $phpcsFile->fixer->beginChangeset();
3✔
172

173
                    for ($i = ($scopePtr + 1); $scopePtr < $stackPtr; $i++) {
3✔
174
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
175
                            break;
3✔
176
                        }
177

178
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
179
                    }
1✔
180

181
                    $phpcsFile->fixer->replaceToken($scopePtr, '');
3✔
182
                    $phpcsFile->fixer->addContentBefore($staticPtr, $propertyInfo['scope'].' ');
3✔
183

184
                    $phpcsFile->fixer->endChangeset();
3✔
185
                }
1✔
186
            }
1✔
187
        }//end if
1✔
188

189
        if ($propertyInfo['scope_specified'] === true && $propertyInfo['is_readonly'] === true) {
3✔
190
            $scopePtr    = $phpcsFile->findPrevious(Tokens::$scopeModifiers, ($stackPtr - 1));
3✔
191
            $readonlyPtr = $phpcsFile->findPrevious(T_READONLY, ($stackPtr - 1));
3✔
192
            if ($scopePtr > $readonlyPtr) {
3✔
193
                $error = 'The readonly declaration must come after the visibility declaration';
3✔
194
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'ReadonlyBeforeVisibility');
3✔
195
                if ($fix === true) {
3✔
196
                    $phpcsFile->fixer->beginChangeset();
3✔
197

198
                    for ($i = ($scopePtr + 1); $scopePtr < $stackPtr; $i++) {
3✔
199
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
200
                            break;
3✔
201
                        }
202

203
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
204
                    }
1✔
205

206
                    $phpcsFile->fixer->replaceToken($scopePtr, '');
3✔
207
                    $phpcsFile->fixer->addContentBefore($readonlyPtr, $propertyInfo['scope'].' ');
3✔
208

209
                    $phpcsFile->fixer->endChangeset();
3✔
210
                }
1✔
211
            }
1✔
212
        }//end if
1✔
213

214
    }//end processMemberVar()
2✔
215

216

217
    /**
218
     * Processes normal variables.
219
     *
220
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
221
     * @param int                         $stackPtr  The position where the token was found.
222
     *
223
     * @return void
224
     */
225
    protected function processVariable(File $phpcsFile, $stackPtr)
×
226
    {
227
        /*
228
            We don't care about normal variables.
229
        */
230

231
    }//end processVariable()
×
232

233

234
    /**
235
     * Processes variables in double quoted strings.
236
     *
237
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
238
     * @param int                         $stackPtr  The position where the token was found.
239
     *
240
     * @return void
241
     */
242
    protected function processVariableInString(File $phpcsFile, $stackPtr)
×
243
    {
244
        /*
245
            We don't care about normal variables.
246
        */
247

248
    }//end processVariableInString()
×
249

250

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