- ExtractFirstScanner: reads-only scan of non-local/param read expressions (member/field/property access, element access, invocation, cast), deduped by symbol+text with occurrence counts; invocations flagged (hoisting changes eval count); own-class field/property reads marked optional. Assignment LHS skipped whole (decision 5) — surfaced as a report note. - SignatureBuilder: promotes bucket overlaps into one coherent signature (declared-inside vars drop from params; declared-inside return candidates become the return; outside candidates already covered by their ref write-back stay ref params). Trailing composite returns stay void with a note suggesting an extract-variable first. - ExtractionReporter + ReportFormatter: single composition shared by CLI and tests; report = header, params (ref), returns, locals, extract-first (count + flags), notes, suggested-signature line last. - DataFlowClassifier: Returns now carry types (ReturnSuggestion) — the signature line needs them. - Displays: compact symbol/type formatting for the report. - Fixture: RepeatReads (dedupe ×2 + skipped field write), Casts (cast candidate) appended at the end so all pinned line numbers stay put.
59 lines
2.5 KiB
C#
59 lines
2.5 KiB
C#
using Microsoft.CodeAnalysis;
|
|
|
|
namespace ExtractMethod.Tooling;
|
|
|
|
/// <summary>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.</summary>
|
|
public sealed record ExtractionReport(
|
|
SelectionReport Selection,
|
|
ExtractionSuggestion Suggestion,
|
|
IReadOnlyList<ExtractFirstEntry> ExtractFirst,
|
|
SignatureSuggestion Signature,
|
|
IReadOnlyList<string> Notes);
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// <see cref="DataFlowClassifier.Classify"/> when analysis cannot bind.
|
|
/// </summary>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private static IReadOnlyList<string> BuildNotes(SemanticModel model, SelectionReport selection)
|
|
{
|
|
var notes = new List<string>();
|
|
|
|
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;
|
|
}
|
|
}
|