diff --git a/src/EditorFeatures/CSharpTest/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionFixAllTests.cs b/src/EditorFeatures/CSharpTest/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionFixAllTests.cs new file mode 100644 index 0000000000000..9c8446ca8c5cc --- /dev/null +++ b/src/EditorFeatures/CSharpTest/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionFixAllTests.cs @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertSwitchStatementToExpression +{ + public partial class ConvertSwitchStatementToExpressionTests + { + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestNested_01() + { + await TestInCSharp8( +@"class Program +{ + int M(int i, int j) + { + int r; + {|FixAllInDocument:switch|} (i) + { + case 1: + r = 1; + break; + case 2: + r = 2; + break; + case 3: + r = 3; + break; + default: + r = 4; + break; + } + int x, y; + switch (i) + { + case 1: + x = 1; + y = 1; + break; + case 2: + x = 1; + y = 1; + break; + case 3: + x = 1; + y = 1; + break; + default: + x = 1; + y = 1; + break; + } + switch (i) + { + default: + throw null; + case 1: + switch (j) + { + case 10: + return 10; + case 20: + return 20; + case 30: + return 30; + } + return 0; + case 2: + switch (j) + { + case 10: + return 10; + case 20: + return 20; + case 30: + return 30; + case var _: + return 0; + } + case 3: + switch (j) + { + case 10: + return 10; + case 20: + return 20; + case 30: + return 30; + case var v: + return 0; + } + } + } +}", +@"class Program +{ + int M(int i, int j) + { + var r = i switch + { + 1 => 1, + 2 => 2, + 3 => 3, + _ => 4, + }; + int x, y; + switch (i) + { + case 1: + x = 1; + y = 1; + break; + case 2: + x = 1; + y = 1; + break; + case 3: + x = 1; + y = 1; + break; + default: + x = 1; + y = 1; + break; + } + switch (i) + { + default: + throw null; + case 1: + return j switch + { + 10 => 10, + 20 => 20, + 30 => 30, + _ => 0, + }; + case 2: + return j switch + { + 10 => 10, + 20 => 20, + 30 => 30, + var _ => 0, + }; + case 3: + return j switch + { + 10 => 10, + 20 => 20, + 30 => 30, + var v => 0, + }; + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestNested_02() + { + await TestInCSharp8( +@"class Program +{ + System.Action M(int i, int j) + { + {|FixAllInDocument:switch|} (i) + { + default: + return () => + { + switch (j) + { + default: + return 3; + } + }; + } + } +}", +@"class Program +{ + System.Action M(int i, int j) + { + return i switch + { + _ => () => + { + switch (j) + { + default: + return 3; + } + } + , + }; + } +}"); + } + } +} diff --git a/src/EditorFeatures/CSharpTest/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionTests.cs b/src/EditorFeatures/CSharpTest/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionTests.cs new file mode 100644 index 0000000000000..4ee5d4d8d6c0c --- /dev/null +++ b/src/EditorFeatures/CSharpTest/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionTests.cs @@ -0,0 +1,628 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.ConvertSwitchStatementToExpression; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertSwitchStatementToExpression +{ + public partial class ConvertSwitchStatementToExpressionTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + { + internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) + => (new ConvertSwitchStatementToExpressionDiagnosticAnalyzer(), new ConvertSwitchStatementToExpressionCodeFixProvider()); + + private Task TestInCSharp8(string actual, string expected) + => TestInRegularAndScriptAsync(actual, expected, parseOptions: TestOptions.Regular8); + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestReturn() + { + await TestInCSharp8( +@"class Program +{ + int M(int i) + { + [||]switch (i) + { + case 1: + return 4; + case 2: + return 5; + case 3: + return 6; + default: + return 7; + } + } +}", +@"class Program +{ + int M(int i) + { + return i switch + { + 1 => 4, + 2 => 5, + 3 => 6, + _ => 7, + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestReturnAndThrow() + { + await TestInCSharp8( +@"class Program +{ + int M(int i) + { + [||]switch (i) + { + case 1: + return 4; + default: + throw null; + case 2: + return 5; + case 3: + return 6; + } + } +}", +@"class Program +{ + int M(int i) + { + return i switch + { + 1 => 4, + 2 => 5, + 3 => 6, + _ => throw null, + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestAssignment_Array() + { + await TestInCSharp8( +@"class Program +{ + int[] array = new int[1]; + + int M(int i) + { + [||]switch (i) + { + case 1: + array[0] = 4; + break; + case 2: + array[0] = 5; + break; + case 3: + array[0] = 6; + break; + default: + array[0] = 7; + break; + } + } +}", +@"class Program +{ + int[] array = new int[1]; + + int M(int i) + { + array[0] = i switch + { + 1 => 4, + 2 => 5, + 3 => 6, + _ => 7, + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestMissingOnDifferentIndexerArgs() + { + await TestMissingAsync( +@"class Program +{ + int[] array = new int[1]; + + int M(int i) + { + [||]switch (i) + { + case 1: + array[1] = 4; + break; + case 2: + array[2] = 5; + break; + case 3: + array[2] = 6; + break; + default: + array[2] = 7; + break; + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestMissingOnQualifiedName() + { + await TestMissingAsync( +@"class Program +{ + int[] array = new int[1]; + + int M(int i) + { + [||]switch (i) + { + case 1: + this.array[2] = 4; + break; + case 2: + array[2] = 5; + break; + case 3: + array[2] = 6; + break; + default: + array[2] = 7; + break; + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestMissingOnDefaultBreak_01() + { + await TestMissingAsync( +@"class Program +{ + void M(int i) + { + [||]switch (i) + { + default: + break; + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestMissingOnDefaultBreak_02() + { + await TestMissingAsync( +@"class Program +{ + void M(int i) + { + [||]switch (i) + { + case _: + break; + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestMissingOnDefaultBreak_03() + { + await TestMissingAsync( +@"class Program +{ + void M(int i) + { + [||]switch (i) + { + case var _: + break; + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestMissingOnDefaultBreak_04() + { + await TestMissingAsync( +@"class Program +{ + void M(int i) + { + [||]switch (i) + { + case var x: + break; + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestMissingOnAllBreak() + { + await TestMissingAsync( +@"class Program +{ + void M(int i) + { + [||]switch (i) + { + case 1: + break; + case 2: + break; + case 3: + break; + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestAllThrow() + { + await TestInCSharp8( +@"class Program +{ + void M(int i) + { + [||]switch (i) + { + case 1: + throw null; + default: + throw new Exception(); + } + } +}", +@"class Program +{ + void M(int i) + { + throw i switch + { + 1 => null, + _ => new Exception(), + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestAssignment() + { + await TestInCSharp8( +@"class Program +{ + void M(int i) + { + int j; + [||]switch (i) + { + case 1: + j = 4; + break; + case 2: + j = 5; + break; + case 3: + j = 6; + break; + } + throw null; + } +}", +@"class Program +{ + void M(int i) + { + var j = i switch + { + 1 => 4, + 2 => 5, + 3 => 6, + _ => throw null, + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestMissingOnNextStatementMismatch() + { + await TestMissingAsync( +@"class Program +{ + int M(int i) + { + int j = 0; + [||]switch (i) + { + case 1: + j = 4; + break; + case 2: + j = 5; + break; + case 3: + j = 6; + break; + } + return j; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestMissingOnAssignmentMismatch() + { + await TestMissingAsync( +@"class Program +{ + int M(int i) + { + int j = 0; + [||]switch (i) + { + case 1: + j = 4; + break; + case 2: + j += 5; + break; + } + return j; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestAssignment_Compound() + { + await TestInCSharp8( +@"class Program +{ + void M(int i) + { + int j = 0; + [||]switch (i) + { + case 1: + j += 4; + break; + case 2: + j += 5; + break; + case 3: + j += 6; + break; + } + throw null; + } +}", +@"class Program +{ + void M(int i) + { + int j = 0; + j += i switch + { + 1 => 4, + 2 => 5, + 3 => 6, + _ => throw null, + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestAssignment_UseBeforeAssignment() + { + await TestInCSharp8( +@"class Program +{ + void M(int i) + { + int j = 123; + M(i); + [||]switch (i) + { + case 1: + j = 4; + break; + case 2: + j = 5; + break; + case 3: + j = 6; + break; + } + throw null; + } +}", +@"class Program +{ + void M(int i) + { + int j = 123; + M(i); + j = i switch + { + 1 => 4, + 2 => 5, + 3 => 6, + _ => throw null, + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestMissingOnMultiAssignment() + { + await TestMissingAsync( +@"class Program +{ + void M(int i) + { + int j, k; + [||]switch (i) + { + case 1: + j = 4; + k = 5; + break; + case 2: + j = 6; + k = 7; + break; + case 3: + j = 8; + k = 9; + break; + } + throw null; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestMissingONMultiCaseSection() + { + await TestMissingAsync( +@"class Program +{ + void M(int i) + { + int j; + [||]switch (i) + { + case 1: + case 2: + j = 4; + break; + } + throw null; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestMissingOnMultiCompoundAssignment() + { + await TestMissingAsync( +@"class Program +{ + void M(int i) + { + int j = 0, k = 0; + [||]switch (i) + { + case 1: + j += 4; + k += 5; + break; + case 2: + j += 6; + k += 7; + break; + case 3: + j += 8; + k += 9; + break; + } + throw null; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestMissingOnGoto() + { + await TestMissingAsync( +@"class Program +{ + int M(int i) + { + [||]switch (i) + { + case 1: + return 0; + case 2: + goto default; + default: + return 2; + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertSwitchStatementToExpression)] + public async Task TestTrivia() + { + await TestInCSharp8( +@"class Program +{ + int M(int i) + { + // leading switch + [||]switch (i) // trailing switch + { + // leading label + case 1: + return 4; // trailing body + case 2: + return 5; + case 3: + return 6; + } + + // leading next statement + throw null; // leading next statement + } +}", +@"class Program +{ + int M(int i) + { + // leading switch + return i switch // trailing switch + { + // leading label + 1 => 4, // trailing body + 2 => 5, + 3 => 6, + + // leading next statement + _ => throw null, // leading next statement + }; + } +}"); + } + } +} diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.Designer.cs b/src/Features/CSharp/Portable/CSharpFeaturesResources.Designer.cs index 196743fb78d4b..6c4fa20636e66 100644 --- a/src/Features/CSharp/Portable/CSharpFeaturesResources.Designer.cs +++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.Designer.cs @@ -394,6 +394,15 @@ internal static string conversion_operator { } } + /// + /// Looks up a localized string similar to Convert switch statement to expression. + /// + internal static string Convert_switch_statement_to_expression { + get { + return ResourceManager.GetString("Convert_switch_statement_to_expression", resourceCulture); + } + } + /// /// Looks up a localized string similar to Convert to 'for'. /// @@ -1359,6 +1368,15 @@ internal static string Use_is_null_check { } } + /// + /// Looks up a localized string similar to Use 'switch' expression. + /// + internal static string Use_switch_expression { + get { + return ResourceManager.GetString("Use_switch_expression", resourceCulture); + } + } + /// /// Looks up a localized string similar to use 'var' instead of explicit type. /// diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx index 1a28988e19702..57deeae65bf63 100644 --- a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx +++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx @@ -602,4 +602,10 @@ &Sort Usings + + Convert switch statement to expression + + + Use 'switch' expression + diff --git a/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.Rewriter.cs b/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.Rewriter.cs new file mode 100644 index 0000000000000..badcdcb27c85b --- /dev/null +++ b/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.Rewriter.cs @@ -0,0 +1,282 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Diagnostics; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.ConvertSwitchStatementToExpression +{ + using static SyntaxFactory; + + internal sealed partial class ConvertSwitchStatementToExpressionCodeFixProvider + { + private sealed class Rewriter : CSharpSyntaxVisitor + { + private ExpressionSyntax _assignmentTargetOpt; + private readonly bool _isAllThrowStatements; + + private Rewriter(bool isAllThrowStatements) + { + _isAllThrowStatements = isAllThrowStatements; + } + + public static StatementSyntax Rewrite( + SwitchStatementSyntax switchStatement, SemanticModel semanticModel, SyntaxEditor editor, + SyntaxKind nodeToGenerate, bool shouldMoveNextStatementToSwitchExpression) + { + var rewriter = new Rewriter(isAllThrowStatements: nodeToGenerate == SyntaxKind.ThrowStatement); + + // Rewrite the switch statement as a switch expression. + var switchExpression = rewriter.RewriteSwitchStatement(switchStatement, + allowMoveNextStatementToSwitchExpression: shouldMoveNextStatementToSwitchExpression); + + // Only on simple assignments we attempt to remove variable declarators. + var isSimpleAssignment = nodeToGenerate == SyntaxKind.SimpleAssignmentExpression; + var generateDeclaration = isSimpleAssignment && rewriter.TryRemoveVariableDeclarators(switchStatement, semanticModel, editor); + + // Generate the final statement to wrap the switch expression, e.g. a "return" or an assignment. + return rewriter.GetFinalStatement(switchExpression, + switchStatement.SwitchKeyword.LeadingTrivia, nodeToGenerate, generateDeclaration); + } + + private bool TryRemoveVariableDeclarators(SwitchStatementSyntax switchStatement, SemanticModel semanticModel, SyntaxEditor editor) + { + Debug.Assert(_assignmentTargetOpt != null); + + // Try to remove variable declarator only if it's a simple identifier. + if (!_assignmentTargetOpt.IsKind(SyntaxKind.IdentifierName)) + { + return false; + } + + var symbol = semanticModel.GetSymbolInfo(_assignmentTargetOpt).Symbol; + if (symbol == null) + { + return false; + } + + if (symbol.Kind != SymbolKind.Local) + { + return false; + } + + var syntaxReferences = symbol.DeclaringSyntaxReferences; + if (syntaxReferences.Length != 1) + { + return false; + } + + if (!(syntaxReferences[0].GetSyntax() is VariableDeclaratorSyntax declarator)) + { + return false; + } + + if (declarator.Initializer != null) + { + return false; + } + + var symbolName = symbol.Name; + var declaratorSpanStart = declarator.SpanStart; + var switchStatementSpanStart = switchStatement.SpanStart; + + // Check for uses before the switch expression. + foreach (var descendentNode in declarator.GetAncestor().DescendantNodes()) + { + var nodeSpanStart = descendentNode.SpanStart; + if (nodeSpanStart <= declaratorSpanStart) + { + // We haven't yet reached the declarator node. + continue; + } + + if (nodeSpanStart >= switchStatementSpanStart) + { + // We've reached the switch statement. + break; + } + + if (descendentNode.IsKind(SyntaxKind.IdentifierName, out IdentifierNameSyntax identifierName) && + identifierName.Identifier.ValueText == symbolName && + symbol.Equals(semanticModel.GetSymbolInfo(identifierName).Symbol)) + { + // The variable is being used outside the switch statement. + return false; + } + } + + // Safe to remove declarator node. + editor.RemoveNode(symbol.DeclaringSyntaxReferences[0].GetSyntax()); + return true; + } + + private StatementSyntax GetFinalStatement( + ExpressionSyntax switchExpression, + SyntaxTriviaList leadingTrivia, + SyntaxKind nodeToGenerate, + bool generateDeclaration) + { + switch (nodeToGenerate) + { + case SyntaxKind.ReturnStatement: + return ReturnStatement( + Token(leadingTrivia, SyntaxKind.ReturnKeyword, trailing: default), + switchExpression, + Token(SyntaxKind.SemicolonToken)); + case SyntaxKind.ThrowStatement: + return ThrowStatement( + Token(leadingTrivia, SyntaxKind.ThrowKeyword, trailing: default), + switchExpression, + Token(SyntaxKind.SemicolonToken)); + } + + Debug.Assert(SyntaxFacts.IsAssignmentExpression(nodeToGenerate)); + Debug.Assert(_assignmentTargetOpt != null); + + return generateDeclaration + ? GenerateVariableDeclaration(switchExpression, leadingTrivia) + : GenerateAssignment(switchExpression, nodeToGenerate, leadingTrivia); + } + + private ExpressionStatementSyntax GenerateAssignment(ExpressionSyntax switchExpression, SyntaxKind assignmentKind, SyntaxTriviaList leadingTrivia) + { + Debug.Assert(_assignmentTargetOpt != null); + + return ExpressionStatement( + AssignmentExpression(assignmentKind, + left: _assignmentTargetOpt, + right: switchExpression)) + .WithLeadingTrivia(leadingTrivia); + } + + private ExpressionStatementSyntax GenerateVariableDeclaration(ExpressionSyntax switchExpression, SyntaxTriviaList leadingTrivia) + { + Debug.Assert(_assignmentTargetOpt is IdentifierNameSyntax); + return ExpressionStatement( + AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, + left: DeclarationExpression(IdentifierName(Identifier(leadingTrivia, "var", trailing: default)), + SingleVariableDesignation(((IdentifierNameSyntax)_assignmentTargetOpt).Identifier)), + right: switchExpression)); + } + + private SwitchExpressionArmSyntax GetSwitchExpressionArm(SwitchSectionSyntax node) + { + Debug.Assert(node.Labels.Count == 1); + + return SwitchExpressionArm( + pattern: GetPattern(node.Labels[0], out var whenClauseOpt), + whenClause: whenClauseOpt, + expression: RewriteStatements(node.Statements)); + } + + private static PatternSyntax GetPattern(SwitchLabelSyntax switchLabel, out WhenClauseSyntax whenClauseOpt) + { + switch (switchLabel.Kind()) + { + case SyntaxKind.CasePatternSwitchLabel: + var node = (CasePatternSwitchLabelSyntax)switchLabel; + whenClauseOpt = node.WhenClause; + return node.Pattern; + + case SyntaxKind.CaseSwitchLabel: + whenClauseOpt = null; + return ConstantPattern(((CaseSwitchLabelSyntax)switchLabel).Value); + + case SyntaxKind.DefaultSwitchLabel: + whenClauseOpt = null; + return DiscardPattern(); + + case var value: + throw ExceptionUtilities.UnexpectedValue(value); + } + } + + public override ExpressionSyntax VisitAssignmentExpression(AssignmentExpressionSyntax node) + { + if (_assignmentTargetOpt == null) + { + _assignmentTargetOpt = node.Left; + } + + return node.Right; + } + + private ExpressionSyntax RewriteStatements(SyntaxList statements) + { + Debug.Assert(statements.Count == 1 || statements.Count == 2); + Debug.Assert(!statements[0].IsKind(SyntaxKind.BreakStatement)); + return Visit(statements[0]); + } + + public override ExpressionSyntax VisitSwitchStatement(SwitchStatementSyntax node) + { + return RewriteSwitchStatement(node); + } + + private ExpressionSyntax RewriteSwitchStatement(SwitchStatementSyntax node, bool allowMoveNextStatementToSwitchExpression = true) + { + var switchArms = node.Sections + // The default label must come last in the switch expression. + .OrderBy(section => section.Labels[0].IsKind(SyntaxKind.DefaultSwitchLabel)) + .Select(s => + (leadingTrivia: s.Labels[0].GetFirstToken().LeadingTrivia, + trailingTrivia: s.Statements[0].GetLastToken().TrailingTrivia, + armExpression: GetSwitchExpressionArm(s))) + .ToList(); + + // This is possibly false only on the top-level switch statement. + // On nested nodes, if there's a subsequent statement, it is most definitely a + // "return" or "throw" which is already validated in the analysis phase. + if (allowMoveNextStatementToSwitchExpression) + { + var nextStatement = node.GetNextStatement(); + if (nextStatement != null) + { + Debug.Assert(nextStatement.IsKind(SyntaxKind.ThrowStatement, SyntaxKind.ReturnStatement)); + switchArms.Add( + (nextStatement.GetFirstToken().LeadingTrivia, + nextStatement.GetLastToken().TrailingTrivia, + SwitchExpressionArm(DiscardPattern(), Visit(nextStatement)))); + } + } + + return SwitchExpression( + node.Expression, + Token(leading: default, SyntaxKind.SwitchKeyword, node.CloseParenToken.TrailingTrivia), + Token(SyntaxKind.OpenBraceToken), + SeparatedList( + switchArms.Select(t => t.armExpression.WithLeadingTrivia(t.leadingTrivia)), + switchArms.Select(t => Token(leading: default, SyntaxKind.CommaToken, t.trailingTrivia))), + Token(SyntaxKind.CloseBraceToken)); + } + + public override ExpressionSyntax VisitReturnStatement(ReturnStatementSyntax node) + { + Debug.Assert(node.Expression != null); + return node.Expression; + } + + public override ExpressionSyntax VisitThrowStatement(ThrowStatementSyntax node) + { + Debug.Assert(node.Expression != null); + // If this is an all-throw switch statement, we return the expression rather than + // creating a throw expression so we can wrap the switch expression inside a throw expression. + return _isAllThrowStatements ? node.Expression : ThrowExpression(node.Expression); + } + + public override ExpressionSyntax VisitExpressionStatement(ExpressionStatementSyntax node) + { + return Visit(node.Expression); + } + + public override ExpressionSyntax DefaultVisit(SyntaxNode node) + { + throw ExceptionUtilities.UnexpectedValue(node.Kind()); + } + } + } +} diff --git a/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.cs b/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.cs new file mode 100644 index 0000000000000..3949f16cde96a --- /dev/null +++ b/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionCodeFixProvider.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.ConvertSwitchStatementToExpression +{ + using Constants = ConvertSwitchStatementToExpressionConstants; + + [ExportCodeFixProvider(LanguageNames.CSharp), Shared] + internal sealed partial class ConvertSwitchStatementToExpressionCodeFixProvider : SyntaxEditorBasedCodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds + => ImmutableArray.Create(IDEDiagnosticIds.ConvertSwitchStatementToExpressionDiagnosticId); + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + context.RegisterCodeFix( + new MyCodeAction(c => FixAsync(context.Document, context.Diagnostics.First(), c)), + context.Diagnostics); + return Task.CompletedTask; + } + + protected override async Task FixAllAsync(Document document, ImmutableArray diagnostics, SyntaxEditor editor, CancellationToken cancellationToken) + { + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var spans = ArrayBuilder.GetInstance(diagnostics.Length); + try + { + foreach (var diagnostic in diagnostics) + { + cancellationToken.ThrowIfCancellationRequested(); + + var span = diagnostic.AdditionalLocations[0].SourceSpan; + if (spans.Any((s, nodeSpan) => s.Contains(nodeSpan), span)) + { + // Skip nested switch expressions in case of a fix-all operation. + continue; + } + + spans.Add(span); + + var properties = diagnostic.Properties; + var nodeToGenerate = (SyntaxKind)int.Parse(properties[Constants.NodeToGenerateKey]); + var shouldRemoveNextStatement = bool.Parse(properties[Constants.ShouldRemoveNextStatementKey]); + + var switchStatement = (SwitchStatementSyntax)editor.OriginalRoot.FindNode(span); + editor.ReplaceNode(switchStatement, + Rewriter.Rewrite(switchStatement, semanticModel, editor, + nodeToGenerate, shouldMoveNextStatementToSwitchExpression: shouldRemoveNextStatement) + .WithAdditionalAnnotations(Formatter.Annotation)); + + if (shouldRemoveNextStatement) + { + // Already morphed into the top-level switch expression. + var nextStatement = switchStatement.GetNextStatement(); + Debug.Assert(nextStatement.IsKind(SyntaxKind.ThrowStatement, SyntaxKind.ReturnStatement)); + editor.RemoveNode(nextStatement); + } + } + } + finally + { + spans.Free(); + } + } + + private sealed class MyCodeAction : CodeAction.DocumentChangeAction + { + public MyCodeAction(Func> createChangedDocument) : + base(CSharpFeaturesResources.Convert_switch_statement_to_expression, createChangedDocument) + { + } + } + } +} diff --git a/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionConstants.cs b/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionConstants.cs new file mode 100644 index 0000000000000..164db7a1e0638 --- /dev/null +++ b/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionConstants.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + +namespace Microsoft.CodeAnalysis.CSharp.ConvertSwitchStatementToExpression +{ + internal static class ConvertSwitchStatementToExpressionConstants + { + public const string NodeToGenerateKey = nameof(NodeToGenerateKey); + public const string ShouldRemoveNextStatementKey = nameof(ShouldRemoveNextStatementKey); + } +} diff --git a/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionDiagnosticAnalyzer.Analyzer.cs b/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionDiagnosticAnalyzer.Analyzer.cs new file mode 100644 index 0000000000000..c58b8e191c4a3 --- /dev/null +++ b/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionDiagnosticAnalyzer.Analyzer.cs @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.CodeAnalysis.CSharp.ConvertSwitchStatementToExpression +{ + internal sealed partial class ConvertSwitchStatementToExpressionDiagnosticAnalyzer + { + private sealed class Analyzer : CSharpSyntaxVisitor + { + private ExpressionSyntax _assignmentTargetOpt; + + private Analyzer() + { + } + + public static SyntaxKind Analyze(SwitchStatementSyntax node, out bool shouldRemoveNextStatement) + { + return new Analyzer().AnalyzeSwitchStatement(node, out shouldRemoveNextStatement); + } + + private static bool IsDefaultSwitchLabel(SwitchLabelSyntax node) + { + // default: + if (node.IsKind(SyntaxKind.DefaultSwitchLabel)) + { + return true; + } + + if (node.IsKind(SyntaxKind.CasePatternSwitchLabel, out CasePatternSwitchLabelSyntax @case)) + { + // case _: + if (@case.Pattern.IsKind(SyntaxKind.DiscardPattern)) + { + return true; + } + + // case var _: + // case var x: + if (@case.Pattern.IsKind(SyntaxKind.VarPattern, out VarPatternSyntax varPattern) && + varPattern.Designation.IsKind(SyntaxKind.DiscardDesignation, SyntaxKind.SingleVariableDesignation)) + { + return true; + } + } + + return false; + } + + public override SyntaxKind VisitSwitchStatement(SwitchStatementSyntax node) + { + return AnalyzeSwitchStatement(node, out _); + } + + private SyntaxKind AnalyzeSwitchStatement(SwitchStatementSyntax switchStatement, out bool shouldRemoveNextStatement) + { + shouldRemoveNextStatement = false; + + // Fail if the switch statement is empty or any of sections have more than one "case" label. + // Once we have "or" patterns, we can relax this to accept multi-case sections. + var sections = switchStatement.Sections; + if (sections.Count == 0 || sections.Any(section => section.Labels.Count != 1)) + { + return default; + } + + // If there's no "default" case, we look at the next statement. + // For instance, it could be a "return" statement which we'll use + // as the default case in the switch expression. + var nextStatement = AnalyzeNextStatement(switchStatement, ref shouldRemoveNextStatement); + + // We do need to intersect the next statement analysis result to catch possible + // arm kind mismatch, e.g. a "return" after a non-exhaustive assignment switch. + return Aggregate(nextStatement, sections, (result, section) => Intersect(result, AnalyzeSwitchSection(section))); + } + + private SyntaxKind AnalyzeNextStatement(SwitchStatementSyntax switchStatement, ref bool shouldRemoveNextStatement) + { + if (switchStatement.Sections.Any(section => IsDefaultSwitchLabel(section.Labels[0]))) + { + // Throw can be overridden by other section bodies, therefore it has no effect on the result. + return SyntaxKind.ThrowStatement; + } + + shouldRemoveNextStatement = true; + return AnalyzeNextStatement(switchStatement.GetNextStatement()); + } + + private static SyntaxKind Intersect(SyntaxKind left, SyntaxKind right) + { + if (left == SyntaxKind.ThrowStatement) + { + return right; + } + + if (right == SyntaxKind.ThrowStatement) + { + return left; + } + + if (left == right) + { + return left; + } + + return default; + } + + private SyntaxKind AnalyzeNextStatement(StatementSyntax nextStatement) + { + // Only the following "throw" and "return" can be moved into the switch expression. + return nextStatement.IsKind(SyntaxKind.ThrowStatement, SyntaxKind.ReturnStatement) + ? Visit(nextStatement) + : default; + } + + private SyntaxKind AnalyzeSwitchSection(SwitchSectionSyntax section) + { + switch (section.Statements.Count) + { + case 1: + case 2 when section.Statements[1].IsKind(SyntaxKind.BreakStatement): + return Visit(section.Statements[0]); + default: + return default; + } + } + + private static SyntaxKind Aggregate(SyntaxKind seed, SyntaxList nodes, Func func) + where T : SyntaxNode + { + var result = seed; + foreach (var node in nodes) + { + result = func(result, node); + if (result == default) + { + // No point to continue if any node was not + // convertible to a switch arm's expression + break; + } + } + + return result; + } + + public override SyntaxKind VisitAssignmentExpression(AssignmentExpressionSyntax node) + { + if (_assignmentTargetOpt != null) + { + if (!SyntaxFactory.AreEquivalent(node.Left, _assignmentTargetOpt)) + { + return default; + } + } + else + { + _assignmentTargetOpt = node.Left; + } + + return node.Kind(); + } + + public override SyntaxKind VisitExpressionStatement(ExpressionStatementSyntax node) + { + return Visit(node.Expression); + } + + public override SyntaxKind VisitReturnStatement(ReturnStatementSyntax node) + { + // A "return" statement's expression will be placed in the switch arm expression. + return node.Expression is null ? default : SyntaxKind.ReturnStatement; + } + + public override SyntaxKind VisitThrowStatement(ThrowStatementSyntax node) + { + // A "throw" statement can be converted to a throw expression. + // Gives Failure if Expression is null because a throw expression needs one. + return node.Expression is null ? default : SyntaxKind.ThrowStatement; + } + + public override SyntaxKind DefaultVisit(SyntaxNode node) + { + // In all other cases we return failure result. + return default; + } + } + } +} diff --git a/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionDiagnosticAnalyzer.cs new file mode 100644 index 0000000000000..74fd2618627d3 --- /dev/null +++ b/src/Features/CSharp/Portable/ConvertSwitchStatementToExpression/ConvertSwitchStatementToExpressionDiagnosticAnalyzer.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp.ConvertSwitchStatementToExpression +{ + using Constants = ConvertSwitchStatementToExpressionConstants; + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal sealed partial class ConvertSwitchStatementToExpressionDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer + { + public ConvertSwitchStatementToExpressionDiagnosticAnalyzer() + : base(IDEDiagnosticIds.ConvertSwitchStatementToExpressionDiagnosticId, + new LocalizableResourceString(nameof(CSharpFeaturesResources.Convert_switch_statement_to_expression), CSharpFeaturesResources.ResourceManager, typeof(CSharpFeaturesResources)), + new LocalizableResourceString(nameof(CSharpFeaturesResources.Use_switch_expression), CSharpFeaturesResources.ResourceManager, typeof(CSharpFeaturesResources))) + { + } + + protected override void InitializeWorker(AnalysisContext context) + => context.RegisterSyntaxNodeAction(AnalyzeSyntax, SyntaxKind.SwitchStatement); + + private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) + { + var switchStatement = context.Node; + var syntaxTree = switchStatement.SyntaxTree; + + if (((CSharpParseOptions)syntaxTree.Options).LanguageVersion < LanguageVersion.CSharp8) + { + return; + } + + var options = context.Options; + var cancellationToken = context.CancellationToken; + var optionSet = options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); + if (optionSet == null) + { + return; + } + + var styleOption = optionSet.GetOption(CSharpCodeStyleOptions.PreferSwitchExpression); + if (!styleOption.Value) + { + // User has disabled this feature. + return; + } + + if (switchStatement.GetDiagnostics().Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)) + { + return; + } + + var nodeToGenerate = Analyzer.Analyze((SwitchStatementSyntax)switchStatement, out var shouldRemoveNextStatement); + if (nodeToGenerate == default) + { + return; + } + + context.ReportDiagnostic(Diagnostic.Create(Descriptor, + // Report the diagnostic on the "switch" keyword. + location: switchStatement.GetFirstToken().GetLocation(), + additionalLocations: new[] { switchStatement.GetLocation() }, + properties: ImmutableDictionary.Empty + .Add(Constants.NodeToGenerateKey, ((int)nodeToGenerate).ToString(CultureInfo.InvariantCulture)) + .Add(Constants.ShouldRemoveNextStatementKey, shouldRemoveNextStatement.ToString(CultureInfo.InvariantCulture)))); + } + + public override bool OpenFileOnly(Workspace workspace) + => false; + + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() + => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; + } +} diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf index 2b25598ce5889..eacae31b947ec 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf @@ -62,6 +62,11 @@ Použít předvolby kvalifikace this. + + Convert switch statement to expression + Convert switch statement to expression + + Convert to method Převést na metodu @@ -137,6 +142,11 @@ Unseal class '{0}' + + Use 'switch' expression + Use 'switch' expression + + 'if' statement can be simplified Příkaz if lze zjednodušit. diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf index 2dbcd33032345..e65edc9d75eea 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf @@ -62,6 +62,11 @@ Einstellungen zur Qualifikation "this." anwenden + + Convert switch statement to expression + Convert switch statement to expression + + Convert to method In Methode konvertieren @@ -137,6 +142,11 @@ Unseal class '{0}' + + Use 'switch' expression + Use 'switch' expression + + 'if' statement can be simplified Die If-Anweisung kann vereinfacht werden. diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf index 13ecbfef0b55b..07beda0e355f1 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf @@ -62,6 +62,11 @@ Aplicar preferencias de calificación “this.” + + Convert switch statement to expression + Convert switch statement to expression + + Convert to method Convertir al método @@ -137,6 +142,11 @@ Unseal class '{0}' + + Use 'switch' expression + Use 'switch' expression + + 'if' statement can be simplified La instrucción "if" se puede simplificar diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf index 2490e2b557839..ee0f4d1b98ed9 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf @@ -62,6 +62,11 @@ Appliquer les préférences de qualification 'this.' + + Convert switch statement to expression + Convert switch statement to expression + + Convert to method Convertir en méthode @@ -137,6 +142,11 @@ Unseal class '{0}' + + Use 'switch' expression + Use 'switch' expression + + 'if' statement can be simplified L'instruction 'if' peut être simplifiée diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf index 541668aa85b84..b65ed5d8810f8 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf @@ -62,6 +62,11 @@ Applica le preferenze relative alla qualificazione 'this.' + + Convert switch statement to expression + Convert switch statement to expression + + Convert to method Converti in metodo @@ -137,6 +142,11 @@ Unseal class '{0}' + + Use 'switch' expression + Use 'switch' expression + + 'if' statement can be simplified L'istruzione 'If' può essere semplificata diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf index 741dcc11976d5..1f97a41f75c14 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf @@ -62,6 +62,11 @@ 'this.' 修飾の基本設定を適用します + + Convert switch statement to expression + Convert switch statement to expression + + Convert to method メソッドに変換 @@ -137,6 +142,11 @@ Unseal class '{0}' + + Use 'switch' expression + Use 'switch' expression + + 'if' statement can be simplified 'if' ステートメントは簡素化できます diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf index 13a6c7e67938d..7b977e329b669 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf @@ -62,6 +62,11 @@ 'this.' 한정자 기본 설정 적용 + + Convert switch statement to expression + Convert switch statement to expression + + Convert to method 메서드로 변환 @@ -137,6 +142,11 @@ Unseal class '{0}' + + Use 'switch' expression + Use 'switch' expression + + 'if' statement can be simplified 'if' 문을 간단하게 줄일 수 있습니다. diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf index ae09577de71b4..f3eb6e748a176 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf @@ -62,6 +62,11 @@ Zastosuj preferencje kwalifikacji „this.” + + Convert switch statement to expression + Convert switch statement to expression + + Convert to method Konwertuj na metodę @@ -137,6 +142,11 @@ Unseal class '{0}' + + Use 'switch' expression + Use 'switch' expression + + 'if' statement can be simplified Instrukcja „if” może zostać uproszczona diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf index 7fc677f9167d8..ef316ffb2b2e8 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf @@ -62,6 +62,11 @@ Aplicar as preferências de qualificação 'this.' + + Convert switch statement to expression + Convert switch statement to expression + + Convert to method Converter em método @@ -137,6 +142,11 @@ Unseal class '{0}' + + Use 'switch' expression + Use 'switch' expression + + 'if' statement can be simplified A instrução 'if' pode ser simplificada diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf index 962f9c7b13cff..d6914e564006d 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf @@ -62,6 +62,11 @@ Применять предпочтения для квалификации this. + + Convert switch statement to expression + Convert switch statement to expression + + Convert to method Преобразовать в метод @@ -137,6 +142,11 @@ Unseal class '{0}' + + Use 'switch' expression + Use 'switch' expression + + 'if' statement can be simplified Оператор if можно упростить diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf index 4a7eb99e4a92b..5ddc3984fede0 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf @@ -62,6 +62,11 @@ 'this.' nitelemesi tercihlerini uygula + + Convert switch statement to expression + Convert switch statement to expression + + Convert to method Yönteme dönüştür @@ -137,6 +142,11 @@ Unseal class '{0}' + + Use 'switch' expression + Use 'switch' expression + + 'if' statement can be simplified 'If' deyimi basitleştirilebilir diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf index f1d9beefe108e..cfc3f30a61c34 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf @@ -62,6 +62,11 @@ 应用 “this.” 资格首选项 + + Convert switch statement to expression + Convert switch statement to expression + + Convert to method 转换为方法 @@ -137,6 +142,11 @@ Unseal class '{0}' + + Use 'switch' expression + Use 'switch' expression + + 'if' statement can be simplified 可简化“If”语句 diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf index 8f600717d79a4..0145ee5674053 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf @@ -62,6 +62,11 @@ 套用 'this.' 資格喜好設定 + + Convert switch statement to expression + Convert switch statement to expression + + Convert to method 轉換為方法 @@ -137,6 +142,11 @@ Unseal class '{0}' + + Use 'switch' expression + Use 'switch' expression + + 'if' statement can be simplified 'if' 陳述式可簡化 diff --git a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs index a42770a665f62..b18d95e62f838 100644 --- a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs +++ b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs @@ -109,6 +109,8 @@ internal static class IDEDiagnosticIds public const string MoveMisplacedUsingDirectivesDiagnosticId = "IDE0065"; + public const string ConvertSwitchStatementToExpressionDiagnosticId = "IDE0066"; + // Analyzer error Ids public const string AnalyzerChangedId = "IDE1001"; public const string AnalyzerDependencyConflictId = "IDE1002"; diff --git a/src/Test/Utilities/Portable/Traits/Traits.cs b/src/Test/Utilities/Portable/Traits/Traits.cs index 761e3b71a39ae..41ace58471f11 100644 --- a/src/Test/Utilities/Portable/Traits/Traits.cs +++ b/src/Test/Utilities/Portable/Traits/Traits.cs @@ -64,6 +64,7 @@ public static class Features public const string CodeActionsConvertTupleToStruct = "CodeActions.ConvertTupleToStruct"; public const string CodeActionsConvertQueryToForEach = "CodeActions.ConvertQueryToForEach"; public const string CodeActionsConvertForEachToQuery = "CodeActions.ConvertForEachToQuery"; + public const string CodeActionsConvertSwitchStatementToExpression = "CodeActions.ConvertSwitchStatementToExpression"; public const string CodeActionsCorrectExitContinue = "CodeActions.CorrectExitContinue"; public const string CodeActionsCorrectFunctionReturnType = "CodeActions.CorrectFunctionReturnType"; public const string CodeActionsCorrectNextControlVariable = "CodeActions.CorrectNextControlVariable"; diff --git a/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs b/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs index d03d85977a081..d7fa22047b418 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs +++ b/src/VisualStudio/CSharp/Impl/CSharpVSResources.Designer.cs @@ -1077,6 +1077,15 @@ internal static string Prefer_pattern_matching_over_is_with_cast_check { } } + /// + /// Looks up a localized string similar to Prefer switch expression. + /// + internal static string Prefer_switch_expression { + get { + return ResourceManager.GetString("Prefer_switch_expression", resourceCulture); + } + } + /// /// Looks up a localized string similar to Prefer 'this.'. /// diff --git a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx index ed20c1582fad0..3241eba368041 100644 --- a/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx +++ b/src/VisualStudio/CSharp/Impl/CSharpVSResources.resx @@ -597,4 +597,7 @@ General Title of the control group on the General Formatting options page + + Prefer switch expression + \ No newline at end of file diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs index 4c06ee1cf9530..ccfa1e28c4bae 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject.cs @@ -671,6 +671,12 @@ public string Style_PreferConditionalDelegateCall set { SetXmlOption(CSharpCodeStyleOptions.PreferConditionalDelegateCall, value); } } + public string Style_PreferSwitchExpression + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferSwitchExpression); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferSwitchExpression, value); } + } + public string Style_PreferPatternMatchingOverAsWithNullCheck { get { return GetXmlOption(CSharpCodeStyleOptions.PreferPatternMatchingOverAsWithNullCheck); } diff --git a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs index 044dae74b8df7..b58affd9b3a8f 100644 --- a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs +++ b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs @@ -328,6 +328,36 @@ void M2(object o) //] }} }} +"; + + private static readonly string s_preferSwitchExpression = $@" +class C +{{ + void M1() + {{ +//[ + // {ServicesVSResources.Prefer_colon} + return num switch + {{ + 1 => 1, + _ => 2, + }} +//] + }} + void M2() + {{ +//[ + // {ServicesVSResources.Over_colon} + switch (num) + {{ + case 1: + return 1; + default: + return 2; + }} +//] + }} +}} "; private static readonly string s_preferPatternMatchingOverAsWithNullCheck = $@" @@ -1593,6 +1623,7 @@ internal StyleViewModel(OptionStore optionStore, IServiceProvider serviceProvide // Expression preferences CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferObjectInitializer, ServicesVSResources.Prefer_object_initializer, s_preferObjectInitializer, s_preferObjectInitializer, this, optionStore, expressionPreferencesGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferCollectionInitializer, ServicesVSResources.Prefer_collection_initializer, s_preferCollectionInitializer, s_preferCollectionInitializer, this, optionStore, expressionPreferencesGroupTitle)); + CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferSwitchExpression, CSharpVSResources.Prefer_switch_expression, s_preferSwitchExpression, s_preferSwitchExpression, this, optionStore, expressionPreferencesGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferPatternMatchingOverIsWithCastCheck, CSharpVSResources.Prefer_pattern_matching_over_is_with_cast_check, s_preferPatternMatchingOverIsWithCastCheck, s_preferPatternMatchingOverIsWithCastCheck, this, optionStore, expressionPreferencesGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferPatternMatchingOverAsWithNullCheck, CSharpVSResources.Prefer_pattern_matching_over_as_with_null_check, s_preferPatternMatchingOverAsWithNullCheck, s_preferPatternMatchingOverAsWithNullCheck, this, optionStore, expressionPreferencesGroupTitle)); CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CodeStyleOptions.PreferConditionalExpressionOverAssignment, ServicesVSResources.Prefer_conditional_expression_over_if_with_assignments, s_preferConditionalExpressionOverIfWithAssignments, s_preferConditionalExpressionOverIfWithAssignments, this, optionStore, expressionPreferencesGroupTitle)); diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf index 7ff6426c1e2ea..c3321440c240a 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.cs.xlf @@ -67,6 +67,11 @@ U kontrol rovnosti odkazů dávat přednost možnosti is null 'is null' is a C# string and should not be localized. + + Prefer switch expression + Prefer switch expression + + Preferred 'using' directive placement Preferred 'using' directive placement diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf index 77f953590292c..ba5187835c6c0 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.de.xlf @@ -67,6 +67,11 @@ "is null" für Verweisübereinstimmungsprüfungen vorziehen 'is null' is a C# string and should not be localized. + + Prefer switch expression + Prefer switch expression + + Preferred 'using' directive placement Preferred 'using' directive placement diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf index 66666349bc635..b5af19d24711e 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.es.xlf @@ -67,6 +67,11 @@ Preferir “is null” para comprobaciones de igualdad de referencias 'is null' is a C# string and should not be localized. + + Prefer switch expression + Prefer switch expression + + Preferred 'using' directive placement Preferred 'using' directive placement diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf index bdd1b687fbcf1..ac958b43d331a 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.fr.xlf @@ -67,6 +67,11 @@ Préférer 'is nul' pour les vérifications d'égalité de référence 'is null' is a C# string and should not be localized. + + Prefer switch expression + Prefer switch expression + + Preferred 'using' directive placement Preferred 'using' directive placement diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf index c8bb34dda7457..a59e88995d481 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.it.xlf @@ -67,6 +67,11 @@ Preferisci 'is null' per i controlli di uguaglianza dei riferimenti 'is null' is a C# string and should not be localized. + + Prefer switch expression + Prefer switch expression + + Preferred 'using' directive placement Preferred 'using' directive placement diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf index 63599153f9635..f640835dcdcf2 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ja.xlf @@ -67,6 +67,11 @@ 参照の等値性のチェックには 'is null' を優先する 'is null' is a C# string and should not be localized. + + Prefer switch expression + Prefer switch expression + + Preferred 'using' directive placement Preferred 'using' directive placement diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf index 22b1f6754c01f..b29b11e3b690b 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ko.xlf @@ -67,6 +67,11 @@ 참조 같음 검사에 대해 'is null' 선호 'is null' is a C# string and should not be localized. + + Prefer switch expression + Prefer switch expression + + Preferred 'using' directive placement Preferred 'using' directive placement diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf index 286f2616cf61e..63a98ec484210 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pl.xlf @@ -67,6 +67,11 @@ Preferuj wyrażenie „is null” w przypadku sprawdzeń odwołań pod kątem równości 'is null' is a C# string and should not be localized. + + Prefer switch expression + Prefer switch expression + + Preferred 'using' directive placement Preferred 'using' directive placement diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf index e9894b50554e4..27ec42e61ceb9 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.pt-BR.xlf @@ -67,6 +67,11 @@ Preferir 'is null' para as verificações de igualdade de referência 'is null' is a C# string and should not be localized. + + Prefer switch expression + Prefer switch expression + + Preferred 'using' directive placement Preferred 'using' directive placement diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf index 502b06898df5b..144057cdb1a9d 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.ru.xlf @@ -67,6 +67,11 @@ Использовать "is null" вместо проверки ссылок на равенство. 'is null' is a C# string and should not be localized. + + Prefer switch expression + Prefer switch expression + + Preferred 'using' directive placement Preferred 'using' directive placement diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf index 181029db61eb7..28476cae9dc81 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.tr.xlf @@ -67,6 +67,11 @@ Başvuru eşitliği denetimleri için 'is null'ı tercih et 'is null' is a C# string and should not be localized. + + Prefer switch expression + Prefer switch expression + + Preferred 'using' directive placement Preferred 'using' directive placement diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf index c003ed3fee968..751b248bd6cf2 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hans.xlf @@ -67,6 +67,11 @@ 引用相等检查偏好 “is null” 'is null' is a C# string and should not be localized. + + Prefer switch expression + Prefer switch expression + + Preferred 'using' directive placement Preferred 'using' directive placement diff --git a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf index 826496173a7c2..d6646c51be6be 100644 --- a/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf +++ b/src/VisualStudio/CSharp/Impl/xlf/CSharpVSResources.zh-Hant.xlf @@ -67,6 +67,11 @@ 參考相等檢查最好使用 'is null' 'is null' is a C# string and should not be localized. + + Prefer switch expression + Prefer switch expression + + Preferred 'using' directive placement Preferred 'using' directive placement diff --git a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb index 95497f2204f55..4b2820f5101c9 100644 --- a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb +++ b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb @@ -100,6 +100,7 @@ csharp_style_expression_bodied_properties = true:silent # Pattern matching preferences csharp_style_pattern_matching_over_as_with_null_check = true:suggestion csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion # Null-checking preferences csharp_style_conditional_delegate_call = true:suggestion @@ -308,6 +309,7 @@ csharp_style_expression_bodied_properties = true:silent # Pattern matching preferences csharp_style_pattern_matching_over_as_with_null_check = true:suggestion csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion # Null-checking preferences csharp_style_conditional_delegate_call = true:suggestion diff --git a/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions.cs b/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions.cs index 4c54a1c33a505..1b8606c1eb4e9 100644 --- a/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions.cs +++ b/src/Workspaces/CSharp/Portable/CodeStyle/CSharpCodeStyleOptions.cs @@ -46,14 +46,23 @@ private static Option CreateOption(OptionGroup group, string name, T defau EditorConfigStorageLocation.ForBoolCodeStyleOption("csharp_style_conditional_delegate_call"), new RoamingProfileStorageLocation("TextEditor.CSharp.Specific.PreferConditionalDelegateCall")}); + public static readonly Option> PreferSwitchExpression = CreateOption( + CSharpCodeStyleOptionGroups.PatternMatching, nameof(PreferSwitchExpression), + defaultValue: CodeStyleOptions.TrueWithSuggestionEnforcement, + storageLocations: new OptionStorageLocation[] { + EditorConfigStorageLocation.ForBoolCodeStyleOption("csharp_style_prefer_switch_expression"), + new RoamingProfileStorageLocation($"TextEditor.CSharp.Specific.{nameof(PreferSwitchExpression)}")}); + public static readonly Option> PreferPatternMatchingOverAsWithNullCheck = CreateOption( - CSharpCodeStyleOptionGroups.PatternMatching, nameof(PreferPatternMatchingOverAsWithNullCheck), defaultValue: CodeStyleOptions.TrueWithSuggestionEnforcement, + CSharpCodeStyleOptionGroups.PatternMatching, nameof(PreferPatternMatchingOverAsWithNullCheck), + defaultValue: CodeStyleOptions.TrueWithSuggestionEnforcement, storageLocations: new OptionStorageLocation[] { EditorConfigStorageLocation.ForBoolCodeStyleOption("csharp_style_pattern_matching_over_as_with_null_check"), new RoamingProfileStorageLocation($"TextEditor.CSharp.Specific.{nameof(PreferPatternMatchingOverAsWithNullCheck)}")}); public static readonly Option> PreferPatternMatchingOverIsWithCastCheck = CreateOption( - CSharpCodeStyleOptionGroups.PatternMatching, nameof(PreferPatternMatchingOverIsWithCastCheck), defaultValue: CodeStyleOptions.TrueWithSuggestionEnforcement, + CSharpCodeStyleOptionGroups.PatternMatching, nameof(PreferPatternMatchingOverIsWithCastCheck), + defaultValue: CodeStyleOptions.TrueWithSuggestionEnforcement, storageLocations: new OptionStorageLocation[] { EditorConfigStorageLocation.ForBoolCodeStyleOption("csharp_style_pattern_matching_over_is_with_cast_check"), new RoamingProfileStorageLocation($"TextEditor.CSharp.Specific.{nameof(PreferPatternMatchingOverIsWithCastCheck)}")}); @@ -272,6 +281,7 @@ public static IEnumerable>> GetCodeStyleOptions() yield return VarWhenTypeIsApparent; yield return VarElsewhere; yield return PreferConditionalDelegateCall; + yield return PreferSwitchExpression; yield return PreferPatternMatchingOverAsWithNullCheck; yield return PreferPatternMatchingOverIsWithCastCheck; yield return PreferSimpleDefaultExpression;