• 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

93.69
/src/Standards/Squiz/Sniffs/PHP/CommentedOutCodeSniff.php
1
<?php
2
/**
3
 * Warn about commented out code.
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\Squiz\Sniffs\PHP;
11

12
use PHP_CodeSniffer\Exceptions\TokenizerException;
13
use PHP_CodeSniffer\Files\File;
14
use PHP_CodeSniffer\Sniffs\Sniff;
15
use PHP_CodeSniffer\Tokenizers\PHP;
16
use PHP_CodeSniffer\Util\Tokens;
17
use PHP_CodeSniffer\Util\Writers\StatusWriter;
18

19
class CommentedOutCodeSniff implements Sniff
20
{
21

22
    /**
23
     * If a comment is more than $maxPercentage% code, a warning will be shown.
24
     *
25
     * @var integer
26
     */
27
    public $maxPercentage = 35;
28

29

30
    /**
31
     * Returns an array of tokens this test wants to listen for.
32
     *
33
     * @return array<int|string>
34
     */
35
    public function register()
3✔
36
    {
37
        return [T_COMMENT];
3✔
38

39
    }//end register()
40

41

42
    /**
43
     * Processes this test, when one of its tokens is encountered.
44
     *
45
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned.
46
     * @param int                         $stackPtr  The position of the current token
47
     *                                               in the stack passed in $tokens.
48
     *
49
     * @return int|void Integer stack pointer to skip forward or void to continue
50
     *                  normal file processing.
51
     */
52
    public function process(File $phpcsFile, $stackPtr)
