Skip to content

Commit

Permalink
feat: handle multiline type declaration
Browse files Browse the repository at this point in the history
The following type will now be handled:

```php
/**
 * @var array{
 *     foo: string,
 *     bar: int
 * }
 */
public array $foo;
```
  • Loading branch information
romm committed Dec 1, 2021
1 parent dd4624c commit d99c59d
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 80 deletions.
18 changes: 0 additions & 18 deletions src/Type/Parser/Exception/ImpossibleParsing.php

This file was deleted.

11 changes: 1 addition & 10 deletions src/Type/Parser/LexingParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace CuyZ\Valinor\Type\Parser;

use CuyZ\Valinor\Type\Parser\Exception\ImpossibleParsing;
use CuyZ\Valinor\Type\Parser\Lexer\TokenStream;
use CuyZ\Valinor\Type\Parser\Lexer\TypeLexer;
use CuyZ\Valinor\Type\Type;
Expand Down Expand Up @@ -34,15 +33,7 @@ public function parse(string $raw): Type
$symbols
);

$stream = new TokenStream(...$tokens);

$type = $stream->read();

if (! $stream->done()) {
throw new ImpossibleParsing($raw);
}

return $type;
return (new TokenStream(...$tokens))->read();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Type/Parser/Template/BasicTemplateParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public function templates(string $source, TypeParser $typeParser): array
{
$templates = [];

preg_match_all('/@(phpstan-|psalm-)?template\s+(\w+)(\s+of\s+([\w\s|&<>,-\[\]\\\\]+))?/m', $source, $raw);
preg_match_all('/@(phpstan-|psalm-)?template\s+(\w+)(\s+of\s+([\w\s|&<>\'",-\[\]\\\\]+))?/', $source, $raw);

/** @var string[] $list */
$list = $raw[2];
Expand Down
8 changes: 7 additions & 1 deletion src/Utility/Reflection/Reflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use function get_class;
use function implode;
use function preg_match;
use function preg_replace;
use function trim;

final class Reflection
Expand Down Expand Up @@ -95,9 +96,14 @@ public static function docBlockType(Reflector $reflection): ?string
$regex = '@var\s+([\w\s?|&<>\'",-\[\]{}:\\\\]+)';
} else {
$docComment = $reflection->getDeclaringFunction()->getDocComment() ?: '';
$regex = "@param\s+([\w\s?|&<>'\",-\[\]{}:\\\\]+)\s+\\$$reflection->name\s+";
$regex = "@param\s+([\w\s?|&<>'\",-\[\]{}:\\\\]+)\s+\\$$reflection->name(\s+|$)";
}

/** @var string $docComment */
$docComment = preg_replace('#^\s*/\*\*([^/]+)/\s*$#', '$1', $docComment);
$docComment = preg_replace('/\s*\*([^\s]*)/', '$1', $docComment);

/** @var string $docComment */
if (! preg_match("/$regex/", $docComment, $matches)) {
return null;
}
Expand Down
10 changes: 0 additions & 10 deletions tests/Functional/Type/Parser/Lexer/NativeLexerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use CuyZ\Valinor\Tests\Fixture\Object\AbstractObject;
use CuyZ\Valinor\Type\IntegerType;
use CuyZ\Valinor\Type\Parser\Exception\ClassStringClosingBracketMissing;
use CuyZ\Valinor\Type\Parser\Exception\ImpossibleParsing;
use CuyZ\Valinor\Type\Parser\Exception\InvalidClassStringSubType;
use CuyZ\Valinor\Type\Parser\Exception\InvalidIntersectionType;
use CuyZ\Valinor\Type\Parser\Exception\Iterable\ArrayClosingBracketMissing;
Expand Down Expand Up @@ -512,15 +511,6 @@ public function test_missing_right_intersection_type_throws_exception(): void
$this->parser->parse('DateTimeInterface&');
}

public function test_type_that_cannot_be_parsed_throws_exception(): void
{
$this->expectException(ImpossibleParsing::class);
$this->expectExceptionCode(1585373891);
$this->expectExceptionMessage('The type `int float` could not be parsed.');

$this->parser->parse('int float');
}

