diff --git a/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchTests.cs b/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchStatementTests.cs similarity index 98% rename from src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchTests.cs rename to src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchStatementTests.cs index cbbb627f17448d37e5fea72e1effb70c6fa32f4c..726a049861873fd00a95a2cb1ec3f2f14e8b1cfc 100644 --- a/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchTests.cs +++ b/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchStatementTests.cs @@ -10,10 +10,10 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.PopulateSwitch { - public partial class PopulateSwitchTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + public partial class PopulateSwitchStatementTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest { internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) - => (new PopulateSwitchDiagnosticAnalyzer(), new PopulateSwitchCodeFixProvider()); + => (new PopulateSwitchStatementDiagnosticAnalyzer(), new PopulateSwitchStatementCodeFixProvider()); [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsPopulateSwitch)] public async Task OnlyOnFirstToken() diff --git a/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchTests_FixAllTests.cs b/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchStatementTests_FixAllTests.cs similarity index 98% rename from src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchTests_FixAllTests.cs rename to src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchStatementTests_FixAllTests.cs index e09f8887b6eb76c657e62af6bfe3b781dfb80bda..7ce59a87aea1abeeec55c6f192fe02e777c5f0ed 100644 --- a/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchTests_FixAllTests.cs +++ b/src/EditorFeatures/CSharpTest/PopulateSwitch/PopulateSwitchStatementTests_FixAllTests.cs @@ -6,7 +6,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.PopulateSwitch { - public partial class PopulateSwitchTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + public partial class PopulateSwitchStatementTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest { [Fact] [Trait(Traits.Feature, Traits.Features.CodeActionsPopulateSwitch)] diff --git a/src/EditorFeatures/VisualBasicTest/PopulateSwitch/PopulateSwitchTests.vb b/src/EditorFeatures/VisualBasicTest/PopulateSwitch/PopulateSwitchStatementTests.vb similarity index 98% rename from src/EditorFeatures/VisualBasicTest/PopulateSwitch/PopulateSwitchTests.vb rename to src/EditorFeatures/VisualBasicTest/PopulateSwitch/PopulateSwitchStatementTests.vb index fc657c49748d6aa2ea78527cf83836f9d19cde61..777f2ce3444e280735762e42526aa6024385b098 100644 --- a/src/EditorFeatures/VisualBasicTest/PopulateSwitch/PopulateSwitchTests.vb +++ b/src/EditorFeatures/VisualBasicTest/PopulateSwitch/PopulateSwitchStatementTests.vb @@ -5,11 +5,11 @@ Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.PopulateSwitch Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics.PopulateSwitch - Partial Public Class PopulateSwitchTests + Partial Public Class PopulateSwitchStatementTests Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider) - Return (New PopulateSwitchDiagnosticAnalyzer(), New PopulateSwitchCodeFixProvider()) + Return (New PopulateSwitchStatementDiagnosticAnalyzer(), New PopulateSwitchStatementCodeFixProvider()) End Function diff --git a/src/EditorFeatures/VisualBasicTest/PopulateSwitch/PopulateSwitchTests_FixAllTests.vb b/src/EditorFeatures/VisualBasicTest/PopulateSwitch/PopulateSwitchStatementTests_FixAllTests.vb similarity index 99% rename from src/EditorFeatures/VisualBasicTest/PopulateSwitch/PopulateSwitchTests_FixAllTests.vb rename to src/EditorFeatures/VisualBasicTest/PopulateSwitch/PopulateSwitchStatementTests_FixAllTests.vb index 266eea2a20da2462017f0f08dd38c41406dfc09c..6528128cefcb00a751f25a4cd160c92a9a38d9af 100644 --- a/src/EditorFeatures/VisualBasicTest/PopulateSwitch/PopulateSwitchTests_FixAllTests.vb +++ b/src/EditorFeatures/VisualBasicTest/PopulateSwitch/PopulateSwitchStatementTests_FixAllTests.vb @@ -1,5 +1,5 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics.PopulateSwitch - Partial Public Class PopulateSwitchTests + Partial Public Class PopulateSwitchStatementTests Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest diff --git a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs index 2d13ee4d60a70d347ede6d398ceda3f95624e541..36f73bd853ecbe14b5195ffd37189e3a8025f961 100644 --- a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs +++ b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs @@ -15,7 +15,7 @@ internal static class IDEDiagnosticIds public const string UseImplicitTypeDiagnosticId = "IDE0007"; public const string UseExplicitTypeDiagnosticId = "IDE0008"; public const string AddQualificationDiagnosticId = "IDE0009"; - public const string PopulateSwitchDiagnosticId = "IDE0010"; + public const string PopulateSwitchStatementDiagnosticId = "IDE0010"; public const string AddBracesDiagnosticId = "IDE0011"; // IDE0012-IDE0015 deprecated and replaced with PreferBuiltInOrFrameworkTypeDiagnosticId (IDE0049) @@ -115,6 +115,8 @@ internal static class IDEDiagnosticIds public const string UseSystemHashCode = "IDE0070"; + public const string PopulateExpressionStatementDiagnosticId = "IDE0071"; + // Analyzer error Ids public const string AnalyzerChangedId = "IDE1001"; public const string AnalyzerDependencyConflictId = "IDE1002"; diff --git a/src/Features/Core/Portable/PopulateSwitch/PopulateSwitchCodeFixProvider.cs b/src/Features/Core/Portable/PopulateSwitch/PopulateSwitchCodeFixProvider.cs index 19d642344904260e0475596f1f264d5f17e35ae5..2f2c5f6d326e04f20009c1eef29e07f628a447eb 100644 --- a/src/Features/Core/Portable/PopulateSwitch/PopulateSwitchCodeFixProvider.cs +++ b/src/Features/Core/Portable/PopulateSwitch/PopulateSwitchCodeFixProvider.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; @@ -21,19 +22,17 @@ namespace Microsoft.CodeAnalysis.PopulateSwitch { - [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, - Name = PredefinedCodeFixProviderNames.PopulateSwitch), Shared] - [ExtensionOrder(After = PredefinedCodeFixProviderNames.ImplementInterface)] - internal class PopulateSwitchCodeFixProvider : SyntaxEditorBasedCodeFixProvider + internal abstract class AbstractPopulateSwitchCodeFixProvider + : SyntaxEditorBasedCodeFixProvider + where TSwitchOperation : IOperation { - [ImportingConstructor] - public PopulateSwitchCodeFixProvider() + public sealed override ImmutableArray FixableDiagnosticIds { get; } + + protected AbstractPopulateSwitchCodeFixProvider(string diagnosticId) { + FixableDiagnosticIds = ImmutableArray.Create(diagnosticId); } - public override ImmutableArray FixableDiagnosticIds - => ImmutableArray.Create(IDEDiagnosticIds.PopulateSwitchDiagnosticId); - internal sealed override CodeFixCategory CodeFixCategory => CodeFixCategory.Custom; public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) @@ -129,8 +128,76 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) var hasMissingDefaultCase = bool.Parse(diagnostic.Properties[PopulateSwitchHelpers.MissingDefaultCase]); var switchLocation = diagnostic.AdditionalLocations[0]; - var switchNode = switchLocation.FindNode(cancellationToken); - var switchStatement = (ISwitchOperation)model.GetOperation(switchNode, cancellationToken); + var switchNode = switchLocation.FindNode(getInnermostNodeForTie: true, cancellationToken); + var switchStatement = (TSwitchOperation)model.GetOperation(switchNode, cancellationToken); + + FixOneDiagnostic( + document, editor, addCases, addDefaultCase, onlyOneDiagnostic, + hasMissingCases, hasMissingDefaultCase, switchNode, switchStatement); + } + + protected abstract void FixOneDiagnostic( + Document document, SyntaxEditor editor, + bool addCases, bool addDefaultCase, bool onlyOneDiagnostic, + bool hasMissingCases, bool hasMissingDefaultCase, + SyntaxNode switchNode, TSwitchOperation switchStatement); + + protected static void AddMissingBraces( + Document document, + ref SyntaxNode root, + ref SyntaxNode switchNode) + { + // Parsing of the switch may have caused imbalanced braces. i.e. the switch + // may have consumed a brace that was intended for a higher level construct. + // So balance the tree first, then do the switch replacement. + var syntaxFacts = document.GetLanguageService(); + syntaxFacts.AddFirstMissingCloseBrace( + root, switchNode, out var newRoot, out var newSwitchNode); + + root = newRoot; + switchNode = newSwitchNode; + } + + protected override Task FixAllAsync( + Document document, + ImmutableArray diagnostics, + SyntaxEditor editor, + CancellationToken cancellationToken) + { + // If the user is performing a fix-all, then fix up all the issues we see. i.e. + // add missing cases and missing 'default' cases for any switches we reported an + // issue on. + return FixWithEditorAsync(document, editor, diagnostics, + addCases: true, addDefaultCase: true, + cancellationToken: cancellationToken); + } + + private class MyCodeAction : CodeAction.DocumentChangeAction + { + public MyCodeAction(string title, Func> createChangedDocument) + : base(title, createChangedDocument) + { + } + } + } + + [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic, + Name = PredefinedCodeFixProviderNames.PopulateSwitch), Shared] + [ExtensionOrder(After = PredefinedCodeFixProviderNames.ImplementInterface)] + internal class PopulateSwitchStatementCodeFixProvider : AbstractPopulateSwitchCodeFixProvider + { + [ImportingConstructor] + public PopulateSwitchStatementCodeFixProvider() + : base(IDEDiagnosticIds.PopulateSwitchStatementDiagnosticId) + { + } + + protected override void FixOneDiagnostic( + Document document, SyntaxEditor editor, + bool addCases, bool addDefaultCase, bool onlyOneDiagnostic, + bool hasMissingCases, bool hasMissingDefaultCase, + SyntaxNode switchNode, ISwitchOperation switchStatement) + { var enumType = switchStatement.Value.Type; var generator = editor.Generator; @@ -144,7 +211,7 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) var missingEnumMembers = PopulateSwitchHelpers.GetMissingEnumMembers(switchStatement); var missingSections = from e in missingEnumMembers - let caseLabel = generator.MemberAccessExpression(generator.TypeExpression(enumType), e.Name).WithAdditionalAnnotations(Simplifier.Annotation) + let caseLabel = generator.MemberAccessExpression(generator.TypeExpression(enumType), e.Name).WithAdditionalAnnotations(Simplifier.Annotation) let section = generator.SwitchSection(caseLabel, sectionStatements) select section; @@ -160,7 +227,7 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) var insertLocation = InsertPosition(switchStatement); var newSwitchNode = generator.InsertSwitchSections(switchNode, insertLocation, newSections) - .WithAdditionalAnnotations(Formatter.Annotation); + .WithAdditionalAnnotations(Formatter.Annotation); if (onlyOneDiagnostic) { @@ -171,7 +238,7 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) var root = editor.OriginalRoot; AddMissingBraces(document, ref root, ref switchNode); - var newRoot = root.ReplaceNode(switchNode, newSwitchNode); + var newRoot = root.ReplaceNode(switchNode, newSwitchNode); editor.ReplaceNode(editor.OriginalRoot, newRoot); } else @@ -180,22 +247,6 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) } } - private void AddMissingBraces( - Document document, - ref SyntaxNode root, - ref SyntaxNode switchNode) - { - // Parsing of the switch may have caused imbalanced braces. i.e. the switch - // may have consumed a brace that was intended for a higher level construct. - // So balance the tree first, then do the switch replacement. - var syntaxFacts = document.GetLanguageService(); - syntaxFacts.AddFirstMissingCloseBrace( - root, switchNode, out var newRoot, out var newSwitchNode); - - root = newRoot; - switchNode = newSwitchNode; - } - private int InsertPosition(ISwitchOperation switchStatement) { // If the last section has a default label, then we want to be above that. @@ -213,27 +264,5 @@ private int InsertPosition(ISwitchOperation switchStatement) return cases.Length; } - - protected override Task FixAllAsync( - Document document, - ImmutableArray diagnostics, - SyntaxEditor editor, - CancellationToken cancellationToken) - { - // If the user is performing a fix-all, then fix up all the issues we see. i.e. - // add missing cases and missing 'default' cases for any switches we reported an - // issue on. - return FixWithEditorAsync(document, editor, diagnostics, - addCases: true, addDefaultCase: true, - cancellationToken: cancellationToken); - } - - private class MyCodeAction : CodeAction.DocumentChangeAction - { - public MyCodeAction(string title, Func> createChangedDocument) - : base(title, createChangedDocument) - { - } - } } } diff --git a/src/Features/Core/Portable/PopulateSwitch/PopulateSwitchDiagnosticAnalyzer.cs b/src/Features/Core/Portable/PopulateSwitch/PopulateSwitchDiagnosticAnalyzer.cs index 1ff6027cbbd6f0e50d36fd01c9766f12ed0d4e20..68161aee60e2b12617ef1cf685ede5d719b5ee42 100644 --- a/src/Features/Core/Portable/PopulateSwitch/PopulateSwitchDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/PopulateSwitch/PopulateSwitchDiagnosticAnalyzer.cs @@ -1,5 +1,6 @@ // 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.Generic; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CodeStyle; @@ -9,15 +10,15 @@ namespace Microsoft.CodeAnalysis.PopulateSwitch { - [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] - internal sealed class PopulateSwitchDiagnosticAnalyzer : + internal abstract class AbstractPopulateSwitchDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer + where TSwitchOperation : IOperation { private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(FeaturesResources.Add_missing_cases), FeaturesResources.ResourceManager, typeof(FeaturesResources)); private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString(nameof(WorkspacesResources.Populate_switch), WorkspacesResources.ResourceManager, typeof(WorkspacesResources)); - public PopulateSwitchDiagnosticAnalyzer() - : base(IDEDiagnosticIds.PopulateSwitchDiagnosticId, + protected AbstractPopulateSwitchDiagnosticAnalyzer(string diagnosticId) + : base(diagnosticId, option: null, s_localizableTitle, s_localizableMessage) { @@ -25,12 +26,19 @@ public PopulateSwitchDiagnosticAnalyzer() #region Interface methods - protected override void InitializeWorker(AnalysisContext context) - => context.RegisterOperationAction(AnalyzeOperation, OperationKind.Switch); + protected abstract OperationKind OperationKind { get; } + + protected abstract ICollection GetMissingEnumMembers(TSwitchOperation operation); + protected abstract bool HasDefaultCase(TSwitchOperation operation); + + public sealed override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; + + protected sealed override void InitializeWorker(AnalysisContext context) + => context.RegisterOperationAction(AnalyzeOperation, this.OperationKind); private void AnalyzeOperation(OperationAnalysisContext context) { - var switchOperation = (ISwitchOperation)context.Operation; + var switchOperation = (TSwitchOperation)context.Operation; var switchBlock = switchOperation.Syntax; var tree = switchBlock.SyntaxTree; @@ -54,18 +62,34 @@ private void AnalyzeOperation(OperationAnalysisContext context) #endregion private bool SwitchIsIncomplete( - ISwitchOperation switchStatement, + TSwitchOperation operation, out bool missingCases, out bool missingDefaultCase) { - var missingEnumMembers = PopulateSwitchHelpers.GetMissingEnumMembers(switchStatement); + var missingEnumMembers = GetMissingEnumMembers(operation); missingCases = missingEnumMembers.Count > 0; - missingDefaultCase = !PopulateSwitchHelpers.HasDefaultCase(switchStatement); + missingDefaultCase = !HasDefaultCase(operation); // The switch is incomplete if we're missing any cases or we're missing a default case. return missingDefaultCase || missingCases; } + } + + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + internal sealed class PopulateSwitchStatementDiagnosticAnalyzer : + AbstractPopulateSwitchDiagnosticAnalyzer + { + public PopulateSwitchStatementDiagnosticAnalyzer() + : base(IDEDiagnosticIds.PopulateSwitchStatementDiagnosticId) + { + } + + protected override OperationKind OperationKind => OperationKind.Switch; + + protected override ICollection GetMissingEnumMembers(ISwitchOperation operation) + => PopulateSwitchHelpers.GetMissingEnumMembers(operation); - public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; + protected override bool HasDefaultCase(ISwitchOperation operation) + => PopulateSwitchHelpers.HasDefaultCase(operation); } }