diff --git a/.doctrine-project.json b/.doctrine-project.json index d01956a..192de97 100644 --- a/.doctrine-project.json +++ b/.doctrine-project.json @@ -4,10 +4,28 @@ "slug": "lexer", "docsSlug": "doctrine-lexer", "versions": [ + { + "name": "3.0", + "branchName": "3.0.x", + "slug": "latest", + "upcoming": true + }, + { + "name": "2.1", + "branchName": "2.1.x", + "upcoming": true + }, + { + "name": "2.0", + "branchName": "2.0.x", + "aliases": [ + "current", + "stable" + ] + }, { "name": "1.3", "branchName": "1.3.x", - "slug": "latest", "upcoming": true }, { @@ -15,11 +33,7 @@ "branchName": "1.2.x", "slug": "1.2", "current": true, - "maintained": true, - "aliases": [ - "current", - "stable" - ] + "maintained": true }, { "name": "1.1", diff --git a/docs/en/simple-parser-example.rst b/docs/en/simple-parser-example.rst index 23becdb..c663ee3 100644 --- a/docs/en/simple-parser-example.rst +++ b/docs/en/simple-parser-example.rst @@ -11,6 +11,9 @@ It tokenizes a string to ``T_UPPER``, ``T_LOWER`` and``T_NUMBER`` tokens: use Doctrine\Common\Lexer\AbstractLexer; + /** + * @extends AbstractLexer + */ class CharacterTypeLexer extends AbstractLexer { const T_UPPER = 1; diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 6d0fc0d..8d89ad9 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -3,3 +3,8 @@ parameters: paths: - %rootDir%/../../../src - %rootDir%/../../../tests + + ignoreErrors: + - + message: '#^Method Doctrine\\Tests\\Common\\Lexer\\AbstractLexerTest\:\:dataProvider\(\) should return array\\>\}\> but returns array\{array\{''price\=10'', array\{Doctrine\\Common\\Lexer\\Token\, Doctrine\\Common\\Lexer\\Token\, Doctrine\\Common\\Lexer\\Token\\}\}\}\.$#' + path: tests/AbstractLexerTest.php diff --git a/psalm.xml b/psalm.xml index 21acc2b..dfb8bc6 100644 --- a/psalm.xml +++ b/psalm.xml @@ -35,5 +35,22 @@ + + + + + + + + + + + + + + + + + diff --git a/src/AbstractLexer.php b/src/AbstractLexer.php index 7d33e41..eed4c51 100644 --- a/src/AbstractLexer.php +++ b/src/AbstractLexer.php @@ -21,6 +21,7 @@ * Base class for writing simple lexers, i.e. for creating small DSLs. * * @template T of UnitEnum|string|int + * @template V of string|int */ abstract class AbstractLexer { @@ -34,7 +35,7 @@ abstract class AbstractLexer /** * Array of scanned tokens. * - * @var list> + * @var list> */ private $tokens = []; @@ -56,7 +57,7 @@ abstract class AbstractLexer * The next token in the input. * * @var mixed[]|null - * @psalm-var Token|null + * @psalm-var Token|null */ public $lookahead; @@ -64,7 +65,7 @@ abstract class AbstractLexer * The last matched/seen token. * * @var mixed[]|null - * @psalm-var Token|null + * @psalm-var Token|null */ public $token; @@ -147,6 +148,8 @@ public function getInputUntilPosition($position) * @param T $type * * @return bool + * + * @psalm-assert-if-true !=null $this->lookahead */ public function isNextToken($type) { @@ -159,6 +162,8 @@ public function isNextToken($type) * @param list $types * * @return bool + * + * @psalm-assert-if-true !=null $this->lookahead */ public function isNextTokenAny(array $types) { @@ -169,6 +174,8 @@ public function isNextTokenAny(array $types) * Moves to the next token in the input string. * * @return bool + * + * @psalm-assert-if-true !null $this->lookahead */ public function moveNext() { @@ -211,7 +218,7 @@ public function isA($value, $token) * Moves the lookahead token forward. * * @return mixed[]|null The next token or NULL if there are no more tokens ahead. - * @psalm-return Token|null + * @psalm-return Token|null */ public function peek() { @@ -226,7 +233,7 @@ public function peek() * Peeks at the next token, returns it and immediately resets the peek. * * @return mixed[]|null The next token or NULL if there are no more tokens ahead. - * @psalm-return Token|null + * @psalm-return Token|null */ public function glimpse() { @@ -264,10 +271,11 @@ protected function scan($input) foreach ($matches as $match) { // Must remain before 'value' assignment since it can change content - $type = $this->getType($match[0]); + $firstMatch = $match[0]; + $type = $this->getType($firstMatch); $this->tokens[] = new Token( - $match[0], + $firstMatch, $type, $match[1] ); @@ -331,6 +339,8 @@ abstract protected function getNonCatchablePatterns(); * @param string $value * * @return T|null + * + * @param-out V $value */ abstract protected function getType(&$value); } diff --git a/src/Token.php b/src/Token.php index ec2fdc4..68a9fa8 100644 --- a/src/Token.php +++ b/src/Token.php @@ -8,14 +8,17 @@ use function in_array; -/** @template T of UnitEnum|string|int */ +/** + * @template T of UnitEnum|string|int + * @template V of string|int + */ final class Token { /** * The string value of the token in the input string * * @readonly - * @var string|int + * @var V */ public $value; @@ -36,8 +39,8 @@ final class Token public $position; /** - * @param string|int $value - * @param T|null $type + * @param V $value + * @param T|null $type */ public function __construct($value, $type, int $position) { diff --git a/tests/AbstractLexerTest.php b/tests/AbstractLexerTest.php index f635d5b..7050914 100644 --- a/tests/AbstractLexerTest.php +++ b/tests/AbstractLexerTest.php @@ -30,7 +30,7 @@ public function tearDown(): void } /** - * @psalm-return list>}> + * @psalm-return list>}> */ public function dataProvider(): array { @@ -86,7 +86,7 @@ public function testResetPosition(): void } /** - * @psalm-param list> $expectedTokens + * @psalm-param list> $expectedTokens * * @dataProvider dataProvider */ @@ -130,7 +130,7 @@ public function testUtf8Mismatch(): void } /** - * @psalm-param list> $expectedTokens + * @psalm-param list> $expectedTokens * * @dataProvider dataProvider */ @@ -150,7 +150,7 @@ public function testPeek(string $input, array $expectedTokens): void } /** - * @psalm-param list> $expectedTokens + * @psalm-param list> $expectedTokens * * @dataProvider dataProvider */ @@ -196,7 +196,7 @@ public function testGetInputUntilPosition( } /** - * @psalm-param list> $expectedTokens + * @psalm-param list> $expectedTokens * * @dataProvider dataProvider */ @@ -213,7 +213,7 @@ public function testIsNextToken(string $input, array $expectedTokens): void } /** - * @psalm-param list> $expectedTokens + * @psalm-param list> $expectedTokens * * @dataProvider dataProvider */ diff --git a/tests/ConcreteLexer.php b/tests/ConcreteLexer.php index 1c8601c..62fc2db 100644 --- a/tests/ConcreteLexer.php +++ b/tests/ConcreteLexer.php @@ -9,7 +9,7 @@ use function in_array; use function is_numeric; -/** @extends AbstractLexer */ +/** @extends AbstractLexer */ class ConcreteLexer extends AbstractLexer { public const INT = 'int'; diff --git a/tests/EnumLexer.php b/tests/EnumLexer.php index f6fcd5f..083d0a2 100644 --- a/tests/EnumLexer.php +++ b/tests/EnumLexer.php @@ -9,7 +9,7 @@ use function in_array; use function is_numeric; -/** @extends AbstractLexer */ +/** @extends AbstractLexer */ class EnumLexer extends AbstractLexer { /** diff --git a/tests/MutableLexer.php b/tests/MutableLexer.php index 20776b0..5530b29 100644 --- a/tests/MutableLexer.php +++ b/tests/MutableLexer.php @@ -6,7 +6,7 @@ use Doctrine\Common\Lexer\AbstractLexer; -/** @extends AbstractLexer */ +/** @extends AbstractLexer */ class MutableLexer extends AbstractLexer { /** @var string[] */ diff --git a/tests/TokenTest.php b/tests/TokenTest.php index 43503f7..4944938 100644 --- a/tests/TokenTest.php +++ b/tests/TokenTest.php @@ -11,7 +11,7 @@ final class TokenTest extends TestCase { public function testIsA(): void { - /** @var Token<'string'|'int'> $token */ + /** @var Token<'string'|'int', string> $token */ $token = new Token('foo', 'string', 1); self::assertTrue($token->isA('string'));