Skip to content

Commit b8ef9f5

Browse files
Merge pull request #2028 from dymanoid/implements-interface-analyzer
Add new 'Implements' analyzer for interface implementation lookup
2 parents 5f27fd9 + 8e7774d commit b8ef9f5

7 files changed

+254
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Copyright (c) 2020 Siegfried Pammer
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4+
// software and associated documentation files (the "Software"), to deal in the Software
5+
// without restriction, including without limitation the rights to use, copy, modify, merge,
6+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7+
// to whom the Software is furnished to do so, subject to the following conditions:
8+
//
9+
// The above copyright notice and this permission notice shall be included in all copies or
10+
// substantial portions of the Software.
11+
//
12+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17+
// DEALINGS IN THE SOFTWARE.
18+
19+
using System;
20+
using System.IO;
21+
using System.Linq;
22+
using System.Reflection.Metadata;
23+
using System.Reflection.PortableExecutable;
24+
using ICSharpCode.Decompiler.Metadata;
25+
using ICSharpCode.Decompiler.TypeSystem;
26+
using ICSharpCode.Decompiler.TypeSystem.Implementation;
27+
using ICSharpCode.ILSpy.Analyzers;
28+
using ICSharpCode.ILSpy.Analyzers.Builtin;
29+
using Moq;
30+
using NUnit.Framework;
31+
32+
namespace ICSharpCode.ILSpy.Tests.Analyzers
33+
{
34+
[TestFixture, Parallelizable(ParallelScope.All)]
35+
public class MemberImplementsInterfaceAnalyzerTests
36+
{
37+
static readonly SymbolKind[] ValidSymbolKinds = { SymbolKind.Event, SymbolKind.Indexer, SymbolKind.Method, SymbolKind.Property };
38+
static readonly SymbolKind[] InvalidSymbolKinds =
39+
Enum.GetValues(typeof(SymbolKind)).Cast<SymbolKind>().Except(ValidSymbolKinds).ToArray();
40+
41+
static readonly TypeKind[] ValidTypeKinds = { TypeKind.Class, TypeKind.Struct };
42+
static readonly TypeKind[] InvalidTypeKinds = Enum.GetValues(typeof(TypeKind)).Cast<TypeKind>().Except(ValidTypeKinds).ToArray();
43+
44+
private ICompilation testAssembly;
45+
46+
[OneTimeSetUp]
47+
public void Setup()
48+
{
49+
string fileName = GetType().Assembly.Location;
50+
51+
using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read)) {
52+
var module = new PEFile(fileName, stream, PEStreamOptions.PrefetchEntireImage, MetadataReaderOptions.None);
53+
54+
testAssembly = new SimpleCompilation(module.WithOptions(TypeSystemOptions.Default), MinimalCorlib.Instance);
55+
}
56+
}
57+
58+
[Test]
59+
public void VerifyDoesNotShowForNoSymbol()
60+
{
61+
// Arrange
62+
var analyzer = new MemberImplementsInterfaceAnalyzer();
63+
64+
// Act
65+
var shouldShow = analyzer.Show(symbol: null);
66+
67+
// Assert
68+
Assert.IsFalse(shouldShow, $"The analyzer will be unexpectedly shown for no symbol");
69+
}
70+
71+
[Test]
72+
[TestCaseSource(nameof(InvalidSymbolKinds))]
73+
public void VerifyDoesNotShowForNonMembers(SymbolKind symbolKind)
74+
{
75+
// Arrange
76+
var symbolMock = new Mock<ISymbol>();
77+
symbolMock.Setup(s => s.SymbolKind).Returns(symbolKind);
78+
var analyzer = new MemberImplementsInterfaceAnalyzer();
79+
80+
// Act
81+
var shouldShow = analyzer.Show(symbolMock.Object);
82+
83+
// Assert
84+
Assert.IsFalse(shouldShow, $"The analyzer will be unexpectedly shown for symbol '{symbolKind}'");
85+
}
86+
87+
[Test]
88+
[TestCaseSource(nameof(ValidSymbolKinds))]
89+
public void VerifyDoesNotShowForStaticMembers(SymbolKind symbolKind)
90+
{
91+
// Arrange
92+
var memberMock = SetupMemberMock(symbolKind, TypeKind.Unknown, isStatic: true);
93+
var analyzer = new MemberImplementsInterfaceAnalyzer();
94+
95+
// Act
96+
var shouldShow = analyzer.Show(memberMock.Object);
97+
98+
// Assert
99+
Assert.IsFalse(shouldShow, $"The analyzer will be unexpectedly shown for static symbol '{symbolKind}'");
100+
}
101+
102+
[Test]
103+
[Pairwise]
104+
public void VerifyDoesNotShowForUnsupportedTypes(
105+
[ValueSource(nameof(ValidSymbolKinds))] SymbolKind symbolKind,
106+
[ValueSource(nameof(InvalidTypeKinds))] TypeKind typeKind)
107+
{
108+
// Arrange
109+
var memberMock = SetupMemberMock(symbolKind, typeKind, isStatic: true);
110+
var analyzer = new MemberImplementsInterfaceAnalyzer();
111+
112+
// Act
113+
var shouldShow = analyzer.Show(memberMock.Object);
114+
115+
// Assert
116+
Assert.IsFalse(shouldShow, $"The analyzer will be unexpectedly shown for symbol '{symbolKind}' and '{typeKind}'");
117+
}
118+
119+
[Test]
120+
[Pairwise]
121+
public void VerifyShowsForSupportedTypes(
122+
[ValueSource(nameof(ValidSymbolKinds))] SymbolKind symbolKind,
123+
[ValueSource(nameof(ValidTypeKinds))] TypeKind typeKind)
124+
{
125+
// Arrange
126+
var memberMock = SetupMemberMock(symbolKind, typeKind, isStatic: false);
127+
var analyzer = new MemberImplementsInterfaceAnalyzer();
128+
129+
// Act
130+
var shouldShow = analyzer.Show(memberMock.Object);
131+
132+
// Assert
133+
Assert.IsTrue(shouldShow, $"The analyzer will not be shown for symbol '{symbolKind}' and '{typeKind}'");
134+
}
135+
136+
[Test]
137+
public void VerifyReturnsOnlyInterfaceMembers()
138+
{
139+
// Arrange
140+
var symbol = SetupSymbolForAnalysis(typeof(TestClass), nameof(TestClass.TestMethod));
141+
var analyzer = new MemberImplementsInterfaceAnalyzer();
142+
143+
// Act
144+
var results = analyzer.Analyze(symbol, new AnalyzerContext());
145+
146+
// Assert
147+
Assert.IsNotNull(results);
148+
Assert.AreEqual(1, results.Count());
149+
var result = results.FirstOrDefault() as IMethod;
150+
Assert.IsNotNull(result);
151+
Assert.IsNotNull(result.DeclaringTypeDefinition);
152+
Assert.AreEqual(TypeKind.Interface, result.DeclaringTypeDefinition.Kind);
153+
Assert.AreEqual(nameof(ITestInterface), result.DeclaringTypeDefinition.Name);
154+
}
155+
156+
private ISymbol SetupSymbolForAnalysis(Type type, string methodName)
157+
{
158+
var typeDefinition = testAssembly.FindType(type).GetDefinition();
159+
return typeDefinition.Methods.First(m => m.Name == methodName);
160+
}
161+
162+
private static Mock<IMember> SetupMemberMock(SymbolKind symbolKind, TypeKind typeKind, bool isStatic)
163+
{
164+
var memberMock = new Mock<IMember>();
165+
memberMock.Setup(m => m.SymbolKind).Returns(symbolKind);
166+
memberMock.Setup(m => m.DeclaringTypeDefinition.Kind).Returns(typeKind);
167+
memberMock.Setup(m => m.IsStatic).Returns(isStatic);
168+
return memberMock;
169+
}
170+
171+
private interface ITestInterface
172+
{
173+
void TestMethod();
174+
}
175+
176+
private class BaseClass
177+
{
178+
public virtual void TestMethod() => throw new NotImplementedException();
179+
}
180+
181+
private class TestClass : BaseClass, ITestInterface
182+
{
183+
public override void TestMethod() => throw new NotImplementedException();
184+
}
185+
}
186+
}

