diff --git a/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/ConditionalAccess/Diagnostics.xml b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/ConditionalAccess/Diagnostics.xml new file mode 100644 index 0000000..328f7a0 --- /dev/null +++ b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/ConditionalAccess/Diagnostics.xml @@ -0,0 +1,6 @@ + + + + Test0.cs: (8, 32-57) + + diff --git a/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/ConditionalAccess/Result.cs b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/ConditionalAccess/Result.cs new file mode 100644 index 0000000..cdefe23 --- /dev/null +++ b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/ConditionalAccess/Result.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Linq; + +public class Bob +{ + public int[] Method(Bob other) + { + var items = other.GetItems()?.Where(x => x > 0).Append(42).ToArray(); + return items; + } + + public IEnumerable GetItems() => new[] { 1, 2, 3 }; +} diff --git a/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/ConditionalAccess/Source.cs b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/ConditionalAccess/Source.cs new file mode 100644 index 0000000..52f6dde --- /dev/null +++ b/src/WTG.Analyzers.Test/TestData/LinqEnumerableAnalyzer/ConditionalAccess/Source.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Linq; + +public class Bob +{ + public int[] Method(Bob other) + { + var items = other.GetItems()?.Where(x => x > 0).Concat(new[] { 42 }).ToArray(); + return items; + } + + public IEnumerable GetItems() => new[] { 1, 2, 3 }; +} diff --git a/src/WTG.Analyzers/Analyzers/LinqEnumerable/LinqEnumerableCodeFixProvider.cs b/src/WTG.Analyzers/Analyzers/LinqEnumerable/LinqEnumerableCodeFixProvider.cs index 18ea77f..ab12fe0 100644 --- a/src/WTG.Analyzers/Analyzers/LinqEnumerable/LinqEnumerableCodeFixProvider.cs +++ b/src/WTG.Analyzers/Analyzers/LinqEnumerable/LinqEnumerableCodeFixProvider.cs @@ -121,9 +121,7 @@ public static SyntaxNode FixConcatWithAppendMethod(MemberAccessExpressionSyntax return InvocationExpression( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, - ParenthesizedExpression(m.Expression.WithoutTrivia()) - .WithTriviaFrom(m.Expression) - .WithAdditionalAnnotations(Simplifier.Annotation), + WrapExpressionIfNeeded(m.Expression), m.OperatorToken, IdentifierName(nameof(Enumerable.Append)) .WithTriviaFrom(m.Name))) @@ -146,9 +144,7 @@ public static SyntaxNode FixConcatWithAppendMethod(MemberAccessExpressionSyntax { case 1: listOfArgumentsAndSeparators.Add(Argument(LinqEnumerableUtils.GetFirstValue(m.Expression.TryGetExpressionFromParenthesizedExpression())!)); - member = ParenthesizedExpression(invocation.ArgumentList.Arguments[0].Expression.WithoutTrivia()) - .WithTriviaFrom(m.Expression) - .WithAdditionalAnnotations(Simplifier.Annotation); + member = WrapExpressionIfNeeded(invocation.ArgumentList.Arguments[0].Expression.WithTriviaFrom(m.Expression)); break; case 2: listOfArgumentsAndSeparators.Add(invocation.ArgumentList.Arguments[1]); @@ -213,5 +209,22 @@ public static SyntaxNode FixConcatWithNewCollection(MemberAccessExpressionSyntax .WithTriviaFrom(invocation) .WithAdditionalAnnotations(Simplifier.Annotation); } + + static ExpressionSyntax WrapExpressionIfNeeded(ExpressionSyntax expression) + { + // Invocation expressions never need parenthesization as they already have + // clear binding. Wrapping them can cause issues when the expression is part + // of a conditional access chain (?.), as it disconnects MemberBindingExpressions + // from their ConditionalAccessExpression, causing Roslyn's speculative semantic + // model to crash with a NullReferenceException. + if (expression.IsKind(SyntaxKind.InvocationExpression)) + { + return expression; + } + + return ParenthesizedExpression(expression.WithoutTrivia()) + .WithTriviaFrom(expression) + .WithAdditionalAnnotations(Simplifier.Annotation); + } } }