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

keradus / PHP-CS-Fixer / 16303177127

15 Jul 2025 06:22PM UTC coverage: 94.758% (-0.05%) from 94.806%
16303177127

push

github

keradus
bumped version

28199 of 29759 relevant lines covered (94.76%)

45.91 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
 *
23
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
24
 *
25
 * @readonly
26
 */
27
final class Token
28
{
29
    /**
30
     * Content of token prototype.
31
     */
32
    private string $content;
33

34
    /**
35
     * ID of token prototype, if available.
36
     */
37
    private ?int $id;
38

39
    /**
40
     * If token prototype is an array.
41
     */
42
    private bool $isArray;
43

44
    /**
45
     * @param array{int, string}|string $token token prototype
46
     */
47
    public function __construct($token)
48
    {
49
        if (\is_array($token)) {
41✔
50
            if (!\is_int($token[0])) {
33✔
51
                throw new \InvalidArgumentException(\sprintf(
3✔
52
                    'Id must be an int, got "%s".',
3✔
53
                    get_debug_type($token[0])
3✔
54
                ));
3✔
55
            }
56

57
            if (!\is_string($token[1])) {
30✔
58
                throw new \InvalidArgumentException(\sprintf(
3✔
59
                    'Content must be a string, got "%s".',
3✔
60
                    get_debug_type($token[1])
3✔
61
                ));
3✔
62
            }
63

64
            if ('' === $token[1]) {
27✔
65
                throw new \InvalidArgumentException('Cannot set empty content for id-based Token.');
1✔
66
            }
67

68
            $this->isArray = true;
26✔
69
            $this->id = $token[0];
26✔
70
            $this->content = $token[1];
26✔
71
        } elseif (\is_string($token)) {
12✔
72
            $this->isArray = false;
6✔
73
            $this->id = null;
6✔
74
            $this->content = $token;
6✔
75
        } else {
76
            throw new \InvalidArgumentException(\sprintf('Cannot recognize input value as valid Token prototype, got "%s".', get_debug_type($token)));
6✔
77
        }
78
    }
79

80
    /**
81
     * @return list<int>
82
     */
83
    public static function getCastTokenKinds(): array
84
    {
85
        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✔
86
    }
87

88
    /**
89
     * Get classy tokens kinds: T_ENUM, T_CLASS, T_INTERFACE and T_TRAIT.
90
     *
91
     * @return list<int>
92
     */
93
    public static function getClassyTokenKinds(): array
94
    {
95
        return [\T_CLASS, \T_TRAIT, \T_INTERFACE, FCT::T_ENUM];
6✔
96
    }
97

98
    /**
99
     * Get object operator tokens kinds: T_OBJECT_OPERATOR and (if available) T_NULLSAFE_OBJECT_OPERATOR.
100
     *
101
     * @return list<int>
102
     */
103
    public static function getObjectOperatorKinds(): array
104
    {
105
        return [\T_OBJECT_OPERATOR, FCT::T_NULLSAFE_OBJECT_OPERATOR];
6✔
106
    }
107

108
    /**
109
     * Check if token is equals to given one.
110
     *
111
     * If tokens are arrays, then only keys defined in parameter token are checked.
112
     *
113
     * @param array{0: int, 1?: string}|string|Token $other         token or it's prototype
114
     * @param bool                                   $caseSensitive perform a case sensitive comparison
115
     */
116
    public function equals($other, bool $caseSensitive = true): bool
117
    {
118
        if ('&' === $other) {
39✔
119
            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✔
120
        }
121
        if (null === $this->id && '&' === $this->content) {
36✔
122
            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✔
123
        }
124

125
        if ($other instanceof self) {
34✔
126
            // Inlined getPrototype() on this very hot path.
127
            // We access the private properties of $other directly to save function call overhead.
128
            // This is only possible because $other is of the same class as `self`.
129
            if (!$other->isArray) {
11✔
130
                $otherPrototype = $other->content;
6✔
131
            } else {
132
                $otherPrototype = [
10✔
133
                    $other->id,
10✔
134
                    $other->content,
10✔
135
                ];
10✔
136
            }
137
        } else {
138
            $otherPrototype = $other;
27✔
139
        }
140

141
        if ($this->isArray !== \is_array($otherPrototype)) {
34✔
142
            return false;
8✔
143
        }
144

145
        if (!$this->isArray) {
31✔
146
            return $this->content === $otherPrototype;
4✔
147
        }
148

149
        if ($this->id !== $otherPrototype[0]) {
27✔
150
            return false;
12✔
151
        }
152

153
        if (isset($otherPrototype[1])) {
20✔
154
            if ($caseSensitive) {
17✔
155
                if ($this->content !== $otherPrototype[1]) {
11✔
156
                    return false;
5✔
157
                }
158
            } elseif (0 !== strcasecmp($this->content, $otherPrototype[1])) {
6✔
159
                return false;
2✔
160
            }
161
        }
162

163
        // detect unknown keys
164
        unset($otherPrototype[0], $otherPrototype[1]);
15✔
165

166
        return [] === $otherPrototype;
15✔
167
    }
168

169
    /**
170
     * Check if token is equals to one of given.
171
     *
172
     * @param list<array{0: int, 1?: string}|string|Token> $others        array of tokens or token prototypes
173
     * @param bool                                         $caseSensitive perform a case sensitive comparison
174
     */
175
    public function equalsAny(array $others, bool $caseSensitive = true): bool
176
    {
177
        foreach ($others as $other) {
9✔
178
            if ($this->equals($other, $caseSensitive)) {
8✔
179
                return true;
4✔
180
            }
181
        }
182

183
        return false;
6✔
184
    }
185

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

203
        if (\is_array($caseSensitive)) {
12✔
204
            return $caseSensitive[$key] ?? true;
9✔
205
        }
206

207
        return $caseSensitive;
3✔
208
    }