public function test_missing_simple_array_closing_bracket_throws_exception(): void
{
$this->expectException(SimpleArrayClosingBracketMissing::class);
Expand Down
164 changes: 125 additions & 39 deletions tests/Integration/Mapping/Type/GenericValuesMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,27 @@ public function test_values_are_mapped_properly(): void
'genericWithBoolean' => ['value' => true],
'genericWithFloat' => ['value' => 42.404],
'genericWithInteger' => ['value' => 1337],
'genericWithIntegerValue' => ['value' => 1337],
'genericWithString' => ['value' => 'foo'],
'genericWithSingleQuoteStringValue' => ['value' => 'foo'],
'genericWithDoubleQuoteStringValue' => ['value' => 'foo'],
'genericWithArrayOfStrings' => ['value' => ['foo', 'bar', 'baz']],
'genericWithSimpleArrayOfStrings' => ['value' => ['foo', 'bar', 'baz']],
'genericWithUnionOfScalar' => ['value' => 'foo'],
'genericWithObject' => ['value' => ['value' => 'foo']],
'genericWithObjectAlias' => ['value' => ['value' => 'foo']],
'genericWithTwoTemplates' => [
'valueA' => 'foo',
'valueB' => 42,
],
'genericWithTwoTemplatesOnSeveralLines' => [
'valueA' => 'foo',
'valueB' => 42,
],
'genericWithSpecifiedTypeWithString' => ['value' => 'foo'],
'genericWithSpecifiedTypeWithObject' => ['value' => ['value' => 'foo']],
'genericWithSpecifiedTypeWithIntegerValue' => ['value' => 42],
'genericWithSpecifiedTypeWithStringValue' => ['value' => 'foo'],
];

foreach ([GenericValues::class, GenericValuesWithConstructor::class] as $class) {
Expand All @@ -37,29 +50,52 @@ public function test_values_are_mapped_properly(): void
self::assertSame(true, $result->genericWithBoolean->value);
self::assertSame(42.404, $result->genericWithFloat->value);
self::assertSame(1337, $result->genericWithInteger->value);
self::assertSame(1337, $result->genericWithIntegerValue->value);
self::assertSame('foo', $result->genericWithString->value);
self::assertSame('foo', $result->genericWithSingleQuoteStringValue->value);
self::assertSame('foo', $result->genericWithDoubleQuoteStringValue->value);
self::assertSame(['foo', 'bar', 'baz'], $result->genericWithArrayOfStrings->value);
self::assertSame(['foo', 'bar', 'baz'], $result->genericWithSimpleArrayOfStrings->value);
self::assertSame('foo', $result->genericWithUnionOfScalar->value);
self::assertSame('foo', $result->genericWithObject->value->value);
self::assertSame('foo', $result->genericWithObjectAlias->value->value);
self::assertSame('foo', $result->genericWithTwoTemplates->valueA);
self::assertSame(42, $result->genericWithTwoTemplates->valueB);
self::assertSame('foo', $result->genericWithTwoTemplatesOnSeveralLines->valueA);
self::assertSame(42, $result->genericWithTwoTemplatesOnSeveralLines->valueB);
self::assertSame('foo', $result->genericWithSpecifiedTypeWithString->value);
self::assertSame('foo', $result->genericWithSpecifiedTypeWithObject->value->value);
self::assertSame(42, $result->genericWithSpecifiedTypeWithIntegerValue->value);
self::assertSame('foo', $result->genericWithSpecifiedTypeWithStringValue->value);
}
}
}

/**
* @template T
*/
final class GenericObject
final class GenericObjectWithOneTemplate
{
/** @var T */
public $value;
}

/**
* @template T of string|object
* @template TemplateA
* @template TemplateB
*/
final class GenericObjectWithTwoTemplates
{
/** @var TemplateA */
public $valueA;

/** @var TemplateB */
public $valueB;
}

