Skip to content

Commit 4f2a5ba

Browse files
authored
fix(dart_frog_gen): detect rogue routes (#248)
1 parent 81adab7 commit 4f2a5ba

File tree

2 files changed

+159
-2
lines changed

2 files changed

+159
-2
lines changed

packages/dart_frog_gen/lib/src/build_route_configuration.dart

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ RouteConfiguration buildRouteConfiguration(Directory directory) {
2828
if (globalMiddleware != null) globalMiddleware
2929
];
3030
final routes = <RouteFile>[];
31+
final rogueRoutes = <RouteFile>[];
3132
final directories = _getRouteDirectories(
3233
directory: routesDirectory,
3334
routesDirectory: routesDirectory,
@@ -40,13 +41,15 @@ RouteConfiguration buildRouteConfiguration(Directory directory) {
4041
endpoints[endpoint]!.add(file);
4142
}
4243
},
44+
onRogueRoute: rogueRoutes.add,
4345
);
4446
final publicDirectory = Directory(path.join(directory.path, 'public'));
4547
return RouteConfiguration(
4648
globalMiddleware: globalMiddleware,
4749
middleware: middleware,
4850
directories: directories,
4951
routes: routes,
52+
rogueRoutes: rogueRoutes,
5053
endpoints: endpoints,
5154
serveStaticFiles: publicDirectory.existsSync(),
5255
);
@@ -58,6 +61,7 @@ List<RouteDirectory> _getRouteDirectories({
5861
required void Function(RouteFile route) onRoute,
5962
required void Function(MiddlewareFile route) onMiddleware,
6063
required void Function(String endpoint, RouteFile file) onEndpoint,
64+
required void Function(RouteFile route) onRogueRoute,
6165
}) {
6266
final directories = <RouteDirectory>[];
6367
final entities = directory.listSync().sorted();
@@ -87,11 +91,13 @@ List<RouteDirectory> _getRouteDirectories({
8791
directory: directory,
8892
routesDirectory: routesDirectory,
8993
onRoute: onRoute,
94+
onRogueRoute: onRogueRoute,
9095
),
9196
..._getRouteFilesForDynamicDirectories(
9297
directory: directory,
9398
routesDirectory: routesDirectory,
9499
onRoute: onRoute,
100+
onRogueRoute: onRogueRoute,
95101
),
96102
];
97103

@@ -123,6 +129,7 @@ List<RouteDirectory> _getRouteDirectories({
123129
onRoute: onRoute,
124130
onMiddleware: onMiddleware,
125131
onEndpoint: onEndpoint,
132+
onRogueRoute: onRogueRoute,
126133
),
127134
);
128135
}
@@ -135,6 +142,7 @@ List<RouteFile> _getRouteFilesForDynamicDirectories({
135142
required Directory directory,
136143
required Directory routesDirectory,
137144
required void Function(RouteFile route) onRoute,
145+
required void Function(RouteFile route) onRogueRoute,
138146
String prefix = '',
139147
}) {
140148
final files = <RouteFile>[];
@@ -149,12 +157,14 @@ List<RouteFile> _getRouteFilesForDynamicDirectories({
149157
directory: dynamicDirectory,
150158
routesDirectory: routesDirectory,
151159
onRoute: onRoute,
160+
onRogueRoute: onRogueRoute,
152161
prefix: newPrefix,
153162
);
154163
final dynamicSubset = _getRouteFilesForDynamicDirectories(
155164
directory: dynamicDirectory,
156165
routesDirectory: routesDirectory,
157166
onRoute: onRoute,
167+
onRogueRoute: onRogueRoute,
158168
prefix: newPrefix,
159169
);
160170
files.addAll([...subset, ...dynamicSubset]);
@@ -166,6 +176,7 @@ List<RouteFile> _getRouteFiles({
166176
required Directory directory,
167177
required Directory routesDirectory,
168178
required void Function(RouteFile route) onRoute,
179+
required void Function(RouteFile route) onRogueRoute,
169180
String prefix = '',
170181
}) {
171182
final files = <RouteFile>[];
@@ -175,6 +186,10 @@ List<RouteFile> _getRouteFiles({
175186
? directorySegment
176187
: '/$directorySegment';
177188
final entities = directory.listSync().sorted();
189+
final subDirectories = entities
190+
.whereType<Directory>()
191+
.map((directory) => path.basename(directory.path))
192+
.toSet();
178193
entities.where((e) => e.isRoute).cast<File>().forEach((entity) {
179194
final filePath = path
180195
.relative(entity.path, from: routesDirectory.path)
@@ -209,6 +224,10 @@ List<RouteFile> _getRouteFiles({
209224
);
210225
onRoute(route);
211226
files.add(route);
227+
228+
if (subDirectories.contains(path.basenameWithoutExtension(filePath))) {
229+
onRogueRoute(route);
230+
}
212231
});
213232
return files;
214233
}
@@ -261,6 +280,7 @@ class RouteConfiguration {
261280
required this.directories,
262281
required this.routes,
263282
required this.endpoints,
283+
required this.rogueRoutes,
264284
this.serveStaticFiles = false,
265285
});
266286

@@ -283,6 +303,34 @@ class RouteConfiguration {
283303

284304
/// A map of all endpoint paths to resolved route files.
285305
final Map<String, List<RouteFile>> endpoints;
306+
307+
/// List of all rogue routes.
308+
///
309+
/// A route is considered rogue when it is defined outside
310+
/// of an existing subdirectory with the same name.
311+
///
312+
/// For example:
313+
///
314+
/// ```
315+
/// ├── routes
316+
/// │ ├── foo
317+
/// │ │ └── example.dart
318+
/// │ ├── foo.dart
319+
/// ```
320+
///
321+
/// In the above scenario, `foo.dart` is rogue because it is defined
322+
/// outside of the existing `foo` directory.
323+
///
324+
/// Instead, `foo.dart` should be renamed to `index.dart` and placed within
325+
/// the `foo` directory like:
326+
///
327+
/// ```
328+
/// ├── routes
329+
/// │ ├── foo
330+
/// │ │ ├── example.dart
331+
/// │ │ └── index.dart
332+
/// ```
333+
final List<RouteFile> rogueRoutes;
286334
}
287335

288336
/// {@template route_directory}
@@ -336,7 +384,7 @@ class RouteDirectory {
336384
}
337385

338386
/// {@template route_file}
339-
/// A class containing metadata regarding a route directory.
387+
/// A class containing metadata regarding a route file.
340388
/// {@endtemplate}
341389
class RouteFile {
342390
/// {@macro route_file}
@@ -346,7 +394,7 @@ class RouteFile {
346394
required this.route,
347395
});
348396

349-
/// The alias for the current directory.
397+
/// The alias for the current file.
350398
final String name;
351399

352400
/// The import path for the current instance.

packages/dart_frog_gen/test/src/build_route_configuration_test.dart

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,5 +696,114 @@ void main() {
696696
}),
697697
);
698698
});
699+
700+
test('detects rogue routes.', () {
701+
const expected = [
702+
{
703+
'name': '_',
704+
'route': '/',
705+
'middleware': false,
706+
'files': [
707+
{'name': 'api', 'path': '../routes/api.dart', 'route': '/api'}
708+
]
709+
},
710+
{
711+
'name': '_api',
712+
'route': '/api',
713+
'middleware': false,
714+
'files': [
715+
{'name': 'api_v1', 'path': '../routes/api/v1.dart', 'route': '/v1'},
716+
{
717+
'name': r'api_$id',
718+
'path': '../routes/api/[id].dart',
719+
'route': '/<id>'
720+
}
721+
]
722+
},
723+
{
724+
'name': '_api_v1',
725+
'route': '/api/v1',
726+
'middleware': false,
727+
'files': [
728+
{
729+
'name': 'api_v1_hello',
730+
'path': '../routes/api/v1/hello.dart',
731+
'route': '/hello'
732+
}
733+
]
734+
}
735+
];
736+
final directory = Directory(
737+
path.join(
738+
Directory.current.path,
739+
'test',
740+
'.fixtures',
741+
'rogue_routes',
742+
),
743+
)..createSync(recursive: true);
744+
final routes = Directory(path.join(directory.path, 'routes'))
745+
..createSync();
746+
File(path.join(routes.path, 'api.dart')).createSync();
747+
final apiDirectory = Directory(path.join(routes.path, 'api'))
748+
..createSync();
749+
File(path.join(apiDirectory.path, '[id].dart')).createSync();
750+
File(path.join(apiDirectory.path, 'v1.dart')).createSync();
751+
final v1Directory = Directory(path.join(apiDirectory.path, 'v1'))
752+
..createSync();
753+
File(path.join(v1Directory.path, 'hello.dart')).createSync();
754+
final configuration = buildRouteConfiguration(directory);
755+
expect(
756+
configuration.directories.map((d) => d.toJson()).toList(),
757+
equals(expected),
758+
);
759+
expect(
760+
configuration.endpoints,
761+
equals({
762+
'/api': [
763+
isA<RouteFile>().having(
764+
(r) => r.path,
765+
'path',
766+
'../routes/api.dart',
767+
)
768+
],
769+
'/api/v1': [
770+
isA<RouteFile>().having(
771+
(r) => r.path,
772+
'path',
773+
'../routes/api/v1.dart',
774+
)
775+
],
776+
'/api/<id>': [
777+
isA<RouteFile>().having(
778+
(r) => r.path,
779+
'path',
780+
'../routes/api/[id].dart',
781+
)
782+
],
783+
'/api/v1/hello': [
784+
isA<RouteFile>().having(
785+
(r) => r.path,
786+
'path',
787+
'../routes/api/v1/hello.dart',
788+
)
789+
],
790+
}),
791+
);
792+
expect(
793+
configuration.rogueRoutes,
794+
equals([
795+
isA<RouteFile>().having(
796+
(r) => r.path,
797+
'path',
798+
'../routes/api.dart',
799+
),
800+
isA<RouteFile>().having(
801+
(r) => r.path,
802+
'path',
803+
'../routes/api/v1.dart',
804+
)
805+
]),
806+
);
807+
});
699808
});
700809
}

0 commit comments

Comments
 (0)