- 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.
118 lines
4.7 KiB
C#
118 lines
4.7 KiB
C#
// Demo fixture for the extract-method micro-tool (tools/ExtractMethod).
|
|
//
|
|
// This file is DATA, not project source: the test project excludes it from
|
|
// compilation (see BeforeAfter.Tests.csproj) and runs Roslyn over its raw
|
|
// text, so the tool sees exactly what is checked in here. It uses explicit
|
|
// usings because the scratch compilation has no implicit usings.
|
|
//
|
|
// Bucket map (the classification yak 02 works from):
|
|
// bucket where example
|
|
// local, read-only (param) ScoreReads seed, score
|
|
// written + read-later (return) Summarize, Heaviest total, message, best
|
|
// scratch local (local) Summarize for-loop i
|
|
// enclosing-method param (param) ScoreReads, Summarize bonus, limit
|
|
// field access (extract-first) ScoreReads _seed (optional)
|
|
// property access (extract-first) Summarize Scale (optional)
|
|
// indexer access (extract-first) Heaviest widgets[i]
|
|
// method invocation (extract-first, flagged) Heaviest best.Bigger(...)
|
|
// multi-statement range incl. for Summarize total + for-loop
|
|
//
|
|
// Tests pin EXACT line numbers of the statements they select. Keep the
|
|
// formatting stable; when a line must change, update DemoFixtureTests too.
|
|
using System.Collections.Generic;
|
|
|
|
namespace BeforeAfter.Tests.ExtractMethod.Fixtures;
|
|
|
|
/// <summary>
|
|
/// Small first-class citizen of the extract-method tool: every method below
|
|
/// shows one or more of the classification "buckets" the tool must report.
|
|
/// </summary>
|
|
public class Demo
|
|
{
|
|
// Own-class state: reading it from a selection is "extract-first optional"
|
|
// (a same-class extracted method can still see these fields). Initialized
|
|
// here so the fixture compiles with zero diagnostics (CS0649 otherwise).
|
|
private int _seed = 5;
|
|
|
|
// Own-class property: like a field, accessible from a same-class method,
|
|
// so hoisting it is optional in the report.
|
|
public int Scale { get; set; } = 1;
|
|
|
|
/// <summary>Small local type so the scratch compilation binds.</summary>
|
|
public sealed record Widget(int Weight, int Count, string Label)
|
|
{
|
|
/// <summary>Instance method used to exercise the invocation bucket.</summary>
|
|
public Widget Bigger(Widget other) =>
|
|
Weight >= other.Weight ? this : other;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bucket: enclosing-method parameter read (bonus => param), local
|
|
/// read-only (seed, score => param), own-class field access
|
|
/// (_seed => extract-first, optional). Ends with a return expression.
|
|
/// </summary>
|
|
public int ScoreReads(int bonus)
|
|
{
|
|
int seed = _seed;
|
|
int score = seed * 2;
|
|
return score + bonus;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bucket: written + read-later locals (total, message => return),
|
|
/// scratch local (i => local), own-class property access (Scale =>
|
|
/// extract-first optional), multi-statement range incl. a for-loop.
|
|
/// </summary>
|
|
public string Summarize(int limit)
|
|
{
|
|
int total = 0;
|
|
for (int i = 0; i < limit; i++)
|
|
{
|
|
total += i;
|
|
}
|
|
|
|
int scaled = total * Scale;
|
|
string message = "sum=" + scaled;
|
|
return message;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bucket: indexer access (widgets[i] on List<T> => extract-first),
|
|
/// method invocation (Bigger => extract-first, flagged because hoisting
|
|
/// changes how often it is evaluated), local written + read-later
|
|
/// (best => return).
|
|
/// </summary>
|
|
public Widget Heaviest(List<Widget> widgets, int count)
|
|
{
|
|
Widget best = widgets[0];
|
|
for (int i = 1; i < count; i++)
|
|
{
|
|
best = best.Bigger(widgets[i]);
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bucket (em 03): the field read <c>_seed</c> appears TWICE — the
|
|
/// extract-first scan must dedupe it to one entry with an occurrence
|
|
/// count of 2. The field WRITE below (<c>_seed = a;</c>) is a
|
|
/// non-variable assignment left-hand side: the reads-only scan skips it
|
|
/// (decision 5) and the report notes the limitation.
|
|
/// </summary>
|
|
public int RepeatReads(int n)
|
|
{
|
|
int a = _seed + n;
|
|
int b = _seed * n;
|
|
_seed = a;
|
|
return a + b;
|
|
}
|
|
|
|
/// <summary>Bucket (em 03): a cast is an extract-first candidate too
|
|
/// (parent spec lists casts); its bound symbol is null — conversions are
|
|
/// not symbols — so the entry keys on its text alone.</summary>
|
|
public double Casts(int total)
|
|
{
|
|
return (double)total / _seed;
|
|
}
|
|
} |