Skip to content

Commit 84bcb18

Browse files
wip
1 parent fcc502b commit 84bcb18

23 files changed

+677
-43
lines changed

src/Actions/TranspilePhpStanTypeToTypeScriptTypeAction.php

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ protected function identifierNode(
8282
}
8383

8484
if ($node->name === 'array') {
85-
return new TypeScriptArray(null);
85+
return new TypeScriptArray([]);
8686
}
8787

8888
if ($node->name === 'callable') {
@@ -121,8 +121,9 @@ protected function arrayTypeNode(
121121
ArrayTypeNode $node,
122122
?ReflectionClass $reflectionClass
123123
): TypeScriptNode {
124-
return new TypeScriptArray(
125-
$this->execute($node->type, $reflectionClass)
124+
return new TypeScriptGeneric(
125+
new TypeScriptIdentifier('Array'),
126+
[$this->execute($node->type, $reflectionClass)]
126127
);
127128
}
128129

@@ -183,17 +184,12 @@ protected function genericNode(
183184
GenericTypeNode $node,
184185
?ReflectionClass $reflectionClass
185186
): TypeScriptNode {
186-
$type = $this->execute($node->type, $reflectionClass);
187-
188-
if ($type instanceof TypeScriptString) {
189-
return $type; // class-string<something> case
190-
}
191-
192-
if ($type instanceof TypeScriptArray) {
187+
if ($node->type->name === 'array') {
193188
return match (count($node->genericTypes)) {
194-
0 => $type,
195-
1 => new TypeScriptArray(
196-
$this->execute($node->genericTypes[0], $reflectionClass)
189+
0 => new TypeScriptArray([]),
190+
1 => new TypeScriptGeneric(
191+
new TypeScriptIdentifier('Array'),
192+
[$this->execute($node->genericTypes[0], $reflectionClass)]
197193
),
198194
2 => new TypeScriptGeneric(
199195
new TypeScriptIdentifier('Record'),
@@ -206,6 +202,12 @@ protected function genericNode(
206202
};
207203
}
208204

205+
$type = $this->execute($node->type, $reflectionClass);
206+
207+
if ($type instanceof TypeScriptString) {
208+
return $type; // class-string<something> case
209+
}
210+
209211
return new TypeScriptGeneric(
210212
$type,
211213
array_map(

src/Actions/TranspileReflectionTypeToTypeScriptTypeAction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ protected function reflectionNamedType(
7979
}
8080

8181
if ($type->getName() === 'array') {
82-
return new TypeScriptArray(null);
82+
return new TypeScriptArray([]);
8383
}
8484

8585
if ($type->getName() === 'null') {

src/Laravel/LaravelDefaultTypesProvider.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
use Spatie\TypeScriptTransformer\Transformed\Transformed;
1212
use Spatie\TypeScriptTransformer\TypeScript\TypeReference;
1313
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptAlias;
14-
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptArray;
1514
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptBoolean;
1615
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptExport;
1716
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptGeneric;
@@ -47,8 +46,9 @@ protected function collection(): Transformed
4746
new TypeScriptIdentifier('Collection'),
4847
[new TypeScriptIdentifier('T')],
4948
),
50-
new TypeScriptArray(
51-
new TypeScriptIdentifier('T'),
49+
new TypeScriptGeneric(
50+
new TypeScriptIdentifier('Array'),
51+
[new TypeScriptIdentifier('T')],
5252
),
5353
)
5454
),
@@ -68,8 +68,9 @@ protected function eloquentCollection(): Transformed
6868
new TypeScriptIdentifier('Collection'),
6969
[new TypeScriptIdentifier('T')],
7070
),
71-
new TypeScriptArray(
72-
new TypeScriptIdentifier('T'),
71+
new TypeScriptGeneric(
72+
new TypeScriptIdentifier('Array'),
73+
[new TypeScriptIdentifier('T')],
7374
),
7475
)
7576
),
@@ -90,7 +91,10 @@ protected function lengthAwarePaginator(): Transformed
9091
[new TypeScriptIdentifier('T')],
9192
),
9293
new TypeScriptObject([
93-
new TypeScriptProperty('data', new TypeScriptArray(new TypeScriptIdentifier('T'))),
94+
new TypeScriptProperty('data', new TypeScriptGeneric(
95+
new TypeScriptIdentifier('Array'),
96+
[new TypeScriptIdentifier('T')],
97+
),),
9498
new TypeScriptProperty('links', new TypeScriptObject([
9599
new TypeScriptProperty('url', new TypeScriptUnion([
96100
new TypeScriptIdentifier('string'),

src/Laravel/RouterGenerator.php

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<?php
2+
3+
namespace Spatie\TypeScriptTransformer\Laravel;
4+
5+
use Illuminate\Routing\Route;
6+
use Illuminate\Routing\Router;
7+
use Spatie\TypeScriptTransformer\DefaultTypeProviders\DefaultTypesProvider;
8+
use Spatie\TypeScriptTransformer\Laravel\Routes\InvokableRouteController;
9+
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteController;
10+
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteControllerAction;
11+
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteControllerCollection;
12+
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteParameter;
13+
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteParameterCollection;
14+
use Spatie\TypeScriptTransformer\Transformed\Transformed;
15+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptAlias;
16+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptArray;
17+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptConditional;
18+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptFunctionDefinition;
19+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptGeneric;
20+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptGenericTypeVariable;
21+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptIdentifier;
22+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptIndexedAccess;
23+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptInterface;
24+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptObject;
25+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptOperator;
26+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptParameter;
27+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptProperty;
28+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptRaw;
29+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptString;
30+
31+
// @todo implement the method, probably using a RawTypeScriptNode, creating individual notes for each JS construct is probably a bit far fetched
32+
// @todo make sure we support __invoke routes without action
33+
// @todo add support for nullable parameters, these should be inferred
34+
// @todo a syntax like route(['Controller', 'action'], {params}), route(['Controller', 'action'], param), route(InvokeableController) would be even cooler but maybe too complicated at the moment
35+
36+
/**
37+
* function route<
38+
* TController extends keyof Routes,
39+
* TAction extends keyof Routes[TController],
40+
* TParams extends Routes[TController][TAction]["parameters"]
41+
* >(action: [TController, TAction] | TController, params?: TParams): string {
42+
*
43+
* }
44+
*/
45+
class RouterGenerator implements DefaultTypesProvider
46+
{
47+
public function provide(): array
48+
{
49+
$controllers = $this->resolveRoutes();
50+
51+
$transformedRoutes = new Transformed(
52+
new TypeScriptAlias(
53+
new TypeScriptIdentifier('Routes'),
54+
$controllers->toTypeScriptNode(),
55+
),
56+
null,
57+
'Routes',
58+
true,
59+
[],
60+
);
61+
62+
$actionParam = new Transformed(
63+
new TypeScriptAlias(
64+
new TypeScriptGeneric(
65+
new TypeScriptIdentifier('ActionParam'),
66+
[
67+
new TypeScriptGenericTypeVariable(new TypeScriptIdentifier('TController')),
68+
new TypeScriptGenericTypeVariable(new TypeScriptIdentifier('TAction')),
69+
]
70+
),
71+
new TypeScriptConditional(
72+
TypeScriptOperator::extends(
73+
new TypeScriptIndexedAccess(
74+
new TypeScriptIdentifier('Routes'),
75+
[new TypeScriptIdentifier('TController')],
76+
),
77+
new TypeScriptObject([
78+
new TypeScriptProperty('invokable', new TypeScriptRaw('true')),
79+
])
80+
),
81+
new TypeScriptIdentifier('TController'),
82+
new TypeScriptArray([
83+
new TypeScriptIdentifier('TController'),
84+
new TypeScriptIdentifier('TAction'),
85+
])
86+
)
87+
),
88+
null,
89+
'ActionParam',
90+
true,
91+
[],
92+
);
93+
94+
$transformedAction = new Transformed(
95+
new TypeScriptFunctionDefinition(
96+
new TypeScriptGeneric(
97+
new TypeScriptIdentifier('route'),
98+
[
99+
new TypeScriptGenericTypeVariable(
100+
new TypeScriptIdentifier('TController'),
101+
extends: new TypeScriptIdentifier('keyof Routes'),
102+
),
103+
new TypeScriptGenericTypeVariable(
104+
new TypeScriptIdentifier('TAction'),
105+
extends: new TypeScriptIdentifier('keyof Routes[TController]["actions"]'),
106+
),
107+
new TypeScriptGenericTypeVariable(
108+
new TypeScriptIdentifier('TParams'),
109+
extends: new TypeScriptIdentifier('Routes[TController]["actions"][TAction]["parameters"]'),
110+
),
111+
]
112+
),
113+
[
114+
new TypeScriptParameter('action', new TypeScriptGeneric(
115+
new TypeScriptIdentifier('ActionParam'),
116+
[
117+
new TypeScriptGenericTypeVariable(new TypeScriptIdentifier('TController')),
118+
new TypeScriptGenericTypeVariable(new TypeScriptIdentifier('TAction')),
119+
]
120+
)),
121+
new TypeScriptParameter('params', new TypeScriptIdentifier('TParams'), isOptional: true),
122+
],
123+
new TypeScriptString(),
124+
new TypeScriptRaw("let routes = JSON.parse('".json_encode($controllers->toJsObject(), flags: JSON_UNESCAPED_SLASHES). "')")
125+
),
126+
null,
127+
'route',
128+
true,
129+
[],
130+
);
131+
132+
return [$transformedRoutes, $actionParam, $transformedAction];
133+
}
134+
135+
private function resolveRoutes(): RouteControllerCollection
136+
{
137+
/** @var array<string, RouteController> $controllers */
138+
$controllers = [];
139+
140+
foreach (app(Router::class)->getRoutes()->getRoutes() as $route) {
141+
$controllerClass = $route->getControllerClass();
142+
143+
if ($controllerClass === null) {
144+
continue;
145+
}
146+
147+
$controllerClass = str_replace('\\', '.', $controllerClass);
148+
149+
if ($route->getActionMethod() === $route->getControllerClass()) {
150+
$controllers[$controllerClass] = new InvokableRouteController(
151+
$this->resolveRouteParameters($route),
152+
$route->methods,
153+
$this->resolveUrl($route),
154+
);
155+
156+
continue;
157+
}
158+
159+
if (! array_key_exists($controllerClass, $controllers)) {
160+
$controllers[$controllerClass] = new RouteController([]);
161+
}
162+
163+
$controllers[$controllerClass]->actions[$route->getActionMethod()] = new RouteControllerAction(
164+
$route->getActionMethod(),
165+
$this->resolveRouteParameters($route),
166+
$route->methods,
167+
$this->resolveUrl($route),
168+
);
169+
}
170+
171+
return new RouteControllerCollection($controllers);
172+
}
173+
174+
protected function resolveRouteParameters(
175+
Route $route
176+
): RouteParameterCollection {
177+
preg_match_all('/\{(.*?)\}/', $route->getDomain().$route->uri, $matches);
178+
179+
$parameters = array_map(fn (string $match) => new RouteParameter(
180+
trim($match, '?'),
181+
str_ends_with($match, '?')
182+
), $matches[1]);
183+
184+
return new RouteParameterCollection($parameters);
185+
}
186+
187+
protected function resolveUrl(Route $route): string
188+
{
189+
return str_replace('?}', '}', $route->getDomain().$route->uri);
190+
}
191+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Spatie\TypeScriptTransformer\Laravel\Routes;
4+
5+
use PHPUnit\Runner\Extension\ParameterCollection;
6+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptNode;
7+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptObject;
8+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptProperty;
9+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptRaw;
10+
11+
class InvokableRouteController implements RouterStructure
12+
{
13+
/**
14+
* @param array<string> $methods
15+
*/
16+
public function __construct(
17+
public RouteParameterCollection $parameters,
18+
public array $methods,
19+
public string $url,
20+
)
21+
{
22+
}
23+
24+
public function toTypeScriptNode(): TypeScriptNode
25+
{
26+
return new TypeScriptObject([
27+
new TypeScriptProperty('invokable', new TypeScriptRaw('true')),
28+
new TypeScriptProperty('parameters', $this->parameters->toTypeScriptNode()),
29+
]);
30+
}
31+
32+
public function toJsObject(): array
33+
{
34+
return [
35+
'url' => $this->url,
36+
'methods' => array_values($this->methods)
37+
];
38+
}
39+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
namespace Spatie\TypeScriptTransformer\Laravel\Routes;
4+
5+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptNode;
6+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptObject;
7+
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptProperty;
8+
9+
class RouteController implements RouterStructure
10+
{
11+
/**
12+
* @param array<string, RouteControllerAction> $actions
13+
*/
14+
public function __construct(
15+
public array $actions,
16+
) {
17+
}
18+
19+
public function toTypeScriptNode(): TypeScriptNode
20+
{
21+
return new TypeScriptObject([
22+
new TypeScriptProperty('actions', new TypeScriptObject(collect($this->actions)->map(function (RouteControllerAction $controller, string $name) {
23+
return new TypeScriptProperty(
24+
$name,
25+
$controller->toTypeScriptNode(),
26+
);
27+
})->all())),
28+
]);
29+
}
30+
31+
public function toJsObject(): array
32+
{
33+
return [
34+
'actions' => collect($this->actions)->map(function (RouteControllerAction $action, string $name) {
35+
return [
36+
$name => $action->toJsObject(),
37+
];
38+
})->all(),
39+
];
40+
}
41+
}

0 commit comments

Comments
 (0)