using Microsoft.CodeAnalysis;
namespace ExtractMethod.Tooling;
/// Everything the extract-method report is built from: the resolved
/// selection, the raw classification buckets (em 02), the extract-first
/// candidates (em 03), the promoted signature line (em 03) and the v1
/// limitation notes. One record so the CLI and the tests compose EXACTLY the
/// same thing — the report cannot drift between them.
public sealed record ExtractionReport(
SelectionReport Selection,
ExtractionSuggestion Suggestion,
IReadOnlyList ExtractFirst,
SignatureSuggestion Signature,
IReadOnlyList Notes);
///
/// Composes the full extraction suggestion for a resolved selection: the em 02
/// data-flow buckets, the em 03 extract-first scan, the promoted signature and
/// the limitation notes. Throws the same clean exceptions as
/// when analysis cannot bind.
///
public static class ExtractionReporter
{
public static ExtractionReport Compose(SemanticModel model, SelectionReport selection)
{
var suggestion = DataFlowClassifier.Classify(model, selection);
var extractFirst = ExtractFirstScanner.Scan(model, selection);
var signature = SignatureBuilder.Build(model, selection, suggestion);
return new ExtractionReport(selection, suggestion, extractFirst, signature, BuildNotes(model, selection));
}
///
/// The v1 limitation notes (parent decision #5 and the em 02 contract):
/// each note is printed only when its limitation actually applies to the
/// selection, so a clean selection gets a clean report.
///
private static IReadOnlyList BuildNotes(SemanticModel model, SelectionReport selection)
{
var notes = new List();
if (ExtractFirstScanner.HasNonVariableAssignmentLeftSide(model, selection))
{
notes.Add(
"reads-only scan (decision 5): assignment left-hand sides are skipped, " +
"so writes to fields/indexers/properties are invisible to this report");
}
if (DataFlowClassifier.CompositeTrailingReturn(selection) is { } trailing)
{
notes.Add(
$"the selection ends with a composite return expression ({trailing.Expression!.ToFullString().Trim()}) — " +
"extract it into a local first to make the return value nameable");
}
return notes;
}
}