3✔
53
    {
54
        $tokens = $phpcsFile->getTokens();
3✔
55

56
        // Ignore comments at the end of code blocks.
57
        if (substr($tokens[$stackPtr]['content'], 0, 6) === '//end ') {
3✔
58
            return;
3✔
59
        }
60

61
        $content      = '';
3✔
62
        $lastLineSeen = $tokens[$stackPtr]['line'];
3✔
63
        $commentStyle = 'line';
3✔
64
        if (strpos($tokens[$stackPtr]['content'], '/*') === 0) {
3✔
65
            $commentStyle = 'block';
3✔
66
        }
67

68
        $lastCommentBlockToken = $stackPtr;
3✔
69
        for ($i = $stackPtr; $i < $phpcsFile->numTokens; $i++) {
3✔
70
            if (isset(Tokens::EMPTY_TOKENS[$tokens[$i]['code']]) === false) {
3✔
71
                break;
3✔
72
            }
73

74
            if ($tokens[$i]['code'] === T_WHITESPACE) {
3✔
75
                continue;
3✔
76
            }
77

78
            if (isset(Tokens::PHPCS_ANNOTATION_TOKENS[$tokens[$i]['code']]) === true) {
3✔
79
                $lastLineSeen = $tokens[$i]['line'];
3✔
80
                continue;
3✔
81
            }
82

83
            if ($commentStyle === 'line'
3✔
84
                && ($lastLineSeen + 1) <= $tokens[$i]['line']
3✔
85
                && strpos($tokens[$i]['content'], '/*') === 0
3✔
86
            ) {
87
                // First non-whitespace token on a new line is start of a different style comment.
88
                break;
3✔
89
            }
90

91
            if ($commentStyle === 'line'
3✔
92
                && ($lastLineSeen + 1) < $tokens[$i]['line']
3✔
93
            ) {
94
                // Blank line breaks a '//' style comment block.
95
                break;
3✔
96
            }
97

98
            /*
99
                Trim as much off the comment as possible so we don't
100
                have additional whitespace tokens or comment tokens
101
            */
102

103
            $tokenContent = trim($tokens[$i]['content']);
3✔
104
            $break        = false;
3✔
105

106
            if ($commentStyle === 'line') {
3✔
107
                if (substr($tokenContent, 0, 2) === '//') {
3✔
108
                    $tokenContent = substr($tokenContent, 2);
3✔
109
                }
110

111
                if (substr($tokenContent, 0, 1) === '#') {
3✔
112
                    $tokenContent = substr($tokenContent, 1);
3✔
113
                }
114
            } else {
115
                if (substr($tokenContent, 0, 3) === '/**') {
3✔
116
                    $tokenContent = substr($tokenContent, 3);
×
117
                }
118

119
                if (substr($tokenContent, 0, 2) === '/*') {
3✔
120
                    $tokenContent = substr($tokenContent, 2);
3✔
121
                }
122

123
                if (substr($tokenContent, -2) === '*/') {
3✔
124
                    $tokenContent = substr($tokenContent, 0, -2);
3✔
125
                    $break        = true;
3✔
126
                }
127

128
                if (substr($tokenContent, 0, 1) === '*') {
3✔
129
                    $tokenContent = substr($tokenContent, 1);
3✔
130
                }
131
            }//end if
132

133
            $content     .= $tokenContent.$phpcsFile->eolChar;
3✔
134
            $lastLineSeen = $tokens[$i]['line'];
3✔
135

136
            $lastCommentBlockToken = $i;
3✔
137

138
            if ($break === true) {
3✔
139
                // Closer of a block comment found.
140
                break;
3✔
141
            }
142
        }//end for
143

144
        // Ignore typical warning suppression annotations from other tools.
145
        if (preg_match('`^\s*@[A-Za-z()\._-]+\s*$`', $content) === 1) {
3✔
146
            return ($lastCommentBlockToken + 1);
3✔
147
        }
148

149
        // Quite a few comments use multiple dashes, equals signs etc
150
        // to frame comments and licence headers.
151
        $content = preg_replace('/[-=#*]{2,}/', '-', $content);
3✔
152

153
        // Random numbers sitting inside the content can throw parse errors
154
        // for invalid literals in PHP7+, so strip those.
155
        $content = preg_replace('/\d+/', '', $content);
3✔
156

157
        $content = trim($content);
3✔
158

159
        if ($content === '') {
3✔
160
            return ($lastCommentBlockToken + 1);
3✔
161
        }
162

163
        $content = '<?php '.$content.' ?>';
3✔
164

165
        // Because we are not really parsing code, the tokenizer can throw all sorts
166
        // of errors that don't mean anything, so ignore them.
167
        $oldErrors = ini_get('error_reporting');
3✔
168
        ini_set('error_reporting', 0);
3✔
169

170
        // Pause the StatusWriter to silence Tokenizer debug info about the comments being parsed (which only confuses things).
171
        StatusWriter::pause();
3✔
172

173
        try {
174
            $tokenizer    = new PHP($content, $phpcsFile->config, $phpcsFile->eolChar);
3✔
175
            $stringTokens = $tokenizer->getTokens();
3✔
176
        } catch (TokenizerException $e) {
×
177
            // We couldn't check the comment, so ignore it.
178
            StatusWriter::resume();
×
179
            ini_set('error_reporting', $oldErrors);
×
180
            return ($lastCommentBlockToken + 1);
×
181
        }
182

183
        StatusWriter::resume();
3✔
184

185
        ini_set('error_reporting', $oldErrors);
3✔
186

187
        $numTokens = count($stringTokens);
3✔
188

189
        /*
190
            We know what the first two and last two tokens should be
191
            (because we put them there) so ignore this comment if those
192
            tokens were not parsed correctly. It obviously means this is not
193
            valid code.
194
        */
195

196
        // First token is always the opening tag.
197
        if ($stringTokens[0]['code'] !== T_OPEN_TAG || $stringTokens[1]['code'] !== T_WHITESPACE) {
3✔
198
            return ($lastCommentBlockToken + 1);
×
199
        } else {
200
            // Remove the PHP open tag + the whitespace token following it.
201
            array_shift($stringTokens);
3✔
202
            array_shift($stringTokens);
3✔
203
            $numTokens -= 2;
3✔
204
        }
205

206
        // Last token is always the closing tag, unless something went wrong.
207
        if (isset($stringTokens[($numTokens - 1)]) === false
3✔
208
            || $stringTokens[($numTokens - 1)]['code'] !== T_CLOSE_TAG
3✔
209
        ) {
210
            return ($lastCommentBlockToken + 1);
3✔
211
        } else {
212
            array_pop($stringTokens);
3✔
213
            --$numTokens;
3✔
214
        }
215

216
        // The second last token is always whitespace or a comment, depending
217
        // on the code inside the comment.
218
        if (isset(Tokens::EMPTY_TOKENS[$stringTokens[($numTokens - 1)]['code']]) === false) {
3✔
219
            return ($lastCommentBlockToken + 1);
×
220
        }
221

222
        if ($stringTokens[($numTokens - 1)]['code'] === T_WHITESPACE) {
3✔
223
            array_pop($stringTokens);
3✔
224
            --$numTokens;
3✔
225
        }
226

227
        $emptyTokens  = [
2✔
228
            T_WHITESPACE              => true,
3✔
229
            T_STRING                  => true,
3✔
230
            T_STRING_CONCAT           => true,
3✔
231
            T_ENCAPSED_AND_WHITESPACE => true,
3✔
232
            T_NONE                    => true,
3✔
233
            T_COMMENT                 => true,
3✔
234
        ];
2✔
235
        $emptyTokens += Tokens::PHPCS_ANNOTATION_TOKENS;
3✔
236

237
        $numCode          = 0;
3✔
238
        $numNonWhitespace = 0;
3✔
239

240
        for ($i = 0; $i < $numTokens; $i++) {
3✔
241
            // Do not count comments.
242
            if (isset($emptyTokens[$stringTokens[$i]['code']]) === false
3✔
243
                // Commented out HTML/XML and other docs contain a lot of these
244
                // characters, so it is best to not use them directly.
245
                && isset(Tokens::COMPARISON_TOKENS[$stringTokens[$i]['code']]) === false
3✔
246
                && isset(Tokens::ARITHMETIC_TOKENS[$stringTokens[$i]['code']]) === false
3✔
247
                && $stringTokens[$i]['code'] !== T_GOTO_LABEL
3✔
248
            ) {
249
                // Looks like code.
250
                $numCode++;
3✔
251
            }
252

253
            if ($stringTokens[$i]['code'] !== T_WHITESPACE) {
3✔
254
                ++$numNonWhitespace;
3✔
255
            }
256
        }
257

258
        // Ignore comments with only two or less non-whitespace tokens.
259
        // Sample size too small for a reliably determination.
260
        if ($numNonWhitespace <= 2) {
3✔
261
            return ($lastCommentBlockToken + 1);
3✔
262
        }
263

264
        $percentCode = ceil((($numCode / $numTokens) * 100));
3✔
265
        if ($percentCode > $this->maxPercentage) {
3✔
266
            // Just in case.
267
            $percentCode = min(100, $percentCode);
3✔
268

269
            $error = 'This comment is %s%% valid code; is this commented out code?';
3✔
270
            $data  = [$percentCode];
3✔
271
            $phpcsFile->addWarning($error, $stackPtr, 'Found', $data);
3✔
272
        }
273

274
        return ($lastCommentBlockToken + 1);
3✔
275

276
    }//end process()
277

278

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