Skip to content

Commit 5df83a3

Browse files
Fix routes
1 parent 35024f0 commit 5df83a3

22 files changed

+509
-99
lines changed

src/Actions/AppendDefaultTypesAction.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ public function __construct(
1717

1818
public function execute(TransformedCollection $collection): void
1919
{
20-
foreach ($this->config->defaultTypeProviders as $defaultTypeProviderClass) {
21-
/** @var DefaultTypesProvider $defaultTypeProvider */
22-
$defaultTypeProvider = new $defaultTypeProviderClass;
20+
foreach ($this->config->defaultTypeProviders as $defaultTypeProvider) {
21+
$defaultTypeProvider = $defaultTypeProvider instanceof DefaultTypesProvider
22+
? $defaultTypeProvider
23+
: new $defaultTypeProvider;
2324

2425
$collection->add(...$defaultTypeProvider->provide());
2526
}

src/Actions/VisitTypeScriptTreeAction.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ public function execute(
2121
}
2222

2323
if ($typeScriptNode instanceof TypeScriptNodeWithChildren) {
24-
foreach ($typeScriptNode->children() as $child) {
24+
$children = array_values(array_filter($typeScriptNode->children()));
25+
26+
foreach ($children as $child) {
2527
$this->execute($child, $walker, $allowedNodes);
2628
}
2729
}

src/Laravel/Actions/ResolveLaravelRoutControllerCollectionsAction.php

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,53 @@
44

55
use Illuminate\Routing\Route;
66
use Illuminate\Routing\Router;
7-
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteInvokableController;
7+
use Illuminate\Support\Str;
8+
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteClosure;
9+
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteCollection;
810
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteController;
911
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteControllerAction;
10-
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteControllerCollection;
12+
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteInvokableController;
1113
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteParameter;
1214
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteParameterCollection;
1315

1416
class ResolveLaravelRoutControllerCollectionsAction
1517
{
16-
public function execute(): RouteControllerCollection
17-
{
18+
public function execute(
19+
?string $defaultNamespace,
20+
bool $includeRouteClosures,
21+
): RouteCollection {
1822
/** @var array<string, RouteController> $controllers */
1923
$controllers = [];
24+
/** @var array<RouteClosure> $closures */
25+
$closures = [];
2026

2127
foreach (app(Router::class)->getRoutes()->getRoutes() as $route) {
2228
$controllerClass = $route->getControllerClass();
2329

30+
if ($controllerClass === null && ! $includeRouteClosures) {
31+
continue;
32+
}
33+
2434
if ($controllerClass === null) {
35+
$name = "Closure({$route->uri})";
36+
37+
$closures[$name] = new RouteClosure(
38+
$this->resolveRouteParameters($route),
39+
$route->methods,
40+
$this->resolveUrl($route),
41+
);
42+
2543
continue;
2644
}
2745

46+
if ($defaultNamespace !== null) {
47+
$controllerClass = Str::of($controllerClass)
48+
->trim('\\')
49+
->replace($defaultNamespace, '')
50+
->trim('\\')
51+
->toString();
52+
}
53+
2854
$controllerClass = str_replace('\\', '.', $controllerClass);
2955

3056
if ($route->getActionMethod() === $route->getControllerClass()) {
@@ -49,7 +75,7 @@ public function execute(): RouteControllerCollection
4975
);
5076
}
5177

52-
return new RouteControllerCollection($controllers);
78+
return new RouteCollection($controllers, $closures);
5379
}
5480

5581
protected function resolveRouteParameters(

src/Laravel/LaravelActionDefaultTypesProvider.php

Lines changed: 105 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44

55
use Spatie\TypeScriptTransformer\DefaultTypeProviders\DefaultTypesProvider;
66
use Spatie\TypeScriptTransformer\Laravel\Actions\ResolveLaravelRoutControllerCollectionsAction;
7+
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteCollection;
78
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteController;
89
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteControllerAction;
9-
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteControllerCollection;
1010
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteInvokableController;
1111
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteParameter;
1212
use Spatie\TypeScriptTransformer\Laravel\Routes\RouteParameterCollection;
13+
use Spatie\TypeScriptTransformer\References\CustomReference;
1314
use Spatie\TypeScriptTransformer\Transformed\Transformed;
15+
use Spatie\TypeScriptTransformer\TypeScript\TypeReference;
1416
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptAlias;
1517
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptArray;
1618
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptConditional;
@@ -30,115 +32,174 @@
3032
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptString;
3133
use Spatie\TypeScriptTransformer\TypeScript\TypeScriptUnion;
3234

33-
// @todo implement the method, probably using a RawTypeScriptNode, creating individual notes for each JS construct is probably a bit far fetched
34-
// @todo make sure we support __invoke routes without action
35-
// @todo add support for nullable parameters, these should be inferred
36-
37-
/**
38-
* function route<
39-
* TController extends keyof Routes,
40-
* TAction extends keyof Routes[TController],
41-
* TParams extends Routes[TController][TAction]["parameters"]
42-
* >(action: [TController, TAction] | TController, params?: TParams): string {
43-
*
44-
* }
45-
*/
4635
class LaravelActionDefaultTypesProvider implements DefaultTypesProvider
4736
{
4837
public function __construct(
49-
protected ResolveLaravelRoutControllerCollectionsAction $resolveLaravelRoutControllerCollectionsAction = new ResolveLaravelRoutControllerCollectionsAction()
38+
protected ResolveLaravelRoutControllerCollectionsAction $resolveLaravelRoutControllerCollectionsAction = new ResolveLaravelRoutControllerCollectionsAction(),
39+
protected ?string $defaultNamespace = null,
40+
protected array $location = [],
5041
) {
5142
}
5243

5344
public function provide(): array
5445
{
55-
$controllers = $this->resolveLaravelRoutControllerCollectionsAction->execute();
46+
$controllers = $this->resolveLaravelRoutControllerCollectionsAction->execute(
47+
$this->defaultNamespace,
48+
includeRouteClosures: false,
49+
);
5650

5751
$transformedRoutes = new Transformed(
5852
new TypeScriptAlias(
59-
new TypeScriptIdentifier('Routes'),
53+
new TypeScriptIdentifier('RoutesList'),
6054
$this->parseRouteControllerCollection($controllers),
6155
),
62-
null,
63-
'Routes',
56+
$routesListReference = new CustomReference('laravel_route_actions', 'routes_list'),
57+
'RoutesList',
6458
true,
65-
[],
59+
$this->location,
6660
);
6761

68-
$actionParam = new Transformed(
62+
$isInvokableControllerCondition = TypeScriptOperator::extends(
63+
new TypeScriptIndexedAccess(
64+
new TypeReference($routesListReference),
65+
[new TypeScriptIdentifier('TController')],
66+
),
67+
new TypeScriptObject([
68+
new TypeScriptProperty('invokable', new TypeScriptRaw('true')),
69+
])
70+
);
71+
72+
$actionController = new Transformed(
6973
new TypeScriptAlias(
7074
new TypeScriptGeneric(
71-
new TypeScriptIdentifier('ActionParam'),
75+
new TypeScriptIdentifier('ActionController'),
7276
[
7377
new TypeScriptGenericTypeVariable(new TypeScriptIdentifier('TController')),
7478
new TypeScriptGenericTypeVariable(new TypeScriptIdentifier('TAction')),
7579
]
7680
),
7781
new TypeScriptConditional(
78-
TypeScriptOperator::extends(
79-
new TypeScriptIndexedAccess(
80-
new TypeScriptIdentifier('Routes'),
81-
[new TypeScriptIdentifier('TController')],
82-
),
83-
new TypeScriptObject([
84-
new TypeScriptProperty('invokable', new TypeScriptRaw('true')),
85-
])
86-
),
82+
$isInvokableControllerCondition,
8783
new TypeScriptIdentifier('TController'),
8884
new TypeScriptArray([
8985
new TypeScriptIdentifier('TController'),
9086
new TypeScriptIdentifier('TAction'),
9187
])
9288
)
9389
),
94-
null,
95-
'ActionParam',
90+
$actionControllerReference = new CustomReference('laravel_route_actions', 'action_controller'),
91+
'ActionController',
9692
true,
97-
[],
93+
$this->location,
9894
);
9995

96+
$actionParameters = new Transformed(
97+
new TypeScriptAlias(
98+
new TypeScriptGeneric(
99+
new TypeScriptIdentifier('ActionParameters'),
100+
[
101+
new TypeScriptGenericTypeVariable(new TypeScriptIdentifier('TController')),
102+
new TypeScriptGenericTypeVariable(new TypeScriptIdentifier('TAction')),
103+
]
104+
),
105+
new TypeScriptConditional(
106+
$isInvokableControllerCondition,
107+
new TypeScriptIndexedAccess(new TypeReference($routesListReference), [
108+
new TypeScriptIdentifier('TController'),
109+
new TypeScriptIdentifier('"parameters"'),
110+
]),
111+
new TypeScriptIndexedAccess(new TypeReference($routesListReference), [
112+
new TypeScriptIdentifier('TController'),
113+
new TypeScriptIdentifier('"actions"'),
114+
new TypeScriptIdentifier('TAction'),
115+
new TypeScriptIdentifier('"parameters"'),
116+
])
117+
)
118+
),
119+
$actionParametersReference = new CustomReference('laravel_route_actions', 'action_parameters'),
120+
'ActionParameters',
121+
true,
122+
$this->location,
123+
);
124+
125+
$jsonEncodedRoutes = json_encode($controllers->toJsObject(), flags: JSON_UNESCAPED_SLASHES);
126+
$baseUrl = url('/');
127+
100128
$transformedAction = new Transformed(
101129
new TypeScriptFunctionDefinition(
102130
new TypeScriptGeneric(
103131
new TypeScriptIdentifier('action'),
104132
[
105133
new TypeScriptGenericTypeVariable(
106134
new TypeScriptIdentifier('TController'),
107-
extends: new TypeScriptIdentifier('keyof Routes'),
135+
extends: TypeScriptOperator::keyof(new TypeReference($routesListReference))
108136
),
109137
new TypeScriptGenericTypeVariable(
110138
new TypeScriptIdentifier('TAction'),
111-
extends: new TypeScriptIdentifier('keyof Routes[TController]["actions"]'),
139+
extends: TypeScriptOperator::keyof(new TypeScriptIndexedAccess(new TypeReference($routesListReference), [
140+
new TypeScriptIdentifier('TController'),
141+
new TypeScriptIdentifier('"actions"'),
142+
]))
112143
),
113144
new TypeScriptGenericTypeVariable(
114145
new TypeScriptIdentifier('TParams'),
115-
extends: new TypeScriptIdentifier('Routes[TController]["actions"][TAction]["parameters"]'),
146+
extends: new TypeScriptIndexedAccess(new TypeReference($routesListReference), [
147+
new TypeScriptIdentifier('TController'),
148+
new TypeScriptIdentifier('"actions"'),
149+
new TypeScriptIdentifier('TAction'),
150+
new TypeScriptIdentifier('"parameters"'),
151+
])
116152
),
117153
]
118154
),
119155
[
120156
new TypeScriptParameter('action', new TypeScriptGeneric(
121-
new TypeScriptIdentifier('ActionParam'),
157+
new TypeReference($actionControllerReference),
122158
[
123159
new TypeScriptGenericTypeVariable(new TypeScriptIdentifier('TController')),
124160
new TypeScriptGenericTypeVariable(new TypeScriptIdentifier('TAction')),
125161
]
126162
)),
127-
new TypeScriptParameter('params', new TypeScriptIdentifier('TParams'), isOptional: true),
163+
new TypeScriptParameter('parameters', new TypeScriptGeneric(
164+
new TypeReference($actionParametersReference),
165+
[
166+
new TypeScriptGenericTypeVariable(new TypeScriptIdentifier('TController')),
167+
new TypeScriptGenericTypeVariable(new TypeScriptIdentifier('TAction')),
168+
]
169+
), isOptional: true),
128170
],
129171
new TypeScriptString(),
130-
new TypeScriptRaw("let routes = JSON.parse('".json_encode($controllers->toJsObject(), flags: JSON_UNESCAPED_SLASHES)."')")
172+
new TypeScriptRaw(<<<TS
173+
let routes = JSON.parse('$jsonEncodedRoutes');
174+
let baseUrl = '$baseUrl';
175+
176+
let found = typeof action === 'string'
177+
? routes.controllers[action]
178+
: routes.controllers[action[0]]['actions'][action[1]];
179+
180+
let url = baseUrl + '/' + found.url;
181+
182+
if(parameters) {
183+
for(let parameter in parameters) {
184+
url = url.replace('{' + parameter + '}', parameters[parameter]);
185+
}
186+
}
187+
188+
return url;
189+
TS
190+
)
191+
// new TypeScriptRaw("let routes = JSON.parse('".json_encode($controllers->toJsObject(), flags: JSON_UNESCAPED_SLASHES)."')")
131192
),
132-
null,
193+
new CustomReference('laravel_route_actions', 'action_function'),
133194
'action',
134195
true,
135-
[],
196+
$this->location,
136197
);
137198

138-
return [$transformedRoutes, $actionParam, $transformedAction];
199+
return [$transformedRoutes, $actionController, $actionParameters, $transformedAction];
139200
}
140201

141-
protected function parseRouteControllerCollection(RouteControllerCollection $collection): TypeScriptNode
202+
protected function parseRouteControllerCollection(RouteCollection $collection): TypeScriptNode
142203
{
143204
return new TypeScriptObject(collect($collection->controllers)->map(function (RouteController|RouteInvokableController $controller, string $name) {
144205
return new TypeScriptProperty(

src/Laravel/Routes/RouteClosure.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Spatie\TypeScriptTransformer\Laravel\Routes;
4+
5+
class RouteClosure implements RouterStructure
6+
{
7+
/**
8+
* @param array<string> $methods
9+
*/
10+
public function __construct(
11+
public RouteParameterCollection $parameters,
12+
public array $methods,
13+
public string $url,
14+
) {
15+
}
16+
17+
public function toJsObject(): array
18+
{
19+
return [
20+
'url' => $this->url,
21+
'methods' => array_values($this->methods),
22+
];
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Spatie\TypeScriptTransformer\Laravel\Routes;
4+
5+
class RouteCollection implements RouterStructure
6+
{
7+
/**
8+
* @param array<string, RouteController|RouteInvokableController> $controllers
9+
* @param array<string, RouteClosure> $closures
10+
*/
11+
public function __construct(
12+
public array $controllers,
13+
public array $closures,
14+
) {
15+
}
16+
17+
public function toJsObject(): array
18+
{
19+
return [
20+
'controllers' => collect($this->controllers)->map(fn (RouteController|RouteInvokableController $controller) => $controller->toJsObject())->all(),
21+
'closures' => collect($this->closures)->map(fn (RouteClosure $closure) => $closure->toJsObject())->all(),
22+
];
23+
}
24+
}

0 commit comments

Comments
 (0)