diff --git a/ChangeLog.md b/ChangeLog.md index ed6c4c1a51..0460452d85 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -27,12 +27,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix ([RCS1206](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1206.md)) ([#1049](https://github.com/JosefPihrt/Roslynator/pull/1049)). - Prevent possible recursion in ([RCS1235](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1235.md)) ([#1054](https://github.com/JosefPihrt/Roslynator/pull/1054)). - Fix ([RCS1223](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1223.md)) ([#1051](https://github.com/JosefPihrt/Roslynator/pull/1051)). -- Do not remove braces in the cases where there are overlapping local variables. ([RCS1031](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1013.md), [RCS1211](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1211.md), [RCS1208](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1208.md)) ([#1039](https://github.com/JosefPihrt/Roslynator/pull/1039)). +- Do not remove braces in the cases where there are overlapping local variables. ([RCS1031](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1013.md), [RCS1211](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1211.md), [RCS1208](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1208.md)) ([#1039](https://github.com/JosefPihrt/Roslynator/pull/1039),[#1058](https://github.com/JosefPihrt/Roslynator/pull/1058)). - [CLI] Analyze command does not create the XML output file and returns incorrect exit code when only compiler diagnostics are reported ([#1056](https://github.com/JosefPihrt/Roslynator/pull/1056) by @PeterKaszab). - [CLI] Fix exit code when multiple projects are processed ([#1061](https://github.com/JosefPihrt/Roslynator/pull/1061) by @PeterKaszab). - Fix code fix for CS0164 ([#1031](https://github.com/JosefPihrt/Roslynator/pull/1031)). - ## [4.2.0] - 2022-11-27 ### Added diff --git a/src/Analyzers/CSharp/Analysis/RemoveUnnecessaryBracesInSwitchSectionAnalyzer.cs b/src/Analyzers/CSharp/Analysis/RemoveUnnecessaryBracesInSwitchSectionAnalyzer.cs index 59e4ae08cf..8f686f3d6d 100644 --- a/src/Analyzers/CSharp/Analysis/RemoveUnnecessaryBracesInSwitchSectionAnalyzer.cs +++ b/src/Analyzers/CSharp/Analysis/RemoveUnnecessaryBracesInSwitchSectionAnalyzer.cs @@ -121,11 +121,20 @@ private static bool LocallyDeclaredVariablesOverlapWithAnyOtherSwitchSections(Sw foreach (var otherSection in switchStatement.Sections) { - // If the other section is not a block then we do not need to check as if there were overlapping variables then there would already be a error. - if (otherSection.Statements.SingleOrDefault(shouldThrow: false) is not BlockSyntax otherBlock) + if (otherSection.Span.Contains(switchBlock.Span)) continue; - if (otherBlock.Span == switchBlock.Span) + foreach (var label in otherSection.Labels) + { + if (label is not CasePatternSwitchLabelSyntax casePatternSwitchLabelSyntax) + continue; + + if (PattenMatchingVariableDeclarationHelper.GetVariablesDeclared(casePatternSwitchLabelSyntax.Pattern).Intersect(sectionDeclaredVariablesNames).Any()) + return true; + } + + // If the other section is not a block then we do not need to check as if there were overlapping variables then there would already be a error. + if (otherSection.Statements.SingleOrDefault(shouldThrow: false) is not BlockSyntax otherBlock) continue; foreach (var v in semanticModel.AnalyzeDataFlow(otherBlock)!.VariablesDeclared) @@ -137,5 +146,4 @@ private static bool LocallyDeclaredVariablesOverlapWithAnyOtherSwitchSections(Sw return false; } - } diff --git a/src/CSharp/PattenMatchingVariableDeclarationHelper.cs b/src/CSharp/PattenMatchingVariableDeclarationHelper.cs new file mode 100644 index 0000000000..47fdfeb1c5 --- /dev/null +++ b/src/CSharp/PattenMatchingVariableDeclarationHelper.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Roslynator.CSharp.Analysis; + +public static class PattenMatchingVariableDeclarationHelper +{ + public static IEnumerable GetVariablesDeclared(PatternSyntax patternSyntax) + { + switch (patternSyntax) + { + case DeclarationPatternSyntax { Designation: var variableDesignationSyntax}: + return GetVariablesDeclared(variableDesignationSyntax); + case RecursivePatternSyntax { PositionalPatternClause: var positionalPatternClause, PropertyPatternClause: var propertyPatternClause, Designation: var designation }: + var designationVars = designation != null ? GetVariablesDeclared(designation):Array.Empty(); + var propertyVars = propertyPatternClause?.Subpatterns.SelectMany(p=>GetVariablesDeclared(p.Pattern)) ?? Array.Empty(); + var positionalVars = positionalPatternClause?.Subpatterns.SelectMany(p => GetVariablesDeclared(p.Pattern)) ?? Array.Empty(); + return designationVars.Concat(propertyVars).Concat(positionalVars); + case VarPatternSyntax { Designation: var variableDesignationSyntax}: + return GetVariablesDeclared(variableDesignationSyntax); + case BinaryPatternSyntax binaryPatternSyntax: + return GetVariablesDeclared(binaryPatternSyntax.Left) + .Concat(GetVariablesDeclared(binaryPatternSyntax.Right)); + case ParenthesizedPatternSyntax parenthesizedPatternSyntax: + return GetVariablesDeclared(parenthesizedPatternSyntax.Pattern); + } + return Array.Empty(); + } + + public static IEnumerable GetVariablesDeclared(VariableDesignationSyntax? designationSyntax) + { + switch (designationSyntax) + { + case SingleVariableDesignationSyntax singleVariableDesignationSyntax: + yield return singleVariableDesignationSyntax.Identifier.ValueText; + break; + case ParenthesizedVariableDesignationSyntax parenthesizedVariableDesignationSyntax: + foreach (var variable in parenthesizedVariableDesignationSyntax.Variables) + { + foreach (var v in GetVariablesDeclared(variable)) + { + yield return v; + } + } + break; + case DiscardDesignationSyntax _: + yield break; + } + } + +} \ No newline at end of file diff --git a/src/Common/CSharp/Analysis/ReduceIfNesting/ReduceIfNestingAnalysis.cs b/src/Common/CSharp/Analysis/ReduceIfNesting/ReduceIfNestingAnalysis.cs index 68484accf7..c64d45cd22 100644 --- a/src/Common/CSharp/Analysis/ReduceIfNesting/ReduceIfNestingAnalysis.cs +++ b/src/Common/CSharp/Analysis/ReduceIfNesting/ReduceIfNestingAnalysis.cs @@ -1,5 +1,8 @@ // Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading; @@ -108,7 +111,16 @@ private static ReduceIfNestingAnalysisResult AnalyzeCore( return Fail(parent); } - if (LocallyDeclaredVariablesOverlapWithOuterScope(ifStatement, parent, semanticModel)) + var parentBody = parent switch + { + ForStatementSyntax forStatementSyntax => forStatementSyntax.Statement, + ForEachStatementSyntax forEachStatementSyntax => forEachStatementSyntax.Statement, + DoStatementSyntax doStatementSyntax => doStatementSyntax.Statement, + WhileStatementSyntax whileStatementSyntax => whileStatementSyntax.Statement, + _ => throw new ArgumentOutOfRangeException() + }; + + if (LocallyDeclaredVariablesOverlapWithOuterScope(ifStatement, parentBody, semanticModel)) return Fail(parent); return Success(jumpKind, parent); @@ -134,7 +146,15 @@ private static ReduceIfNestingAnalysisResult AnalyzeCore( return Fail(parent); } - if (LocallyDeclaredVariablesOverlapWithOuterScope(ifStatement, parent, semanticModel)) + var parentBody = parent switch + { + ConstructorDeclarationSyntax constructorDeclarationSyntax => constructorDeclarationSyntax.Body, + DestructorDeclarationSyntax destructorDeclarationSyntax => destructorDeclarationSyntax.Body, + AccessorDeclarationSyntax accessorDeclarationSyntax => accessorDeclarationSyntax.Body, + _ => throw new NotImplementedException() + }; + + if (LocallyDeclaredVariablesOverlapWithOuterScope(ifStatement, parentBody, semanticModel)) return Fail(parent); return Success(jumpKind, parent); @@ -142,11 +162,19 @@ private static ReduceIfNestingAnalysisResult AnalyzeCore( case SyntaxKind.OperatorDeclaration: case SyntaxKind.ConversionOperatorDeclaration: case SyntaxKind.GetAccessorDeclaration: - { + { if (jumpKind == SyntaxKind.None) return Fail(parent); - if (LocallyDeclaredVariablesOverlapWithOuterScope(ifStatement, parent, semanticModel)) + var parentBody = parent switch + { + OperatorDeclarationSyntax operatorDeclarationSyntax => operatorDeclarationSyntax.Body, + ConversionOperatorDeclarationSyntax conversionOperatorDeclarationSyntax => conversionOperatorDeclarationSyntax.Body, + AccessorDeclarationSyntax accessorDeclarationSyntax=> accessorDeclarationSyntax.Body, + _ => throw new NotImplementedException() + }; + + if (LocallyDeclaredVariablesOverlapWithOuterScope(ifStatement, parentBody, semanticModel)) return Fail(parent); return Success(jumpKind, parent); @@ -294,13 +322,31 @@ SemanticModel semanticModel if (ifVariablesDeclared.IsEmpty) return false; - var parentStatementDeclared = semanticModel.AnalyzeDataFlow(parent)! - .VariablesDeclared; - + IEnumerable parentVariablesDeclared; + if (parent is SwitchSectionSyntax switchSectionSyntax) + { + var allDeclaredVariables = new List(); + foreach (var statement in switchSectionSyntax.Statements) + { + allDeclaredVariables.AddRange(semanticModel.AnalyzeDataFlow(statement)!.VariablesDeclared); + } + + parentVariablesDeclared = allDeclaredVariables; + } + else + { + parentVariablesDeclared = semanticModel.AnalyzeDataFlow(parent)! + .VariablesDeclared; + } + // The parent's declared variables will include those from the if and so we have to check for any symbols occurring twice. - return ifVariablesDeclared.Any(variable => - parentStatementDeclared.Count(s => s.Name == variable.Name) > 1 - ); + foreach (var variable in ifVariablesDeclared) + { + if (parentVariablesDeclared.Count(s => s.Name == variable.Name) > 1) + return true; + } + + return false; } private static bool IsNestedFix(SyntaxNode node, SemanticModel semanticModel, ReduceIfNestingOptions options, CancellationToken cancellationToken) diff --git a/src/Tests/Analyzers.Tests/RCS1031RemoveUnnecessaryBracesInSwitchSectionTests.cs b/src/Tests/Analyzers.Tests/RCS1031RemoveUnnecessaryBracesInSwitchSectionTests.cs index e77d79ce4b..495f0caecb 100644 --- a/src/Tests/Analyzers.Tests/RCS1031RemoveUnnecessaryBracesInSwitchSectionTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1031RemoveUnnecessaryBracesInSwitchSectionTests.cs @@ -237,7 +237,6 @@ void M() } "); } - [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveUnnecessaryBracesInSwitchSection)] public async Task TestNoDiagnostic_WhenOverlappingLocalVariableDeclaration() { @@ -267,4 +266,66 @@ void M() } "); } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveUnnecessaryBracesInSwitchSection)] + public async Task TestNoDiagnostic_WhenOverlappingLocalVariableWithPatternMatchDeclaration() + { + await VerifyNoDiagnosticAsync(@" +using System; + +class C +{ + void M() + { + object o = null; + + switch (o) + { + case string s: + var x = 1; + break; + default: + { + var s = 1; + break; + } + } + } } +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveUnnecessaryBracesInSwitchSection)] + public async Task TestNoDiagnostic_WhenOverlappingLocalVariableWithRecursivePatternMatchDeclaration() + { + await VerifyNoDiagnosticAsync(@" +using System; + +class C +{ + class Wrapper + { + public string S; + } + void M() + { + object o = null; + + switch (o) + { + case Wrapper { S: var s }: + var x = 1; + break; + default: + { + var s = 1; + break; + } + } + } +} +"); + } +} + + diff --git a/src/Tests/Analyzers.Tests/RCS1208ReduceIfNestingTests.cs b/src/Tests/Analyzers.Tests/RCS1208ReduceIfNestingTests.cs index c8a3df99c2..d0cdab7268 100644 --- a/src/Tests/Analyzers.Tests/RCS1208ReduceIfNestingTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1208ReduceIfNestingTests.cs @@ -13,7 +13,226 @@ public class RCS1208ReduceIfNestingTests : AbstractCSharpDiagnosticVerifier + { + [|if|] (p) + { + M2(); + } + }; + } + + void M2() + { + } +} +", @" +class C +{ + void M(bool p) + { + var f = () => + { + if (!p) + { + return; + } + + M2(); + } +; + } + + void M2() + { + } +} +"); + } + + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.ReduceIfNesting)] + public async Task Test_WhenParentIsLocalFunction() + { + await VerifyDiagnosticAndFixAsync(@" +class C +{ + void M(bool p) + { + void M3() + { + [|if|] (p) + { + M2(); + } + + } + M3(); + } + + void M2() + { + } +} +", @" +class C +{ + void M(bool p) + { + void M3() + { + if (!p) + { + return; + } + + M2(); + } + M3(); + } + + void M2() + { + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.ReduceIfNesting)] + public async Task Test_WhenParentIsMethod() { await VerifyDiagnosticAndFixAsync(@" class C @@ -50,8 +269,160 @@ void M2() "); } + + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.ReduceIfNesting)] + public async Task TestNoDiagnostic_OverlappingLocalVariables_WhenParentIsConstructor() + { + await VerifyNoDiagnosticAsync(@" +class C +{ + C(bool p) + { + if (p) + { + var s = 1; + } + + if (p) + { + var s = 2; + M2(); + } + } + + void M2() + { + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.ReduceIfNesting)] + public async Task TestNoDiagnostic_OverlappingLocalVariables_WhenParentIsConversionOperator() + { + await VerifyNoDiagnosticAsync(@" +class C +{ + static bool b=false; + public static implicit operator bool(C c) + { + if (b) + { + var s = 1; + } + if (b) + { + var s = 2; + return M2(); + } + return false; + } + + static bool M2() + { + return true; + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.ReduceIfNesting)] + public async Task TestNoDiagnostic_OverlappingLocalVariables_WhenParentIsGetAccessor() + { + await VerifyNoDiagnosticAsync(@" +class C +{ + static bool b=false; + public static bool s { + get { + if (b) + { + var s = 1; + } + + if (b) + { + var s = 2; + return M2(); + } + return false; + } + } + + static bool M2() + { + return true; + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.ReduceIfNesting)] + public async Task TestNoDiagnostic_OverlappingLocalVariables_WhenParentIsLambda() + { + await VerifyNoDiagnosticAsync(@" +class C +{ + void M(bool p) + { + var f = () => + { + if (p) + { + var s = 1; + } + + if (p) + { + var s = 2; + M2(); + } + }; + } + + void M2() + { + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.ReduceIfNesting)] + public async Task TestNoDiagnostic_OverlappingLocalVariables_WhenParentIsLocalFunction() + { + await VerifyNoDiagnosticAsync(@" +class C +{ + void M(bool p) + { + void M3() + { + + if (p) + { + var s = 1; + } + + if (p) + { + var s = 2; + M2(); + } + + } + M3(); + } + + void M2() + { + } +} +"); + } + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.ReduceIfNesting)] - public async Task TestNoDiagnostic_OverlappingLocalVariables() + public async Task TestNoDiagnostic_OverlappingLocalVariables_WhenParentIsMethod() { await VerifyNoDiagnosticAsync(@" class C diff --git a/src/Tests/CSharp.Tests/PatternMatchingVariableDeclarationHelperTests.cs b/src/Tests/CSharp.Tests/PatternMatchingVariableDeclarationHelperTests.cs new file mode 100644 index 0000000000..d6d59f2cee --- /dev/null +++ b/src/Tests/CSharp.Tests/PatternMatchingVariableDeclarationHelperTests.cs @@ -0,0 +1,184 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Roslynator.CSharp.Analysis; +using Xunit; + +namespace Roslynator.Testing.CSharp; + +public class PatternMatchingVariableDeclarationHelperTests +{ + [Fact] + public void SingleVariableDesignation() + { + VariableDesignationSyntax designationSyntax = SyntaxFactory.SingleVariableDesignation( + SyntaxFactory.Identifier("x") + ); + var variables = PattenMatchingVariableDeclarationHelper.GetVariablesDeclared(designationSyntax); + Assert.True(1==variables.Count()); + Assert.True("x"== variables.First()); + } + + [Fact] + public void ParenthesizedVariableDesignation() + { + VariableDesignationSyntax designationSyntax = SyntaxFactory.ParenthesizedVariableDesignation( + SyntaxFactory.SeparatedList(new List + { + SyntaxFactory.SingleVariableDesignation(SyntaxFactory.Identifier("x")), + SyntaxFactory.SingleVariableDesignation(SyntaxFactory.Identifier("y")) + }) + ); + + var variables = PattenMatchingVariableDeclarationHelper.GetVariablesDeclared(designationSyntax); + Assert.True(2 == variables.Count()); + Assert.True(variables.Contains("x")); + Assert.True(variables.Contains("y")); + } + + [Fact] + public void DiscardDesignation() + { + VariableDesignationSyntax designationSyntax = SyntaxFactory.DiscardDesignation(); + var variables = PattenMatchingVariableDeclarationHelper.GetVariablesDeclared(designationSyntax); + Assert.True(!variables.Any()); + } + + [Fact] + public void NullTest() + { + VariableDesignationSyntax designationSyntax = null; + var variables = PattenMatchingVariableDeclarationHelper.GetVariablesDeclared(designationSyntax); + Assert.True(!variables.Any()); + + } + + [Fact] + public void DeclarationPattern() + { + PatternSyntax patternSyntax = SyntaxFactory.DeclarationPattern( + SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword)), + SyntaxFactory.SingleVariableDesignation(SyntaxFactory.Identifier("x")) + ); + var variables = PattenMatchingVariableDeclarationHelper.GetVariablesDeclared(patternSyntax); + Assert.True(1==variables.Count()); + Assert.True("x"== variables.First()); + } + + [Fact] + public void RecursivePattern_WithPositional() + { + PatternSyntax patternSyntax = SyntaxFactory.RecursivePattern( + SyntaxFactory.IdentifierName("TypeA"), + positionalPatternClause: SyntaxFactory.PositionalPatternClause( + SyntaxFactory.SeparatedList(new List + { + SyntaxFactory.Subpattern( + SyntaxFactory.DeclarationPattern( + SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword)), + SyntaxFactory.SingleVariableDesignation(SyntaxFactory.Identifier("p1")) + ) + ), + SyntaxFactory.Subpattern( + SyntaxFactory.DeclarationPattern( + SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword)), + SyntaxFactory.SingleVariableDesignation(SyntaxFactory.Identifier("p2")) + ) + ) + }) + ), + propertyPatternClause: default, + designation: default + ); + var variables = PattenMatchingVariableDeclarationHelper.GetVariablesDeclared(patternSyntax); + Assert.True(2==variables.Count()); + Assert.True(variables.Contains("p1")); + Assert.True(variables.Contains("p2")); + } + + [Fact] + public void RecursivePattern_WithProperty() + { + PatternSyntax patternSyntax = SyntaxFactory.RecursivePattern( + SyntaxFactory.IdentifierName("TypeA"), + positionalPatternClause: default, + propertyPatternClause: SyntaxFactory.PropertyPatternClause( + SyntaxFactory.SeparatedList(new List + { + SyntaxFactory.Subpattern( + SyntaxFactory.NameColon(SyntaxFactory.IdentifierName("PropertyName")), + SyntaxFactory.VarPattern( + SyntaxFactory.SingleVariableDesignation(SyntaxFactory.Identifier("x")) + ) + ), + }) + ), + designation: default + ); + var variables = PattenMatchingVariableDeclarationHelper.GetVariablesDeclared(patternSyntax); + Assert.True(1==variables.Count()); + Assert.True(variables.Contains("x")); + } + + [Fact] + public void RecursivePattern_WithDesignation() + { + PatternSyntax patternSyntax = SyntaxFactory.RecursivePattern( + SyntaxFactory.IdentifierName("TypeA"), + positionalPatternClause: default, + propertyPatternClause: SyntaxFactory.PropertyPatternClause( + SyntaxFactory.SeparatedList(new List + { + SyntaxFactory.Subpattern( + SyntaxFactory.NameColon(SyntaxFactory.IdentifierName("PropertyName")), + SyntaxFactory.ConstantPattern(SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(42))) + ), + }) + ), + designation: SyntaxFactory.SingleVariableDesignation(SyntaxFactory.Identifier("x")) + ); + var variables = PattenMatchingVariableDeclarationHelper.GetVariablesDeclared(patternSyntax); + Assert.True(1==variables.Count()); + Assert.True(variables.Contains("x")); + + } + + [Fact] + public void VarPattern() + { + PatternSyntax patternSyntax = SyntaxFactory.VarPattern( + SyntaxFactory.SingleVariableDesignation(SyntaxFactory.Identifier("x")) + ); + var variables = PattenMatchingVariableDeclarationHelper.GetVariablesDeclared(patternSyntax); + Assert.True(1==variables.Count()); + Assert.True("x"==variables.First()); + + } + + [Fact] + public void BinaryPattern() + { + PatternSyntax patternSyntax = SyntaxFactory.BinaryPattern( + SyntaxKind.AndPattern, + SyntaxFactory.ConstantPattern(SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(42))), + SyntaxFactory.ConstantPattern(SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(99))) + ); + var variables = PattenMatchingVariableDeclarationHelper.GetVariablesDeclared(patternSyntax); + Assert.True(!variables.Any()); + } + + [Fact] + public void ParenthesizedPattern() + { + PatternSyntax patternSyntax = SyntaxFactory.ParenthesizedPattern( + SyntaxFactory.VarPattern( + SyntaxFactory.SingleVariableDesignation(SyntaxFactory.Identifier("x")) + ) + ); + var variables = PattenMatchingVariableDeclarationHelper.GetVariablesDeclared(patternSyntax); + Assert.True(1==variables.Count()); + Assert.True("x"==variables.First()); + } + +} \ No newline at end of file