run method

  1. @override
Future<int> run()
override

Runs this command.

The return value is wrapped in a Future if necessary and returned by CommandRunner.runCommand.

Implementation

@override
Future<int> run() async {
  final logger = Logger();
  final rest = argResults?.rest ?? const [];
  final targetPath = rest.isEmpty ? '.' : rest.first;
  final absolutePath = p.absolute(targetPath);

  if (!Directory(absolutePath).existsSync()) {
    logger.err('Directory not found: $absolutePath');
    return 1;
  }

  final outputFormat = argResults!['output'] as String;
  final reportFile = argResults!['report-file'] as String?;
  final failOnError = argResults!['fail-on-error'] as bool;
  final isJson = outputFormat == 'json';
  final isHtml = outputFormat == 'html';
  final isQuiet = isJson; // JSON goes to stdout — keep progress UI quiet.

  final stopwatch = Stopwatch()..start();

  // ----- Scan -----
  final scanProgress = isQuiet ? null : logger.progress('Scanning project');
  final scanner = Scanner();
  final files = await scanner.scan(absolutePath);
  scanProgress?.complete('Scanned ${files.length} Dart files');

  if (files.isEmpty) {
    if (!isJson) logger.warn('No Dart files found in $absolutePath');
    return 0;
  }

  // ----- Parse -----
  final parseProgress = isQuiet ? null : logger.progress('Parsing AST');
  final parser = Parser();
  final parsed = await parser.parseAll(files);
  parseProgress?.complete('Parsed ${parsed.length} files');

  // ----- Run rules -----
  final rules = defaultRules();
  final ruleProgress = isQuiet
      ? null
      : logger.progress('Running ${rules.length} rules');
  final engine = RulesEngine(rules);
  final issues = engine.run(parsed);
  ruleProgress?.complete('Found ${issues.length} issues');

  // ----- Score & build report -----
  final scores = ScoreCalculator().compute(issues);
  final totalLines = parsed.fold<int>(0, (sum, f) => sum + f.lineCount);

  stopwatch.stop();

  final report = Report(
    projectName: p.basename(absolutePath),
    filesScanned: parsed.length,
    linesAnalyzed: totalLines,
    elapsed: stopwatch.elapsed,
    issues: issues,
    scores: scores,
    timestamp: DateTime.now(),
  );

  // ----- Render -----
  if (isHtml) {
    final outPath = reportFile ?? 'flutter_audit_report.html';
    final outFile = File(outPath);
    await outFile.writeAsString(
      HtmlReporter(projectRoot: absolutePath).render(report),
    );
    logger.info('');
    logger.info('HTML report written to ${outFile.absolute.path}');
  } else if (isJson) {
    if (reportFile != null) {
      await File(reportFile).writeAsString(JsonReporter().render(report));
    } else {
      stdout.write(JsonReporter().render(report));
      stdout.writeln();
    }
  } else {
    TerminalReporter(
      projectRoot: absolutePath,
      logger: logger,
    ).render(report);
  }

  // ----- Exit code -----
  if (failOnError) {
    final hasSerious = issues.any(
      (i) => i.severity == Severity.error || i.severity == Severity.critical,
    );
    if (hasSerious) return 2;
  }
  return 0;
}