Skip to content

Commit

Permalink
Rollback to typescript transformer 2
Browse files Browse the repository at this point in the history
  • Loading branch information
rubenvanassche committed Dec 21, 2023
1 parent a0a163b commit c7a4be1
Show file tree
Hide file tree
Showing 23 changed files with 380 additions and 327 deletions.
22 changes: 22 additions & 0 deletions src/Support/TypeScriptTransformer/DataTypeScriptCollector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Spatie\LaravelData\Support\TypeScriptTransformer;

use ReflectionClass;
use Spatie\LaravelData\Contracts\BaseData;
use Spatie\TypeScriptTransformer\Collectors\Collector;
use Spatie\TypeScriptTransformer\Structures\TransformedType;

class DataTypeScriptCollector extends Collector
{
public function getTransformedType(ReflectionClass $class): ?TransformedType
{
if (! $class->isSubclassOf(BaseData::class)) {
return null;
}

$transformer = new DataTypeScriptTransformer($this->config);

return $transformer->transform($class, $class->getShortName());
}
}
162 changes: 155 additions & 7 deletions src/Support/TypeScriptTransformer/DataTypeScriptTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,169 @@

namespace Spatie\LaravelData\Support\TypeScriptTransformer;

use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\Boolean;
use phpDocumentor\Reflection\Types\Integer;
use phpDocumentor\Reflection\Types\Nullable;
use phpDocumentor\Reflection\Types\Object_;
use phpDocumentor\Reflection\Types\String_;
use ReflectionClass;
use ReflectionProperty;
use RuntimeException;
use Spatie\LaravelData\Contracts\BaseData;
use Spatie\TypeScriptTransformer\Transformers\ClassTransformer;
use Spatie\LaravelData\Enums\DataTypeKind;
use Spatie\LaravelData\Support\DataConfig;
use Spatie\LaravelData\Support\DataProperty;
use Spatie\LaravelData\Support\Lazy\ClosureLazy;
use Spatie\LaravelTypeScriptTransformer\Transformers\DtoTransformer;
use Spatie\TypeScriptTransformer\Attributes\Optional as TypeScriptOptional;
use Spatie\TypeScriptTransformer\Structures\MissingSymbolsCollection;
use Spatie\TypeScriptTransformer\TypeProcessors\DtoCollectionTypeProcessor;
use Spatie\TypeScriptTransformer\TypeProcessors\ReplaceDefaultsTypeProcessor;
use Spatie\TypeScriptTransformer\Types\StructType;

