- 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.
64 lines
2.1 KiB
C#
64 lines
2.1 KiB
C#
using ExtractMethod.Tooling;
|
|
using Microsoft.CodeAnalysis;
|
|
|
|
// CLIs are boring on purpose: argument parsing and printing live here, all
|
|
// Roslyn logic lives in Tooling/ so the tests can drive it directly.
|
|
|
|
string usage = "usage: ExtractMethod <file.cs> <startLine> <endLine> (1-based, inclusive)";
|
|
|
|
if (args.Length != 3)
|
|
{
|
|
Console.Error.WriteLine(usage);
|
|
return 2;
|
|
}
|
|
|
|
string file = Path.GetFullPath(args[0]);
|
|
if (!File.Exists(file))
|
|
{
|
|
Console.Error.WriteLine($"error: file not found: {file}");
|
|
Console.Error.WriteLine(usage);
|
|
return 2;
|
|
}
|
|
|
|
if (!int.TryParse(args[1], out int startLine) || !int.TryParse(args[2], out int endLine))
|
|
{
|
|
Console.Error.WriteLine($"error: line numbers must be integers");
|
|
Console.Error.WriteLine(usage);
|
|
return 2;
|
|
}
|
|
|
|
// 1. parse the file
|
|
var tree = CompilationLoader.ParseFile(file);
|
|
|
|
// 2. build the scratch compilation (refs from TRUSTED_PLATFORM_ASSEMBLIES)
|
|
var compilation = CompilationLoader.CreateCompilation(tree, Path.GetFileNameWithoutExtension(file));
|
|
if (compilation.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error))
|
|
{
|
|
Console.Error.WriteLine("warning: the file does not compile cleanly under a plain Roslyn compilation; reporting syntax-level resolution only");
|
|
}
|
|
|
|
// 3. snap the range to whole statements, report cleanly otherwise
|
|
var resolved = SelectionResolver.Resolve(tree, startLine, endLine);
|
|
if (!resolved.Succeeded)
|
|
{
|
|
Console.Error.WriteLine($"error: {resolved.Error}");
|
|
return SelectionResolver.ExitError;
|
|
}
|
|
|
|
// 4. compose the full suggestion (em 02 buckets + em 03 extract-first and
|
|
// signature) and print the report. A composition failure is a semantic
|
|
// resolution error: same exit code as the resolver, message on stderr,
|
|
// but never a stack trace.
|
|
var model = compilation.GetSemanticModel(tree);
|
|
try
|
|
{
|
|
var extraction = ExtractionReporter.Compose(model, resolved);
|
|
Console.Write(ReportFormatter.Format(extraction));
|
|
}
|
|
catch (Exception e) when (e is InvalidOperationException or ArgumentException)
|
|
{
|
|
Console.Error.WriteLine($"error: data-flow analysis failed: {e.Message}");
|
|
return SelectionResolver.ExitError;
|
|
}
|
|
|
|
return 0; |