mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
The analyzer can be used on any non-static members of classes and structs. The analyzer shows a new 'Implements' node for those members that implement an interface (implicitly or explicitly). Abstract members are considered as implementation. Overridden virtual members of base classes are not considered.pull/2028/head
4 changed files with 247 additions and 0 deletions
@ -0,0 +1,186 @@ |
|||||||
|
// Copyright (c) 2020 Siegfried Pammer
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System; |
||||||
|
using System.IO; |
||||||
|
using System.Linq; |
||||||
|
using System.Reflection.Metadata; |
||||||
|
using System.Reflection.PortableExecutable; |
||||||
|
using ICSharpCode.Decompiler.Metadata; |
||||||
|
using ICSharpCode.Decompiler.TypeSystem; |
||||||
|
using ICSharpCode.Decompiler.TypeSystem.Implementation; |
||||||
|
using ICSharpCode.ILSpy.Analyzers; |
||||||
|
using ICSharpCode.ILSpy.Analyzers.Builtin; |
||||||
|
using Moq; |
||||||
|
using NUnit.Framework; |
||||||
|
|
||||||
|
namespace ICSharpCode.ILSpy.Tests.Analyzers |
||||||
|
{ |
||||||
|
[TestFixture, Parallelizable(ParallelScope.All)] |
||||||
|
public class MemberImplementsInterfaceAnalyzerTests |
||||||
|
{ |
||||||
|
static readonly SymbolKind[] ValidSymbolKinds = { SymbolKind.Event, SymbolKind.Indexer, SymbolKind.Method, SymbolKind.Property }; |
||||||
|
static readonly SymbolKind[] InvalidSymbolKinds = |
||||||
|
Enum.GetValues(typeof(SymbolKind)).Cast<SymbolKind>().Except(ValidSymbolKinds).ToArray(); |
||||||
|
|
||||||
|
static readonly TypeKind[] ValidTypeKinds = { TypeKind.Class, TypeKind.Struct }; |
||||||
|
static readonly TypeKind[] InvalidTypeKinds = Enum.GetValues(typeof(TypeKind)).Cast<TypeKind>().Except(ValidTypeKinds).ToArray(); |
||||||
|
|
||||||
|
private ICompilation testAssembly; |
||||||
|
|
||||||
|
[OneTimeSetUp] |
||||||
|
public void Setup() |
||||||
|
{ |
||||||
|
string fileName = GetType().Assembly.Location; |
||||||
|
|
||||||
|
using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read)) { |
||||||
|
var module = new PEFile(fileName, stream, PEStreamOptions.PrefetchEntireImage, MetadataReaderOptions.None); |
||||||
|
|
||||||
|
testAssembly = new SimpleCompilation(module.WithOptions(TypeSystemOptions.Default), MinimalCorlib.Instance); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void VerifyDoesNotShowForNoSymbol() |
||||||
|
{ |
||||||
|
// Arrange
|
||||||
|
var analyzer = new MemberImplementsInterfaceAnalyzer(); |
||||||
|
|
||||||
|
// Act
|
||||||
|
var shouldShow = analyzer.Show(symbol: null); |
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsFalse(shouldShow, $"The analyzer will be unexpectedly shown for no symbol"); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
[TestCaseSource(nameof(InvalidSymbolKinds))] |
||||||
|
public void VerifyDoesNotShowForNonMembers(SymbolKind symbolKind) |
||||||
|
{ |
||||||
|
// Arrange
|
||||||
|
var symbolMock = new Mock<ISymbol>(); |
||||||
|
symbolMock.Setup(s => s.SymbolKind).Returns(symbolKind); |
||||||
|
var analyzer = new MemberImplementsInterfaceAnalyzer(); |
||||||
|
|
||||||
|
// Act
|
||||||
|
var shouldShow = analyzer.Show(symbolMock.Object); |
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsFalse(shouldShow, $"The analyzer will be unexpectedly shown for symbol '{symbolKind}'"); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
[TestCaseSource(nameof(ValidSymbolKinds))] |
||||||
|
public void VerifyDoesNotShowForStaticMembers(SymbolKind symbolKind) |
||||||
|
{ |
||||||
|
// Arrange
|
||||||
|
var memberMock = SetupMemberMock(symbolKind, TypeKind.Unknown, isStatic: true); |
||||||
|
var analyzer = new MemberImplementsInterfaceAnalyzer(); |
||||||
|
|
||||||
|
// Act
|
||||||
|
var shouldShow = analyzer.Show(memberMock.Object); |
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsFalse(shouldShow, $"The analyzer will be unexpectedly shown for static symbol '{symbolKind}'"); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
[Pairwise] |
||||||
|
public void VerifyDoesNotShowForUnsupportedTypes( |
||||||
|
[ValueSource(nameof(ValidSymbolKinds))] SymbolKind symbolKind, |
||||||
|
[ValueSource(nameof(InvalidTypeKinds))] TypeKind typeKind) |
||||||
|
{ |
||||||
|
// Arrange
|
||||||
|
var memberMock = SetupMemberMock(symbolKind, typeKind, isStatic: true); |
||||||
|
var analyzer = new MemberImplementsInterfaceAnalyzer(); |
||||||
|
|
||||||
|
// Act
|
||||||
|
var shouldShow = analyzer.Show(memberMock.Object); |
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsFalse(shouldShow, $"The analyzer will be unexpectedly shown for symbol '{symbolKind}' and '{typeKind}'"); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
[Pairwise] |
||||||
|
public void VerifyShowsForSupportedTypes( |
||||||
|
[ValueSource(nameof(ValidSymbolKinds))] SymbolKind symbolKind, |
||||||
|
[ValueSource(nameof(ValidTypeKinds))] TypeKind typeKind) |
||||||
|
{ |
||||||
|
// Arrange
|
||||||
|
var memberMock = SetupMemberMock(symbolKind, typeKind, isStatic: false); |
||||||
|
var analyzer = new MemberImplementsInterfaceAnalyzer(); |
||||||
|
|
||||||
|
// Act
|
||||||
|
var shouldShow = analyzer.Show(memberMock.Object); |
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsTrue(shouldShow, $"The analyzer will not be shown for symbol '{symbolKind}' and '{typeKind}'"); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void VerifyReturnsOnlyInterfaceMembers() |
||||||
|
{ |
||||||
|
// Arrange
|
||||||
|
var symbol = SetupSymbolForAnalysis(typeof(TestClass), nameof(TestClass.TestMethod)); |
||||||
|
var analyzer = new MemberImplementsInterfaceAnalyzer(); |
||||||
|
|
||||||
|
// Act
|
||||||
|
var results = analyzer.Analyze(symbol, new AnalyzerContext()); |
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.IsNotNull(results); |
||||||
|
Assert.AreEqual(1, results.Count()); |
||||||
|
var result = results.FirstOrDefault() as IMethod; |
||||||
|
Assert.IsNotNull(result); |
||||||
|
Assert.IsNotNull(result.DeclaringTypeDefinition); |
||||||
|
Assert.AreEqual(TypeKind.Interface, result.DeclaringTypeDefinition.Kind); |
||||||
|
Assert.AreEqual(nameof(ITestInterface), result.DeclaringTypeDefinition.Name); |
||||||
|
} |
||||||
|
|
||||||
|
private ISymbol SetupSymbolForAnalysis(Type type, string methodName) |
||||||
|
{ |
||||||
|
var typeDefinition = testAssembly.FindType(type).GetDefinition(); |
||||||
|
return typeDefinition.Methods.First(m => m.Name == methodName); |
||||||
|
} |
||||||
|
|
||||||
|
private static Mock<IMember> SetupMemberMock(SymbolKind symbolKind, TypeKind typeKind, bool isStatic) |
||||||
|
{ |
||||||
|
var memberMock = new Mock<IMember>(); |
||||||
|
memberMock.Setup(m => m.SymbolKind).Returns(symbolKind); |
||||||
|
memberMock.Setup(m => m.DeclaringTypeDefinition.Kind).Returns(typeKind); |
||||||
|
memberMock.Setup(m => m.IsStatic).Returns(isStatic); |
||||||
|
return memberMock; |
||||||
|
} |
||||||
|
|
||||||
|
private interface ITestInterface |
||||||
|
{ |
||||||
|
void TestMethod(); |
||||||
|
} |
||||||
|
|
||||||
|
private class BaseClass |
||||||
|
{ |
||||||
|
public virtual void TestMethod() => throw new NotImplementedException(); |
||||||
|
} |
||||||
|
|
||||||
|
private class TestClass : BaseClass, ITestInterface |
||||||
|
{ |
||||||
|
public override void TestMethod() => throw new NotImplementedException(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,59 @@ |
|||||||
|
// Copyright (c) 2018 Siegfried Pammer
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||||
|
// software and associated documentation files (the "Software"), to deal in the Software
|
||||||
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||||
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||||
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
// substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||||
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
// DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics; |
||||||
|
using System.Linq; |
||||||
|
using ICSharpCode.Decompiler.TypeSystem; |
||||||
|
|
||||||
|
namespace ICSharpCode.ILSpy.Analyzers.Builtin |
||||||
|
{ |
||||||
|
/// <summary>
|
||||||
|
/// Shows members from all corresponding interfaces the selected member implements.
|
||||||
|
/// </summary>
|
||||||
|
[ExportAnalyzer(Header = "Implements", Order = 40)] |
||||||
|
class MemberImplementsInterfaceAnalyzer : IAnalyzer |
||||||
|
{ |
||||||
|
public IEnumerable<ISymbol> Analyze(ISymbol analyzedSymbol, AnalyzerContext context) |
||||||
|
{ |
||||||
|
Debug.Assert(analyzedSymbol is IMember); |
||||||
|
var member = (IMember)analyzedSymbol; |
||||||
|
|
||||||
|
Debug.Assert(!member.IsStatic); |
||||||
|
|
||||||
|
var baseMembers = InheritanceHelper.GetBaseMembers(member, includeImplementedInterfaces: true); |
||||||
|
return baseMembers.Where(m => m.DeclaringTypeDefinition.Kind == TypeKind.Interface); |
||||||
|
} |
||||||
|
|
||||||
|
public bool Show(ISymbol symbol) |
||||||
|
{ |
||||||
|
switch (symbol?.SymbolKind) { |
||||||
|
case SymbolKind.Event: |
||||||
|
case SymbolKind.Indexer: |
||||||
|
case SymbolKind.Method: |
||||||
|
case SymbolKind.Property: |
||||||
|
var member = (IMember)symbol; |
||||||
|
var type = member.DeclaringTypeDefinition; |
||||||
|
return !member.IsStatic && (type.Kind == TypeKind.Class || type.Kind == TypeKind.Struct); |
||||||
|
|
||||||
|
default: |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue