-
-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathNoCapturedVariableValidatorTests.cs
58 lines (49 loc) · 3.45 KB
/
NoCapturedVariableValidatorTests.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using System;
using System.Linq;
using Cecilifier.Core.AST;
using Cecilifier.Core.Misc;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using NUnit.Framework;
namespace Cecilifier.Core.Tests.Tests.Unit;
[TestFixture]
public class NoCapturedVariableValidatorTests
{
[TestCase("class Foo { void M(Foo foo) { bool Capture() => foo == null; } }", TestName = "Parent Parameter")]
[TestCase("class Foo { void M(Foo foo) { void Capture() => foo.M(foo); } }", TestName = "Parent Parameter With Method Invocation")]
[TestCase("class Foo { void M(int parameter) { void Capture() { parameter++; } } }", TestName = "Parameter")]
[TestCase("class Foo { void M() { int local = 42; void Capture() { local++; } } }", TestName = "Local")]
public void LocalFunctions_Positive(string source)
{
var ctx = ParseAndCreateContextFor(source);
var nodeToTest = ctx.SemanticModel.SyntaxTree.GetRoot().DescendantNodes().Single(node => node.IsKind(SyntaxKind.LocalFunctionStatement));
Assert.That(NoCapturedVariableValidator.IsValid(ctx, nodeToTest), Is.False);
Assert.That(ctx.Output, Does.Match(@"Local function that captures context are not supported. Node '.+ Capture\(\).+' captures .+"));
}
[TestCase("class Foo { void M() { void NonCapture(int parameter) { parameter++; } } }", TestName = "Parameter")]
[TestCase("class Foo { void M() { void NonCapture() { int local = 42; local++; } } }", TestName = "Local")]
[TestCase("class Foo { int field; void M() { void NonCapture() { field++; } } }", TestName = "Field")]
[TestCase("class Foo { void M() { int NonCapture(Bar bar) => bar.value; } } struct Bar { public int value; }", TestName = "Member Access (field on parameter)")]
[TestCase("class Foo { void M() { int NonCapture(Bar[] bars) => bars[0].value; } } struct Bar { public int value; }", TestName = "Member Access (field on array)")]
[TestCase("class Foo { void M() { int NonCapture() { Bar bar = new Bar(); return bar.Get(); } } } struct Bar { public int value; public int Get() => 42; }", TestName = "Member Access (method on local)")]
public void LocalFunctions_FalsePositive(string source)
{
var ctx = ParseAndCreateContextFor(source);
var nodeToTest = ctx.SemanticModel.SyntaxTree.GetRoot().DescendantNodes().Single(node => node.IsKind(SyntaxKind.LocalFunctionStatement));
Assert.That(NoCapturedVariableValidator.IsValid(ctx, nodeToTest), Is.True, ctx.Output);
Assert.That(ctx.Output, Does.Not.Contain("Local function that captures context are not supported"));
}
private static CecilifierContext ParseAndCreateContextFor(string source)
{
var syntaxTree = CSharpSyntaxTree.ParseText(source);
var comp = CSharpCompilation.Create(null, new[] { syntaxTree }, new[] { MetadataReference.CreateFromFile(typeof(Func<>).Assembly.Location) }, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
var diagnostics = comp.GetDiagnostics();
var errors = diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error).ToArray();
if (errors.Length != 0)
throw new Exception(errors.Aggregate("", (acc, curr) => acc + curr.GetMessage() + Environment.NewLine));
var context = new CecilifierContext(comp.GetSemanticModel(syntaxTree), new CecilifierOptions(), -1);
DefaultParameterExtractorVisitor.Initialize(context);
UsageVisitor.ResetInstance();
return context;
}
}