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; } }