using ExtractMethod.Tooling; using Microsoft.CodeAnalysis; namespace BeforeAfter.Tests.ExtractMethod; /// /// Tests for yak em 03: the signature promotion (SignatureBuilder) and the /// formatted report (ReportFormatter) over the checked-in Demo.cs fixture. /// Line anchors are the exact fixture lines pinned by em 01/02: ScoreReads /// body 56..58, Summarize 68..76 (partial selection 68..72), Heaviest 87..93. /// public class ReportTests { // --------------------------------------------------------------------- // Signature promotion, the case em 02's comment promises: in Summarize // lines 68..72, `total` sits in params[ref] AND returns (declared inside, // reassigned inside, read after), and `i` sits in params AND locals (the // for-header declares it inside). Promotion: both drop out of the params, // total becomes the plain return value -> `int Extract(int limit)`. // --------------------------------------------------------------------- [Fact] public void Signature_for_Summarize_selection_promotes_declared_inside_variables() { var (report, formatted) = Compose(68, 72); Assert.Equal(new[] { "limit" }, report.Signature.Params.Select(p => p.Name)); Assert.Equal("int", report.Signature.ReturnType); Assert.Contains("suggested signature: int Extract(int limit)", formatted); } // --------------------------------------------------------------------- // Heaviest full body: `best` (declared inside, ref in the raw params, // trailing return candidate) promotes to the return; the for-var `i` // drops from the params; widgets/count stay as plain in-params. // --------------------------------------------------------------------- [Fact] public void Signature_for_Heaviest_returns_the_trailing_candidate_and_drops_declared_inside_params() { var (_, formatted) = Compose(87, 93); // Parameter order follows the classifier's ordinal-by-name order — // the same order the params bucket above prints (em 02 determinism). Assert.Contains("suggested signature: Widget Extract(int count, List widgets)", formatted); } // --------------------------------------------------------------------- // No nameable return -> void (ScoreReads ends with `return score + bonus;`, // a composite expression v1 cannot name — the report notes this); locals // declared inside (seed, score) drop from the params; only the enclosing // method's own parameter survives. // --------------------------------------------------------------------- [Fact] public void Signature_for_ScoreReads_is_void_and_carries_only_the_enclosing_parameter() { var (report, formatted) = Compose(56, 58); Assert.Equal(new[] { "bonus" }, report.Signature.Params.Select(p => p.Name)); Assert.Equal("void", report.Signature.ReturnType); Assert.Contains("suggested signature: void Extract(int bonus)", formatted); // The void-ness has an explanation in the report: the composite // trailing return is not nameable in v1. Assert.Contains(report.Notes, n => n.Contains("composite return expression (score + bonus)", StringComparison.Ordinal)); } // --------------------------------------------------------------------- // The report the CLI prints, pinned EXACTLY for one fixture selection // (acceptance: "CLI report on the fixture matches the tests"). Heaviest // full body is the richest case: raw buckets (params include the // declared-inside best/i — the promotion happens only in the signature), // extract-first with the invocation flag, no notes, signature last. // --------------------------------------------------------------------- [Fact] public void Formatted_report_for_Heaviest_matches_the_CLI_output_exactly() { var (_, formatted) = Compose(87, 93); var expected = "3 statement(s) selected, lines 87..93 in Heaviest(): LocalDeclarationStatement, ForStatement, ReturnStatement\n" + "params: best (Widget) [ref]\n" + "params: count (int) [in]\n" + "params: i (int) [ref]\n" + "params: widgets (List) [in]\n" + "returns: best (Widget)\n" + "locals: i\n" + "extract-first:\n" + " - widgets[0] — List.this[int] ×1\n" + " - best.Bigger(widgets[i]) — Widget.Bigger(Widget) ×1 [hoisting changes eval count]\n" + " - widgets[i] — List.this[int] ×1\n" + "suggested signature: Widget Extract(int count, List widgets)\n"; Assert.Equal(expected, formatted); } private static (ExtractionReport Report, string Formatted) Compose(int startLine, int endLine) { var (tree, compilation) = DemoFixture.Load(); var model = compilation.GetSemanticModel(tree); var resolved = SelectionResolver.Resolve(tree, startLine, endLine); Assert.True(resolved.Succeeded, resolved.Error); var report = ExtractionReporter.Compose(model, resolved); return (report, ReportFormatter.Format(report)); } }