diff --git a/src/BookingsDomain/Events.cs b/src/BookingsDomain/Events.cs index c2df573e..aff0e435 100644 --- a/src/BookingsDomain/Events.cs +++ b/src/BookingsDomain/Events.cs @@ -24,9 +24,9 @@ public static Created Create(Identifier id, Identifier organizationId) public required DateTime OccurredUtc { get; set; } } -#pragma warning disable SAS063 +#pragma warning disable SAASDDD043 public sealed class ReservationMade : IDomainEvent -#pragma warning restore SAS063 +#pragma warning restore SAASDDD043 { public static ReservationMade Create(Identifier id, Identifier organizationId, Identifier borrowerId, DateTime start, DateTime end) @@ -99,9 +99,9 @@ public static TripAdded Create(Identifier id, Identifier organizationId) public required DateTime OccurredUtc { get; set; } } -#pragma warning disable SAS063 +#pragma warning disable SAASDDD043 public sealed class TripBegan : IDomainEvent -#pragma warning restore SAS063 +#pragma warning restore SAASDDD043 { public static TripBegan Create(Identifier id, Identifier organizationId, Identifier tripId, DateTime beganAt, Location from) diff --git a/src/SaaStack.sln.DotSettings b/src/SaaStack.sln.DotSettings index bc2606b1..fe01bcd7 100644 --- a/src/SaaStack.sln.DotSettings +++ b/src/SaaStack.sln.DotSettings @@ -1079,6 +1079,10 @@ public void When$condition$_Then$outcome$() True True True + True + True + True + True True True True diff --git a/src/Tools.Analyzers.Common/AnalyzerConstants.cs b/src/Tools.Analyzers.Common/AnalyzerConstants.cs index 5b443776..3c760ba6 100644 --- a/src/Tools.Analyzers.Common/AnalyzerConstants.cs +++ b/src/Tools.Analyzers.Common/AnalyzerConstants.cs @@ -2,6 +2,10 @@ namespace Tools.Analyzers.Common; public static class AnalyzerConstants { + public const string ServiceOperationTypesNamespace = "Infrastructure.Web.Api.Operations.Shared"; + public const string ResourceTypesNamespace = "Application.Resources.Shared"; + public const string RequestTypeSuffix = "Request"; + public const string ResponseTypeSuffix = "Response"; public static readonly string[] PlatformNamespaces = { #if TESTINGONLY @@ -19,13 +23,11 @@ public static class AnalyzerConstants "IntegrationTesting.WebApi.Common", "UnitTesting.Common" }; - public const string ResourceTypesNamespace = "Application.Resources.Shared"; - public static class Categories { + public const string Application = "SaaStackApplication"; public const string Ddd = "SaaStackDDD"; public const string Documentation = "SaaStackDocumentation"; public const string WebApi = "SaaStackWebApi"; - public const string Application = "SaaStackApplication"; } } \ No newline at end of file diff --git a/src/Tools.Analyzers.NonPlatform.UnitTests/ApiLayerAnalyzerSpec.cs b/src/Tools.Analyzers.NonPlatform.UnitTests/ApiLayerAnalyzerSpec.cs new file mode 100644 index 00000000..aa34991d --- /dev/null +++ b/src/Tools.Analyzers.NonPlatform.UnitTests/ApiLayerAnalyzerSpec.cs @@ -0,0 +1,2112 @@ +extern alias NonPlatformAnalyzers; +extern alias CommonAnalyzers; +using CommonAnalyzers::Tools.Analyzers.Common; +using Xunit; +using TypeExtensions = NonPlatformAnalyzers::Tools.Analyzers.NonPlatform.TypeExtensions; +using ServiceOperation = NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.ServiceOperation; +using IWebResponse = NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebResponse; +using IWebSearchResponse = NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebSearchResponse; +using Route = NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.RouteAttribute; +using Authorize = NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.AuthorizeAttribute; +using SearchResultMetadata = NonPlatformAnalyzers::Application.Interfaces.SearchResultMetadata; +using AccessType = NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.AccessType; +using ApiLayerAnalyzer = NonPlatformAnalyzers::Tools.Analyzers.NonPlatform.ApiLayerAnalyzer; +using Roles = NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.Roles; +using UsedImplicitly = NonPlatformAnalyzers::JetBrains.Annotations.UsedImplicitlyAttribute; + +namespace Tools.Analyzers.NonPlatform.UnitTests; + +[UsedImplicitly] +public class ApiLayerAnalyzerSpec +{ + [UsedImplicitly] + public class GivenAWebApiService + { + [Trait("Category", "Unit")] + public class GivenAnyRule + { + [Fact] + public async Task WhenInExcludedNamespace_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +namespace Common; +public class AClass : IWebApiService +{ +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenNotWebApiClass_ThenNoAlert() + { + const string input = @" +namespace ANamespace; +public class AClass +{ +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasNoMethods_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +namespace ANamespace; +public class AClass : IWebApiService +{ +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasPrivateMethod_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +namespace ANamespace; +public class AClass : IWebApiService +{ + private void AMethod(){} +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasInternalMethod_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +namespace ANamespace; +public class AClass : IWebApiService +{ + internal void AMethod(){} +}"; + + await Verify.NoDiagnosticExists(input); + } + } + + [Trait("Category", "Unit")] + public class GivenRule010 + { + [Fact] + public async Task WhenHasPublicMethodWithVoidReturnType_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +namespace ANamespace; +public class AClass : IWebApiService +{ + public void AMethod(){} +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule010, + input, 6, 17, "AMethod", + TypeExtensions.Stringify( + ApiLayerAnalyzer.AllowableServiceOperationReturnTypes)); + } + + [Fact] + public async Task WhenHasPublicMethodWithTaskReturnType_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +namespace ANamespace; +public class AClass : IWebApiService +{ + public Task AMethod(){ return Task.CompletedTask; } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule010, + input, 7, 17, "AMethod", + TypeExtensions.Stringify( + ApiLayerAnalyzer.AllowableServiceOperationReturnTypes)); + } + + [Fact] + public async Task WhenHasPublicMethodWithWrongTaskReturnType_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +namespace ANamespace; +public class AClass : IWebApiService +{ + public Task AMethod(){ return Task.FromResult(string.Empty); } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule010, + input, 7, 25, "AMethod", + TypeExtensions.Stringify( + ApiLayerAnalyzer.AllowableServiceOperationReturnTypes)); + } + + [Fact] + public async Task WhenHasPublicMethodWithTaskOfApiEmptyResultReturnType_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public Task AMethod(TestGetRouteAttributeRequest request) + { + return Task.FromResult(() => new Result()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasPublicMethodWithTaskOfApiResultReturnType_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public Task> AMethod(TestGetRouteAttributeRequest request) + { + return Task.FromResult>(() => new Result()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasPublicMethodWithTaskOfApiPostResultReturnType_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public Task> AMethod(TestPostRouteAttributeRequest request) + { + return Task.FromResult>(() => new Result, Error>()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasPublicMethodWithTaskOfApiGetResultReturnType_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public Task> AMethod(TestGetRouteAttributeRequest request) + { + return Task.FromResult>(() => new Result()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasPublicMethodWithTaskOfApiSearchResultReturnType_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public Task> AMethod(TestSearchRouteAttributeRequest request) + { + return Task.FromResult>(() => new Result()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasPublicMethodWithTaskOfApiPutPatchResultReturnType_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public Task> AMethod(TestPutPatchRouteAttributeRequest request) + { + return Task.FromResult>(() => new Result()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasPublicMethodWithTaskOfApiDeleteResultReturnType_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public Task AMethod(TestDeleteRouteAttributeRequest request) + { + return Task.FromResult(() => new Result()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasPublicMethodWithWrongNakedReturnType_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +namespace ANamespace; +public class AClass : IWebApiService +{ + public string AMethod(){ return string.Empty; } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule010, + input, 6, + 19, "AMethod", + TypeExtensions.Stringify( + ApiLayerAnalyzer + .AllowableServiceOperationReturnTypes)); + } + + [Fact] + public async Task WhenHasPublicMethodWithNakedApiEmptyResultReturnType_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasPublicMethodWithNakedApiResultReturnType_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiResult AMethod(TestGetRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasPublicMethodWithNakedApiPostResultReturnType_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiPostResult AMethod(TestPostRouteAttributeRequest request) + { + return () => new Result, Error>(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasPublicMethodWithNakedApiGetResultReturnType_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiGetResult AMethod(TestGetRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasPublicMethodWithNakedApiSearchResultReturnType_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiSearchResult AMethod(TestSearchRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasPublicMethodWithNakedApiPutPatchResultReturnType_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiPutPatchResult AMethod(TestPutPatchRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenHasPublicMethodWithNakedApiDeleteResultReturnType_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiDeleteResult AMethod(TestDeleteRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + } + + [Trait("Category", "Unit")] + public class GivenRule011And012 + { + [Fact] + public async Task WhenHasNoParameters_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod() + { + return () => new Result(); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule011, + input, 8, 27, "AMethod"); + } + + [Fact] + public async Task WhenHasTooManyParameters_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request, CancellationToken cancellationToken, string value) + { + return () => new Result(); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule011, + input, 10, 27, "AMethod"); + } + + [Fact] + public async Task WhenFirstParameterIsNotRequestType_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(string value) + { + return () => new Result(); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule011, + input, 8, 27, "AMethod"); + } + + [Fact] + public async Task WhenSecondParameterIsNotCancellationToken_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request, string value) + { + return () => new Result(); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule012, + input, 9, 27, "AMethod"); + } + + [Fact] + public async Task WhenOnlyRequest_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenRequestAndCancellation_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request, CancellationToken cancellationToken) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + } + + [Trait("Category", "Unit")] + public class GivenRule013AndRule017 + { + [Fact] + public async Task WhenHasNoAttributes_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestNoRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.DiagnosticExists(input, + (ApiLayerAnalyzer.Rule013, 9, 27, "AMethod", null), + (ApiLayerAnalyzer.Rule017, 9, 35, "TestNoRouteAttributeRequest", null)); + } + + [Fact] + public async Task WhenMissingAttribute_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + [TestAttribute] + public ApiEmptyResult AMethod(TestNoRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.DiagnosticExists(input, + (ApiLayerAnalyzer.Rule013, 10, 27, "AMethod", null), + (ApiLayerAnalyzer.Rule017, 10, 35, "TestNoRouteAttributeRequest", null)); + } + + [Fact] + public async Task WhenAttribute_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + } + + [Trait("Category", "Unit")] + public class GivenRule014 + { + [Fact] + public async Task WhenOneRoute_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod1(TestGetRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenTwoWithSameRoute_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod1(TestGetRouteAttributeRequest1 request) + { + return () => new Result(); + } + public ApiEmptyResult AMethod2(TestGetRouteAttributeRequest2 request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenThreeWithSameRouteFirstSegment_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod1(TestGetRouteAttributeRequest1 request) + { + return () => new Result(); + } + public ApiEmptyResult AMethod2(TestGetRouteAttributeRequest2 request) + { + return () => new Result(); + } + public ApiEmptyResult AMethod3(TestGetRouteAttributeRequest3 request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenDifferentRouteSegments_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod1(TestGetRouteAttributeRequest1 request) + { + return () => new Result(); + } + public ApiEmptyResult AMethod2(TestGetRouteAttributeRequest2 request) + { + return () => new Result(); + } + public ApiEmptyResult AMethod4(TestGetRouteAttributeRequest4 request) + { + return () => new Result(); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule014, + input, 17, 27, "AMethod4"); + } + } + + [Trait("Category", "Unit")] + public class GivenRule015 + { + [Fact] + public async Task WhenNoDuplicateRequests_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenDuplicateRequests_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod1(TestGetRouteAttributeRequest1 request) + { + return () => new Result(); + } + public ApiEmptyResult AMethod2(TestGetRouteAttributeRequest1 request) + { + return () => new Result(); + } + public ApiEmptyResult AMethod3(TestGetRouteAttributeRequest2 request) + { + return () => new Result(); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule015, + input, + (9, 27, "AMethod1"), + (13, 27, "AMethod2")); + } + } + + [Trait("Category", "Unit")] + public class GivenRule016 + { + [Fact] + public async Task WhenPostAndReturnsApiEmptyResult_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestPostRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenGetAndReturnsApiEmptyResult_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenSearchAndReturnsApiEmptyResult_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestSearchRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenPutPatchAndReturnsApiEmptyResult_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestPutPatchRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenDeleteAndReturnsApiEmptyResult_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestDeleteRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenPostAndReturnsApiResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiResult AMethod(TestPostRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 44, "AMethod", ServiceOperation.Post, ExpectedAllowedResultTypes(ServiceOperation.Post)); + } + + [Fact] + public async Task WhenGetAndReturnsApiResult_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiResult AMethod(TestGetRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenSearchAndReturnsApiResult_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiResult AMethod(TestSearchRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenPutPatchAndReturnsApiResult_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiResult AMethod(TestPutPatchRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenDeleteAndReturnsApiResult_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiResult AMethod(TestDeleteRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenPostAndReturnsApiPostResult_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiPostResult AMethod(TestPostRouteAttributeRequest request) + { + return () => new PostResult(new TestResponse(), ""/alocation""); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenGetAndReturnsApiPostResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiPostResult AMethod(TestGetRouteAttributeRequest request) + { + return () => new PostResult(new TestResponse(), ""/alocation""); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 54, "AMethod", ServiceOperation.Get, ExpectedAllowedResultTypes(ServiceOperation.Get)); + } + + [Fact] + public async Task WhenSearchAndReturnsApiPostResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiPostResult AMethod(TestSearchRouteAttributeRequest request) + { + return () => new PostResult(new TestResponse(), ""/alocation""); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 54, "AMethod", ServiceOperation.Search, + ExpectedAllowedResultTypes(ServiceOperation.Search)); + } + + [Fact] + public async Task WhenPutPatchAndReturnsApiPostResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiPostResult AMethod(TestPutPatchRouteAttributeRequest request) + { + return () => new PostResult(new TestResponse(), ""/alocation""); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 54, "AMethod", ServiceOperation.PutPatch, + ExpectedAllowedResultTypes(ServiceOperation.PutPatch)); + } + + [Fact] + public async Task WhenDeleteAndReturnsApiPostResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiPostResult AMethod(TestDeleteRouteAttributeRequest request) + { + return () => new PostResult(new TestResponse(), ""/alocation""); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 54, "AMethod", ServiceOperation.Delete, + ExpectedAllowedResultTypes(ServiceOperation.Delete)); + } + + [Fact] + public async Task WhenPostAndReturnsApiGetResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiGetResult AMethod(TestPostRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 47, "AMethod", ServiceOperation.Post, ExpectedAllowedResultTypes(ServiceOperation.Post)); + } + + [Fact] + public async Task WhenGetAndReturnsApiGetResult_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiGetResult AMethod(TestGetRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenSearchAndReturnsApiGetResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiGetResult AMethod(TestSearchRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenPutPatchAndReturnsApiGetResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiGetResult AMethod(TestPutPatchRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 47, "AMethod", ServiceOperation.PutPatch, + ExpectedAllowedResultTypes(ServiceOperation.PutPatch)); + } + + [Fact] + public async Task WhenDeleteAndReturnsApiGetResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiGetResult AMethod(TestDeleteRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 47, "AMethod", ServiceOperation.Delete, + ExpectedAllowedResultTypes(ServiceOperation.Delete)); + } + + [Fact] + public async Task WhenPostAndReturnsApiSearchResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiSearchResult AMethod(TestPostRouteAttributeRequest request) + { + return () => new Result(new TestSearchResponse()); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 56, "AMethod", ServiceOperation.Post, ExpectedAllowedResultTypes(ServiceOperation.Post)); + } + + [Fact] + public async Task WhenGetAndReturnsApiSearchResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiSearchResult AMethod(TestGetRouteAttributeRequest request) + { + return () => new Result(new TestSearchResponse()); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 56, "AMethod", ServiceOperation.Get, ExpectedAllowedResultTypes(ServiceOperation.Get)); + } + + [Fact] + public async Task WhenSearchAndReturnsApiSearchResult_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiSearchResult AMethod(TestSearchRouteAttributeRequest request) + { + return () => new Result(new TestSearchResponse()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenPutPatchAndReturnsApiSearchResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiSearchResult AMethod(TestPutPatchRouteAttributeRequest request) + { + return () => new Result(new TestSearchResponse()); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 56, "AMethod", ServiceOperation.PutPatch, + ExpectedAllowedResultTypes(ServiceOperation.PutPatch)); + } + + [Fact] + public async Task WhenDeleteAndReturnsApiSearchResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiSearchResult AMethod(TestDeleteRouteAttributeRequest request) + { + return () => new Result(new TestSearchResponse()); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 56, "AMethod", ServiceOperation.Delete, + ExpectedAllowedResultTypes(ServiceOperation.Delete)); + } + + [Fact] + public async Task WhenPostAndReturnsApiPutPatchResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiPutPatchResult AMethod(TestPostRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 52, "AMethod", ServiceOperation.Post, ExpectedAllowedResultTypes(ServiceOperation.Post)); + } + + [Fact] + public async Task WhenGetAndReturnsApiPutPatchResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiPutPatchResult AMethod(TestGetRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 52, "AMethod", ServiceOperation.Get, ExpectedAllowedResultTypes(ServiceOperation.Get)); + } + + [Fact] + public async Task WhenSearchAndReturnsApiPutPatchResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiPutPatchResult AMethod(TestSearchRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 52, "AMethod", ServiceOperation.Search, + ExpectedAllowedResultTypes(ServiceOperation.Search)); + } + + [Fact] + public async Task WhenPutPatchAndReturnsApiPutPatchResult_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiPutPatchResult AMethod(TestPutPatchRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenDeleteAndReturnsApiPutPatchResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiPutPatchResult AMethod(TestDeleteRouteAttributeRequest request) + { + return () => new Result(new TestResponse()); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 52, "AMethod", ServiceOperation.Delete, + ExpectedAllowedResultTypes(ServiceOperation.Delete)); + } + + [Fact] + public async Task WhenPostAndReturnsApiDeleteResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiDeleteResult AMethod(TestPostRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 28, "AMethod", ServiceOperation.Post, ExpectedAllowedResultTypes(ServiceOperation.Post)); + } + + [Fact] + public async Task WhenGetAndReturnsApiDeleteResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiDeleteResult AMethod(TestGetRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 28, "AMethod", ServiceOperation.Get, ExpectedAllowedResultTypes(ServiceOperation.Get)); + } + + [Fact] + public async Task WhenSearchAndReturnsApiDeleteResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiDeleteResult AMethod(TestSearchRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 28, "AMethod", ServiceOperation.Search, + ExpectedAllowedResultTypes(ServiceOperation.Search)); + } + + [Fact] + public async Task WhenPutPatchAndReturnsApiDeleteResult_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiDeleteResult AMethod(TestPutPatchRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule016, + input, 9, 28, "AMethod", ServiceOperation.PutPatch, + ExpectedAllowedResultTypes(ServiceOperation.PutPatch)); + } + + [Fact] + public async Task WhenDeleteAndReturnsApiDeleteResult_ThenNotAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiDeleteResult AMethod(TestDeleteRouteAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + private static string ExpectedAllowedResultTypes(ServiceOperation operation) + { + return TypeExtensions.Stringify( + ApiLayerAnalyzer + .AllowableOperationReturnTypes[operation].ToArray()); + } + } + + [Trait("Category", "Unit")] + public class GivenRule018AndRule019 + { + [Fact] + public async Task WhenRouteIsAnonymousAndMissingAuthorizeAttribute_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestAnonymousRouteNoAuthorizeAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenRouteIsNotAnonymousAndAuthorizeAttribute_ThenNoAlert() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestSecureRouteAuthorizeAttributeRequest request) + { + return () => new Result(); + } +}"; + + await Verify.NoDiagnosticExists(input); + } + + [Fact] + public async Task WhenRouteIsAnonymousAndAuthorizeAttribute_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestAnonymousRouteAuthorizeAttributeRequest request) + { + return () => new Result(); + } +}"; + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule018, + input, 9, 35, "TestAnonymousRouteAuthorizeAttributeRequest"); + } + + [Fact] + public async Task WhenRouteIsNotAnonymousAndNoAuthorizeAttribute_ThenAlerts() + { + const string input = @" +using Infrastructure.Web.Api.Interfaces; +using System.Threading.Tasks; +using Common; +using Tools.Analyzers.NonPlatform.UnitTests; +namespace ANamespace; +public class AClass : IWebApiService +{ + public ApiEmptyResult AMethod(TestSecureRouteNoAuthorizeAttributeRequest request) + { + return () => new Result(); + } +}"; + await Verify.DiagnosticExists(ApiLayerAnalyzer.Rule019, + input, 9, 35, "TestSecureRouteNoAuthorizeAttributeRequest"); + } + } + } + + [UsedImplicitly] + public class GivenARequest + { + [Trait("Category", "Unit")] + public class GivenRule030 + { + [Fact] + public async Task WhenIsNotPublic_ThenAlerts() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +[Route(""/apath"", ServiceOperation.Get)] +internal class ARequest : IWebRequest +{ + public required string AProperty { get; set; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule030, input, 6, 16, "ARequest"); + } + } + + [Trait("Category", "Unit")] + public class GivenRule031 + { + [Fact] + public async Task WhenIsNotNamedCorrectly_ThenAlerts() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +[Route(""/apath"", ServiceOperation.Get)] +public class AClass : IWebRequest +{ + public required string AProperty { get; set; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule031, input, 6, 14, "AClass"); + } + } + + [Trait("Category", "Unit")] + public class GivenRule032 + { + [Fact] + public async Task WhenIsNotInCorrectAssembly_ThenAlerts() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace anamespace; +[Route(""/apath"", ServiceOperation.Get)] +public class ARequest : IWebRequest +{ + public required string AProperty { get; set; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule032, input, 6, 14, "ARequest", + AnalyzerConstants.ServiceOperationTypesNamespace); + } + } + + [Trait("Category", "Unit")] + public class GivenRule033 + { + [Fact] + public async Task WhenHasNoRouteAttribute_ThenAlerts() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +public class ARequest : IWebRequest +{ + public required string AProperty { get; set; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule033, input, 5, 14, "ARequest"); + } + } + + [Trait("Category", "Unit")] + public class GivenRule034 + { + [Fact] + public async Task WhenHasCtorAndNotParameterless_ThenAlerts() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +[Route(""/apath"", ServiceOperation.Get)] +public class ARequest : IWebRequest +{ + public ARequest(string value) + { + AProperty = value; + } + + public required string AProperty { get; set; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule034, input, 6, 14, "ARequest"); + } + + [Fact] + public async Task WhenHasCtorAndPrivate_ThenAlerts() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +[Route(""/apath"", ServiceOperation.Get)] +public class ARequest : IWebRequest +{ + private ARequest() + { + AProperty = string.Empty; + } + + public required string AProperty { get; set; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule034, input, 6, 14, "ARequest"); + } + + [Fact] + public async Task WhenHasCtorAndIsParameterless_ThenNoAlert() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +[Route(""/apath"", ServiceOperation.Get)] +public class ARequest : IWebRequest +{ + public ARequest() + { + AProperty = string.Empty; + } + + public required string AProperty { get; set; } +}"; + + await Verify.NoDiagnosticExists(input); + } + } + + [Trait("Category", "Unit")] + public class GivenRule035 + { + [Fact] + public async Task WhenAnyPropertyHasNoSetter_ThenAlerts() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +[Route(""/apath"", ServiceOperation.Get)] +public class ARequest : IWebRequest +{ + public string? AProperty { get; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule035, input, 8, 20, "AProperty"); + } + } + + [Trait("Category", "Unit")] + public class GivenRule036 + { + [Fact] + public async Task WhenAnyPropertyReferenceTypeIsOptional_ThenAlerts() + { + const string input = @" +using System; +using Common; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +[Route(""/apath"", ServiceOperation.Get)] +public class ARequest : IWebRequest +{ + public Optional AProperty { get; set; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule036, input, 9, 29, "AProperty"); + } + + [Fact] + public async Task WhenAnyPropertyReferenceTypeIsNullable_ThenNoAlert() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +[Route(""/apath"", ServiceOperation.Get)] +public class ARequest : IWebRequest +{ + public string? AProperty { get; set; } +}"; + + await Verify.NoDiagnosticExists(input); + } + } + } + + [UsedImplicitly] + public class GivenAResponse + { + [Trait("Category", "Unit")] + public class GivenRule040 + { + [Fact] + public async Task WhenIsNotPublic_ThenAlerts() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +internal class AResponse : IWebResponse +{ + public required string AProperty { get; set; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule040, input, 5, 16, "AResponse"); + } + } + + [Trait("Category", "Unit")] + public class GivenRule041 + { + [Fact] + public async Task WhenIsNotNamedCorrectly_ThenAlerts() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +public class AClass : IWebResponse +{ + public required string AProperty { get; set; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule041, input, 5, 14, "AClass"); + } + } + + [Trait("Category", "Unit")] + public class GivenRule042 + { + [Fact] + public async Task WhenIsNotInCorrectAssembly_ThenAlerts() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace anamespace; +public class AResponse : IWebResponse +{ + public required string AProperty { get; set; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule042, input, 5, 14, "AResponse", + AnalyzerConstants.ServiceOperationTypesNamespace); + } + } + + [Trait("Category", "Unit")] + [Trait("Category", "Unit")] + public class GivenRule043 + { + [Fact] + public async Task WhenHasCtorAndNotParameterless_ThenAlerts() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +public class AResponse : IWebResponse +{ + public AResponse(string value) + { + AProperty = value; + } + + public required string AProperty { get; set; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule043, input, 5, 14, "AResponse"); + } + + [Fact] + public async Task WhenHasCtorAndPrivate_ThenAlerts() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +public class AResponse : IWebResponse +{ + private AResponse() + { + AProperty = string.Empty; + } + + public required string AProperty { get; set; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule043, input, 5, 14, "AResponse"); + } + + [Fact] + public async Task WhenHasCtorAndIsParameterless_ThenNoAlert() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +public class AResponse : IWebResponse +{ + public AResponse() + { + AProperty = string.Empty; + } + + public required string AProperty { get; set; } +}"; + + await Verify.NoDiagnosticExists(input); + } + } + + [Trait("Category", "Unit")] + public class GivenRule044 + { + [Fact] + public async Task WhenAnyPropertyHasNoSetter_ThenAlerts() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +public class AResponse : IWebResponse +{ + public string? AProperty { get; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule044, input, 7, 20, "AProperty"); + } + } + + [Trait("Category", "Unit")] + public class GivenRule045 + { + [Fact] + public async Task WhenAnyPropertyReferenceTypeIsOptional_ThenAlerts() + { + const string input = @" +using System; +using Common; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +public class AResponse : IWebResponse +{ + public Optional AProperty { get; set; } +}"; + + await Verify.DiagnosticExists( + ApiLayerAnalyzer.Rule045, input, 8, 29, "AProperty"); + } + + [Fact] + public async Task WhenAnyPropertyReferenceTypeIsNullable_ThenNoAlert() + { + const string input = @" +using System; +using Infrastructure.Web.Api.Interfaces; +namespace Infrastructure.Web.Api.Operations.Shared.Test; +public class AResponse : IWebResponse +{ + public string? AProperty { get; set; } +}"; + + await Verify.NoDiagnosticExists(input); + } + } + } +} + +[UsedImplicitly] +public class TestResource; + +[UsedImplicitly] +public class TestResponse : IWebResponse; + +[UsedImplicitly] +public class TestSearchResponse : IWebSearchResponse +{ + public SearchResultMetadata? Metadata { get; set; } +} + +[UsedImplicitly] +public class TestNoRouteAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; + +[Route("/aresource", ServiceOperation.Search)] +[UsedImplicitly] +public class TestSearchRouteAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; + +[Route("/aresource", ServiceOperation.Post)] +[UsedImplicitly] +public class TestPostRouteAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; + +[Route("/aresource", ServiceOperation.Get)] +[UsedImplicitly] +public class TestGetRouteAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; + +[Route("/aresource/1", ServiceOperation.Get)] +[UsedImplicitly] +public class TestGetRouteAttributeRequest1 : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; + +[Route("/aresource/2", ServiceOperation.Get)] +[UsedImplicitly] +public class TestGetRouteAttributeRequest2 : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; + +[Route("/aresource/3", ServiceOperation.Get)] +[UsedImplicitly] +public class TestGetRouteAttributeRequest3 : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; + +[Route("/anotherresource/1", ServiceOperation.Get)] +[UsedImplicitly] +public class TestGetRouteAttributeRequest4 : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; + +[Route("/aresource", ServiceOperation.PutPatch)] +[UsedImplicitly] +public class TestPutPatchRouteAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; + +[Route("/aresource", ServiceOperation.Delete)] +[UsedImplicitly] +public class TestDeleteRouteAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; + +[AttributeUsage(AttributeTargets.Method)] +[UsedImplicitly] +public class TestAttribute : Attribute; + +[Route("/aresource", ServiceOperation.Post)] +[UsedImplicitly] +public class TestAnonymousRouteNoAuthorizeAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; + +[Route("/aresource", ServiceOperation.Post)] +[Authorize(Roles.Platform_Standard)] +[UsedImplicitly] +public class TestAnonymousRouteAuthorizeAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; + +[Route("/aresource", ServiceOperation.Post, AccessType.Token)] +[Authorize(Roles.Platform_Standard)] +[UsedImplicitly] +public class TestSecureRouteAuthorizeAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; + +[Route("/aresource", ServiceOperation.Post, AccessType.Token)] +[UsedImplicitly] +public class TestSecureRouteNoAuthorizeAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; \ No newline at end of file diff --git a/src/Tools.Analyzers.NonPlatform.UnitTests/ApplicationLayerAnalyzerSpec.cs b/src/Tools.Analyzers.NonPlatform.UnitTests/ApplicationLayerAnalyzerSpec.cs index 57ddcd6c..9cb2ecfd 100644 --- a/src/Tools.Analyzers.NonPlatform.UnitTests/ApplicationLayerAnalyzerSpec.cs +++ b/src/Tools.Analyzers.NonPlatform.UnitTests/ApplicationLayerAnalyzerSpec.cs @@ -33,7 +33,7 @@ public class AClass : IIdentifiableResource } [Trait("Category", "Unit")] - public class GivenRuleSas070 + public class GivenRule010 { [Fact] public async Task WhenIsNotPublic_ThenAlerts() @@ -48,12 +48,12 @@ internal class AClass : IIdentifiableResource }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas070, input, 5, 16, "AClass"); + ApplicationLayerAnalyzer.Rule010, input, 5, 16, "AClass"); } } [Trait("Category", "Unit")] - public class GivenRuleSas071 + public class GivenRule011 { [Fact] public async Task WhenHasCtorAndNotParameterless_ThenAlerts() @@ -73,7 +73,7 @@ public AClass(string id) }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas071, input, 5, 14, "AClass"); + ApplicationLayerAnalyzer.Rule011, input, 5, 14, "AClass"); } [Fact] @@ -94,7 +94,7 @@ private AClass() }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas071, input, 5, 14, "AClass"); + ApplicationLayerAnalyzer.Rule011, input, 5, 14, "AClass"); } [Fact] @@ -119,7 +119,7 @@ public AClass() } [Trait("Category", "Unit")] - public class GivenRuleSas072 + public class GivenRule012 { [Fact] public async Task WhenAnyPropertyHasNoSetter_ThenAlerts() @@ -136,12 +136,12 @@ public class AClass : IIdentifiableResource }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas072, input, 9, 20, "AProperty"); + ApplicationLayerAnalyzer.Rule012, input, 9, 20, "AProperty"); } } [Trait("Category", "Unit")] - public class GivenRuleSas073 + public class GivenRule013 { [Fact] public async Task WhenAnyPropertyReferenceTypeIsOptional_ThenAlerts() @@ -159,9 +159,9 @@ public class AClass : IIdentifiableResource }"; await Verify.DiagnosticExists(input, - (ApplicationLayerAnalyzer.Sas073, 10, 29, "AProperty", null), - (ApplicationLayerAnalyzer.Sas074, 10, 29, "AProperty", [ - GivenRuleSas074.AllTypes, + (ApplicationLayerAnalyzer.Rule013, 10, 29, "AProperty", null), + (ApplicationLayerAnalyzer.Rule014, 10, 29, "AProperty", [ + GivenRule014.AllTypes, AnalyzerConstants.ResourceTypesNamespace ])); } @@ -185,7 +185,7 @@ public class AClass : IIdentifiableResource } [Trait("Category", "Unit")] - public class GivenRuleSas074 + public class GivenRule014 { public const string AllTypes = "bool or string or ulong or int or long or double or decimal or System.DateTime or byte or System.IO.Stream"; @@ -206,7 +206,7 @@ public class AClass : IIdentifiableResource }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas074, input, 10, 26, "AProperty", AllTypes, + ApplicationLayerAnalyzer.Rule014, input, 10, 26, "AProperty", AllTypes, AnalyzerConstants.ResourceTypesNamespace); } @@ -226,7 +226,7 @@ public class AClass : IIdentifiableResource }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas074, input, 10, 32, "AProperty", AllTypes, + ApplicationLayerAnalyzer.Rule014, input, 10, 32, "AProperty", AllTypes, AnalyzerConstants.ResourceTypesNamespace); } @@ -246,7 +246,7 @@ public class AClass : IIdentifiableResource }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas074, input, 10, 46, "AProperty", AllTypes, + ApplicationLayerAnalyzer.Rule014, input, 10, 46, "AProperty", AllTypes, AnalyzerConstants.ResourceTypesNamespace); } @@ -266,7 +266,7 @@ public class AClass : IIdentifiableResource }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas074, input, 10, 46, "AProperty", AllTypes, + ApplicationLayerAnalyzer.Rule014, input, 10, 46, "AProperty", AllTypes, AnalyzerConstants.ResourceTypesNamespace); } @@ -423,7 +423,7 @@ public enum AnotherClass }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas074, input, 12, 38, "AProperty", AllTypes, + ApplicationLayerAnalyzer.Rule014, input, 12, 38, "AProperty", AllTypes, AnalyzerConstants.ResourceTypesNamespace); } @@ -509,7 +509,7 @@ public enum AnEnum }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas074, input, 12, 32, "AProperty", AllTypes, + ApplicationLayerAnalyzer.Rule014, input, 12, 32, "AProperty", AllTypes, AnalyzerConstants.ResourceTypesNamespace); } @@ -610,7 +610,7 @@ public enum AnotherClass }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas074, input, 12, 44, "AProperty", AllTypes, + ApplicationLayerAnalyzer.Rule014, input, 12, 44, "AProperty", AllTypes, AnalyzerConstants.ResourceTypesNamespace); } @@ -711,7 +711,7 @@ public enum AnotherClass }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas074, input, 12, 58, "AProperty", AllTypes, + ApplicationLayerAnalyzer.Rule014, input, 12, 58, "AProperty", AllTypes, AnalyzerConstants.ResourceTypesNamespace); } } @@ -743,7 +743,7 @@ public class AClass : ReadModelEntity } [Trait("Category", "Unit")] - public class GivenRuleSas080 + public class GivenRule020 { [Fact] public async Task WhenIsNotPublic_ThenAlerts() @@ -761,12 +761,12 @@ internal class AClass : ReadModelEntity }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas080, input, 8, 16, "AClass"); + ApplicationLayerAnalyzer.Rule020, input, 8, 16, "AClass"); } } [Trait("Category", "Unit")] - public class GivenRuleSas081 + public class GivenRule021 { [Fact] public async Task WhenMissingEntityNameAttribute_ThenAlerts() @@ -783,12 +783,12 @@ public class AClass : ReadModelEntity }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas081, input, 7, 14, "AClass"); + ApplicationLayerAnalyzer.Rule021, input, 7, 14, "AClass"); } } [Trait("Category", "Unit")] - public class GivenRuleSas082 + public class GivenRule022 { [Fact] public async Task WhenHasCtorAndNotParameterless_ThenAlerts() @@ -811,7 +811,7 @@ public AClass(string id) }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas082, input, 8, 14, "AClass"); + ApplicationLayerAnalyzer.Rule022, input, 8, 14, "AClass"); } [Fact] @@ -835,7 +835,7 @@ private AClass() }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas082, input, 8, 14, "AClass"); + ApplicationLayerAnalyzer.Rule022, input, 8, 14, "AClass"); } [Fact] @@ -863,7 +863,7 @@ public AClass() } [Trait("Category", "Unit")] - public class GivenRuleSas083 + public class GivenRule023 { [Fact] public async Task WhenAnyPropertyHasNoSetter_ThenAlerts() @@ -881,12 +881,12 @@ public class AClass : ReadModelEntity }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas083, input, 10, 29, "AProperty"); + ApplicationLayerAnalyzer.Rule023, input, 10, 29, "AProperty"); } } [Trait("Category", "Unit")] - public class GivenRuleSas084 + public class GivenRule024 { [Fact] public async Task WhenAnyPropertyIsNullable_ThenAlerts() @@ -904,7 +904,7 @@ public class AClass : ReadModelEntity }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas084, input, 10, 20, "AProperty"); + ApplicationLayerAnalyzer.Rule024, input, 10, 20, "AProperty"); } [Fact] @@ -947,7 +947,7 @@ public class AClass : ReadModelEntity } [Trait("Category", "Unit")] - public class GivenRuleSas085 + public class GivenRule025 { private const string AllTypes = "bool or string or ulong or int or long or double or decimal or System.DateTime or byte"; @@ -970,7 +970,7 @@ public class AClass : ReadModelEntity }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas085, input, 12, 27, "AProperty", AllTypes); + ApplicationLayerAnalyzer.Rule025, input, 12, 27, "AProperty", AllTypes); } [Fact] @@ -991,7 +991,7 @@ public class AClass : ReadModelEntity }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas085, input, 12, 33, "AProperty", AllTypes); + ApplicationLayerAnalyzer.Rule025, input, 12, 33, "AProperty", AllTypes); } [Fact] @@ -1012,7 +1012,7 @@ public class AClass : ReadModelEntity }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas085, input, 12, 47, "AProperty", AllTypes); + ApplicationLayerAnalyzer.Rule025, input, 12, 47, "AProperty", AllTypes); } [Fact] @@ -1033,7 +1033,7 @@ public class AClass : ReadModelEntity }"; await Verify.DiagnosticExists( - ApplicationLayerAnalyzer.Sas085, input, 12, 47, "AProperty", AllTypes); + ApplicationLayerAnalyzer.Rule025, input, 12, 47, "AProperty", AllTypes); } [Fact] diff --git a/src/Tools.Analyzers.NonPlatform.UnitTests/DomainDrivenDesignAnalyzerSpec.cs b/src/Tools.Analyzers.NonPlatform.UnitTests/DomainDrivenDesignAnalyzerSpec.cs index 082bb064..5c816c7e 100644 --- a/src/Tools.Analyzers.NonPlatform.UnitTests/DomainDrivenDesignAnalyzerSpec.cs +++ b/src/Tools.Analyzers.NonPlatform.UnitTests/DomainDrivenDesignAnalyzerSpec.cs @@ -66,7 +66,7 @@ public sealed class AClass public class GivenARootAggregate { [Trait("Category", "Unit")] - public class GivenRuleSas030 + public class GivenRule010 { [Fact] public async Task WhenHasNoCreateMethod_ThenAlerts() @@ -104,7 +104,7 @@ public static AggregateRootFactory Rehydrate() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas030, input, 11, + DomainDrivenDesignAnalyzer.Rule010, input, 11, 21, "AClass"); } @@ -149,7 +149,7 @@ public void Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas030, input, 11, + DomainDrivenDesignAnalyzer.Rule010, input, 11, 21, "AClass"); } @@ -217,7 +217,7 @@ public static EventOccurred Create() } [Trait("Category", "Unit")] - public class GivenRuleSas031 + public class GivenRule011 { [Fact] public async Task WhenCreateReturnsVoid_ThenAlerts() @@ -277,7 +277,7 @@ public static EventOccurred Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas031, input, 33, + DomainDrivenDesignAnalyzer.Rule011, input, 33, 24, "Create", "ANamespace.AClass or Common.Result"); } @@ -341,7 +341,7 @@ public static EventOccurred Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas031, input, 33, + DomainDrivenDesignAnalyzer.Rule011, input, 33, 26, "Create", "ANamespace.AClass or Common.Result"); } @@ -470,7 +470,7 @@ public static EventOccurred Create() } [Trait("Category", "Unit")] - public class GivenRuleSas032 + public class GivenRule012 { [Fact] public async Task WhenCreateMethodIsEmpty_ThenAlerts() @@ -513,7 +513,7 @@ public static AClass Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas032, input, 32, + DomainDrivenDesignAnalyzer.Rule012, input, 32, 26, "Create", DomainDrivenDesignAnalyzer.ConstructorMethodCall); } @@ -581,7 +581,7 @@ public static EventOccurred Create() } [Trait("Category", "Unit")] - public class GivenRuleSas033 + public class GivenRule013 { [Fact] public async Task WhenNonPrivateConstructor_ThenAlerts() @@ -642,7 +642,7 @@ public static EventOccurred Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas033, input, 14, + DomainDrivenDesignAnalyzer.Rule013, input, 14, 12, "AClass"); } @@ -709,7 +709,7 @@ public static EventOccurred Create() } [Trait("Category", "Unit")] - public class GivenRuleSas034 + public class GivenRule014 { [Fact] public async Task WhenMissingRehydrateMethod_ThenAlerts() @@ -756,7 +756,7 @@ public static EventOccurred Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas034, input, 8, + DomainDrivenDesignAnalyzer.Rule014, input, 8, 21, "AClass"); } @@ -825,7 +825,7 @@ public static EventOccurred Create() } [Trait("Category", "Unit")] - public class GivenRuleSas035 + public class GivenRule015 { [Fact] public async Task WhenDehydratableAndMissingDehydrateMethod_ThenAlerts() @@ -888,7 +888,7 @@ public static EventOccurred Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas035, input, 14, + DomainDrivenDesignAnalyzer.Rule015, input, 14, 21, "AClass"); } @@ -963,7 +963,7 @@ public static EventOccurred Create() } [Trait("Category", "Unit")] - public class GivenRuleSas036 + public class GivenRule016 { [Fact] public async Task WhenDehydratableAndMissingEntityNameAttribute_ThenAlerts() @@ -1031,7 +1031,7 @@ public static EventOccurred Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas036, input, 14, + DomainDrivenDesignAnalyzer.Rule016, input, 14, 21, "AClass"); } @@ -1107,7 +1107,7 @@ public static EventOccurred Create() } [Trait("Category", "Unit")] - public class GivenRuleSas037 + public class GivenRule017 { [Fact] public async Task WhenPropertyHasPublicSetter_ThenAlerts() @@ -1170,7 +1170,7 @@ public static EventOccurred Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas037, input, 40, + DomainDrivenDesignAnalyzer.Rule017, input, 40, 19, "AProperty"); } @@ -1365,7 +1365,7 @@ public static EventOccurred Create() } [Trait("Category", "Unit")] - public class GivenRuleSas038 + public class GivenRule018 { [Fact] public async Task WhenClassIsNotSealed_ThenAlerts() @@ -1428,7 +1428,7 @@ public static EventOccurred Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas038, input, 12, + DomainDrivenDesignAnalyzer.Rule018, input, 12, 14, "AClass"); } @@ -1501,7 +1501,7 @@ public static EventOccurred Create() public class GivenAnEntity { [Trait("Category", "Unit")] - public class GivenRuleSas040 + public class GivenRule020 { [Fact] public async Task WhenHasNoCreateMethod_ThenAlerts() @@ -1527,7 +1527,7 @@ protected override Result OnStateChanged(IDomainEvent @event) }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas040, input, 9, + DomainDrivenDesignAnalyzer.Rule020, input, 9, 21, "AClass"); } @@ -1559,7 +1559,7 @@ public void Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas040, input, 9, + DomainDrivenDesignAnalyzer.Rule020, input, 9, 21, "AClass"); } @@ -1598,7 +1598,7 @@ public static AClass Create() } [Trait("Category", "Unit")] - public class GivenRuleSas041 + public class GivenRule021 { [Fact] public async Task WhenCreateReturnsVoid_ThenAlerts() @@ -1629,7 +1629,7 @@ public static void Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas041, input, 21, + DomainDrivenDesignAnalyzer.Rule021, input, 21, 24, "Create", "ANamespace.AClass or Common.Result"); } @@ -1663,7 +1663,7 @@ public static string Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas041, input, 21, + DomainDrivenDesignAnalyzer.Rule021, input, 21, 26, "Create", "ANamespace.AClass or Common.Result"); } @@ -1733,7 +1733,7 @@ public static Result Create() } [Trait("Category", "Unit")] - public class GivenRuleSas042 + public class GivenRule022 { [Fact] public async Task WhenNonPrivateConstructor_ThenAlerts() @@ -1765,7 +1765,7 @@ public static AClass Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas042, input, 12, + DomainDrivenDesignAnalyzer.Rule022, input, 12, 12, "AClass"); } @@ -1803,7 +1803,7 @@ public static AClass Create() } [Trait("Category", "Unit")] - public class GivenRuleSas043 + public class GivenRule023 { [Fact] public async Task WhenDehydratableAndMissingRehydrateMethod_ThenAlerts() @@ -1850,7 +1850,7 @@ public override HydrationProperties Dehydrate() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas043, input, 16, + DomainDrivenDesignAnalyzer.Rule023, input, 16, 21, "AClass"); } @@ -1908,7 +1908,7 @@ public static EntityFactory Rehydrate() } [Trait("Category", "Unit")] - public class GivenRuleSas044 + public class GivenRule024 { [Fact] public async Task WhenDehydratableAndMissingDehydrateMethod_ThenAlerts() @@ -1955,7 +1955,7 @@ public static EntityFactory Rehydrate() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas044, input, 16, + DomainDrivenDesignAnalyzer.Rule024, input, 16, 21, "AClass"); } @@ -2012,7 +2012,7 @@ public static EntityFactory Rehydrate() } [Trait("Category", "Unit")] - public class GivenRuleSas045 + public class GivenRule025 { [Fact] public async Task WhenDehydratableAndMissingEntityNameAttribute_ThenAlerts() @@ -2062,7 +2062,7 @@ public static EntityFactory Rehydrate() } }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas045, input, 15, + DomainDrivenDesignAnalyzer.Rule025, input, 15, 21, "AClass"); } @@ -2120,7 +2120,7 @@ public static EntityFactory Rehydrate() } [Trait("Category", "Unit")] - public class GivenRuleSas046 + public class GivenRule026 { [Fact] public async Task WhenPropertyHasPublicSetter_ThenAlerts() @@ -2155,7 +2155,7 @@ public static AClass Create(IRecorder recorder, IIdentifierFactory idFactory, Ro }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas046, input, 27, + DomainDrivenDesignAnalyzer.Rule026, input, 27, 19, "AProperty"); } @@ -2266,7 +2266,7 @@ public static AClass Create(IRecorder recorder, IIdentifierFactory idFactory, Ro } [Trait("Category", "Unit")] - public class GivenRuleSas047 + public class GivenRule027 { [Fact] public async Task WhenClassIsNotSealed_ThenAlerts() @@ -2301,7 +2301,7 @@ public static AClass Create(IRecorder recorder, IIdentifierFactory idFactory, Ro }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas047, input, 11, + DomainDrivenDesignAnalyzer.Rule027, input, 11, 14, "AClass"); } @@ -2346,7 +2346,7 @@ public static AClass Create(IRecorder recorder, IIdentifierFactory idFactory, Ro public class GivenAValueObject { [Trait("Category", "Unit")] - public class GivenRuleSas050 + public class GivenRule030 { [Fact] public async Task WhenHasNoCreateMethod_ThenAlerts() @@ -2382,7 +2382,7 @@ public static ValueObjectFactory Rehydrate() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas050, input, 11, + DomainDrivenDesignAnalyzer.Rule030, input, 11, 21, "AClass"); } @@ -2425,7 +2425,7 @@ public static ValueObjectFactory Rehydrate() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas050, input, 11, + DomainDrivenDesignAnalyzer.Rule030, input, 11, 21, "AClass"); } @@ -2473,7 +2473,7 @@ public static ValueObjectFactory Rehydrate() } [Trait("Category", "Unit")] - public class GivenRuleSas051 + public class GivenRule031 { [Fact] public async Task WhenCreateReturnsVoid_ThenAlerts() @@ -2514,7 +2514,7 @@ public static ValueObjectFactory Rehydrate() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas051, input, 19, + DomainDrivenDesignAnalyzer.Rule031, input, 19, 24, "Create", "ANamespace.AClass or Common.Result"); } @@ -2558,7 +2558,7 @@ public static ValueObjectFactory Rehydrate() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas051, input, 19, + DomainDrivenDesignAnalyzer.Rule031, input, 19, 26, "Create", "ANamespace.AClass or Common.Result"); } @@ -2648,7 +2648,7 @@ public static ValueObjectFactory Rehydrate() } [Trait("Category", "Unit")] - public class GivenRuleSas052 + public class GivenRule032 { [Fact] public async Task WhenNonPrivateConstructor_ThenAlerts() @@ -2690,7 +2690,7 @@ public static ValueObjectFactory Rehydrate() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas052, input, 14, + DomainDrivenDesignAnalyzer.Rule032, input, 14, 12, "AClass"); } @@ -2738,7 +2738,7 @@ public static ValueObjectFactory Rehydrate() } [Trait("Category", "Unit")] - public class GivenRuleSas053 + public class GivenRule033 { [Fact] public async Task WhenMissingRehydrateMethod_ThenAlerts() @@ -2775,7 +2775,7 @@ public static AClass Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas053, input, 12, + DomainDrivenDesignAnalyzer.Rule033, input, 12, 21, "AClass"); } @@ -2824,7 +2824,7 @@ public static ValueObjectFactory Rehydrate() } [Trait("Category", "Unit")] - public class GivenRuleSas054 + public class GivenRule034 { [Fact] public async Task WhenPropertyHasPublicSetter_ThenAlerts() @@ -2867,7 +2867,7 @@ public static ValueObjectFactory Rehydrate() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas054, input, 35, + DomainDrivenDesignAnalyzer.Rule034, input, 35, 19, "AProperty"); } @@ -3001,7 +3001,7 @@ public static ValueObjectFactory Rehydrate() } [Trait("Category", "Unit")] - public class GivenRuleSas055 + public class GivenRule035 { [Fact] public async Task WhenMethodReturnsVoid_ThenAlerts() @@ -3048,7 +3048,7 @@ public void AMethod() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas055, input, 37, + DomainDrivenDesignAnalyzer.Rule035, input, 37, 17, "AMethod", "ANamespace.AClass or Common.Result", nameof(SkipImmutabilityCheckAttribute)); @@ -3100,7 +3100,7 @@ public string AMethod() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas055, input, 37, + DomainDrivenDesignAnalyzer.Rule035, input, 37, 19, "AMethod", "ANamespace.AClass or Common.Result", nameof(SkipImmutabilityCheckAttribute)); } @@ -3302,7 +3302,7 @@ public Result AMethod() } [Trait("Category", "Unit")] - public class GivenRuleSas056 + public class GivenRule036 { [Fact] public async Task WhenIsNotSealed_ThenAlerts() @@ -3350,7 +3350,7 @@ public Result AMethod() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas056, input, 13, + DomainDrivenDesignAnalyzer.Rule036, input, 13, 14, "AClass"); } @@ -3438,7 +3438,7 @@ public static AClassed Create() } [Trait("Category", "Unit")] - public class GivenRuleSas060 + public class GivenRule040 { [Fact] public async Task WhenIsNotPublic_ThenAlerts() @@ -3464,12 +3464,12 @@ public static AClassed Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas060, input, 5, 23, "AClassed"); + DomainDrivenDesignAnalyzer.Rule040, input, 5, 23, "AClassed"); } } [Trait("Category", "Unit")] - public class GivenRuleSas061 + public class GivenRule041 { [Fact] public async Task WhenIsNotSealed_ThenAlerts() @@ -3495,12 +3495,12 @@ public static AClassed Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas061, input, 5, 14, "AClassed"); + DomainDrivenDesignAnalyzer.Rule041, input, 5, 14, "AClassed"); } } [Trait("Category", "Unit")] - public class GivenRuleSas062 + public class GivenRule042 { [Fact] public async Task WhenHasCtorAndNotParameterless_ThenAlerts() @@ -3532,7 +3532,7 @@ public static AClassed Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas062, input, 5, 21, "AClassed"); + DomainDrivenDesignAnalyzer.Rule042, input, 5, 21, "AClassed"); } [Fact] @@ -3565,7 +3565,7 @@ public static AClassed Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas062, input, 5, 21, "AClassed"); + DomainDrivenDesignAnalyzer.Rule042, input, 5, 21, "AClassed"); } [Fact] @@ -3602,7 +3602,7 @@ public static AClassed Create() } [Trait("Category", "Unit")] - public class GivenRuleSas063 + public class GivenRule043 { [Fact] public async Task WhenNotNamedInThePastTense_ThenAlerts() @@ -3628,7 +3628,7 @@ public static AClass Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas063, input, 5, 21, "AClass"); + DomainDrivenDesignAnalyzer.Rule043, input, 5, 21, "AClass"); } [Fact] @@ -3659,7 +3659,7 @@ public static AClassed Create() } [Trait("Category", "Unit")] - public class GivenRuleSas064 + public class GivenRule044 { [Fact] public async Task WhenMissingCreateFactory_ThenAlerts() @@ -3676,12 +3676,12 @@ public sealed class AClassed : IDomainEvent }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas064, input, 5, 21, "AClassed"); + DomainDrivenDesignAnalyzer.Rule044, input, 5, 21, "AClassed"); } } [Trait("Category", "Unit")] - public class GivenRuleSas065 + public class GivenRule045 { [Fact] public async Task WhenCreateFactoryReturnsWrongType_ThenAlerts() @@ -3703,12 +3703,12 @@ public static string Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas065, input, 7, 26, "Create", "ANamespace.AClassed"); + DomainDrivenDesignAnalyzer.Rule045, input, 7, 26, "Create", "ANamespace.AClassed"); } } [Trait("Category", "Unit")] - public class GivenRuleSas066 + public class GivenRule046 { [Fact] public async Task WhenAnyPropertyHasNoSetter_ThenAlerts() @@ -3736,12 +3736,12 @@ public static AClassed Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas066, input, 20, 20, "AProperty", null); + DomainDrivenDesignAnalyzer.Rule046, input, 20, 20, "AProperty", null); } } [Trait("Category", "Unit")] - public class GivenRuleSas067 + public class GivenRule047 { [Fact] public async Task WhenAnyPropertyReferenceTypeIsNotRequiredAndNotInitializedAndNotNullable_ThenAlerts() @@ -3769,7 +3769,7 @@ public static AClassed Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas067, input, 20, 19, "AProperty"); + DomainDrivenDesignAnalyzer.Rule047, input, 20, 19, "AProperty"); } [Fact] @@ -4026,7 +4026,7 @@ public static AClassed Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas067, input, 20, 21, "AProperty"); + DomainDrivenDesignAnalyzer.Rule047, input, 20, 21, "AProperty"); } [Fact] @@ -4259,7 +4259,7 @@ public static AClassed Create() } [Trait("Category", "Unit")] - public class GivenRuleSas068 + public class GivenRule048 { [Fact] public async Task WhenAnyPropertyReferenceTypeIsOptional_ThenAlerts() @@ -4289,8 +4289,8 @@ public static AClassed Create() }"; await Verify.DiagnosticExists(input, - (DomainDrivenDesignAnalyzer.Sas068, 22, 38, "AProperty", null), - (DomainDrivenDesignAnalyzer.Sas069, 22, 38, "AProperty", [GivenRuleSas069.AllTypes])); + (DomainDrivenDesignAnalyzer.Rule048, 22, 38, "AProperty", null), + (DomainDrivenDesignAnalyzer.Rule049, 22, 38, "AProperty", [GivenRule049.AllTypes])); } [Fact] @@ -4324,7 +4324,7 @@ public static AClassed Create() } [Trait("Category", "Unit")] - public class GivenRuleSas069 + public class GivenRule049 { public const string AllTypes = "bool or string or ulong or int or long or double or decimal or System.DateTime or byte"; @@ -4356,7 +4356,7 @@ public static AClassed Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas069, input, 21, 26, "AProperty", AllTypes); + DomainDrivenDesignAnalyzer.Rule049, input, 21, 26, "AProperty", AllTypes); } [Fact] @@ -4387,7 +4387,7 @@ public static AClassed Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas069, input, 22, 32, "AProperty", AllTypes); + DomainDrivenDesignAnalyzer.Rule049, input, 22, 32, "AProperty", AllTypes); } [Fact] @@ -4418,7 +4418,7 @@ public static AClassed Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas069, input, 22, 46, "AProperty", AllTypes); + DomainDrivenDesignAnalyzer.Rule049, input, 22, 46, "AProperty", AllTypes); } [Fact] @@ -4449,7 +4449,7 @@ public static AClassed Create() }"; await Verify.DiagnosticExists( - DomainDrivenDesignAnalyzer.Sas069, input, 22, 46, "AProperty", AllTypes); + DomainDrivenDesignAnalyzer.Rule049, input, 22, 46, "AProperty", AllTypes); } [Fact] diff --git a/src/Tools.Analyzers.NonPlatform.UnitTests/DomainDrivenDesignCodeFixSpec.cs b/src/Tools.Analyzers.NonPlatform.UnitTests/DomainDrivenDesignCodeFixSpec.cs index c3254f71..4ca58780 100644 --- a/src/Tools.Analyzers.NonPlatform.UnitTests/DomainDrivenDesignCodeFixSpec.cs +++ b/src/Tools.Analyzers.NonPlatform.UnitTests/DomainDrivenDesignCodeFixSpec.cs @@ -15,7 +15,7 @@ public class DomainDrivenDesignCodeFixSpec public class GivenARootAggregate { [Trait("Category", "Unit")] - public class GivenRuleSas030 + public class GivenRuleRule010 { [Fact] public async Task WhenFixingMissingCreateMethod_ThenAddsMethod() @@ -130,13 +130,13 @@ public static Created Create(Identifier id, Identifier organizationId) }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas030, + DomainDrivenDesignAnalyzer.Rule010, problem, fix, 14, 21, "AClass"); } } [Trait("Category", "Unit")] - public class GivenRuleSas034 + public class GivenRuleRule014 { [Fact] public async Task WhenFixingMissingRehydrateMethodAndDehydratable_ThenAddsMethod() @@ -262,7 +262,7 @@ public static EventOccurred Create() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas034, + DomainDrivenDesignAnalyzer.Rule014, problem, fix, 16, 21, "AClass"); } @@ -370,13 +370,13 @@ public static EventOccurred Create() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas034, + DomainDrivenDesignAnalyzer.Rule014, problem, fix, 11, 21, "AClass"); } } [Trait("Category", "Unit")] - public class GivenRuleSas035 + public class GivenRuleRule015 { [Fact] public async Task WhenFixingMissingDehydrateMethodAndDehydratable_ThenAddsMethod() @@ -503,13 +503,13 @@ public static EventOccurred Create() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas035, + DomainDrivenDesignAnalyzer.Rule015, problem, fix, 16, 21, "AClass"); } } [Trait("Category", "Unit")] - public class GivenRuleSas036 + public class GivenRuleRule016 { [Fact] public async Task WhenFixingMissingEntityNameAttributeAndDehydratable_ThenAddsAttribute() @@ -642,13 +642,13 @@ public static EventOccurred Create() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas036, + DomainDrivenDesignAnalyzer.Rule016, problem, fix, 15, 21, "AClass"); } } [Trait("Category", "Unit")] - public class GivenRuleSas038 + public class GivenRuleRule018 { [Fact] public async Task WhenFixingNotSealed_ThenAddsSealed() @@ -783,7 +783,7 @@ public static EventOccurred Create() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas038, + DomainDrivenDesignAnalyzer.Rule018, problem, fix, 17, 14, "AClass"); } } @@ -793,7 +793,7 @@ await Verify.CodeFixed( public class GivenAnEntity { [Trait("Category", "Unit")] - public class GivenRuleSas040 + public class GivenRuleRule020 { [Fact] public async Task WhenFixingMissingCreateMethod_ThenAddsMethod() @@ -870,13 +870,13 @@ public override HydrationProperties Dehydrate() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas040, + DomainDrivenDesignAnalyzer.Rule020, problem, fix, 14, 21, "AClass"); } } [Trait("Category", "Unit")] - public class GivenRuleSas043 + public class GivenRuleRule023 { [Fact] public async Task WhenFixingMissingRehydrateMethodAndDehydratable_ThenAddsMethod() @@ -968,13 +968,13 @@ public static Domain.Interfaces.EntityFactory Rehydrate() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas043, + DomainDrivenDesignAnalyzer.Rule023, problem, fix, 16, 21, "AClass"); } } [Trait("Category", "Unit")] - public class GivenRuleSas045 + public class GivenRuleRule025 { [Fact] public async Task WhenFixingMissingEntityNameAttributeAndDehydratable_ThenAddsAttribute() @@ -1071,13 +1071,13 @@ public static EntityFactory Rehydrate() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas045, + DomainDrivenDesignAnalyzer.Rule025, problem, fix, 15, 21, "AClass"); } } [Trait("Category", "Unit")] - public class GivenRuleSas047 + public class GivenRuleRule027 { [Fact] public async Task WhenFixingNotSealed_ThenAddsSealed() @@ -1176,7 +1176,7 @@ public static EntityFactory Rehydrate() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas047, + DomainDrivenDesignAnalyzer.Rule027, problem, fix, 17, 14, "AClass"); } } @@ -1186,7 +1186,7 @@ await Verify.CodeFixed( public class GivenAValueObject { [Trait("Category", "Unit")] - public class GivenRuleSas050 + public class GivenRuleRule030 { [Fact] public async Task WhenFixingMissingCreateMethodAndSingleValueObject_ThenAddsMethod() @@ -1264,7 +1264,7 @@ public static ValueObjectFactory Rehydrate() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas050, + DomainDrivenDesignAnalyzer.Rule030, problem, fix, 14, 21, "AClass"); } @@ -1348,13 +1348,13 @@ public static ValueObjectFactory Rehydrate() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas050, + DomainDrivenDesignAnalyzer.Rule030, problem, fix, 14, 21, "AClass"); } } [Trait("Category", "Unit")] - public class GivenRuleSas053 + public class GivenRuleRule033 { [Fact] public async Task WhenFixingMissingRehydrateMethodAndSingleValueObject_ThenAddsMethod() @@ -1420,7 +1420,7 @@ public static Domain.Interfaces.ValueObjectFactory Rehydrate() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas053, + DomainDrivenDesignAnalyzer.Rule033, problem, fix, 12, 21, "AClass"); } @@ -1498,13 +1498,13 @@ public static Domain.Interfaces.ValueObjectFactory Rehydrate() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas053, + DomainDrivenDesignAnalyzer.Rule033, problem, fix, 12, 21, "AClass"); } } [Trait("Category", "Unit")] - public class GivenRuleSas055 + public class GivenRuleRule035 { [Fact(Skip = "see: https://github.com/dotnet/roslyn/issues/72535")] public async Task WhenFixingWrongReturnTypeWithCorrectReturnType_ThenChangesReturnType() @@ -1598,7 +1598,7 @@ public Result AMethod() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas055, + DomainDrivenDesignAnalyzer.Rule035, Resources.CodeFix_Title_AddSkipImmutabilityCheckAttributeToValueObjectMethod, problem, fix, 40, 17, "AMethod", "ANamespace.AClass or Common.Result", @@ -1697,7 +1697,7 @@ public void AMethod() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas055, + DomainDrivenDesignAnalyzer.Rule035, Resources.CodeFix_Title_ChangeValueObjectMethodReturnType, problem, fix, 40, 17, "AMethod", "ANamespace.AClass or Common.Result", @@ -1706,7 +1706,7 @@ await Verify.CodeFixed( } [Trait("Category", "Unit")] - public class GivenRuleSas056 + public class GivenRuleRule036 { [Fact] public async Task WhenFixingNotSealed_ThenAddsSealed() @@ -1791,7 +1791,7 @@ public static Domain.Interfaces.ValueObjectFactory Rehydrate() }"; await Verify.CodeFixed( - DomainDrivenDesignAnalyzer.Sas056, problem, fix, 12, 14, "AClass"); + DomainDrivenDesignAnalyzer.Rule036, problem, fix, 12, 14, "AClass"); } } } diff --git a/src/Tools.Analyzers.NonPlatform.UnitTests/WebApiClassAnalyzerSpec.cs b/src/Tools.Analyzers.NonPlatform.UnitTests/WebApiClassAnalyzerSpec.cs deleted file mode 100644 index c037ef7e..00000000 --- a/src/Tools.Analyzers.NonPlatform.UnitTests/WebApiClassAnalyzerSpec.cs +++ /dev/null @@ -1,1695 +0,0 @@ -extern alias NonPlatformAnalyzers; -using Xunit; -using TypeExtensions = NonPlatformAnalyzers::Tools.Analyzers.NonPlatform.TypeExtensions; -using WebApiClassAnalyzer = NonPlatformAnalyzers::Tools.Analyzers.NonPlatform.WebApiClassAnalyzer; -using ServiceOperation = NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.ServiceOperation; -using IWebResponse = NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebResponse; -using IWebSearchResponse = NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebSearchResponse; -using Route = NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.RouteAttribute; -using Authorize = NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.AuthorizeAttribute; -using SearchResultMetadata = NonPlatformAnalyzers::Application.Interfaces.SearchResultMetadata; -using AccessType = NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.AccessType; -using Roles = NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.Roles; -using UsedImplicitly = NonPlatformAnalyzers::JetBrains.Annotations.UsedImplicitlyAttribute; - -namespace Tools.Analyzers.NonPlatform.UnitTests; - -[UsedImplicitly] -public class WebApiClassAnalyzerSpec -{ - [Trait("Category", "Unit")] - public class GivenAnyRule - { - [Fact] - public async Task WhenInExcludedNamespace_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -namespace Common; -public class AClass : IWebApiService -{ -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenNotWebApiClass_ThenNoAlert() - { - const string input = @" -namespace ANamespace; -public class AClass -{ -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasNoMethods_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -namespace ANamespace; -public class AClass : IWebApiService -{ -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasPrivateMethod_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -namespace ANamespace; -public class AClass : IWebApiService -{ - private void AMethod(){} -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasInternalMethod_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -namespace ANamespace; -public class AClass : IWebApiService -{ - internal void AMethod(){} -}"; - - await Verify.NoDiagnosticExists(input); - } - } - - [Trait("Category", "Unit")] - public class GivenRuleSas010 - { - [Fact] - public async Task WhenHasPublicMethodWithVoidReturnType_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -namespace ANamespace; -public class AClass : IWebApiService -{ - public void AMethod(){} -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas010, - input, 6, 17, "AMethod", - TypeExtensions.Stringify( - WebApiClassAnalyzer.AllowableReturnTypes)); - } - - [Fact] - public async Task WhenHasPublicMethodWithTaskReturnType_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -namespace ANamespace; -public class AClass : IWebApiService -{ - public Task AMethod(){ return Task.CompletedTask; } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas010, - input, 7, 17, "AMethod", - TypeExtensions.Stringify( - WebApiClassAnalyzer.AllowableReturnTypes)); - } - - [Fact] - public async Task WhenHasPublicMethodWithWrongTaskReturnType_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -namespace ANamespace; -public class AClass : IWebApiService -{ - public Task AMethod(){ return Task.FromResult(string.Empty); } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas010, - input, 7, 25, "AMethod", - TypeExtensions.Stringify( - WebApiClassAnalyzer.AllowableReturnTypes)); - } - - [Fact] - public async Task WhenHasPublicMethodWithTaskOfApiEmptyResultReturnType_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public Task AMethod(TestGetRouteAttributeRequest request) - { - return Task.FromResult(() => new Result()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasPublicMethodWithTaskOfApiResultReturnType_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public Task> AMethod(TestGetRouteAttributeRequest request) - { - return Task.FromResult>(() => new Result()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasPublicMethodWithTaskOfApiPostResultReturnType_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public Task> AMethod(TestPostRouteAttributeRequest request) - { - return Task.FromResult>(() => new Result, Error>()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasPublicMethodWithTaskOfApiGetResultReturnType_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public Task> AMethod(TestGetRouteAttributeRequest request) - { - return Task.FromResult>(() => new Result()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasPublicMethodWithTaskOfApiSearchResultReturnType_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public Task> AMethod(TestSearchRouteAttributeRequest request) - { - return Task.FromResult>(() => new Result()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasPublicMethodWithTaskOfApiPutPatchResultReturnType_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public Task> AMethod(TestPutPatchRouteAttributeRequest request) - { - return Task.FromResult>(() => new Result()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasPublicMethodWithTaskOfApiDeleteResultReturnType_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public Task AMethod(TestDeleteRouteAttributeRequest request) - { - return Task.FromResult(() => new Result()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasPublicMethodWithWrongNakedReturnType_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -namespace ANamespace; -public class AClass : IWebApiService -{ - public string AMethod(){ return string.Empty; } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas010, - input, 6, - 19, "AMethod", - TypeExtensions.Stringify( - WebApiClassAnalyzer - .AllowableReturnTypes)); - } - - [Fact] - public async Task WhenHasPublicMethodWithNakedApiEmptyResultReturnType_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasPublicMethodWithNakedApiResultReturnType_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiResult AMethod(TestGetRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasPublicMethodWithNakedApiPostResultReturnType_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiPostResult AMethod(TestPostRouteAttributeRequest request) - { - return () => new Result, Error>(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasPublicMethodWithNakedApiGetResultReturnType_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiGetResult AMethod(TestGetRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasPublicMethodWithNakedApiSearchResultReturnType_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiSearchResult AMethod(TestSearchRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasPublicMethodWithNakedApiPutPatchResultReturnType_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiPutPatchResult AMethod(TestPutPatchRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenHasPublicMethodWithNakedApiDeleteResultReturnType_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiDeleteResult AMethod(TestDeleteRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - } - - [Trait("Category", "Unit")] - public class GivenRuleSas011AndSas012 - { - [Fact] - public async Task WhenHasNoParameters_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod() - { - return () => new Result(); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas011, - input, 8, 27, "AMethod"); - } - - [Fact] - public async Task WhenHasTooManyParameters_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request, CancellationToken cancellationToken, string value) - { - return () => new Result(); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas011, - input, 10, 27, "AMethod"); - } - - [Fact] - public async Task WhenFirstParameterIsNotRequestType_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(string value) - { - return () => new Result(); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas011, - input, 8, 27, "AMethod"); - } - - [Fact] - public async Task WhenSecondParameterIsNotCancellationToken_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request, string value) - { - return () => new Result(); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas012, - input, 9, 27, "AMethod"); - } - - [Fact] - public async Task WhenOnlyRequest_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenRequestAndCancellation_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request, CancellationToken cancellationToken) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - } - - [Trait("Category", "Unit")] - public class GivenRuleSas013AndSas017 - { - [Fact] - public async Task WhenHasNoAttributes_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestNoRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.DiagnosticExists(input, - (WebApiClassAnalyzer.Sas013, 9, 27, "AMethod", null), - (WebApiClassAnalyzer.Sas017, 9, 35, "TestNoRouteAttributeRequest", null)); - } - - [Fact] - public async Task WhenMissingAttribute_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - [TestAttribute] - public ApiEmptyResult AMethod(TestNoRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.DiagnosticExists(input, - (WebApiClassAnalyzer.Sas013, 10, 27, "AMethod", null), - (WebApiClassAnalyzer.Sas017, 10, 35, "TestNoRouteAttributeRequest", null)); - } - - [Fact] - public async Task WhenAttribute_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - } - - [Trait("Category", "Unit")] - public class GivenRuleSas014 - { - [Fact] - public async Task WhenOneRoute_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod1(TestGetRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenTwoWithSameRoute_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod1(TestGetRouteAttributeRequest1 request) - { - return () => new Result(); - } - public ApiEmptyResult AMethod2(TestGetRouteAttributeRequest2 request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenThreeWithSameRouteFirstSegment_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod1(TestGetRouteAttributeRequest1 request) - { - return () => new Result(); - } - public ApiEmptyResult AMethod2(TestGetRouteAttributeRequest2 request) - { - return () => new Result(); - } - public ApiEmptyResult AMethod3(TestGetRouteAttributeRequest3 request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenDifferentRouteSegments_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod1(TestGetRouteAttributeRequest1 request) - { - return () => new Result(); - } - public ApiEmptyResult AMethod2(TestGetRouteAttributeRequest2 request) - { - return () => new Result(); - } - public ApiEmptyResult AMethod4(TestGetRouteAttributeRequest4 request) - { - return () => new Result(); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas014, - input, 17, 27, "AMethod4"); - } - } - - [Trait("Category", "Unit")] - public class GivenRuleSas015 - { - [Fact] - public async Task WhenNoDuplicateRequests_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenDuplicateRequests_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod1(TestGetRouteAttributeRequest1 request) - { - return () => new Result(); - } - public ApiEmptyResult AMethod2(TestGetRouteAttributeRequest1 request) - { - return () => new Result(); - } - public ApiEmptyResult AMethod3(TestGetRouteAttributeRequest2 request) - { - return () => new Result(); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas015, - input, - (9, 27, "AMethod1"), - (13, 27, "AMethod2")); - } - } - - [Trait("Category", "Unit")] - public class GivenRuleSas016 - { - [Fact] - public async Task WhenPostAndReturnsApiEmptyResult_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestPostRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenGetAndReturnsApiEmptyResult_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestGetRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenSearchAndReturnsApiEmptyResult_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestSearchRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenPutPatchAndReturnsApiEmptyResult_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestPutPatchRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenDeleteAndReturnsApiEmptyResult_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestDeleteRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenPostAndReturnsApiResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiResult AMethod(TestPostRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 44, "AMethod", ServiceOperation.Post, ExpectedAllowedResultTypes(ServiceOperation.Post)); - } - - [Fact] - public async Task WhenGetAndReturnsApiResult_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiResult AMethod(TestGetRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenSearchAndReturnsApiResult_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiResult AMethod(TestSearchRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenPutPatchAndReturnsApiResult_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiResult AMethod(TestPutPatchRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenDeleteAndReturnsApiResult_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiResult AMethod(TestDeleteRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenPostAndReturnsApiPostResult_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiPostResult AMethod(TestPostRouteAttributeRequest request) - { - return () => new PostResult(new TestResponse(), ""/alocation""); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenGetAndReturnsApiPostResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiPostResult AMethod(TestGetRouteAttributeRequest request) - { - return () => new PostResult(new TestResponse(), ""/alocation""); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 54, "AMethod", ServiceOperation.Get, ExpectedAllowedResultTypes(ServiceOperation.Get)); - } - - [Fact] - public async Task WhenSearchAndReturnsApiPostResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiPostResult AMethod(TestSearchRouteAttributeRequest request) - { - return () => new PostResult(new TestResponse(), ""/alocation""); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 54, "AMethod", ServiceOperation.Search, ExpectedAllowedResultTypes(ServiceOperation.Search)); - } - - [Fact] - public async Task WhenPutPatchAndReturnsApiPostResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiPostResult AMethod(TestPutPatchRouteAttributeRequest request) - { - return () => new PostResult(new TestResponse(), ""/alocation""); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 54, "AMethod", ServiceOperation.PutPatch, - ExpectedAllowedResultTypes(ServiceOperation.PutPatch)); - } - - [Fact] - public async Task WhenDeleteAndReturnsApiPostResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiPostResult AMethod(TestDeleteRouteAttributeRequest request) - { - return () => new PostResult(new TestResponse(), ""/alocation""); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 54, "AMethod", ServiceOperation.Delete, ExpectedAllowedResultTypes(ServiceOperation.Delete)); - } - - [Fact] - public async Task WhenPostAndReturnsApiGetResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiGetResult AMethod(TestPostRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 47, "AMethod", ServiceOperation.Post, ExpectedAllowedResultTypes(ServiceOperation.Post)); - } - - [Fact] - public async Task WhenGetAndReturnsApiGetResult_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiGetResult AMethod(TestGetRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenSearchAndReturnsApiGetResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiGetResult AMethod(TestSearchRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenPutPatchAndReturnsApiGetResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiGetResult AMethod(TestPutPatchRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 47, "AMethod", ServiceOperation.PutPatch, - ExpectedAllowedResultTypes(ServiceOperation.PutPatch)); - } - - [Fact] - public async Task WhenDeleteAndReturnsApiGetResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiGetResult AMethod(TestDeleteRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 47, "AMethod", ServiceOperation.Delete, ExpectedAllowedResultTypes(ServiceOperation.Delete)); - } - - [Fact] - public async Task WhenPostAndReturnsApiSearchResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiSearchResult AMethod(TestPostRouteAttributeRequest request) - { - return () => new Result(new TestSearchResponse()); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 56, "AMethod", ServiceOperation.Post, ExpectedAllowedResultTypes(ServiceOperation.Post)); - } - - [Fact] - public async Task WhenGetAndReturnsApiSearchResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiSearchResult AMethod(TestGetRouteAttributeRequest request) - { - return () => new Result(new TestSearchResponse()); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 56, "AMethod", ServiceOperation.Get, ExpectedAllowedResultTypes(ServiceOperation.Get)); - } - - [Fact] - public async Task WhenSearchAndReturnsApiSearchResult_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiSearchResult AMethod(TestSearchRouteAttributeRequest request) - { - return () => new Result(new TestSearchResponse()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenPutPatchAndReturnsApiSearchResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiSearchResult AMethod(TestPutPatchRouteAttributeRequest request) - { - return () => new Result(new TestSearchResponse()); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 56, "AMethod", ServiceOperation.PutPatch, - ExpectedAllowedResultTypes(ServiceOperation.PutPatch)); - } - - [Fact] - public async Task WhenDeleteAndReturnsApiSearchResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiSearchResult AMethod(TestDeleteRouteAttributeRequest request) - { - return () => new Result(new TestSearchResponse()); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 56, "AMethod", ServiceOperation.Delete, ExpectedAllowedResultTypes(ServiceOperation.Delete)); - } - - [Fact] - public async Task WhenPostAndReturnsApiPutPatchResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiPutPatchResult AMethod(TestPostRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 52, "AMethod", ServiceOperation.Post, ExpectedAllowedResultTypes(ServiceOperation.Post)); - } - - [Fact] - public async Task WhenGetAndReturnsApiPutPatchResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiPutPatchResult AMethod(TestGetRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 52, "AMethod", ServiceOperation.Get, ExpectedAllowedResultTypes(ServiceOperation.Get)); - } - - [Fact] - public async Task WhenSearchAndReturnsApiPutPatchResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiPutPatchResult AMethod(TestSearchRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 52, "AMethod", ServiceOperation.Search, ExpectedAllowedResultTypes(ServiceOperation.Search)); - } - - [Fact] - public async Task WhenPutPatchAndReturnsApiPutPatchResult_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiPutPatchResult AMethod(TestPutPatchRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenDeleteAndReturnsApiPutPatchResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiPutPatchResult AMethod(TestDeleteRouteAttributeRequest request) - { - return () => new Result(new TestResponse()); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 52, "AMethod", ServiceOperation.Delete, ExpectedAllowedResultTypes(ServiceOperation.Delete)); - } - - [Fact] - public async Task WhenPostAndReturnsApiDeleteResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiDeleteResult AMethod(TestPostRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 28, "AMethod", ServiceOperation.Post, ExpectedAllowedResultTypes(ServiceOperation.Post)); - } - - [Fact] - public async Task WhenGetAndReturnsApiDeleteResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiDeleteResult AMethod(TestGetRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 28, "AMethod", ServiceOperation.Get, ExpectedAllowedResultTypes(ServiceOperation.Get)); - } - - [Fact] - public async Task WhenSearchAndReturnsApiDeleteResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiDeleteResult AMethod(TestSearchRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 28, "AMethod", ServiceOperation.Search, ExpectedAllowedResultTypes(ServiceOperation.Search)); - } - - [Fact] - public async Task WhenPutPatchAndReturnsApiDeleteResult_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiDeleteResult AMethod(TestPutPatchRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas016, - input, 9, 28, "AMethod", ServiceOperation.PutPatch, - ExpectedAllowedResultTypes(ServiceOperation.PutPatch)); - } - - [Fact] - public async Task WhenDeleteAndReturnsApiDeleteResult_ThenNotAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiDeleteResult AMethod(TestDeleteRouteAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - private static string ExpectedAllowedResultTypes(ServiceOperation operation) - { - return TypeExtensions.Stringify( - WebApiClassAnalyzer - .AllowableOperationReturnTypes[operation].ToArray()); - } - } - - [Trait("Category", "Unit")] - public class GivenRuleSas018AndSas019 - { - [Fact] - public async Task WhenRouteIsAnonymousAndMissingAuthorizeAttribute_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestAnonymousRouteNoAuthorizeAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenRouteIsNotAnonymousAndAuthorizeAttribute_ThenNoAlert() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestSecureRouteAuthorizeAttributeRequest request) - { - return () => new Result(); - } -}"; - - await Verify.NoDiagnosticExists(input); - } - - [Fact] - public async Task WhenRouteIsAnonymousAndAuthorizeAttribute_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestAnonymousRouteAuthorizeAttributeRequest request) - { - return () => new Result(); - } -}"; - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas018, - input, 9, 35, "TestAnonymousRouteAuthorizeAttributeRequest"); - } - - [Fact] - public async Task WhenRouteIsNotAnonymousAndNoAuthorizeAttribute_ThenAlerts() - { - const string input = @" -using Infrastructure.Web.Api.Interfaces; -using System.Threading.Tasks; -using Common; -using Tools.Analyzers.NonPlatform.UnitTests; -namespace ANamespace; -public class AClass : IWebApiService -{ - public ApiEmptyResult AMethod(TestSecureRouteNoAuthorizeAttributeRequest request) - { - return () => new Result(); - } -}"; - await Verify.DiagnosticExists(WebApiClassAnalyzer.Sas019, - input, 9, 35, "TestSecureRouteNoAuthorizeAttributeRequest"); - } - } -} - -[UsedImplicitly] -public class TestResource; - -[UsedImplicitly] -public class TestResponse : IWebResponse; - -[UsedImplicitly] -public class TestSearchResponse : IWebSearchResponse -{ - public SearchResultMetadata? Metadata { get; set; } -} - -[UsedImplicitly] -public class TestNoRouteAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; - -[Route("/aresource", ServiceOperation.Search)] -[UsedImplicitly] -public class TestSearchRouteAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; - -[Route("/aresource", ServiceOperation.Post)] -[UsedImplicitly] -public class TestPostRouteAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; - -[Route("/aresource", ServiceOperation.Get)] -[UsedImplicitly] -public class TestGetRouteAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; - -[Route("/aresource/1", ServiceOperation.Get)] -[UsedImplicitly] -public class TestGetRouteAttributeRequest1 : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; - -[Route("/aresource/2", ServiceOperation.Get)] -[UsedImplicitly] -public class TestGetRouteAttributeRequest2 : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; - -[Route("/aresource/3", ServiceOperation.Get)] -[UsedImplicitly] -public class TestGetRouteAttributeRequest3 : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; - -[Route("/anotherresource/1", ServiceOperation.Get)] -[UsedImplicitly] -public class TestGetRouteAttributeRequest4 : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; - -[Route("/aresource", ServiceOperation.PutPatch)] -[UsedImplicitly] -public class TestPutPatchRouteAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; - -[Route("/aresource", ServiceOperation.Delete)] -[UsedImplicitly] -public class TestDeleteRouteAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; - -[AttributeUsage(AttributeTargets.Method)] -[UsedImplicitly] -public class TestAttribute : Attribute; - -[Route("/aresource", ServiceOperation.Post)] -[UsedImplicitly] -public class TestAnonymousRouteNoAuthorizeAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; - -[Route("/aresource", ServiceOperation.Post)] -[Authorize(Roles.Platform_Standard)] -[UsedImplicitly] -public class TestAnonymousRouteAuthorizeAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; - -[Route("/aresource", ServiceOperation.Post, AccessType.Token)] -[Authorize(Roles.Platform_Standard)] -[UsedImplicitly] -public class TestSecureRouteAuthorizeAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; - -[Route("/aresource", ServiceOperation.Post, AccessType.Token)] -[UsedImplicitly] -public class TestSecureRouteNoAuthorizeAttributeRequest : NonPlatformAnalyzers::Infrastructure.Web.Api.Interfaces.IWebRequest; \ No newline at end of file diff --git a/src/Tools.Analyzers.NonPlatform/AnalyzerReleases.Shipped.md b/src/Tools.Analyzers.NonPlatform/AnalyzerReleases.Shipped.md index fd188d31..74778e4c 100644 --- a/src/Tools.Analyzers.NonPlatform/AnalyzerReleases.Shipped.md +++ b/src/Tools.Analyzers.NonPlatform/AnalyzerReleases.Shipped.md @@ -2,60 +2,60 @@ ### New Rules - Rule ID | Category | Severity | Notes ----------|---------------------|----------|----------------------------------------------------------------------------------------------------------- - SAS010 | SaaStackWebApi | Warning | Methods that are public, should return a Task or just any T, where T is one of the supported results . - SAS011 | SaaStackWebApi | Warning | These methods must have at least one parameter. - SAS012 | SaaStackWebApi | Warning | The second parameter can only be a CancellationToken. - SAS013 | SaaStackWebApi | Warning | These methods must be decorated with a RouteAttribute. - SAS014 | SaaStackWebApi | Warning | The route (of all these methods in this class) should start with the same path. - SAS015 | SaaStackWebApi | Warning | There should be no methods in this class with the same IWebRequest{TResponse}. - SAS016 | SaaStackWebApi | Warning | This service operation should return an appropriate Result type for the operation. - SAS017 | SaaStackWebApi | Warning | The request type should be declared with a RouteAttribute on it. - SAS018 | SaaStackWebApi | Error | The request type should not be declared with a AuthorizeAttribute on it. - SAS019 | SaaStackWebApi | Warning | The request type should be declared with a AuthorizeAttribute on it. - SAS030 | SaaStackDDD | Error | Aggregate roots must have at least one Create() class factory method. - SAS031 | SaaStackDDD | Error | Create() class factory methods must return correct types. - SAS032 | SaaStackDDD | Error | Aggregate roots must raise a create event in the class factory. - SAS033 | SaaStackDDD | Error | Aggregate roots must only have private constructors. - SAS034 | SaaStackDDD | Error | Aggregate roots must have a Rehydrate method. - SAS035 | SaaStackDDD | Error | Dehydratable aggregate roots must override the Dehydrate method. - SAS036 | SaaStackDDD | Error | Dehydratable aggregate roots must declare the EntityNameAttribute. - SAS037 | SaaStackDDD | Error | Properties must not have public setters. - SAS038 | SaaStackDDD | Error | Aggregate roots should be marked as sealed. - SAS040 | SaaStackDDD | Error | Entities must have at least one Create() class factory method. - SAS041 | SaaStackDDD | Error | Create() class factory methods must return correct types. - SAS042 | SaaStackDDD | Error | Entities must only have private constructors. - SAS043 | SaaStackDDD | Error | Entities must have a Rehydrate method. - SAS044 | SaaStackDDD | Error | Dehydratable entities must override the Dehydrate method. - SAS045 | SaaStackDDD | Error | Dehydratable entities must declare the EntityNameAttribute. - SAS046 | SaaStackDDD | Error | Properties must not have public setters. - SAS047 | SaaStackDDD | Error | Entities should be marked as sealed. - SAS050 | SaaStackDDD | Error | ValueObjects must have at least one Create() class factory method. - SAS051 | SaaStackDDD | Error | Create() class factory methods must return correct types. - SAS052 | SaaStackDDD | Error | ValueObjects must only have private constructors. - SAS053 | SaaStackDDD | Error | ValueObjects must have a Rehydrate method. - SAS054 | SaaStackDDD | Error | Properties must not have public setters. - SAS055 | SaaStackDDD | Error | ValueObjects must only have immutable methods - SAS056 | SaaStackDDD | Warning | ValueObjects should be marked as sealed. - SAS060 | SaaStackDDD | Error | DomainEvents must be public - SAS061 | SaaStackDDD | Warning | DomainEvents must be sealed - SAS062 | SaaStackDDD | Error | DomainEvents must have a parameterless constructor - SAS063 | SaaStackDDD | Error | DomainEvents must be named in the past tense - SAS064 | SaaStackDDD | Error | DomainEvents must have at least one Create() class factory method - SAS065 | SaaStackDDD | Error | Create() class factory methods must return correct types - SAS066 | SaaStackDDD | Error | Properties must have public getters and setters - SAS067 | SaaStackDDD | Error | Properties must be marked required or nullable or initialized - SAS068 | SaaStackDDD | Error | Properties must be nullable not Optional{T} - SAS069 | SaaStackDDD | Error | Properties must be of correct type - SAS070 | SaaStackApplication | Error | Resources must be public - SAS071 | SaaStackApplication | Error | Resources must have a parameterless constructor - SAS072 | SaaStackApplication | Error | Properties must have public getters and setters - SAS073 | SaaStackApplication | Error | Properties must be nullable not Optional{T} - SAS074 | SaaStackApplication | Error | Properties must of correct type - SAS080 | SaaStackApplication | Error | ReadModels must be public - SAS081 | SaaStackApplication | Error | ReadModels must have the EntityNameAttribute - SAS082 | SaaStackApplication | Error | ReadModels must have a parameterless constructor - SAS083 | SaaStackApplication | Error | Properties must have public getters and setters - SAS084 | SaaStackApplication | Warning | Properties must be Optional{T} not nullable - SAS085 | SaaStackApplication | Error | Properties must of correct type \ No newline at end of file + Rule ID | Category | Severity | Notes +------------|---------------------|----------|----------------------------------------------------------------------------------------------------------- + SAASWEB010 | SaaStackWebApi | Warning | Methods that are public, should return a Task or just any T, where T is one of the supported results . + SAASWEB011 | SaaStackWebApi | Warning | These methods must have at least one parameter. + SAASWEB012 | SaaStackWebApi | Warning | The second parameter can only be a CancellationToken. + SAASWEB013 | SaaStackWebApi | Warning | These methods must be decorated with a RouteAttribute. + SAASWEB014 | SaaStackWebApi | Warning | The route (of all these methods in this class) should start with the same path. + SAASWEB015 | SaaStackWebApi | Warning | There should be no methods in this class with the same IWebRequest{TResponse}. + SAASWEB016 | SaaStackWebApi | Warning | This service operation should return an appropriate Result type for the operation. + SAASWEB017 | SaaStackWebApi | Warning | The request type should be declared with a RouteAttribute on it. + SAASWEB018 | SaaStackWebApi | Error | The request type should not be declared with a AuthorizeAttribute on it. + SAASWEB019 | SaaStackWebApi | Warning | The request type should be declared with a AuthorizeAttribute on it. + SAASDDD010 | SaaStackDDD | Error | Aggregate roots must have at least one Create() class factory method. + SAASDDD011 | SaaStackDDD | Error | Create() class factory methods must return correct types. + SAASDDD012 | SaaStackDDD | Error | Aggregate roots must raise a create event in the class factory. + SAASDDD013 | SaaStackDDD | Error | Aggregate roots must only have private constructors. + SAASDDD014 | SaaStackDDD | Error | Aggregate roots must have a Rehydrate method. + SAASDDD015 | SaaStackDDD | Error | Dehydratable aggregate roots must override the Dehydrate method. + SAASDDD016 | SaaStackDDD | Error | Dehydratable aggregate roots must declare the EntityNameAttribute. + SAASDDD017 | SaaStackDDD | Error | Properties must not have public setters. + SAASDDD018 | SaaStackDDD | Error | Aggregate roots should be marked as sealed. + SAASDDD020 | SaaStackDDD | Error | Entities must have at least one Create() class factory method. + SAASDDD021 | SaaStackDDD | Error | Create() class factory methods must return correct types. + SAASDDD022 | SaaStackDDD | Error | Entities must only have private constructors. + SAASDDD023 | SaaStackDDD | Error | Entities must have a Rehydrate method. + SAASDDD024 | SaaStackDDD | Error | Dehydratable entities must override the Dehydrate method. + SAASDDD025 | SaaStackDDD | Error | Dehydratable entities must declare the EntityNameAttribute. + SAASDDD026 | SaaStackDDD | Error | Properties must not have public setters. + SAASDDD027 | SaaStackDDD | Error | Entities should be marked as sealed. + SAASDDD030 | SaaStackDDD | Error | ValueObjects must have at least one Create() class factory method. + SAASDDD031 | SaaStackDDD | Error | Create() class factory methods must return correct types. + SAASDDD032 | SaaStackDDD | Error | ValueObjects must only have private constructors. + SAASDDD033 | SaaStackDDD | Error | ValueObjects must have a Rehydrate method. + SAASDDD034 | SaaStackDDD | Error | Properties must not have public setters. + SAASDDD035 | SaaStackDDD | Error | ValueObjects must only have immutable methods + SAASDDD036 | SaaStackDDD | Warning | ValueObjects should be marked as sealed. + SAASDDD040 | SaaStackDDD | Error | DomainEvents must be public + SAASDDD041 | SaaStackDDD | Warning | DomainEvents must be sealed + SAASDDD042 | SaaStackDDD | Error | DomainEvents must have a parameterless constructor + SAASDDD043 | SaaStackDDD | Error | DomainEvents must be named in the past tense + SAASDDD044 | SaaStackDDD | Error | DomainEvents must have at least one Create() class factory method + SAASDDD045 | SaaStackDDD | Error | Create() class factory methods must return correct types + SAASDDD046 | SaaStackDDD | Error | Properties must have public getters and setters + SAASDDD047 | SaaStackDDD | Error | Properties must be marked required or nullable or initialized + SAASDDD048 | SaaStackDDD | Error | Properties must be nullable not Optional{T} + SAASDDD049 | SaaStackDDD | Error | Properties must be of correct type + SAASAPP010 | SaaStackApplication | Error | Resources must be public + SAASAPP011 | SaaStackApplication | Error | Resources must have a parameterless constructor + SAASAPP012 | SaaStackApplication | Error | Properties must have public getters and setters + SAASAPP013 | SaaStackApplication | Error | Properties must be nullable not Optional{T} + SAASAPP014 | SaaStackApplication | Error | Properties must of correct type + SAASAPP020 | SaaStackApplication | Error | ReadModels must be public + SAASAPP021 | SaaStackApplication | Error | ReadModels must have the EntityNameAttribute + SAASAPP022 | SaaStackApplication | Error | ReadModels must have a parameterless constructor + SAASAPP023 | SaaStackApplication | Error | Properties must have public getters and setters + SAASAPP024 | SaaStackApplication | Warning | Properties must be Optional{T} not nullable + SAASAPP025 | SaaStackApplication | Error | Properties must of correct type \ No newline at end of file diff --git a/src/Tools.Analyzers.NonPlatform/ApiLayerAnalyzer.cs b/src/Tools.Analyzers.NonPlatform/ApiLayerAnalyzer.cs new file mode 100644 index 00000000..1b1e7442 --- /dev/null +++ b/src/Tools.Analyzers.NonPlatform/ApiLayerAnalyzer.cs @@ -0,0 +1,656 @@ +using System.Collections.Immutable; +using System.Text; +using Common.Extensions; +using Infrastructure.Web.Api.Interfaces; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Tools.Analyzers.Common; +using Tools.Analyzers.Common.Extensions; +using Tools.Analyzers.NonPlatform.Extensions; + +// ReSharper disable InvalidXmlDocComment + +namespace Tools.Analyzers.NonPlatform; + +/// +/// An analyzer to the correct implementation of WebAPI classes, and their requests and responses. +/// WebApiServices: +/// SAASWEB10: Warning: Methods that are public, should return a or just any T, where T is +/// either: +/// or or +/// +/// SAASWEB11: Warning: These methods must have at least one parameter, and first parameter must be +/// , where +/// TResponse is same type as in the return value. +/// SAASWEB12: Warning: The second parameter can only be a +/// SAASWEB13: Warning: These methods must be decorated with a +/// SAASWEB14: Warning: The route (of all these methods in this class) should start with the same path +/// SAASWEB15: Warning: There should be no methods in this class with the same +/// SAASWEB16: Warning: This service operation should return an appropriate Result type for the operation +/// SAASWEB17: Warning: The request type should be declared with a on it +/// SAASWEB18: Error: There should not be an if the +/// declares access +/// SAASWEB19: Warning: There should be a if the +/// declares +/// anything other than access +/// Requests: +/// SAASWEB30: Error: Request must be public +/// SAASWEB31: Error: Request must be named with "Request" suffix +/// SAASWEB32: Error: Request must be in namespace "Infrastructure.Web.Api.Operations.Shared" +/// SAASWEB33: Error: Request must have a +/// SAASWEB34: Error: Request must have a parameterless constructor +/// SAASWEB35: Error: Properties must have public getters and setters +/// SAASWEB36: Error: Properties should be nullable not Optional{T} for interoperability +/// Responses: +/// SAASWEB40: Error: Response must be public +/// SAASWEB41: Error: Response must be named with "Response" suffix +/// SAASWEB42: Error: Response must be in namespace "Infrastructure.Web.Api.Operations.Shared" +/// SAASWEB43: Error: Response must have a parameterless constructor +/// SAASWEB44: Error: Properties must have public getters and setters +/// SAASWEB45: Error: Properties should be nullable not Optional{T} for interoperability +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class ApiLayerAnalyzer : DiagnosticAnalyzer +{ + internal static readonly Dictionary> + AllowableOperationReturnTypes = + new() + { + { + Infrastructure.Web.Api.Interfaces.ServiceOperation.Post, + new List { typeof(ApiEmptyResult), typeof(ApiPostResult<,>) } + }, + { + Infrastructure.Web.Api.Interfaces.ServiceOperation.Get, + new List { typeof(ApiEmptyResult), typeof(ApiResult<,>), typeof(ApiGetResult<,>) } + }, + { + Infrastructure.Web.Api.Interfaces.ServiceOperation.Search, + new List + { + typeof(ApiEmptyResult), typeof(ApiResult<,>), typeof(ApiGetResult<,>), + typeof(ApiSearchResult<,>) + } + }, + { + Infrastructure.Web.Api.Interfaces.ServiceOperation.PutPatch, + new List { typeof(ApiEmptyResult), typeof(ApiResult<,>), typeof(ApiPutPatchResult<,>) } + }, + { + Infrastructure.Web.Api.Interfaces.ServiceOperation.Delete, + new List { typeof(ApiEmptyResult), typeof(ApiResult<,>), typeof(ApiDeleteResult) } + } + }; + + internal static readonly Type[] AllowableServiceOperationReturnTypes = + { + typeof(ApiEmptyResult), + typeof(ApiResult<,>), + typeof(ApiPostResult<,>), + typeof(ApiGetResult<,>), + typeof(ApiSearchResult<,>), + typeof(ApiPutPatchResult<,>), + typeof(ApiDeleteResult) + }; + + internal static readonly DiagnosticDescriptor Rule010 = "SAASWEB10".GetDescriptor(DiagnosticSeverity.Warning, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB010Title), nameof(Resources.SAASWEB010Description), + nameof(Resources.SAASWEB010MessageFormat)); + internal static readonly DiagnosticDescriptor Rule011 = "SAASWEB011".GetDescriptor(DiagnosticSeverity.Warning, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB011Title), nameof(Resources.SAASWEB011Description), + nameof(Resources.SAASWEB011MessageFormat)); + internal static readonly DiagnosticDescriptor Rule012 = "SAASWEB012".GetDescriptor(DiagnosticSeverity.Warning, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB012Title), nameof(Resources.SAASWEB012Description), + nameof(Resources.SAASWEB012MessageFormat)); + internal static readonly DiagnosticDescriptor Rule013 = "SAASWEB013".GetDescriptor(DiagnosticSeverity.Warning, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB013Title), nameof(Resources.SAASWEB013Description), + nameof(Resources.SAASWEB013MessageFormat)); + internal static readonly DiagnosticDescriptor Rule014 = "SAASWEB014".GetDescriptor(DiagnosticSeverity.Warning, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB014Title), nameof(Resources.SAASWEB014Description), + nameof(Resources.SAASWEB014MessageFormat)); + internal static readonly DiagnosticDescriptor Rule015 = "SAASWEB015".GetDescriptor(DiagnosticSeverity.Warning, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB015Title), nameof(Resources.SAASWEB015Description), + nameof(Resources.SAASWEB015MessageFormat)); + internal static readonly DiagnosticDescriptor Rule016 = "SAASWEB016".GetDescriptor(DiagnosticSeverity.Warning, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB016Title), nameof(Resources.SAASWEB016Description), + nameof(Resources.SAASWEB016MessageFormat)); + internal static readonly DiagnosticDescriptor Rule017 = "SAASWEB017".GetDescriptor(DiagnosticSeverity.Warning, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB017Title), nameof(Resources.SAASWEB017Description), + nameof(Resources.SAASWEB017MessageFormat)); + internal static readonly DiagnosticDescriptor Rule018 = "SAASWEB018".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB018Title), nameof(Resources.SAASWEB018Description), + nameof(Resources.SAASWEB018MessageFormat)); + internal static readonly DiagnosticDescriptor Rule019 = "SAASWEB019".GetDescriptor(DiagnosticSeverity.Warning, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB019Title), nameof(Resources.SAASWEB019Description), + nameof(Resources.SAASWEB019MessageFormat)); + + internal static readonly DiagnosticDescriptor Rule030 = "SAASWEB030".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.WebApi, nameof(Resources.Diagnostic_Title_ClassMustBePublic), + nameof(Resources.Diagnostic_Description_ClassMustBePublic), + nameof(Resources.Diagnostic_MessageFormat_ClassMustBePublic)); + internal static readonly DiagnosticDescriptor Rule031 = "SAASWEB031".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB031Title), nameof(Resources.SAASWEB031Description), + nameof(Resources.SAASWEB031MessageFormat)); + internal static readonly DiagnosticDescriptor Rule032 = "SAASWEB032".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB032Title), nameof(Resources.SAASWEB032Description), + nameof(Resources.SAASWEB032MessageFormat)); + internal static readonly DiagnosticDescriptor Rule033 = "SAASWEB033".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB033Title), nameof(Resources.SAASWEB033Description), + nameof(Resources.SAASWEB033MessageFormat)); + internal static readonly DiagnosticDescriptor Rule034 = "SAASWEB034".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.WebApi, nameof(Resources.Diagnostic_Title_ClassMustHaveParameterlessConstructor), + nameof(Resources.Diagnostic_Description_ClassMustHaveParameterlessConstructor), + nameof(Resources.Diagnostic_MessageFormat_ClassMustHaveParameterlessConstructor)); + internal static readonly DiagnosticDescriptor Rule035 = "SAASWEB035".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.WebApi, nameof(Resources.Diagnostic_Title_PropertyMustBeGettableAndSettable), + nameof(Resources.Diagnostic_Description_PropertyMustBeGettableAndSettable), + nameof(Resources.Diagnostic_MessageFormat_PropertyMustBeGettableAndSettable)); + internal static readonly DiagnosticDescriptor Rule036 = "SAASWEB036".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.WebApi, nameof(Resources.Diagnostic_Title_PropertyMustBeNullableNotOptional), + nameof(Resources.Diagnostic_Description_PropertyMustBeNullableNotOptional), + nameof(Resources.Diagnostic_MessageFormat_PropertyMustBeNullableNotOptional)); + + internal static readonly DiagnosticDescriptor Rule040 = "SAASWEB040".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.WebApi, nameof(Resources.Diagnostic_Title_ClassMustBePublic), + nameof(Resources.Diagnostic_Description_ClassMustBePublic), + nameof(Resources.Diagnostic_MessageFormat_ClassMustBePublic)); + internal static readonly DiagnosticDescriptor Rule041 = "SAASWEB041".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB041Title), nameof(Resources.SAASWEB041Description), + nameof(Resources.SAASWEB041MessageFormat)); + internal static readonly DiagnosticDescriptor Rule042 = "SAASWEB042".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.WebApi, nameof(Resources.SAASWEB042Title), nameof(Resources.SAASWEB042Description), + nameof(Resources.SAASWEB042MessageFormat)); + internal static readonly DiagnosticDescriptor Rule043 = "SAASWEB043".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.WebApi, nameof(Resources.Diagnostic_Title_ClassMustHaveParameterlessConstructor), + nameof(Resources.Diagnostic_Description_ClassMustHaveParameterlessConstructor), + nameof(Resources.Diagnostic_MessageFormat_ClassMustHaveParameterlessConstructor)); + internal static readonly DiagnosticDescriptor Rule044 = "SAASWEB044".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.WebApi, nameof(Resources.Diagnostic_Title_PropertyMustBeGettableAndSettable), + nameof(Resources.Diagnostic_Description_PropertyMustBeGettableAndSettable), + nameof(Resources.Diagnostic_MessageFormat_PropertyMustBeGettableAndSettable)); + internal static readonly DiagnosticDescriptor Rule045 = "SAASWEB045".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.WebApi, nameof(Resources.Diagnostic_Title_PropertyMustBeNullableNotOptional), + nameof(Resources.Diagnostic_Description_PropertyMustBeNullableNotOptional), + nameof(Resources.Diagnostic_MessageFormat_PropertyMustBeNullableNotOptional)); + + public override ImmutableArray SupportedDiagnostics => + ImmutableArray.Create(Rule010, Rule011, Rule012, Rule013, Rule014, Rule015, Rule016, Rule017, Rule018, Rule019, + Rule030, Rule031, Rule032, Rule033, Rule034, Rule035, Rule036, + Rule040, Rule041, Rule042, Rule043, Rule044, Rule045); + + public override void Initialize(AnalysisContext context) + { + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + context.RegisterSyntaxNodeAction(AnalyzeWebApiClass, SyntaxKind.ClassDeclaration); + context.RegisterSyntaxNodeAction(AnalyzeRequestClass, SyntaxKind.ClassDeclaration); + context.RegisterSyntaxNodeAction(AnalyzeResponseClass, SyntaxKind.ClassDeclaration); + } + + private static void AnalyzeWebApiClass(SyntaxNodeAnalysisContext context) + { + var methodSyntax = context.Node; + if (methodSyntax is not ClassDeclarationSyntax classDeclarationSyntax) + { + return; + } + + if (context.IsExcludedInNamespace(classDeclarationSyntax, AnalyzerConstants.PlatformNamespaces)) + { + return; + } + + if (classDeclarationSyntax.IsNotType(context)) + { + return; + } + + var allMethods = classDeclarationSyntax.Members.Where(member => member is MethodDeclarationSyntax) + .Cast(); + var operations = new Dictionary(); + foreach (var methodDeclarationSyntax in allMethods) + { + if (methodDeclarationSyntax.IsNotPublicInstanceMethod()) + { + continue; + } + + if (ReturnTypeIsNotCorrect(context, methodDeclarationSyntax, out var returnType)) + { + continue; + } + + if (ParametersAreInCorrect(context, methodDeclarationSyntax, out var requestTypeSyntax)) + { + continue; + } + + if (RouteAttributeIsNotPresent(context, methodDeclarationSyntax, requestTypeSyntax!, + out var routeAttribute)) + { + continue; + } + + var requestType = requestTypeSyntax!.GetBaseOfType(context); + operations.Add(methodDeclarationSyntax, new ServiceOperation(requestType!)); + + var operation = + (Infrastructure.Web.Api.Interfaces.ServiceOperation)routeAttribute!.ConstructorArguments[1].Value!; + if (OperationAndReturnsTypeDontMatch(context, methodDeclarationSyntax, operation, returnType!)) + { + continue; + } + + var access = (AccessType)routeAttribute.ConstructorArguments[2].Value!; + if (AuthorizeAttributePresence(context, requestTypeSyntax!, access)) + { + continue; + } + + var routePath = routeAttribute.ConstructorArguments[0] + .Value?.ToString(); + operations[methodDeclarationSyntax] + .SetRouteSegments(routePath); + } + + RoutesHaveSamePrimaryResource(context, operations); + RequestTypesAreNotDuplicated(context, operations); + } + + private static void AnalyzeRequestClass(SyntaxNodeAnalysisContext context) + { + var methodSyntax = context.Node; + if (methodSyntax is not ClassDeclarationSyntax classDeclarationSyntax) + { + return; + } + + if (context.IsExcludedInNamespace(classDeclarationSyntax, AnalyzerConstants.PlatformNamespaces)) + { + return; + } + + if (classDeclarationSyntax.IsNotType(context)) + { + return; + } + + if (!classDeclarationSyntax.IsPublic()) + { + context.ReportDiagnostic(Rule030, classDeclarationSyntax); + } + + if (!classDeclarationSyntax.IsNamedEndingIn(AnalyzerConstants.RequestTypeSuffix)) + { + context.ReportDiagnostic(Rule031, classDeclarationSyntax); + } + + if (!classDeclarationSyntax.IsWithinNamespace(context, AnalyzerConstants.ServiceOperationTypesNamespace)) + { + context.ReportDiagnostic(Rule032, classDeclarationSyntax); + } + + if (!classDeclarationSyntax.HasRouteAttribute(context)) + { + context.ReportDiagnostic(Rule033, classDeclarationSyntax); + } + + if (!classDeclarationSyntax.HasParameterlessConstructor()) + { + context.ReportDiagnostic(Rule034, classDeclarationSyntax); + } + + var allProperties = classDeclarationSyntax.Members.Where(member => member is PropertyDeclarationSyntax) + .Cast() + .ToList(); + if (allProperties.HasAny()) + { + foreach (var property in allProperties) + { + if (!property.HasPublicGetterAndSetter()) + { + context.ReportDiagnostic(Rule035, property); + } + + if (!property.IsNullableType(context) && property.IsOptionalType(context)) + { + context.ReportDiagnostic(Rule036, property); + } + } + } + } + + private static void AnalyzeResponseClass(SyntaxNodeAnalysisContext context) + { + var methodSyntax = context.Node; + if (methodSyntax is not ClassDeclarationSyntax classDeclarationSyntax) + { + return; + } + + if (context.IsExcludedInNamespace(classDeclarationSyntax, AnalyzerConstants.PlatformNamespaces)) + { + return; + } + + if (classDeclarationSyntax.IsNotType(context)) + { + return; + } + + if (!classDeclarationSyntax.IsPublic()) + { + context.ReportDiagnostic(Rule040, classDeclarationSyntax); + } + + if (!classDeclarationSyntax.IsNamedEndingIn(AnalyzerConstants.ResponseTypeSuffix)) + { + context.ReportDiagnostic(Rule041, classDeclarationSyntax); + } + + if (!classDeclarationSyntax.IsWithinNamespace(context, AnalyzerConstants.ServiceOperationTypesNamespace)) + { + context.ReportDiagnostic(Rule042, classDeclarationSyntax); + } + + if (!classDeclarationSyntax.HasParameterlessConstructor()) + { + context.ReportDiagnostic(Rule043, classDeclarationSyntax); + } + + var allProperties = classDeclarationSyntax.Members.Where(member => member is PropertyDeclarationSyntax) + .Cast() + .ToList(); + if (allProperties.HasAny()) + { + foreach (var property in allProperties) + { + if (!property.HasPublicGetterAndSetter()) + { + context.ReportDiagnostic(Rule044, property); + } + + if (!property.IsNullableType(context) && property.IsOptionalType(context)) + { + context.ReportDiagnostic(Rule045, property); + } + } + } + } + + + private static bool OperationAndReturnsTypeDontMatch(SyntaxNodeAnalysisContext context, + MethodDeclarationSyntax methodDeclarationSyntax, Infrastructure.Web.Api.Interfaces.ServiceOperation operation, + ITypeSymbol returnType) + { + var allowedReturnTypes = AllowableOperationReturnTypes[operation]; + + if (MatchesAllowedTypes(context, returnType, allowedReturnTypes.ToArray())) + { + return false; + } + + context.ReportDiagnostic(Rule016, methodDeclarationSyntax, operation, + allowedReturnTypes.ToArray().Stringify()); + + return true; + } + + private static bool ReturnTypeIsNotCorrect(SyntaxNodeAnalysisContext context, + MethodDeclarationSyntax methodDeclarationSyntax, out ITypeSymbol? returnType) + { + returnType = null; + var symbol = context.SemanticModel.GetDeclaredSymbol(methodDeclarationSyntax); + if (symbol is null) + { + return true; + } + + returnType = symbol.ReturnType; + if (returnType.IsVoid(context)) + { + context.ReportDiagnostic(Rule010, methodDeclarationSyntax, + AllowableServiceOperationReturnTypes.Stringify()); + return true; + } + + if (returnType.IsVoidTask(context)) + { + context.ReportDiagnostic(Rule010, methodDeclarationSyntax, + AllowableServiceOperationReturnTypes.Stringify()); + return true; + } + + if (!MatchesAllowedTypes(context, returnType, AllowableServiceOperationReturnTypes)) + { + context.ReportDiagnostic(Rule010, methodDeclarationSyntax, + AllowableServiceOperationReturnTypes.Stringify()); + return true; + } + + return false; + } + + private static bool RouteAttributeIsNotPresent(SyntaxNodeAnalysisContext context, + MethodDeclarationSyntax methodDeclarationSyntax, ParameterSyntax requestTypeSyntax, + out AttributeData? attribute) + { + var requestTypeSymbol = context.SemanticModel.GetSymbolInfo(requestTypeSyntax.Type!).Symbol!; + attribute = requestTypeSymbol.GetAttributeOfType(context); + if (attribute is null) + { + context.ReportDiagnostic(Rule013, methodDeclarationSyntax); + if (requestTypeSyntax.Type is IdentifierNameSyntax nameSyntax) + { + context.ReportDiagnostic(Rule017, nameSyntax); + } + + return true; + } + + return false; + } + + private static bool AuthorizeAttributePresence(SyntaxNodeAnalysisContext context, ParameterSyntax requestTypeSyntax, + AccessType access) + { + var requestTypeSymbol = context.SemanticModel.GetSymbolInfo(requestTypeSyntax.Type!).Symbol!; + var attribute = requestTypeSymbol.GetAttributeOfType(context); + if (access == AccessType.Anonymous) + { + if (attribute is not null) + { + if (requestTypeSyntax.Type is IdentifierNameSyntax nameSyntax) + { + context.ReportDiagnostic(Rule018, nameSyntax); + } + + return true; + } + } + else + { + if (attribute is null) + { + if (requestTypeSyntax.Type is IdentifierNameSyntax nameSyntax) + { + context.ReportDiagnostic(Rule019, nameSyntax); + } + + return true; + } + } + + return false; + } + + private static bool ParametersAreInCorrect(SyntaxNodeAnalysisContext context, + MethodDeclarationSyntax methodDeclarationSyntax, out ParameterSyntax? requestTypeSyntax) + { + requestTypeSyntax = null; + var parameters = methodDeclarationSyntax.ParameterList.Parameters; + if (parameters.Count is < 1 or > 2) + { + context.ReportDiagnostic(Rule011, methodDeclarationSyntax); + return true; + } + + var firstParam = parameters.First(); + var requestType = firstParam.GetBaseOfType(context); + if (requestType is null) + { + context.ReportDiagnostic(Rule011, methodDeclarationSyntax); + return true; + } + + requestTypeSyntax = firstParam; + + if (parameters.Count == 2) + { + var secondParam = parameters[1]; + if (secondParam.IsNotType(context)) + { + context.ReportDiagnostic(Rule012, methodDeclarationSyntax); + return true; + } + } + + return false; + } + + private static void RequestTypesAreNotDuplicated(SyntaxNodeAnalysisContext context, + Dictionary operations) + { + var duplicateRequestTypes = operations.GroupBy(ops => ops.Value.RequestType.ToDisplayString()) + .Where(grp => grp.Count() > 1); + foreach (var duplicateGroup in duplicateRequestTypes) + { + foreach (var entry in duplicateGroup) + { + context.ReportDiagnostic(Rule015, entry.Key); + } + } + } + + private static void RoutesHaveSamePrimaryResource(SyntaxNodeAnalysisContext context, + Dictionary operations) + { + var primaryResource = string.Empty; + foreach (var operation in operations.Where(ops => ops.Value.RouteSegments.Any())) + { + if (primaryResource.HasNoValue()) + { + primaryResource = operation.Value.RouteSegments.First(); + continue; + } + + if (operation.Value.RouteSegments.First() != primaryResource) + { + context.ReportDiagnostic(Rule014, operation.Key); + } + } + } + + /// + /// Determines whether the is the same as one of the + /// + /// , or one of the as a + /// + private static bool MatchesAllowedTypes(SyntaxNodeAnalysisContext context, ITypeSymbol returnType, + Type[] allowableReturnTypes) + { + var nakedType = returnType; + if (IsGenericTask()) + { + //Task + if (returnType is not INamedTypeSymbol namedTypeSymbol) + { + return false; + } + + nakedType = namedTypeSymbol.TypeArguments.First(); + } + + foreach (var allowedType in allowableReturnTypes) + { + var allowedTypeNaked = context.Compilation.GetTypeByMetadataName(allowedType.FullName!)!; + if (nakedType.OriginalDefinition.IsOfType(allowedTypeNaked)) + { + return true; + } + + var taskType = context.Compilation.GetTypeByMetadataName(typeof(Task<>).FullName!)!; + var returnTypeTasked = + taskType.Construct(context.Compilation.GetTypeByMetadataName(allowedType.FullName!)!); + var allowedTypeTasked = + taskType.Construct(context.Compilation.GetTypeByMetadataName(allowedType.FullName!)!); + if (returnTypeTasked.OriginalDefinition.IsOfType(allowedTypeTasked)) + { + return true; + } + } + + return false; + + bool IsGenericTask() + { + var genericTaskSymbol = context.Compilation.GetTypeByMetadataName(typeof(Task<>).FullName!)!; + return returnType.OriginalDefinition.IsOfType(genericTaskSymbol); + } + } + + private class ServiceOperation + { + public ServiceOperation(ITypeSymbol requestType) + { + RequestType = requestType; + } + + public ITypeSymbol RequestType { get; } + + public IEnumerable RouteSegments { get; private set; } = Enumerable.Empty(); + + public void SetRouteSegments(string? routePath) + { + if (routePath.HasValue()) + { + RouteSegments = routePath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + } + } + } +} + +internal static class TypeExtensions +{ + public static string Stringify(this Type[] allowableReturnTypes) + { + var taskOfList = new StringBuilder(); + var nakedList = new StringBuilder(); + + var stringifiedTypes = allowableReturnTypes.Select(Stringify).ToList(); + nakedList.AppendJoin(", ", stringifiedTypes); + taskOfList.AppendJoin(", ", stringifiedTypes.Select(s => $"Task<{s}>")); + + return $"{taskOfList}, or {nakedList}"; + } + + private static string Stringify(this Type type) + { + if (!type.IsGenericType) + { + return type.Name; + } + + var arguments = type.GetGenericArguments(); + var builder = new StringBuilder(); + + builder.Append(type.Name.Substring(0, type.Name.LastIndexOf('`'))); + builder.Append("<"); + builder.AppendJoin(", ", arguments.Select(arg => arg.Name)); + builder.Append(">"); + + return builder.ToString(); + } +} \ No newline at end of file diff --git a/src/Tools.Analyzers.NonPlatform/ApplicationLayerAnalyzer.cs b/src/Tools.Analyzers.NonPlatform/ApplicationLayerAnalyzer.cs index 9628f0be..aeb48992 100644 --- a/src/Tools.Analyzers.NonPlatform/ApplicationLayerAnalyzer.cs +++ b/src/Tools.Analyzers.NonPlatform/ApplicationLayerAnalyzer.cs @@ -16,19 +16,19 @@ namespace Tools.Analyzers.NonPlatform; /// /// An analyzer to the correct implementation of application DTOs and ReadModels: /// Application Resources: -/// SAS070: Error: Resources must be public -/// SAS071: Error: Resources must have a parameterless constructor -/// SAS072: Error: Properties must have public getters and setters -/// SAS073: Error: Properties must be nullable, not Optional{T} for interoperability -/// SAS074: Error: Properties must have primitives, List{T}, Dictionary{string,T}, +/// SAASAPP010: Error: Resources must be public +/// SAASAPP011: Error: Resources must have a parameterless constructor +/// SAASAPP012: Error: Properties must have public getters and setters +/// SAASAPP013: Error: Properties must be nullable, not Optional{T} for interoperability +/// SAASAPP014: Error: Properties must have primitives, List{T}, Dictionary{string,T}, /// or any other Type in the 'Application.Resources.Shared' namespace, or be enums /// ReadModels: -/// SAS080: Error: ReadModels must be public -/// SAS081: Error: ReadModels must have the EntityNameAttribute -/// SAS082: Error: ReadModels must have a parameterless constructor -/// SAS083: Error: Properties must have public getters and setters -/// SAS084: Warning: Properties should be Optional{T} not nullable -/// SAS085: Error: Properties must have return type of primitives, any ValueObject, +/// SAASAPP020: Error: ReadModels must be public +/// SAASAPP021: Error: ReadModels must have the EntityNameAttribute +/// SAASAPP022: Error: ReadModels must have a parameterless constructor +/// SAASAPP023: Error: Properties must have public getters and setters +/// SAASAPP024: Warning: Properties should be Optional{T} not nullable +/// SAASAPP025: Error: Properties must have return type of primitives, any ValueObject, /// Optional{TPrimitive}, Optional{TValueObject}, List{TPrimitive}, Dictionary{string,TPrimitive}, or be enums /// [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -47,54 +47,57 @@ public class ApplicationLayerAnalyzer : DiagnosticAnalyzer SpecialType.System_Byte }; internal static readonly SpecialType[] AllowableResourcePrimitives = AllowableReadModelPrimitives; - internal static readonly DiagnosticDescriptor Sas070 = "SAS070".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule010 = "SAASAPP010".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Application, nameof(Resources.Diagnostic_Title_ClassMustBePublic), nameof(Resources.Diagnostic_Description_ClassMustBePublic), nameof(Resources.Diagnostic_MessageFormat_ClassMustBePublic)); - internal static readonly DiagnosticDescriptor Sas071 = "SAS071".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule011 = "SAASAPP011".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Application, nameof(Resources.Diagnostic_Title_ClassMustHaveParameterlessConstructor), nameof(Resources.Diagnostic_Description_ClassMustHaveParameterlessConstructor), nameof(Resources.Diagnostic_MessageFormat_ClassMustHaveParameterlessConstructor)); - internal static readonly DiagnosticDescriptor Sas072 = "SAS072".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule012 = "SAASAPP012".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Application, nameof(Resources.Diagnostic_Title_PropertyMustBeGettableAndSettable), nameof(Resources.Diagnostic_Description_PropertyMustBeGettableAndSettable), nameof(Resources.Diagnostic_MessageFormat_PropertyMustBeGettableAndSettable)); - internal static readonly DiagnosticDescriptor Sas073 = "SAS073".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule013 = "SAASAPP013".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Application, nameof(Resources.Diagnostic_Title_PropertyMustBeNullableNotOptional), nameof(Resources.Diagnostic_Description_PropertyMustBeNullableNotOptional), nameof(Resources.Diagnostic_MessageFormat_PropertyMustBeNullableNotOptional)); - internal static readonly DiagnosticDescriptor Sas074 = "SAS074".GetDescriptor(DiagnosticSeverity.Error, - AnalyzerConstants.Categories.Application, nameof(Resources.SAS074Title), nameof(Resources.SAS074Description), - nameof(Resources.SAS074MessageFormat)); - internal static readonly DiagnosticDescriptor Sas080 = "SAS080".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule014 = "SAASAPP014".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.Application, nameof(Resources.SAASAPP014Title), + nameof(Resources.SAASAPP014Description), + nameof(Resources.SAASAPP014MessageFormat)); + internal static readonly DiagnosticDescriptor Rule020 = "SAASAPP020".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Application, nameof(Resources.Diagnostic_Title_ClassMustBePublic), nameof(Resources.Diagnostic_Description_ClassMustBePublic), nameof(Resources.Diagnostic_MessageFormat_ClassMustBePublic)); - internal static readonly DiagnosticDescriptor Sas081 = "SAS081".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule021 = "SAASAPP021".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Application, nameof(Resources.Diagnostic_Title_MustDeclareEntityNameAttribute), nameof(Resources.Diagnostic_Description_MustDeclareEntityNameAttribute), nameof(Resources.Diagnostic_MessageFormat_MustDeclareEntityNameAttribute)); - internal static readonly DiagnosticDescriptor Sas082 = "SAS082".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule022 = "SAASAPP022".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Application, nameof(Resources.Diagnostic_Title_ClassMustHaveParameterlessConstructor), nameof(Resources.Diagnostic_Description_ClassMustHaveParameterlessConstructor), nameof(Resources.Diagnostic_MessageFormat_ClassMustHaveParameterlessConstructor)); - internal static readonly DiagnosticDescriptor Sas083 = "SAS083".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule023 = "SAASAPP023".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Application, nameof(Resources.Diagnostic_Title_PropertyMustBeGettableAndSettable), nameof(Resources.Diagnostic_Description_PropertyMustBeGettableAndSettable), nameof(Resources.Diagnostic_MessageFormat_PropertyMustBeGettableAndSettable)); - internal static readonly DiagnosticDescriptor Sas084 = "SAS084".GetDescriptor(DiagnosticSeverity.Warning, - AnalyzerConstants.Categories.Application, nameof(Resources.SAS084Title), nameof(Resources.SAS084Description), - nameof(Resources.SAS084MessageFormat)); - internal static readonly DiagnosticDescriptor Sas085 = "SAS085".GetDescriptor(DiagnosticSeverity.Error, - AnalyzerConstants.Categories.Application, nameof(Resources.SAS085Title), nameof(Resources.SAS085Description), - nameof(Resources.SAS085MessageFormat)); + internal static readonly DiagnosticDescriptor Rule024 = "SAASAPP024".GetDescriptor(DiagnosticSeverity.Warning, + AnalyzerConstants.Categories.Application, nameof(Resources.SAASAPP024Title), + nameof(Resources.SAASAPP024Description), + nameof(Resources.SAASAPP024MessageFormat)); + internal static readonly DiagnosticDescriptor Rule025 = "SAASAPP025".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.Application, nameof(Resources.SAASAPP025Title), + nameof(Resources.SAASAPP025Description), + nameof(Resources.SAASAPP025MessageFormat)); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( - Sas070, Sas071, Sas072, Sas073, Sas074, - Sas080, Sas081, Sas082, Sas083, Sas084, Sas085); + Rule010, Rule011, Rule012, Rule013, Rule014, + Rule020, Rule021, Rule022, Rule023, Rule024, Rule025); public override void Initialize(AnalysisContext context) { @@ -124,20 +127,12 @@ private static void AnalyzeResource(SyntaxNodeAnalysisContext context) if (!classDeclarationSyntax.IsPublic()) { - context.ReportDiagnostic(Sas070, classDeclarationSyntax); + context.ReportDiagnostic(Rule010, classDeclarationSyntax); } - var allConstructors = classDeclarationSyntax.Members.Where(member => member is ConstructorDeclarationSyntax) - .Cast() - .ToList(); - if (allConstructors.HasAny()) + if (!classDeclarationSyntax.HasParameterlessConstructor()) { - var parameterlessConstructors = allConstructors - .Where(constructor => constructor.ParameterList.Parameters.Count == 0 && constructor.IsPublic()); - if (parameterlessConstructors.HasNone()) - { - context.ReportDiagnostic(Sas071, classDeclarationSyntax); - } + context.ReportDiagnostic(Rule011, classDeclarationSyntax); } var allProperties = classDeclarationSyntax.Members.Where(member => member is PropertyDeclarationSyntax) @@ -149,12 +144,12 @@ private static void AnalyzeResource(SyntaxNodeAnalysisContext context) { if (!property.HasPublicGetterAndSetter()) { - context.ReportDiagnostic(Sas072, property); + context.ReportDiagnostic(Rule012, property); } if (!property.IsNullableType(context) && property.IsOptionalType(context)) { - context.ReportDiagnostic(Sas073, property); + context.ReportDiagnostic(Rule013, property); } var allowedReturnTypes = context.GetAllowableApplicationResourcePropertyReturnTypes(); @@ -167,7 +162,7 @@ private static void AnalyzeResource(SyntaxNodeAnalysisContext context) !allowable.ToDisplayString().StartsWith("System.Collections") && !allowable.ToDisplayString().EndsWith("?")) .Select(allowable => allowable.ToDisplayString()).Join(" or "); - context.ReportDiagnostic(Sas074, property, acceptableReturnTypes, + context.ReportDiagnostic(Rule014, property, acceptableReturnTypes, AnalyzerConstants.ResourceTypesNamespace); } } @@ -194,25 +189,17 @@ private static void AnalyzeReadModel(SyntaxNodeAnalysisContext context) if (!classDeclarationSyntax.IsPublic()) { - context.ReportDiagnostic(Sas080, classDeclarationSyntax); + context.ReportDiagnostic(Rule020, classDeclarationSyntax); } if (!classDeclarationSyntax.HasEntityNameAttribute(context)) { - context.ReportDiagnostic(Sas081, classDeclarationSyntax); + context.ReportDiagnostic(Rule021, classDeclarationSyntax); } - var allConstructors = classDeclarationSyntax.Members.Where(member => member is ConstructorDeclarationSyntax) - .Cast() - .ToList(); - if (allConstructors.HasAny()) + if (!classDeclarationSyntax.HasParameterlessConstructor()) { - var parameterlessConstructors = allConstructors - .Where(constructor => constructor.ParameterList.Parameters.Count == 0 && constructor.IsPublic()); - if (parameterlessConstructors.HasNone()) - { - context.ReportDiagnostic(Sas082, classDeclarationSyntax); - } + context.ReportDiagnostic(Rule022, classDeclarationSyntax); } var allProperties = classDeclarationSyntax.Members.Where(member => member is PropertyDeclarationSyntax) @@ -224,18 +211,18 @@ private static void AnalyzeReadModel(SyntaxNodeAnalysisContext context) { if (!property.HasPublicGetterAndSetter()) { - context.ReportDiagnostic(Sas083, property); + context.ReportDiagnostic(Rule023, property); } if (property.IsNullableType(context)) { - context.ReportDiagnostic(Sas084, property); + context.ReportDiagnostic(Rule024, property); } var allowedReturnTypes = context.GetAllowableApplicationReadModelPropertyReturnTypes(); if (context.HasIncorrectReturnType(property, allowedReturnTypes) - && !property.IsEnumType(context) - && !property.IsValueObjectType(context)) + && !property.IsReadModelEnumType(context) + && !property.IsReadModelValueObjectType(context)) { var acceptableReturnTypes = allowedReturnTypes @@ -243,14 +230,14 @@ private static void AnalyzeReadModel(SyntaxNodeAnalysisContext context) !allowable.ToDisplayString().StartsWith("System.Collections") && !allowable.ToDisplayString().StartsWith("Common.Optional")) .Select(allowable => allowable.ToDisplayString()).Join(" or "); - context.ReportDiagnostic(Sas085, property, acceptableReturnTypes); + context.ReportDiagnostic(Rule025, property, acceptableReturnTypes); } } } } } -internal static partial class DomainDrivenDesignExtensions +internal static class ApplicationLayerExtensions { private static INamedTypeSymbol[]? _allowableApplicationReadModelPropertyReturnTypes; private static INamedTypeSymbol[]? _allowableApplicationResourcePropertyReturnTypes; @@ -339,13 +326,7 @@ public static INamedTypeSymbol[] GetAllowableApplicationResourcePropertyReturnTy return _allowableApplicationResourcePropertyReturnTypes; } - public static bool HasEntityNameAttribute(this ClassDeclarationSyntax classDeclarationSyntax, - SyntaxNodeAnalysisContext context) - { - return HasEntityNameAttribute(context.SemanticModel, context.Compilation, classDeclarationSyntax); - } - - public static bool IsOptionalType(this PropertyDeclarationSyntax propertyDeclarationSyntax, + public static bool IsReadModelEnumType(this PropertyDeclarationSyntax propertyDeclarationSyntax, SyntaxNodeAnalysisContext context) { var propertySymbol = context.SemanticModel.GetDeclaredSymbol(propertyDeclarationSyntax); @@ -360,63 +341,24 @@ public static bool IsOptionalType(this PropertyDeclarationSyntax propertyDeclara return false; } - var returnType = propertySymbol.GetMethod!.ReturnType; - if (returnType.IsOptionalType(context)) - { - return true; - } - - return false; - } - - public static bool IsReturnTypeInNamespace(this PropertyDeclarationSyntax propertyDeclarationSyntax, - SyntaxNodeAnalysisContext context, string containedNamespace) - { - var propertySymbol = context.SemanticModel.GetDeclaredSymbol(propertyDeclarationSyntax); - if (propertySymbol is null) - { - return false; - } - - var getter = propertySymbol.GetMethod; - if (getter is null) - { - return false; - } - - var returnType = getter.ReturnType.WithoutNullable(context); + var returnType = getter.ReturnType.WithoutOptional(context); var listType = context.Compilation.GetTypeByMetadataName(typeof(List<>).FullName!)!; if (returnType.OriginalDefinition.IsOfType(listType)) { - returnType = ((INamedTypeSymbol)returnType).TypeArguments[0].WithoutNullable(context); + returnType = ((INamedTypeSymbol)returnType).TypeArguments[0]; } var dictionaryType = context.Compilation.GetTypeByMetadataName(typeof(Dictionary<,>).FullName!)!; if (returnType.OriginalDefinition.IsOfType(dictionaryType)) { - returnType = ((INamedTypeSymbol)returnType).TypeArguments[1].WithoutNullable(context); - } - - if (returnType.ContainingNamespace.ToDisplayString().StartsWith(containedNamespace)) - { - return true; - } - - return false; - } - - private static ITypeSymbol WithoutOptional(this ITypeSymbol symbol, SyntaxNodeAnalysisContext context) - { - if (symbol.IsOptionalType(context)) - { - return ((INamedTypeSymbol)symbol).TypeArguments[0]; + returnType = ((INamedTypeSymbol)returnType).TypeArguments[1]; } - return symbol; + return returnType.IsEnum(); } - public static bool IsValueObjectType(this PropertyDeclarationSyntax propertyDeclarationSyntax, + public static bool IsReadModelValueObjectType(this PropertyDeclarationSyntax propertyDeclarationSyntax, SyntaxNodeAnalysisContext context) { var propertySymbol = context.SemanticModel.GetDeclaredSymbol(propertyDeclarationSyntax); @@ -449,8 +391,8 @@ public static bool IsValueObjectType(this PropertyDeclarationSyntax propertyDecl return returnType.AllInterfaces.Any(@interface => @interface.IsOfType(valueObjectType)); } - public static bool IsEnumType(this PropertyDeclarationSyntax propertyDeclarationSyntax, - SyntaxNodeAnalysisContext context) + public static bool IsReturnTypeInNamespace(this PropertyDeclarationSyntax propertyDeclarationSyntax, + SyntaxNodeAnalysisContext context, string containedNamespace) { var propertySymbol = context.SemanticModel.GetDeclaredSymbol(propertyDeclarationSyntax); if (propertySymbol is null) @@ -464,27 +406,25 @@ public static bool IsEnumType(this PropertyDeclarationSyntax propertyDeclaration return false; } - var returnType = getter.ReturnType.WithoutOptional(context); + var returnType = getter.ReturnType.WithoutNullable(context); var listType = context.Compilation.GetTypeByMetadataName(typeof(List<>).FullName!)!; if (returnType.OriginalDefinition.IsOfType(listType)) { - returnType = ((INamedTypeSymbol)returnType).TypeArguments[0]; + returnType = ((INamedTypeSymbol)returnType).TypeArguments[0].WithoutNullable(context); } var dictionaryType = context.Compilation.GetTypeByMetadataName(typeof(Dictionary<,>).FullName!)!; if (returnType.OriginalDefinition.IsOfType(dictionaryType)) { - returnType = ((INamedTypeSymbol)returnType).TypeArguments[1]; + returnType = ((INamedTypeSymbol)returnType).TypeArguments[1].WithoutNullable(context); } - return returnType.IsEnum(); - } - - private static bool IsOptionalType(this ITypeSymbol symbol, SyntaxNodeAnalysisContext context) - { - var optionalType = context.Compilation.GetTypeByMetadataName(typeof(global::Common.Optional<>).FullName!)!; + if (returnType.ContainingNamespace.ToDisplayString().StartsWith(containedNamespace)) + { + return true; + } - return symbol.OriginalDefinition.IsOfType(optionalType); + return false; } } \ No newline at end of file diff --git a/src/Tools.Analyzers.NonPlatform/DomainDrivenDesignAnalyzer.cs b/src/Tools.Analyzers.NonPlatform/DomainDrivenDesignAnalyzer.cs index 415b77d3..d840a7a5 100644 --- a/src/Tools.Analyzers.NonPlatform/DomainDrivenDesignAnalyzer.cs +++ b/src/Tools.Analyzers.NonPlatform/DomainDrivenDesignAnalyzer.cs @@ -18,44 +18,45 @@ namespace Tools.Analyzers.NonPlatform; /// /// An analyzer to the correct implementation of root aggregates, entities, value objects and domain events: /// Aggregate Roots: -/// SAS030: Error: Aggregate roots must have at least one Create() class factory method -/// SAS031: Error: Create() class factory methods must return correct types -/// SAS032: Error: Aggregate roots must raise a create event in the class factory -/// SAS033: Error: Aggregate roots must only have private constructors -/// SAS034: Error: Aggregate roots must have a method -/// SAS035: Error: Dehydratable aggregate roots must override the method -/// SAS036: Error: Dehydratable aggregate roots must declare the -/// SAS037: Error: Properties must not have public setters -/// SAS038: Error: Aggregate roots must be marked as sealed +/// SAASDDD010: Error: Aggregate roots must have at least one Create() class factory method +/// SAASDDD011: Error: Create() class factory methods must return correct types +/// SAASDDD012: Error: Aggregate roots must raise a create event in the class factory +/// SAASDDD013: Error: Aggregate roots must only have private constructors +/// SAASDDD014: Error: Aggregate roots must have a method +/// SAASDDD015: Error: Dehydratable aggregate roots must override the +/// method +/// SAASDDD016: Error: Dehydratable aggregate roots must declare the +/// SAASDDD017: Error: Properties must not have public setters +/// SAASDDD018: Error: Aggregate roots must be marked as sealed /// Entities: -/// SAS040: Error: Entities must have at least one Create() class factory method -/// SAS041: Error: Create() class factory methods must return correct types -/// SAS042: Error: Entities must only have private constructors -/// SAS043: Error: Dehydratable entities must have a method -/// SAS044: Error: Dehydratable entities must override the method -/// SAS045: Error: Dehydratable entities must declare the -/// SAS046: Error: Properties must not have public setters -/// SAS047: Error: Entities must be marked as sealed +/// SAASDDD020: Error: Entities must have at least one Create() class factory method +/// SAASDDD021: Error: Create() class factory methods must return correct types +/// SAASDDD022: Error: Entities must only have private constructors +/// SAASDDD023: Error: Dehydratable entities must have a method +/// SAASDDD024: Error: Dehydratable entities must override the method +/// SAASDDD025: Error: Dehydratable entities must declare the +/// SAASDDD026: Error: Properties must not have public setters +/// SAASDDD027: Error: Entities must be marked as sealed /// Value Objects: -/// SAS050: Error: ValueObjects must have at least one Create() class factory method -/// SAS051: Error: Create() class factory methods must return correct types -/// SAS052: Error: ValueObjects must only have private constructors -/// SAS053: Error: ValueObjects must have a method -/// SAS054: Error: Properties must not have public setters -/// SAS055: Error: ValueObjects must only have immutable methods (or be overridden by the +/// SAASDDD030: Error: ValueObjects must have at least one Create() class factory method +/// SAASDDD031: Error: Create() class factory methods must return correct types +/// SAASDDD032: Error: ValueObjects must only have private constructors +/// SAASDDD033: Error: ValueObjects must have a method +/// SAASDDD034: Error: Properties must not have public setters +/// SAASDDD035: Error: ValueObjects must only have immutable methods (or be overridden by the /// ) -/// SAS056: Warning: ValueObjects should be marked as sealed +/// SAASDDD036: Warning: ValueObjects should be marked as sealed /// DomainEvents: -/// SAS060: Error: DomainEvents must be public -/// SAS061: Warning: DomainEvents must be sealed -/// SAS062: Error: DomainEvents must have a parameterless constructor -/// SAS063: Information: DomainEvents must be named in the past tense -/// SAS064: Error: DomainEvents must have at least one Create() class factory method -/// SAS065: Error: Create() class factory methods must return correct types -/// SAS066: Error: Properties must have public getters and setters -/// SAS067: Error: Properties must be required or nullable or initialized -/// SAS068: Error: Properties must be nullable, not Optional{T} for interoperability -/// SAS069: Error: Properties must have return type of primitives, List{TPrimitive}, Dictionary{string,TPrimitive}, +/// SAASDDD040: Error: DomainEvents must be public +/// SAASDDD041: Warning: DomainEvents must be sealed +/// SAASDDD042: Error: DomainEvents must have a parameterless constructor +/// SAASDDD043: Information: DomainEvents must be named in the past tense +/// SAASDDD044: Error: DomainEvents must have at least one Create() class factory method +/// SAASDDD045: Error: Create() class factory methods must return correct types +/// SAASDDD046: Error: Properties must have public getters and setters +/// SAASDDD047: Error: Properties must be required or nullable or initialized +/// SAASDDD048: Error: Properties must be nullable, not Optional{T} for interoperability +/// SAASDDD049: Error: Properties must have return type of primitives, List{TPrimitive}, Dictionary{string,TPrimitive}, /// or be enums /// [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -75,140 +76,140 @@ public class DomainDrivenDesignAnalyzer : DiagnosticAnalyzer SpecialType.System_DateTime, SpecialType.System_Byte }; - internal static readonly DiagnosticDescriptor Sas030 = "SAS030".GetDescriptor(DiagnosticSeverity.Error, - AnalyzerConstants.Categories.Ddd, nameof(Resources.SAS030Title), nameof(Resources.SAS030Description), - nameof(Resources.SAS030MessageFormat)); - internal static readonly DiagnosticDescriptor Sas031 = "SAS031".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule010 = "SAASDDD010".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.Ddd, nameof(Resources.SAASDDD010Title), nameof(Resources.SAASDDD010Description), + nameof(Resources.SAASDDD010MessageFormat)); + internal static readonly DiagnosticDescriptor Rule011 = "SAASDDD011".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_ClassFactoryWrongReturnType), nameof(Resources.Diagnostic_Description_ClassFactoryWrongReturnType), nameof(Resources.Diagnostic_MessageFormat_ClassFactoryWrongReturnType)); - internal static readonly DiagnosticDescriptor Sas032 = "SAS032".GetDescriptor(DiagnosticSeverity.Error, - AnalyzerConstants.Categories.Ddd, nameof(Resources.SAS032Title), nameof(Resources.SAS032Description), - nameof(Resources.SAS032MessageFormat)); - internal static readonly DiagnosticDescriptor Sas033 = "SAS033".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule012 = "SAASDDD012".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.Ddd, nameof(Resources.SAASDDD012Title), nameof(Resources.SAASDDD012Description), + nameof(Resources.SAASDDD012MessageFormat)); + internal static readonly DiagnosticDescriptor Rule013 = "SAASDDD013".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_ConstructorMustBePrivate), nameof(Resources.Diagnostic_Description_ConstructorMustBePrivate), nameof(Resources.Diagnostic_MessageFormat_ConstructorMustBePrivate)); - internal static readonly DiagnosticDescriptor Sas034 = "SAS034".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule014 = "SAASDDD014".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_MustImplementRehydrate), nameof(Resources.Diagnostic_Description_MustImplementRehydrate), nameof(Resources.Diagnostic_MessageFormat_MustImplementRehydrate)); - internal static readonly DiagnosticDescriptor Sas035 = "SAS035".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule015 = "SAASDDD015".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_MustImplementDehydrate), nameof(Resources.Diagnostic_Description_MustImplementDehydrate), nameof(Resources.Diagnostic_MessageFormat_MustImplementDehydrate)); - internal static readonly DiagnosticDescriptor Sas036 = "SAS036".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule016 = "SAASDDD016".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_MustDeclareEntityNameAttribute), nameof(Resources.Diagnostic_Description_MustDeclareEntityNameAttribute), nameof(Resources.Diagnostic_MessageFormat_MustDeclareEntityNameAttribute)); - internal static readonly DiagnosticDescriptor Sas037 = "SAS037".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule017 = "SAASDDD017".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_PropertyMustBeSettable), nameof(Resources.Diagnostic_Description_PropertyMustBeSettable), nameof(Resources.Diagnostic_MessageFormat_PropertyMustBeSettable)); - internal static readonly DiagnosticDescriptor Sas038 = "SAS038".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule018 = "SAASDDD018".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_ClassMustBeSealed), nameof(Resources.Diagnostic_Description_ClassMustBeSealed), nameof(Resources.Diagnostic_MessageFormat_ClassMustBeSealed)); - internal static readonly DiagnosticDescriptor Sas040 = "SAS040".GetDescriptor(DiagnosticSeverity.Error, - AnalyzerConstants.Categories.Ddd, nameof(Resources.SAS040Title), nameof(Resources.SAS040Description), - nameof(Resources.SAS040MessageFormat)); - internal static readonly DiagnosticDescriptor Sas041 = "SAS041".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule020 = "SAASDDD020".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.Ddd, nameof(Resources.SAASDDD020Title), nameof(Resources.SAASDDD020Description), + nameof(Resources.SAASDDD020MessageFormat)); + internal static readonly DiagnosticDescriptor Rule021 = "SAASDDD021".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_ClassFactoryWrongReturnType), nameof(Resources.Diagnostic_Description_ClassFactoryWrongReturnType), nameof(Resources.Diagnostic_MessageFormat_ClassFactoryWrongReturnType)); - internal static readonly DiagnosticDescriptor Sas042 = "SAS042".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule022 = "SAASDDD022".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_ConstructorMustBePrivate), nameof(Resources.Diagnostic_Description_ConstructorMustBePrivate), nameof(Resources.Diagnostic_MessageFormat_ConstructorMustBePrivate)); - internal static readonly DiagnosticDescriptor Sas043 = "SAS043".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule023 = "SAASDDD023".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_MustImplementRehydrate), nameof(Resources.Diagnostic_Description_MustImplementRehydrate), nameof(Resources.Diagnostic_MessageFormat_MustImplementRehydrate)); - internal static readonly DiagnosticDescriptor Sas044 = "SAS044".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule024 = "SAASDDD024".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_MustImplementDehydrate), nameof(Resources.Diagnostic_Description_MustImplementDehydrate), nameof(Resources.Diagnostic_MessageFormat_MustImplementDehydrate)); - internal static readonly DiagnosticDescriptor Sas045 = "SAS045".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule025 = "SAASDDD025".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_MustDeclareEntityNameAttribute), nameof(Resources.Diagnostic_Description_MustDeclareEntityNameAttribute), nameof(Resources.Diagnostic_MessageFormat_MustDeclareEntityNameAttribute)); - internal static readonly DiagnosticDescriptor Sas046 = "SAS046".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule026 = "SAASDDD026".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_PropertyMustBeSettable), nameof(Resources.Diagnostic_Description_PropertyMustBeSettable), nameof(Resources.Diagnostic_MessageFormat_PropertyMustBeSettable)); - internal static readonly DiagnosticDescriptor Sas047 = "SAS047".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule027 = "SAASDDD027".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_ClassMustBeSealed), nameof(Resources.Diagnostic_Description_ClassMustBeSealed), nameof(Resources.Diagnostic_MessageFormat_ClassMustBeSealed)); - internal static readonly DiagnosticDescriptor Sas050 = "SAS050".GetDescriptor(DiagnosticSeverity.Error, - AnalyzerConstants.Categories.Ddd, nameof(Resources.SAS050Title), nameof(Resources.SAS050Description), - nameof(Resources.SAS050MessageFormat)); - internal static readonly DiagnosticDescriptor Sas051 = "SAS051".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule030 = "SAASDDD030".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.Ddd, nameof(Resources.SAASDDD030Title), nameof(Resources.SAASDDD030Description), + nameof(Resources.SAASDDD030MessageFormat)); + internal static readonly DiagnosticDescriptor Rule031 = "SAASDDD031".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_ClassFactoryWrongReturnType), nameof(Resources.Diagnostic_Description_ClassFactoryWrongReturnType), nameof(Resources.Diagnostic_MessageFormat_ClassFactoryWrongReturnType)); - internal static readonly DiagnosticDescriptor Sas052 = "SAS052".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule032 = "SAASDDD032".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_ConstructorMustBePrivate), nameof(Resources.Diagnostic_Description_ConstructorMustBePrivate), nameof(Resources.Diagnostic_MessageFormat_ConstructorMustBePrivate)); - internal static readonly DiagnosticDescriptor Sas053 = "SAS053".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule033 = "SAASDDD033".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_MustImplementRehydrate), nameof(Resources.Diagnostic_Description_MustImplementRehydrate), nameof(Resources.Diagnostic_MessageFormat_MustImplementRehydrate)); - internal static readonly DiagnosticDescriptor Sas054 = "SAS054".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule034 = "SAASDDD034".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_PropertyMustBeSettable), nameof(Resources.Diagnostic_Description_PropertyMustBeSettable), nameof(Resources.Diagnostic_MessageFormat_PropertyMustBeSettable)); - internal static readonly DiagnosticDescriptor Sas055 = "SAS055".GetDescriptor(DiagnosticSeverity.Error, - AnalyzerConstants.Categories.Ddd, nameof(Resources.SAS055Title), nameof(Resources.SAS055Description), - nameof(Resources.SAS055MessageFormat)); - internal static readonly DiagnosticDescriptor Sas056 = "SAS056".GetDescriptor(DiagnosticSeverity.Warning, + internal static readonly DiagnosticDescriptor Rule035 = "SAASDDD035".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.Ddd, nameof(Resources.SAASDDD035Title), nameof(Resources.SAASDDD035Description), + nameof(Resources.SAASDDD035MessageFormat)); + internal static readonly DiagnosticDescriptor Rule036 = "SAASDDD036".GetDescriptor(DiagnosticSeverity.Warning, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_ClassMustBeSealed), nameof(Resources.Diagnostic_Description_ClassMustBeSealed), nameof(Resources.Diagnostic_MessageFormat_ClassMustBeSealed)); - internal static readonly DiagnosticDescriptor Sas060 = "SAS060".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule040 = "SAASDDD040".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_ClassMustBePublic), nameof(Resources.Diagnostic_Description_ClassMustBePublic), nameof(Resources.Diagnostic_MessageFormat_ClassMustBePublic)); - internal static readonly DiagnosticDescriptor Sas061 = "SAS061".GetDescriptor(DiagnosticSeverity.Warning, + internal static readonly DiagnosticDescriptor Rule041 = "SAASDDD041".GetDescriptor(DiagnosticSeverity.Warning, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_ClassMustBeSealed), nameof(Resources.Diagnostic_Description_ClassMustBeSealed), nameof(Resources.Diagnostic_MessageFormat_ClassMustBeSealed)); - internal static readonly DiagnosticDescriptor Sas062 = "SAS062".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule042 = "SAASDDD042".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_ClassMustHaveParameterlessConstructor), nameof(Resources.Diagnostic_Description_ClassMustHaveParameterlessConstructor), nameof(Resources.Diagnostic_MessageFormat_ClassMustHaveParameterlessConstructor)); - internal static readonly DiagnosticDescriptor Sas063 = "SAS063".GetDescriptor(DiagnosticSeverity.Info, - AnalyzerConstants.Categories.Ddd, nameof(Resources.SAS063Title), nameof(Resources.SAS063Description), - nameof(Resources.SAS063MessageFormat)); - internal static readonly DiagnosticDescriptor Sas064 = "SAS064".GetDescriptor(DiagnosticSeverity.Error, - AnalyzerConstants.Categories.Ddd, nameof(Resources.SAS064Title), nameof(Resources.SAS064Description), - nameof(Resources.SAS064MessageFormat)); - internal static readonly DiagnosticDescriptor Sas065 = "SAS065".GetDescriptor(DiagnosticSeverity.Error, - AnalyzerConstants.Categories.Ddd, nameof(Resources.SAS065Title), nameof(Resources.SAS065Description), - nameof(Resources.SAS065MessageFormat)); - internal static readonly DiagnosticDescriptor Sas066 = "SAS066".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule043 = "SAASDDD043".GetDescriptor(DiagnosticSeverity.Info, + AnalyzerConstants.Categories.Ddd, nameof(Resources.SAASDDD043Title), nameof(Resources.SAASDDD043Description), + nameof(Resources.SAASDDD043MessageFormat)); + internal static readonly DiagnosticDescriptor Rule044 = "SAASDDD044".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.Ddd, nameof(Resources.SAASDDD044Title), nameof(Resources.SAASDDD044Description), + nameof(Resources.SAASDDD044MessageFormat)); + internal static readonly DiagnosticDescriptor Rule045 = "SAASDDD045".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.Ddd, nameof(Resources.SAASDDD045Title), nameof(Resources.SAASDDD045Description), + nameof(Resources.SAASDDD045MessageFormat)); + internal static readonly DiagnosticDescriptor Rule046 = "SAASDDD046".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_PropertyMustBeGettableAndSettable), nameof(Resources.Diagnostic_Description_PropertyMustBeGettableAndSettable), nameof(Resources.Diagnostic_MessageFormat_PropertyMustBeGettableAndSettable)); - internal static readonly DiagnosticDescriptor Sas067 = "SAS067".GetDescriptor(DiagnosticSeverity.Error, - AnalyzerConstants.Categories.Ddd, nameof(Resources.SAS067Title), nameof(Resources.SAS067Description), - nameof(Resources.SAS067MessageFormat)); - internal static readonly DiagnosticDescriptor Sas068 = "SAS068".GetDescriptor(DiagnosticSeverity.Error, + internal static readonly DiagnosticDescriptor Rule047 = "SAASDDD047".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.Ddd, nameof(Resources.SAASDDD047Title), nameof(Resources.SAASDDD047Description), + nameof(Resources.SAASDDD047MessageFormat)); + internal static readonly DiagnosticDescriptor Rule048 = "SAASDDD048".GetDescriptor(DiagnosticSeverity.Error, AnalyzerConstants.Categories.Ddd, nameof(Resources.Diagnostic_Title_PropertyMustBeNullableNotOptional), nameof(Resources.Diagnostic_Description_PropertyMustBeNullableNotOptional), nameof(Resources.Diagnostic_MessageFormat_PropertyMustBeNullableNotOptional)); - internal static readonly DiagnosticDescriptor Sas069 = "SAS069".GetDescriptor(DiagnosticSeverity.Error, - AnalyzerConstants.Categories.Ddd, nameof(Resources.SAS069Title), nameof(Resources.SAS069Description), - nameof(Resources.SAS069MessageFormat)); + internal static readonly DiagnosticDescriptor Rule049 = "SAASDDD049".GetDescriptor(DiagnosticSeverity.Error, + AnalyzerConstants.Categories.Ddd, nameof(Resources.SAASDDD049Title), nameof(Resources.SAASDDD049Description), + nameof(Resources.SAASDDD049MessageFormat)); private static readonly string[] IgnoredValueObjectMethodNames = { nameof(IDehydratableEntity.Dehydrate) }; public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( - Sas030, Sas031, Sas032, Sas033, Sas034, Sas035, Sas036, Sas037, Sas038, - Sas040, Sas041, Sas042, Sas043, Sas044, Sas045, Sas046, Sas047, - Sas050, Sas051, Sas052, Sas053, Sas054, Sas055, Sas056, - Sas060, Sas061, Sas062, Sas063, Sas064, Sas065, Sas066, Sas067, Sas068, Sas069); + Rule010, Rule011, Rule012, Rule013, Rule014, Rule015, Rule016, Rule017, Rule018, + Rule020, Rule021, Rule022, Rule023, Rule024, Rule025, Rule026, Rule027, + Rule030, Rule031, Rule032, Rule033, Rule034, Rule035, Rule036, + Rule040, Rule041, Rule042, Rule043, Rule044, Rule045, Rule046, Rule047, Rule048, Rule049); public override void Initialize(AnalysisContext context) { @@ -238,6 +239,11 @@ private static void AnalyzeAggregateRoot(SyntaxNodeAnalysisContext context) return; } + if (!classDeclarationSyntax.IsSealed()) + { + context.ReportDiagnostic(Rule018, classDeclarationSyntax); + } + var allMethods = classDeclarationSyntax.Members.Where(member => member is MethodDeclarationSyntax) .Cast() .ToList(); @@ -246,7 +252,7 @@ private static void AnalyzeAggregateRoot(SyntaxNodeAnalysisContext context) .ToList(); if (classFactoryMethods.HasNone()) { - context.ReportDiagnostic(Sas030, classDeclarationSyntax); + context.ReportDiagnostic(Rule010, classDeclarationSyntax); } else { @@ -257,44 +263,35 @@ private static void AnalyzeAggregateRoot(SyntaxNodeAnalysisContext context) { var acceptableReturnTypes = allowedReturnTypes.Select(allowable => allowable.ToDisplayString()).Join(" or "); - context.ReportDiagnostic(Sas031, method, acceptableReturnTypes); + context.ReportDiagnostic(Rule011, method, acceptableReturnTypes); } if (context.IsMissingContent(method, ConstructorMethodCall)) { - context.ReportDiagnostic(Sas032, method, ConstructorMethodCall); + context.ReportDiagnostic(Rule012, method, ConstructorMethodCall); } } } - var allConstructors = classDeclarationSyntax.Members.Where(member => member is ConstructorDeclarationSyntax) - .Cast() - .ToList(); - if (allConstructors.HasAny()) + if (!classDeclarationSyntax.HasOnlyPrivateInstanceConstructors(out var constructor)) { - foreach (var constructor in allConstructors) - { - if (!constructor.IsPrivateInstanceConstructor()) - { - context.ReportDiagnostic(Sas033, constructor); - } - } + context.ReportDiagnostic(Rule013, constructor!); } var dehydratable = context.IsDehydratableAggregateRoot(classDeclarationSyntax); if (!dehydratable.ImplementsRehydrate) { - context.ReportDiagnostic(Sas034, classDeclarationSyntax); + context.ReportDiagnostic(Rule014, classDeclarationSyntax); } if (dehydratable is { IsDehydratable: true, ImplementsDehydrate: false }) { - context.ReportDiagnostic(Sas035, classDeclarationSyntax); + context.ReportDiagnostic(Rule015, classDeclarationSyntax); } if (dehydratable is { IsDehydratable: true, MarkedAsEntityName: false }) { - context.ReportDiagnostic(Sas036, classDeclarationSyntax); + context.ReportDiagnostic(Rule016, classDeclarationSyntax); } var allProperties = classDeclarationSyntax.Members.Where(member => member is PropertyDeclarationSyntax) @@ -306,15 +303,10 @@ private static void AnalyzeAggregateRoot(SyntaxNodeAnalysisContext context) { if (property.HasPublicSetter()) { - context.ReportDiagnostic(Sas037, property); + context.ReportDiagnostic(Rule017, property); } } } - - if (!classDeclarationSyntax.IsSealed()) - { - context.ReportDiagnostic(Sas038, classDeclarationSyntax); - } } private static void AnalyzeEntity(SyntaxNodeAnalysisContext context) @@ -335,6 +327,11 @@ private static void AnalyzeEntity(SyntaxNodeAnalysisContext context) return; } + if (!classDeclarationSyntax.IsSealed()) + { + context.ReportDiagnostic(Rule027, classDeclarationSyntax); + } + var allMethods = classDeclarationSyntax.Members.Where(member => member is MethodDeclarationSyntax) .Cast() .ToList(); @@ -343,7 +340,7 @@ private static void AnalyzeEntity(SyntaxNodeAnalysisContext context) .ToList(); if (classFactoryMethods.HasNone()) { - context.ReportDiagnostic(Sas040, classDeclarationSyntax); + context.ReportDiagnostic(Rule020, classDeclarationSyntax); } else { @@ -354,39 +351,30 @@ private static void AnalyzeEntity(SyntaxNodeAnalysisContext context) { var acceptableReturnTypes = allowedReturnTypes.Select(allowable => allowable.ToDisplayString()).Join(" or "); - context.ReportDiagnostic(Sas041, method, acceptableReturnTypes); + context.ReportDiagnostic(Rule021, method, acceptableReturnTypes); } } } - var allConstructors = classDeclarationSyntax.Members.Where(member => member is ConstructorDeclarationSyntax) - .Cast() - .ToList(); - if (allConstructors.HasAny()) + if (!classDeclarationSyntax.HasOnlyPrivateInstanceConstructors(out var constructor)) { - foreach (var constructor in allConstructors) - { - if (!constructor.IsPrivateInstanceConstructor()) - { - context.ReportDiagnostic(Sas042, constructor); - } - } + context.ReportDiagnostic(Rule022, constructor!); } var dehydratable = context.IsDehydratableEntity(classDeclarationSyntax); if (dehydratable is { IsDehydratable: true, ImplementsRehydrate: false }) { - context.ReportDiagnostic(Sas043, classDeclarationSyntax); + context.ReportDiagnostic(Rule023, classDeclarationSyntax); } if (dehydratable is { IsDehydratable: true, ImplementsDehydrate: false }) { - context.ReportDiagnostic(Sas044, classDeclarationSyntax); + context.ReportDiagnostic(Rule024, classDeclarationSyntax); } if (dehydratable is { IsDehydratable: true, MarkedAsEntityName: false }) { - context.ReportDiagnostic(Sas045, classDeclarationSyntax); + context.ReportDiagnostic(Rule025, classDeclarationSyntax); } var allProperties = classDeclarationSyntax.Members.Where(member => member is PropertyDeclarationSyntax) @@ -398,15 +386,10 @@ private static void AnalyzeEntity(SyntaxNodeAnalysisContext context) { if (property.HasPublicSetter()) { - context.ReportDiagnostic(Sas046, property); + context.ReportDiagnostic(Rule026, property); } } } - - if (!classDeclarationSyntax.IsSealed()) - { - context.ReportDiagnostic(Sas047, classDeclarationSyntax); - } } private static void AnalyzeValueObject(SyntaxNodeAnalysisContext context) @@ -427,6 +410,11 @@ private static void AnalyzeValueObject(SyntaxNodeAnalysisContext context) return; } + if (!classDeclarationSyntax.IsSealed()) + { + context.ReportDiagnostic(Rule036, classDeclarationSyntax); + } + var allMethods = classDeclarationSyntax.Members.Where(member => member is MethodDeclarationSyntax) .Cast() .ToList(); @@ -435,7 +423,7 @@ private static void AnalyzeValueObject(SyntaxNodeAnalysisContext context) .ToList(); if (classFactoryMethods.HasNone()) { - context.ReportDiagnostic(Sas050, classDeclarationSyntax); + context.ReportDiagnostic(Rule030, classDeclarationSyntax); } else { @@ -446,29 +434,20 @@ private static void AnalyzeValueObject(SyntaxNodeAnalysisContext context) { var acceptableReturnTypes = allowedReturnTypes.Select(allowable => allowable.ToDisplayString()).Join(" or "); - context.ReportDiagnostic(Sas051, method, acceptableReturnTypes); + context.ReportDiagnostic(Rule031, method, acceptableReturnTypes); } } } - var allConstructors = classDeclarationSyntax.Members.Where(member => member is ConstructorDeclarationSyntax) - .Cast() - .ToList(); - if (allConstructors.HasAny()) + if (!classDeclarationSyntax.HasOnlyPrivateInstanceConstructors(out var constructor)) { - foreach (var constructor in allConstructors) - { - if (!constructor.IsPrivateInstanceConstructor()) - { - context.ReportDiagnostic(Sas052, constructor); - } - } + context.ReportDiagnostic(Rule032, constructor!); } var dehydratable = context.IsDehydratableValueObject(classDeclarationSyntax); if (!dehydratable.ImplementsRehydrate) { - context.ReportDiagnostic(Sas053, classDeclarationSyntax); + context.ReportDiagnostic(Rule033, classDeclarationSyntax); } var allProperties = classDeclarationSyntax.Members.Where(member => member is PropertyDeclarationSyntax) @@ -480,7 +459,7 @@ private static void AnalyzeValueObject(SyntaxNodeAnalysisContext context) { if (property.HasPublicSetter()) { - context.ReportDiagnostic(Sas054, property); + context.ReportDiagnostic(Rule034, property); } } } @@ -500,14 +479,11 @@ private static void AnalyzeValueObject(SyntaxNodeAnalysisContext context) { var acceptableReturnTypes = allowedReturnTypes.Select(allowable => allowable.ToDisplayString()).Join(" or "); - context.ReportDiagnostic(Sas055, method, acceptableReturnTypes, nameof(SkipImmutabilityCheckAttribute)); + context.ReportDiagnostic(Rule035, method, acceptableReturnTypes, + nameof(SkipImmutabilityCheckAttribute)); } } - if (!classDeclarationSyntax.IsSealed()) - { - context.ReportDiagnostic(Sas056, classDeclarationSyntax); - } } private static void AnalyzeDomainEvent(SyntaxNodeAnalysisContext context) @@ -530,17 +506,17 @@ private static void AnalyzeDomainEvent(SyntaxNodeAnalysisContext context) if (!context.IsNamedInPastTense(classDeclarationSyntax)) { - context.ReportDiagnostic(Sas063, classDeclarationSyntax); + context.ReportDiagnostic(Rule043, classDeclarationSyntax); } if (!classDeclarationSyntax.IsPublic()) { - context.ReportDiagnostic(Sas060, classDeclarationSyntax); + context.ReportDiagnostic(Rule040, classDeclarationSyntax); } if (!classDeclarationSyntax.IsSealed()) { - context.ReportDiagnostic(Sas061, classDeclarationSyntax); + context.ReportDiagnostic(Rule041, classDeclarationSyntax); } var allMethods = classDeclarationSyntax.Members.Where(member => member is MethodDeclarationSyntax) @@ -551,7 +527,7 @@ private static void AnalyzeDomainEvent(SyntaxNodeAnalysisContext context) .ToList(); if (classFactoryMethods.HasNone()) { - context.ReportDiagnostic(Sas064, classDeclarationSyntax); + context.ReportDiagnostic(Rule044, classDeclarationSyntax); } else { @@ -562,22 +538,14 @@ private static void AnalyzeDomainEvent(SyntaxNodeAnalysisContext context) { var acceptableReturnTypes = allowedReturnTypes.Select(allowable => allowable.ToDisplayString()).Join(" or "); - context.ReportDiagnostic(Sas065, method, acceptableReturnTypes); + context.ReportDiagnostic(Rule045, method, acceptableReturnTypes); } } } - var allConstructors = classDeclarationSyntax.Members.Where(member => member is ConstructorDeclarationSyntax) - .Cast() - .ToList(); - if (allConstructors.HasAny()) + if (!classDeclarationSyntax.HasParameterlessConstructor()) { - var parameterlessConstructors = allConstructors - .Where(constructor => constructor.ParameterList.Parameters.Count == 0 && constructor.IsPublic()); - if (parameterlessConstructors.HasNone()) - { - context.ReportDiagnostic(Sas062, classDeclarationSyntax); - } + context.ReportDiagnostic(Rule042, classDeclarationSyntax); } var allProperties = classDeclarationSyntax.Members.Where(member => member is PropertyDeclarationSyntax) @@ -589,20 +557,20 @@ private static void AnalyzeDomainEvent(SyntaxNodeAnalysisContext context) { if (!property.HasPublicGetterAndSetter()) { - context.ReportDiagnostic(Sas066, property); + context.ReportDiagnostic(Rule046, property); } if (!property.IsRequired() && !property.IsInitialized()) { if (!property.IsNullableType(context)) { - context.ReportDiagnostic(Sas067, property); + context.ReportDiagnostic(Rule047, property); } } if (!property.IsNullableType(context) && property.IsOptionalType(context)) { - context.ReportDiagnostic(Sas068, property); + context.ReportDiagnostic(Rule048, property); } var allowedReturnTypes = context.GetAllowableDomainEventPropertyReturnTypes(); @@ -615,14 +583,14 @@ private static void AnalyzeDomainEvent(SyntaxNodeAnalysisContext context) !allowable.ToDisplayString().StartsWith("System.Collections") && !allowable.ToDisplayString().EndsWith("?")) .Select(allowable => allowable.ToDisplayString()).Join(" or "); - context.ReportDiagnostic(Sas069, property, acceptableReturnTypes); + context.ReportDiagnostic(Rule049, property, acceptableReturnTypes); } } } } } -internal static partial class DomainDrivenDesignExtensions +internal static class DomainDrivenDesignExtensions { private static INamedTypeSymbol[]? _allowableDomainEventPropertyReturnTypes; @@ -733,7 +701,7 @@ public static DehydratableStatus IsDehydratableAggregateRoot(this SemanticModel ImplementsAggregateRehydrateMethod(semanticModel, compilation, classDeclarationSyntax, allMethods); var implementsDehydrate = ImplementsDehydrateMethod(semanticModel, compilation, classDeclarationSyntax, allMethods); - var hasAttribute = HasEntityNameAttribute(semanticModel, compilation, classDeclarationSyntax); + var hasAttribute = semanticModel.HasEntityNameAttribute(compilation, classDeclarationSyntax); return new DehydratableStatus(implementsDehydrate, implementsRehydrate, hasAttribute, () => implementsDehydrate || hasAttribute); } @@ -801,7 +769,7 @@ private static DehydratableStatus IsDehydratableEntity(this SemanticModel semant ImplementsEntityRehydrateMethod(semanticModel, compilation, classDeclarationSyntax, allMethods); var implementsDehydrate = ImplementsDehydrateMethod(semanticModel, compilation, classDeclarationSyntax, allMethods); - var hasAttribute = HasEntityNameAttribute(semanticModel, compilation, classDeclarationSyntax); + var hasAttribute = semanticModel.HasEntityNameAttribute(compilation, classDeclarationSyntax); return new DehydratableStatus(implementsDehydrate, implementsRehydrate, hasAttribute, () => implementsRehydrate || hasAttribute || implementsRehydrate); } @@ -874,16 +842,6 @@ private static bool HasIncorrectReturnType(this SemanticModel semanticModel, Com return true; } - /// - /// Whether the class either has a declaration - /// - private static bool HasEntityNameAttribute(this SemanticModel semanticModel, Compilation compilation, - ClassDeclarationSyntax classDeclarationSyntax) - { - var attribute = classDeclarationSyntax.GetAttributeOfType(semanticModel, compilation); - return attribute.Exists(); - } - /// /// Whether the class implements the method /// diff --git a/src/Tools.Analyzers.NonPlatform/DomainDrivenDesignCodeFix.cs b/src/Tools.Analyzers.NonPlatform/DomainDrivenDesignCodeFix.cs index d3280eed..bb34bfba 100644 --- a/src/Tools.Analyzers.NonPlatform/DomainDrivenDesignCodeFix.cs +++ b/src/Tools.Analyzers.NonPlatform/DomainDrivenDesignCodeFix.cs @@ -26,21 +26,21 @@ public class DomainDrivenDesignCodeFix : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create( - DomainDrivenDesignAnalyzer.Sas030.Id, - DomainDrivenDesignAnalyzer.Sas034.Id, - DomainDrivenDesignAnalyzer.Sas035.Id, - DomainDrivenDesignAnalyzer.Sas036.Id, - DomainDrivenDesignAnalyzer.Sas038.Id, - DomainDrivenDesignAnalyzer.Sas040.Id, - DomainDrivenDesignAnalyzer.Sas043.Id, - DomainDrivenDesignAnalyzer.Sas044.Id, - DomainDrivenDesignAnalyzer.Sas045.Id, - DomainDrivenDesignAnalyzer.Sas047.Id, - DomainDrivenDesignAnalyzer.Sas050.Id, - DomainDrivenDesignAnalyzer.Sas053.Id, - DomainDrivenDesignAnalyzer.Sas055.Id, - DomainDrivenDesignAnalyzer.Sas056.Id, - DomainDrivenDesignAnalyzer.Sas061.Id + DomainDrivenDesignAnalyzer.Rule010.Id, + DomainDrivenDesignAnalyzer.Rule014.Id, + DomainDrivenDesignAnalyzer.Rule015.Id, + DomainDrivenDesignAnalyzer.Rule016.Id, + DomainDrivenDesignAnalyzer.Rule018.Id, + DomainDrivenDesignAnalyzer.Rule020.Id, + DomainDrivenDesignAnalyzer.Rule023.Id, + DomainDrivenDesignAnalyzer.Rule024.Id, + DomainDrivenDesignAnalyzer.Rule025.Id, + DomainDrivenDesignAnalyzer.Rule027.Id, + DomainDrivenDesignAnalyzer.Rule030.Id, + DomainDrivenDesignAnalyzer.Rule033.Id, + DomainDrivenDesignAnalyzer.Rule035.Id, + DomainDrivenDesignAnalyzer.Rule036.Id, + DomainDrivenDesignAnalyzer.Rule041.Id ); public override FixAllProvider GetFixAllProvider() @@ -82,7 +82,7 @@ private static void FixClass(CodeFixContext context, ClassDeclarationSyntax clas var diagnostics = context.Diagnostics; var diagnostic = diagnostics.First(); - if (diagnostics.Any(d => d.Id == DomainDrivenDesignAnalyzer.Sas034.Id)) + if (diagnostics.Any(d => d.Id == DomainDrivenDesignAnalyzer.Rule014.Id)) { var title = Resources.CodeFix_Title_AddRehydrateMethod; context.RegisterCodeFix( @@ -92,7 +92,7 @@ private static void FixClass(CodeFixContext context, ClassDeclarationSyntax clas diagnostic); } - if (diagnostics.Any(d => d.Id == DomainDrivenDesignAnalyzer.Sas043.Id)) + if (diagnostics.Any(d => d.Id == DomainDrivenDesignAnalyzer.Rule023.Id)) { var title = Resources.CodeFix_Title_AddRehydrateMethod; context.RegisterCodeFix( @@ -102,7 +102,7 @@ private static void FixClass(CodeFixContext context, ClassDeclarationSyntax clas diagnostic); } - if (diagnostics.Any(d => d.Id == DomainDrivenDesignAnalyzer.Sas053.Id)) + if (diagnostics.Any(d => d.Id == DomainDrivenDesignAnalyzer.Rule033.Id)) { var title = Resources.CodeFix_Title_AddRehydrateMethod; context.RegisterCodeFix( @@ -113,7 +113,7 @@ private static void FixClass(CodeFixContext context, ClassDeclarationSyntax clas } if (diagnostics.Any(d => - d.Id == DomainDrivenDesignAnalyzer.Sas035.Id)) + d.Id == DomainDrivenDesignAnalyzer.Rule015.Id)) { var title = Resources.CodeFix_Title_AddDehydrateMethodToEntity; context.RegisterCodeFix( @@ -124,10 +124,10 @@ private static void FixClass(CodeFixContext context, ClassDeclarationSyntax clas } if (diagnostics.Any(d => - d.Id == DomainDrivenDesignAnalyzer.Sas038.Id - || d.Id == DomainDrivenDesignAnalyzer.Sas047.Id - || d.Id == DomainDrivenDesignAnalyzer.Sas056.Id - || d.Id == DomainDrivenDesignAnalyzer.Sas061.Id)) + d.Id == DomainDrivenDesignAnalyzer.Rule018.Id + || d.Id == DomainDrivenDesignAnalyzer.Rule027.Id + || d.Id == DomainDrivenDesignAnalyzer.Rule036.Id + || d.Id == DomainDrivenDesignAnalyzer.Rule041.Id)) { var title = Resources.CodeFix_Title_AddSealedToClass; context.RegisterCodeFix( @@ -138,7 +138,7 @@ private static void FixClass(CodeFixContext context, ClassDeclarationSyntax clas } if (diagnostics.Any(d => - d.Id == DomainDrivenDesignAnalyzer.Sas044.Id)) + d.Id == DomainDrivenDesignAnalyzer.Rule024.Id)) { var title = Resources.CodeFix_Title_AddDehydrateMethodToEntity; context.RegisterCodeFix( @@ -149,7 +149,7 @@ private static void FixClass(CodeFixContext context, ClassDeclarationSyntax clas } if (diagnostics.Any(d => - d.Id == DomainDrivenDesignAnalyzer.Sas030.Id)) + d.Id == DomainDrivenDesignAnalyzer.Rule010.Id)) { var title = Resources.CodeFix_Title_AddClassFactoryMethodToAggregate; context.RegisterCodeFix( @@ -160,7 +160,7 @@ private static void FixClass(CodeFixContext context, ClassDeclarationSyntax clas } if (diagnostics.Any(d => - d.Id == DomainDrivenDesignAnalyzer.Sas040.Id)) + d.Id == DomainDrivenDesignAnalyzer.Rule020.Id)) { var title = Resources.CodeFix_Title_AddClassFactoryMethodToEntity; context.RegisterCodeFix( @@ -171,7 +171,7 @@ private static void FixClass(CodeFixContext context, ClassDeclarationSyntax clas } if (diagnostics.Any(d => - d.Id == DomainDrivenDesignAnalyzer.Sas050.Id)) + d.Id == DomainDrivenDesignAnalyzer.Rule030.Id)) { var title = Resources.CodeFix_Title_AddClassFactoryMethodToValueObject; context.RegisterCodeFix( @@ -182,7 +182,7 @@ private static void FixClass(CodeFixContext context, ClassDeclarationSyntax clas } if (diagnostics.Any(d => - d.Id == DomainDrivenDesignAnalyzer.Sas036.Id || d.Id == DomainDrivenDesignAnalyzer.Sas045.Id)) + d.Id == DomainDrivenDesignAnalyzer.Rule016.Id || d.Id == DomainDrivenDesignAnalyzer.Rule025.Id)) { var title = Resources.CodeFix_Title_AddEntityValueAttributeToEntiyOrAggregate; context.RegisterCodeFix( @@ -199,7 +199,7 @@ private static void FixMethod(CodeFixContext context, MethodDeclarationSyntax me var diagnostic = diagnostics.First(); if (diagnostics.Any(d => - d.Id == DomainDrivenDesignAnalyzer.Sas055.Id)) + d.Id == DomainDrivenDesignAnalyzer.Rule035.Id)) { var title1 = Resources.CodeFix_Title_AddSkipImmutabilityCheckAttributeToValueObjectMethod; context.RegisterCodeFix( diff --git a/src/Tools.Analyzers.NonPlatform/Extensions/SyntaxExtensions.cs b/src/Tools.Analyzers.NonPlatform/Extensions/SyntaxExtensions.cs new file mode 100644 index 00000000..648ed8d0 --- /dev/null +++ b/src/Tools.Analyzers.NonPlatform/Extensions/SyntaxExtensions.cs @@ -0,0 +1,134 @@ +using Common.Extensions; +using Infrastructure.Web.Api.Interfaces; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using QueryAny; +using Tools.Analyzers.Common.Extensions; +using CSharpExtensions = Microsoft.CodeAnalysis.CSharp.CSharpExtensions; + +namespace Tools.Analyzers.NonPlatform.Extensions; + +public static class SyntaxExtensions +{ + public static bool HasEntityNameAttribute(this ClassDeclarationSyntax classDeclarationSyntax, + SyntaxNodeAnalysisContext context) + { + return HasEntityNameAttribute(context.SemanticModel, context.Compilation, classDeclarationSyntax); + } + + public static bool HasEntityNameAttribute(this SemanticModel semanticModel, Compilation compilation, + ClassDeclarationSyntax classDeclarationSyntax) + { + var attribute = classDeclarationSyntax.GetAttributeOfType(semanticModel, compilation); + return attribute.Exists(); + } + + public static bool HasOnlyPrivateInstanceConstructors(this ClassDeclarationSyntax classDeclarationSyntax, + out ConstructorDeclarationSyntax? constructor) + { + constructor = null; + var allConstructors = classDeclarationSyntax.Members.Where(member => member is ConstructorDeclarationSyntax) + .Cast() + .ToList(); + if (allConstructors.HasAny()) + { + foreach (var ctor in allConstructors) + { + if (!ctor.IsPrivateInstanceConstructor()) + { + constructor = ctor; + return false; + } + } + } + + return true; + } + + public static bool HasParameterlessConstructor(this ClassDeclarationSyntax classDeclarationSyntax) + { + var allConstructors = classDeclarationSyntax.Members.Where(member => member is ConstructorDeclarationSyntax) + .Cast() + .ToList(); + if (allConstructors.HasAny()) + { + var parameterlessConstructors = allConstructors + .Where(constructor => constructor.ParameterList.Parameters.Count == 0 && constructor.IsPublic()); + if (parameterlessConstructors.HasNone()) + { + return false; + } + } + + return true; + } + + public static bool HasRouteAttribute(this ClassDeclarationSyntax classDeclarationSyntax, + SyntaxNodeAnalysisContext context) + { + var attribute = + classDeclarationSyntax.GetAttributeOfType(context.SemanticModel, context.Compilation); + return attribute.Exists(); + } + + public static bool IsNamedEndingIn(this ClassDeclarationSyntax classDeclarationSyntax, string name) + { + var className = classDeclarationSyntax.Identifier.Text; + return className.EndsWith(name); + } + + public static bool IsOptionalType(this PropertyDeclarationSyntax propertyDeclarationSyntax, + SyntaxNodeAnalysisContext context) + { + var propertySymbol = CSharpExtensions.GetDeclaredSymbol(context.SemanticModel, propertyDeclarationSyntax); + if (propertySymbol is null) + { + return false; + } + + var getter = propertySymbol.GetMethod; + if (getter is null) + { + return false; + } + + var returnType = propertySymbol.GetMethod!.ReturnType; + if (returnType.IsOptionalType(context)) + { + return true; + } + + return false; + } + + public static bool IsOptionalType(this ITypeSymbol symbol, SyntaxNodeAnalysisContext context) + { + var optionalType = context.Compilation.GetTypeByMetadataName(typeof(global::Common.Optional<>).FullName!)!; + + return symbol.OriginalDefinition.IsOfType(optionalType); + } + + public static bool IsWithinNamespace(this ClassDeclarationSyntax classDeclarationSyntax, + SyntaxNodeAnalysisContext context, string ns) + { + var symbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax); + if (symbol is null) + { + return false; + } + + var containingNamespace = symbol.ContainingNamespace.ToDisplayString(); + return containingNamespace.StartsWith(ns); + } + + public static ITypeSymbol WithoutOptional(this ITypeSymbol symbol, SyntaxNodeAnalysisContext context) + { + if (symbol.IsOptionalType(context)) + { + return ((INamedTypeSymbol)symbol).TypeArguments[0]; + } + + return symbol; + } +} \ No newline at end of file diff --git a/src/Tools.Analyzers.NonPlatform/Resources.Designer.cs b/src/Tools.Analyzers.NonPlatform/Resources.Designer.cs index 8f03d0df..064cf5a3 100644 --- a/src/Tools.Analyzers.NonPlatform/Resources.Designer.cs +++ b/src/Tools.Analyzers.NonPlatform/Resources.Designer.cs @@ -465,623 +465,758 @@ internal static string Diagnostic_Title_PropertyMustBeSettable { } /// - /// Looks up a localized string similar to This method should return a 'Result<T>' type.. + /// Looks up a localized string similar to Property must have the return the correct type. /// - internal static string SAS010Description { + internal static string SAASAPP014Description { get { - return ResourceManager.GetString("SAS010Description", resourceCulture); + return ResourceManager.GetString("SAASAPP014Description", resourceCulture); } } /// - /// Looks up a localized string similar to If method '{0}' is supposed to be a service operation, then it should return one of these possible types: '{1}'. + /// Looks up a localized string similar to Property '{0}' must return one of these primitive types: '{1}', or a List<T>/Dictionary<string, T> of one of those types, or be another type in the '{2}' namespace. /// - internal static string SAS010MessageFormat { + internal static string SAASAPP014MessageFormat { get { - return ResourceManager.GetString("SAS010MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASAPP014MessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Wrong return type. /// - internal static string SAS010Title { + internal static string SAASAPP014Title { get { - return ResourceManager.GetString("SAS010Title", resourceCulture); + return ResourceManager.GetString("SAASAPP014Title", resourceCulture); } } /// - /// Looks up a localized string similar to This service operation should have at least one parameter, and that parameter should be derived from: 'IWebRequest<TResponse>'.. + /// Looks up a localized string similar to Property should be 'Optional<T>' not nullable.. /// - internal static string SAS011Description { + internal static string SAASAPP024Description { get { - return ResourceManager.GetString("SAS011Description", resourceCulture); + return ResourceManager.GetString("SAASAPP024Description", resourceCulture); } } /// - /// Looks up a localized string similar to Service operation '{0}' should have at least one parameter of a type derived from: 'IWebRequest<TResponse>'. + /// Looks up a localized string similar to Property '{0}' should be 'Optional<T>' not nullable. /// - internal static string SAS011MessageFormat { + internal static string SAASAPP024MessageFormat { get { - return ResourceManager.GetString("SAS011MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASAPP024MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Missing first parameter or wrong parameter type. + /// Looks up a localized string similar to Wrong declaration. /// - internal static string SAS011Title { + internal static string SAASAPP024Title { get { - return ResourceManager.GetString("SAS011Title", resourceCulture); + return ResourceManager.GetString("SAASAPP024Title", resourceCulture); } } /// - /// Looks up a localized string similar to This service operation can only have a 'CancellationToken' as its second parameter.. + /// Looks up a localized string similar to Property must have the return the correct type. /// - internal static string SAS012Description { + internal static string SAASAPP025Description { get { - return ResourceManager.GetString("SAS012Description", resourceCulture); + return ResourceManager.GetString("SAASAPP025Description", resourceCulture); } } /// - /// Looks up a localized string similar to Service operation '{0}' can only have a 'CancellationToken' as its second parameter. + /// Looks up a localized string similar to Property '{0}' must return one of these primitive types: '{1}', or any ValueObject, or any Enum, or an Optional<T>/List<T>/Dictionary<string, T> of one of those types. /// - internal static string SAS012MessageFormat { + internal static string SAASAPP025MessageFormat { get { - return ResourceManager.GetString("SAS012MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASAPP025MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Wrong second parameter type. + /// Looks up a localized string similar to Wrong return type. /// - internal static string SAS012Title { + internal static string SAASAPP025Title { get { - return ResourceManager.GetString("SAS012Title", resourceCulture); + return ResourceManager.GetString("SAASAPP025Title", resourceCulture); } } /// - /// Looks up a localized string similar to The request type for this service operation should be declared with a '[RouteAttribute]' on it.. + /// Looks up a localized string similar to Aggregate roots must have at least one 'Create()' class factory method.. /// - internal static string SAS013Description { + internal static string SAASDDD010Description { get { - return ResourceManager.GetString("SAS013Description", resourceCulture); + return ResourceManager.GetString("SAASDDD010Description", resourceCulture); } } /// - /// Looks up a localized string similar to Service operation '{0}' has a request type that does not have a '[RouteAttribute]' on it. + /// Looks up a localized string similar to Aggregate root type '{0}' must implement a class factory method called 'Create()' to create new instances. /// - internal static string SAS013MessageFormat { + internal static string SAASDDD010MessageFormat { get { - return ResourceManager.GetString("SAS013MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASDDD010MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Missing '[RouteAttribute]' on request type. + /// Looks up a localized string similar to Missing 'Create()' method. /// - internal static string SAS013Title { + internal static string SAASDDD010Title { get { - return ResourceManager.GetString("SAS013Title", resourceCulture); + return ResourceManager.GetString("SAASDDD010Title", resourceCulture); } } /// - /// Looks up a localized string similar to This service operation has a route declared on its request that is different from other service operations in this class.. + /// Looks up a localized string similar to Create() class factory method must call specific code.. /// - internal static string SAS014Description { + internal static string SAASDDD012Description { get { - return ResourceManager.GetString("SAS014Description", resourceCulture); + return ResourceManager.GetString("SAASDDD012Description", resourceCulture); } } /// - /// Looks up a localized string similar to Service operation '{0}' is required to have a request with a route for the same primary resource as the other service operations in this class. + /// Looks up a localized string similar to Factory method '{0}' must make a call to method '{1}'. /// - internal static string SAS014MessageFormat { + internal static string SAASDDD012MessageFormat { get { - return ResourceManager.GetString("SAS014MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASDDD012MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Wrong route group. + /// Looks up a localized string similar to Missing required call to method. /// - internal static string SAS014Title { + internal static string SAASDDD012Title { get { - return ResourceManager.GetString("SAS014Title", resourceCulture); + return ResourceManager.GetString("SAASDDD012Title", resourceCulture); } } /// - /// Looks up a localized string similar to This service operation uses the same request type as another service operation in this class.. + /// Looks up a localized string similar to Entities must have at least one 'Create()' class factory method.. /// - internal static string SAS015Description { + internal static string SAASDDD020Description { get { - return ResourceManager.GetString("SAS015Description", resourceCulture); + return ResourceManager.GetString("SAASDDD020Description", resourceCulture); } } /// - /// Looks up a localized string similar to Service operation '{0}' uses the same type for its first parameter as does another service operation in this class. They must use different request types. + /// Looks up a localized string similar to Entity type '{0}' must implement a class factory method called 'Create()' to create new instances. /// - internal static string SAS015MessageFormat { + internal static string SAASDDD020MessageFormat { get { - return ResourceManager.GetString("SAS015MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASDDD020MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Duplicate request type. + /// Looks up a localized string similar to Missing 'Create()' method. /// - internal static string SAS015Title { + internal static string SAASDDD020Title { get { - return ResourceManager.GetString("SAS015Title", resourceCulture); + return ResourceManager.GetString("SAASDDD020Title", resourceCulture); } } /// - /// Looks up a localized string similar to This service operation should return an appropriate Result type for the operation.. + /// Looks up a localized string similar to ValueObjects must have at least one 'Create()' class factory method.. /// - internal static string SAS016Description { + internal static string SAASDDD030Description { get { - return ResourceManager.GetString("SAS016Description", resourceCulture); + return ResourceManager.GetString("SAASDDD030Description", resourceCulture); } } /// - /// Looks up a localized string similar to Service operation '{0}' is defined as a '{1}' operation, and can only return one of these types: '{2}'. + /// Looks up a localized string similar to ValueObject type '{0}' must implement a class factory method called 'Create()' to create new instances. /// - internal static string SAS016MessageFormat { + internal static string SAASDDD030MessageFormat { get { - return ResourceManager.GetString("SAS016MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASDDD030MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Unexpected return type for operation. + /// Looks up a localized string similar to Missing 'Create()' method. /// - internal static string SAS016Title { + internal static string SAASDDD030Title { get { - return ResourceManager.GetString("SAS016Title", resourceCulture); + return ResourceManager.GetString("SAASDDD030Title", resourceCulture); } } /// - /// Looks up a localized string similar to The request type should be declared with a '[RouteAttribute]' on it.. + /// Looks up a localized string similar to Method must not mutate the ValueObject.. /// - internal static string SAS017Description { + internal static string SAASDDD035Description { get { - return ResourceManager.GetString("SAS017Description", resourceCulture); + return ResourceManager.GetString("SAASDDD035Description", resourceCulture); } } /// - /// Looks up a localized string similar to Request type '{0}' should declare a '[RouteAttribute]'. + /// Looks up a localized string similar to Method '{0}' must not mutate this ValueObject's state, and must return a new instance of this ValueObject using one of these return types, either: '{1}', or can be suppressed by adding the '{2}' attribute to the method. /// - internal static string SAS017MessageFormat { + internal static string SAASDDD035MessageFormat { get { - return ResourceManager.GetString("SAS017MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASDDD035MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Missing '[RouteAttribute]'. + /// Looks up a localized string similar to Method mutates ValueObject state. /// - internal static string SAS017Title { + internal static string SAASDDD035Title { get { - return ResourceManager.GetString("SAS017Title", resourceCulture); + return ResourceManager.GetString("SAASDDD035Title", resourceCulture); } } /// - /// Looks up a localized string similar to The request type should be not declare a '[AuthorizeAttribute]' on it, since the '[RouteAttribute]' has been configured with 'Anonymous' access.. + /// Looks up a localized string similar to Domain Event must be named in the past tense, as they occured in the past.. /// - internal static string SAS018Description { + internal static string SAASDDD043Description { get { - return ResourceManager.GetString("SAS018Description", resourceCulture); + return ResourceManager.GetString("SAASDDD043Description", resourceCulture); } } /// - /// Looks up a localized string similar to Request type '{0}' should not declare a '[AuthorizeAttribute]'. + /// Looks up a localized string similar to Domain Event '{0}' must be named in the past tense. /// - internal static string SAS018MessageFormat { + internal static string SAASDDD043MessageFormat { get { - return ResourceManager.GetString("SAS018MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASDDD043MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Unexpected '[AuthorizeAttribute]'. + /// Looks up a localized string similar to Domain Event must be named in the past tense. /// - internal static string SAS018Title { + internal static string SAASDDD043Title { get { - return ResourceManager.GetString("SAS018Title", resourceCulture); + return ResourceManager.GetString("SAASDDD043Title", resourceCulture); } } /// - /// Looks up a localized string similar to The request type should be declared with a '[AuthorizeAttribute]' on it to constrain role/feature access to it, since the '[RouteAttribute]' has been configured with secure access.. + /// Looks up a localized string similar to DomainEvent must have at least one 'Create()' class factory method.. /// - internal static string SAS019Description { + internal static string SAASDDD044Description { get { - return ResourceManager.GetString("SAS019Description", resourceCulture); + return ResourceManager.GetString("SAASDDD044Description", resourceCulture); } } /// - /// Looks up a localized string similar to Request type '{0}' should declare a '[AuthorizeAttribute]' to constrain role/feature access. + /// Looks up a localized string similar to DomainEvent type '{0}' must implement a class factory method called 'Create()' to create new instances. /// - internal static string SAS019MessageFormat { + internal static string SAASDDD044MessageFormat { get { - return ResourceManager.GetString("SAS019MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASDDD044MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Missing '[AuthorizeAttribute]'. + /// Looks up a localized string similar to Missing 'Create()' method. /// - internal static string SAS019Title { + internal static string SAASDDD044Title { get { - return ResourceManager.GetString("SAS019Title", resourceCulture); + return ResourceManager.GetString("SAASDDD044Title", resourceCulture); } } /// - /// Looks up a localized string similar to Aggregate roots must have at least one 'Create()' class factory method.. + /// Looks up a localized string similar to Create() class factory method must return the correct type.. /// - internal static string SAS030Description { + internal static string SAASDDD045Description { get { - return ResourceManager.GetString("SAS030Description", resourceCulture); + return ResourceManager.GetString("SAASDDD045Description", resourceCulture); } } /// - /// Looks up a localized string similar to Aggregate root type '{0}' must implement a class factory method called 'Create()' to create new instances. + /// Looks up a localized string similar to Factory method '{0}' must return this type: '{1}'. /// - internal static string SAS030MessageFormat { + internal static string SAASDDD045MessageFormat { get { - return ResourceManager.GetString("SAS030MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASDDD045MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Missing 'Create()' method. + /// Looks up a localized string similar to Wrong return type. /// - internal static string SAS030Title { + internal static string SAASDDD045Title { get { - return ResourceManager.GetString("SAS030Title", resourceCulture); + return ResourceManager.GetString("SAASDDD045Title", resourceCulture); } } /// - /// Looks up a localized string similar to Create() class factory method must call specific code.. + /// Looks up a localized string similar to Property must be marked 'required' or be nullable or be initialized.. /// - internal static string SAS032Description { + internal static string SAASDDD047Description { get { - return ResourceManager.GetString("SAS032Description", resourceCulture); + return ResourceManager.GetString("SAASDDD047Description", resourceCulture); } } /// - /// Looks up a localized string similar to Factory method '{0}' must make a call to method '{1}'. + /// Looks up a localized string similar to Property '{0}' must be marked 'required' or be nullable or be initialized. /// - internal static string SAS032MessageFormat { + internal static string SAASDDD047MessageFormat { get { - return ResourceManager.GetString("SAS032MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASDDD047MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Missing required call to method. + /// Looks up a localized string similar to Wrong declaration. /// - internal static string SAS032Title { + internal static string SAASDDD047Title { get { - return ResourceManager.GetString("SAS032Title", resourceCulture); + return ResourceManager.GetString("SAASDDD047Title", resourceCulture); } } /// - /// Looks up a localized string similar to Entities must have at least one 'Create()' class factory method.. + /// Looks up a localized string similar to Property must have the return the correct type.. /// - internal static string SAS040Description { + internal static string SAASDDD049Description { get { - return ResourceManager.GetString("SAS040Description", resourceCulture); + return ResourceManager.GetString("SAASDDD049Description", resourceCulture); } } /// - /// Looks up a localized string similar to Entity type '{0}' must implement a class factory method called 'Create()' to create new instances. + /// Looks up a localized string similar to Property '{0}' must return one of these primitive types: '{1}', or any Enum, or a List<T>/Dictionary<string, T> of one of those types. /// - internal static string SAS040MessageFormat { + internal static string SAASDDD049MessageFormat { get { - return ResourceManager.GetString("SAS040MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASDDD049MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Missing 'Create()' method. + /// Looks up a localized string similar to Wrong return type. /// - internal static string SAS040Title { + internal static string SAASDDD049Title { get { - return ResourceManager.GetString("SAS040Title", resourceCulture); + return ResourceManager.GetString("SAASDDD049Title", resourceCulture); } } /// - /// Looks up a localized string similar to ValueObjects must have at least one 'Create()' class factory method.. + /// Looks up a localized string similar to This method should return a 'Result<T>' type.. /// - internal static string SAS050Description { + internal static string SAASWEB010Description { get { - return ResourceManager.GetString("SAS050Description", resourceCulture); + return ResourceManager.GetString("SAASWEB010Description", resourceCulture); } } /// - /// Looks up a localized string similar to ValueObject type '{0}' must implement a class factory method called 'Create()' to create new instances. + /// Looks up a localized string similar to If method '{0}' is supposed to be a service operation, then it should return one of these possible types: '{1}'. /// - internal static string SAS050MessageFormat { + internal static string SAASWEB010MessageFormat { get { - return ResourceManager.GetString("SAS050MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASWEB010MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Missing 'Create()' method. + /// Looks up a localized string similar to Wrong return type. /// - internal static string SAS050Title { + internal static string SAASWEB010Title { get { - return ResourceManager.GetString("SAS050Title", resourceCulture); + return ResourceManager.GetString("SAASWEB010Title", resourceCulture); } } /// - /// Looks up a localized string similar to Method must not mutate the ValueObject.. + /// Looks up a localized string similar to This service operation should have at least one parameter, and that parameter should be derived from: 'IWebRequest<TResponse>'.. /// - internal static string SAS055Description { + internal static string SAASWEB011Description { get { - return ResourceManager.GetString("SAS055Description", resourceCulture); + return ResourceManager.GetString("SAASWEB011Description", resourceCulture); } } /// - /// Looks up a localized string similar to Method '{0}' must not mutate this ValueObject's state, and must return a new instance of this ValueObject using one of these return types, either: '{1}', or can be suppressed by adding the '{2}' attribute to the method. + /// Looks up a localized string similar to Service operation '{0}' should have at least one parameter of a type derived from: 'IWebRequest<TResponse>'. /// - internal static string SAS055MessageFormat { + internal static string SAASWEB011MessageFormat { get { - return ResourceManager.GetString("SAS055MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASWEB011MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Method mutates ValueObject state. + /// Looks up a localized string similar to Missing first parameter or wrong parameter type. /// - internal static string SAS055Title { + internal static string SAASWEB011Title { get { - return ResourceManager.GetString("SAS055Title", resourceCulture); + return ResourceManager.GetString("SAASWEB011Title", resourceCulture); } } /// - /// Looks up a localized string similar to Domain Event must be named in the past tense, as they occured in the past.. + /// Looks up a localized string similar to This service operation can only have a 'CancellationToken' as its second parameter.. /// - internal static string SAS063Description { + internal static string SAASWEB012Description { get { - return ResourceManager.GetString("SAS063Description", resourceCulture); + return ResourceManager.GetString("SAASWEB012Description", resourceCulture); } } /// - /// Looks up a localized string similar to Domain Event '{0}' must be named in the past tense. + /// Looks up a localized string similar to Service operation '{0}' can only have a 'CancellationToken' as its second parameter. /// - internal static string SAS063MessageFormat { + internal static string SAASWEB012MessageFormat { get { - return ResourceManager.GetString("SAS063MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASWEB012MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Domain Event must be named in the past tense. + /// Looks up a localized string similar to Wrong second parameter type. /// - internal static string SAS063Title { + internal static string SAASWEB012Title { get { - return ResourceManager.GetString("SAS063Title", resourceCulture); + return ResourceManager.GetString("SAASWEB012Title", resourceCulture); } } /// - /// Looks up a localized string similar to DomainEvent must have at least one 'Create()' class factory method.. + /// Looks up a localized string similar to The request type for this service operation should be declared with a '[RouteAttribute]' on it.. /// - internal static string SAS064Description { + internal static string SAASWEB013Description { get { - return ResourceManager.GetString("SAS064Description", resourceCulture); + return ResourceManager.GetString("SAASWEB013Description", resourceCulture); } } /// - /// Looks up a localized string similar to DomainEvent type '{0}' must implement a class factory method called 'Create()' to create new instances. + /// Looks up a localized string similar to Service operation '{0}' has a request type that does not have a '[RouteAttribute]' on it. /// - internal static string SAS064MessageFormat { + internal static string SAASWEB013MessageFormat { get { - return ResourceManager.GetString("SAS064MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASWEB013MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Missing 'Create()' method. + /// Looks up a localized string similar to Missing '[RouteAttribute]' on request type. /// - internal static string SAS064Title { + internal static string SAASWEB013Title { get { - return ResourceManager.GetString("SAS064Title", resourceCulture); + return ResourceManager.GetString("SAASWEB013Title", resourceCulture); } } /// - /// Looks up a localized string similar to Create() class factory method must return the correct type.. + /// Looks up a localized string similar to This service operation has a route declared on its request that is different from other service operations in this class.. /// - internal static string SAS065Description { + internal static string SAASWEB014Description { get { - return ResourceManager.GetString("SAS065Description", resourceCulture); + return ResourceManager.GetString("SAASWEB014Description", resourceCulture); } } /// - /// Looks up a localized string similar to Factory method '{0}' must return this type: '{1}'. + /// Looks up a localized string similar to Service operation '{0}' is required to have a request with a route for the same primary resource as the other service operations in this class. /// - internal static string SAS065MessageFormat { + internal static string SAASWEB014MessageFormat { get { - return ResourceManager.GetString("SAS065MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASWEB014MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Wrong return type. + /// Looks up a localized string similar to Wrong route group. /// - internal static string SAS065Title { + internal static string SAASWEB014Title { get { - return ResourceManager.GetString("SAS065Title", resourceCulture); + return ResourceManager.GetString("SAASWEB014Title", resourceCulture); } } /// - /// Looks up a localized string similar to Property must be marked 'required' or be nullable or be initialized.. + /// Looks up a localized string similar to This service operation uses the same request type as another service operation in this class.. /// - internal static string SAS067Description { + internal static string SAASWEB015Description { get { - return ResourceManager.GetString("SAS067Description", resourceCulture); + return ResourceManager.GetString("SAASWEB015Description", resourceCulture); } } /// - /// Looks up a localized string similar to Property '{0}' must be marked 'required' or be nullable or be initialized. + /// Looks up a localized string similar to Service operation '{0}' uses the same type for its first parameter as does another service operation in this class. They must use different request types. /// - internal static string SAS067MessageFormat { + internal static string SAASWEB015MessageFormat { get { - return ResourceManager.GetString("SAS067MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASWEB015MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Wrong declaration. + /// Looks up a localized string similar to Duplicate request type. /// - internal static string SAS067Title { + internal static string SAASWEB015Title { get { - return ResourceManager.GetString("SAS067Title", resourceCulture); + return ResourceManager.GetString("SAASWEB015Title", resourceCulture); } } /// - /// Looks up a localized string similar to Property must have the return the correct type.. + /// Looks up a localized string similar to This service operation should return an appropriate Result type for the operation.. /// - internal static string SAS069Description { + internal static string SAASWEB016Description { get { - return ResourceManager.GetString("SAS069Description", resourceCulture); + return ResourceManager.GetString("SAASWEB016Description", resourceCulture); } } /// - /// Looks up a localized string similar to Property '{0}' must return one of these primitive types: '{1}', or any Enum, or a List<T>/Dictionary<string, T> of one of those types. + /// Looks up a localized string similar to Service operation '{0}' is defined as a '{1}' operation, and can only return one of these types: '{2}'. /// - internal static string SAS069MessageFormat { + internal static string SAASWEB016MessageFormat { get { - return ResourceManager.GetString("SAS069MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASWEB016MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Wrong return type. + /// Looks up a localized string similar to Unexpected return type for operation. /// - internal static string SAS069Title { + internal static string SAASWEB016Title { get { - return ResourceManager.GetString("SAS069Title", resourceCulture); + return ResourceManager.GetString("SAASWEB016Title", resourceCulture); } } /// - /// Looks up a localized string similar to Property must have the return the correct type. + /// Looks up a localized string similar to The request type should be declared with a '[RouteAttribute]' on it.. /// - internal static string SAS074Description { + internal static string SAASWEB017Description { get { - return ResourceManager.GetString("SAS074Description", resourceCulture); + return ResourceManager.GetString("SAASWEB017Description", resourceCulture); } } /// - /// Looks up a localized string similar to Property '{0}' must return one of these primitive types: '{1}', or a List<T>/Dictionary<string, T> of one of those types, or be another type in the '{2}' namespace. + /// Looks up a localized string similar to Request type '{0}' should declare a '[RouteAttribute]'. /// - internal static string SAS074MessageFormat { + internal static string SAASWEB017MessageFormat { get { - return ResourceManager.GetString("SAS074MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASWEB017MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Wrong return type. + /// Looks up a localized string similar to Missing '[RouteAttribute]'. /// - internal static string SAS074Title { + internal static string SAASWEB017Title { get { - return ResourceManager.GetString("SAS074Title", resourceCulture); + return ResourceManager.GetString("SAASWEB017Title", resourceCulture); } } /// - /// Looks up a localized string similar to Property should be 'Optional<T>' not nullable.. + /// Looks up a localized string similar to The request type should be not declare a '[AuthorizeAttribute]' on it, since the '[RouteAttribute]' has been configured with 'Anonymous' access.. /// - internal static string SAS084Description { + internal static string SAASWEB018Description { get { - return ResourceManager.GetString("SAS084Description", resourceCulture); + return ResourceManager.GetString("SAASWEB018Description", resourceCulture); } } /// - /// Looks up a localized string similar to Property '{0}' should be 'Optional<T>' not nullable. + /// Looks up a localized string similar to Request type '{0}' should not declare a '[AuthorizeAttribute]'. /// - internal static string SAS084MessageFormat { + internal static string SAASWEB018MessageFormat { get { - return ResourceManager.GetString("SAS084MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASWEB018MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Wrong declaration. + /// Looks up a localized string similar to Unexpected '[AuthorizeAttribute]'. /// - internal static string SAS084Title { + internal static string SAASWEB018Title { get { - return ResourceManager.GetString("SAS084Title", resourceCulture); + return ResourceManager.GetString("SAASWEB018Title", resourceCulture); } } /// - /// Looks up a localized string similar to Property must have the return the correct type. + /// Looks up a localized string similar to The request type should be declared with a '[AuthorizeAttribute]' on it to constrain role/feature access to it, since the '[RouteAttribute]' has been configured with secure access.. /// - internal static string SAS085Description { + internal static string SAASWEB019Description { get { - return ResourceManager.GetString("SAS085Description", resourceCulture); + return ResourceManager.GetString("SAASWEB019Description", resourceCulture); } } /// - /// Looks up a localized string similar to Property '{0}' must return one of these primitive types: '{1}', or any ValueObject, or any Enum, or an Optional<T>/List<T>/Dictionary<string, T> of one of those types. + /// Looks up a localized string similar to Request type '{0}' should declare a '[AuthorizeAttribute]' to constrain role/feature access. /// - internal static string SAS085MessageFormat { + internal static string SAASWEB019MessageFormat { get { - return ResourceManager.GetString("SAS085MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASWEB019MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Wrong return type. + /// Looks up a localized string similar to Missing '[AuthorizeAttribute]'. + /// + internal static string SAASWEB019Title { + get { + return ResourceManager.GetString("SAASWEB019Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request type should be suffixed with the word 'Request'.. + /// + internal static string SAASWEB031Description { + get { + return ResourceManager.GetString("SAASWEB031Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request '{0}' should be suffixed with the word 'Request'. + /// + internal static string SAASWEB031MessageFormat { + get { + return ResourceManager.GetString("SAASWEB031MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Naming is wrong. + /// + internal static string SAASWEB031Title { + get { + return ResourceManager.GetString("SAASWEB031Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request type should be declared in the 'Infrastructure.Web.Api.Operations.Shared' namespace.. + /// + internal static string SAASWEB032Description { + get { + return ResourceManager.GetString("SAASWEB032Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request '{0}' should be declared in the 'Infrastructure.Web.Api.Operations.Shared' namespace. + /// + internal static string SAASWEB032MessageFormat { + get { + return ResourceManager.GetString("SAASWEB032MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Location is wrong. + /// + internal static string SAASWEB032Title { + get { + return ResourceManager.GetString("SAASWEB032Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request should be declared with a '[RouteAttribute]' on it.. + /// + internal static string SAASWEB033Description { + get { + return ResourceManager.GetString("SAASWEB033Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request '{0}' should have a '[RouteAttribute]' on it. + /// + internal static string SAASWEB033MessageFormat { + get { + return ResourceManager.GetString("SAASWEB033MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Missing '[RouteAttribute]' on request. + /// + internal static string SAASWEB033Title { + get { + return ResourceManager.GetString("SAASWEB033Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The request type should be suffixed with the word 'Response'.. + /// + internal static string SAASWEB041Description { + get { + return ResourceManager.GetString("SAASWEB041Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Request '{0}' should be suffixed with the word 'Response'. + /// + internal static string SAASWEB041MessageFormat { + get { + return ResourceManager.GetString("SAASWEB041MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Naming is wrong. + /// + internal static string SAASWEB041Title { + get { + return ResourceManager.GetString("SAASWEB041Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The response type should be declared in the 'Infrastructure.Web.Api.Operations.Shared' namespace.. + /// + internal static string SAASWEB042Description { + get { + return ResourceManager.GetString("SAASWEB042Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Response '{0}' should be declared in the 'Infrastructure.Web.Api.Operations.Shared' namespace. + /// + internal static string SAASWEB042MessageFormat { + get { + return ResourceManager.GetString("SAASWEB042MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Location is wrong. /// - internal static string SAS085Title { + internal static string SAASWEB042Title { get { - return ResourceManager.GetString("SAS085Title", resourceCulture); + return ResourceManager.GetString("SAASWEB042Title", resourceCulture); } } } diff --git a/src/Tools.Analyzers.NonPlatform/Resources.resx b/src/Tools.Analyzers.NonPlatform/Resources.resx index 17182a3f..b9917162 100644 --- a/src/Tools.Analyzers.NonPlatform/Resources.resx +++ b/src/Tools.Analyzers.NonPlatform/Resources.resx @@ -134,211 +134,258 @@ Wrong nullable return type - + This method should return a 'Result<T>' type. - + If method '{0}' is supposed to be a service operation, then it should return one of these possible types: '{1}' - + Wrong return type - + This service operation should have at least one parameter, and that parameter should be derived from: 'IWebRequest<TResponse>'. - + Service operation '{0}' should have at least one parameter of a type derived from: 'IWebRequest<TResponse>' - + Missing first parameter or wrong parameter type - + This service operation can only have a 'CancellationToken' as its second parameter. - + Service operation '{0}' can only have a 'CancellationToken' as its second parameter - + Wrong second parameter type - + The request type for this service operation should be declared with a '[RouteAttribute]' on it. - + Service operation '{0}' has a request type that does not have a '[RouteAttribute]' on it - + Missing '[RouteAttribute]' on request type - + This service operation has a route declared on its request that is different from other service operations in this class. - + Service operation '{0}' is required to have a request with a route for the same primary resource as the other service operations in this class - + Wrong route group - + This service operation uses the same request type as another service operation in this class. - + Service operation '{0}' uses the same type for its first parameter as does another service operation in this class. They must use different request types - + Duplicate request type - + This service operation should return an appropriate Result type for the operation. - + Service operation '{0}' is defined as a '{1}' operation, and can only return one of these types: '{2}' - + Unexpected return type for operation - + The request type should be declared with a '[RouteAttribute]' on it. - + Request type '{0}' should declare a '[RouteAttribute]' - + Missing '[RouteAttribute]' - + The request type should be not declare a '[AuthorizeAttribute]' on it, since the '[RouteAttribute]' has been configured with 'Anonymous' access. - + Request type '{0}' should not declare a '[AuthorizeAttribute]' - + Unexpected '[AuthorizeAttribute]' - + The request type should be declared with a '[AuthorizeAttribute]' on it to constrain role/feature access to it, since the '[RouteAttribute]' has been configured with secure access. - + Request type '{0}' should declare a '[AuthorizeAttribute]' to constrain role/feature access - + Missing '[AuthorizeAttribute]' - + + The request type should be suffixed with the word 'Request'. + + + Request '{0}' should be suffixed with the word 'Request' + + + Naming is wrong + + + The request type should be declared in the 'Infrastructure.Web.Api.Operations.Shared' namespace. + + + Request '{0}' should be declared in the 'Infrastructure.Web.Api.Operations.Shared' namespace + + + Location is wrong + + + The request should be declared with a '[RouteAttribute]' on it. + + + Request '{0}' should have a '[RouteAttribute]' on it + + + Missing '[RouteAttribute]' on request + + + The request type should be suffixed with the word 'Response'. + + + Request '{0}' should be suffixed with the word 'Response' + + + Naming is wrong + + + The response type should be declared in the 'Infrastructure.Web.Api.Operations.Shared' namespace. + + + Response '{0}' should be declared in the 'Infrastructure.Web.Api.Operations.Shared' namespace + + + Location is wrong + + + Aggregate roots must have at least one 'Create()' class factory method. - + Aggregate root type '{0}' must implement a class factory method called 'Create()' to create new instances - + Missing 'Create()' method - + Create() class factory method must call specific code. - + Factory method '{0}' must make a call to method '{1}' - + Missing required call to method - + Entities must have at least one 'Create()' class factory method. - + Entity type '{0}' must implement a class factory method called 'Create()' to create new instances - + Missing 'Create()' method - + ValueObjects must have at least one 'Create()' class factory method. - + ValueObject type '{0}' must implement a class factory method called 'Create()' to create new instances - + Missing 'Create()' method - + Method must not mutate the ValueObject. - + Method '{0}' must not mutate this ValueObject's state, and must return a new instance of this ValueObject using one of these return types, either: '{1}', or can be suppressed by adding the '{2}' attribute to the method - + Method mutates ValueObject state - + Domain Event must be named in the past tense, as they occured in the past. - + Domain Event '{0}' must be named in the past tense - + Domain Event must be named in the past tense - + DomainEvent must have at least one 'Create()' class factory method. - + DomainEvent type '{0}' must implement a class factory method called 'Create()' to create new instances - + Missing 'Create()' method - + Create() class factory method must return the correct type. - + Factory method '{0}' must return this type: '{1}' - + Wrong return type - + Property must be marked 'required' or be nullable or be initialized. - + Property '{0}' must be marked 'required' or be nullable or be initialized - + Wrong declaration - + Property must have the return the correct type. - + Property '{0}' must return one of these primitive types: '{1}', or any Enum, or a List<T>/Dictionary<string, T> of one of those types - + Wrong return type - + + Property must have the return the correct type - + Property '{0}' must return one of these primitive types: '{1}', or a List<T>/Dictionary<string, T> of one of those types, or be another type in the '{2}' namespace - + Wrong return type - + Property should be 'Optional<T>' not nullable. - + Property '{0}' should be 'Optional<T>' not nullable - + Wrong declaration - + Property must have the return the correct type - + Property '{0}' must return one of these primitive types: '{1}', or any ValueObject, or any Enum, or an Optional<T>/List<T>/Dictionary<string, T> of one of those types - + Wrong return type diff --git a/src/Tools.Analyzers.NonPlatform/WebApiClassAnalyzer.cs b/src/Tools.Analyzers.NonPlatform/WebApiClassAnalyzer.cs deleted file mode 100644 index 44de51b7..00000000 --- a/src/Tools.Analyzers.NonPlatform/WebApiClassAnalyzer.cs +++ /dev/null @@ -1,468 +0,0 @@ -using System.Collections.Immutable; -using System.Text; -using Common.Extensions; -using Infrastructure.Web.Api.Interfaces; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using Tools.Analyzers.Common; -using Tools.Analyzers.Common.Extensions; -using Tools.Analyzers.NonPlatform.Extensions; - -// ReSharper disable InvalidXmlDocComment - -namespace Tools.Analyzers.NonPlatform; - -/// -/// An analyzer to ensure that WebAPI classes are configured correctly. -/// SAS010: Warning: Methods that are public, should return a or just any T, where T is either: -/// or or -/// -/// SAS011: Warning: These methods must have at least one parameter, and first parameter must be -/// , where -/// TResponse is same type as in the return value. -/// SAS012: Warning: The second parameter can only be a -/// SAS013: Warning: These methods must be decorated with a -/// SAS014: Warning: The route (of all these methods in this class) should start with the same path -/// SAS015: Warning: There should be no methods in this class with the same -/// SAS016: Warning: This service operation should return an appropriate Result type for the operation -/// SAS017: Warning: The request type should be declared with a on it -/// SAS018: Error: There should not be an if the -/// declares access -/// SAS019: Warning: There should be a if the declares -/// anything other than access -/// -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public class WebApiClassAnalyzer : DiagnosticAnalyzer -{ - internal static readonly Dictionary> - AllowableOperationReturnTypes = - new() - { - { - Infrastructure.Web.Api.Interfaces.ServiceOperation.Post, - new List { typeof(ApiEmptyResult), typeof(ApiPostResult<,>) } - }, - { - Infrastructure.Web.Api.Interfaces.ServiceOperation.Get, - new List { typeof(ApiEmptyResult), typeof(ApiResult<,>), typeof(ApiGetResult<,>) } - }, - { - Infrastructure.Web.Api.Interfaces.ServiceOperation.Search, - new List - { - typeof(ApiEmptyResult), typeof(ApiResult<,>), typeof(ApiGetResult<,>), - typeof(ApiSearchResult<,>) - } - }, - { - Infrastructure.Web.Api.Interfaces.ServiceOperation.PutPatch, - new List { typeof(ApiEmptyResult), typeof(ApiResult<,>), typeof(ApiPutPatchResult<,>) } - }, - { - Infrastructure.Web.Api.Interfaces.ServiceOperation.Delete, - new List { typeof(ApiEmptyResult), typeof(ApiResult<,>), typeof(ApiDeleteResult) } - } - }; - - internal static readonly Type[] AllowableReturnTypes = - { - typeof(ApiEmptyResult), - typeof(ApiResult<,>), - typeof(ApiPostResult<,>), - typeof(ApiGetResult<,>), - typeof(ApiSearchResult<,>), - typeof(ApiPutPatchResult<,>), - typeof(ApiDeleteResult) - }; - - internal static readonly DiagnosticDescriptor Sas010 = "SAS010".GetDescriptor(DiagnosticSeverity.Warning, - AnalyzerConstants.Categories.WebApi, nameof(Resources.SAS010Title), nameof(Resources.SAS010Description), - nameof(Resources.SAS010MessageFormat)); - - internal static readonly DiagnosticDescriptor Sas011 = "SAS011".GetDescriptor(DiagnosticSeverity.Warning, - AnalyzerConstants.Categories.WebApi, nameof(Resources.SAS011Title), nameof(Resources.SAS011Description), - nameof(Resources.SAS011MessageFormat)); - - internal static readonly DiagnosticDescriptor Sas012 = "SAS012".GetDescriptor(DiagnosticSeverity.Warning, - AnalyzerConstants.Categories.WebApi, nameof(Resources.SAS012Title), nameof(Resources.SAS012Description), - nameof(Resources.SAS012MessageFormat)); - - internal static readonly DiagnosticDescriptor Sas013 = "SAS013".GetDescriptor(DiagnosticSeverity.Warning, - AnalyzerConstants.Categories.WebApi, nameof(Resources.SAS013Title), nameof(Resources.SAS013Description), - nameof(Resources.SAS013MessageFormat)); - - internal static readonly DiagnosticDescriptor Sas014 = "SAS014".GetDescriptor(DiagnosticSeverity.Warning, - AnalyzerConstants.Categories.WebApi, nameof(Resources.SAS014Title), nameof(Resources.SAS014Description), - nameof(Resources.SAS014MessageFormat)); - - internal static readonly DiagnosticDescriptor Sas015 = "SAS015".GetDescriptor(DiagnosticSeverity.Warning, - AnalyzerConstants.Categories.WebApi, nameof(Resources.SAS015Title), nameof(Resources.SAS015Description), - nameof(Resources.SAS015MessageFormat)); - - internal static readonly DiagnosticDescriptor Sas016 = "SAS016".GetDescriptor(DiagnosticSeverity.Warning, - AnalyzerConstants.Categories.WebApi, nameof(Resources.SAS016Title), nameof(Resources.SAS016Description), - nameof(Resources.SAS016MessageFormat)); - - internal static readonly DiagnosticDescriptor Sas017 = "SAS017".GetDescriptor(DiagnosticSeverity.Warning, - AnalyzerConstants.Categories.WebApi, nameof(Resources.SAS017Title), nameof(Resources.SAS017Description), - nameof(Resources.SAS017MessageFormat)); - - internal static readonly DiagnosticDescriptor Sas018 = "SAS018".GetDescriptor(DiagnosticSeverity.Error, - AnalyzerConstants.Categories.WebApi, nameof(Resources.SAS018Title), nameof(Resources.SAS018Description), - nameof(Resources.SAS018MessageFormat)); - - internal static readonly DiagnosticDescriptor Sas019 = "SAS019".GetDescriptor(DiagnosticSeverity.Warning, - AnalyzerConstants.Categories.WebApi, nameof(Resources.SAS019Title), nameof(Resources.SAS019Description), - nameof(Resources.SAS019MessageFormat)); - - public override ImmutableArray SupportedDiagnostics => - ImmutableArray.Create(Sas010, Sas011, Sas012, Sas013, Sas014, Sas015, Sas016, Sas017, Sas018, Sas019); - - public override void Initialize(AnalysisContext context) - { - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.EnableConcurrentExecution(); - context.RegisterSyntaxNodeAction(AnalyzeClass, SyntaxKind.ClassDeclaration); - } - - private static void AnalyzeClass(SyntaxNodeAnalysisContext context) - { - var methodSyntax = context.Node; - if (methodSyntax is not ClassDeclarationSyntax classDeclarationSyntax) - { - return; - } - - if (context.IsExcludedInNamespace(classDeclarationSyntax, AnalyzerConstants.PlatformNamespaces)) - { - return; - } - - if (classDeclarationSyntax.IsNotType(context)) - { - return; - } - - var allMethods = classDeclarationSyntax.Members.Where(member => member is MethodDeclarationSyntax) - .Cast(); - var operations = new Dictionary(); - foreach (var methodDeclarationSyntax in allMethods) - { - if (methodDeclarationSyntax.IsNotPublicInstanceMethod()) - { - continue; - } - - if (ReturnTypeIsNotCorrect(context, methodDeclarationSyntax, out var returnType)) - { - continue; - } - - if (ParametersAreInCorrect(context, methodDeclarationSyntax, out var requestTypeSyntax)) - { - continue; - } - - if (RouteAttributeIsNotPresent(context, methodDeclarationSyntax, requestTypeSyntax!, - out var routeAttribute)) - { - continue; - } - - var requestType = requestTypeSyntax!.GetBaseOfType(context); - operations.Add(methodDeclarationSyntax, new ServiceOperation(requestType!)); - - var operation = - (Infrastructure.Web.Api.Interfaces.ServiceOperation)routeAttribute!.ConstructorArguments[1].Value!; - if (OperationAndReturnsTypeDontMatch(context, methodDeclarationSyntax, operation, returnType!)) - { - continue; - } - - var access = (AccessType)routeAttribute.ConstructorArguments[2].Value!; - if (AuthorizeAttributePresence(context, requestTypeSyntax!, access)) - { - continue; - } - - var routePath = routeAttribute.ConstructorArguments[0] - .Value?.ToString(); - operations[methodDeclarationSyntax] - .SetRouteSegments(routePath); - } - - RoutesHaveSamePrimaryResource(context, operations); - RequestTypesAreNotDuplicated(context, operations); - } - - private static bool OperationAndReturnsTypeDontMatch(SyntaxNodeAnalysisContext context, - MethodDeclarationSyntax methodDeclarationSyntax, Infrastructure.Web.Api.Interfaces.ServiceOperation operation, - ITypeSymbol returnType) - { - var allowedReturnTypes = AllowableOperationReturnTypes[operation]; - - if (MatchesAllowedTypes(context, returnType, allowedReturnTypes.ToArray())) - { - return false; - } - - context.ReportDiagnostic(Sas016, methodDeclarationSyntax, operation, - allowedReturnTypes.ToArray().Stringify()); - - return true; - } - - private static bool ReturnTypeIsNotCorrect(SyntaxNodeAnalysisContext context, - MethodDeclarationSyntax methodDeclarationSyntax, out ITypeSymbol? returnType) - { - returnType = null; - var symbol = context.SemanticModel.GetDeclaredSymbol(methodDeclarationSyntax); - if (symbol is null) - { - return true; - } - - returnType = symbol.ReturnType; - if (returnType.IsVoid(context)) - { - context.ReportDiagnostic(Sas010, methodDeclarationSyntax, AllowableReturnTypes.Stringify()); - return true; - } - - if (returnType.IsVoidTask(context)) - { - context.ReportDiagnostic(Sas010, methodDeclarationSyntax, AllowableReturnTypes.Stringify()); - return true; - } - - if (!MatchesAllowedTypes(context, returnType, AllowableReturnTypes)) - { - context.ReportDiagnostic(Sas010, methodDeclarationSyntax, AllowableReturnTypes.Stringify()); - return true; - } - - return false; - } - - private static bool RouteAttributeIsNotPresent(SyntaxNodeAnalysisContext context, - MethodDeclarationSyntax methodDeclarationSyntax, ParameterSyntax requestTypeSyntax, - out AttributeData? attribute) - { - var requestTypeSymbol = context.SemanticModel.GetSymbolInfo(requestTypeSyntax.Type!).Symbol!; - attribute = requestTypeSymbol.GetAttributeOfType(context); - if (attribute is null) - { - context.ReportDiagnostic(Sas013, methodDeclarationSyntax); - if (requestTypeSyntax.Type is IdentifierNameSyntax nameSyntax) - { - context.ReportDiagnostic(Sas017, nameSyntax); - } - - return true; - } - - return false; - } - - private static bool AuthorizeAttributePresence(SyntaxNodeAnalysisContext context, ParameterSyntax requestTypeSyntax, - AccessType access) - { - var requestTypeSymbol = context.SemanticModel.GetSymbolInfo(requestTypeSyntax.Type!).Symbol!; - var attribute = requestTypeSymbol.GetAttributeOfType(context); - if (access == AccessType.Anonymous) - { - if (attribute is not null) - { - if (requestTypeSyntax.Type is IdentifierNameSyntax nameSyntax) - { - context.ReportDiagnostic(Sas018, nameSyntax); - } - - return true; - } - } - else - { - if (attribute is null) - { - if (requestTypeSyntax.Type is IdentifierNameSyntax nameSyntax) - { - context.ReportDiagnostic(Sas019, nameSyntax); - } - - return true; - } - } - - return false; - } - - private static bool ParametersAreInCorrect(SyntaxNodeAnalysisContext context, - MethodDeclarationSyntax methodDeclarationSyntax, out ParameterSyntax? requestTypeSyntax) - { - requestTypeSyntax = null; - var parameters = methodDeclarationSyntax.ParameterList.Parameters; - if (parameters.Count is < 1 or > 2) - { - context.ReportDiagnostic(Sas011, methodDeclarationSyntax); - return true; - } - - var firstParam = parameters.First(); - var requestType = firstParam.GetBaseOfType(context); - if (requestType is null) - { - context.ReportDiagnostic(Sas011, methodDeclarationSyntax); - return true; - } - - requestTypeSyntax = firstParam; - - if (parameters.Count == 2) - { - var secondParam = parameters[1]; - if (secondParam.IsNotType(context)) - { - context.ReportDiagnostic(Sas012, methodDeclarationSyntax); - return true; - } - } - - return false; - } - - private static void RequestTypesAreNotDuplicated(SyntaxNodeAnalysisContext context, - Dictionary operations) - { - var duplicateRequestTypes = operations.GroupBy(ops => ops.Value.RequestType.ToDisplayString()) - .Where(grp => grp.Count() > 1); - foreach (var duplicateGroup in duplicateRequestTypes) - { - foreach (var entry in duplicateGroup) - { - context.ReportDiagnostic(Sas015, entry.Key); - } - } - } - - private static void RoutesHaveSamePrimaryResource(SyntaxNodeAnalysisContext context, - Dictionary operations) - { - var primaryResource = string.Empty; - foreach (var operation in operations.Where(ops => ops.Value.RouteSegments.Any())) - { - if (primaryResource.HasNoValue()) - { - primaryResource = operation.Value.RouteSegments.First(); - continue; - } - - if (operation.Value.RouteSegments.First() != primaryResource) - { - context.ReportDiagnostic(Sas014, operation.Key); - } - } - } - - /// - /// Determines whether the is the same as one of the - /// , or one of the as a - /// - private static bool MatchesAllowedTypes(SyntaxNodeAnalysisContext context, ITypeSymbol returnType, - Type[] allowableReturnTypes) - { - var nakedType = returnType; - if (IsGenericTask()) - { - //Task - if (returnType is not INamedTypeSymbol namedTypeSymbol) - { - return false; - } - - nakedType = namedTypeSymbol.TypeArguments.First(); - } - - foreach (var allowedType in allowableReturnTypes) - { - var allowedTypeNaked = context.Compilation.GetTypeByMetadataName(allowedType.FullName!)!; - if (nakedType.OriginalDefinition.IsOfType(allowedTypeNaked)) - { - return true; - } - - var taskType = context.Compilation.GetTypeByMetadataName(typeof(Task<>).FullName!)!; - var returnTypeTasked = - taskType.Construct(context.Compilation.GetTypeByMetadataName(allowedType.FullName!)!); - var allowedTypeTasked = - taskType.Construct(context.Compilation.GetTypeByMetadataName(allowedType.FullName!)!); - if (returnTypeTasked.OriginalDefinition.IsOfType(allowedTypeTasked)) - { - return true; - } - } - - return false; - - bool IsGenericTask() - { - var genericTaskSymbol = context.Compilation.GetTypeByMetadataName(typeof(Task<>).FullName!)!; - return returnType.OriginalDefinition.IsOfType(genericTaskSymbol); - } - } - - private class ServiceOperation - { - public ServiceOperation(ITypeSymbol requestType) - { - RequestType = requestType; - } - - public ITypeSymbol RequestType { get; } - - public IEnumerable RouteSegments { get; private set; } = Enumerable.Empty(); - - public void SetRouteSegments(string? routePath) - { - if (routePath.HasValue()) - { - RouteSegments = routePath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); - } - } - } -} - -internal static class TypeExtensions -{ - public static string Stringify(this Type[] allowableReturnTypes) - { - var taskOfList = new StringBuilder(); - var nakedList = new StringBuilder(); - - var stringifiedTypes = allowableReturnTypes.Select(Stringify).ToList(); - nakedList.AppendJoin(", ", stringifiedTypes); - taskOfList.AppendJoin(", ", stringifiedTypes.Select(s => $"Task<{s}>")); - - return $"{taskOfList}, or {nakedList}"; - } - - private static string Stringify(this Type type) - { - if (!type.IsGenericType) - { - return type.Name; - } - - var arguments = type.GetGenericArguments(); - var builder = new StringBuilder(); - - builder.Append(type.Name.Substring(0, type.Name.LastIndexOf('`'))); - builder.Append("<"); - builder.AppendJoin(", ", arguments.Select(arg => arg.Name)); - builder.Append(">"); - - return builder.ToString(); - } -} \ No newline at end of file diff --git a/src/Tools.Analyzers.Platform.UnitTests/MissingDocsAnalyzerSpec.cs b/src/Tools.Analyzers.Platform.UnitTests/MissingDocsAnalyzerSpec.cs index 6647f82a..42f7d651 100644 --- a/src/Tools.Analyzers.Platform.UnitTests/MissingDocsAnalyzerSpec.cs +++ b/src/Tools.Analyzers.Platform.UnitTests/MissingDocsAnalyzerSpec.cs @@ -9,7 +9,7 @@ namespace Tools.Analyzers.Platform.UnitTests; public class MissingDocsAnalyzerSpec { [Trait("Category", "Unit")] - public class GivenRuleSas001 + public class GivenRule001 { [Fact] public async Task WhenInJetbrainsAnnotationsNamespace_ThenNoAlert() @@ -40,7 +40,7 @@ public async Task WhenPublicDelegate_ThenAlerts() { const string input = @"public delegate void ADelegate();"; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 1, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 1, 22, "ADelegate"); } @@ -49,7 +49,7 @@ public async Task WhenInternalDelegate_ThenAlerts() { const string input = @"internal delegate void ADelegate();"; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 1, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 1, 24, "ADelegate"); } @@ -61,7 +61,7 @@ public interface AnInterface { }"; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 2, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 2, 18, "AnInterface"); } @@ -73,7 +73,7 @@ internal interface AnInterface { }"; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 2, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 2, 20, "AnInterface"); } @@ -85,7 +85,7 @@ public enum AnEnum { }"; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 2, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 2, 13, "AnEnum"); } @@ -97,7 +97,7 @@ internal enum AnEnum { }"; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 2, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 2, 15, "AnEnum"); } @@ -109,7 +109,7 @@ public struct AStruct { }"; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 2, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 2, 15, "AStruct"); } @@ -121,7 +121,7 @@ internal struct AStruct { }"; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 2, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 2, 17, "AStruct"); } @@ -133,7 +133,7 @@ public readonly struct AStruct { }"; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 2, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 2, 24, "AStruct"); } @@ -145,7 +145,7 @@ internal readonly struct AStruct { }"; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 2, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 2, 26, "AStruct"); } @@ -157,7 +157,7 @@ public record ARecord() { }"; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 2, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 2, 15, "ARecord"); } @@ -169,7 +169,7 @@ internal record ARecord { }"; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 2, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 2, 17, "ARecord"); } @@ -245,7 +245,7 @@ public class AClass2 } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 7, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 7, 18, "AClass2"); } @@ -276,7 +276,7 @@ public class AClass } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 2, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 2, 14, "AClass"); } @@ -289,7 +289,7 @@ public partial class AClass } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 2, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 2, 22, "AClass"); } @@ -306,8 +306,8 @@ public partial class AClass "; await Verify.DiagnosticExists(input, - (MissingDocsAnalyzer.Sas001, 2, 22, "AClass"), - (MissingDocsAnalyzer.Sas001, 5, 22, "AClass")); + (MissingDocsAnalyzer.Rule001, 2, 22, "AClass"), + (MissingDocsAnalyzer.Rule001, 5, 22, "AClass")); } [Fact] @@ -319,7 +319,7 @@ internal class AClass } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 2, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 2, 16, "AClass"); } @@ -332,7 +332,7 @@ public class AClass } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 2, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 2, 14, "AClass"); } @@ -346,7 +346,7 @@ public class AClass } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 3, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 3, 14, "AClass"); } @@ -362,7 +362,7 @@ public class AClass } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 5, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 5, 14, "AClass"); } @@ -378,7 +378,7 @@ public class AClass } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 5, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 5, 14, "AClass"); } @@ -394,7 +394,7 @@ public class AClass } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 5, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 5, 14, "AClass"); } @@ -410,7 +410,7 @@ public class AClass } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas001, input, 5, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule001, input, 5, 14, "AClass"); } @@ -477,7 +477,7 @@ public partial class AClass } [Trait("Category", "Unit")] - public class GivenRuleSas002 + public class GivenRule002 { [Fact] public async Task WhenInJetbrainsAnnotationsNamespace_ThenNoAlert() @@ -525,7 +525,7 @@ public static void AMethod(){} } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas002, input, 4, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule002, input, 4, 24, "AMethod"); } @@ -555,7 +555,7 @@ internal static void AMethod(){} } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas002, input, 4, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule002, input, 4, 26, "AMethod"); } @@ -569,7 +569,7 @@ public static void AMethod(string value){} } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas002, input, 4, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule002, input, 4, 24, "AMethod"); } @@ -583,7 +583,7 @@ internal static void AMethod(string value){} } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas002, input, 4, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule002, input, 4, 26, "AMethod"); } @@ -597,7 +597,7 @@ internal static void AMethod(this string value){} } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas002, input, 4, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule002, input, 4, 26, "AMethod"); } @@ -624,7 +624,7 @@ public static void AMethod(this string value){} } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas002, input, 4, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule002, input, 4, 24, "AMethod"); } @@ -641,7 +641,7 @@ public static void AMethod(this string value){} } "; - await Verify.DiagnosticExists(MissingDocsAnalyzer.Sas002, input, 7, + await Verify.DiagnosticExists(MissingDocsAnalyzer.Rule002, input, 7, 24, "AMethod"); } diff --git a/src/Tools.Analyzers.Platform/AnalyzerReleases.Shipped.md b/src/Tools.Analyzers.Platform/AnalyzerReleases.Shipped.md index ddeecc55..ad02b4bb 100644 --- a/src/Tools.Analyzers.Platform/AnalyzerReleases.Shipped.md +++ b/src/Tools.Analyzers.Platform/AnalyzerReleases.Shipped.md @@ -2,7 +2,7 @@ ### New Rules - Rule ID | Category | Severity | Notes ----------|-----------------------|----------|------------------------------------------------------------------------------------------------- - SAS001 | SaaStackDocumentation | Warning | All public/internal classes, structs, records, interfaces, delegates and enums. - SAS002 | SaaStackDocumentation | Warning | All public/internal static methods and all public/internal extension methods (in public types). \ No newline at end of file + Rule ID | Category | Severity | Notes +------------|-----------------------|----------|------------------------------------------------------------------------------------------------- + SAASDOC001 | SaaStackDocumentation | Warning | All public/internal classes, structs, records, interfaces, delegates and enums. + SAASDOC002 | SaaStackDocumentation | Warning | All public/internal static methods and all public/internal extension methods (in public types). \ No newline at end of file diff --git a/src/Tools.Analyzers.Platform/MissingDocsAnalyzer.cs b/src/Tools.Analyzers.Platform/MissingDocsAnalyzer.cs index f1a358e9..270bbc18 100644 --- a/src/Tools.Analyzers.Platform/MissingDocsAnalyzer.cs +++ b/src/Tools.Analyzers.Platform/MissingDocsAnalyzer.cs @@ -11,8 +11,8 @@ namespace Tools.Analyzers.Platform; /// /// An analyzer to find public declarations that are missing a documentation <summary> node. -/// SAS001: All public/internal classes, structs, records, interfaces, delegates and enums -/// SAS002: All public/internal static methods and all public/internal extension methods (in public types) +/// SAASDOC001: All public/internal classes, structs, records, interfaces, delegates and enums +/// SAASDOC002: All public/internal static methods and all public/internal extension methods (in public types) /// Note: Document declarations are only enforced for Platform projects. /// [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -21,16 +21,18 @@ public class MissingDocsAnalyzer : DiagnosticAnalyzer private const string InheritDocXmlElementName = "inheritdoc"; private const string SummaryXmlElementName = "summary"; - internal static readonly DiagnosticDescriptor Sas001 = "SAS001".GetDescriptor(DiagnosticSeverity.Warning, - AnalyzerConstants.Categories.Documentation, nameof(Resources.SAS001Title), nameof(Resources.SAS001Description), - nameof(Resources.SAS001MessageFormat)); + internal static readonly DiagnosticDescriptor Rule001 = "SAASDOC001".GetDescriptor(DiagnosticSeverity.Warning, + AnalyzerConstants.Categories.Documentation, nameof(Resources.SAASDOC001Title), + nameof(Resources.SAASDOC001Description), + nameof(Resources.SAASDOC001MessageFormat)); - internal static readonly DiagnosticDescriptor Sas002 = "SAS002".GetDescriptor(DiagnosticSeverity.Warning, - AnalyzerConstants.Categories.Documentation, nameof(Resources.SAS002Title), nameof(Resources.SAS002Description), - nameof(Resources.SAS002MessageFormat)); + internal static readonly DiagnosticDescriptor Rule002 = "SAASDOC002".GetDescriptor(DiagnosticSeverity.Warning, + AnalyzerConstants.Categories.Documentation, nameof(Resources.SAASDOC002Title), + nameof(Resources.SAASDOC002Description), + nameof(Resources.SAASDOC002MessageFormat)); public override ImmutableArray SupportedDiagnostics => - ImmutableArray.Create(Sas001, Sas002); + ImmutableArray.Create(Rule001, Rule002); public override void Initialize(AnalysisContext context) { @@ -68,13 +70,13 @@ private static void AnalyzeType(SyntaxNodeAnalysisContext context) var docs = memberDeclarationSyntax.GetDocumentationCommentTriviaSyntax(context); if (docs is null) { - context.ReportDiagnostic(Sas001, memberDeclarationSyntax); + context.ReportDiagnostic(Rule001, memberDeclarationSyntax); return; } if (!docs.IsLanguageForCSharp()) { - context.ReportDiagnostic(Sas001, memberDeclarationSyntax); + context.ReportDiagnostic(Rule001, memberDeclarationSyntax); return; } @@ -88,13 +90,13 @@ private static void AnalyzeType(SyntaxNodeAnalysisContext context) var summary = xmlContent.GetFirstXmlElement(SummaryXmlElementName); if (summary is null) { - context.ReportDiagnostic(Sas001, memberDeclarationSyntax); + context.ReportDiagnostic(Rule001, memberDeclarationSyntax); return; } if (summary.IsEmptyNode()) { - context.ReportDiagnostic(Sas001, memberDeclarationSyntax); + context.ReportDiagnostic(Rule001, memberDeclarationSyntax); } } @@ -129,13 +131,13 @@ private static void AnalyzeMethod(SyntaxNodeAnalysisContext context) var docs = methodDeclarationSyntax.GetDocumentationCommentTriviaSyntax(context); if (docs is null) { - context.ReportDiagnostic(Sas002, methodDeclarationSyntax); + context.ReportDiagnostic(Rule002, methodDeclarationSyntax); return; } if (!docs.IsLanguageForCSharp()) { - context.ReportDiagnostic(Sas002, methodDeclarationSyntax); + context.ReportDiagnostic(Rule002, methodDeclarationSyntax); return; } @@ -149,13 +151,13 @@ private static void AnalyzeMethod(SyntaxNodeAnalysisContext context) var summary = xmlContent.GetFirstXmlElement(SummaryXmlElementName); if (summary is null) { - context.ReportDiagnostic(Sas002, methodDeclarationSyntax); + context.ReportDiagnostic(Rule002, methodDeclarationSyntax); return; } if (summary.IsEmptyNode()) { - context.ReportDiagnostic(Sas002, methodDeclarationSyntax); + context.ReportDiagnostic(Rule002, methodDeclarationSyntax); } } } \ No newline at end of file diff --git a/src/Tools.Analyzers.Platform/Resources.Designer.cs b/src/Tools.Analyzers.Platform/Resources.Designer.cs index fa95457d..b0c10188 100644 --- a/src/Tools.Analyzers.Platform/Resources.Designer.cs +++ b/src/Tools.Analyzers.Platform/Resources.Designer.cs @@ -62,54 +62,54 @@ internal Resources() { /// /// Looks up a localized string similar to This type should have a <summary> to describe what it designed to do.. /// - internal static string SAS001Description { + internal static string SAASDOC001Description { get { - return ResourceManager.GetString("SAS001Description", resourceCulture); + return ResourceManager.GetString("SAASDOC001Description", resourceCulture); } } /// /// Looks up a localized string similar to Type '{0}' requires a documentation <summary/>. /// - internal static string SAS001MessageFormat { + internal static string SAASDOC001MessageFormat { get { - return ResourceManager.GetString("SAS001MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASDOC001MessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Missing documentation. /// - internal static string SAS001Title { + internal static string SAASDOC001Title { get { - return ResourceManager.GetString("SAS001Title", resourceCulture); + return ResourceManager.GetString("SAASDOC001Title", resourceCulture); } } /// /// Looks up a localized string similar to This method should have a <summary> to describe what it designed to do.. /// - internal static string SAS002Description { + internal static string SAASDOC002Description { get { - return ResourceManager.GetString("SAS002Description", resourceCulture); + return ResourceManager.GetString("SAASDOC002Description", resourceCulture); } } /// /// Looks up a localized string similar to Extension method '{0}' requires a documentation <summary/>. /// - internal static string SAS002MessageFormat { + internal static string SAASDOC002MessageFormat { get { - return ResourceManager.GetString("SAS002MessageFormat", resourceCulture); + return ResourceManager.GetString("SAASDOC002MessageFormat", resourceCulture); } } /// /// Looks up a localized string similar to Missing documentation. /// - internal static string SAS002Title { + internal static string SAASDOC002Title { get { - return ResourceManager.GetString("SAS002Title", resourceCulture); + return ResourceManager.GetString("SAASDOC002Title", resourceCulture); } } } diff --git a/src/Tools.Analyzers.Platform/Resources.resx b/src/Tools.Analyzers.Platform/Resources.resx index 23cac6ae..f3ad9124 100644 --- a/src/Tools.Analyzers.Platform/Resources.resx +++ b/src/Tools.Analyzers.Platform/Resources.resx @@ -25,22 +25,22 @@ - + This type should have a <summary> to describe what it designed to do. - + Type '{0}' requires a documentation <summary/> - + Missing documentation - + This method should have a <summary> to describe what it designed to do. - + Extension method '{0}' requires a documentation <summary/> - + Missing documentation \ No newline at end of file