• 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

91.95
/src/Tokenizers/Comment.php
1
<?php
2
/**
3
 * Tokenizes doc block comments.
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\Tokenizers;
11

12
use PHP_CodeSniffer\Util\Common;
13

14
class Comment
15
{
16

17

18
    /**
19
     * Splits a single doc block comment token up into something that can be easily iterated over.
20
     *
21
     * @param string $string   The doc block comment string to parse.
22
     * @param string $eolChar  The EOL character to use for splitting strings.
23
     * @param int    $stackPtr The position of the token in the "new"/final token stream.
24
     *
25
     * @return array<int, array<string, string|int|array<int>>>
26
     */
27
    public function tokenizeString($string, $eolChar, $stackPtr)
201✔
28
    {
29
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
201✔
30
            echo "\t\t*** START COMMENT TOKENIZING ***".PHP_EOL;
×
31
        }
32

33
        $tokens   = [];
201✔
34
        $numChars = strlen($string);
201✔
35

36
        /*
37
            Doc block comments start with /*, but typically contain an
38
            extra star when they are used for function and class comments.
39
        */
40

41
        $char      = ($numChars - strlen(ltrim($string, '/*')));
201✔
42
        $lastChars = substr($string, -2);
201✔
43
        if ($char === $numChars && $lastChars === '*/') {
201✔
44
            // Edge case: docblock without whitespace or contents.
45
            $openTag = substr($string, 0, -2);
39✔
46
            $string  = $lastChars;
39✔
47
        } else {
13✔
48
            $openTag = substr($string, 0, $char);
201✔
49
            $string  = ltrim($string, '/*');
201✔
50
        }
51

52
        $tokens[$stackPtr] = [
201✔
53
            'content'      => $openTag,
201✔
54
            'code'         => T_DOC_COMMENT_OPEN_TAG,
201✔
55
            'type'         => 'T_DOC_COMMENT_OPEN_TAG',
201✔
56
            'comment_tags' => [],
134✔
57
        ];
67✔
58

59
        $openPtr = $stackPtr;
201✔
60
        $stackPtr++;
201✔
61

62
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
201✔
63
            $content = Common::prepareForOutput($openTag);
×
64
            echo "\t\tCreate comment token: T_DOC_COMMENT_OPEN_TAG => $content".PHP_EOL;
×
65
        }
66

67
        /*
68
            Strip off the close tag so it doesn't interfere with any
69
            of our comment line processing. The token will be added to the
70
            stack just before we return it.
71
        */
72

73
        $closeTag = [
67✔
74
            'content'        => substr($string, strlen(rtrim($string, '/*'))),
201✔
75
            'code'           => T_DOC_COMMENT_CLOSE_TAG,
201✔
76
            'type'           => 'T_DOC_COMMENT_CLOSE_TAG',
201✔
77
            'comment_opener' => $openPtr,
201✔
78
        ];
134✔
79

80
        if ($closeTag['content'] === false) {
201✔
81
            // In PHP < 8.0 substr() can return `false` instead of always returning a string.
82
            $closeTag['content'] = '';
6✔
83
        }
6✔
84

85
        $string = rtrim($string, '/*');
201✔
86

87
        /*
88
            Process each line of the comment.
89
        */
90

91
        $lines    = explode($eolChar, $string);
201✔
92
        $numLines = count($lines);
201✔
93
        foreach ($lines as $lineNum => $string) {
201✔
94
            if ($lineNum !== ($numLines - 1)) {
201✔
95
                $string .= $eolChar;
156✔
96
            }
52✔
97

98
            $char     = 0;
201✔
99
            $numChars = strlen($string);
201✔
100

101
            // We've started a new line, so process the indent.
102
            $space = $this->collectWhitespace($string, $char, $numChars);
201✔
103
            if ($space !== null) {
201✔
104
                $tokens[$stackPtr] = $space;
195✔
105
                $stackPtr++;
195✔
106
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
195✔
107
                    $content = Common::prepareForOutput($space['content']);
×
108
                    echo "\t\tCreate comment token: T_DOC_COMMENT_WHITESPACE => $content".PHP_EOL;
×
109
                }
110

111
                $char += strlen($space['content']);
195✔
112
                if ($char === $numChars) {
195✔
113
                    break;
183✔
114
                }
115
            }
65✔
116

117
            if ($string === '') {
201✔
118
                continue;
51✔
119
            }
120

121
            if ($lineNum > 0 && $string[$char] === '*') {
195✔
122
                // This is a function or class doc block line.
123
                $char++;
156✔
124
                $tokens[$stackPtr] = [
156✔
125
                    'content' => '*',
156✔
126
                    'code'    => T_DOC_COMMENT_STAR,
156✔
127
                    'type'    => 'T_DOC_COMMENT_STAR',
156✔
128
                ];
52✔
129

130
                $stackPtr++;
156✔
131

132
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
156✔
133
                    echo "\t\tCreate comment token: T_DOC_COMMENT_STAR => *".PHP_EOL;
×
134
                }
135
            }
52✔
136

137
            // Now we are ready to process the actual content of the line.
138
            $lineTokens = $this->processLine($string, $eolChar, $char, $numChars);
