From 522c7e2951edc42dc1210b419757ad6aabec330f Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Thu, 29 Jun 2023 22:24:51 +0200 Subject: [PATCH] TokenIterator - detect newline --- src/Parser/TokenIterator.php | 32 ++++++++++++ tests/PHPStan/Parser/TokenIteratorTest.php | 58 ++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 tests/PHPStan/Parser/TokenIteratorTest.php diff --git a/src/Parser/TokenIterator.php b/src/Parser/TokenIterator.php index 796f66da..9be7593d 100644 --- a/src/Parser/TokenIterator.php +++ b/src/Parser/TokenIterator.php @@ -9,6 +9,7 @@ use function count; use function in_array; use function strlen; +use function substr; class TokenIterator { @@ -25,6 +26,9 @@ class TokenIterator /** @var list */ private $skippedTokenTypes = [Lexer::TOKEN_HORIZONTAL_WS]; + /** @var string|null */ + private $newline = null; + /** * @param list $tokens */ @@ -144,6 +148,12 @@ public function consumeTokenType(int $tokenType): void $this->throwError($tokenType); } + if ($tokenType === Lexer::TOKEN_PHPDOC_EOL) { + if ($this->newline === null) { + $this->detectNewline(); + } + } + $this->index++; $this->skipIrrelevantTokens(); } @@ -184,6 +194,12 @@ public function tryConsumeTokenType(int $tokenType): bool return false; } + if ($tokenType === Lexer::TOKEN_PHPDOC_EOL) { + if ($this->newline === null) { + $this->detectNewline(); + } + } + $this->index++; $this->skipIrrelevantTokens(); @@ -191,6 +207,17 @@ public function tryConsumeTokenType(int $tokenType): bool } + private function detectNewline(): void + { + $value = $this->currentTokenValue(); + if (substr($value, 0, 2) === "\r\n") { + $this->newline = "\r\n"; + } elseif (substr($value, 0, 1) === "\n") { + $this->newline = "\n"; + } + } + + public function getSkippedHorizontalWhiteSpaceIfAny(): string { if ($this->index > 0 && $this->tokens[$this->index - 1][Lexer::TYPE_OFFSET] === Lexer::TOKEN_HORIZONTAL_WS) { @@ -339,6 +366,11 @@ public function hasTokenImmediatelyAfter(int $pos, int $expectedTokenType): bool return false; } + public function getDetectedNewline(): ?string + { + return $this->newline; + } + /** * Whether the given position is immediately surrounded by parenthesis. */ diff --git a/tests/PHPStan/Parser/TokenIteratorTest.php b/tests/PHPStan/Parser/TokenIteratorTest.php new file mode 100644 index 00000000..6f526029 --- /dev/null +++ b/tests/PHPStan/Parser/TokenIteratorTest.php @@ -0,0 +1,58 @@ + + */ + public function dataGetDetectedNewline(): iterable + { + yield [ + '/** @param Foo $a */', + null, + ]; + + yield [ + '/**' . "\n" . + ' * @param Foo $a' . "\n" . + ' */', + "\n", + ]; + + yield [ + '/**' . "\r\n" . + ' * @param Foo $a' . "\r\n" . + ' */', + "\r\n", + ]; + + yield [ + '/**' . PHP_EOL . + ' * @param Foo $a' . PHP_EOL . + ' */', + PHP_EOL, + ]; + } + + /** + * @dataProvider dataGetDetectedNewline + */ + public function testGetDetectedNewline(string $phpDoc, ?string $expectedNewline): void + { + $lexer = new Lexer(true); + $tokens = new TokenIterator($lexer->tokenize($phpDoc)); + $constExprParser = new ConstExprParser(); + $typeParser = new TypeParser($constExprParser); + $phpDocParser = new PhpDocParser($typeParser, $constExprParser); + $phpDocParser->parse($tokens); + $this->assertSame($expectedNewline, $tokens->getDetectedNewline()); + } + +}