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);
+ }
}
}