using System.Text;
namespace ExtractMethod.Tooling;
///
/// Renders an 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.
///
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();
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 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]}");
}
}
}