209

210
    /**
211
     * @return array{int, non-empty-string}|string
212
     */
213
    public function getPrototype()
214
    {
215
        if (!$this->isArray) {
1✔
216
            return $this->content;
1✔
217
        }
218

219
        \assert('' !== $this->content);
1✔
220

221
        return [
1✔
222
            $this->id,
1✔
223
            $this->content,
1✔
224
        ];
1✔
225
    }
226

227
    /**
228
     * Get token's content.
229
     *
230
     * It shall be used only for getting the content of token, not for checking it against excepted value.
231
     */
232
    public function getContent(): string
233
    {
234
        return $this->content;
2✔
235
    }
236

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

247
    /**
248
     * Get token's name.
249
     *
250
     * It shall be used only for getting the name of token, not for checking it against excepted value.
251
     *
252
     * @return null|non-empty-string token name
253
     */
254
    public function getName(): ?string
255
    {
256
        if (null === $this->id) {
6✔
257
            return null;
4✔
258
        }
259

260
        return self::getNameForId($this->id);
2✔
261
    }
262

263
    /**
264
     * Get token's name.
265
     *
266
     * It shall be used only for getting the name of token, not for checking it against excepted value.
267
     *
268
     * @return null|non-empty-string token name
269
     */
270
    public static function getNameForId(int $id): ?string
271
    {
272
        if (CT::has($id)) {
5✔
273
            return CT::getName($id);
1✔
274
        }
275

276
        $name = token_name($id);
4✔
277

278
        return 'UNKNOWN' === $name ? null : $name;
4✔
279
    }
280

281
    /**
282
     * Generate array containing all keywords that exists in PHP version in use.
283
     *
284
     * @return list<int>
285
     */
286
    public static function getKeywords(): array
287
    {
288
        static $keywords = null;
1✔
289

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

321
        return $keywords;
1✔
322
    }
323

324
    /**
325
     * Generate array containing all predefined constants that exists in PHP version in use.
326
     *
327
     * @return array<int, int>
328
     *
329
     * @see https://php.net/manual/en/language.constants.predefined.php
330
     */
331
    public static function getMagicConstants(): array
332
    {
333
        static $magicConstants = null;
11✔
334

335
        if (null === $magicConstants) {
11✔
336
            $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✔
337
        }
338

339
        return $magicConstants;
11✔
340
    }
341

342
    /**
343
     * Check if token prototype is an array.
344
     *
345
     * @return bool is array
346
     *
347
     * @phpstan-assert-if-true !=null $this->getId()
348
     * @phpstan-assert-if-true !='' $this->getContent()
349
     */
350
    public function isArray(): bool
351
    {
352
        return $this->isArray;
3✔
353
    }
354

355
    /**
356
     * Check if token is one of type cast tokens.
357
     *
358
     * @phpstan-assert-if-true !='' $this->getContent()
359
     */
360
    public function isCast(): bool
361
    {
362
        return $this->isGivenKind(self::getCastTokenKinds());
9✔
363
    }
364

365
    /**
366
     * Check if token is one of classy tokens: T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM.
367
     *
368
     * @phpstan-assert-if-true !='' $this->getContent()
369
     */
370
    public function isClassy(): bool
371
    {
372
        return $this->isGivenKind(self::getClassyTokenKinds());
6✔
373
    }
374

375
    /**
376
     * Check if token is one of comment tokens: T_COMMENT or T_DOC_COMMENT.
377
     *
378
     * @phpstan-assert-if-true !='' $this->getContent()
379
     */
380
    public function isComment(): bool
