analyzeFile method

Future<DataFlowResult> analyzeFile({
  1. required String filePath,
  2. required int startLine,
  3. required int endLine,
  4. String methodName = '_extracted',
})

Analyzes a file on disk by file path and 1-based line bounds.

Implementation

Future<DataFlowResult> analyzeFile({
  required String filePath,
  required int startLine,
  required int endLine,
  String methodName = '_extracted',
}) async {
  final absPath = p.normalize(p.absolute(filePath));
  final file = File(absPath);
  if (!file.existsSync()) {
    throw FileSystemException('Target file does not exist', filePath);
  }

  final helper = AnalysisContextHelper(
    includedPaths: [absPath],
    sdkPath: sdkPath,
  );
  final unitResult = await helper.getRequiredResolvedUnit(absPath);

  final lineInfo = unitResult.lineInfo;
  if (startLine < 1 || endLine < startLine) {
    throw FormatException(
      'Invalid line bounds: startLine ($startLine) must be >= 1 and '
      '<= endLine ($endLine)',
    );
  }

  final totalLines = lineInfo.lineCount;
  if (startLine > totalLines) {
    throw FormatException(
      'Start line $startLine exceeds total file lines ($totalLines)',
    );
  }

  final startOffset = lineInfo.getOffsetOfLine(startLine - 1);
  final endOffset = endLine >= totalLines
      ? unitResult.unit.end
      : lineInfo.getOffsetOfLine(endLine) - 1;

  final locator = _EnclosingDeclarationVisitor(
    startOffset,
    endLine,
    lineInfo,
  );
  unitResult.unit.accept(locator);
  final enclosingNode = locator.enclosing;

  if (enclosingNode == null) {
    throw FormatException(
      'Target line range $startLine-$endLine does not fall entirely within '
      'a single function or method declaration.',
    );
  }

  final enclosingName = switch (enclosingNode) {
    FunctionDeclaration(:final name) ||
    MethodDeclaration(:final name) => name.lexeme,
    ConstructorDeclaration(:final name) => name?.lexeme ?? 'new',
    _ => 'unknown',
  };

  // 1. Traverse within slice for inputs, mutations, escapes, and async
  final inBlockVisitor = InBlockVisitor(
    sliceStartOffset: startOffset,
    sliceEndOffset: endOffset,
    lineInfo: lineInfo,
  );
  enclosingNode.accept(inBlockVisitor);

  // 2. Traverse after slice for liveness analysis (outputs)
  final loopCollector = _EnclosingLoopCollector(startOffset, endOffset);
  enclosingNode.accept(loopCollector);
  final postBlockVisitor = PostBlockVisitor(
    sliceStartOffset: startOffset,
    sliceEndOffset: endOffset,
    lineInfo: lineInfo,
    internalDeclarations: inBlockVisitor.internalDeclarations,
    mutations: inBlockVisitor.mutations,
    enclosingLoopSpans: loopCollector.spans,
  );
  enclosingNode.accept(postBlockVisitor);

  final inputList = inBlockVisitor.inputs.values.toList();
  final mutationList = inBlockVisitor.mutations.values.toList();
  final outputList = postBlockVisitor.liveOutputs.values.toList();
  final escapes = inBlockVisitor.escapes;

  final typeParams = _extractTypeParams(enclosingNode, inputList, outputList);

  final signature = synthesizer.synthesize(
    inputs: inputList,
    outputs: outputList,
    typeParameters: typeParams,
    methodName: methodName,
    isAsync: inBlockVisitor.hasAwait,
  );

  final (:enclosingScore, :sliceScoreInPlace, :sliceScoreAtRoot) =
      _computeComplexityImpact(enclosingNode, startOffset, endOffset);
  final warnings = _buildExtractionWarnings(
    inputCount: inputList.length,
    sliceLineCount: endLine - startLine + 1,
    enclosingScore: enclosingScore,
    sliceScoreInPlace: sliceScoreInPlace,
  );

  return DataFlowResult(
    filePath: filePath,
    startLine: startLine,
    endLine: endLine,
    enclosingDeclaration: enclosingName,
    inputs: inputList,
    mutations: mutationList,
    outputs: outputList,
    escapes: escapes,
    suggestedSignature: signature,
    isCleanlyExtractable: escapes.isEmpty,
    enclosingScore: enclosingScore,
    sliceScoreInPlace: sliceScoreInPlace,
    sliceScoreAtRoot: sliceScoreAtRoot,
    extractionWarnings: warnings,
  );
}