Skip to content

Commit

Permalink
Merge pull request #51 from innocenzi/feat/record-type
Browse files Browse the repository at this point in the history
feat: add support for record types
  • Loading branch information
rubenvanassche authored Apr 7, 2023
2 parents 8947cfc + d307f0c commit 3ee4694
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 0 deletions.
46 changes: 46 additions & 0 deletions docs/usage/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<K, V>` 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<App.Enums.AircraftType, App.Data.AircraftData>
}
```
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<App.Enums.AircraftType, Array<App.Data.AircraftData>>
}
```
## Selecting a transformer
Want to define a specific transformer for the file? You can use the following annotation:
Expand Down
7 changes: 7 additions & 0 deletions src/Actions/TranspileTypeToTypeScriptAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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',
Expand Down Expand Up @@ -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) {
Expand Down
27 changes: 27 additions & 0 deletions src/Attributes/RecordTypeScriptType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Spatie\TypeScriptTransformer\Attributes;

use Attribute;
use phpDocumentor\Reflection\Type;
use Spatie\TypeScriptTransformer\Types\RecordType;

#[Attribute]
class RecordTypeScriptType implements TypeScriptTransformableAttribute
{
private string $keyType;
private string | array $valueType;
private bool $array;

public function __construct(string $keyType, string | array $valueType, ?bool $array = false)
{
$this->keyType = $keyType;
$this->valueType = $valueType;
$this->array = $array;
}

public function getType(): Type
{
return new RecordType($this->keyType, $this->valueType, $this->array);
}
}
42 changes: 42 additions & 0 deletions src/Types/RecordType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace Spatie\TypeScriptTransformer\Types;

use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Array_;

/** @psalm-immutable */
class RecordType implements Type
{
private Type $keyType;
private Type $valueType;

public function __construct(string $keyType, string | array $valueType, ?bool $array = false)
{
$this->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;
}
}
46 changes: 46 additions & 0 deletions tests/Types/RecordTypeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Types\Array_;
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;

it('creates a scalar key and an object value', function () {
$record = new RecordType('string', RegularEnum::class);

assertInstanceOf(RecordType::class, $record);
assertEquals(new String_(), $record->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());
});

0 comments on commit 3ee4694

Please sign in to comment.