diff --git a/src/MassTransit.Analyzers/MessageContractAnalyzer.cs b/src/MassTransit.Analyzers/MessageContractAnalyzer.cs index acf83246e86..7d6183042c4 100644 --- a/src/MassTransit.Analyzers/MessageContractAnalyzer.cs +++ b/src/MassTransit.Analyzers/MessageContractAnalyzer.cs @@ -162,7 +162,7 @@ static bool TypesAreStructurallyCompatible(TypeConversionHelper typeConverterHel foreach (var inputProperty in inputProperties) { - var contractProperty = contractProperties.FirstOrDefault(m => m.Name == inputProperty.Name); + var contractProperty = contractProperties.FirstOrDefault(m => m.Name.Equals(inputProperty.Name, StringComparison.OrdinalIgnoreCase)); var propertyPath = Append(path, inputProperty.Name); @@ -210,8 +210,7 @@ static bool PropertyTypesAreStructurallyCompatible(TypeConversionHelper typeConv { if (contractPropertyType.TypeKind.IsClassOrInterface()) { - if (!TypesAreStructurallyCompatible(typeConverterHelper, contractPropertyType, - inputPropertyType, path, incompatibleProperties)) + if (!TypesAreStructurallyCompatible(typeConverterHelper, contractPropertyType, inputPropertyType, path, incompatibleProperties)) return false; } else @@ -309,8 +308,7 @@ static bool KeyValueTypesAreStructurallyCompatible(TypeConversionHelper typeConv if (contractValueType.TypeKind.IsClassOrInterface()) { - if (!TypesAreStructurallyCompatible(typeConverterHelper, contractValueType, - inputValueType, path, incompatibleProperties)) + if (!TypesAreStructurallyCompatible(typeConverterHelper, contractValueType, inputValueType, path, incompatibleProperties)) return false; } else @@ -357,7 +355,7 @@ static bool HasMissingProperties(ITypeSymbol inputType, ITypeSymbol contractType foreach (var contractProperty in contractProperties) { - var inputProperty = inputProperties.FirstOrDefault(m => m.Name == contractProperty.Name); + var inputProperty = inputProperties.FirstOrDefault(m => m.Name.Equals(contractProperty.Name, StringComparison.OrdinalIgnoreCase)); var propertyPath = Append(path, contractProperty.Name); diff --git a/src/MassTransit.Analyzers/MessageContractCodeFixProvider.cs b/src/MassTransit.Analyzers/MessageContractCodeFixProvider.cs index eb83b0c300e..52379e5363e 100644 --- a/src/MassTransit.Analyzers/MessageContractCodeFixProvider.cs +++ b/src/MassTransit.Analyzers/MessageContractCodeFixProvider.cs @@ -1,5 +1,6 @@ namespace MassTransit.Analyzers { + using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; @@ -53,9 +54,9 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) } static async Task AddMissingProperties(Document document, - AnonymousObjectCreationExpressionSyntax anonymousObject, - string fullType, - CancellationToken cancellationToken) + AnonymousObjectCreationExpressionSyntax anonymousObject, + string fullType, + CancellationToken cancellationToken) { var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); @@ -92,7 +93,7 @@ static async Task FindAnonymousTypesWithMessageContractsInTree(IDictionary p.Name == name); + var contractProperty = contractProperties.FirstOrDefault(m => m.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); if (contractProperty != null) { @@ -122,9 +123,9 @@ await FindAnonymousTypesWithMessageContractsInTree(dictionary, anonymousObjectPr .ConfigureAwait(false); } else if (initializer.Expression is InvocationExpressionSyntax invocationExpressionSyntax - && semanticModel.GetSymbolInfo(invocationExpressionSyntax).Symbol is IMethodSymbol method - && method.ReturnType.IsList(out var methodReturnTypeArgument) - && methodReturnTypeArgument.IsAnonymousType) + && semanticModel.GetSymbolInfo(invocationExpressionSyntax).Symbol is IMethodSymbol method + && method.ReturnType.IsList(out var methodReturnTypeArgument) + && methodReturnTypeArgument.IsAnonymousType) { if (contractProperty.Type.IsImmutableArray(out var contractElementType) || contractProperty.Type.IsList(out contractElementType) || @@ -198,7 +199,8 @@ static SyntaxNode AddMissingProperties(SyntaxNode root, AnonymousObjectCreationE var propertiesToAdd = new List(); foreach (var messageContractProperty in contractProperties) { - var initializer = anonymousObject.Initializers.FirstOrDefault(i => GetName(i) == messageContractProperty.Name); + var initializer = anonymousObject.Initializers + .FirstOrDefault(i => GetName(i).Equals(messageContractProperty.Name, StringComparison.OrdinalIgnoreCase)); if (initializer == null) { var path = Enumerable.Empty(); @@ -237,8 +239,8 @@ static AnonymousObjectMemberDeclaratorSyntax CreateProperty(IPropertySymbol cont ExpressionSyntax expression; if (contractProperty.Type.IsImmutableArray(out var contractElementType) || - contractProperty.Type.IsList(out contractElementType) || - contractProperty.Type.IsArray(out contractElementType)) + contractProperty.Type.IsList(out contractElementType) || + contractProperty.Type.IsArray(out contractElementType)) { if (path.Contains(contractElementType, SymbolEqualityComparer.Default)) expression = CreateEmptyArray(contractElementType); @@ -265,16 +267,16 @@ static AnonymousObjectMemberDeclaratorSyntax CreateProperty(IPropertySymbol cont static ExpressionSyntax CreateEmptyArray(ITypeSymbol type) { return SyntaxFactory.InvocationExpression( - SyntaxFactory.MemberAccessExpression( - SyntaxKind.SimpleMemberAccessExpression, - SyntaxFactory.IdentifierName("Array"), - SyntaxFactory.GenericName( - SyntaxFactory.Identifier("Empty")) - .WithTypeArgumentList( - SyntaxFactory.TypeArgumentList( - SyntaxFactory.SingletonSeparatedList( - SyntaxFactory.IdentifierName(type.Name)))))) - .NormalizeWhitespace(); + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName("Array"), + SyntaxFactory.GenericName( + SyntaxFactory.Identifier("Empty")) + .WithTypeArgumentList( + SyntaxFactory.TypeArgumentList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.IdentifierName(type.Name)))))) + .NormalizeWhitespace(); } static ImplicitArrayCreationExpressionSyntax CreateImplicitArray(ITypeSymbol type, IEnumerable path) diff --git a/tests/MassTransit.Analyzers.Tests/MessageContractAnalyzerUnitTests.cs b/tests/MassTransit.Analyzers.Tests/MessageContractAnalyzerUnitTests.cs index dfb1accb06e..9975a069c15 100644 --- a/tests/MassTransit.Analyzers.Tests/MessageContractAnalyzerUnitTests.cs +++ b/tests/MassTransit.Analyzers.Tests/MessageContractAnalyzerUnitTests.cs @@ -828,6 +828,76 @@ await bus.Publish(new VerifyCSharpFix(test, fixtest); } + [Test] + public void WhenMessageContractHasNullableAreStructurallyCompatibleAndMissingCaseNullableProperty_ShouldHaveDiagnosticAndCodeFix() + { + var test = Usings + @" +namespace ConsoleApplication1 +{ + public interface OrderSubmitted + { + Guid Id { get; } + int Quantity { get; } + decimal? Price { get; } + } + + class Program + { + static async Task Main() + { + var bus = Bus.Factory.CreateUsingInMemory(cfg => { }); + + await bus.Publish(new + { + Id = NewId.NextGuid(), + quantity = 10 + }); + } + } +} +"; + var expected = new DiagnosticResult + { + Id = "MCA0003", + Message = + "Anonymous type is missing properties that are in the message contract 'OrderSubmitted'. The following properties are missing: Price.", + Severity = DiagnosticSeverity.Info, + Locations = + new[] { new DiagnosticResultLocation("Test0.cs", 23, 47) } + }; + + VerifyCSharpDiagnostic(test, expected); + + var fixtest = Usings + @" +namespace ConsoleApplication1 +{ + public interface OrderSubmitted + { + Guid Id { get; } + int Quantity { get; } + decimal? Price { get; } + } + + class Program + { + static async Task Main() + { + var bus = Bus.Factory.CreateUsingInMemory(cfg => { }); + + await bus.Publish(new + { + Id = NewId.NextGuid(), + quantity = 10 +, + Price = default(decimal?) + }); + } + } +} +"; + VerifyCSharpFix(test, fixtest); + } + [Test] public void WhenPublishTypesAreStructurallyCompatibleAndMissingProperty_ShouldHaveDiagnostic() {