class DataTypeScriptTransformer extends ClassTransformer
class DataTypeScriptTransformer extends DtoTransformer
{
protected function shouldTransform(ReflectionClass $reflection): bool
public function canTransform(ReflectionClass $class): bool
{
return $reflection->implementsInterface(BaseData::class);
return $class->isSubclassOf(BaseData::class);
}

protected function classPropertyProcessors(): array
protected function typeProcessors(): array
{
return array_merge(parent::classPropertyProcessors(), [
new DataUtilitiesClassPropertyProcessor(),
return [
new ReplaceDefaultsTypeProcessor(
$this->config->getDefaultTypeReplacements()
),
new RemoveLazyTypeProcessor(),
new RemoveOptionalTypeProcessor(),
new DtoCollectionTypeProcessor(),
];
}


protected function transformProperties(
ReflectionClass $class,
MissingSymbolsCollection $missingSymbols
): string {
$dataClass = app(DataConfig::class)->getDataClass($class->getName());

$isOptional = $dataClass->attributes->contains(
fn (object $attribute) => $attribute instanceof TypeScriptOptional
);

return array_reduce(
$this->resolveProperties($class),
function (string $carry, ReflectionProperty $property) use ($isOptional, $dataClass, $missingSymbols) {
/** @var \Spatie\LaravelData\Support\DataProperty $dataProperty */
$dataProperty = $dataClass->properties[$property->getName()];

$type = $this->resolveTypeForProperty($property, $dataProperty, $missingSymbols);

if ($type === null) {
return $carry;
}

$isOptional = $isOptional
|| $dataProperty->attributes->contains(
fn (object $attribute) => $attribute instanceof TypeScriptOptional
)
|| ($dataProperty->type->lazyType && $dataProperty->type->lazyType !== ClosureLazy::class)
|| $dataProperty->type->isOptional;

$transformed = $this->typeToTypeScript(
$type,
$missingSymbols,
$property->getDeclaringClass()->getName(),
);

$propertyName = $dataProperty->outputMappedName ?? $dataProperty->name;

if (! preg_match('/^[$_a-zA-Z][$_a-zA-Z0-9]*$/', $propertyName)) {
$propertyName = "'{$propertyName}'";
}

return $isOptional
? "{$carry}{$propertyName}?: {$transformed};" . PHP_EOL
: "{$carry}{$propertyName}: {$transformed};" . PHP_EOL;
},
''
);
}

protected function resolveTypeForProperty(
ReflectionProperty $property,
DataProperty $dataProperty,
MissingSymbolsCollection $missingSymbols,
): ?Type {
if (! $dataProperty->type->kind->isDataCollectable()) {
return $this->reflectionToType(
$property,
$missingSymbols,
...$this->typeProcessors()
);
}

$collectionType = match ($dataProperty->type->kind) {
DataTypeKind::DataCollection, DataTypeKind::Array, DataTypeKind::Enumerable => $this->defaultCollectionType($dataProperty->type->dataClass),
DataTypeKind::Paginator, DataTypeKind::DataPaginatedCollection => $this->paginatedCollectionType($dataProperty->type->dataClass),
DataTypeKind::CursorPaginator, DataTypeKind::DataCursorPaginatedCollection => $this->cursorPaginatedCollectionType($dataProperty->type->dataClass),
null => throw new RuntimeException('Cannot end up here since the type is dataCollectable')
};

if ($dataProperty->type->isNullable()) {
return new Nullable($collectionType);
}

return $collectionType;
}

protected function defaultCollectionType(string $class): Type
{
return new Array_(new Object_(new Fqsen("\\{$class}")));
}

protected function paginatedCollectionType(string $class): Type
{
return new StructType([
'data' => $this->defaultCollectionType($class),
'links' => new Array_(new StructType([
'url' => new Nullable(new String_()),
'label' => new String_(),
'active' => new Boolean(),
])),
'meta' => new StructType([
'current_page' => new Integer(),
'first_page_url' => new String_(),
'from' => new Nullable(new Integer()),
'last_page' => new Integer(),
'last_page_url' => new String_(),
'next_page_url' => new Nullable(new String_()),
'path' => new String_(),
'per_page' => new Integer(),
'prev_page_url' => new Nullable(new String_()),
'to' => new Nullable(new Integer()),
'total' => new Integer(),

]),
]);
}

protected function cursorPaginatedCollectionType(string $class): Type
{
return new StructType([
'data' => $this->defaultCollectionType($class),
'links' => new Array_(),
'meta' => new StructType([
'path' => new String_(),
'per_page' => new Integer(),
'next_cursor' => new Nullable(new String_()),
'next_cursor_url' => new Nullable(new String_()),
'prev_cursor' => new Nullable(new String_()),
'prev_cursor_url' => new Nullable(new String_()),
]),
]);
}
}

This file was deleted.

47 changes: 47 additions & 0 deletions src/Support/TypeScriptTransformer/RemoveLazyTypeProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace Spatie\LaravelData\Support\TypeScriptTransformer;

use Exception;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Object_;
use ReflectionMethod;
use ReflectionParameter;
use ReflectionProperty;
use Spatie\LaravelData\Lazy;
use Spatie\TypeScriptTransformer\Structures\MissingSymbolsCollection;
use Spatie\TypeScriptTransformer\TypeProcessors\TypeProcessor;

class RemoveLazyTypeProcessor implements TypeProcessor
{
public function process(
Type $type,
ReflectionParameter | ReflectionMethod | ReflectionProperty $reflection,
MissingSymbolsCollection $missingSymbolsCollection
): ?Type {
if (! $type instanceof Compound) {
return $type;
}

/** @var \Illuminate\Support\Collection $types */
$types = collect(iterator_to_array($type->getIterator()))
->reject(function (Type $type) {
if (! $type instanceof Object_) {
return false;
}

return is_a((string)$type->getFqsen(), Lazy::class, true);
});

if ($types->isEmpty()) {
throw new Exception("Type {$reflection->getDeclaringClass()->name}:{$reflection->getName()} cannot be only Lazy");
}

if ($types->count() === 1) {
return $types->first();
}

return new Compound($types->all());
}
}
Loading

0 comments on commit c7a4be1

Please sign in to comment.