381
    {
382
        return $this->isGivenKind([\T_COMMENT, \T_DOC_COMMENT]);
5✔
383
    }
384

385
    /**
386
     * Check if token is one of object operator tokens: T_OBJECT_OPERATOR or T_NULLSAFE_OBJECT_OPERATOR.
387
     *
388
     * @phpstan-assert-if-true !='' $this->getContent()
389
     */
390
    public function isObjectOperator(): bool
391
    {
392
        return $this->isGivenKind(self::getObjectOperatorKinds());
6✔
393
    }
394

395
    /**
396
     * Check if token is one of given kind.
397
     *
398
     * @param int|list<int> $possibleKind kind or array of kinds
399
     *
400
     * @phpstan-assert-if-true !='' $this->getContent()
401
     */
402
    public function isGivenKind($possibleKind): bool
403
    {
404
        return $this->isArray && (\is_array($possibleKind) ? \in_array($this->id, $possibleKind, true) : $this->id === $possibleKind);
37✔
405
    }
406

407
    /**
408
     * Check if token is a keyword.
409
     *
410
     * @phpstan-assert-if-true !='' $this->getContent()
411
     */
412
    public function isKeyword(): bool
413
    {
414
        $keywords = self::getKeywords();
1✔
415

416
        return $this->isArray && isset($keywords[$this->id]);
1✔
417
    }
418

419
    /**
420
     * Check if token is a native PHP constant: true, false or null.
421
     *
422
     * @phpstan-assert-if-true !='' $this->getContent()
423
     */
424
    public function isNativeConstant(): bool
425
    {
426
        return $this->isArray && \in_array(strtolower($this->content), ['true', 'false', 'null'], true);
7✔
427
    }
428

429
    /**
430
     * Returns if the token is of a Magic constants type.
431
     *
432
     * @phpstan-assert-if-true !='' $this->getContent()
433
     *
434
     * @see https://php.net/manual/en/language.constants.predefined.php
435
     */
436
    public function isMagicConstant(): bool
437
    {
438
        $magicConstants = self::getMagicConstants();
11✔
439

440
        return $this->isArray && isset($magicConstants[$this->id]);
11✔
441
    }
442

443
    /**
444
     * Check if token is whitespace.
445
     *
446
     * @param null|string $whitespaces whitespace characters, default is " \t\n\r\0\x0B"
447
     */
448
    public function isWhitespace(?string $whitespaces = " \t\n\r\0\x0B"): bool
449
    {
450
        if (null === $whitespaces) {
10✔
451
            $whitespaces = " \t\n\r\0\x0B";
8✔
452
        }
453

454
        if ($this->isArray && !$this->isGivenKind(\T_WHITESPACE)) {
10✔
455
            return false;
1✔
456
        }
457

458
        return '' === trim($this->content, $whitespaces);
9✔
459
    }
460

461
    /**
462
     * @return array{
463
     *     id: int|null,
464
     *     name: non-empty-string|null,
465
     *     content: string,
466
     *     isArray: bool,
467
     *     changed: bool,
468
     * }
469
     */
470
    public function toArray(): array
471
    {
472
        return [
3✔
473
            'id' => $this->id,
3✔
474
            'name' => $this->getName(),
3✔
475
            'content' => $this->content,
3✔
476
            'isArray' => $this->isArray,
3✔
477
            'changed' => false, // @TODO v4: remove index
3✔
478
        ];
3✔
479
    }
480

481
    /**
482
     * @return non-empty-string
483
     */
484
    public function toJson(): string
485
    {
486
        $jsonResult = json_encode($this->toArray(), \JSON_PRETTY_PRINT | \JSON_NUMERIC_CHECK);
×
487

488
        if (\JSON_ERROR_NONE !== json_last_error()) {
×
489
            $jsonResult = json_encode(
×
490
                [
×
491
                    'errorDescription' => 'Cannot encode Tokens to JSON.',
×
492
                    'rawErrorMessage' => json_last_error_msg(),
×
493
                ],
×
494
                \JSON_PRETTY_PRINT | \JSON_NUMERIC_CHECK
×
495
            );
×
496
        }
497

498
        \assert(false !== $jsonResult);
×
499

500
        return $jsonResult;
×
501
    }
502

503
    /**
504
     * @param list<string> $tokenNames
505
     *
506
     * @return array<int, int>
507
     */
508
    private static function getTokenKindsForNames(array $tokenNames): array
509
    {
510
        $keywords = [];
2✔
511
        foreach ($tokenNames as $keywordName) {
2✔
512
            $keyword = \constant($keywordName);
2✔
513
            $keywords[$keyword] = $keyword;
2✔
514
        }
515

516
        return $keywords;
2✔
517
    }
518
}
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