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

keradus / PHP-CS-Fixer / 17252691116

26 Aug 2025 11:09PM UTC coverage: 94.743% (-0.01%) from 94.755%
17252691116

push

github

keradus
chore: apply phpdoc_tag_no_named_arguments

28313 of 29884 relevant lines covered (94.74%)

45.64 hits per line

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

92.72
/src/Tokenizer/Token.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of PHP CS Fixer.
7
 *
8
 * (c) Fabien Potencier <fabien@symfony.com>
9
 *     Dariusz Rumiński <dariusz.ruminski@gmail.com>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14

15
namespace PhpCsFixer\Tokenizer;
16

17
use PhpCsFixer\Utils;
18

19
/**
20
 * Representation of single token.
21
 * As a token prototype you should understand a single element generated by token_get_all.
22
 * Also, this class exposes PHPStan types. (hint: string in those types shall ideally not be empty - yet we are not there yet).
23
 *
24
 * @phpstan-type _PhpTokenKind int|string
25
 * @phpstan-type _PhpTokenArray array{0: int, 1: string}
26
 * @phpstan-type _PhpTokenArrayPartial array{0: int, 1?: string}
27
 * @phpstan-type _PhpTokenPrototype _PhpTokenArray|string
28
 * @phpstan-type _PhpTokenPrototypePartial _PhpTokenArrayPartial|string
29
 *
30
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
31
 *
32
 * @readonly
33
 *
34
 * @no-named-arguments Parameter names are not covered by the backward compatibility promise.
35
 */
