Skip to content

Commit df2f1b1

Browse files
committed
Merge branch 'develop'
2 parents af20908 + 3d4acb1 commit df2f1b1

File tree

3 files changed

+132
-33
lines changed

3 files changed

+132
-33
lines changed

VSDiagnostics/VSDiagnostics/VSDiagnostics.Test/Tests/General/SwitchDoesNotHandleAllEnumOptionsTests.cs

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ void Method()
122122
}
123123

124124
[TestMethod]
125-
public void SwitchDoesNotHandleAllEnumOptions_CaseHasDefaultStatement_MissingEnumStatement()
125+
public void SwitchDoesNotHandleAllEnumOptions_CaseHasDefaultStatement_NewStatementsAreAddedAboveDefault()
126126
{
127127
var original = @"
128128
namespace ConsoleApplication1
@@ -139,8 +139,6 @@ void Method()
139139
var e = MyEnum.Fizz;
140140
switch (e)
141141
{
142-
case MyEnum.Fizz:
143-
case MyEnum.Buzz:
144142
default:
145143
break;
146144
}
@@ -165,8 +163,10 @@ void Method()
165163
{
166164
case MyEnum.FizzBuzz:
167165
throw new System.NotImplementedException();
168-
case MyEnum.Fizz:
169166
case MyEnum.Buzz:
167+
throw new System.NotImplementedException();
168+
case MyEnum.Fizz:
169+
throw new System.NotImplementedException();
170170
default:
171171
break;
172172
}
@@ -232,6 +232,7 @@ void Method()
232232
}";
233233

234234
VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString());
235+
VerifyFix(original, result);
235236
}
236237

