提交 edc99e42 编写于 作者: C CyrusNajmabadi

Move to using an IOperation analyzer for PopulateSwitch.

上级 43f2abfa
...@@ -294,7 +294,7 @@ ...@@ -294,7 +294,7 @@
<Compile Include="Navigation\NavigationOptions.cs" /> <Compile Include="Navigation\NavigationOptions.cs" />
<Compile Include="Navigation\NavigationOptionsProvider.cs" /> <Compile Include="Navigation\NavigationOptionsProvider.cs" />
<Compile Include="PopulateSwitch\AbstractPopulateSwitchCodeFixProvider.cs" /> <Compile Include="PopulateSwitch\AbstractPopulateSwitchCodeFixProvider.cs" />
<Compile Include="PopulateSwitch\AbstractPopulateSwitchDiagnosticAnalyzer.cs" /> <Compile Include="PopulateSwitch\PopulateSwitchDiagnosticAnalyzer.cs" />
<Compile Include="PopulateSwitch\PopulateSwitchHelpers.cs" /> <Compile Include="PopulateSwitch\PopulateSwitchHelpers.cs" />
<Compile Include="QuickInfo\QuickInfoUtilities.cs" /> <Compile Include="QuickInfo\QuickInfoUtilities.cs" />
<Compile Include="RemoveUnnecessaryImports\AbstractRemoveUnnecessaryImportsService.cs" /> <Compile Include="RemoveUnnecessaryImports\AbstractRemoveUnnecessaryImportsService.cs" />
......
...@@ -10,15 +10,16 @@ ...@@ -10,15 +10,16 @@
using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Semantics;
using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities; using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.PopulateSwitch namespace Microsoft.CodeAnalysis.PopulateSwitch
{ {
internal abstract class AbstractPopulateSwitchCodeFixProvider<TSwitchBlockSyntax, TExpressionSyntax, TSwitchSectionSyntax> : CodeFixProvider internal abstract class AbstractPopulateSwitchCodeFixProvider<TSwitchBlockSyntax, TExpressionSyntax, TSwitchSectionSyntax> : CodeFixProvider
where TSwitchBlockSyntax : SyntaxNode where TSwitchBlockSyntax : SyntaxNode
where TSwitchSectionSyntax : SyntaxNode where TSwitchSectionSyntax : SyntaxNode
where TExpressionSyntax : SyntaxNode where TExpressionSyntax : SyntaxNode
{ {
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(IDEDiagnosticIds.PopulateSwitchDiagnosticId); public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(IDEDiagnosticIds.PopulateSwitchDiagnosticId);
...@@ -63,16 +64,12 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) ...@@ -63,16 +64,12 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
return SpecializedTasks.EmptyTask; return SpecializedTasks.EmptyTask;
} }
protected abstract TExpressionSyntax GetSwitchExpression(TSwitchBlockSyntax switchBlock);
protected abstract int InsertPosition(SyntaxList<TSwitchSectionSyntax> sections); protected abstract int InsertPosition(SyntaxList<TSwitchSectionSyntax> sections);
protected abstract SyntaxList<TSwitchSectionSyntax> GetSwitchSections(TSwitchBlockSyntax switchBlock); protected abstract SyntaxList<TSwitchSectionSyntax> GetSwitchSections(TSwitchBlockSyntax switchBlock);
protected abstract TSwitchBlockSyntax NewSwitchNode(TSwitchBlockSyntax switchBlock, SyntaxList<TSwitchSectionSyntax> sections); protected abstract TSwitchBlockSyntax NewSwitchNode(TSwitchBlockSyntax switchBlock, SyntaxList<TSwitchSectionSyntax> sections);
protected abstract List<TExpressionSyntax> GetCaseLabels(TSwitchBlockSyntax switchBlock, out bool containsDefaultLabel);
private async Task<Document> AddMissingSwitchCasesAsync( private async Task<Document> AddMissingSwitchCasesAsync(
CodeFixContext context, bool includeMissingCases, bool includeDefaultCase) CodeFixContext context, bool includeMissingCases, bool includeDefaultCase)
{ {
...@@ -82,13 +79,13 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) ...@@ -82,13 +79,13 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var switchNode = (TSwitchBlockSyntax) root.FindNode(span);
var enumType = (INamedTypeSymbol)model.GetTypeInfo(GetSwitchExpression(switchNode)).Type; var switchNode = (TSwitchBlockSyntax)root.FindNode(span);
var switchStatement = (ISwitchStatement)model.GetOperation(switchNode, cancellationToken);
var enumType = switchStatement.Value.Type;
bool containsDefaultCase; var containsDefaultCase = PopulateSwitchHelpers.HasDefaultCase(switchStatement);
var caseLabels = GetCaseLabels(switchNode, out containsDefaultCase); var missingLabels = PopulateSwitchHelpers.GetMissingEnumMembers(switchStatement, enumType);
var missingLabels = PopulateSwitchHelpers.GetMissingSwitchCases(model, enumType, caseLabels);
var generator = SyntaxGenerator.GetGenerator(document); var generator = SyntaxGenerator.GetGenerator(document);
......
...@@ -3,13 +3,14 @@ ...@@ -3,13 +3,14 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Diagnostics;
using System.Diagnostics; using System.Diagnostics;
using Microsoft.CodeAnalysis.Semantics;
using System.Linq;
using System;
namespace Microsoft.CodeAnalysis.PopulateSwitch namespace Microsoft.CodeAnalysis.PopulateSwitch
{ {
internal abstract class AbstractPopulateSwitchDiagnosticAnalyzerBase<TLanguageKindEnum, TSwitchBlockSyntax, TExpressionSyntax> : DiagnosticAnalyzer, IBuiltInAnalyzer [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
where TLanguageKindEnum : struct internal class PopulateSwitchDiagnosticAnalyzer : DiagnosticAnalyzer, IBuiltInAnalyzer
where TSwitchBlockSyntax : SyntaxNode
where TExpressionSyntax : SyntaxNode
{ {
private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(FeaturesResources.Add_missing_switch_cases), FeaturesResources.ResourceManager, typeof(FeaturesResources)); private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(FeaturesResources.Add_missing_switch_cases), FeaturesResources.ResourceManager, typeof(FeaturesResources));
private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString(nameof(WorkspacesResources.PopulateSwitch), WorkspacesResources.ResourceManager, typeof(WorkspacesResources)); private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString(nameof(WorkspacesResources.PopulateSwitch), WorkspacesResources.ResourceManager, typeof(WorkspacesResources));
...@@ -23,26 +24,22 @@ internal abstract class AbstractPopulateSwitchDiagnosticAnalyzerBase<TLanguageKi ...@@ -23,26 +24,22 @@ internal abstract class AbstractPopulateSwitchDiagnosticAnalyzerBase<TLanguageKi
#region Interface methods #region Interface methods
protected abstract ImmutableArray<TLanguageKindEnum> SyntaxKindsOfInterest { get; }
protected abstract TExpressionSyntax GetExpression(TSwitchBlockSyntax node);
protected abstract List<TExpressionSyntax> GetCaseLabels(TSwitchBlockSyntax switchBlock, out bool hasDefaultCase);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_descriptor); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(s_descriptor);
public override void Initialize(AnalysisContext context) public override void Initialize(AnalysisContext context)
{ {
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKindsOfInterest); context.RegisterOperationAction(AnalyzeOperation, OperationKind.SwitchStatement);
} }
private void AnalyzeNode(SyntaxNodeAnalysisContext context) private void AnalyzeOperation(OperationAnalysisContext context)
{ {
var model = context.SemanticModel; var switchOperation = (ISwitchStatement)context.Operation;
var tree = model.SyntaxTree; var switchBlock = switchOperation.Syntax;
var switchBlock = (TSwitchBlockSyntax)context.Node; var tree = switchBlock.SyntaxTree;
bool missingCases; bool missingCases;
bool missingDefaultCase; bool missingDefaultCase;
if (SwitchIsIncomplete(model, switchBlock, out missingCases, out missingDefaultCase) && if (SwitchIsIncomplete(switchOperation, out missingCases, out missingDefaultCase) &&
!tree.OverlapsHiddenPosition(switchBlock.Span, context.CancellationToken)) !tree.OverlapsHiddenPosition(switchBlock.Span, context.CancellationToken))
{ {
Debug.Assert(missingCases || missingDefaultCase); Debug.Assert(missingCases || missingDefaultCase);
...@@ -59,32 +56,28 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context) ...@@ -59,32 +56,28 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context)
#endregion #endregion
private bool SwitchIsIncomplete( private bool SwitchIsIncomplete(
SemanticModel model, TSwitchBlockSyntax node, ISwitchStatement switchStatement,
out bool missingCases, out bool missingDefaultCase) out bool missingCases, out bool missingDefaultCase)
{ {
bool hasDefaultCase; missingDefaultCase = !PopulateSwitchHelpers.HasDefaultCase(switchStatement);
var caseLabels = GetCaseLabels(node, out hasDefaultCase);
missingDefaultCase = !hasDefaultCase;
missingCases = false; missingCases = false;
// We know the switch has a 'default' label. Now we need to determine if there are var switchExpression = switchStatement.Value;
// any missing labels so that we can offer to generate them for the user. var switchExpressionType = switchExpression?.Type;
// If we can't determine the type of this switch, or we're switching over someting if (switchExpressionType?.TypeKind == TypeKind.Enum)
// that sin't an enum, just consider this switch complete. We can't add any cases
// here.
var enumType = model.GetTypeInfo(GetExpression(node)).Type as INamedTypeSymbol;
if (enumType != null && enumType.TypeKind == TypeKind.Enum)
{ {
var missingSwitchCases = PopulateSwitchHelpers.GetMissingSwitchCases(model, enumType, caseLabels); var missingEnumMembers = PopulateSwitchHelpers.GetMissingEnumMembers(
missingCases = missingSwitchCases.Count > 0; switchStatement, switchExpressionType);
missingCases = missingEnumMembers.Count > 0;
} }
// The switch is incomplete if we're missing any cases or we're missing a default case. // The switch is incomplete if we're missing any cases or we're missing a default case.
return missingDefaultCase || missingCases; return missingDefaultCase || missingCases;
} }
public DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SyntaxAnalysis;
public DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
} }
} }
\ No newline at end of file
using System.Collections.Generic; using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Semantics;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities; using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.PopulateSwitch namespace Microsoft.CodeAnalysis.PopulateSwitch
...@@ -8,12 +11,89 @@ internal static class PopulateSwitchHelpers ...@@ -8,12 +11,89 @@ internal static class PopulateSwitchHelpers
public const string MissingCases = nameof(MissingCases); public const string MissingCases = nameof(MissingCases);
public const string MissingDefaultCase = nameof(MissingDefaultCase); public const string MissingDefaultCase = nameof(MissingDefaultCase);
public static IReadOnlyList<ISymbol> GetMissingSwitchCases<TExpressionSyntax>( public static bool HasDefaultCase(ISwitchStatement switchStatement)
SemanticModel model, {
INamedTypeSymbol enumType, foreach (var switchCase in switchStatement.Cases)
IReadOnlyList<TExpressionSyntax> labelNames) where TExpressionSyntax : SyntaxNode {
if (HasDefaultCase(switchCase))
{
return true;
}
}
return false;
}
private static bool HasDefaultCase(ISwitchCase switchCase)
{
foreach (var clause in switchCase.Clauses)
{
if (clause.CaseKind == CaseKind.Default)
{
return true;
}
}
return false;
}
public static ICollection<ISymbol> GetMissingEnumMembers(
ISwitchStatement switchStatement, ITypeSymbol enumType)
{
var enumMembers = new Dictionary<long, ISymbol>();
if (!TryGetAllEnumMembers(enumType, enumMembers) ||
!TryRemoveExistingEnumMembers(switchStatement, enumMembers))
{
return SpecializedCollections.EmptyCollection<ISymbol>();
}
return enumMembers.Values;
}
private static bool TryRemoveExistingEnumMembers(ISwitchStatement switchStatement, Dictionary<long, ISymbol> enumValues)
{
foreach (var switchCase in switchStatement.Cases)
{
foreach (var clause in switchCase.Clauses)
{
switch (clause.CaseKind)
{
default:
case CaseKind.None:
case CaseKind.Relational:
case CaseKind.Range:
// This was some sort of complex switch. For now just ignore
// these and assume that they're complete.
return false;
case CaseKind.Default:
// ignore the 'default/else' clause.
continue;
case CaseKind.SingleValue:
var value = ((ISingleValueCaseClause)clause).Value;
if (!value.ConstantValue.HasValue)
{
// We had a case which didn't resolve properly.
// Assume the switch is complete.
return false;
}
var caseValue = IntegerUtilities.ToInt64(value.ConstantValue.Value);
enumValues.Remove(caseValue);
break;
}
}
}
return true;
}
private static bool TryGetAllEnumMembers(
ITypeSymbol enumType,
Dictionary<long, ISymbol> enumValues)
{ {
var missingSwitchCases = new List<ISymbol>();
foreach (var member in enumType.GetMembers()) foreach (var member in enumType.GetMembers())
{ {
// skip `.ctor` and `__value` // skip `.ctor` and `__value`
...@@ -23,24 +103,24 @@ internal static class PopulateSwitchHelpers ...@@ -23,24 +103,24 @@ internal static class PopulateSwitchHelpers
continue; continue;
} }
missingSwitchCases.Add(member); if (fieldSymbol.ConstantValue == null)
}
foreach (var label in labelNames)
{
var symbol = model.GetSymbolInfo(label).Symbol;
if (symbol == null)
{ {
// something is wrong with the label and the SemanticModel was unable to // We have an enum that has problems with it (i.e. non-const members). We won't
// determine its symbol. Abort the analyzer by considering this switch // be able to determine properly if the switch is complete. Assume it is so we
// statement as complete. // don't offer to do anything.
return SpecializedCollections.EmptyReadOnlyList<ISymbol>(); return false;
} }
missingSwitchCases.Remove(symbol); // Multiple enum members may have the same value. Only consider the first one
// we run int.
var enumValue = IntegerUtilities.ToInt64(fieldSymbol.ConstantValue);
if (!enumValues.ContainsKey(enumValue))
{
enumValues.Add(enumValue, fieldSymbol);
}
} }
return missingSwitchCases; return true;
} }
} }
} }
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册