forked from CuyZ/Valinor
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: strengthen type tokens extraction
This commit refactors the way tokens are extracted from a raw type string, by using a better approach that consists in splitting first into tokens, and then detecting texts (aka string values). This aims to fix edge cases like the following example, where the `$bar` annotation would previously have confused the lexer, leading to the type of `$foo` being used for `$bar` as well. ```php final class SomeClass { /** * @param non-empty-string $foo Some description containing $bar * which is the next parameter name */ public function __construct( public string $foo, public int $bar, ) {} } ```
- Loading branch information
Showing
5 changed files
with
138 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
<?php | ||
|
||
namespace CuyZ\Valinor\Type\Parser\Lexer; | ||
|
||
use function array_map; | ||
use function array_shift; | ||
use function implode; | ||
use function preg_split; | ||
|
||
/** @internal */ | ||
final class TokensExtractor | ||
{ | ||
private const TOKEN_PATTERNS = [ | ||
'Anonymous class' => '[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++', | ||
'Double colons' => '\:\:', | ||
'Triple dots' => '\.\.\.', | ||
'Dollar sign' => '\$', | ||
'Whitespace' => '\s', | ||
'Union' => '\|', | ||
'Intersection' => '&', | ||
'Opening bracket' => '\<', | ||
'Closing bracket' => '\>', | ||
'Opening square bracket' => '\[', | ||
'Closing square bracket' => '\]', | ||
'Opening curly bracket' => '\{', | ||
'Closing curly bracket' => '\}', | ||
'Colon' => '\:', | ||
'Question mark' => '\?', | ||
'Comma' => ',', | ||
'Single quote' => "'", | ||
'Double quote' => '"', | ||
]; | ||
|
||
/** @var list<string> */ | ||
private array $symbols = []; | ||
|
||
public function __construct(string $string) | ||
{ | ||
$pattern = '/(' . implode('|', self::TOKEN_PATTERNS) . ')' . '/'; | ||
$tokens = preg_split($pattern, $string, flags: PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); | ||
|
||
$quote = null; | ||
$text = null; | ||
|
||
while (($token = array_shift($tokens)) !== null) { | ||
if ($token === $quote) { | ||
if ($text !== null) { | ||
$this->symbols[] = $text; | ||
} | ||
|
||
$this->symbols[] = $token; | ||
|
||
$text = null; | ||
$quote = null; | ||
} elseif ($quote !== null) { | ||
$text .= $token; | ||
} elseif ($token === '"' || $token === "'") { | ||
$quote = $token; | ||
|
||
$this->symbols[] = $token; | ||
} else { | ||
$this->symbols[] = $token; | ||
} | ||
} | ||
|
||
if ($text !== null) { | ||
$this->symbols[] = $text; | ||
} | ||
|
||
$this->symbols = array_map('trim', $this->symbols); | ||
$this->symbols = array_filter($this->symbols, static fn ($value) => $value !== ''); | ||
$this->symbols = array_values($this->symbols); | ||
} | ||
|
||
/** | ||
* @return list<string> | ||
*/ | ||
public function all(): array | ||
{ | ||
return $this->symbols; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
tests/Integration/Mapping/DocBlockParameterWithDescriptionTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace CuyZ\Valinor\Tests\Integration\Mapping; | ||
|
||
use CuyZ\Valinor\Tests\Integration\IntegrationTestCase; | ||
|
||
final class DocBlockParameterWithDescriptionTest extends IntegrationTestCase | ||
{ | ||
public function test_parameter_doc_block_description_containing_name_of_other_parameter_is_parsed_properly(): void | ||
{ | ||
$class = new class ('foo', 42) { | ||
/** | ||
* @param non-empty-string $foo Some description containing $bar | ||
* which is the next parameter name | ||
*/ | ||
public function __construct( | ||
public string $foo, | ||
public int $bar, | ||
) {} | ||
}; | ||
|
||
$result = $this->mapperBuilder()->mapper()->map($class::class, [ | ||
'foo' => 'foo', | ||
'bar' => 42, | ||
]); | ||
|
||
self::assertSame('foo', $result->foo); | ||
self::assertSame(42, $result->bar); | ||
} | ||
} |