ILSpy.Tests/ILSpy.Tests.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
</PropertyGroup>
3838

3939
<ItemGroup>
40+
<Compile Include="Analyzers\MemberImplementsInterfaceAnalyzerTests.cs" />
4041
<Compile Include="Analyzers\MethodUsesAnalyzerTests.cs" />
4142
<Compile Include="Analyzers\TestCases\MainAssembly.cs" />
4243
<Compile Include="Analyzers\TypeUsedByAnalyzerTests.cs" />
@@ -50,6 +51,7 @@
5051
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
5152
<PackageReference Include="System.Collections.Immutable" Version="1.5.0" />
5253
<PackageReference Include="NUnit" Version="3.11.0" />
54+
<PackageReference Include="Moq" Version="4.14.1" />
5355
</ItemGroup>
5456

5557
<ItemGroup>

ILSpy/Analyzers/Builtin/EventImplementsInterfaceAnalyzer.cs ILSpy/Analyzers/Builtin/EventImplementedByAnalyzer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace ICSharpCode.ILSpy.Analyzers.Builtin
2828
/// Shows events that implement an interface event.
2929
/// </summary>
3030
[ExportAnalyzer(Header = "Implemented By", Order = 10)]
31-
class EventImplementsInterfaceAnalyzer : IAnalyzer
31+
class EventImplementedByAnalyzer : IAnalyzer
3232
{
3333
public IEnumerable<ISymbol> Analyze(ISymbol analyzedSymbol, AnalyzerContext context)
3434
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) 2018 Siegfried Pammer
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4+
// software and associated documentation files (the "Software"), to deal in the Software
5+
// without restriction, including without limitation the rights to use, copy, modify, merge,
6+
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7+
// to whom the Software is furnished to do so, subject to the following conditions:
8+
//
9+
// The above copyright notice and this permission notice shall be included in all copies or
10+
// substantial portions of the Software.
11+
//
12+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13+
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14+
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15+
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17+
// DEALINGS IN THE SOFTWARE.
18+
19+
using System.Collections.Generic;
20+
using System.Diagnostics;
21+
using System.Linq;
22+
using ICSharpCode.Decompiler.TypeSystem;
23+
24+
namespace ICSharpCode.ILSpy.Analyzers.Builtin
25+
{
26+
/// <summary>
27+
/// Shows members from all corresponding interfaces the selected member implements.
28+
/// </summary>
29+
[ExportAnalyzer(Header = "Implements", Order = 40)]
30+
class MemberImplementsInterfaceAnalyzer : IAnalyzer
31+
{
32+
public IEnumerable<ISymbol> Analyze(ISymbol analyzedSymbol, AnalyzerContext context)
33+
{
34+
Debug.Assert(analyzedSymbol is IMember);
35+
var member = (IMember)analyzedSymbol;
36+
37+
Debug.Assert(!member.IsStatic);
38+
39+
var baseMembers = InheritanceHelper.GetBaseMembers(member, includeImplementedInterfaces: true);
40+
return baseMembers.Where(m => m.DeclaringTypeDefinition.Kind == TypeKind.Interface);
41+
}
42+
43+
public bool Show(ISymbol symbol)
44+
{
45+
switch (symbol?.SymbolKind) {
46+
case SymbolKind.Event:
47+
case SymbolKind.Indexer:
48+
case SymbolKind.Method:
49+
case SymbolKind.Property:
50+
var member = (IMember)symbol;
51+
var type = member.DeclaringTypeDefinition;
52+
return !member.IsStatic && (type.Kind == TypeKind.Class || type.Kind == TypeKind.Struct);
53+
54+
default:
55+
return false;
56+
}
57+
}
58+
}
59+
}

