提交 a4159d46 编写于 作者: C Cyrus Najmabadi

Break out common code for 'populate switch'

上级 22e24f64
......@@ -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()
......
......@@ -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)]
......
......@@ -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
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsPopulateSwitch)>
......
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics.PopulateSwitch
Partial Public Class PopulateSwitchTests
Partial Public Class PopulateSwitchStatementTests
Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest
<Fact>
......
......@@ -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";
......
......@@ -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<TSwitchOperation>
: SyntaxEditorBasedCodeFixProvider
where TSwitchOperation : IOperation
{
[ImportingConstructor]
public PopulateSwitchCodeFixProvider()
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; }
protected AbstractPopulateSwitchCodeFixProvider(string diagnosticId)
{
FixableDiagnosticIds = ImmutableArray.Create(diagnosticId);
}
public override ImmutableArray<string> 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<ISyntaxFactsService>();
syntaxFacts.AddFirstMissingCloseBrace(
root, switchNode, out var newRoot, out var newSwitchNode);
root = newRoot;
switchNode = newSwitchNode;
}
protected override Task FixAllAsync(
Document document,
ImmutableArray<Diagnostic> 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<CancellationToken, Task<Document>> createChangedDocument)
: base(title, createChangedDocument)
{
}
}
}
[ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic,
Name = PredefinedCodeFixProviderNames.PopulateSwitch), Shared]
[ExtensionOrder(After = PredefinedCodeFixProviderNames.ImplementInterface)]
internal class PopulateSwitchStatementCodeFixProvider : AbstractPopulateSwitchCodeFixProvider<ISwitchOperation>
{
[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<SyntaxNode>(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<SyntaxNode>(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<SyntaxNode>(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<ISyntaxFactsService>();
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<Diagnostic> 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<CancellationToken, Task<Document>> createChangedDocument)
: base(title, createChangedDocument)
{
}
}
}
}
// 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<TSwitchOperation> :
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<ISymbol> 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<ISwitchOperation>
{
public PopulateSwitchStatementDiagnosticAnalyzer()
: base(IDEDiagnosticIds.PopulateSwitchStatementDiagnosticId)
{
}
protected override OperationKind OperationKind => OperationKind.Switch;
protected override ICollection<ISymbol> GetMissingEnumMembers(ISwitchOperation operation)
=> PopulateSwitchHelpers.GetMissingEnumMembers(operation);
public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
protected override bool HasDefaultCase(ISwitchOperation operation)
=> PopulateSwitchHelpers.HasDefaultCase(operation);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册