scan function
Scans the project filesystem and builds a RouteTree.
Implementation
Future<RouteTree> scan(BuildConfig config) async {
final root = config.rootDir;
final routesRoot = Directory(p.join(root, config.routesDir));
final middlewareRoot = Directory(p.join(root, config.middlewareDir));
final globalMiddleware = <MiddlewareEntry>[];
final scopedMiddleware = <MiddlewareEntry>[];
final scopedErrors = <ErrorEntry>[];
final routes = <RouteEntry>[];
RouteEntry? fallback;
if (await middlewareRoot.exists()) {
final files = await _collectDartFiles(middlewareRoot, recursive: false);
files.sort((a, b) => p.basename(a.path).compareTo(p.basename(b.path)));
for (final file in files) {
final parsed = _parseScopedHandlerFile(
p.basename(file.path),
expectedBaseName: null,
)!;
globalMiddleware.add(
MiddlewareEntry(filePath: file.path, path: '/*', method: parsed.method),
);
}
}
final seenRoutes = <String, String>{};
final seenShapes = <String, _ShapeRecord>{};
final catchAllKindsByDir = <String, bool?>{};
if (await routesRoot.exists()) {
final files = await _collectDartFiles(routesRoot, recursive: true);
files.sort((a, b) => a.path.compareTo(b.path));
for (final file in files) {
final relativePath = p.relative(file.path, from: routesRoot.path);
final segments = p.split(relativePath);
final fileName = segments.last;
final dirSegments = segments.take(segments.length - 1).toList();
if (dirSegments.any((segment) => segment.startsWith('_'))) {
continue;
}
final scopedMiddlewareFile = _parseScopedHandlerFile(
fileName,
expectedBaseName: '_middleware',
);
if (scopedMiddlewareFile != null) {
scopedMiddleware.add(
MiddlewareEntry(
filePath: file.path,
path: _scopePath(dirSegments),
method: scopedMiddlewareFile.method,
),
);
continue;
}
final scopedErrorFile = _parseScopedHandlerFile(
fileName,
expectedBaseName: '_error',
);
if (scopedErrorFile != null) {
scopedErrors.add(
ErrorEntry(
filePath: file.path,
path: _scopePath(dirSegments),
method: scopedErrorFile.method,
),
);
continue;
}
if (fileName.startsWith('_')) {
continue;
}
final parsed = _parseRouteFile(relativePath);
final dirKey = p.dirname(relativePath);
if (parsed.catchAllKind != null) {
final previous = catchAllKindsByDir[dirKey];
if (previous != null && previous != parsed.catchAllKind) {
throw RouteScanException(
'Conflicting catch-all files in "$dirKey": both named and unnamed catch-all routes are present.',
);
}
catchAllKindsByDir[dirKey] = parsed.catchAllKind;
}
final routeKey = '${parsed.method ?? '*'} ${parsed.path}';
final previous = seenRoutes[routeKey];
if (previous != null) {
throw RouteScanException(
'Duplicate route "$routeKey" declared by "$previous" and "$relativePath".',
);
}
seenRoutes[routeKey] = relativePath;
final shapeKey = parsed.shapePath;
final shapeRecord = seenShapes[shapeKey];
if (shapeRecord != null &&
!_sameNames(shapeRecord.names, parsed.paramNames)) {
throw RouteScanException(
'Param-name drift for route shape "$shapeKey": "${shapeRecord.source}" and "$relativePath".',
);
}
seenShapes.putIfAbsent(
shapeKey,
() => _ShapeRecord(relativePath, parsed.paramNames),
);
final entry = RouteEntry(
filePath: file.path,
path: parsed.path,
method: parsed.method,
wildcardParam: parsed.wildcardParam,
);
if (parsed.isRootFallback && fallback == null) {
fallback = entry;
} else {
routes.add(entry);
}
}
}
final hooksFile = File(p.join(root, 'hooks.dart'));
return RouteTree(
routes: routes,
globalMiddleware: globalMiddleware,
scopedMiddleware: scopedMiddleware,
scopedErrors: scopedErrors,
fallback: fallback,
hooks: await hooksFile.exists() ? await _scanHooks(hooksFile) : null,
);
}