237238
[TestMethod]
@@ -678,6 +679,67 @@ void Method()
678679
var result = @"
679680
using static System.IO.FileOptions;
680681
682+
namespace ConsoleApplication1
683+
{
684+
class MyClass
685+
{
686+
void Method()
687+
{
688+
var e = DeleteOnClose;
689+
switch (e)
690+
{
691+
case DeleteOnClose:
692+
throw new System.NotImplementedException();
693+
case Asynchronous:
694+
throw new System.NotImplementedException();
695+
case WriteThrough:
696+
throw new System.NotImplementedException();
697+
case None:
698+
throw new System.NotImplementedException();
699+
case Encrypted:
700+
break;
701+
case SequentialScan:
702+
break;
703+
case RandomAccess:
704+
break;
705+
}
706+
}
707+
}
708+
}";
709+
710+
VerifyDiagnostic(original, SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.MessageFormat.ToString());
711+
VerifyFix(original, result);
712+
}
713+
714+
[TestMethod]
715+
public void SwitchDoesNotHandleAllEnumOptions_UsingStaticEnum_MissingEnumStatement_SimplifiesAllStatementsWhenUsingDirectiveIncludesWhitespace()
716+
{
717+
var original = @"
718+
using static System . IO . FileOptions; // Happy maintaining
719+
720+
namespace ConsoleApplication1
721+
{
722+
class MyClass
723+
{
724+
void Method()
725+
{
726+
var e = DeleteOnClose;
727+
switch (e)
728+
{
729+
case Encrypted:
730+
break;
731+
case SequentialScan:
732+
break;
733+
case RandomAccess:
734+
break;
735+
}
736+
}
737+
}
738+
}";
739+
740+
var result = @"
741+
using static System . IO . FileOptions; // Happy maintaining
742+
681743
namespace ConsoleApplication1
682744
{
683745
class MyClass

VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SwitchDoesNotHandleAllEnumOptions/SwitchDoesNotHandleAllEnumOptionsAnalyzer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ private void AnalyzeSymbol(SyntaxNodeAnalysisContext context)
3030
if (switchBlock == null) { return; }
3131

3232
var enumType = context.SemanticModel.GetTypeInfo(switchBlock.Expression).Type as INamedTypeSymbol;
33-
if (enumType == null || enumType.BaseType.SpecialType != SpecialType.System_Enum) { return; }
33+
if (enumType == null || enumType.TypeKind != TypeKind.Enum) { return; }
3434

3535
var caseLabels = switchBlock.Sections.SelectMany(l => l.Labels)
3636
.OfType<CaseSwitchLabelSyntax>()

VSDiagnostics/VSDiagnostics/VSDiagnostics/Diagnostics/General/SwitchDoesNotHandleAllEnumOptions/SwitchDoesNotHandleAllEnumOptionsCodeFix.cs

Lines changed: 65 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,12 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
3131
var statement = root.FindNode(diagnosticSpan);
3232
context.RegisterCodeFix(
3333
CodeAction.Create(VSDiagnosticsResources.SwitchDoesNotHandleAllEnumOptionsCodeFixTitle,
34-
x => AddMissingCaseAsync(context.Document, root, statement),
34+
x => AddMissingCaseAsync(context.Document, (CompilationUnitSyntax)root, statement),
3535
SwitchDoesNotHandleAllEnumOptionsAnalyzer.Rule.Id), diagnostic);
3636
}
3737

38-
private async Task<Solution> AddMissingCaseAsync(Document document, SyntaxNode root, SyntaxNode statement)
38+
private async Task<Solution> AddMissingCaseAsync(Document document, CompilationUnitSyntax root, SyntaxNode statement)
3939
{
40-
var qualifier = "System.";
41-
var usingSystemDirective = ((CompilationUnitSyntax) root).Usings.Where(u => u.Name is IdentifierNameSyntax).FirstOrDefault(u => ((IdentifierNameSyntax) u.Name).Identifier.ValueText == "System");
42-
43-
if (usingSystemDirective != null)
44-
{
45-
qualifier = usingSystemDirective.Alias == null
46-
? string.Empty
47-
: usingSystemDirective.Alias.Name.Identifier.ValueText + ".";
48-
}
49-
5040
var semanticModel = await document.GetSemanticModelAsync();
5141

5242
var switchBlock = (SwitchStatementSyntax)statement;
@@ -57,41 +47,35 @@ private async Task<Solution> AddMissingCaseAsync(Document document, SyntaxNode r
5747
.Select(l => l.Value)
5848
.ToList();
5949

60-
// these are the labels like `MyEnum.EnumMember`
61-
var labels = caseLabels
62-
.OfType<MemberAccessExpressionSyntax>()
63-
.Select(l => l.Name.Identifier.ValueText)
64-
.ToList();
65-
66-
// these are the labels like `EnumMember` (such as when using `using static Namespace.MyEnum;`)
67-
labels.AddRange(caseLabels.OfType<IdentifierNameSyntax>().Select(l => l.Identifier.ValueText));
50+
var missingLabels = GetMissingLabels(caseLabels, enumType);
6851

6952
// use simplified form if there are any in simplified form or if there are not any labels at all
70-
var useSimplifiedForm = caseLabels.OfType<IdentifierNameSyntax>().Any() ||
71-
!caseLabels.OfType<MemberAccessExpressionSyntax>().Any();
72-
73-
// don't create members like ".ctor"
74-
var missingLabels = enumType.MemberNames.Except(labels).Where(m => !m.StartsWith("."));
53+
var useSimplifiedForm = (caseLabels.OfType<IdentifierNameSyntax>().Any() ||
54+
!caseLabels.OfType<MemberAccessExpressionSyntax>().Any()) &&
55+
EnumIsUsingStatic(root, enumType);
7556

76-
var newSections = SyntaxFactory.List(switchBlock.Sections);
57+
var qualifier = GetQualifierForException(root);
7758

7859
var notImplementedException =
7960
SyntaxFactory.ThrowStatement(SyntaxFactory.ParseExpression($" new {qualifier}NotImplementedException()"))
8061
.WithAdditionalAnnotations(Simplifier.Annotation);
62+
var statements = SyntaxFactory.List(new List<StatementSyntax> { notImplementedException });
63+
64+
var newSections = SyntaxFactory.List(switchBlock.Sections);
8165

8266
foreach (var label in missingLabels)
8367
{
68+
// ReSharper disable once PossibleNullReferenceException
8469
var caseLabel =
8570
SyntaxFactory.CaseSwitchLabel(
8671
SyntaxFactory.ParseExpression(useSimplifiedForm ? $" {label}" : $" {enumType.Name}.{label}")
8772
.WithTrailingTrivia(SyntaxFactory.ParseTrailingTrivia(Environment.NewLine)));
88-
89-
var statements = SyntaxFactory.List(new List<StatementSyntax> {notImplementedException.WithAdditionalAnnotations(Simplifier.Annotation)});
9073

9174
var section =
9275
SyntaxFactory.SwitchSection(SyntaxFactory.List(new List<SwitchLabelSyntax> {caseLabel}), statements)
9376
.WithAdditionalAnnotations(Formatter.Annotation);
9477

78+
// ensure that the new cases are above the default case
9579
newSections = newSections.Insert(0, section);
9680
}
9781

@@ -103,5 +87,58 @@ private async Task<Solution> AddMissingCaseAsync(Document document, SyntaxNode r
10387
var newDocument = await Simplifier.ReduceAsync(document.WithSyntaxRoot(newRoot));
10488
return newDocument.Project.Solution;
10589
}
90+
91+
private bool EnumIsUsingStatic(CompilationUnitSyntax root, INamedTypeSymbol enumType)
92+
{
93+
var fullyQualifiedName = enumType.Name;
94+
95+
var containingNamespace = enumType.ContainingNamespace;
96+
while (!string.IsNullOrEmpty(containingNamespace.Name))
97+
{
98+
fullyQualifiedName = fullyQualifiedName.Insert(0, containingNamespace.Name + ".");
99+
containingNamespace = containingNamespace.ContainingNamespace;
100+
}
101+
102+
return root.Usings.Any(u =>
103+
{
104+
if (!u.StaticKeyword.IsKind(SyntaxKind.StaticKeyword)) { return false; }
105+
106+
var name = u.Name as QualifiedNameSyntax;
107+
if (name == null) { return false; }
108+
109+
return new string(name.GetText().ToString().ToCharArray().Where(c => !char.IsWhiteSpace(c)).ToArray()) == fullyQualifiedName;
110+
});
111+
}
112+
113+
private IEnumerable<string> GetMissingLabels(List<ExpressionSyntax> caseLabels, INamedTypeSymbol enumType)
114+
{
115+
// these are the labels like `MyEnum.EnumMember`
116+
var labels = caseLabels
117+
.OfType<MemberAccessExpressionSyntax>()
118+
.Select(l => l.Name.Identifier.ValueText)
119+
.ToList();
120+
121+
// these are the labels like `EnumMember` (such as when using `using static Namespace.MyEnum;`)
122+
labels.AddRange(caseLabels.OfType<IdentifierNameSyntax>().Select(l => l.Identifier.ValueText));
123+
124+
// don't create members like ".ctor"
125+
return enumType.MemberNames.Except(labels).Where(m => !m.StartsWith("."));
126+
}
127+
128+
private string GetQualifierForException(CompilationUnitSyntax root)
129+
{
130+
var qualifier = "System.";
131+
var usingSystemDirective =
132+
root.Usings.Where(u => u.Name is IdentifierNameSyntax)
133+
.FirstOrDefault(u => ((IdentifierNameSyntax) u.Name).Identifier.ValueText == "System");
134+
135+
if (usingSystemDirective != null)
136+
{
137+
qualifier = usingSystemDirective.Alias == null
138+
? string.Empty
139+
: usingSystemDirective.Alias.Name.Identifier.ValueText + ".";
140+
}
141+
return qualifier;
142+
}
106143
}
107144
}

0 commit comments

Comments
 (0)