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

PHPCSStandards / PHP_CodeSniffer / 14516416464

17 Apr 2025 01:09PM UTC coverage: 77.945% (+0.3%) from 77.666%
14516416464

push

github

web-flow
Merge pull request #1010 from PHPCSStandards/phpcs-4.0/feature/sq-1612-stdout-vs-stderr

(Nearly) All status, debug, and progress output is now sent to STDERR instead of STDOUT

63 of 457 new or added lines in 18 files covered. (13.79%)

1 existing line in 1 file now uncovered.

19455 of 24960 relevant lines covered (77.94%)

78.64 hits per line

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

93.64
/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::$emptyTokens[$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::$phpcsCommentTokens[$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.
NEW
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) {
3✔
198
            return ($lastCommentBlockToken + 1);
×
199
        } else {
200
            array_shift($stringTokens);
3✔
201
            --$numTokens;
3✔
202
        }
203

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

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

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

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

235
        $numCode          = 0;
3✔
236
        $numNonWhitespace = 0;
3✔
237

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

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

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

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

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

272
        return ($lastCommentBlockToken + 1);
3✔
273

274
    }//end process()
275

276

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