• 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

21.85
/src/Doctrine/Annotation/Tokens.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\Doctrine\Annotation;
16

17
use Doctrine\Common\Annotations\DocLexer;
18
use PhpCsFixer\Preg;
19
use PhpCsFixer\Tokenizer\Token as PhpToken;
20

21
/**
22
 * A list of Doctrine annotation tokens.
23
 *
24
 * @internal
25
 *
26
 * @extends \SplFixedArray<Token>
27
 */
28
final class Tokens extends \SplFixedArray
29
{
30
    /**
31
     * @param string[] $ignoredTags
32
     *
33
     * @throws \InvalidArgumentException
34
     */
35
    public static function createFromDocComment(PhpToken $input, array $ignoredTags = []): self
36
    {
37
        if (!$input->isGivenKind(T_DOC_COMMENT)) {
3✔
38
            throw new \InvalidArgumentException('Input must be a T_DOC_COMMENT token.');
×
39
        }
40

41
        $tokens = [];
3✔
42

43
        $content = $input->getContent();
3✔
44
        $ignoredTextPosition = 0;
3✔
45
        $currentPosition = 0;
3✔
46
        $token = null;
3✔
47
        while (false !== $nextAtPosition = strpos($content, '@', $currentPosition)) {
3✔
48
            if (0 !== $nextAtPosition && !Preg::match('/\s/', $content[$nextAtPosition - 1])) {
×
49
                $currentPosition = $nextAtPosition + 1;
×
50

51
                continue;
×
52
            }
53

54
            $lexer = new DocLexer();
×
55
            $lexer->setInput(substr($content, $nextAtPosition));
×
56

57
            $scannedTokens = [];
×
58
            $index = 0;
×
59
            $nbScannedTokensToUse = 0;
×
60
            $nbScopes = 0;
×
61
            while (null !== $token = $lexer->peek()) {
×
62
                if (0 === $index && DocLexer::T_AT !== $token['type']) {
×
63
                    break;
×
64
                }
65

66
                if (1 === $index) {
×
67
                    if (DocLexer::T_IDENTIFIER !== $token['type'] || \in_array($token['value'], $ignoredTags, true)) {
×
68
                        break;
×
69
                    }
70

71
                    $nbScannedTokensToUse = 2;
×
72
                }
73

74
                if ($index >= 2 && 0 === $nbScopes && !\in_array($token['type'], [DocLexer::T_NONE, DocLexer::T_OPEN_PARENTHESIS], true)) {
×
75
                    break;
×
76
                }
77

78
                $scannedTokens[] = $token;
×
79

80
                if (DocLexer::T_OPEN_PARENTHESIS === $token['type']) {
×
81
                    ++$nbScopes;
×
82
                } elseif (DocLexer::T_CLOSE_PARENTHESIS === $token['type']) {
×
83
                    if (0 === --$nbScopes) {
×
84
                        $nbScannedTokensToUse = \count($scannedTokens);
×
85

86
                        break;
×
87
                    }
88
                }
89

90
                ++$index;
×
91
            }
92

93
            if (0 !== $nbScopes) {
×
94
                break;
×
95
            }
96

97
            if (0 !== $nbScannedTokensToUse) {
×
98
                $ignoredTextLength = $nextAtPosition - $ignoredTextPosition;
×
99
                if (0 !== $ignoredTextLength) {
×
100
                    $tokens[] = new Token(DocLexer::T_NONE, substr($content, $ignoredTextPosition, $ignoredTextLength));
×
101
                }
102

103
                $lastTokenEndIndex = 0;
×
104
                foreach (\array_slice($scannedTokens, 0, $nbScannedTokensToUse) as $token) {
×
105
                    if (DocLexer::T_STRING === $token['type']) {
×
106
                        $token['value'] = '"'.str_replace('"', '""', $token['value']).'"';
×
107
                    }
108

109
                    $missingTextLength = $token['position'] - $lastTokenEndIndex;
×
110
                    if ($missingTextLength > 0) {
×
111
                        $tokens[] = new Token(DocLexer::T_NONE, substr(
×
112
                            $content,
×
113
                            $nextAtPosition + $lastTokenEndIndex,
×
114
                            $missingTextLength
×
115
                        ));
×
116
                    }
117

118
                    $tokens[] = new Token($token['type'], $token['value']);
×
119
                    $lastTokenEndIndex = $token['position'] + \strlen($token['value']);
×
120
                }
121

122
                $currentPosition = $ignoredTextPosition = $nextAtPosition + $token['position'] + \strlen($token['value']);
×
123
            } else {
124
                $currentPosition = $nextAtPosition + 1;
×
125
            }
126
        }
127

128
        if ($ignoredTextPosition < \strlen($content)) {
3✔
129
            $tokens[] = new Token(DocLexer::T_NONE, substr($content, $ignoredTextPosition));
3✔
130
        }
131

132
        return self::fromArray($tokens);
3✔
133
    }
134

135
    /**
136
     * Create token collection from array.
137
     *
138
     * @param Token[] $array       the array to import
139
     * @param ?bool   $saveIndices save the numeric indices used in the original array, default is yes
140
     */
141
    public static function fromArray($array, $saveIndices = null): self
142
    {
143
        $tokens = new self(\count($array));
3✔
144

145
        if (null === $saveIndices || $saveIndices) {
3✔
146
            foreach ($array as $key => $val) {
3✔
147
                $tokens[$key] = $val;
3✔
148
            }
149
        } else {
150
            $index = 0;
×
151

152
            foreach ($array as $val) {
×
153
                $tokens[$index++] = $val;
×
154
            }
155
        }
156

157
        return $tokens;
3✔
158
    }
159

160
    /**
161
     * Returns the index of the closest next token that is neither a comment nor a whitespace token.
162
     */
163
    public function getNextMeaningfulToken(int $index): ?int
164
    {
165
        return $this->getMeaningfulTokenSibling($index, 1);
×
166
    }
167

168
    /**
169
     * Returns the index of the closest previous token that is neither a comment nor a whitespace token.
170
     */
171
    public function getPreviousMeaningfulToken(int $index): ?int
172
    {
173
        return $this->getMeaningfulTokenSibling($index, -1);
×
174
    }
175

176
    /**
177
     * Returns the index of the last token that is part of the annotation at the given index.
178
     */
179
    public function getAnnotationEnd(int $index): ?int
180
    {
181
        $currentIndex = null;
×
182

183
        if (isset($this[$index + 2])) {
×
184
            if ($this[$index + 2]->isType(DocLexer::T_OPEN_PARENTHESIS)) {
×
185
                $currentIndex = $index + 2;
×
186
            } elseif (
187
                isset($this[$index + 3])
×
188
                && $this[$index + 2]->isType(DocLexer::T_NONE)
×
189
                && $this[$index + 3]->isType(DocLexer::T_OPEN_PARENTHESIS)
×
190
                && Preg::match('/^(\R\s*\*\s*)*\s*$/', $this[$index + 2]->getContent())
×
191
            ) {
192
                $currentIndex = $index + 3;
×
193
            }
194
        }
195

196
        if (null !== $currentIndex) {
×
197
            $level = 0;
×
198
            for ($max = \count($this); $currentIndex < $max; ++$currentIndex) {
×
199
                if ($this[$currentIndex]->isType(DocLexer::T_OPEN_PARENTHESIS)) {
×
200
                    ++$level;
×
201
                } elseif ($this[$currentIndex]->isType(DocLexer::T_CLOSE_PARENTHESIS)) {
×
202
                    --$level;
×
203
                }
204

205
                if (0 === $level) {
×
206
                    return $currentIndex;
×
207
                }
208
            }
209

210
            return null;
×
211
        }
212

213
        return $index + 1;
×
214
    }
215

216
    /**
217
     * Returns the code from the tokens.
218
     */
219
    public function getCode(): string
220
    {
221
        $code = '';
1✔
222
        foreach ($this as $token) {
1✔
223
            $code .= $token->getContent();
1✔
224
        }
225

226
        return $code;
1✔
227
    }
228

229
    /**
230
     * Inserts a token at the given index.
231
     */
232
    public function insertAt(int $index, Token $token): void
233
    {
234
        $this->setSize($this->getSize() + 1);
×
235

236
        for ($i = $this->getSize() - 1; $i > $index; --$i) {
×
237
            $this[$i] = $this[$i - 1] ?? new Token();
×
238
        }
239

240
        $this[$index] = $token;
×
241
    }
242

243
    public function offsetSet($index, $token): void
244
    {
245
        // @phpstan-ignore-next-line as we type checking here
246
        if (null === $token) {
3✔
247
            throw new \InvalidArgumentException('Token must be an instance of PhpCsFixer\\Doctrine\\Annotation\\Token, "null" given.');
1✔
248
        }
249

250
        if (!$token instanceof Token) {
3✔
251
            $type = \gettype($token);
1✔
252

253
            if ('object' === $type) {
1✔
254
                $type = \get_class($token);
×
255
            }
256

257
            throw new \InvalidArgumentException(sprintf('Token must be an instance of PhpCsFixer\\Doctrine\\Annotation\\Token, "%s" given.', $type));
1✔
258
        }
259

260
        parent::offsetSet($index, $token);
3✔
261
    }
262

263
    /**
264
     * {@inheritdoc}
265
     *
266
     * @throws \OutOfBoundsException
267
     */
268
    public function offsetUnset($index): void
269
    {
270
        if (!isset($this[$index])) {
×
271
            throw new \OutOfBoundsException(sprintf('Index "%s" is invalid or does not exist.', $index));
×
272
        }
273

274
        $max = \count($this) - 1;
×
275
        while ($index < $max) {
×
276
            // @phpstan-ignore-next-line Next index always exists.
277
            $this[$index] = $this[$index + 1];
×
278
            ++$index;
×
279
        }
280

281
        parent::offsetUnset($index);
×
282

283
        $this->setSize($max);
×
284
    }
285

286
    private function getMeaningfulTokenSibling(int $index, int $direction): ?int
287
    {
288
        while (true) {
×
289
            $index += $direction;
×
290

291
            if (!$this->offsetExists($index)) {
×
292
                break;
×
293
            }
294

295
            if (!$this[$index]->isType(DocLexer::T_NONE)) {
×
296
                return $index;
×
297
            }
298
        }
299

300
        return null;
×
301
    }
302
}
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