ILSpy/Analyzers/Builtin/MethodImplementsInterfaceAnalyzer.cs ILSpy/Analyzers/Builtin/MethodImplementedByAnalyzer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ namespace ICSharpCode.ILSpy.Analyzers.Builtin
3131
/// Shows methods that implement an interface method.
3232
/// </summary>
3333
[ExportAnalyzer(Header = "Implemented By", Order = 40)]
34-
class MethodImplementsInterfaceAnalyzer : IAnalyzer
34+
class MethodImplementedByAnalyzer : IAnalyzer
3535
{
3636
public IEnumerable<ISymbol> Analyze(ISymbol analyzedSymbol, AnalyzerContext context)
3737
{

ILSpy/Analyzers/Builtin/PropertyImplementsInterfaceAnalyzer.cs ILSpy/Analyzers/Builtin/PropertyImplementedByAnalyzer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace ICSharpCode.ILSpy.Analyzers.Builtin
2828
/// Shows properties that implement an interface property.
2929
/// </summary>
3030
[ExportAnalyzer(Header = "Implemented By", Order = 10)]
31-
class PropertyImplementsInterfaceAnalyzer : IAnalyzer
31+
class PropertyImplementedByAnalyzer : IAnalyzer
3232
{
3333
public IEnumerable<ISymbol> Analyze(ISymbol analyzedSymbol, AnalyzerContext context)
3434
{

ILSpy/ILSpy.csproj

+4-3
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,15 @@
7777
<Compile Include="Analyzers\AnalyzerContext.cs" />
7878
<Compile Include="Analyzers\AnalyzerScope.cs" />
7979
<Compile Include="Analyzers\Builtin\AttributeAppliedToAnalyzer.cs" />
80-
<Compile Include="Analyzers\Builtin\EventImplementsInterfaceAnalyzer.cs" />
80+
<Compile Include="Analyzers\Builtin\EventImplementedByAnalyzer.cs" />
8181
<Compile Include="Analyzers\Builtin\EventOverriddenByAnalyzer.cs" />
82-
<Compile Include="Analyzers\Builtin\MethodImplementsInterfaceAnalyzer.cs" />
82+
<Compile Include="Analyzers\Builtin\MethodImplementedByAnalyzer.cs" />
83+
<Compile Include="Analyzers\Builtin\MemberImplementsInterfaceAnalyzer.cs" />
8384
<Compile Include="Analyzers\Builtin\MethodOverriddenByAnalyzer.cs" />
8485
<Compile Include="Analyzers\Builtin\MethodVirtualUsedByAnalyzer.cs" />
8586
<Compile Include="Analyzers\Builtin\MethodUsedByAnalyzer.cs" />
8687
<Compile Include="Analyzers\Builtin\MethodUsesAnalyzer.cs" />
87-
<Compile Include="Analyzers\Builtin\PropertyImplementsInterfaceAnalyzer.cs" />
88+
<Compile Include="Analyzers\Builtin\PropertyImplementedByAnalyzer.cs" />
8889
<Compile Include="Analyzers\Builtin\PropertyOverriddenByAnalyzer.cs" />
8990
<Compile Include="Analyzers\Builtin\TypeExposedByAnalyzer.cs" />
9091
<Compile Include="Analyzers\Builtin\TypeInstantiatedByAnalyzer.cs" />

0 commit comments

Comments
 (0)