Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests
Dim duplicateProjectAnalyzersReference = New AnalyzerImageReference(duplicateProjectAnalyzers)
project = project.WithAnalyzerReferences({duplicateProjectAnalyzersReference})

' Verify no duplicate descriptors or diagnsotics.
' Verify duplicate descriptors or diagnsotics.
' We don't do de-duplication of analyzer that belong to different layer (host and project)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? I'd thought we explicitly want to de-duplicate. Is the suggestion here that situation is too contrived to be assumed realistic?
I could accidentally come up in a situation with duplicate analyzer references. A project nuget reference might be pulling in analyzer and it might also be installed in the box on some machine which wants specific analyzer enabled for all development. I don't want to remove project nuget reference if not all of machines on which project is developed on has VSIX or host analyzer.
Is it non-trivial to de-duplicate here? Else, we should just do it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mavasani that de-duplication will work. so, having same analyzer reference (basically same file) in multiple places regardless of layer (host, project) works.

the one I removed is same instance of diagnostic analyzer in two different analyzer reference. for this to happen, people has to do what the test did. create custom analyzer references and put same instance of diagnostic analyzer in multiple references.

I don't think that is something we need to care.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, thanks for the explanation.

descriptorsMap = diagnosticService.GetDiagnosticDescriptors(project)
Assert.Equal(2, descriptorsMap.Count)
descriptors = descriptorsMap.Values.SelectMany(Function(d) d).OrderBy(Function(d) d.Id).ToImmutableArray()
Expand All @@ -123,7 +124,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests
diagnostics = diagnosticService.GetDiagnosticsForSpanAsync(document,
document.GetSyntaxRootAsync().WaitAndGetResult(CancellationToken.None).FullSpan,
CancellationToken.None).WaitAndGetResult(CancellationToken.None)
Assert.Equal(1, diagnostics.Count())
Assert.Equal(2, diagnostics.Count())
End Using
End Sub

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ private void ClearAnalyzerDiagnostics(DiagnosticAnalyzer analyzer, ProjectId pro

private DiagnosticsUpdatedArgs MakeArgs(DiagnosticAnalyzer analyzer, ImmutableHashSet<DiagnosticData> items, Project project)
{
var id = WorkspaceAnalyzerManager.GetUniqueIdForAnalyzer(analyzer);
var id = analyzer.GetUniqueId();

return new DiagnosticsUpdatedArgs(
id: Tuple.Create(this, id, project?.Id),
Expand Down
48 changes: 34 additions & 14 deletions src/Features/Core/Diagnostics/AnalyzerHelper.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// 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.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Diagnostics

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My latest review removes a lot of methods that I recently added from this helper type. It will now just have 2 static methods used by IDE driver and HostAnalyzerManager for supported diagnostics.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool! but still merge conflict :(

{
Expand All @@ -12,12 +14,12 @@ internal static class AnalyzerHelper
private const string CSharpCompilerAnalyzerTypeName = "Microsoft.CodeAnalysis.Diagnostics.CSharp.CSharpCompilerDiagnosticAnalyzer";
private const string VisualBasicCompilerAnalyzerTypeName = "Microsoft.CodeAnalysis.Diagnostics.VisualBasic.VisualBasicCompilerDiagnosticAnalyzer";

public static bool IsBuiltInAnalyzer(DiagnosticAnalyzer analyzer)
public static bool IsBuiltInAnalyzer(this DiagnosticAnalyzer analyzer)
{
return analyzer is IBuiltInAnalyzer || analyzer is DocumentDiagnosticAnalyzer || analyzer is ProjectDiagnosticAnalyzer || IsCompilerAnalyzer(analyzer);
return analyzer is IBuiltInAnalyzer || analyzer is DocumentDiagnosticAnalyzer || analyzer is ProjectDiagnosticAnalyzer || analyzer.IsCompilerAnalyzer();
}

public static bool IsCompilerAnalyzer(DiagnosticAnalyzer analyzer)
public static bool IsCompilerAnalyzer(this DiagnosticAnalyzer analyzer)
{
// TODO: find better way.
var typeString = analyzer.GetType().ToString();
Expand All @@ -34,43 +36,61 @@ public static bool IsCompilerAnalyzer(DiagnosticAnalyzer analyzer)
return false;
}

public static Action<Diagnostic> GetAddExceptionDiagnosticDelegate(DiagnosticAnalyzer analyzer, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, Project project)
public static ValueTuple<string, VersionStamp> GetUniqueId(this DiagnosticAnalyzer analyzer)
{
// Get the unique ID for given diagnostic analyzer.
// note that we also put version stamp so that we can detect changed analyzer.
var type = analyzer.GetType();
return ValueTuple.Create(type.AssemblyQualifiedName, GetAnalyzerVersion(type.Assembly.Location));
}

public static Action<Diagnostic> GetAddExceptionDiagnosticDelegate(this DiagnosticAnalyzer analyzer, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, Project project)
{
return diagnostic =>
hostDiagnosticUpdateSource?.ReportAnalyzerDiagnostic(analyzer, diagnostic, project.Solution.Workspace, project);
}

public static Action<Diagnostic> GetAddExceptionDiagnosticDelegate(DiagnosticAnalyzer analyzer, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, Workspace workspace)
public static Action<Diagnostic> GetAddExceptionDiagnosticDelegate(this DiagnosticAnalyzer analyzer, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource, Workspace workspace)
{
return diagnostic =>
hostDiagnosticUpdateSource?.ReportAnalyzerDiagnostic(analyzer, diagnostic, workspace, null);
}

public static AnalyzerExecutor GetAnalyzerExecutorForSupportedDiagnostics(
DiagnosticAnalyzer analyzer,
this DiagnosticAnalyzer analyzer,
AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource,
Func<Exception, DiagnosticAnalyzer, bool> continueOnAnalyzerException,
Func<Exception, DiagnosticAnalyzer, bool> continueOnAnalyzerException,
CancellationToken cancellationToken)
{
var addExceptionDiagnostic = GetAddExceptionDiagnosticDelegate(analyzer, hostDiagnosticUpdateSource, hostDiagnosticUpdateSource?.Workspace);
var addExceptionDiagnostic = analyzer.GetAddExceptionDiagnosticDelegate(hostDiagnosticUpdateSource, hostDiagnosticUpdateSource?.Workspace);

// Skip telemetry logging if the exception is thrown as we are computing supported diagnostics and
// we can't determine if any descriptors support getting telemetry without having the descriptors.
return AnalyzerExecutor.CreateForSupportedDiagnostics(addExceptionDiagnostic, continueOnAnalyzerException, cancellationToken);
}

public static AnalyzerExecutor GetAnalyzerExecutor(
DiagnosticAnalyzer analyzer,
AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource,
Project project,
Compilation compilation,
this DiagnosticAnalyzer analyzer,
AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource,
Project project,
Compilation compilation,
Action<Diagnostic> addDiagnostic,
AnalyzerOptions analyzerOptions,
Func<Exception, DiagnosticAnalyzer, bool> continueOnAnalyzerException,
CancellationToken cancellationToken)
{
var addExceptionDiagnostic = GetAddExceptionDiagnosticDelegate(analyzer, hostDiagnosticUpdateSource, project);
var addExceptionDiagnostic = analyzer.GetAddExceptionDiagnosticDelegate(hostDiagnosticUpdateSource, project);
return AnalyzerExecutor.Create(compilation, analyzerOptions, addDiagnostic, addExceptionDiagnostic, continueOnAnalyzerException, cancellationToken);
}

private static VersionStamp GetAnalyzerVersion(string path)
{
if (path == null || !File.Exists(path))
{
return VersionStamp.Default;
}

return VersionStamp.Create(File.GetLastWriteTimeUtc(path));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ protected BaseDiagnosticIncrementalAnalyzer(Workspace workspace, AbstractHostDia
public abstract Task<IEnumerable<DiagnosticData>> GetDiagnosticsForSpanAsync(Document document, TextSpan range, CancellationToken cancellationToken);
#endregion

public Workspace Workspace { get; private set; }
protected Workspace Workspace { get; private set; }
protected AbstractHostDiagnosticUpdateSource HostDiagnosticUpdateSource { get; private set; }

public virtual bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs e)
Expand Down
22 changes: 15 additions & 7 deletions src/Features/Core/Diagnostics/DiagnosticAnalyzerService.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// 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.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
Expand All @@ -15,45 +17,51 @@ namespace Microsoft.CodeAnalysis.Diagnostics
[Shared]
internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService
{
private readonly WorkspaceAnalyzerManager _workspaceAnalyzerManager;
private readonly HostAnalyzerManager _hostAnalyzerManager;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the rename, I have always felt WorkspaceAnalyzerManager was the wrong term I chose, given that it is independent of workspace.

private readonly AbstractHostDiagnosticUpdateSource _hostDiagnosticUpdateSource;
private readonly IAsynchronousOperationListener _listener;

[ImportingConstructor]
public DiagnosticAnalyzerService([Import(AllowDefault = true)]IWorkspaceDiagnosticAnalyzerProviderService diagnosticAnalyzerProviderService = null,
public DiagnosticAnalyzerService(
[ImportMany] IEnumerable<Lazy<IAsynchronousOperationListener, FeatureMetadata>> asyncListeners,
[Import(AllowDefault = true)]IWorkspaceDiagnosticAnalyzerProviderService diagnosticAnalyzerProviderService = null,
[Import(AllowDefault = true)]AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null)
: this(workspaceAnalyzerAssemblies: diagnosticAnalyzerProviderService != null ?
diagnosticAnalyzerProviderService.GetWorkspaceAnalyzerAssemblies() :
SpecializedCollections.EmptyEnumerable<string>(),
hostDiagnosticUpdateSource: hostDiagnosticUpdateSource)
{
_listener = new AggregateAsynchronousOperationListener(asyncListeners, FeatureAttribute.DiagnosticService);
}

public IAsynchronousOperationListener Listener => _listener;

private DiagnosticAnalyzerService(IEnumerable<string> workspaceAnalyzerAssemblies, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource) : this()
{
_workspaceAnalyzerManager = new WorkspaceAnalyzerManager(workspaceAnalyzerAssemblies, hostDiagnosticUpdateSource);
_hostAnalyzerManager = new HostAnalyzerManager(workspaceAnalyzerAssemblies, hostDiagnosticUpdateSource);
_hostDiagnosticUpdateSource = hostDiagnosticUpdateSource;
}

// internal for testing purposes.
internal DiagnosticAnalyzerService(ImmutableArray<AnalyzerReference> workspaceAnalyzers, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null) : this()
{
_workspaceAnalyzerManager = new WorkspaceAnalyzerManager(workspaceAnalyzers, hostDiagnosticUpdateSource);
_hostAnalyzerManager = new HostAnalyzerManager(workspaceAnalyzers, hostDiagnosticUpdateSource);
_hostDiagnosticUpdateSource = hostDiagnosticUpdateSource;
}

public ImmutableDictionary<string, ImmutableArray<DiagnosticDescriptor>> GetDiagnosticDescriptors(Project projectOpt)
{
if (projectOpt == null)
{
return _workspaceAnalyzerManager.GetHostDiagnosticDescriptorsPerReference();
return _hostAnalyzerManager.GetHostDiagnosticDescriptorsPerReference();
}

return _workspaceAnalyzerManager.CreateDiagnosticDescriptorsPerReference(projectOpt);
return _hostAnalyzerManager.CreateDiagnosticDescriptorsPerReference(projectOpt);
}

public ImmutableArray<DiagnosticDescriptor> GetDiagnosticDescriptors(DiagnosticAnalyzer analyzer)
{
return _workspaceAnalyzerManager.GetDiagnosticDescriptors(analyzer);
return _hostAnalyzerManager.GetDiagnosticDescriptors(analyzer);
}

public void Reanalyze(Workspace workspace, IEnumerable<ProjectId> projectIds = null, IEnumerable<DocumentId> documentIds = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private BaseDiagnosticIncrementalAnalyzer CreateIncrementalAnalyzerCallback(Work
{
// subscribe to active context changed event for new workspace
workspace.DocumentActiveContextChanged += OnDocumentActiveContextChanged;
return new IncrementalAnalyzerDelegatee(this, workspace, _workspaceAnalyzerManager, _hostDiagnosticUpdateSource);
return new IncrementalAnalyzerDelegatee(this, workspace, _hostAnalyzerManager, _hostDiagnosticUpdateSource);
}

private void OnDocumentActiveContextChanged(object sender, DocumentEventArgs e)
Expand All @@ -60,7 +60,7 @@ private void OnDocumentActiveContextChanged(object sender, DocumentEventArgs e)
// internal for testing
internal class IncrementalAnalyzerDelegatee : BaseDiagnosticIncrementalAnalyzer
{
private readonly WorkspaceAnalyzerManager _workspaceAnalyzerManager;
private readonly HostAnalyzerManager _hostAnalyzerManager;
private readonly DiagnosticAnalyzerService _owner;

// v1 diagnostic engine
Expand All @@ -69,17 +69,17 @@ internal class IncrementalAnalyzerDelegatee : BaseDiagnosticIncrementalAnalyzer
// v2 diagnostic engine - for now v1
private readonly EngineV2.DiagnosticIncrementalAnalyzer _engineV2;

public IncrementalAnalyzerDelegatee(DiagnosticAnalyzerService owner, Workspace workspace, WorkspaceAnalyzerManager workspaceAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource)
public IncrementalAnalyzerDelegatee(DiagnosticAnalyzerService owner, Workspace workspace, HostAnalyzerManager hostAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource)
: base(workspace, hostDiagnosticUpdateSource)
{
_workspaceAnalyzerManager = workspaceAnalyzerManager;
_hostAnalyzerManager = hostAnalyzerManager;
_owner = owner;

var v1CorrelationId = LogAggregator.GetNextId();
_engineV1 = new EngineV1.DiagnosticIncrementalAnalyzer(_owner, v1CorrelationId, workspace, _workspaceAnalyzerManager, hostDiagnosticUpdateSource);
_engineV1 = new EngineV1.DiagnosticIncrementalAnalyzer(_owner, v1CorrelationId, workspace, _hostAnalyzerManager, hostDiagnosticUpdateSource);

var v2CorrelationId = LogAggregator.GetNextId();
_engineV2 = new EngineV2.DiagnosticIncrementalAnalyzer(_owner, v2CorrelationId, workspace, _workspaceAnalyzerManager, hostDiagnosticUpdateSource);
_engineV2 = new EngineV2.DiagnosticIncrementalAnalyzer(_owner, v2CorrelationId, workspace, _hostAnalyzerManager, hostDiagnosticUpdateSource);
}

#region IIncrementalAnalyzer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ internal DiagnosticAnalyzerService(ImmutableDictionary<string, ImmutableArray<Di
{
}

// Internal for testing purposes.
internal DiagnosticAnalyzerService(AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource = null)
: this(workspaceAnalyzerAssemblies: SpecializedCollections.EmptyEnumerable<string>(), hostDiagnosticUpdateSource: hostDiagnosticUpdateSource)
{
}

private class TestAnalyzerReferenceByLanguage : AnalyzerReference
{
private readonly ImmutableDictionary<string, ImmutableArray<DiagnosticAnalyzer>> _analyzersMap;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,19 +339,19 @@ internal void ReportAnalyzerExceptionDiagnostic(DiagnosticAnalyzer analyzer, Dia

private Action<Diagnostic> GetAddExceptionDiagnosticDelegate(DiagnosticAnalyzer analyzer)
{
return AnalyzerHelper.GetAddExceptionDiagnosticDelegate(analyzer, _hostDiagnosticUpdateSource, _project);
return analyzer.GetAddExceptionDiagnosticDelegate(_hostDiagnosticUpdateSource, _project);
}

private AnalyzerExecutor GetAnalyzerExecutorForSupportedDiagnostics(DiagnosticAnalyzer analyzer)
{
// Skip telemetry logging if the exception is thrown as we are computing supported diagnostics and
// we can't determine if any descriptors support getting telemetry without having the descriptors.
return AnalyzerHelper.GetAnalyzerExecutorForSupportedDiagnostics(analyzer, _hostDiagnosticUpdateSource, CatchAnalyzerException_NoTelemetryLogging, _cancellationToken);
return analyzer.GetAnalyzerExecutorForSupportedDiagnostics(_hostDiagnosticUpdateSource, CatchAnalyzerException_NoTelemetryLogging, _cancellationToken);
}

private AnalyzerExecutor GetAnalyzerExecutor(DiagnosticAnalyzer analyzer, Compilation compilation, Action<Diagnostic> addDiagnostic)
{
return AnalyzerHelper.GetAnalyzerExecutor(analyzer, _hostDiagnosticUpdateSource, _project,
return analyzer.GetAnalyzerExecutor(_hostDiagnosticUpdateSource, _project,
compilation, addDiagnostic, _analyzerOptions, CatchAnalyzerException, _cancellationToken);
}

Expand Down Expand Up @@ -573,7 +573,7 @@ internal static bool CatchAnalyzerException_NoTelemetryLogging(Exception e, Diag
return false;
}

if (AnalyzerHelper.IsBuiltInAnalyzer(analyzer))
if (analyzer.IsBuiltInAnalyzer())
{
return FatalError.ReportWithoutCrashUnlessCanceled(e);
}
Expand Down
Loading