Skip to content

Commit

Permalink
refactor: scope AZC0030 "Options" analyzer to Arm
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgerangel-msft committed Mar 6, 2025
1 parent 17a743b commit 4de06d3
Show file tree
Hide file tree
Showing 6 changed files with 298 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,53 @@
using Xunit;

using VerifyCS = Azure.ClientSdk.Analyzers.Tests.AzureAnalyzerVerifier<
Azure.ClientSdk.Analyzers.ModelName.GeneralSuffixAnalyzer>;
Azure.ClientSdk.Analyzers.ModelName.OptionsSuffixAnalyzer>;

// Test cases for `Options` suffix. We allow `Options` suffix for property bags.
// So need to skip property bags which do not have any serialization codes.
namespace Azure.ClientSdk.Analyzers.Tests.ModelName
{
public class AZC0030OptionsSuffixTests
{
private const string diagnosticId = "AZC0030";
private const string DiagnosticId = "AZC0030";

[Fact]
public async Task WithoutAnySerialization()
// This test validates that the analyzer is triggered when the model is in the Azure.ResourceManager namespace
// and has serialization methods.
[Theory]
[InlineData("Azure.ResourceManager")]
[InlineData("Azure.ResourceManager.Models")]
[InlineData("Azure.ResourceManager.SomeOtherNs")]
public async Task WithAzureResourceManagerNamespaceAndSerialization(string ns)
{
var test = @"using System.Text.Json;
namespace Azure.ResourceManager.Models;
public class DiskOptions
namespace " + ns + @"
{
internal interface IUtf8JsonSerializable
{
void Write(Utf8JsonWriter writer);
};
public class DiskOptions: IUtf8JsonSerializable
{
void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) {}
}
}";
await VerifyCS.VerifyAnalyzerAsync(test);
var expectedMessage = GetDiagnosticMessage("DiskOptions");
var expected = VerifyCS.Diagnostic(DiagnosticId).WithSpan(9, 18, 9, 29).WithArguments("DiskOptions", "Options", expectedMessage);
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}

[Fact]
public async Task WithDeserializationMethod()

// This test validates that the analyzer is triggered when the model is in the Azure.ResourceManager namespace
// and has deserialization methods.
[Theory]
[InlineData("Azure.ResourceManager")]
[InlineData("Azure.ResourceManager.Models")]
[InlineData("Azure.ResourceManager.SomeOtherNs")]
public async Task WithAzureResourceManagerNamespaceAndDeserializationMethod(string ns)
{
var test = @"using System.Text.Json;
namespace Azure.ResourceManager
namespace " + ns + @"
{
public class ResponseOptions
{
Expand All @@ -38,28 +58,189 @@ public static ResponseOptions DeserializeResponseOptions(JsonElement element)
}
}
}";
var expected = VerifyCS.Diagnostic(diagnosticId).WithSpan(4, 18, 4, 33).WithArguments("ResponseOptions", "Options", "'ResponseConfig'");
var expectedMessage = GetDiagnosticMessage("ResponseOptions");
var expected = VerifyCS.Diagnostic(DiagnosticId).WithSpan(4, 18, 4, 33).WithArguments("ResponseOptions", "Options", expectedMessage);
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}

[Fact]
public async Task WithSerializationMethod()
[Theory]
[InlineData("Azure.ResourceManager")]
[InlineData("Azure.ResourceManager.Models")]
[InlineData("Azure.ResourceManager.SomeOtherNs")]
public async Task WithAzureResourceManagerNamespaceRoundTripModel(string ns)
{
var test = @"using System.Text.Json;
namespace Azure.ResourceManager.Models
namespace " + ns + @"
{
internal interface IUtf8JsonSerializable
{
void Write(Utf8JsonWriter writer);
};
public class DiskOptions: IUtf8JsonSerializable
public class ResponseOptions : IUtf8JsonSerializable
{
void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) {}
public static ResponseOptions DeserializeResponseOptions(JsonElement element)
{
return null;
}
}
}";
var expectedMessage = GetDiagnosticMessage("ResponseOptions");
var expected = VerifyCS.Diagnostic(DiagnosticId).WithSpan(10, 18, 10, 33).WithArguments("ResponseOptions", "Options", expectedMessage);
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}

[Theory]
[InlineData("Azure.ResourceManager")]
[InlineData("Azure.ResourceManager.Models")]
[InlineData("Azure.ResourceManager.SomeOtherNs")]
public async Task WithNestedNamespaceAndSerialization(string ns)
{
var test = @"using System.Text.Json;
namespace " + ns + @"
{
namespace SubTest
{
internal interface IUtf8JsonSerializable
{
void Write(Utf8JsonWriter writer);
};
public class DiskOptions: IUtf8JsonSerializable
{
void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) {}
}
}
}";
var expected = VerifyCS.Diagnostic(diagnosticId).WithSpan(9, 18, 9, 29).WithArguments("DiskOptions", "Options", "'DiskConfig'");
var expectedMessage = GetDiagnosticMessage("DiskOptions");
var expected = VerifyCS.Diagnostic(DiagnosticId).WithSpan(11, 22, 11, 33).WithArguments("DiskOptions", "Options", expectedMessage);
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}

