From b01ada4ba7a8cab0565717b9d7113e427b7b4fd0 Mon Sep 17 00:00:00 2001 From: Enzo Innocenzi Date: Fri, 10 Mar 2023 12:50:39 +0100 Subject: [PATCH 1/4] feat: add support for record types --- .../TranspileTypeToTypeScriptAction.php | 7 +++ src/Attributes/RecordTypeScriptType.php | 27 +++++++++++ src/Types/RecordType.php | 42 +++++++++++++++++ tests/Types/RecordTypeTest.php | 46 +++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 src/Attributes/RecordTypeScriptType.php create mode 100644 src/Types/RecordType.php create mode 100644 tests/Types/RecordTypeTest.php diff --git a/src/Actions/TranspileTypeToTypeScriptAction.php b/src/Actions/TranspileTypeToTypeScriptAction.php index 09204f7..87b2ebd 100644 --- a/src/Actions/TranspileTypeToTypeScriptAction.php +++ b/src/Actions/TranspileTypeToTypeScriptAction.php @@ -21,6 +21,7 @@ use phpDocumentor\Reflection\Types\This; use phpDocumentor\Reflection\Types\Void_; use Spatie\TypeScriptTransformer\Structures\MissingSymbolsCollection; +use Spatie\TypeScriptTransformer\Types\RecordType; use Spatie\TypeScriptTransformer\Types\StructType; use Spatie\TypeScriptTransformer\Types\TypeScriptType; @@ -46,6 +47,7 @@ public function execute(Type $type): string $type instanceof Nullable => $this->resolveNullableType($type), $type instanceof Object_ => $this->resolveObjectType($type), $type instanceof StructType => $this->resolveStructType($type), + $type instanceof RecordType => $this->resolveRecordType($type), $type instanceof TypeScriptType => (string) $type, $type instanceof Boolean => 'boolean', $type instanceof Float_, $type instanceof Integer => 'number', @@ -105,6 +107,11 @@ private function resolveStructType(StructType $type): string return "{$transformed}}"; } + private function resolveRecordType(RecordType $type): string + { + return "Record<{$this->execute($type->getKeyType())}, {$this->execute($type->getValueType())}>"; + } + private function resolveSelfReferenceType(): string { if ($this->currentClass === null) { diff --git a/src/Attributes/RecordTypeScriptType.php b/src/Attributes/RecordTypeScriptType.php new file mode 100644 index 0000000..311b830 --- /dev/null +++ b/src/Attributes/RecordTypeScriptType.php @@ -0,0 +1,27 @@ +keyType = $keyType; + $this->valueType = $valueType; + $this->array = $array; + } + + public function getType(): Type + { + return new RecordType($this->keyType, $this->valueType, $this->array); + } +} diff --git a/src/Types/RecordType.php b/src/Types/RecordType.php new file mode 100644 index 0000000..d385c5c --- /dev/null +++ b/src/Types/RecordType.php @@ -0,0 +1,42 @@ +keyType = (new TypeResolver())->resolve($keyType); + + if ($array) { + $this->valueType = new Array_((new TypeResolver())->resolve($valueType)); + } else { + $this->valueType = is_array($valueType) + ? StructType::fromArray($valueType) + : (new TypeResolver())->resolve($valueType); + } + } + + public function __toString(): string + { + return 'record'; + } + + public function getKeyType(): Type + { + return $this->keyType; + } + + public function getValueType(): Type + { + return $this->valueType; + } +} diff --git a/tests/Types/RecordTypeTest.php b/tests/Types/RecordTypeTest.php new file mode 100644 index 0000000..4231453 --- /dev/null +++ b/tests/Types/RecordTypeTest.php @@ -0,0 +1,46 @@ +getKeyType()); + assertEquals(new Object_(new Fqsen('\\'.RegularEnum::class)), $record->getValueType()); +}); + +it('creates a scalar key and an struct value', function () { + $record = new RecordType('string', [ + 'enum' => RegularEnum::class, + 'array' => 'int[]', + ]); + + assertInstanceOf(RecordType::class, $record); + assertEquals(new String_(), $record->getKeyType()); + + assertInstanceOf(StructType::class, $record->getValueType()); + assertEquals([ + 'enum' => new Object_(new Fqsen('\\'.RegularEnum::class)), + 'array' => new Array_(new Integer()), + ], $record->getValueType()->getTypes()); +}); + +it('creates a scalar key and an array value', function () { + $record = new RecordType(RegularEnum::class, BackedEnumWithoutAnnotation::class, array: true); + + assertInstanceOf(RecordType::class, $record); + assertEquals(new Object_(new Fqsen('\\'.RegularEnum::class)), $record->getKeyType()); + assertEquals(new Array_(new Object_(new Fqsen('\\'.BackedEnumWithoutAnnotation::class))), $record->getValueType()); +}); From 178fef9d90dc2bc1bfe3946443acb4e454bbcc25 Mon Sep 17 00:00:00 2001 From: innocenzi Date: Fri, 10 Mar 2023 11:51:17 +0000 Subject: [PATCH 2/4] Fix styling --- tests/Types/RecordTypeTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Types/RecordTypeTest.php b/tests/Types/RecordTypeTest.php index 4231453..776a856 100644 --- a/tests/Types/RecordTypeTest.php +++ b/tests/Types/RecordTypeTest.php @@ -5,14 +5,14 @@ use phpDocumentor\Reflection\Types\Integer; use phpDocumentor\Reflection\Types\Object_; use phpDocumentor\Reflection\Types\String_; +use function PHPUnit\Framework\assertEquals; +use function PHPUnit\Framework\assertInstanceOf; use Spatie\TypeScriptTransformer\Tests\FakeClasses\BackedEnumWithoutAnnotation; use Spatie\TypeScriptTransformer\Tests\FakeClasses\Enum\RegularEnum; + use Spatie\TypeScriptTransformer\Types\RecordType; use Spatie\TypeScriptTransformer\Types\StructType; -use function PHPUnit\Framework\assertEquals; -use function PHPUnit\Framework\assertInstanceOf; - it('creates a scalar key and an object value', function () { $record = new RecordType('string', RegularEnum::class); From 887fb16e60bae566d6d6c6864468d2a37d4252da Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Fri, 24 Mar 2023 11:16:43 +0100 Subject: [PATCH 3/4] Fix tests --- tests/Collectors/DefaultCollectorTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Collectors/DefaultCollectorTest.php b/tests/Collectors/DefaultCollectorTest.php index e3b85e7..50b4f44 100644 --- a/tests/Collectors/DefaultCollectorTest.php +++ b/tests/Collectors/DefaultCollectorTest.php @@ -175,7 +175,7 @@ it('will will throw an exception with non existing transformers', function () { $this->expectException(InvalidTransformerGiven::class); - $this->expectDeprecationMessageMatches("/does not exist!/"); + $this->expectExceptionMessage("does not exist!"); /** * @typescript DtoTransformed @@ -192,7 +192,7 @@ it('will will throw an exception with class that does not implement transformer', function () { $this->expectException(InvalidTransformerGiven::class); - $this->expectDeprecationMessageMatches("/does not implement the Transformer interface!/"); + $this->expectExceptionMessage("does not implement the Transformer interface!"); /** * @typescript-transformer \Spatie\TypeScriptTransformer\Structures\TransformedType From d307f0c62b1691213d537a45d497524b961dd2ff Mon Sep 17 00:00:00 2001 From: Enzo Innocenzi Date: Thu, 30 Mar 2023 14:53:30 +0200 Subject: [PATCH 4/4] docs: mention `RecordTypeScriptType` --- docs/usage/annotations.md | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/usage/annotations.md b/docs/usage/annotations.md index 31387d7..d5fc084 100644 --- a/docs/usage/annotations.md +++ b/docs/usage/annotations.md @@ -219,6 +219,52 @@ export type UserRepository = { As you can see, the package is smart enough to convert `Language::class` to an inline enum we defined earlier. +## Generating `Record` types + +If you need to generate a `Record` type, you may use the `RecordTypeScriptType` attribute: + +```php +use Spatie\TypeScriptTransformer\Attributes\RecordTypeScriptType; + +class FleetData extends Data +{ + public function __construct( + #[RecordTypeScriptType(AircraftType::class, AircraftData::class)] + public readonly array $fleet, + ) { + } +} +``` + +This will generate a `Record` type with a key type of `AircraftType::class` and a value type of `AircraftData::class`: + +```ts +export type FleetData = { + fleet: Record +} +``` + +Additionally, if you need the value type to be an array of the specified type, you may set the third parameter of `RecordTypeScriptType` to `true`: + +```php +class FleetData extends Data +{ + public function __construct( + #[RecordTypeScriptType(AircraftType::class, AircraftData::class, array: true)] + public readonly array $fleet, + ) { + } +} +``` + +This will generate the following interface: + +```ts +export type FleetData = { + fleet: Record> +} +``` + ## Selecting a transformer Want to define a specific transformer for the file? You can use the following annotation: