scan method

  1. @override
Future<RuntimeScannerSummary> scan(
  1. String outputFolder,
  2. RuntimeScannerConfiguration configuration, {
  3. Directory? source,
})
override

Defines the contract for a reflection scanner that processes Dart source files, extracts metadata, and optionally persists output.

Used during framework initialization or tooling that requires reflection metadata (e.g., code analyzers, documentation generators, or runtime scanners).

Implementations should handle scanning efficiently and report meaningful summaries including errors, warnings, and informational messages.

Example

final scanner = MyRuntimeScanner();
final loader = RuntimeScanLoader(
  reload: true,
  updatePackages: false,
  updateAssets: true,
  baseFilesToScan: [File('lib/main.dart')],
  packagesToScan: ['package:meta/', 'package:args/'],
);
final summary = await scanner.scan('build/meta', loader);

print(summary.getErrors());

Performs the reflection scan and outputs a RuntimeScannerSummary.

  • outputFolder is the target directory to write scan results.
  • loader is the configuration for the scan.
  • source is the root directory to scan. Defaults to Directory.current.

Returns a Future that resolves to the final RuntimeScannerSummary.

Implementation

@override
Future<RuntimeScannerSummary> scan(String outputFolder, RuntimeScannerConfiguration configuration, {Directory? source}) async {
  bool refreshContext = _context == null || !configuration.reload;
  final stopwatch = Stopwatch()..start();
  FileUtility FileUtils = FileUtility(_logInfo, _logWarning, _logError, configuration, true);

  // 1. Setup directory and verify its existence
  if(refreshContext) {
    _logInfo('Creating target directory structure...');
  }

  Directory directory = source ?? Directory.current;

  // 2. Read package name from pubspec.yaml
  _package ??= await FileUtils.readPackageName();

  Directory outputDirectory = Directory(p.join(directory.path, outputFolder));
  if(!await outputDirectory.exists()) {
    await outputDirectory.create(recursive: true);
  } else {
    await outputDirectory.delete(recursive: true);
    await outputDirectory.create(recursive: true);
  }

  // 3. Add default packages to scan if none specified
  configuration = _addDefaultPackagesToScan(configuration, _package!);

  _logInfo("${refreshContext ? "Reloading" : "Scanning"} $_package application...");
  Set<File> dartFiles = {};
  Set<File> nonDartFiles = {};
  List<Asset> resources = [];
  List<Package> packages = [];

  if(refreshContext) {
    dartFiles = await FileUtils.findDartFiles(directory);
    nonDartFiles = await FileUtils.findNonDartFiles(directory);
    resources = await FileUtils.discoverAllResources(_package!);
    packages = await FileUtils.readPackageGraphDependencies(directory);
  } else {
    // For non-rebuilds, only process additions/removals if specified
    if(configuration.additions.isNotEmpty || configuration.removals.isNotEmpty || configuration.filesToScan.isNotEmpty) {
      dartFiles = (configuration.filesToScan + configuration.additions).where((file) => file.path.endsWith('.dart')).toSet();
      nonDartFiles = (configuration.filesToScan + configuration.additions).where((file) => !file.path.endsWith('.dart')).toSet();
    }

    if(configuration.updateAssets) {
      resources = await FileUtils.discoverAllResources(_package!);
    }

    if(configuration.updatePackages) {
      packages = await FileUtils.readPackageGraphDependencies(directory);
    }
  }

  _logInfo("Found ${dartFiles.length} dart files.");
  _logInfo("Found ${nonDartFiles.length} non-dart files.");
  _logInfo("Found ${resources.length} resources.");
  _logInfo("Found ${packages.length} packages.");

  List<LibraryDeclaration> libraries = [];
  List<TypeDeclaration> specialTypes = [];

  // 3. Setup mirror system and access domain
  _logInfo('Setting up mirror system and access domain...');
  mirrors.MirrorSystem access = mirrors.currentMirrorSystem();
  _logInfo('Mirror system and access domain set up.');

  // 4. Load dart files that are not present in the [currentMirrorSystem]
  _logInfo('Loading dart files that are not present in the [currentMirrorSystem#${access.isolate.debugName}]...');
  Map<File, Uri> urisToLoad = FileUtils.getUrisToLoad(dartFiles, _package!);
  List<mirrors.LibraryMirror> forceLoadedMirrors = [];
  for (final uriEntry in urisToLoad.entries) {
    if(RuntimeUtils.isNonLoadableJetLeafFile(uriEntry.value) || await RuntimeUtils.shouldNotIncludeLibrary(uriEntry.value, configuration)) {
      continue;
    }

    mirrors.LibraryMirror? mirror = await FileUtils.forceLoadLibrary(uriEntry.value, uriEntry.key, access);
    if(mirror != null) {
      forceLoadedMirrors.add(mirror);
    }
  }

  // 5. Generate reflection metadata
  _logInfo('Resolving declaration metadata libraries...');
  LibraryGenerator libraryGenerator = ApplicationLibraryGenerator(
    mirrorSystem: access,
    forceLoadedMirrors: forceLoadedMirrors,
    onInfo: _logInfo,
    onWarning: _logWarning,
    onError: _logError,
    configuration: configuration,
    packages: packages,
  );
  final result = await libraryGenerator.generate(dartFiles.toList());
  _logInfo('Resolved ${result.length} declaration libraries.');

  libraries.addAll(result);

  // 6. Generate AOT Runtime Resolvers
  RuntimeResolving resolving = RuntimeResolving(
    access: access,
    libraries: libraries,
    forceLoadedMirrors: forceLoadedMirrors,
    outputFolder: outputFolder,
    fileUtils: FileUtils,
    package: _package!,
    logInfo: _logInfo,
    logWarning: _logWarning,
    logError: _logError,
  );

  if(refreshContext) {
    _context = StandardRuntimeProvider();
  }
  _context?.setRuntimeResolver(await resolving.resolve());

  if(resources.isNotEmpty) {
    _context?.addAssets(resources, replace: refreshContext);
  }

  if(packages.isNotEmpty) {
    _context?.addPackages(packages, replace: refreshContext);
  }

  if(libraries.isNotEmpty) {
    _context?.addLibraries(libraries, replace: refreshContext);
  }

  // Handle removals (now configuration.removals) by removing them from the context
  if(configuration.removals.isNotEmpty) {
    final libs = _context?.getAllLibraries() ?? [];
    final urisToRemove = configuration.removals.map((f) => FileUtils.resolveToPackageUri(f.absolute.path, _package!, FileUtils.packageConfig)).whereType<String>().toSet();
    final updatedLibs = libs.where((lib) => !urisToRemove.contains(lib.getUri())).toList();
    _context?.addLibraries(updatedLibs, replace: true);
  }

  if(specialTypes.isNotEmpty) {
    _context?.addSpecialTypes(specialTypes, replace: refreshContext);
  }

  if(nonDartFiles.isNotEmpty) {
    (_context as StandardRuntimeProvider?)?.addNonDartFiles(nonDartFiles.toList(), replace: refreshContext);
  }

  stopwatch.stop();
  _logInfo("Application ${refreshContext ? "reloading" : "scanning"} completed in ${stopwatch.elapsedMilliseconds}ms.");

  ConfigurableRuntimeScannerSummary summary = DefaultRuntimeScannerSummary();
  summary.setContext(_context!);
  summary.setBuildTime(DateTime.fromMillisecondsSinceEpoch(stopwatch.elapsedMilliseconds));
  summary.addInfos(_infoLogs);
  summary.addWarnings(_warningLogs);
  summary.addErrors(_errorLogs);

  return summary;
}