36
final class Token
37
{
38
    /**
39
     * Content of token prototype.
40
     */
41
    private string $content;
42

43
    /**
44
     * ID of token prototype, if available.
45
     */
46
    private ?int $id;
47

48
    /**
49
     * If token prototype is an array.
50
     */
51
    private bool $isArray;
52

53
    /**
54
     * @param _PhpTokenPrototype $token token prototype
55
     */
56
    public function __construct($token)
57
    {
58
        if (\is_array($token)) {
41✔
59
            if (!\is_int($token[0])) {
33✔
60
                throw new \InvalidArgumentException(\sprintf(
3✔
61
                    'Id must be an int, got "%s".',
3✔
62
                    get_debug_type($token[0])
3✔
63
                ));
3✔
64
            }
65

66
            if (!\is_string($token[1])) {
30✔
67
                throw new \InvalidArgumentException(\sprintf(
3✔
68
                    'Content must be a string, got "%s".',
3✔
69
                    get_debug_type($token[1])
3✔
70
                ));
3✔
71
            }
72

73
            if ('' === $token[1]) {
27✔
74
                throw new \InvalidArgumentException('Cannot set empty content for id-based Token.');
1✔
75
            }
76

77
            $this->isArray = true;
26✔
78
            $this->id = $token[0];
26✔
79
            $this->content = $token[1];
26✔
80
        } elseif (\is_string($token)) {
12✔
81
            $this->isArray = false;
6✔
82
            $this->id = null;
6✔
83
            $this->content = $token;
6✔
84
        } else {
85
            throw new \InvalidArgumentException(\sprintf('Cannot recognize input value as valid Token prototype, got "%s".', get_debug_type($token)));
6✔
86
        }
87
    }
88

89
    /**
90
     * @return non-empty-list<int>
91
     */
92
    public static function getCastTokenKinds(): array
93
    {
94
        return [\T_ARRAY_CAST, \T_BOOL_CAST, \T_DOUBLE_CAST, \T_INT_CAST, \T_OBJECT_CAST, \T_STRING_CAST, \T_UNSET_CAST, FCT::T_VOID_CAST];
9✔
95
    }
96

97
    /**
98
     * Get classy tokens kinds: T_ENUM, T_CLASS, T_INTERFACE and T_TRAIT.
99
     *
100
     * @return non-empty-list<int>
101
     */
102
    public static function getClassyTokenKinds(): array
103
    {
104
        return [\T_CLASS, \T_TRAIT, \T_INTERFACE, FCT::T_ENUM];
6✔
105
    }
106

107
    /**
108
     * Get object operator tokens kinds: T_OBJECT_OPERATOR and (if available) T_NULLSAFE_OBJECT_OPERATOR.
109
     *
110
     * @return non-empty-list<int>
111
     */
112
    public static function getObjectOperatorKinds(): array
113
    {
114
        return [\T_OBJECT_OPERATOR, FCT::T_NULLSAFE_OBJECT_OPERATOR];
6✔
115
    }
116

117
    /**
118
     * Check if token is equals to given one.
119
     *
120
     * If tokens are arrays, then only keys defined in parameter token are checked.
121
     *
122
     * @param _PhpTokenPrototypePartial|Token $other         token or it's prototype
123
     * @param bool                            $caseSensitive perform a case sensitive comparison
124
     */
125
    public function equals($other, bool $caseSensitive = true): bool
126
    {
127
        if ('&' === $other) {
39✔
128
            return '&' === $this->content && (null === $this->id || $this->isGivenKind([FCT::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, FCT::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG]));
3✔
129
        }
130
        if (null === $this->id && '&' === $this->content) {
36✔
131
            return $other instanceof self && '&' === $other->content && (null === $other->id || $other->isGivenKind([FCT::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, FCT::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG]));
2✔
132
        }
133

134
        if ($other instanceof self) {
34✔
135
            // Inlined getPrototype() on this very hot path.
136
            // We access the private properties of $other directly to save function call overhead.
137
            // This is only possible because $other is of the same class as `self`.
138
            if (!$other->isArray) {
11✔
139
                $otherPrototype = $other->content;
6✔
140
            } else {
141
                $otherPrototype = [
10✔
142
                    $other->id,
10✔
143
                    $other->content,
10✔
144
                ];
10✔
145
            }
146
        } else {
147
            $otherPrototype = $other;
27✔
148
        }
149

150
        if ($this->isArray !== \is_array($otherPrototype)) {
34✔
151
            return false;
8✔
152
        }
153

154
        if (!$this->isArray) {
31✔
155
            return $this->content === $otherPrototype;
4✔
156
        }
157

158
        if ($this->id !== $otherPrototype[0]) {
27✔
159
            return false;
12✔
160
        }
161

162
        if (isset($otherPrototype[1])) {
20✔
163
            if ($caseSensitive) {
17✔
164
                if ($this->content !== $otherPrototype[1]) {
11✔
165
                    return false;
5✔
166
                }
167
            } elseif (0 !== strcasecmp($this->content, $otherPrototype[1])) {
6✔
168
                return false;
2✔
169
            }
170
        }
171

172
        // detect unknown keys
173
        unset($otherPrototype[0], $otherPrototype[1]);
15✔
174

175
        return [] === $otherPrototype;
15✔
176
    }
177

178
    /**
179
     * Check if token is equals to one of given.
180
     *
181
     * @param list<_PhpTokenPrototypePartial|Token> $others        array of tokens or token prototypes
182
     * @param bool                                  $caseSensitive perform a case sensitive comparison
183
     */
184
    public function equalsAny(array $others, bool $caseSensitive = true): bool
185
    {
186
        foreach ($others as $other) {
9✔
187
            if ($this->equals($other, $caseSensitive)) {
8✔
188
                return true;
4✔
189
            }
190
        }
191

192
        return false;
6✔
193
    }
194

195
    /**
196
     * A helper method used to find out whether a certain input token has to be case-sensitively matched.
197
     *
198
     * @param array<int, bool>|bool $caseSensitive global case sensitiveness or an array of booleans, whose keys should match
199
     *                                             the ones used in $sequence. If any is missing, the default case-sensitive
200
     *                                             comparison is used
201
     * @param int                   $key           the key of the token that has to be looked up
202
     *
203
     * @deprecated
204
     */
205
    public static function isKeyCaseSensitive($caseSensitive, int $key): bool
206
    {
207
        Utils::triggerDeprecation(new \InvalidArgumentException(\sprintf(
12✔
208
            'Method "%s" is deprecated and will be removed in the next major version.',
12✔
209
            __METHOD__
12✔
210
        )));
12✔
211

212
        if (\is_array($caseSensitive)) {
12✔
213
            return $caseSensitive[$key] ?? true;
9✔
214
        }
215

216
        return $caseSensitive;
3✔
217
    }
218

219
    /**
220
     * @return array{int, non-empty-string}|string
221
     */
222
    public function getPrototype()
223
    {
224
        if (!$this->isArray) {
1✔
225
            return $this->content;
1✔
226
        }
227

228
        \assert('' !== $this->content);
1✔
229

230
        return [
1✔
231
            $this->id,
1✔
232
            $this->content,
1✔
233
        ];
1✔
234
    }
235

236
    /**
237
     * Get token's content.
238
     *
239
     * It shall be used only for getting the content of token, not for checking it against excepted value.
240
     */
241
    public function getContent(): string
242
    {
243
        return $this->content;
2✔
244
    }
245

246
    /**
247
     * Get token's id.
248
     *
249
     * It shall be used only for getting the internal id of token, not for checking it against excepted value.
250
     */
251
    public function getId(): ?int
252
    {
253
        return $this->id;
2✔
254
    }
255

256
    /**
257
     * Get token's name.
258
     *
259
     * It shall be used only for getting the name of token, not for checking it against excepted value.
260
     *
261
     * @return null|non-empty-string token name
262
     */
263
    public function getName(): ?string
264
    {
265
        if (null === $this->id) {
6✔
266
            return null;
4✔
267
        }
268

269
        return self::getNameForId($this->id);
2✔
270
    }
271

272
    /**
273
     * Get token's name.
274
     *
275
     * It shall be used only for getting the name of token, not for checking it against excepted value.
276
     *
277
     * @return null|non-empty-string token name
278
     */
279
    public static function getNameForId(int $id): ?string
280
    {
281
        if (CT::has($id)) {
5✔
282
            return CT::getName($id);
1✔
283
        }
284

285
        $name = token_name($id);
4✔
286

287
        return 'UNKNOWN' === $name ? null : $name;
4✔
288
    }
289

290
    /**
291
     * Generate array containing all keywords that exists in PHP version in use.
292
     *
293
     * @return non-empty-list<int>
294
     */
295
    public static function getKeywords(): array
296
    {
297
        static $keywords = null;
1✔
298

299
        if (null === $keywords) {
1✔
300
            $keywords = self::getTokenKindsForNames(['T_ABSTRACT', 'T_ARRAY', 'T_AS', 'T_BREAK', 'T_CALLABLE', 'T_CASE',
1✔
301
                'T_CATCH', 'T_CLASS', 'T_CLONE', 'T_CONST', 'T_CONTINUE', 'T_DECLARE', 'T_DEFAULT', 'T_DO',
1✔
302
                'T_ECHO', 'T_ELSE', 'T_ELSEIF', 'T_EMPTY', 'T_ENDDECLARE', 'T_ENDFOR', 'T_ENDFOREACH',
1✔
303
                'T_ENDIF', 'T_ENDSWITCH', 'T_ENDWHILE', 'T_EVAL', 'T_EXIT', 'T_EXTENDS', 'T_FINAL',
1✔
304
                'T_FINALLY', 'T_FN', 'T_FOR', 'T_FOREACH', 'T_FUNCTION', 'T_GLOBAL', 'T_GOTO', 'T_HALT_COMPILER',
1✔
305
                'T_IF', 'T_IMPLEMENTS', 'T_INCLUDE', 'T_INCLUDE_ONCE', 'T_INSTANCEOF', 'T_INSTEADOF',
1✔
306
                'T_INTERFACE', 'T_ISSET', 'T_LIST', 'T_LOGICAL_AND', 'T_LOGICAL_OR', 'T_LOGICAL_XOR',
1✔
307
                'T_NAMESPACE', 'T_NEW', 'T_PRINT', 'T_PRIVATE', 'T_PROTECTED', 'T_PUBLIC', 'T_REQUIRE',
1✔
308
                'T_REQUIRE_ONCE', 'T_RETURN', 'T_STATIC', 'T_SWITCH', 'T_THROW', 'T_TRAIT', 'T_TRY',
1✔
309
                'T_UNSET', 'T_USE', 'T_VAR', 'T_WHILE', 'T_YIELD', 'T_YIELD_FROM',
1✔
310
            ]) + [
1✔
311
                CT::T_ARRAY_TYPEHINT => CT::T_ARRAY_TYPEHINT,
1✔
312
                CT::T_CLASS_CONSTANT => CT::T_CLASS_CONSTANT,
1✔
313
                CT::T_CONST_IMPORT => CT::T_CONST_IMPORT,
1✔
314
                CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PRIVATE,
1✔
315
                CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PROTECTED,
1✔
316
                CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC => CT::T_CONSTRUCTOR_PROPERTY_PROMOTION_PUBLIC,
1✔
317
                CT::T_FUNCTION_IMPORT => CT::T_FUNCTION_IMPORT,
1✔
318
                CT::T_NAMESPACE_OPERATOR => CT::T_NAMESPACE_OPERATOR,
1✔
319
                CT::T_USE_LAMBDA => CT::T_USE_LAMBDA,
1✔
320
                CT::T_USE_TRAIT => CT::T_USE_TRAIT,
1✔
321
                FCT::T_ENUM => FCT::T_ENUM,
1✔
322
                FCT::T_MATCH => FCT::T_MATCH,
1✔
323
                FCT::T_PRIVATE_SET => FCT::T_PRIVATE_SET,
1✔
324
                FCT::T_PROTECTED_SET => FCT::T_PROTECTED_SET,
1✔
325
                FCT::T_PUBLIC_SET => FCT::T_PUBLIC_SET,
1✔
326
                FCT::T_READONLY => FCT::T_READONLY,
1✔
327
            ];
1✔
328
        }
329

330
        return $keywords;
1✔
331
    }
332

333
    /**
334
     * Generate array containing all predefined constants that exists in PHP version in use.
335
     *
336
     * @return non-empty-array<int, int>
337
     *
338
     * @see https://php.net/manual/en/language.constants.predefined.php
339
     */
340
    public static function getMagicConstants(): array
341
    {
342
        static $magicConstants = null;
11✔
343

344
        if (null === $magicConstants) {
11✔
345
            $magicConstants = self::getTokenKindsForNames(['T_CLASS_C', 'T_DIR', 'T_FILE', 'T_FUNC_C', 'T_LINE', 'T_METHOD_C', 'T_NS_C', 'T_TRAIT_C']);
1✔
346
        }
347

348
        return $magicConstants;
11✔
349
    }
350

351
    /**
352
     * Check if token prototype is an array.
353
     *
354
     * @return bool is array
355
     *
356
     * @phpstan-assert-if-true !=null $this->getId()
357
     * @phpstan-assert-if-true !='' $this->getContent()
358
     */
359
    public function isArray(): bool
360
    {
361
        return $this->isArray;
3✔
362
    }
363

364
    /**
365
     * Check if token is one of type cast tokens.
366
     *
367
     * @phpstan-assert-if-true !='' $this->getContent()
368
     */
369
    public function isCast(): bool
370
    {
371
        return $this->isGivenKind(self::getCastTokenKinds());
9✔
372
    }
373

374
    /**
375
     * Check if token is one of classy tokens: T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM.
376
     *
377
     * @phpstan-assert-if-true !='' $this->getContent()
378
     */
379
    public function isClassy(): bool
380
    {
381
        return $this->isGivenKind(self::getClassyTokenKinds());
6✔
382
    }
383

384
    /**
385
     * Check if token is one of comment tokens: T_COMMENT or T_DOC_COMMENT.
386
     *
387
     * @phpstan-assert-if-true !='' $this->getContent()
388
     */
389
    public function isComment(): bool
390
    {
391
        return $this->isGivenKind([\T_COMMENT, \T_DOC_COMMENT]);
5✔
392
    }
393

394
    /**
395
     * Check if token is one of object operator tokens: T_OBJECT_OPERATOR or T_NULLSAFE_OBJECT_OPERATOR.
396
     *
397
     * @phpstan-assert-if-true !='' $this->getContent()
398
     */
399
    public function isObjectOperator(): bool
400
    {
401
        return $this->isGivenKind(self::getObjectOperatorKinds());
6✔
402
    }
403

404
    /**
405
     * Check if token is one of given kind.
406
     *
407
     * @param int|list<int> $possibleKind kind or array of kinds
408
     *
409
     * @phpstan-assert-if-true !=null $this->getId()
410
     * @phpstan-assert-if-true !='' $this->getContent()
411
     */
412
    public function isGivenKind($possibleKind): bool
413
    {
414
        return $this->isArray && (\is_array($possibleKind) ? \in_array($this->id, $possibleKind, true) : $this->id === $possibleKind);
37✔
415
    }
416

417
    /**
418
     * Check if token is a keyword.
419
     *
420
     * @phpstan-assert-if-true !='' $this->getContent()
421
     */
422
    public function isKeyword(): bool
423
    {
424
        $keywords = self::getKeywords();
1✔
425

426
        return $this->isArray && isset($keywords[$this->id]);
1✔
427
    }
428

429
    /**
430
     * Check if token is a native PHP constant: true, false or null.
431
     *
432
     * @phpstan-assert-if-true !='' $this->getContent()
433
     */
434
    public function isNativeConstant(): bool
435
    {
436
        return $this->isArray && \in_array(strtolower($this->content), ['true', 'false', 'null'], true);
7✔
437
    }
438

439
    /**
440
     * Returns if the token is of a Magic constants type.
441
     *
442
     * @phpstan-assert-if-true !='' $this->getContent()
443
     *
444
     * @see https://php.net/manual/en/language.constants.predefined.php
445
     */
446
    public function isMagicConstant(): bool
447
    {
448
        $magicConstants = self::getMagicConstants();
11✔
449

450
        return $this->isArray && isset($magicConstants[$this->id]);
11✔
451
    }
452

453
    /**
454
     * Check if token is whitespace.
455
     *
456
     * @param null|string $whitespaces whitespace characters, default is " \t\n\r\0\x0B"
457
     */
458
    public function isWhitespace(?string $whitespaces = " \t\n\r\0\x0B"): bool
459
    {
460
        if (null === $whitespaces) {
10✔
461
            $whitespaces = " \t\n\r\0\x0B";
8✔
462
        }
463

464
        if ($this->isArray && !$this->isGivenKind(\T_WHITESPACE)) {
10✔
465
            return false;
1✔
466
        }
467

468
        return '' === trim($this->content, $whitespaces);
9✔
469
    }
470

471
    /**
472
     * @return array{
473
     *     id: null|int,
474
     *     name: null|non-empty-string,
475
     *     content: string,
476
     *     isArray: bool,
477
     *     changed: bool,
478
     * }
479
     */
480
    public function toArray(): array
481
    {
482
        return [
3✔
483
            'id' => $this->id,
3✔
484
            'name' => $this->getName(),
3✔
485
            'content' => $this->content,
3✔
486
            'isArray' => $this->isArray,
3✔
487
            'changed' => false, // @TODO v4: remove index
3✔
488
        ];
3✔
489
    }
490

491
    /**
492
     * @return non-empty-string
493
     */
494
    public function toJson(): string
495
    {
496
        $jsonResult = json_encode($this->toArray(), \JSON_PRETTY_PRINT | \JSON_NUMERIC_CHECK);
×
497

498
        if (\JSON_ERROR_NONE !== json_last_error()) {
×
499
            $jsonResult = json_encode(
×
500
                [
×
501
                    'errorDescription' => 'Cannot encode Tokens to JSON.',
×
502
                    'rawErrorMessage' => json_last_error_msg(),
×
503
                ],
×
504
                \JSON_PRETTY_PRINT | \JSON_NUMERIC_CHECK
×
505
            );
×
506
        }
507

508
        \assert(false !== $jsonResult);
×
509

510
        return $jsonResult;
×
511
    }
512

513
    /**
514
     * @param non-empty-list<string> $tokenNames
515
     *
516
     * @return non-empty-array<int, int>
517
     */
518
    private static function getTokenKindsForNames(array $tokenNames): array
519
    {
520
        $keywords = [];
2✔
521
        foreach ($tokenNames as $keywordName) {
2✔
522
            $keyword = \constant($keywordName);
2✔
523
            $keywords[$keyword] = $keyword;
2✔
524
        }
525

526
        return $keywords;
2✔
527
    }
528
}
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