From 8fd2fd4e8306a12c158575195f09f62e4c5c8f00 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 27 Sep 2026 21:10:24 +0200 Subject: [PATCH] Compute token offsets once instead of rescanning from the first token --- src/Parser/TokenIterator.php | 15 +++++++++++---- tests/PHPStan/Parser/TokenIteratorTest.php | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/Parser/TokenIterator.php b/src/Parser/TokenIterator.php index f42cc2fb..043639f1 100644 --- a/src/Parser/TokenIterator.php +++ b/src/Parser/TokenIterator.php @@ -31,6 +31,9 @@ class TokenIterator private ?string $newline = null; + /** @var list|null */ + private ?array $offsets = null; + /** * @param list $tokens */ @@ -81,12 +84,16 @@ public function currentTokenType(): int public function currentTokenOffset(): int { - $offset = 0; - for ($i = 0; $i < $this->index; $i++) { - $offset += strlen($this->tokens[$i][Lexer::VALUE_OFFSET]); + if ($this->offsets === null) { + $offset = 0; + $this->offsets = []; + foreach ($this->tokens as $token) { + $this->offsets[] = $offset; + $offset += strlen($token[Lexer::VALUE_OFFSET]); + } } - return $offset; + return $this->offsets[$this->index]; } public function currentTokenLine(): int diff --git a/tests/PHPStan/Parser/TokenIteratorTest.php b/tests/PHPStan/Parser/TokenIteratorTest.php index 3cc51fc1..2a63824b 100644 --- a/tests/PHPStan/Parser/TokenIteratorTest.php +++ b/tests/PHPStan/Parser/TokenIteratorTest.php @@ -5,6 +5,8 @@ use PHPStan\PhpDocParser\Lexer\Lexer; use PHPStan\PhpDocParser\ParserConfig; use PHPUnit\Framework\TestCase; +use function strlen; +use function substr; use const PHP_EOL; class TokenIteratorTest extends TestCase @@ -57,4 +59,19 @@ public function testGetDetectedNewline(string $phpDoc, ?string $expectedNewline) $this->assertSame($expectedNewline, $tokens->getDetectedNewline()); } + public function testCurrentTokenOffset(): void + { + $lexer = new Lexer(new ParserConfig([])); + $phpDoc = "/**\n * @param array{size?: 'sm'|'md', icon: Icon} \$a\n */"; + $tokens = new TokenIterator($lexer->tokenize($phpDoc)); + + do { + $this->assertSame($tokens->currentTokenValue(), substr($phpDoc, $tokens->currentTokenOffset(), strlen($tokens->currentTokenValue()))); + $this->assertSame(strlen($tokens->getContentBetween(0, $tokens->currentTokenIndex())), $tokens->currentTokenOffset()); + $tokens->next(); + } while (!$tokens->isCurrentTokenType(Lexer::TOKEN_END)); + + $this->assertSame(strlen($phpDoc), $tokens->currentTokenOffset()); + } + }