- Tooling/DataFlowClassifier: buckets a resolved selection via Roslyn DataFlowAnalysis. Selection flow + tail flow (statements AFTER the selection in the same block) both use the two-argument AnalyzeDataFlow overload on the contiguous run; the em 01 spike verdict is pinned in tests — a synthetic BlockSyntax (parent decision #3) throws ArgumentException "statements not within tree" and would re-bind symbols, breaking the written-inside ∩ read-after identity match. - Buckets per parent spec as plain-string records: in-params = local + parameter reads (filtering the implicit `this`; fields/properties stay in the em 03 extract-first bucket), ByRef = reassigned inside via a non-declaration write (declaration initializers are not write-backs), returns = written ∧ read-after (tail, branch-insensitive over-approximation) + trailing `return X;` simple-name candidate, locals = written ∧ never read-after. - CLI now prints the raw bucket dump after the statement count line; classification failures exit 1 with a clean message (exit 0/1/2 contract preserved). - Tests: 6 new (in-param incl. `this` non-leak, multi-statement two-argument path, trailing-return, ref-vs-in differential, extract-first non-leak, synthetic-block dead path, single-statement one-argument path); shared DemoFixture loader; fixed pre-existing CS8602 in DemoFixtureTests. 36/36 green.
94 lines
2.8 KiB
C#
94 lines
2.8 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 report = SelectionResolver.Resolve(tree, startLine, endLine);
|
|
if (!report.Succeeded)
|
|
{
|
|
Console.Error.WriteLine($"error: {report.Error}");
|
|
return SelectionResolver.ExitError;
|
|
}
|
|
|
|
Console.WriteLine(
|
|
$"{report.Count} statement(s) selected, lines {report.StartLine}..{report.EndLine} " +
|
|
$"in {report.Method?.Identifier.ValueText}(): " +
|
|
string.Join(", ", report.Kinds));
|
|
|
|
// 4. classify the data flow into the raw buckets (em 02: no pretty report yet)
|
|
// A classification failure is a semantic resolution error: same exit code as
|
|
// the resolver, message on stderr, but never a stack trace.
|
|
ExtractionSuggestion suggestion;
|
|
try
|
|
{
|
|
suggestion = DataFlowClassifier.Classify(compilation.GetSemanticModel(tree), report);
|
|
}
|
|
catch (Exception e) when (e is InvalidOperationException or ArgumentException)
|
|
{
|
|
Console.Error.WriteLine($"error: data-flow analysis failed: {e.Message}");
|
|
return SelectionResolver.ExitError;
|
|
}
|
|
|
|
foreach (var param in suggestion.Params)
|
|
{
|
|
Console.WriteLine($"params: {param.Name} ({param.Type}){(param.ByRef ? " [ref]" : " [in]")}");
|
|
}
|
|
if (suggestion.Params.Count == 0)
|
|
{
|
|
Console.WriteLine("params: (none)");
|
|
}
|
|
|
|
foreach (var name in suggestion.Returns)
|
|
{
|
|
Console.WriteLine($"returns: {name}");
|
|
}
|
|
if (suggestion.Returns.Count == 0)
|
|
{
|
|
Console.WriteLine("returns: (none)");
|
|
}
|
|
|
|
foreach (var name in suggestion.Locals)
|
|
{
|
|
Console.WriteLine($"locals: {name}");
|
|
}
|
|
if (suggestion.Locals.Count == 0)
|
|
{
|
|
Console.WriteLine("locals: (none)");
|
|
}
|
|
|
|
return 0; |