Files
db-subclass-to-dto/tools/ExtractMethod/Tooling/ReportFormatter.cs
T
mostalive 49a5633a7d em 03: extract-first scan + full suggestion report
- 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.
2026-09-12 18:26:20 +01:00

81 lines
3.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Text;
namespace ExtractMethod.Tooling;
/// <summary>
/// Renders an <see cref="ExtractionReport"/> as the human-readable extract-
/// method suggestion report (parent spec): params (with ref), return
/// candidates, locals, extract-first candidates (deduped, with occurrence
/// counts and flags), the limitation notes, and the suggested signature line
/// LAST. Pure string shaping — no Roslyn — so tests can pin the exact output
/// the CLI prints.
/// </summary>
public static class ReportFormatter
{
public static string Format(ExtractionReport report)
{
var sb = new StringBuilder();
var selection = report.Selection;
var suggestion = report.Suggestion;
// Header (same wording the CLI printed since em 01).
sb.AppendLine(
$"{selection.Count} statement(s) selected, lines {selection.StartLine}..{selection.EndLine} " +
$"in {selection.Method?.Identifier.ValueText}(): " +
string.Join(", ", selection.Kinds));
AppendBucket(sb, "params", suggestion.Params.Select(p =>
$"{p.Name} ({p.Type}){(p.ByRef ? " [ref]" : " [in]")}"));
AppendBucket(sb, "returns", suggestion.Returns.Select(r => $"{r.Name} ({r.Type})"));
AppendBucket(sb, "locals", suggestion.Locals);
if (report.ExtractFirst.Count == 0)
{
sb.AppendLine("extract-first: (none)");
}
else
{
sb.AppendLine("extract-first:");
foreach (var entry in report.ExtractFirst)
{
// Flags accumulate; occurrence count is always shown because
// "count occurrences" is the point of the dedupe.
var flags = new List<string>();
if (entry.IsInvocation)
{
flags.Add("hoisting changes eval count");
}
if (entry.Optional)
{
flags.Add($"optional: own-class {entry.OwnMemberKind}");
}
var flagText = flags.Count > 0 ? $" [{string.Join("; ", flags)}]" : string.Empty;
sb.AppendLine($" - {entry.Text} — {entry.SymbolDisplay} ×{entry.Occurrences}{flagText}");
}
}
foreach (var note in report.Notes)
{
sb.AppendLine($"note: {note}");
}
var signature = report.Signature;
var parameterList = string.Join(", ", signature.Params.Select(p => $"{(p.ByRef ? "ref " : "")}{p.Type} {p.Name}"));
sb.AppendLine($"suggested signature: {signature.ReturnType} {signature.MethodName}({parameterList})");
return sb.ToString();
}
private static void AppendBucket(StringBuilder sb, string name, IEnumerable<string> items)
{
var enumerated = items.ToList();
sb.AppendLine(enumerated.Count == 0 ? $"{name}: (none)" : $"{name}: {enumerated[0]}");
for (var i = 1; i < enumerated.Count; i++)
{
sb.AppendLine($"{name}: {enumerated[i]}");
}
}
}