Skip to content

Commit

Permalink
Add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
julienfalque committed Oct 15, 2021
1 parent bef35f4 commit a19f9ad
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 11 deletions.
59 changes: 48 additions & 11 deletions src/DocBlock/TypeExpression.php
Expand Up @@ -32,14 +32,16 @@ final class TypeExpression
public const REGEX_TYPES = '
(?<types> # alternation of several types separated by `|`
(?<type> # single type
\?? # optionally nullable
(?<nullable>\??)
(?:
(?<object_like_array>
array\h*\{
(?<object_like_array_key>
\h*[^?:\h]+\h*\??\h*:\h*(?&types)
(?<object_like_array_start>array\h*\{)
(?<object_like_array_keys>
(?<object_like_array_key>
\h*[^?:\h]+\h*\??\h*:\h*(?&types)
)
(?:\h*,(?&object_like_array_key))*
)
(?:\h*,(?&object_like_array_key))*
\h*\}
)
|
Expand All @@ -51,7 +53,7 @@ final class TypeExpression
\h*,\h*
(?&types)
)*
)
)?
\h*\)
(?:
\h*\:\h*
Expand Down Expand Up @@ -256,10 +258,11 @@ private function parse(): void
return;
}

$index = '' !== $matches['nullable'] ? 1 : 0;

if ($matches['type'] !== $matches['types']) {
$this->isUnionType = true;

$index = 0;
while (true) {
$innerType = $matches['type'];

Expand Down Expand Up @@ -291,7 +294,7 @@ private function parse(): void

if ('' !== ($matches['generic'] ?? '')) {
$this->parseCommaSeparatedInnerTypes(
\strlen($matches['generic_start']),
$index + \strlen($matches['generic_start']),
$matches['generic_types']
);

Expand All @@ -300,17 +303,26 @@ private function parse(): void

if ('' !== ($matches['callable'] ?? '')) {
$this->parseCommaSeparatedInnerTypes(
\strlen($matches['callable_start']),
$index + \strlen($matches['callable_start']),
$matches['callable_arguments']
);

$return = $matches['callable_return'] ?? null;
if (null !== $return) {
$this->innerTypeExpressions[] = [
'start_index' => \strlen($this->value) - \strlen($matches['callable_return']),
'expression' => new self($matches['callable_return'], $this->namespace, $this->namespaceUses),
'expression' => $this->inner($matches['callable_return']),
];
}

return;
}

if ('' !== ($matches['object_like_array'] ?? '')) {
$this->parseObjectLikeArrayKeys(
$index + \strlen($matches['object_like_array_start']),
$matches['object_like_array_keys']
);
}
}

Expand All @@ -325,7 +337,7 @@ private function parseCommaSeparatedInnerTypes(int $startIndex, string $value):

$this->innerTypeExpressions[] = [
'start_index' => $startIndex,
'expression' => new self($matches['types'], $this->namespace, $this->namespaceUses),
'expression' => $this->inner($matches['types']),
];

$newValue = Preg::replace(
Expand All @@ -339,6 +351,31 @@ private function parseCommaSeparatedInnerTypes(int $startIndex, string $value):
}
}

private function parseObjectLikeArrayKeys(int $startIndex, string $value): void
{
while ('' !== $value) {
Preg::match(
'{(?<_start>^.+?:\h*)'.self::REGEX_TYPES.'\h*(?:,|$)}x',
$value,
$matches
);

$this->innerTypeExpressions[] = [
'start_index' => $startIndex + \strlen($matches['_start']),
'expression' => $this->inner($matches['types']),
];

$newValue = Preg::replace(
'/^.+?:\h*'.preg_quote($matches['types'], '/').'(\h*\,\h*)?/',
'',
$value
);

$startIndex += \strlen($value) - \strlen($newValue);
$value = $newValue;
}
}

private function inner(string $value): self
{
return new self($value, $this->namespace, $this->namespaceUses);
Expand Down
86 changes: 86 additions & 0 deletions tests/DocBlock/TypeExpressionTest.php
Expand Up @@ -179,4 +179,90 @@ public function provideAllowsNullCases(): \Generator
yield ['bool', false];
yield ['string', false];
}

/**
* @dataProvider provideSortUnionTypesCases
*/
public function testSortUnionTypes(string $typesExpression, string $expectResult): void
{
$expression = new TypeExpression($typesExpression, null, []);

$expression->sortUnionTypes(static function (TypeExpression $a, TypeExpression $b): int {
return strcasecmp($a->toString(), $b->toString());
});

static::assertSame($expectResult, $expression->toString());
}

public function provideSortUnionTypesCases(): iterable
{
yield 'not a union type' => [
'int',
'int',
];
yield 'simple' => [
'int|bool',
'bool|int',
];
yield 'simple in generic' => [
'array<int|bool>',
'array<bool|int>',
];
yield 'generic with multiple types' => [
'array<int|bool, string|float>',
'array<bool|int, float|string>',
];
yield 'simple in array shape with int key' => [
'array{0: int|bool}',
'array{0: bool|int}',
];
yield 'simple in array shape with string key' => [
'array{"foo": int|bool}',
'array{"foo": bool|int}',
];
yield 'simple in array shape with multiple keys' => [
'array{0: int|bool, "foo": int|bool}',
'array{0: bool|int, "foo": bool|int}',
];
yield 'simple in callable argument' => [
'callable(int|bool)',
'callable(bool|int)',
];
yield 'callable with multiple arguments' => [
'callable(int|bool, null|array)',
'callable(bool|int, array|null)',
];
yield 'simple in callable return type' => [
'callable(): string|float',
'callable(): float|string',
];
yield 'simple in closure argument' => [
'Closure(int|bool)',
'Closure(bool|int)',
];
yield 'closure with multiple arguments' => [
'Closure(int|bool, null|array)',
'Closure(bool|int, array|null)',
];
yield 'simple in closure return type' => [
'Closure(): string|float',
'Closure(): float|string',
];
yield 'with multiple nesting levels' => [
'array{0: Foo<int|bool>|Bar<callable(string|float|array<int|bool>): Foo|Bar>}',
'array{0: Bar<callable(array<bool|int>|float|string): Bar|Foo>|Foo<bool|int>}',
];
yield 'nullable generic' => [
'?array<Foo|Bar>',
'?array<Bar|Foo>',
];
yield 'nullable callable' => [
'?callable(Foo|Bar): Foo|Bar',
'?callable(Bar|Foo): Bar|Foo',
];
yield 'nullable array shape' => [
'?array{0: Foo|Bar}',
'?array{0: Bar|Foo}',
];
}
}
3 changes: 3 additions & 0 deletions tests/Fixer/Phpdoc/PhpdocTypesOrderFixerTest.php
Expand Up @@ -641,6 +641,9 @@ public function provideFixWithAlphaAlgorithmAndNullAlwaysLastCases(): array
'<?php /** @return array<int, callable(array<string, string|null> , DateTime): bool> */',
'<?php /** @return array<int, callable(array<string, null|string> , DateTime): bool> */',
],
[
'<?php /** @var ?Deferred<TestLocations> */',
],
];
}
}

0 comments on commit a19f9ad

Please sign in to comment.