Skip to content

Commit

Permalink
Merge pull request #3263 from sharwell/parenthesized-with
Browse files Browse the repository at this point in the history
Fix SA1119 handling of 'with' expressions
  • Loading branch information
sharwell authored Dec 5, 2020
2 parents b95f68a + 9715fc0 commit 9b285fc
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,153 @@

namespace StyleCop.Analyzers.Test.CSharp9.MaintainabilityRules
{
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Testing;
using StyleCop.Analyzers.Test.CSharp8.MaintainabilityRules;
using Xunit;
using static StyleCop.Analyzers.Test.Verifiers.StyleCopCodeFixVerifier<
StyleCop.Analyzers.MaintainabilityRules.SA1119StatementMustNotUseUnnecessaryParenthesis,
StyleCop.Analyzers.MaintainabilityRules.SA1119CodeFixProvider>;

public class SA1119CSharp9UnitTests : SA1119CSharp8UnitTests
{
/// <summary>
/// Verifies that a type cast followed by a <c>with</c> expression is handled correctly.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
[WorkItem(3239, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3239")]
public async Task TestTypeCastFollowedByWithExpressionIsHandledCorrectlyAsync()
{
const string testCode = @"
record Foo(int Value)
{
public object TestMethod(Foo n, int a)
{
return (object)(n with { Value = a });
}
}
";

await new CSharpTest(LanguageVersion.CSharp9)
{
ReferenceAssemblies = ReferenceAssemblies.Net.Net50,
TestCode = testCode,
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
}

/// <summary>
/// Verifies that a type cast followed by a <c>with</c> expression with unnecessary parentheses is handled
/// correctly.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
[WorkItem(3239, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3239")]
public async Task TestTypeCastFollowedByWithExpressionWithUnnecessaryParenthesesIsHandledCorrectlyAsync()
{
const string testCode = @"
record Foo(int Value)
{
public object TestMethod(Foo n, int a)
{
return (object){|#0:{|#1:(|}(n with { Value = a }){|#2:)|}|};
}
}
";

const string fixedCode = @"
record Foo(int Value)
{
public object TestMethod(Foo n, int a)
{
return (object)(n with { Value = a });
}
}
";

await new CSharpTest(LanguageVersion.CSharp9)
{
ReferenceAssemblies = ReferenceAssemblies.Net.Net50,
TestCode = testCode,
ExpectedDiagnostics =
{
Diagnostic(DiagnosticId).WithLocation(0),
Diagnostic(ParenthesesDiagnosticId).WithLocation(1),
Diagnostic(ParenthesesDiagnosticId).WithLocation(2),
},
FixedCode = fixedCode,
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
}

/// <summary>
/// Verifies that a <c>with</c> expression with unnecessary parentheses is handled correcly.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
[WorkItem(3239, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3239")]
public async Task TestWithExpressionWithUnnecessaryParenthesesAsync()
{
const string testCode = @"
record Foo(int Value)
{
public void TestMethod(Foo n, int a)
{
var test = {|#0:{|#1:(|}n with { Value = a }{|#2:)|}|};
}
}
";

const string fixedCode = @"
record Foo(int Value)
{
public void TestMethod(Foo n, int a)
{
var test = n with { Value = a };
}
}
";

await new CSharpTest(LanguageVersion.CSharp9)
{
ReferenceAssemblies = ReferenceAssemblies.Net.Net50,
TestCode = testCode,
ExpectedDiagnostics =
{
Diagnostic(DiagnosticId).WithLocation(0),
Diagnostic(ParenthesesDiagnosticId).WithLocation(1),
Diagnostic(ParenthesesDiagnosticId).WithLocation(2),
},
FixedCode = fixedCode,
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
}

[Theory]
[InlineData(".ToString()")]
[InlineData("?.ToString()")]
[InlineData("[0]")]
[InlineData("?[0]")]
[WorkItem(3239, "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3239")]
public async Task TestWithExpressionFollowedByDereferenceAsync(string operation)
{
string testCode = $@"
record Foo(int Value)
{{
public object this[int index] => null;
public object TestMethod(Foo n, int a)
{{
return (n with {{ Value = a }}){operation};
}}
}}
";

await new CSharpTest(LanguageVersion.CSharp9)
{
ReferenceAssemblies = ReferenceAssemblies.Net.Net50,
TestCode = testCode,
}.RunAsync(CancellationToken.None).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ internal static class SyntaxKindEx
public const SyntaxKind NullableDirectiveTrivia = (SyntaxKind)9055;
public const SyntaxKind FunctionPointerType = (SyntaxKind)9056;
public const SyntaxKind FunctionPointerParameter = (SyntaxKind)9057;
public const SyntaxKind WithExpression = (SyntaxKind)9061;
public const SyntaxKind WithInitializerExpression = (SyntaxKind)9062;
public const SyntaxKind RecordDeclaration = (SyntaxKind)9063;
public const SyntaxKind FunctionPointerUnmanagedCallingConventionList = (SyntaxKind)9066;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,12 @@ private static void HandleParenthesizedExpression(SyntaxNodeAnalysisContext cont
return;
}

if (IsSwitchExpressionPrecededByTypeCast(node))
if (IsSwitchOrWithExpressionPrecededByTypeCast(node))
{
return;
}

if (IsSwitchExpressionExpressionOfMemberAccess(node))
if (IsSwitchOrWithExpressionExpressionOfMemberAccess(node))
{
return;
}
Expand Down Expand Up @@ -216,9 +216,10 @@ private static bool IsConditionalAccessInInterpolation(ExpressionSyntax node)
return false;
}

private static bool IsSwitchExpressionPrecededByTypeCast(ParenthesizedExpressionSyntax node)
private static bool IsSwitchOrWithExpressionPrecededByTypeCast(ParenthesizedExpressionSyntax node)
{
if (!node.Expression.IsKind(SyntaxKindEx.SwitchExpression))
if (!node.Expression.IsKind(SyntaxKindEx.SwitchExpression)
&& !node.Expression.IsKind(SyntaxKindEx.WithExpression))
{
return false;
}
Expand All @@ -233,9 +234,10 @@ private static bool IsSwitchExpressionPrecededByTypeCast(ParenthesizedExpression
return previousToken.IsKind(SyntaxKind.CloseParenToken) && previousToken.Parent.IsKind(SyntaxKind.CastExpression);
}

private static bool IsSwitchExpressionExpressionOfMemberAccess(ParenthesizedExpressionSyntax node)
private static bool IsSwitchOrWithExpressionExpressionOfMemberAccess(ParenthesizedExpressionSyntax node)
{
if (!node.Expression.IsKind(SyntaxKindEx.SwitchExpression))
if (!node.Expression.IsKind(SyntaxKindEx.SwitchExpression)
&& !node.Expression.IsKind(SyntaxKindEx.WithExpression))
{
return false;
}
Expand Down

0 comments on commit 9b285fc

Please sign in to comment.