// This test validates that the analyzer is not triggered when the model is in the Azure.ResourceManager namespace
// and has no serialization methods.
[Theory]
[InlineData("Azure.ResourceManager")]
[InlineData("Azure.ResourceManager.Models")]
[InlineData("Azure.ResourceManager.SomeOtherNs")]
public async Task WithAzureResourceManagerNamespaceAndNoSerialization(string ns)
{
var test = @"using System.Text.Json;
namespace " + ns + @"
{
public class DiskOptions
{
}
}";
await VerifyCS.VerifyAnalyzerAsync(test);
}

[Theory]
[InlineData("Azure.ResourceManager")]
[InlineData("Azure.ResourceManager.Models")]
[InlineData("Azure.ResourceManager.SomeOtherNs")]
public async Task WithAzureResourceManagerNamespaceAndOtherMethods(string ns)
{
var test = @"using System.Text.Json;
namespace " + ns + @"
{
public class DiskOptions
{
public void SomeMethod() {}
}
}";
await VerifyCS.VerifyAnalyzerAsync(test);
}

// This test validates that the analyzer does not trigger on a class that is not in the Azure.ResourceManager namespace
// despite it having serialization.
[Theory]
[InlineData("Azure.Foo.Models")]
[InlineData("Azure.Foo")]
public async Task NonManagementModelWithSerializationMethod(string ns)
{
var test = @"using System.Text.Json;
namespace " + ns + @"
{
internal interface IUtf8JsonSerializable
{
void Write(Utf8JsonWriter writer);
};
public class DiskOptions: IUtf8JsonSerializable
{
void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) {}
}
}";
await VerifyCS.VerifyAnalyzerAsync(test);
}

[Theory]
[InlineData("Azure.Foo.Models")]
[InlineData("Azure.Foo")]
public async Task NonManagementModelWithDeserializationMethod(string ns)
{
var test = @"using System.Text.Json;
namespace " + ns + @"
{
public class ResponseOptions
{
public static ResponseOptions DeserializeResponseOptions(JsonElement element)
{
return null;
}
}
}";
await VerifyCS.VerifyAnalyzerAsync(test);
}

[Theory]
[InlineData("Azure.Foo.Models")]
[InlineData("Azure.Foo")]
public async Task NonManagementRoundTripModel(string ns)
{
var test = @"using System.Text.Json;
namespace " + ns + @"
{
internal interface IUtf8JsonSerializable
{
void Write(Utf8JsonWriter writer);
};
public class ResponseOptions : IUtf8JsonSerializable
{
void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) {}
public static ResponseOptions DeserializeResponseOptions(JsonElement element)
{
return null;
}
}
}";
await VerifyCS.VerifyAnalyzerAsync(test);
}

[Theory]
[InlineData("Azure.Foo.Models")]
[InlineData("Azure.Foo")]
public async Task NonManagementModelWithNoMethods(string ns)
{
var test = @"using System.Text.Json;
namespace " + ns + @"
{
public class DiskOptions
{
}
}";
await VerifyCS.VerifyAnalyzerAsync(test);
}

private static string GetDiagnosticMessage(string typeName)
{
return $"The `Options` suffix is reserved for input models described by https://azure.github.io/azure-sdk/dotnet_introduction.html#dotnet-parameters. " +
$"Please rename `{typeName}` according to our guidelines at https://azure.github.io/azure-sdk/general_design.html#model-types for output or roundtrip models.";
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Threading.Tasks;
using Azure.ClientSdk.Analyzers.ModelName;
using Xunit;

using VerifyCS = Azure.ClientSdk.Analyzers.Tests.AzureAnalyzerVerifier<
Expand All @@ -9,7 +8,7 @@ namespace Azure.ClientSdk.Analyzers.Tests.ModelName
{
public class AZC0030Tests
{
private const string diagnosticId = "AZC0030";
private const string DiagnosticId = "AZC0030";

[Fact]
public async Task GoodSuffix()
Expand Down Expand Up @@ -37,7 +36,8 @@ public static ResponseParameters DeserializeResponseParameters(JsonElement eleme
}
}
}";
var expected = VerifyCS.Diagnostic(diagnosticId).WithSpan(4, 18, 4, 36).WithArguments("ResponseParameters", "Parameters", "'ResponseContent' or 'ResponsePatch'");
var expectedMessage = $"Suggest to rename it to 'ResponseContent' or 'ResponsePatch' or any other appropriate name.";
var expected = VerifyCS.Diagnostic(DiagnosticId).WithSpan(4, 18, 4, 36).WithArguments("ResponseParameters", "Parameters", expectedMessage);
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}

