Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: properly handle class/enum name in shaped array key #399

Merged
merged 1 commit into from
Aug 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 0 additions & 27 deletions src/Type/Parser/Exception/Constant/MissingClassConstantColon.php

This file was deleted.

28 changes: 0 additions & 28 deletions src/Type/Parser/Exception/Enum/MissingEnumColon.php

This file was deleted.

2 changes: 2 additions & 0 deletions src/Type/Parser/Lexer/NativeLexer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -53,6 +54,7 @@ public function tokenize(string $symbol): Token
']' => ClosingSquareBracketToken::get(),
'{' => OpeningCurlyBracketToken::get(),
'}' => ClosingCurlyBracketToken::get(),
'::' => DoubleColonToken::get(),
':' => ColonToken::get(),
'?' => NullableToken::get(),
',' => CommaToken::get(),
Expand Down
26 changes: 5 additions & 21 deletions src/Type/Parser/Lexer/Token/ClassNameToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)) {
Expand Down
18 changes: 18 additions & 0 deletions src/Type/Parser/Lexer/Token/DoubleColonToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Type\Parser\Lexer\Token;

use CuyZ\Valinor\Utility\IsSingleton;

/** @internal */
final class DoubleColonToken implements Token
{
use IsSingleton;

public function symbol(): string
{
return '::';
}
}
26 changes: 5 additions & 21 deletions src/Type/Parser/Lexer/Token/EnumNameToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace CuyZ\Valinor\Type\Parser\Lexer\Token;

use CuyZ\Valinor\Type\Parser\Exception\Enum\MissingEnumCase;
use CuyZ\Valinor\Type\Parser\Exception\Enum\MissingEnumColon;
use CuyZ\Valinor\Type\Parser\Exception\Enum\MissingSpecificEnumCase;
use CuyZ\Valinor\Type\Parser\Lexer\TokenStream;
use CuyZ\Valinor\Type\Type;
Expand All @@ -28,37 +27,22 @@ public function traverse(TokenStream $stream): Type

private function findPatternEnumType(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 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);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Type/Parser/LexingParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down
11 changes: 11 additions & 0 deletions tests/Fixture/Enum/EnumAtRootNamespace.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

/** @internal */
enum EnumAtRootNamespace
{
case FOO;
case BAR;
case BAZ;
}
44 changes: 0 additions & 44 deletions tests/Functional/Type/Parser/Lexer/NativeLexerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@
use CuyZ\Valinor\Type\IntegerType;
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\Exception\Enum\EnumCaseNotFound;
use CuyZ\Valinor\Type\Parser\Exception\Enum\MissingEnumCase;
use CuyZ\Valinor\Type\Parser\Exception\Enum\MissingEnumColon;
use CuyZ\Valinor\Type\Parser\Exception\Enum\MissingSpecificEnumCase;
use CuyZ\Valinor\Type\Parser\Exception\InvalidIntersectionType;
use CuyZ\Valinor\Type\Parser\Exception\Iterable\ArrayClosingBracketMissing;
Expand Down Expand Up @@ -1371,30 +1369,6 @@ public function test_missing_specific_enum_case_throws_exception(): void
$this->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);
Expand Down Expand Up @@ -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');
}
}
38 changes: 34 additions & 4 deletions tests/Integration/Mapping/Object/ShapedArrayValuesMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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']);
}
}

Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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;
Expand All @@ -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;
}
}