- 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.
96 lines
4.1 KiB
C#
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;
|
|
} |