/**
* @phpstan-ignore-next-line
* @template T of string|object|42|'foo'
*/
final class GenericObjectWithSpecifiedType
{
Expand All @@ -69,78 +105,128 @@ final class GenericObjectWithSpecifiedType

class GenericValues
{
/** @var GenericObject<bool> */
public GenericObject $genericWithBoolean;
/** @var GenericObjectWithOneTemplate<bool> */
public GenericObjectWithOneTemplate $genericWithBoolean;

/** @var GenericObjectWithOneTemplate<float> */
public GenericObjectWithOneTemplate $genericWithFloat;

/** @var GenericObjectWithOneTemplate<int> */
public GenericObjectWithOneTemplate $genericWithInteger;

/** @var GenericObject<float> */
public GenericObject $genericWithFloat;
/** @var GenericObjectWithOneTemplate<1337> */
public GenericObjectWithOneTemplate $genericWithIntegerValue;

/** @var GenericObject<int> */
public GenericObject $genericWithInteger;
/** @var GenericObjectWithOneTemplate<string> */
public GenericObjectWithOneTemplate $genericWithString;

/** @var GenericObject<string> */
public GenericObject $genericWithString;
/** @var GenericObjectWithOneTemplate<'foo'> */
public GenericObjectWithOneTemplate $genericWithSingleQuoteStringValue;

/** @var GenericObject<array<string>> */
public GenericObject $genericWithArrayOfStrings;
/** @var GenericObjectWithOneTemplate<"foo"> */
public GenericObjectWithOneTemplate $genericWithDoubleQuoteStringValue;

/** @var GenericObject<string[]> */
public GenericObject $genericWithSimpleArrayOfStrings;
/** @var GenericObjectWithOneTemplate<array<string>> */
public GenericObjectWithOneTemplate $genericWithArrayOfStrings;

/** @var GenericObject<bool|float|int|string> */
public GenericObject $genericWithUnionOfScalar;
/** @var GenericObjectWithOneTemplate<string[]> */
public GenericObjectWithOneTemplate $genericWithSimpleArrayOfStrings;

/** @var GenericObject<SimpleObject> */
public GenericObject $genericWithObject;
/** @var GenericObjectWithOneTemplate<bool|float|int|string> */
public GenericObjectWithOneTemplate $genericWithUnionOfScalar;

/** @var GenericObject<SimpleObjectAlias> */
public GenericObject $genericWithObjectAlias;
/** @var GenericObjectWithOneTemplate<SimpleObject> */
public GenericObjectWithOneTemplate $genericWithObject;

/** @var GenericObjectWithOneTemplate<SimpleObjectAlias> */
public GenericObjectWithOneTemplate $genericWithObjectAlias;

/** @var GenericObjectWithTwoTemplates<string, int> */
public GenericObjectWithTwoTemplates $genericWithTwoTemplates;

/**
* @var GenericObjectWithTwoTemplates<
* string,
* int
* >
*/
public GenericObjectWithTwoTemplates $genericWithTwoTemplatesOnSeveralLines;

/** @var GenericObjectWithSpecifiedType<string> */
public GenericObjectWithSpecifiedType $genericWithSpecifiedTypeWithString;

/** @var GenericObjectWithSpecifiedType<SimpleObject> */
public GenericObjectWithSpecifiedType $genericWithSpecifiedTypeWithObject;

/** @var GenericObjectWithSpecifiedType<42> */
public GenericObjectWithSpecifiedType $genericWithSpecifiedTypeWithIntegerValue;

/** @var GenericObjectWithSpecifiedType<'foo'> */
public GenericObjectWithSpecifiedType $genericWithSpecifiedTypeWithStringValue;
}

class GenericValuesWithConstructor extends GenericValues
{
/**
* @param GenericObject<bool> $genericWithBoolean
* @param GenericObject<float> $genericWithFloat
* @param GenericObject<int> $genericWithInteger
* @param GenericObject<string> $genericWithString
* @param GenericObject<array<string>> $genericWithArrayOfStrings
* @param GenericObject<string[]> $genericWithSimpleArrayOfStrings
* @param GenericObject<bool|float|int|string> $genericWithUnionOfScalar
* @param GenericObject<SimpleObject> $genericWithObject
* @param GenericObject<SimpleObjectAlias> $genericWithObjectAlias
* @param GenericObjectWithOneTemplate<bool> $genericWithBoolean
* @param GenericObjectWithOneTemplate<float> $genericWithFloat
* @param GenericObjectWithOneTemplate<int> $genericWithInteger
* @param GenericObjectWithOneTemplate<1337> $genericWithIntegerValue
* @param GenericObjectWithOneTemplate<string> $genericWithString
* @param GenericObjectWithOneTemplate<'foo'> $genericWithSingleQuoteStringValue
* @param GenericObjectWithOneTemplate<"foo"> $genericWithDoubleQuoteStringValue
* @param GenericObjectWithOneTemplate<array<string>> $genericWithArrayOfStrings
* @param GenericObjectWithOneTemplate<string[]> $genericWithSimpleArrayOfStrings
* @param GenericObjectWithOneTemplate<bool|float|int|string> $genericWithUnionOfScalar
* @param GenericObjectWithOneTemplate<SimpleObject> $genericWithObject
* @param GenericObjectWithOneTemplate<SimpleObjectAlias> $genericWithObjectAlias
* @param GenericObjectWithTwoTemplates<string, int> $genericWithTwoTemplates
* @param GenericObjectWithTwoTemplates<
* string,
* int
* > $genericWithTwoTemplatesOnSeveralLines
* @param GenericObjectWithSpecifiedType<string> $genericWithSpecifiedTypeWithString
* @param GenericObjectWithSpecifiedType<SimpleObject> $genericWithSpecifiedTypeWithObject
* @param GenericObjectWithSpecifiedType<42> $genericWithSpecifiedTypeWithIntegerValue
* @param GenericObjectWithSpecifiedType<'foo'> $genericWithSpecifiedTypeWithStringValue
*/
public function __construct(
GenericObject $genericWithBoolean,
GenericObject $genericWithFloat,
GenericObject $genericWithInteger,
GenericObject $genericWithString,
GenericObject $genericWithArrayOfStrings,
GenericObject $genericWithSimpleArrayOfStrings,
GenericObject $genericWithUnionOfScalar,
GenericObject $genericWithObject,
GenericObject $genericWithObjectAlias,
GenericObjectWithOneTemplate $genericWithBoolean,
GenericObjectWithOneTemplate $genericWithFloat,
GenericObjectWithOneTemplate $genericWithInteger,
GenericObjectWithOneTemplate $genericWithIntegerValue,
GenericObjectWithOneTemplate $genericWithString,
GenericObjectWithOneTemplate $genericWithSingleQuoteStringValue,
GenericObjectWithOneTemplate $genericWithDoubleQuoteStringValue,
GenericObjectWithOneTemplate $genericWithArrayOfStrings,
GenericObjectWithOneTemplate $genericWithSimpleArrayOfStrings,
GenericObjectWithOneTemplate $genericWithUnionOfScalar,
GenericObjectWithOneTemplate $genericWithObject,
GenericObjectWithOneTemplate $genericWithObjectAlias,
GenericObjectWithTwoTemplates $genericWithTwoTemplates,
GenericObjectWithTwoTemplates $genericWithTwoTemplatesOnSeveralLines,
GenericObjectWithSpecifiedType $genericWithSpecifiedTypeWithString,
GenericObjectWithSpecifiedType $genericWithSpecifiedTypeWithObject
GenericObjectWithSpecifiedType $genericWithSpecifiedTypeWithObject,
GenericObjectWithSpecifiedType $genericWithSpecifiedTypeWithIntegerValue,
GenericObjectWithSpecifiedType $genericWithSpecifiedTypeWithStringValue
) {
$this->genericWithBoolean = $genericWithBoolean;
$this->genericWithFloat = $genericWithFloat;
$this->genericWithInteger = $genericWithInteger;
$this->genericWithIntegerValue = $genericWithIntegerValue;
$this->genericWithString = $genericWithString;
$this->genericWithSingleQuoteStringValue = $genericWithSingleQuoteStringValue;
$this->genericWithDoubleQuoteStringValue = $genericWithDoubleQuoteStringValue;
$this->genericWithArrayOfStrings = $genericWithArrayOfStrings;
$this->genericWithSimpleArrayOfStrings = $genericWithSimpleArrayOfStrings;
$this->genericWithUnionOfScalar = $genericWithUnionOfScalar;
$this->genericWithObject = $genericWithObject;
$this->genericWithObjectAlias = $genericWithObjectAlias;
$this->genericWithTwoTemplates = $genericWithTwoTemplates;
$this->genericWithTwoTemplatesOnSeveralLines = $genericWithTwoTemplatesOnSeveralLines;
$this->genericWithSpecifiedTypeWithString = $genericWithSpecifiedTypeWithString;
$this->genericWithSpecifiedTypeWithObject = $genericWithSpecifiedTypeWithObject;
$this->genericWithSpecifiedTypeWithIntegerValue = $genericWithSpecifiedTypeWithIntegerValue;
$this->genericWithSpecifiedTypeWithStringValue = $genericWithSpecifiedTypeWithStringValue;
}
}
Loading

0 comments on commit d99c59d

Please sign in to comment.