195✔
139
            foreach ($lineTokens as $lineToken) {
195✔
140
                $tokens[$stackPtr] = $lineToken;
195✔
141
                if (PHP_CODESNIFFER_VERBOSITY > 1) {
195✔
142
                    $content = Common::prepareForOutput($lineToken['content']);
×
143
                    $type    = $lineToken['type'];
×
144
                    echo "\t\tCreate comment token: $type => $content".PHP_EOL;
×
145
                }
146

147
                if ($lineToken['code'] === T_DOC_COMMENT_TAG) {
195✔
148
                    $tokens[$openPtr]['comment_tags'][] = $stackPtr;
177✔
149
                }
59✔
150

151
                $stackPtr++;
195✔
152
            }
65✔
153
        }//end foreach
67✔
154

155
        $tokens[$stackPtr] = $closeTag;
201✔
156
        $tokens[$openPtr]['comment_closer'] = $stackPtr;
201✔
157
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
201✔
158
            $content = Common::prepareForOutput($closeTag['content']);
×
159
            echo "\t\tCreate comment token: T_DOC_COMMENT_CLOSE_TAG => $content".PHP_EOL;
×
160
        }
161

162
        if (PHP_CODESNIFFER_VERBOSITY > 1) {
201✔
163
            echo "\t\t*** END COMMENT TOKENIZING ***".PHP_EOL;
×
164
        }
165

166
        return $tokens;
201✔
167

168
    }//end tokenizeString()
169

170

171
    /**
172
     * Process a single line of a comment.
173
     *
174
     * @param string $string  The comment string being tokenized.
175
     * @param string $eolChar The EOL character to use for splitting strings.
176
     * @param int    $start   The position in the string to start processing.
177
     * @param int    $end     The position in the string to end processing.
178
     *
179
     * @return array<int, array<string, string|int>>
180
     */
181
    private function processLine($string, $eolChar, $start, $end)
195✔
182
    {
183
        $tokens = [];
195✔
184

185
        // Collect content padding.
186
        $space = $this->collectWhitespace($string, $start, $end);
195✔
187
        if ($space !== null) {
195✔
188
            $tokens[] = $space;
156✔
189
            $start   += strlen($space['content']);
156✔
190
        }
52✔
191

192
        if (isset($string[$start]) === false) {
195✔
193
            return $tokens;
6✔
194
        }
195

196
        if ($string[$start] === '@') {
195✔
197
            // The content up until the first whitespace is the tag name.
198
            $matches = [];
177✔
199
            preg_match('/@[^\s]+/', $string, $matches, 0, $start);
177✔
200
            if (isset($matches[0]) === true
177✔
201
                && substr(strtolower($matches[0]), 0, 7) !== '@phpcs:'
177✔
202
            ) {
59✔
203
                $tagName  = $matches[0];
177✔
204
                $start   += strlen($tagName);
177✔
205
                $tokens[] = [
177✔
206
                    'content' => $tagName,
177✔
207
                    'code'    => T_DOC_COMMENT_TAG,
177✔
208
                    'type'    => 'T_DOC_COMMENT_TAG',
177✔
209
                ];
59✔
210

211
                // Then there will be some whitespace.
212
                $space = $this->collectWhitespace($string, $start, $end);
177✔
213
                if ($space !== null) {
177✔
214
                    $tokens[] = $space;
177✔
215
                    $start   += strlen($space['content']);
177✔
216
                }
59✔
217
            }
59✔
218
        }//end if
59✔
219

220
        // Process the rest of the line.
221
        $eol = strpos($string, $eolChar, $start);
195✔
222
        if ($eol === false) {
195✔
223
            $eol = $end;
177✔
224
        }
59✔
225

226
        if ($eol > $start) {
195✔
227
            $tokens[] = [
195✔
228
                'content' => substr($string, $start, ($eol - $start)),
195✔
229
                'code'    => T_DOC_COMMENT_STRING,
195✔
230
                'type'    => 'T_DOC_COMMENT_STRING',
195✔
231
            ];
65✔
232
        }
65✔
233

234
        if ($eol !== $end) {
195✔
235
            $tokens[] = [
156✔
236
                'content' => substr($string, $eol, strlen($eolChar)),
156✔
237
                'code'    => T_DOC_COMMENT_WHITESPACE,
156✔
238
                'type'    => 'T_DOC_COMMENT_WHITESPACE',
156✔
239
            ];
52✔
240
        }
52✔
241

242
        return $tokens;
195✔
243

244
    }//end processLine()
245

246

247
    /**
248
     * Collect consecutive whitespace into a single token.
249
     *
250
     * @param string $string The comment string being tokenized.
251
     * @param int    $start  The position in the string to start processing.
252
     * @param int    $end    The position in the string to end processing.
253
     *
254
     * @return array<string, string|int>|null
255
     */
256
    private function collectWhitespace($string, $start, $end)
201✔
257
    {
258
        $space = '';
201✔
259
        for ($start; $start < $end; $start++) {
201✔
260
            if ($string[$start] !== ' ' && $string[$start] !== "\t") {
195✔
261
                break;
195✔
262
            }
263

264
            $space .= $string[$start];
195✔
265
        }
65✔
266

267
        if ($space === '') {
201✔
268
            return null;
201✔
269
        }
270

271
        return [
65✔
272
            'content' => $space,
195✔
273
            'code'    => T_DOC_COMMENT_WHITESPACE,
195✔
274
            'type'    => 'T_DOC_COMMENT_WHITESPACE',
195✔
275
        ];
130✔
276

277
    }//end collectWhitespace()
278

279

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