run method
Implementation
Future<void> run() async {
final parser = ArgParser()
..addCommand('analyze')
..addCommand('affected')
..addCommand('run');
final affectedCmd = parser.commands['affected'];
if (affectedCmd != null) {
affectedCmd.addFlag('json', negatable: false, help: 'Output result in JSON format');
affectedCmd.addFlag('conservative', negatable: false, help: 'Conservative mode for fallback safety');
affectedCmd.addFlag('symbol-level', negatable: false, help: 'Use symbol-level dependency analysis');
affectedCmd.addFlag('explain', negatable: false, help: 'Explain symbol impact paths');
}
final runCmd = parser.commands['run'];
if (runCmd != null) {
runCmd.addFlag('json', negatable: false, help: 'Output result in JSON format');
runCmd.addFlag('all', negatable: false, help: 'Always run full test suite');
runCmd.addFlag('conservative', negatable: false, help: 'Conservative mode for fallback safety');
runCmd.addFlag('symbol-level', negatable: false, help: 'Use symbol-level dependency analysis');
}
ArgResults results;
try {
results = parser.parse(args);
} catch (e) {
_printUsage(parser);
exit(64);
}
final command = results.command;
if (command == null) {
_printUsage(parser);
return;
}
final cache = AnalysisCache(projectRoot: currentDirectory)..load();
final projectAnalyzer = ProjectAnalyzer(
projectRoot: currentDirectory,
cache: cache,
);
late ProjectAnalysisResult analysis;
bool hasAnalysisErrors = false;
String? analysisErrorReason;
try {
analysis = projectAnalyzer.analyze();
cache.save();
if (analysis.warnings.isNotEmpty) {
hasAnalysisErrors = true;
analysisErrorReason = 'syntax_error';
}
} catch (e) {
hasAnalysisErrors = true;
analysisErrorReason = 'analysis_failed';
}
switch (command.name) {
case 'analyze':
for (final warning in analysis.warnings) {
print(warning);
}
CliOutput.printAnalysis(
fileCount: analysis.sourceFiles.length,
testCount: analysis.testFiles.length,
dependencyCount: analysis.graph.totalEdges,
);
break;
case 'affected':
final isConservative = command['conservative'] == true;
final useSymbolLevel = command['symbol-level'] == true;
final explain = command['explain'] == true;
final gitDetector = GitChangeDetector(projectRoot: currentDirectory);
final gitResult = await gitDetector.detectChanges();
final fileImpactAnalyzer = ImpactAnalyzer(
graph: analysis.graph,
discoveredTests: analysis.testFiles,
);
late final impactResult;
if (useSymbolLevel) {
final symbolAnalyzer = SymbolAnalyzer(
projectRoot: currentDirectory,
packageName: analysis.packageName,
);
final symbolResult = await symbolAnalyzer.analyzeProject();
final symbolImpactAnalyzer = SymbolImpactAnalyzer(
symbolGraph: symbolResult.graph,
discoveredTests: analysis.testFiles,
fallbackFileAnalyzer: fileImpactAnalyzer,
);
impactResult = symbolImpactAnalyzer.analyzeSymbolImpact(
changedFiles: gitResult.changedFiles,
isGitRepo: gitResult.isGitRepository,
gitRequiresFallback: gitResult.requiresFallback,
gitFallbackReason: gitResult.fallbackReason,
isConservative: isConservative,
hasAnalysisErrors: hasAnalysisErrors,
analysisErrorReason: analysisErrorReason,
);
} else {
impactResult = fileImpactAnalyzer.analyzeImpact(
changedFiles: gitResult.changedFiles,
isGitRepo: gitResult.isGitRepository,
gitRequiresFallback: gitResult.requiresFallback,
gitFallbackReason: gitResult.fallbackReason,
isConservative: isConservative,
hasAnalysisErrors: hasAnalysisErrors,
analysisErrorReason: analysisErrorReason,
);
}
if (command['json'] == true) {
CliOutput.printJson(impactResult);
} else {
CliOutput.printAffected(impactResult, warningMessage: gitResult.warningMessage, explain: explain);
}
break;
case 'run':
final forceAll = command['all'] == true;
final isConservative = command['conservative'] == true;
final useSymbolLevel = command['symbol-level'] == true;
final gitDetector = GitChangeDetector(projectRoot: currentDirectory);
final gitResult = await gitDetector.detectChanges();
final fileImpactAnalyzer = ImpactAnalyzer(
graph: analysis.graph,
discoveredTests: analysis.testFiles,
);
late final impactResult;
if (useSymbolLevel) {
final symbolAnalyzer = SymbolAnalyzer(
projectRoot: currentDirectory,
packageName: analysis.packageName,
);
final symbolResult = await symbolAnalyzer.analyzeProject();
final symbolImpactAnalyzer = SymbolImpactAnalyzer(
symbolGraph: symbolResult.graph,
discoveredTests: analysis.testFiles,
fallbackFileAnalyzer: fileImpactAnalyzer,
);
impactResult = symbolImpactAnalyzer.analyzeSymbolImpact(
changedFiles: gitResult.changedFiles,
isGitRepo: gitResult.isGitRepository,
gitRequiresFallback: gitResult.requiresFallback,
gitFallbackReason: gitResult.fallbackReason,
isConservative: isConservative,
forceAll: forceAll,
hasAnalysisErrors: hasAnalysisErrors,
analysisErrorReason: analysisErrorReason,
);
} else {
impactResult = fileImpactAnalyzer.analyzeImpact(
changedFiles: gitResult.changedFiles,
isGitRepo: gitResult.isGitRepository,
gitRequiresFallback: gitResult.requiresFallback,
gitFallbackReason: gitResult.fallbackReason,
isConservative: isConservative,
forceAll: forceAll,
hasAnalysisErrors: hasAnalysisErrors,
analysisErrorReason: analysisErrorReason,
);
}
if (command['json'] == true) {
CliOutput.printJson(impactResult);
}
if (impactResult.affectedTests.isEmpty && !impactResult.isFallback) {
print('No affected tests detected.');
return;
}
if (impactResult.isFallback) {
print('\u26a0 ' + (impactResult.fallbackExplanation ?? ''));
print('Running ALL ' + impactResult.totalTests.toString() + ' test(s)...\n');
} else {
print('Running ' + impactResult.affectedCount.toString() + ' affected test(s)...\n');
}
final runner = TestRunner(
projectRoot: currentDirectory,
isFlutterProject: analysis.isFlutterProject,
);
final testPathsToRun = impactResult.isFallback
? analysis.testFiles.map((t) => t.path).toList()
: impactResult.affectedTests.map((a) => a.testPath).toList();
final testResult = await runner.runTests(testPathsToRun);
print(testResult.stdout);
if (testResult.stderr.isNotEmpty) {
stderr.write(testResult.stderr);
}
exit(testResult.exitCode);
default:
_printUsage(parser);
}
}