Skip to content

Commit

Permalink
add EM0015 Open Interface Subtype of a Closed Type Must Be a Case
Browse files Browse the repository at this point in the history
  • Loading branch information
WalkerCodeRanger committed Nov 4, 2019
1 parent 1a50252 commit f1cf4ee
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace ExhaustiveMatching.Analyzer.Tests
public class TypeDeclarationAnalyzerTests : CodeFixVerifier
{
[TestMethod]
public void SubtypeOfClosedTypeMustBeCase()
public void ConcreteSubtypeOfClosedTypeMustBeCase()
{
const string test = @"using ExhaustiveMatching;
namespace TestNamespace
Expand All @@ -35,6 +35,32 @@ public class Triangle : Shape { }
VerifyCSharpDiagnostic(test, expected);
}

[TestMethod]
public void OpenInterfaceSubtypeOfClosedTypeMustBeCase()
{
const string test = @"using ExhaustiveMatching;
namespace TestNamespace
{
[Closed(
typeof(ISquare),
typeof(ICircle))]
public interface IShape { }
public interface ISquare : IShape { }
public interface ICircle : IShape { }
public interface ITriangle : IShape { }
}";

var expected = new DiagnosticResult
{
Id = "EM0015",
Message = "Open interface TestNamespace.ITriangle is not a case of its closed supertype: TestNamespace.IShape",
Severity = DiagnosticSeverity.Error,
Locations = new[] { new DiagnosticResultLocation("Test0.cs", 10, 22, 9) }
};

VerifyCSharpDiagnostic(test, expected);
}

[TestMethod]
public void CaseTypeMustBeSubtype()
{
Expand Down
2 changes: 1 addition & 1 deletion ExhaustiveMatching.Analyzer.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package>
<metadata>
<id>ExhaustiveMatching.Analyzer</id>
<version>0.3.2</version>
<version>0.4.0</version>
<authors>Jeff Walker</authors>
<owners>Jeff Walker</owners>
<license type="expression">BSD-3-Clause</license>
Expand Down
11 changes: 10 additions & 1 deletion ExhaustiveMatching.Analyzer/ExhaustiveMatchAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public class ExhaustiveMatchAnalyzer : DiagnosticAnalyzer
private static readonly LocalizableString EM0014Message = LoadString(nameof(Resources.EM0014Message));
private static readonly LocalizableString EM0014Description = LoadString(Resources.EM0014Description);

private static readonly LocalizableString EM0015Title = LoadString(nameof(Resources.EM0015Title));
private static readonly LocalizableString EM0015Message = LoadString(nameof(Resources.EM0015Message));
private static readonly LocalizableString EM0015Description = LoadString(Resources.EM0015Description);

private static readonly LocalizableString EM0100Title = LoadString(nameof(Resources.EM0100Title));
private static readonly LocalizableString EM0100Message = LoadString(nameof(Resources.EM0100Message));
private static readonly LocalizableString EM0100Description = LoadString(Resources.EM0100Description);
Expand Down Expand Up @@ -86,6 +90,10 @@ public class ExhaustiveMatchAnalyzer : DiagnosticAnalyzer
new DiagnosticDescriptor("EM0014", EM0014Title, EM0014Message, Category,
DiagnosticSeverity.Error, isEnabledByDefault: true, EM0014Description);

public static readonly DiagnosticDescriptor OpenInterfaceSubtypeMustBeCaseOfClosedType =
new DiagnosticDescriptor("EM0015", EM0015Title, EM0015Message, Category,
DiagnosticSeverity.Error, isEnabledByDefault: true, EM0015Description);

public static readonly DiagnosticDescriptor WhenGuardNotSupported =
new DiagnosticDescriptor("EM0100", EM0100Title, EM0100Message, Category,
DiagnosticSeverity.Error, isEnabledByDefault: true, EM0100Description);
Expand All @@ -105,7 +113,8 @@ public class ExhaustiveMatchAnalyzer : DiagnosticAnalyzer
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(NotExhaustiveEnumSwitch, NotExhaustiveObjectSwitch,
NotExhaustiveNullableEnumSwitch, ConcreteSubtypeMustBeCaseOfClosedType,
MustBeDirectSubtype, MustBeSubtype, SubtypeMustBeCovered, WhenGuardNotSupported,
MustBeDirectSubtype, MustBeSubtype, SubtypeMustBeCovered,
OpenInterfaceSubtypeMustBeCaseOfClosedType, WhenGuardNotSupported,
CaseClauseTypeNotSupported, OpenTypeNotSupported, MatchMustBeOnCaseType);

public override void Initialize(AnalysisContext context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput>
<AssemblyVersion>0.3.2.0</AssemblyVersion>
<FileVersion>0.3.2.0</FileVersion>
<Version>0.3.2</Version>
<AssemblyVersion>0.4.0.0</AssemblyVersion>
<FileVersion>0.4.0.0</FileVersion>
<Version>0.4.0</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
27 changes: 27 additions & 0 deletions ExhaustiveMatching.Analyzer/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions ExhaustiveMatching.Analyzer/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,15 @@
<data name="EM0014Title" xml:space="preserve">
<value>Concrete Subtype of a Closed Type Must Be a Covered</value>
</data>
<data name="EM0015Description" xml:space="preserve">
<value>An open interface subtype of a type marked with the "Closed" attribute must be a case of the closed type.</value>
</data>
<data name="EM0015Message" xml:space="preserve">
<value>Open interface {0} is not a case of its closed supertype: {1}</value>
</data>
<data name="EM0015Title" xml:space="preserve">
<value>Open Interface Subtype of a Closed Type Must Be a Case</value>
</data>
<data name="EM0100Description" xml:space="preserve">
<value>An exhustive switch statement does not support case clauses with "when" guards</value>
</data>
Expand Down
18 changes: 14 additions & 4 deletions ExhaustiveMatching.Analyzer/TypeDeclarationAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ private static void MustBeCase(
ITypeSymbol typeSymbol,
INamedTypeSymbol closedAttribute)
{
if (typeSymbol.IsAbstract)
var isConcrete = !typeSymbol.IsAbstract;
var isOpenInterface = typeSymbol.TypeKind == TypeKind.Interface
&& !typeSymbol.HasAttribute(closedAttribute);
if (!isConcrete && !isOpenInterface)
return;

// Any concrete type directly inheriting from a closed type must be listed in the cases
// Any concrete type or open interface directly inheriting from a closed type must be listed in the cases
var directSuperTypes = typeSymbol.DirectSuperTypes();
var closedSuperTypes = directSuperTypes
.Where(t => t.HasAttribute(closedAttribute))
Expand All @@ -44,11 +47,18 @@ private static void MustBeCase(
if (isMember)
continue;

var diagnostic = Diagnostic.Create(ExhaustiveMatchAnalyzer.ConcreteSubtypeMustBeCaseOfClosedType,
typeDeclaration.Identifier.GetLocation(), typeSymbol.GetFullName(), superType.GetFullName());
var descriptor = isConcrete
? ExhaustiveMatchAnalyzer.ConcreteSubtypeMustBeCaseOfClosedType
// else isOpenInterface is always true
: ExhaustiveMatchAnalyzer.OpenInterfaceSubtypeMustBeCaseOfClosedType;

var diagnostic = Diagnostic.Create(descriptor, typeDeclaration.Identifier.GetLocation(),
typeSymbol.GetFullName(), superType.GetFullName());
context.ReportDiagnostic(diagnostic);
}

if (!isConcrete) return;

// Any concrete type indirectly inheriting from a closed type must be covered by a case type
// that isn't itself closed. If it were closed, then it could be matched by all the cases, and
// this type would not be matched.
Expand Down
6 changes: 3 additions & 3 deletions ExhaustiveMatching/ExhaustiveMatching.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyVersion>0.3.2.0</AssemblyVersion>
<FileVersion>0.3.2.0</FileVersion>
<Version>0.3.2</Version>
<AssemblyVersion>0.4.0.0</AssemblyVersion>
<FileVersion>0.4.0.0</FileVersion>
<Version>0.4.0</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ The analyzer reports various errors for incorrect code. The table below gives a
<th>EM0014</th>
<td>A concrete subtype of a closed type is not covered by some case</td>
</tr>
<tr>
<th>EM0015</th>
<td>An open interface is not listed as a case in a closed type it is a direct subtype of</td>
</tr>
<tr>
<th>EM0100</th>
<td>An exhaustive switch can't contain when guards</td>
Expand Down

0 comments on commit f1cf4ee

Please sign in to comment.