Skip to content

Commit

Permalink
Merge pull request #30980 from mavasani/WithEventsFix
Browse files Browse the repository at this point in the history
Workaround in RemoveUnusedMembersDiagnosticAnalyzer for VB handles cl…
  • Loading branch information
mavasani authored Nov 8, 2018
2 parents 86d7c79 + 72ffd3f commit 8735d96
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,71 @@ End Class")
End Class")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
<WorkItem(30895, "https://github.com/dotnet/roslyn/issues/30895")>
Public Async Function MethodWithHandlesClause() As Task
Await TestDiagnosticMissingAsync(
"Public Interface I
Event M()
End Interface

Public Class C
Private WithEvents _field1 As I

Private Sub [|M|]() Handles _field1.M
End Sub
End Class")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
<WorkItem(30895, "https://github.com/dotnet/roslyn/issues/30895")>
Public Async Function FieldReferencedInHandlesClause() As Task
Await TestDiagnosticMissingAsync(
"Public Interface I
Event M()
End Interface

Public Class C
Private WithEvents [|_field1|] As I

Private Sub M() Handles _field1.M
End Sub
End Class")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
<WorkItem(30895, "https://github.com/dotnet/roslyn/issues/30895")>
Public Async Function FieldReferencedInHandlesClause_02() As Task
Await TestDiagnosticMissingAsync(
"Public Interface I
Event M()
End Interface

Public Class C
Private WithEvents _field1 As I
Private WithEvents [|_field2|] As I

Private Sub M() Handles _field1.M, _field2.M
End Sub
End Class")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
<WorkItem(30895, "https://github.com/dotnet/roslyn/issues/30895")>
Public Async Function EventReferencedInHandlesClause() As Task
Await TestDiagnosticMissingAsync(
"Public Class B
Private Event [|M|]()

Public Class C
Private WithEvents _field1 As B

Private Sub M() Handles _field1.M
End Sub
End Class
End Class")
End Function

<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)>
Public Async Function PropertyIsArg() As Task
Await TestDiagnosticMissingAsync(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
Expand Down Expand Up @@ -49,17 +50,28 @@ protected AbstractRemoveUnusedMembersDiagnosticAnalyzer()

protected sealed override void InitializeWorker(AnalysisContext context)
=> context.RegisterCompilationStartAction(compilationStartContext
=> CompilationAnalyzer.CreateAndRegisterActions(compilationStartContext));
=> CompilationAnalyzer.CreateAndRegisterActions(compilationStartContext, this));

/// <summary>
/// Override this method to register custom language specific actions to find symbol usages.
/// </summary>
protected virtual void HandleNamedTypeSymbolStart(SymbolStartAnalysisContext context, Action<ISymbol, ValueUsageInfo> onSymbolUsageFound)
{
}

private sealed class CompilationAnalyzer
{
private readonly object _gate;
private readonly Dictionary<ISymbol, ValueUsageInfo> _symbolValueUsageStateMap;
private readonly INamedTypeSymbol _taskType, _genericTaskType, _debuggerDisplayAttributeType, _structLayoutAttributeType;
private readonly AbstractRemoveUnusedMembersDiagnosticAnalyzer<TDocumentationCommentTriviaSyntax, TIdentifierNameSyntax> _analyzer;

private CompilationAnalyzer(Compilation compilation)
private CompilationAnalyzer(
Compilation compilation,
AbstractRemoveUnusedMembersDiagnosticAnalyzer<TDocumentationCommentTriviaSyntax, TIdentifierNameSyntax> analyzer)
{
_gate = new object();
_analyzer = analyzer;

// State map for candidate member symbols, with the value indicating how each symbol is used in executable code.
_symbolValueUsageStateMap = new Dictionary<ISymbol, ValueUsageInfo>();
Expand All @@ -70,9 +82,11 @@ private CompilationAnalyzer(Compilation compilation)
_structLayoutAttributeType = compilation.StructLayoutAttributeType();
}

public static void CreateAndRegisterActions(CompilationStartAnalysisContext compilationStartContext)
public static void CreateAndRegisterActions(
CompilationStartAnalysisContext compilationStartContext,
AbstractRemoveUnusedMembersDiagnosticAnalyzer<TDocumentationCommentTriviaSyntax, TIdentifierNameSyntax> analyzer)
{
var compilationAnalyzer = new CompilationAnalyzer(compilationStartContext.Compilation);
var compilationAnalyzer = new CompilationAnalyzer(compilationStartContext.Compilation, analyzer);
compilationAnalyzer.RegisterActions(compilationStartContext);
}

Expand All @@ -89,6 +103,7 @@ private void RegisterActions(CompilationStartAnalysisContext compilationStartCon

compilationStartContext.RegisterSymbolAction(AnalyzeSymbolDeclaration, SymbolKind.Method, SymbolKind.Field, SymbolKind.Property, SymbolKind.Event);

Action<ISymbol, ValueUsageInfo> onSymbolUsageFound = OnSymbolUsage;
compilationStartContext.RegisterSymbolStartAction(symbolStartContext =>
{
if (symbolStartContext.Symbol.GetAttributes().Any(a => a.AttributeClass == _structLayoutAttributeType))
Expand All @@ -105,6 +120,9 @@ private void RegisterActions(CompilationStartAnalysisContext compilationStartCon
symbolStartContext.RegisterOperationAction(AnalyzeObjectCreationOperation, OperationKind.ObjectCreation);
symbolStartContext.RegisterOperationAction(_ => hasInvalidOperation = true, OperationKind.Invalid);
symbolStartContext.RegisterSymbolEndAction(symbolEndContext => OnSymbolEnd(symbolEndContext, hasInvalidOperation));

// Register custom language-specific actions, if any.
_analyzer.HandleNamedTypeSymbolStart(symbolStartContext, onSymbolUsageFound);
}, SymbolKind.NamedType);
}

Expand Down Expand Up @@ -141,17 +159,17 @@ private void AnalyzeFieldInitializer(OperationAnalysisContext operationContext)
{
foreach (var field in initializer.InitializedFields)
{
if (IsCandidateSymbol(field))
{
OnSymbolUsage(field, ValueUsageInfo.Write);
}
OnSymbolUsage(field, ValueUsageInfo.Write);
}
}
}

private void OnSymbolUsage(ISymbol memberSymbol, ValueUsageInfo usageInfo)
{
Debug.Assert(IsCandidateSymbol(memberSymbol));
if (!IsCandidateSymbol(memberSymbol))
{
return;
}

lock (_gate)
{
Expand Down Expand Up @@ -222,23 +240,19 @@ private void AnalyzeMemberReferenceOperation(OperationAnalysisContext operationC
private void AnalyzeInvocationOperation(OperationAnalysisContext operationContext)
{
var targetMethod = ((IInvocationOperation)operationContext.Operation).TargetMethod.OriginalDefinition;
if (IsCandidateSymbol(targetMethod))
{
// A method invocation is considered as a read reference to the symbol
// to ensure that we consider the method as "used".
OnSymbolUsage(targetMethod, ValueUsageInfo.Read);
}

// A method invocation is considered as a read reference to the symbol
// to ensure that we consider the method as "used".
OnSymbolUsage(targetMethod, ValueUsageInfo.Read);
}

private void AnalyzeObjectCreationOperation(OperationAnalysisContext operationContext)
{
var constructor = ((IObjectCreationOperation)operationContext.Operation).Constructor.OriginalDefinition;
if (IsCandidateSymbol(constructor))
{
// An object creation is considered as a read reference to the constructor
// to ensure that we consider the constructor as "used".
OnSymbolUsage(constructor, ValueUsageInfo.Read);
}

// An object creation is considered as a read reference to the constructor
// to ensure that we consider the constructor as "used".
OnSymbolUsage(constructor, ValueUsageInfo.Read);
}

private void OnSymbolEnd(SymbolAnalysisContext symbolEndContext, bool hasInvalidOperation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,38 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.RemoveUnusedMembers
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Friend NotInheritable Class VisualBasicRemoveUnusedMembersDiagnosticAnalyzer
Inherits AbstractRemoveUnusedMembersDiagnosticAnalyzer(Of DocumentationCommentTriviaSyntax, IdentifierNameSyntax)

Protected Overrides Sub HandleNamedTypeSymbolStart(context As SymbolStartAnalysisContext, onSymbolUsageFound As Action(Of ISymbol, ValueUsageInfo))
' Mark all methods with handles clause as having a read reference
' to ensure that we consider the method as "used".
' Such methods are essentially event handlers and are normally
' not referenced directly.
For Each method In DirectCast(context.Symbol, INamedTypeSymbol).GetMembers().OfType(Of IMethodSymbol)
If Not method.HandledEvents.IsEmpty Then
onSymbolUsageFound(method, ValueUsageInfo.Read)
End If
Next

' Register syntax node action for HandlesClause
' This is a workaround for following bugs:
' 1. https://github.com/dotnet/roslyn/issues/30978
' 2. https://github.com/dotnet/roslyn/issues/30979

context.RegisterSyntaxNodeAction(
Sub(syntaxNodeContext As SyntaxNodeAnalysisContext)
AnalyzeHandlesClause(syntaxNodeContext, onSymbolUsageFound)
End Sub,
SyntaxKind.HandlesClause)
End Sub

Private Sub AnalyzeHandlesClause(context As SyntaxNodeAnalysisContext, onSymbolUsageFound As Action(Of ISymbol, ValueUsageInfo))
' Identify all symbol references within the HandlesClause.
For Each node In context.Node.DescendantNodes()
Dim symbolInfo = context.SemanticModel.GetSymbolInfo(node, context.CancellationToken)
For Each symbol In symbolInfo.GetAllSymbols()
onSymbolUsageFound(symbol, ValueUsageInfo.Read)
Next
Next
End Sub
End Class
End Namespace

0 comments on commit 8735d96

Please sign in to comment.