Files
mostalive 7f0981e012 em 02: data-flow classification (params/returns/locals buckets)
- 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.
2026-09-12 15:52:16 +01:00

96 lines
4.1 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 (SyntaxTree Tree, CSharpCompilation Compilation) LoadFixture() => DemoFixture.Load();
// ---------------------------------------------------------------------
// (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);
// AnalyzeDataFlow is nullable-annotated in Roslyn 5.x: assert non-null
// so the dereference below is provably safe (and test intent explicit).
Assert.NotNull(dataFlow);
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;
}