- tools/ExtractMethod: net10.0 console app (Microsoft.CodeAnalysis.CSharp 5.9.0, pinned latest stable). CLI: <file.cs> <startLine> <endLine>. Parses the file, builds a scratch compilation with refs from TRUSTED_PLATFORM_ASSEMBLIES, and reports how many whole statements the line range covers (clean error otherwise; exit codes: 0 ok, 1 resolution error, 2 usage). - Tooling/CompilationLoader: shared parse + compilation path for CLI and tests (tests exercise the exact loading path the CLI uses). - Tooling/SelectionResolver: snaps a 1-based inclusive line range to whole statements in the enclosing method body block; boundary checks never split a statement; nested/blank-line ranges handled cleanly. - tests/: ExtractMethod/Fixtures/Demo.cs checked-in fixture exercising every bucket of the parent spec (read-only local, written+read-later return, scratch local, param read, field+property access, indexer + method invocation, multi-statement range incl. a for-loop); excluded from project compilation, copied to output as data. - Tests: Demo.cs compiles with no diagnostics; range 68..72 resolves to 2 statements (LocalDeclarationStatement, ForStatement); AnalyzeDataFlow succeeds on the fixture's for-loop node; mid-statement range fails cleanly. 30/30 green.
102 lines
4.2 KiB
C#
102 lines
4.2 KiB
C#
using ExtractMethod.Tooling;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.CSharp;
|
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
|
|
|
namespace BeforeAfter.Tests.ExtractMethod;
|
|
|
|
/// <summary>
|
|
/// Tests for yak em 01: the scaffold (console tool) and the checked-in demo
|
|
/// fixture. The fixture is DATA (excluded from compilation, copied to the
|
|
/// output dir), and it is parsed with the tool's own <see cref="CompilationLoader"/>
|
|
/// so these tests exercise the exact loading path the CLI uses.
|
|
/// </summary>
|
|
public class DemoFixtureTests
|
|
{
|
|
private static readonly string FixturePath =
|
|
Path.Combine(AppContext.BaseDirectory, "Fixtures", "Demo.cs");
|
|
|
|
private static (SyntaxTree Tree, CSharpCompilation Compilation) LoadFixture()
|
|
{
|
|
Assert.True(File.Exists(FixturePath), $"fixture missing at {FixturePath}");
|
|
var tree = CompilationLoader.ParseFile(FixturePath);
|
|
var compilation = CompilationLoader.CreateCompilation(tree, "DemoFixture");
|
|
return (tree, compilation);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// (a) The fixture is pristine under the scratch compilation: if this
|
|
// fails, the fixture (or the TRUSTED_PLATFORM_ASSEMBLIES reference
|
|
// loading) is broken and every later yak would analyze garbage.
|
|
// ---------------------------------------------------------------------
|
|
[Fact]
|
|
public void Demo_compiles_with_no_diagnostics()
|
|
{
|
|
var (_, compilation) = LoadFixture();
|
|
|
|
Assert.Empty(compilation.GetDiagnostics());
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// (b) A known line range snaps to the expected whole statements.
|
|
// Lines 68..72 of Demo.cs are `int total = 0;` followed by the whole
|
|
// for-loop (for keyword .. closing brace) inside Summarize — exactly
|
|
// the "multi-statement range incl. a for-loop" shape of the spec.
|
|
// ---------------------------------------------------------------------
|
|
[Fact]
|
|
public void Known_range_resolves_to_expected_statements()
|
|
{
|
|
var (tree, _) = LoadFixture();
|
|
|
|
var report = SelectionResolver.Resolve(tree, 68, 72);
|
|
|
|
Assert.True(report.Succeeded, report.Error);
|
|
Assert.Equal(2, report.Count);
|
|
Assert.Equal(
|
|
new[] { SyntaxKind.LocalDeclarationStatement, SyntaxKind.ForStatement },
|
|
report.Kinds);
|
|
Assert.Equal("Summarize", report.Method?.Identifier.ValueText);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// (c) The semantic model's data-flow analysis works on the fixture's
|
|
// for-loop node — the foundation every classification in yak 02
|
|
// builds on. (AnalyzeDataFlow on the loop itself walks the bound loop
|
|
// body; Succeeded == false would mean the scratch compilation or the
|
|
// selected node cannot be bound.)
|
|
// ---------------------------------------------------------------------
|
|
[Fact]
|
|
public void Fixture_for_loop_supports_data_flow_analysis()
|
|
{
|
|
var (tree, compilation) = LoadFixture();
|
|
|
|
var forStatement = tree.GetRoot()
|
|
.DescendantNodes()
|
|
.OfType<ForStatementSyntax>()
|
|
.Single(n => EnclosingMethodName(n) == "Summarize");
|
|
|
|
var dataFlow = compilation.GetSemanticModel(tree).AnalyzeDataFlow(forStatement);
|
|
|
|
Assert.True(dataFlow.Succeeded);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Acceptance also demands "clean error otherwise": selecting only the
|
|
// for-loop header line (line 69) splits the statement and must fail with
|
|
// a readable reason instead of a crash or a silent wrong count.
|
|
// ---------------------------------------------------------------------
|
|
[Fact]
|
|
public void Range_ending_mid_statement_fails_cleanly()
|
|
{
|
|
var (tree, _) = LoadFixture();
|
|
|
|
var report = SelectionResolver.Resolve(tree, 69, 69);
|
|
|
|
Assert.False(report.Succeeded);
|
|
Assert.NotNull(report.Error);
|
|
Assert.Contains("no whole", report.Error);
|
|
}
|
|
|
|
private static string? EnclosingMethodName(SyntaxNode node) =>
|
|
node.AncestorsAndSelf().OfType<MethodDeclarationSyntax>().FirstOrDefault()?.Identifier.ValueText;
|
|
} |