using AIStudio.Tests.Models.Generation;
using SourceCodeRules.StyleAnalyzers;
using SourceCodeRules.StyleCodeFixes;
namespace AIStudio.Tests.Analyzers;
///
/// Checks that methods and properties which only return a switch expression start it on the line of their arrow,
/// and that the code fix of MWAIS0006 keeps everything else of the member.
///
///
/// Extension blocks turn many such methods into properties, so the rule has to know properties as well: a
/// getter which does nothing but return a switch expression reads like the method it replaced.
///
[TestFixture]
public sealed class SwitchExpressionMethodAnalyzerTests
{
private const string SWITCH = "this.Kind switch { Kind.CALL => \"Call\", _ => \"Meeting\" }";
[TestCase("public string Label() { return " + SWITCH + "; }", "Method", TestName = "A method which returns a switch expression from a block")]
[TestCase("public string Label() =>\n" + SWITCH + ";", "Method", TestName = "A method whose switch expression starts on the next line")]
[TestCase("public string Label =>\n" + SWITCH + ";", "Property", TestName = "A property whose switch expression starts on the next line")]
[TestCase("public string Label { get { return " + SWITCH + "; } }", "Property", TestName = "A getter which returns a switch expression from a block")]
[TestCase("public string Label { get => " + SWITCH + "; }", "Property", TestName = "A getter with a switch expression as its body")]
public async Task AMemberOfAClassIsRefused(string member, string kind)
{
var reported = await CompilationHarness.AnalyzeAsync(ClassWith(member), new SwitchExpressionMethodAnalyzer());
using (Assert.EnterMultipleScope())
{
Assert.That(reported, Has.Count.EqualTo(1));
Assert.That(reported.FirstOrDefault()?.Id, Is.EqualTo("MWAIS0006"));
Assert.That(reported.FirstOrDefault()?.GetMessage(), Does.StartWith($"{kind} with a switch expression"));
}
}
[TestCase("public string Label =>\nkind switch { Kind.CALL => \"Call\", _ => \"Meeting\" };", TestName = "An extension property whose switch expression starts on the next line")]
[TestCase("public string Label { get { return kind switch { Kind.CALL => \"Call\", _ => \"Meeting\" }; } }", TestName = "The getter of an extension property which returns a switch expression from a block")]
[TestCase("public string Label() { return kind switch { Kind.CALL => \"Call\", _ => \"Meeting\" }; }", TestName = "An extension method which returns a switch expression from a block")]
public async Task AMemberOfAnExtensionBlockIsRefused(string member)
{
var reported = await CompilationHarness.AnalyzeAsync(ExtensionBlockWith(member), new SwitchExpressionMethodAnalyzer());
using (Assert.EnterMultipleScope())
{
Assert.That(reported, Has.Count.EqualTo(1));
Assert.That(reported.FirstOrDefault()?.Id, Is.EqualTo("MWAIS0006"));
}
}
[TestCase("public string Label() => " + SWITCH + ";", TestName = "A method with the switch expression on the line of its arrow")]
[TestCase("public string Label => " + SWITCH + ";", TestName = "A property with the switch expression on the line of its arrow")]
[TestCase("public string Label { get { return " + SWITCH + "; } set { } }", TestName = "A property with a getter and a setter")]
[TestCase("public string Label { get { var kind = this.Kind; return kind switch { Kind.CALL => \"Call\", _ => \"Meeting\" }; } }", TestName = "A getter which does more than return a switch expression")]
[TestCase("public string Label() { return \"Meeting\"; }", TestName = "A method which returns no switch expression")]
public async Task AnythingElseIsLeftAlone(string member)
{
var reported = await CompilationHarness.AnalyzeAsync(ClassWith(member), new SwitchExpressionMethodAnalyzer());
Assert.That(reported, Is.Empty);
}
[Test]
public async Task TheCodeFixKeepsTheAttributesTypeParametersAndConstraintsOfAMethod()
{
var source = ClassWith(
"""
/// Names the hash of a value.
[System.Diagnostics.Contracts.Pure]
public static string Label(T value) where T : struct
{
return value.GetHashCode() switch
{
0 => "Zero",
_ => "Other",
};
}
""");
var expected = ClassWith(
"""
/// Names the hash of a value.
[System.Diagnostics.Contracts.Pure]
public static string Label(T value) where T : struct => value.GetHashCode() switch
{
0 => "Zero",
_ => "Other",
};
""");
var fixedSource = await CompilationHarness.FixAsync(source, new SwitchExpressionMethodAnalyzer(), new SwitchExpressionMethodCodeFixProvider());
Assert.That(fixedSource.ReplaceLineEndings("\n"), Is.EqualTo(expected.ReplaceLineEndings("\n")));
}
[Test]
public async Task TheCodeFixTurnsAGetterIntoAnExpressionBody()
{
var source = ClassWith(
"""
public string Label
{
get
{
return this.Kind switch
{
Kind.CALL => "Call",
_ => "Meeting",
};
}
}
""");
var expected = ClassWith(
"""
public string Label => this.Kind switch
{
Kind.CALL => "Call",
_ => "Meeting",
};
""");
var fixedSource = await CompilationHarness.FixAsync(source, new SwitchExpressionMethodAnalyzer(), new SwitchExpressionMethodCodeFixProvider());
Assert.That(fixedSource.ReplaceLineEndings("\n"), Is.EqualTo(expected.ReplaceLineEndings("\n")));
}
[Test]
public async Task TheCodeFixMovesTheSwitchOfAnExtensionPropertyOntoTheLineOfItsArrow()
{
var source = ExtensionBlockWith(
"""
public string Label =>
kind switch
{
Kind.CALL => "Call",
_ => "Meeting",
};
""");
var expected = ExtensionBlockWith(
"""
public string Label => kind switch
{
Kind.CALL => "Call",
_ => "Meeting",
};
""");
var fixedSource = await CompilationHarness.FixAsync(source, new SwitchExpressionMethodAnalyzer(), new SwitchExpressionMethodCodeFixProvider());
Assert.That(fixedSource.ReplaceLineEndings("\n"), Is.EqualTo(expected.ReplaceLineEndings("\n")));
}
private static string ClassWith(string members) =>
$$"""
namespace Sample;
public enum Kind
{
CALL,
MEETING,
}
public sealed class Meeting
{
public Kind Kind { get; set; } = Kind.MEETING;
{{members}}
}
""";
private static string ExtensionBlockWith(string members) =>
$$"""
namespace Sample;
public enum Kind
{
CALL,
MEETING,
}
public static class KindExtensions
{
extension(Kind kind)
{
{{members}}
}
}
""";
}