Skip to content

Commit 1c57886

Browse files
wip
1 parent 0ee5696 commit 1c57886

14 files changed

+220
-155
lines changed

README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This package allows you to convert PHP classes to TypeScript.
99
This class...
1010

1111
```php
12-
/** @typescript */
12+
#[TypeScript]
1313
class User
1414
{
1515
public int $id;
@@ -31,10 +31,10 @@ export type User = {
3131
Here's another example.
3232
3333
```php
34-
class Languages extends Enum
34+
enum Languages: string
3535
{
36-
const TYPESCRIPT = 'typescript';
37-
const PHP = 'php';
36+
case TYPESCRIPT = 'typescript';
37+
case PHP = 'php';
3838
}
3939
```
4040

UPGRADE.md

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Upgrading
2+
3+
Because there are many breaking changes an upgrade is not that easy. There are many edge cases this guide does not
4+
cover. We accept PRs to improve this guide.
5+
6+
## Upgrading to v3
7+
8+
Version 3 is a complete rewrite of the package. That's why writing an upgrade guide is not that easy. The best way to
9+
upgrade is to start reading the new docs and try to implement the new features.
10+
11+
A few noticeable changes are:
12+
13+
- Laravel installs now need to configure the package in a service provider instead of config file
14+
- The package requires PHP 8.2
15+
- If you're using Laravel, v10 is minimally required
16+
- Collectors were removed in favour of Transformers which decide whether a type should be transformed or not
17+
- The transformer should now return a `Transformed` object when it can transform a type
18+
- The transformer interface now should return `Untransformable` when it cannot transform the type
19+
- The `DtoTransformer` was removed in favour of a more flexible transformer system where you can create your own transformers
20+
- The `EnumTransformer` was rewritten to allow multiple types of enums to be transformed and multiple output structures
21+
- All other enum transformers were removed
22+
- The concept of `TypeProcessors` was removed, `ClassPropertyProcessor` is a kinda replacement for this
23+
- The TypeReflectors were removed
24+
- Support for inline types was removed
25+
- If you were implementing your own attributes, you should now implement the `TypeScriptTypeAttributeContract` interface instead of `TypeScriptTransformableAttribute`
26+
- The `RecordTypeScriptType` attribute was removed since deduction of these kinds of types is now done by the transformer
27+
- The `TypeScriptTransformer` attribute was removed
28+
- If you were implementing your own `Formatter`, please update the `format` method to now work on an array of files
29+
30+
And so much more. Please read the docs for more information.
31+
32+
## Upgrading to v2
33+
34+
- The package is now PHP 8 only
35+
- The `ClassPropertyProcessor` interface was renamed to `TypeProcessor` and now takes a union of reflection objects
36+
- In the config:
37+
- `searchingPath` was renamed to `autoDiscoverTypes`
38+
- `classPropertyReplacements` was renamed to `defaultTypeReplacements`
39+
- Collectors now only have one method: `getTransformedType` which should
40+
- return `null` when the collector cannot find a transformer
41+
- return a `TransformedType` from a suitable transformer
42+
- Transformers now only have one method: `transform` which should
43+
- return `null` when the transformer cannot transform the class
44+
- return a `TransformedType` if it can transform the class
45+
- In Writers the `replaceMissingSymbols` method was removed and a `replacesSymbolsWithFullyQualifiedIdentifiers` with `bool` as return type was added
46+
- The DTO transformer was completely rewritten, please take a look at the docs how to create you own
47+
- The step classes are now renamed to actions
48+
49+
Laravel
50+
- In the Laravel config:
51+
- `searching_path` is renamed to `auto_discover_types`
52+
- `class_property_replacements` is renamed to `default_type_relacements`
53+
- `writer` and `formatter` were added
54+
- You should replace the `DefaultCollector::class` with the `DefaultCollector::class`
55+
- It is not possible anymore to convert one file to TypeScript via command

src/Actions/TranspileReflectionTypeToTypeScriptNodeAction.php

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptUndefined;
2222
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptUnion;
2323
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptUnknown;
24+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptVoid;
2425

2526
class TranspileReflectionTypeToTypeScriptNodeAction
2627
{
@@ -98,6 +99,10 @@ protected function reflectionNamedType(
9899
return new TypeScriptObject([]);
99100
}
100101

102+
if ($type->getName() === 'void') {
103+
return new TypeScriptVoid();
104+
}
105+
101106
if (class_exists($type->getName()) || interface_exists($type->getName())) {
102107
return new TypeReference(new ClassStringReference($type->getName()));
103108
}

src/Transformers/ClassTransformer.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public function __construct(
3838

3939
public function transform(ReflectionClass $reflectionClass, TransformationContext $context): Transformed|Untransformable
4040
{
41-
if ($reflectionClass->isEnum()) {
41+
if ($reflectionClass->isEnum() || $reflectionClass->isInterface()) {
4242
return Untransformable::create();
4343
}
4444

src/Transformers/InterfaceTransformer.php

+73-146
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,26 @@
22

33
namespace Spatie\TypeScriptTransformer\Transformers;
44

5-
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
65
use ReflectionClass;
76
use ReflectionMethod;
8-
use ReflectionProperty;
7+
use ReflectionParameter;
98
use Spatie\TypeScriptTransformer\Actions\TranspilePhpStanTypeToTypeScriptNodeAction;
109
use Spatie\TypeScriptTransformer\Actions\TranspileReflectionTypeToTypeScriptNodeAction;
11-
use Spatie\TypeScriptTransformer\Attributes\Hidden;
12-
use Spatie\TypeScriptTransformer\Attributes\Optional;
13-
use Spatie\TypeScriptTransformer\Attributes\TypeScriptTypeAttributeContract;
1410
use Spatie\TypeScriptTransformer\References\ReflectionClassReference;
1511
use Spatie\TypeScriptTransformer\Support\TransformationContext;
1612
use Spatie\TypeScriptTransformer\Transformed\Transformed;
1713
use Spatie\TypeScriptTransformer\Transformed\Untransformable;
14+
use Spatie\TypeScriptTransformer\TypeResolvers\Data\ParsedMethod;
15+
use Spatie\TypeScriptTransformer\TypeResolvers\Data\ParsedNameAndType;
1816
use Spatie\TypeScriptTransformer\TypeResolvers\DocTypeResolver;
19-
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptAlias;
2017
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptIdentifier;
18+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptInterface;
19+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptInterfaceMethod;
2120
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptNode;
21+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptParameter;
2222
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptProperty;
2323
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptUnknown;
24+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptVoid;
2425

2526
abstract class InterfaceTransformer implements Transformer
2627
{
@@ -33,19 +34,22 @@ public function __construct(
3334

3435
public function transform(ReflectionClass $reflectionClass, TransformationContext $context): Transformed|Untransformable
3536
{
36-
if ($reflectionClass->isEnum()) {
37+
if (! $reflectionClass->isInterface()) {
3738
return Untransformable::create();
3839
}
3940

4041
if (! $this->shouldTransform($reflectionClass)) {
4142
return Untransformable::create();
4243
}
4344

45+
$node = new TypeScriptInterface(
46+
new TypeScriptIdentifier($context->name),
47+
$this->getProperties($reflectionClass, $context),
48+
$this->getMethods($reflectionClass, $context)
49+
);
50+
4451
return new Transformed(
45-
new TypeScriptAlias(
46-
new TypeScriptIdentifier($context->name),
47-
$this->getTypeScriptNode($reflectionClass, $context)
48-
),
52+
$node,
4953
new ReflectionClassReference($reflectionClass),
5054
$context->nameSpaceSegments,
5155
true,
@@ -54,173 +58,96 @@ public function transform(ReflectionClass $reflectionClass, TransformationContex
5458

5559
abstract protected function shouldTransform(ReflectionClass $reflection): bool;
5660

57-
protected function getTypeScriptNode(
61+
/** @return TypeScriptInterfaceMethod[] */
62+
protected function getMethods(
5863
ReflectionClass $reflectionClass,
5964
TransformationContext $context,
60-
): TypeScriptNode {
61-
if ($resolvedAttributeType = $this->resolveTypeByAttribute($reflectionClass)) {
62-
return $resolvedAttributeType;
63-
}
64-
65-
$constructorAnnotations = $reflectionClass->hasMethod('__construct')
66-
? $this->docTypeResolver->method($reflectionClass->getMethod('__construct'))?->parameters ?? []
67-
: [];
68-
69-
$properties = [];
70-
71-
foreach ($this->getMethods($reflectionClass) as $reflectionMethod) {
72-
$property = $this->createProperty(
73-
$reflectionClass,
74-
$reflectionMethod,
75-
$annotation?->type,
76-
$context
77-
);
78-
79-
if ($property === null) {
80-
continue;
81-
}
82-
83-
$property = $this->runClassPropertyProcessors(
84-
$reflectionMethod,
85-
$annotation?->type,
86-
$property
87-
);
65+
): array {
66+
$methods = [];
8867

89-
if ($property !== null) {
90-
$properties[] = $property;
91-
}
68+
foreach ($reflectionClass->getMethods() as $reflectionMethod) {
69+
$methods[] = $this->getTypeScriptMethod($reflectionClass, $reflectionMethod, $context);
9270
}
9371

94-
return new Type($properties);
72+
return $methods;
9573
}
9674

97-
protected function resolveTypeByAttribute(
75+
/** @return TypeScriptProperty[] */
76+
protected function getProperties(
9877
ReflectionClass $reflectionClass,
99-
?ReflectionProperty $property = null,
100-
): ?TypeScriptNode {
101-
$subject = $property ?? $reflectionClass;
102-
103-
foreach ($subject->getAttributes() as $attribute) {
104-
if (is_a($attribute->getName(), TypeScriptTypeAttributeContract::class, true)) {
105-
/** @var TypeScriptTypeAttributeContract $attributeInstance */
106-
$attributeInstance = $attribute->newInstance();
107-
108-
return $attributeInstance->getType($reflectionClass);
109-
}
110-
}
111-
112-
return null;
113-
}
114-
115-
protected function getMethods(ReflectionClass $reflection): array
116-
{
117-
return array_filter(
118-
$reflection->getMethods(),
119-
fn (ReflectionMethod $method) => ! $method->isStatic()
120-
);
78+
TransformationContext $context,
79+
): array {
80+
return [];
12181
}
12282

123-
protected function createProperty(
83+
protected function getTypeScriptMethod(
12484
ReflectionClass $reflectionClass,
125-
ReflectionProperty $reflectionProperty,
126-
?TypeNode $annotation,
85+
ReflectionMethod $reflectionMethod,
12786
TransformationContext $context,
128-
): ?TypeScriptProperty {
129-
$type = $this->resolveTypeForProperty(
130-
$reflectionClass,
131-
$reflectionProperty,
132-
$annotation
133-
);
87+
): TypeScriptInterfaceMethod {
88+
$annotation = $this->docTypeResolver->method($reflectionMethod);
13489

135-
$property = new TypeScriptProperty(
136-
$reflectionProperty->getName(),
137-
$type,
138-
$this->isPropertyOptional(
139-
$reflectionProperty,
140-
$reflectionClass,
141-
$type,
142-
$context
143-
),
144-
$this->isPropertyReadonly(
145-
$reflectionProperty,
90+
return new TypeScriptInterfaceMethod(
91+
$reflectionMethod->getName(),
92+
array_map(fn (ReflectionParameter $parameter) => $this->resolveMethodParameterType(
14693
$reflectionClass,
147-
$type,
148-
)
94+
$reflectionMethod,
95+
$parameter,
96+
$context,
97+
$annotation->parameters[$parameter->getName()] ?? null
98+
), $reflectionMethod->getParameters()),
99+
$this->resolveMethodReturnType($reflectionClass, $reflectionMethod, $context, $annotation)
149100
);
150-
151-
if ($this->isPropertyHidden($reflectionProperty, $reflectionClass, $property)) {
152-
return null;
153-
}
154-
155-
return $property;
156101
}
157102

158-
protected function resolveTypeForProperty(
103+
protected function resolveMethodReturnType(
159104
ReflectionClass $reflectionClass,
160-
ReflectionProperty $reflectionProperty,
161-
?TypeNode $annotation,
105+
ReflectionMethod $reflectionMethod,
106+
TransformationContext $context,
107+
?ParsedMethod $annotation
162108
): TypeScriptNode {
163-
if ($resolvedAttributeType = $this->resolveTypeByAttribute($reflectionClass, $reflectionProperty)) {
164-
return $resolvedAttributeType;
165-
}
166-
167-
if ($annotation) {
109+
if ($annotation->returnType) {
168110
return $this->transpilePhpStanTypeToTypeScriptTypeAction->execute(
169-
$annotation,
170-
$reflectionClass,
111+
$annotation->returnType,
112+
$reflectionClass
171113
);
172114
}
173115

174-
if ($reflectionProperty->hasType()) {
116+
$reflectionType = $reflectionMethod->getReturnType();
117+
118+
if ($reflectionType) {
175119
return $this->transpileReflectionTypeToTypeScriptTypeAction->execute(
176-
$reflectionProperty->getType(),
120+
$reflectionType,
177121
$reflectionClass
178122
);
179123
}
180124

181-
return new TypeScriptUnknown();
125+
return new TypeScriptVoid();
182126
}
183127

184-
protected function isPropertyOptional(
185-
ReflectionProperty $reflectionProperty,
128+
protected function resolveMethodParameterType(
186129
ReflectionClass $reflectionClass,
187-
TypeScriptNode $type,
130+
ReflectionMethod $reflectionMethod,
131+
ReflectionParameter $reflectionParameter,
188132
TransformationContext $context,
189-
): bool {
190-
return $context->optional || count($reflectionProperty->getAttributes(Optional::class)) > 0;
191-
}
192-
193-
protected function isPropertyReadonly(
194-
ReflectionProperty $reflectionProperty,
195-
ReflectionClass $reflectionClass,
196-
TypeScriptNode $type,
197-
): bool {
198-
return $reflectionProperty->isReadOnly() || $reflectionClass->isReadOnly();
199-
}
200-
201-
protected function isPropertyHidden(
202-
ReflectionProperty $reflectionProperty,
203-
ReflectionClass $reflectionClass,
204-
TypeScriptProperty $property,
205-
): bool {
206-
return count($reflectionProperty->getAttributes(Hidden::class)) > 0;
207-
}
208-
209-
protected function runClassPropertyProcessors(
210-
ReflectionProperty $reflectionProperty,
211-
?TypeNode $annotation,
212-
TypeScriptProperty $property,
213-
): ?TypeScriptProperty {
214-
$processors = $this->classPropertyProcessors;
215-
216-
foreach ($processors as $processor) {
217-
$property = $processor->execute($reflectionProperty, $annotation, $property);
218-
219-
if ($property === null) {
220-
return null;
221-
}
222-
}
133+
?ParsedNameAndType $annotation,
134+
): TypeScriptParameter {
135+
$type = match (true) {
136+
$annotation !== null => $this->transpilePhpStanTypeToTypeScriptTypeAction->execute(
137+
$annotation->type,
138+
$reflectionClass
139+
),
140+
$reflectionParameter->hasType() => $this->transpileReflectionTypeToTypeScriptTypeAction->execute(
141+
$reflectionParameter->getType(),
142+
$reflectionClass
143+
),
144+
default => new TypeScriptUnknown(),
145+
};
223146

224-
return $property;
147+
return new TypeScriptParameter(
148+
$reflectionParameter->getName(),
149+
$type,
150+
$reflectionParameter->isOptional()
151+
);
225152
}
226153
}

0 commit comments

Comments
 (0)