analyzeSymbolImpact method

ImpactResult analyzeSymbolImpact({
  1. required List<String> changedFiles,
  2. List<String>? changedSymbolIds,
  3. Duration analysisDuration = Duration.zero,
  4. bool isGitRepo = true,
  5. bool gitRequiresFallback = false,
  6. String? gitFallbackReason,
  7. bool isConservative = false,
  8. bool forceAll = false,
  9. bool hasAnalysisErrors = false,
  10. String? analysisErrorReason,
})

Implementation

ImpactResult analyzeSymbolImpact({
  required List<String> changedFiles,
  List<String>? changedSymbolIds,
  Duration analysisDuration = Duration.zero,
  bool isGitRepo = true,
  bool gitRequiresFallback = false,
  String? gitFallbackReason,
  bool isConservative = false,
  bool forceAll = false,
  bool hasAnalysisErrors = false,
  String? analysisErrorReason,
}) {
  // If fallback is triggered at git or config level, run standard fallback
  if (forceAll || !isGitRepo || gitRequiresFallback || hasAnalysisErrors) {
    return fallbackFileAnalyzer.analyzeImpact(
      changedFiles: changedFiles,
      analysisDuration: analysisDuration,
      isGitRepo: isGitRepo,
      gitRequiresFallback: gitRequiresFallback,
      gitFallbackReason: gitFallbackReason,
      isConservative: isConservative,
      forceAll: forceAll,
      hasAnalysisErrors: hasAnalysisErrors,
      analysisErrorReason: analysisErrorReason,
    );
  }

  final testPaths = discoveredTests.map((t) => t.path).toList();
  final allSymbols = symbolGraph.getAllSymbols();

  // Determine changed symbols
  final List<SymbolNode> changedSymbols = [];
  if (changedSymbolIds != null && changedSymbolIds.isNotEmpty) {
    for (final id in changedSymbolIds) {
      final sym = symbolGraph.getSymbol(id);
      if (sym != null) {
        changedSymbols.add(sym);
      } else {
        // If a symbol ID was specified but not found in graph, construct transient symbol
        final parts = id.split('#');
        final file = parts.first;
        final name = parts.length > 1 ? parts.last : id;
        changedSymbols.add(SymbolNode(
          id: id,
          filePath: file,
          symbolName: name,
          symbolType: 'method',
        ));
      }
    }
  } else {
    for (final sym in allSymbols) {
      if (changedFiles.contains(sym.filePath)) {
        changedSymbols.add(sym);
      }
    }
  }

  // If no symbols resolved for changed dart files (or graph empty), fallback to file-level
  final changedDartFiles = changedFiles.where((f) => f.endsWith('.dart')).toList();
  if (symbolGraph.totalNodes == 0 || (changedDartFiles.isNotEmpty && changedSymbols.isEmpty)) {
    return fallbackFileAnalyzer.analyzeImpact(
      changedFiles: changedFiles,
      analysisDuration: analysisDuration,
      isGitRepo: isGitRepo,
      gitRequiresFallback: gitRequiresFallback,
      gitFallbackReason: gitFallbackReason,
      isConservative: isConservative,
      forceAll: forceAll,
      hasAnalysisErrors: hasAnalysisErrors,
      analysisErrorReason: analysisErrorReason,
    );
  }

  final Set<String> affectedTestPaths = {};
  final List<SymbolImpactPath> impactPaths = [];
  final List<AffectedTestReason> affectedReasons = [];

  for (final changedSym in changedSymbols) {
    final dependents = symbolGraph.getTransitiveDependents(changedSym.id);

    for (final entry in dependents.entries) {
      final dependentId = entry.key;
      final chain = entry.value;

      final dependentNode = symbolGraph.getSymbol(dependentId);
      if (dependentNode != null && (dependentNode.isTest || dependentNode.filePath.startsWith('test/'))) {
        affectedTestPaths.add(dependentNode.filePath);

        final path = SymbolImpactPath(
          testPath: dependentNode.filePath,
          changedSymbol: changedSym.symbolName,
          targetSymbol: dependentNode.symbolName,
          symbolChain: chain,
        );
        impactPaths.add(path);

        affectedReasons.add(AffectedTestReason(
          testPath: dependentNode.filePath,
          dependencyChain: [changedSym.symbolName, ...chain],
        ));
      }
    }
  }

  final Map<String, AffectedTestReason> uniqueAffected = {};
  for (final r in affectedReasons) {
    uniqueAffected[r.testPath] = r;
  }

  final unaffectedTests = testPaths.where((t) => !affectedTestPaths.contains(t)).toList();
  unaffectedTests.sort();

  final modeStr = isConservative ? 'conservative' : 'default';

  return ImpactResult(
    changedFiles: changedFiles,
    affectedTests: uniqueAffected.values.toList(),
    unaffectedTests: unaffectedTests,
    totalTests: testPaths.length,
    analysisDuration: analysisDuration,
    status: 'safe',
    mode: modeStr,
    isFallback: false,
    fallbackReason: null,
    fallbackExplanation: null,
    analysisLevel: 'symbol',
    changedSymbols: changedSymbols.map((s) => s.symbolName).toList(),
    impactPaths: impactPaths,
  );
}