Expand All @@ -55,7 +55,8 @@ public static DiskOption DeserializeDiskOption(JsonElement element)
}
}
}";
var expected = VerifyCS.Diagnostic(diagnosticId).WithSpan(4, 18, 4, 28).WithArguments("DiskOption", "Option", "'DiskConfig'");
var expectedMessage = $"Suggest to rename it to 'DiskConfig' or any other appropriate name.";
var expected = VerifyCS.Diagnostic(DiagnosticId).WithSpan(4, 18, 4, 28).WithArguments("DiskOption", "Option", expectedMessage);
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}

Expand All @@ -72,7 +73,8 @@ public class DiskOption
}
}
}";
var expected = VerifyCS.Diagnostic(diagnosticId).WithSpan(6, 22, 6, 32).WithArguments("DiskOption", "Option", "'DiskConfig'");
var expectedMessage = $"Suggest to rename it to 'DiskConfig' or any other appropriate name.";
var expected = VerifyCS.Diagnostic(DiagnosticId).WithSpan(6, 22, 6, 32).WithArguments("DiskOption", "Option", expectedMessage);
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}

Expand All @@ -93,7 +95,8 @@ public static CreationResponses DeserializeCreationResponses(JsonElement element
}
}
}";
var expected = VerifyCS.Diagnostic(diagnosticId).WithSpan(6, 22, 6, 39).WithArguments("CreationResponses", "Responses", "'CreationResults'");
var expectedMessage = $"Suggest to rename it to 'CreationResults' or any other appropriate name.";
var expected = VerifyCS.Diagnostic(DiagnosticId).WithSpan(6, 22, 6, 39).WithArguments("CreationResponses", "Responses", expectedMessage);
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Threading.Tasks;
using Azure.ClientSdk.Analyzers.ModelName;
using Xunit;

using VerifyCS = Azure.ClientSdk.Analyzers.Tests.AzureAnalyzerVerifier<
Expand All @@ -9,7 +8,7 @@ namespace Azure.ClientSdk.Analyzers.Tests.ModelName
{
public class SuffixAnalyzerBaseTests
{
private const string diagnosticId = "AZC0030";
private const string DiagnosticId = "AZC0030";

[Fact]
public async Task NonPublicClassIsNotChecked()
Expand Down Expand Up @@ -38,9 +37,10 @@ public class MonitorParameter
public class MonitorParameter
{
}")]
public async Task ClassWithoutSerliaizationMethodsButInModelsNamespaceIsChecked(string test)
public async Task ClassWithoutSerializationMethodsButInModelsNamespaceIsChecked(string test)
{
var expected = VerifyCS.Diagnostic(diagnosticId).WithSpan(3, 14, 3, 30).WithArguments("MonitorParameter", "Parameter", "'MonitorContent' or 'MonitorPatch'");
var expectedMessage = $"Suggest to rename it to 'MonitorContent' or 'MonitorPatch' or any other appropriate name.";
var expected = VerifyCS.Diagnostic(DiagnosticId).WithSpan(3, 14, 3, 30).WithArguments("MonitorParameter", "Parameter", expectedMessage);
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}

Expand All @@ -57,12 +57,13 @@ public static MonitorParameter DeserializeMonitorParameter(JsonElement element)
return null;
}
}";
var expected = VerifyCS.Diagnostic(diagnosticId).WithSpan(4, 14, 4, 30).WithArguments("MonitorParameter", "Parameter", "'MonitorContent' or 'MonitorPatch'");
var expectedMessage = $"Suggest to rename it to 'MonitorContent' or 'MonitorPatch' or any other appropriate name.";
var expected = VerifyCS.Diagnostic(DiagnosticId).WithSpan(4, 14, 4, 30).WithArguments("MonitorParameter", "Parameter", expectedMessage);
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}

[Fact]
public async Task ClassWithDeserliazationMethodIsChecked()
public async Task ClassWithDeserializationMethodIsChecked()
{
var test = @"using System.Text.Json;
namespace Azure.NotModels;
Expand All @@ -79,7 +80,8 @@ void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
return;
}
}";
var expected = VerifyCS.Diagnostic(diagnosticId).WithSpan(9, 14, 9, 30).WithArguments("MonitorParameter", "Parameter", "'MonitorContent' or 'MonitorPatch'");
var expectedMessage = $"Suggest to rename it to 'MonitorContent' or 'MonitorPatch' or any other appropriate name.";
var expected = VerifyCS.Diagnostic(DiagnosticId).WithSpan(9, 14, 9, 30).WithArguments("MonitorParameter", "Parameter", expectedMessage);
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ internal class Descriptors
public static readonly DiagnosticDescriptor AZC0030 = new DiagnosticDescriptor(
nameof(AZC0030),
"Improper model name suffix",
"Model name '{0}' ends with '{1}'. Suggest to rename it to {2} or any other appropriate name.",
"Model name '{0}' ends with '{1}'. {2}",
DiagnosticCategory.Naming,
DiagnosticSeverity.Warning,
true,
Expand Down
Loading

0 comments on commit 4de06d3

Please sign in to comment.