From 1964d4158e1a196a33e1c430728ffd27c1a2d728 Mon Sep 17 00:00:00 2001 From: Romain Canon Date: Sun, 13 Aug 2023 18:10:39 +0200 Subject: [PATCH] fix: properly handle class/enum name in shaped array key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit changes a bit the behaviour of the parser, which is now unable to properly detect missing colon for class constants or enum cases filters — for instance, in `SomeClass:SOME_CONSTANT`, the missing colon was previously detected, now it won't be. This is intentional, because this behaviour would limit the parser's ability to properly parse shaped array keys containing class/enum name. Because this was not such an important feature of the parser, there will be no replacement for it and instead will fix the issue described above. --- .../Constant/MissingClassConstantColon.php | 27 ------------ .../Exception/Enum/MissingEnumColon.php | 28 ------------ src/Type/Parser/Lexer/NativeLexer.php | 2 + .../Parser/Lexer/Token/ClassNameToken.php | 26 +++-------- .../Parser/Lexer/Token/DoubleColonToken.php | 18 ++++++++ src/Type/Parser/Lexer/Token/EnumNameToken.php | 26 +++-------- src/Type/Parser/LexingParser.php | 2 +- tests/Fixture/Enum/EnumAtRootNamespace.php | 11 +++++ .../Type/Parser/Lexer/NativeLexerTest.php | 44 ------------------- .../Object/ShapedArrayValuesMappingTest.php | 38 ++++++++++++++-- 10 files changed, 76 insertions(+), 146 deletions(-) delete mode 100644 src/Type/Parser/Exception/Constant/MissingClassConstantColon.php delete mode 100644 src/Type/Parser/Exception/Enum/MissingEnumColon.php create mode 100644 src/Type/Parser/Lexer/Token/DoubleColonToken.php create mode 100644 tests/Fixture/Enum/EnumAtRootNamespace.php diff --git a/src/Type/Parser/Exception/Constant/MissingClassConstantColon.php b/src/Type/Parser/Exception/Constant/MissingClassConstantColon.php deleted file mode 100644 index 6838823c..00000000 --- a/src/Type/Parser/Exception/Constant/MissingClassConstantColon.php +++ /dev/null @@ -1,27 +0,0 @@ - $enumName - */ - public function __construct(string $enumName, string $case) - { - if ($case === ':') { - $case = '?'; - } - - parent::__construct( - "Missing second colon symbol for enum `$enumName::$case`.", - 1653468435 - ); - } -} diff --git a/src/Type/Parser/Lexer/NativeLexer.php b/src/Type/Parser/Lexer/NativeLexer.php index 9111ecda..f319f356 100644 --- a/src/Type/Parser/Lexer/NativeLexer.php +++ b/src/Type/Parser/Lexer/NativeLexer.php @@ -12,6 +12,7 @@ use CuyZ\Valinor\Type\Parser\Lexer\Token\ClosingSquareBracketToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\ColonToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\CommaToken; +use CuyZ\Valinor\Type\Parser\Lexer\Token\DoubleColonToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\EnumNameToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\FloatValueToken; use CuyZ\Valinor\Type\Parser\Lexer\Token\IntegerToken; @@ -53,6 +54,7 @@ public function tokenize(string $symbol): Token ']' => ClosingSquareBracketToken::get(), '{' => OpeningCurlyBracketToken::get(), '}' => ClosingCurlyBracketToken::get(), + '::' => DoubleColonToken::get(), ':' => ColonToken::get(), '?' => NullableToken::get(), ',' => CommaToken::get(), diff --git a/src/Type/Parser/Lexer/Token/ClassNameToken.php b/src/Type/Parser/Lexer/Token/ClassNameToken.php index d95a5be3..f3be9269 100644 --- a/src/Type/Parser/Lexer/Token/ClassNameToken.php +++ b/src/Type/Parser/Lexer/Token/ClassNameToken.php @@ -6,7 +6,6 @@ use CuyZ\Valinor\Type\Parser\Exception\Constant\ClassConstantCaseNotFound; use CuyZ\Valinor\Type\Parser\Exception\Constant\MissingClassConstantCase; -use CuyZ\Valinor\Type\Parser\Exception\Constant\MissingClassConstantColon; use CuyZ\Valinor\Type\Parser\Exception\Constant\MissingSpecificClassConstantCase; use CuyZ\Valinor\Type\Parser\Lexer\TokenStream; use CuyZ\Valinor\Type\Type; @@ -58,37 +57,22 @@ public function symbol(): string private function classConstant(TokenStream $stream): ?Type { - if ($stream->done() || ! $stream->next() instanceof ColonToken) { + if ($stream->done() || ! $stream->next() instanceof DoubleColonToken) { return null; } - $case = $stream->forward(); - $missingColon = true; + $stream->forward(); - if (! $stream->done()) { - $case = $stream->forward(); - - $missingColon = ! $case instanceof ColonToken; - } - - if (! $missingColon) { - if ($stream->done()) { - throw new MissingClassConstantCase($this->reflection->name); - } - - $case = $stream->forward(); + if ($stream->done()) { + throw new MissingClassConstantCase($this->reflection->name); } - $symbol = $case->symbol(); + $symbol = $stream->forward()->symbol(); if ($symbol === '*') { throw new MissingSpecificClassConstantCase($this->reflection->name); } - if ($missingColon) { - throw new MissingClassConstantColon($this->reflection->name, $symbol); - } - $cases = []; if (! preg_match('/\*\s*\*/', $symbol)) { diff --git a/src/Type/Parser/Lexer/Token/DoubleColonToken.php b/src/Type/Parser/Lexer/Token/DoubleColonToken.php new file mode 100644 index 00000000..a38a2eaa --- /dev/null +++ b/src/Type/Parser/Lexer/Token/DoubleColonToken.php @@ -0,0 +1,18 @@ +done() || ! $stream->next() instanceof ColonToken) { + if ($stream->done() || ! $stream->next() instanceof DoubleColonToken) { return null; } - $case = $stream->forward(); - $missingColon = true; + $stream->forward(); - if (! $stream->done()) { - $case = $stream->forward(); - - $missingColon = ! $case instanceof ColonToken; - } - - if (! $missingColon) { - if ($stream->done()) { - throw new MissingEnumCase($this->enumName); - } - - $case = $stream->forward(); + if ($stream->done()) { + throw new MissingEnumCase($this->enumName); } - $symbol = $case->symbol(); + $symbol = $stream->forward()->symbol(); if ($symbol === '*') { throw new MissingSpecificEnumCase($this->enumName); } - if ($missingColon) { - throw new MissingEnumColon($this->enumName, $symbol); - } - return EnumType::fromPattern($this->enumName, $symbol); } diff --git a/src/Type/Parser/LexingParser.php b/src/Type/Parser/LexingParser.php index a1a8379c..8eee6fcd 100644 --- a/src/Type/Parser/LexingParser.php +++ b/src/Type/Parser/LexingParser.php @@ -42,7 +42,7 @@ private function splitTokens(string $raw): array } /** @phpstan-ignore-next-line */ - return preg_split('/([\s?|&<>,\[\]{}:\'"])/', $raw, -1, PREG_SPLIT_DELIM_CAPTURE); + return preg_split('/(::|[\s?|&<>,\[\]{}:\'"])/', $raw, -1, PREG_SPLIT_DELIM_CAPTURE); } /** diff --git a/tests/Fixture/Enum/EnumAtRootNamespace.php b/tests/Fixture/Enum/EnumAtRootNamespace.php new file mode 100644 index 00000000..35be6f28 --- /dev/null +++ b/tests/Fixture/Enum/EnumAtRootNamespace.php @@ -0,0 +1,11 @@ +parser->parse(PureEnum::class . '::*'); } - /** - * @requires PHP >= 8.1 - */ - public function test_missing_enum_colon_and_case_throws_exception(): void - { - $this->expectException(MissingEnumColon::class); - $this->expectExceptionCode(1653468435); - $this->expectExceptionMessage('Missing second colon symbol for enum `' . PureEnum::class . '::?`.'); - - $this->parser->parse(PureEnum::class . ':'); - } - - /** - * @requires PHP >= 8.1 - */ - public function test_missing_enum_colon_throws_exception(): void - { - $this->expectException(MissingEnumColon::class); - $this->expectExceptionCode(1653468435); - $this->expectExceptionMessage('Missing second colon symbol for enum `' . PureEnum::class . '::FOO`.'); - - $this->parser->parse(PureEnum::class . ':FOO'); - } - public function test_missing_class_constant_case_throws_exception(): void { $this->expectException(MissingClassConstantCase::class); @@ -1439,22 +1413,4 @@ public function test_missing_specific_class_constant_case_throws_exception(): vo $this->parser->parse(ObjectWithConstants::className() . '::*'); } - - public function test_missing_class_constant_colon_and_case_throws_exception(): void - { - $this->expectException(MissingClassConstantColon::class); - $this->expectExceptionCode(1652189143); - $this->expectExceptionMessage('Missing second colon symbol for class constant `' . ObjectWithConstants::className() . '::?`.'); - - $this->parser->parse(ObjectWithConstants::className() . ':'); - } - - public function test_missing_class_constant_colon_throws_exception(): void - { - $this->expectException(MissingClassConstantColon::class); - $this->expectExceptionCode(1652189143); - $this->expectExceptionMessage('Missing second colon symbol for class constant `' . ObjectWithConstants::className() . '::FOO`.'); - - $this->parser->parse(ObjectWithConstants::className() . ':FOO'); - } } diff --git a/tests/Integration/Mapping/Object/ShapedArrayValuesMappingTest.php b/tests/Integration/Mapping/Object/ShapedArrayValuesMappingTest.php index 5ca37ce1..9cad0db9 100644 --- a/tests/Integration/Mapping/Object/ShapedArrayValuesMappingTest.php +++ b/tests/Integration/Mapping/Object/ShapedArrayValuesMappingTest.php @@ -41,8 +41,17 @@ public function test_values_are_mapped_properly(): void 42.404, ], 'shapedArrayWithClassNameAsKey' => [ + 'stdClass' => 'foo', + ], + 'shapedArrayWithLowercaseClassNameAsKey' => [ 'stdclass' => 'foo', ], + 'shapedArrayWithEnumNameAsKey' => [ + 'EnumAtRootNamespace' => 'foo', + ], + 'shapedArrayWithLowercaseEnumNameAsKey' => [ + 'enumatrootnamespace' => 'foo', + ], ]; foreach ([ShapedArrayValues::class, ShapedArrayValuesWithConstructor::class] as $class) { @@ -61,7 +70,10 @@ public function test_values_are_mapped_properly(): void self::assertSame('bar', $result->advancedShapedArray['mandatoryString']); self::assertSame(1337, $result->advancedShapedArray[0]); self::assertSame(42.404, $result->advancedShapedArray[1]); - self::assertSame('foo', $result->shapedArrayWithClassNameAsKey['stdclass']); + self::assertSame('foo', $result->shapedArrayWithClassNameAsKey['stdClass']); + self::assertSame('foo', $result->shapedArrayWithLowercaseClassNameAsKey['stdclass']); + self::assertSame('foo', $result->shapedArrayWithEnumNameAsKey['EnumAtRootNamespace']); + self::assertSame('foo', $result->shapedArrayWithLowercaseEnumNameAsKey['enumatrootnamespace']); } } @@ -115,8 +127,17 @@ class ShapedArrayValues /** @var array{0: int, float, optionalString?: string, mandatoryString: string} */ public array $advancedShapedArray; - /** @var array{stdclass: string} */ + /** @var array{stdClass: string} */ public array $shapedArrayWithClassNameAsKey; + + /** @var array{stdclass: string} */ + public array $shapedArrayWithLowercaseClassNameAsKey; + + /** @var array{EnumAtRootNamespace: string} */ + public array $shapedArrayWithEnumNameAsKey; + + /** @var array{enumatrootnamespace: string} */ + public array $shapedArrayWithLowercaseEnumNameAsKey; } class ShapedArrayValuesWithConstructor extends ShapedArrayValues @@ -135,7 +156,10 @@ class ShapedArrayValuesWithConstructor extends ShapedArrayValues * bar: int, * } $shapedArrayOnSeveralLinesWithTrailingComma * @param array{0: int, float, optionalString?: string, mandatoryString: string} $advancedShapedArray - * @param array{stdclass: string} $shapedArrayWithClassNameAsKey + * @param array{stdClass: string} $shapedArrayWithClassNameAsKey + * @param array{stdclass: string} $shapedArrayWithLowercaseClassNameAsKey + * @param array{EnumAtRootNamespace: string} $shapedArrayWithEnumNameAsKey + * @param array{enumatrootnamespace: string} $shapedArrayWithLowercaseEnumNameAsKey */ public function __construct( array $basicShapedArrayWithStringKeys, @@ -145,7 +169,10 @@ public function __construct( array $shapedArrayOnSeveralLines, array $shapedArrayOnSeveralLinesWithTrailingComma, array $advancedShapedArray, - array $shapedArrayWithClassNameAsKey + array $shapedArrayWithClassNameAsKey, + array $shapedArrayWithLowercaseClassNameAsKey, + array $shapedArrayWithEnumNameAsKey, + array $shapedArrayWithLowercaseEnumNameAsKey, ) { $this->basicShapedArrayWithStringKeys = $basicShapedArrayWithStringKeys; $this->basicShapedArrayWithIntegerKeys = $basicShapedArrayWithIntegerKeys; @@ -155,5 +182,8 @@ public function __construct( $this->shapedArrayOnSeveralLinesWithTrailingComma = $shapedArrayOnSeveralLinesWithTrailingComma; $this->advancedShapedArray = $advancedShapedArray; $this->shapedArrayWithClassNameAsKey = $shapedArrayWithClassNameAsKey; + $this->shapedArrayWithLowercaseClassNameAsKey = $shapedArrayWithLowercaseClassNameAsKey; + $this->shapedArrayWithEnumNameAsKey = $shapedArrayWithEnumNameAsKey; + $this->shapedArrayWithLowercaseEnumNameAsKey = $shapedArrayWithLowercaseEnumNameAsKey; } }