scan function

Future<RouteTree> scan(
  1. BuildConfig config
)

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,
  );
}