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

PHP-CS-Fixer / PHP-CS-Fixer / 3721300657

pending completion
3721300657

push

github

GitHub
minor: Follow PSR12 ordered imports in Symfony ruleset (#6712)

9 of 9 new or added lines in 2 files covered. (100.0%)

22674 of 24281 relevant lines covered (93.38%)

39.08 hits per line

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

70.59
/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
/**
18
 * Representation of single token.
19
 * As a token prototype you should understand a single element generated by token_get_all.
20
 *
21
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
22
 */
23
final class Token
24
{
25
    /**
26
     * Content of token prototype.
27
     */
28
    private string $content;
29

30
    /**
31
     * ID of token prototype, if available.
32
     */
33
    private ?int $id = null;
34

35
    /**
36
     * If token prototype is an array.
37
     */
38
    private bool $isArray;
39

40
    /**
41
     * Flag is token was changed.
42
     */
43
    private bool $changed = false;
44

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

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

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

69
            $this->isArray = true;
26✔
70
            $this->id = $token[0];
26✔
71
            $this->content = $token[1];
26✔
72
        } elseif (\is_string($token)) {
13✔
73
            $this->isArray = false;
7✔
74
            $this->content = $token;
7✔
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
        static $castTokens = [T_ARRAY_CAST, T_BOOL_CAST, T_DOUBLE_CAST, T_INT_CAST, T_OBJECT_CAST, T_STRING_CAST, T_UNSET_CAST];
9✔
86

87
        return $castTokens;
9✔
88
    }
89

90
    /**
91
     * Get classy tokens kinds: T_CLASS, T_INTERFACE and T_TRAIT.
92
     *
93
     * @return list<int>
94
     */
95
    public static function getClassyTokenKinds(): array
96
    {
97
        static $classTokens;
7✔
98

99
        if (null === $classTokens) {
7✔
100
            $classTokens = [T_CLASS, T_TRAIT, T_INTERFACE];
×
101

102
            if (\defined('T_ENUM')) { // @TODO: drop condition when PHP 8.1+ is required
×
103
                $classTokens[] = T_ENUM;
×
104
            }
105
        }
106

107
        return $classTokens;
7✔
108
    }
109

110
    /**
111
     * Get object operator tokens kinds: T_OBJECT_OPERATOR and (if available) T_NULLSAFE_OBJECT_OPERATOR.
112
     *
113
     * @return list<int>
114
     */
115
    public static function getObjectOperatorKinds(): array
116
    {
117
        static $objectOperators = null;
6✔
118

119
        if (null === $objectOperators) {
6✔
120
            $objectOperators = [T_OBJECT_OPERATOR];
×
121
            if (\defined('T_NULLSAFE_OBJECT_OPERATOR')) {
×
122
                $objectOperators[] = T_NULLSAFE_OBJECT_OPERATOR;
×
123
            }
124
        }
125

126
        return $objectOperators;
6✔
127
    }
128

129
    /**
130
     * Check if token is equals to given one.
131
     *
132
     * If tokens are arrays, then only keys defined in parameter token are checked.
133
     *
134
     * @param array{0: int, 1?: string}|string|Token $other         token or it's prototype
135
     * @param bool                                   $caseSensitive perform a case sensitive comparison
136
     */
137
    public function equals($other, bool $caseSensitive = true): bool
138
    {
139
        if (\defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG')) { // @TODO: drop condition with new MAJOR release 4.0
39✔
140
            if ('&' === $other) {
39✔
141
                return '&' === $this->content && (null === $this->id || $this->isGivenKind([T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG]));
3✔
142
            }
143
            if (null === $this->id && '&' === $this->content) {
36✔
144
                return $other instanceof self && '&' === $other->content && (null === $other->id || $other->isGivenKind([T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG]));
2✔
145
            }
146
        }
147

148
        if ($other instanceof self) {
34✔
149
            // Inlined getPrototype() on this very hot path.
150
            // We access the private properties of $other directly to save function call overhead.
151
            // This is only possible because $other is of the same class as `self`.
152
            if (!$other->isArray) {
11✔
153
                $otherPrototype = $other->content;
6✔
154
            } else {
155
                $otherPrototype = [
11✔
156
                    $other->id,
11✔
157
                    $other->content,
11✔
158
                ];
11✔
159
            }
160
        } else {
161
            $otherPrototype = $other;
27✔
162
        }
163

164
        if ($this->isArray !== \is_array($otherPrototype)) {
34✔
165
            return false;
8✔
166
        }
167

168
        if (!$this->isArray) {
31✔
169
            return $this->content === $otherPrototype;
4✔
170
        }
171

172
        if ($this->id !== $otherPrototype[0]) {
27✔
173
            return false;
12✔
174
        }
175

176
        if (isset($otherPrototype[1])) {
20✔
177
            if ($caseSensitive) {
17✔
178
                if ($this->content !== $otherPrototype[1]) {
11✔
179
                    return false;
11✔
180
                }
181
            } elseif (0 !== strcasecmp($this->content, $otherPrototype[1])) {
6✔
182
                return false;
2✔
183
            }
184
        }
185

186
        // detect unknown keys
187
        unset($otherPrototype[0], $otherPrototype[1]);
15✔
188

189
        /*
190
         * @phpstan-ignore-next-line This validation is required when the method
191
         *                           is called in a codebase that does not use
192
         *                           static analysis.
193
         */
194
        return empty($otherPrototype);
15✔
195
    }
196

197
    /**
198
     * Check if token is equals to one of given.
199
     *
200
     * @param list<array{0: int, 1?: string}|string|Token> $others        array of tokens or token prototypes
201
     * @param bool                                         $caseSensitive perform a case sensitive comparison
202
     */
203
    public function equalsAny(array $others, bool $caseSensitive = true): bool
204
    {
205
        foreach ($others as $other) {
9✔
206
            if ($this->equals($other, $caseSensitive)) {
8✔
207
                return true;
4✔
208
            }
209
        }
210

211
        return false;
6✔
212
    }
213

214
    /**
215
     * A helper method used to find out whether a certain input token has to be case-sensitively matched.
216
     *
217
     * @param bool|list<bool> $caseSensitive global case sensitiveness or an array of booleans, whose keys should match
218
     *                                       the ones used in $sequence. If any is missing, the default case-sensitive
219
     *                                       comparison is used
220
     * @param int             $key           the key of the token that has to be looked up
221
     */
222
    public static function isKeyCaseSensitive($caseSensitive, int $key): bool
223
    {
224
        if (\is_array($caseSensitive)) {
12✔
225
            return $caseSensitive[$key] ?? true;
9✔
226
        }
227

228
        return $caseSensitive;
3✔
229
    }
230

231
    /**
232
     * @return array{int, string}|string
233
     */
234
    public function getPrototype()
235
    {
236
        if (!$this->isArray) {
1✔
237
            return $this->content;
1✔
238
        }
239

240
        return [
1✔
241
            $this->id,
1✔
242
            $this->content,
1✔
243
        ];
1✔
244
    }
245

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

256
    /**
257
     * Get token's id.
258
     *
259
     * It shall be used only for getting the internal id of token, not for checking it against excepted value.
260
     */
261
    public function getId(): ?int
262
    {
263
        return $this->id;
2✔
264
    }
265

266
    /**
267
     * Get token's name.
268
     *
269
     * It shall be used only for getting the name of token, not for checking it against excepted value.
270
     *
271
     * @return null|string token name
272
     */
273
    public function getName(): ?string
274
    {
275
        if (null === $this->id) {
6✔
276
            return null;
4✔
277
        }
278

279
        return self::getNameForId($this->id);
2✔
280
    }
281

282
    /**
283
     * Get token's name.
284
     *
285
     * It shall be used only for getting the name of token, not for checking it against excepted value.
286
     *
287
     * @return null|string token name
288
     */
289
    public static function getNameForId(int $id): ?string
290
    {
291
        if (CT::has($id)) {
5✔
292
            return CT::getName($id);
1✔
293
        }
294

295
        $name = token_name($id);
4✔
296

297
        return 'UNKNOWN' === $name ? null : $name;
4✔
298
    }
299

300
    /**
301
     * Generate array containing all keywords that exists in PHP version in use.
302
     *
303
     * @return array<int, int>
304
     */
305
    public static function getKeywords(): array
306
    {
307
        static $keywords = null;
1✔
308

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

334
        return $keywords;
1✔
335
    }
336

337
    /**
338
     * Generate array containing all predefined constants that exists in PHP version in use.
339
     *
340
     * @see https://php.net/manual/en/language.constants.predefined.php
341
     *
342
     * @return array<int, int>
343
     */
344
    public static function getMagicConstants(): array
345
    {
346
        static $magicConstants = null;
12✔
347

348
        if (null === $magicConstants) {
12✔
349
            $magicConstants = self::getTokenKindsForNames(['T_CLASS_C', 'T_DIR', 'T_FILE', 'T_FUNC_C', 'T_LINE', 'T_METHOD_C', 'T_NS_C', 'T_TRAIT_C']);
×
350
        }
351

352
        return $magicConstants;
12✔
353
    }
354

355
    /**
356
     * Check if token prototype is an array.
357
     *
358
     * @return bool is array
359
     */
360
    public function isArray(): bool
361
    {
362
        return $this->isArray;
3✔
363
    }
364

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

373
    /**
374
     * Check if token is one of classy tokens: T_CLASS, T_INTERFACE, T_TRAIT or T_ENUM.
375
     */
376
    public function isClassy(): bool
377
    {
378
        return $this->isGivenKind(self::getClassyTokenKinds());
6✔
379
    }
380

381
    /**
382
     * Check if token is one of comment tokens: T_COMMENT or T_DOC_COMMENT.
383
     */
384
    public function isComment(): bool
385
    {
386
        static $commentTokens = [T_COMMENT, T_DOC_COMMENT];
5✔
387

388
        return $this->isGivenKind($commentTokens);
5✔
389
    }
390

391
    /**
392
     * Check if token is one of object operator tokens: T_OBJECT_OPERATOR or T_NULLSAFE_OBJECT_OPERATOR.
393
     */
394
    public function isObjectOperator(): bool
395
    {
396
        return $this->isGivenKind(self::getObjectOperatorKinds());
6✔
397
    }
398

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

409
    /**
410
     * Check if token is a keyword.
411
     */
412
    public function isKeyword(): bool
413
    {
414
        $keywords = static::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
    public function isNativeConstant(): bool
423
    {
424
        static $nativeConstantStrings = ['true', 'false', 'null'];
7✔
425

426
        return $this->isArray && \in_array(strtolower($this->content), $nativeConstantStrings, true);
7✔
427
    }
428

429
    /**
430
     * Returns if the token is of a Magic constants type.
431
     *
432
     * @see https://php.net/manual/en/language.constants.predefined.php
433
     */
434
    public function isMagicConstant(): bool
435
    {
436
        $magicConstants = static::getMagicConstants();
12✔
437

438
        return $this->isArray && isset($magicConstants[$this->id]);
12✔
439
    }
440

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

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

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

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

479
    public function toJson(): string
480
    {
481
        $jsonResult = json_encode($this->toArray(), JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK);
×
482

483
        if (JSON_ERROR_NONE !== json_last_error()) {
×
484
            $jsonResult = json_encode(
×
485
                [
×
486
                    'errorDescription' => 'Cannot encode Tokens to JSON.',
×
487
                    'rawErrorMessage' => json_last_error_msg(),
×
488
                ],
×
489
                JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK
×
490
            );
×
491
        }
492

493
        return $jsonResult;
×
494
    }
495

496
    /**
497
     * @param list<string> $tokenNames
498
     *
499
     * @return array<int, int>
500
     */
501
    private static function getTokenKindsForNames(array $tokenNames): array
502
    {
503
        $keywords = [];
×
504
        foreach ($tokenNames as $keywordName) {
×
505
            if (\defined($keywordName)) {
×
506
                $keyword = \constant($keywordName);
×
507
                $keywords[$keyword] = $keyword;
×
508
            }
509
        }
510

511
        return $keywords;
×
512
    }
513
}
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