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 @@ -20,7 +20,10 @@

namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.Analyzer;

internal sealed class AnalyzerSettingsProvider : SettingsProviderBase<AnalyzerSetting, AnalyzerSettingsUpdater, AnalyzerSetting, ReportDiagnostic>
internal sealed class AnalyzerSettingsProvider
: SettingsProviderBase<AnalyzerSetting, AnalyzerSettingsUpdater, AnalyzerSetting, ReportDiagnostic>,
// So we can unify descriptors across VB/C# to create single settings that apply to both languages.
IEqualityComparer<DiagnosticDescriptor>
{
public AnalyzerSettingsProvider(
IThreadingContext threadingContext,
Expand All @@ -43,7 +46,6 @@ protected override async Task UpdateOptionsAsync(
{
foreach (var analyzerReference in project.AnalyzerReferences)
analyzerReferenceToSomeReferencingProject[analyzerReference] = project;

}

foreach (var analyzerReference in analyzerReferences)
Expand All @@ -60,33 +62,16 @@ private async Task<ImmutableArray<AnalyzerSetting>> GetSettingsAsync(
{
var solution = someReferencingProject.Solution;
var service = solution.Services.GetRequiredService<IDiagnosticAnalyzerService>();
var map = await service.GetLanguageKeyedDiagnosticDescriptorsAsync(
solution, someReferencingProject.Id, analyzerReference, cancellationToken).ConfigureAwait(false);

using var _ = ArrayBuilder<AnalyzerSetting>.GetInstance(out var allSettings);

foreach (var (languages, descriptors) in map)
allSettings.AddRange(ToAnalyzerSettings(descriptors, ConvertToLanguage(languages)));
var csharpDescriptors = await service.GetDiagnosticDescriptorsAsync(solution, someReferencingProject.Id, analyzerReference, LanguageNames.CSharp, cancellationToken).ConfigureAwait(false);
var vbDescriptors = await service.GetDiagnosticDescriptorsAsync(solution, someReferencingProject.Id, analyzerReference, LanguageNames.VisualBasic, cancellationToken).ConfigureAwait(false);

return allSettings.ToImmutableAndClear();
var dotnetDescriptors = csharpDescriptors.Intersect(vbDescriptors, this).ToImmutableArray();

Language ConvertToLanguage(ImmutableArray<string> languages)
{
Contract.ThrowIfTrue(languages.Length == 0);
var language = (Language)0;

foreach (var languageString in languages)
{
language |= languageString switch
{
LanguageNames.CSharp => Language.CSharp,
LanguageNames.VisualBasic => Language.VisualBasic,
_ => throw new ArgumentException($"Unsupported language: {languageString}")
};
}

return language;
}
return [
.. ToAnalyzerSettings(csharpDescriptors.Except(dotnetDescriptors), Language.CSharp),
.. ToAnalyzerSettings(vbDescriptors.Except(dotnetDescriptors), Language.VisualBasic),
.. ToAnalyzerSettings(dotnetDescriptors, Language.CSharp | Language.VisualBasic)];

IEnumerable<AnalyzerSetting> ToAnalyzerSettings(
IEnumerable<DiagnosticDescriptor> descriptors, Language language)
Expand All @@ -104,4 +89,10 @@ IEnumerable<AnalyzerSetting> ToAnalyzerSettings(
});
}
}

bool IEqualityComparer<DiagnosticDescriptor>.Equals(DiagnosticDescriptor x, DiagnosticDescriptor y)
=> x.Id == y.Id;

int IEqualityComparer<DiagnosticDescriptor>.GetHashCode(DiagnosticDescriptor obj)
=> obj.Id.GetHashCode();
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,6 @@ Task<ImmutableArray<DiagnosticData>> GetDiagnosticsForSpanAsync(
Task<ImmutableArray<DiagnosticDescriptor>> GetDiagnosticDescriptorsAsync(
Solution solution, ProjectId projectId, AnalyzerReference analyzerReference, string language, CancellationToken cancellationToken);

/// <summary>
/// Returns all the descriptors for all <see cref="DiagnosticAnalyzer"/>s defined within <paramref name="analyzerReference"/>.
/// The results are returned in a dictionary where the key is an <see cref="ImmutableArray{T}"/> of languages that descriptor
/// is defined for. This can be <c>[<see cref="LanguageNames.CSharp"/>]</c>, <c>[<see cref="LanguageNames.VisualBasic"/>]</c>,
/// or an array containing both languages if the descriptor is defined for both languages.
/// </summary>
/// <param name="projectId">A project within <paramref name="solution"/> where <paramref name="analyzerReference"/> can be found</param>
Task<ImmutableDictionary<ImmutableArray<string>, ImmutableArray<DiagnosticDescriptor>>> GetLanguageKeyedDiagnosticDescriptorsAsync(
Solution solution, ProjectId projectId, AnalyzerReference analyzerReference, CancellationToken cancellationToken);

/// <summary>
/// Given a list of errors ids (like CS1234), attempts to find an associated descriptor for each id.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ internal sealed partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerSer
{
private static readonly Option2<bool> s_crashOnAnalyzerException = new("dotnet_crash_on_analyzer_exception", defaultValue: false);

private static readonly ImmutableArray<string> s_csharpLanguageArray = [LanguageNames.CSharp];
private static readonly ImmutableArray<string> s_visualBasicLanguageArray = [LanguageNames.VisualBasic];
private static readonly ImmutableArray<string> s_csharpAndVisualBasicLanguageArray = [.. s_csharpLanguageArray, .. s_visualBasicLanguageArray];

public IAsynchronousOperationListener Listener { get; }
private IGlobalOptionService GlobalOptions { get; }

Expand Down Expand Up @@ -278,46 +274,6 @@ public async Task<ImmutableArray<DiagnosticDescriptor>> GetDiagnosticDescriptors
.SelectManyAsArray(this._analyzerInfoCache.GetDiagnosticDescriptors);
}

public async Task<ImmutableDictionary<ImmutableArray<string>, ImmutableArray<DiagnosticDescriptor>>> GetLanguageKeyedDiagnosticDescriptorsAsync(
Solution solution, ProjectId projectId, AnalyzerReference analyzerReference, CancellationToken cancellationToken)
{
var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false);
if (client is not null &&
analyzerReference is AnalyzerFileReference analyzerFileReference)
{
var map = await client.TryInvokeAsync<IRemoteDiagnosticAnalyzerService, ImmutableDictionary<ImmutableArray<string>, ImmutableArray<DiagnosticDescriptorData>>>(
solution,
(service, solution, cancellationToken) => service.GetLanguageKeyedDiagnosticDescriptorsAsync(solution, projectId, analyzerFileReference.FullPath, cancellationToken),
cancellationToken).ConfigureAwait(false);

if (!map.HasValue)
return ImmutableDictionary<ImmutableArray<string>, ImmutableArray<DiagnosticDescriptor>>.Empty;

return map.Value.ToImmutableDictionary(
kvp => kvp.Key,
kvp => kvp.Value.SelectAsArray(d => d.ToDiagnosticDescriptor()));
}

// Otherwise, fallback to computing in proc.
var mapBuilder = ImmutableDictionary.CreateBuilder<ImmutableArray<string>, ImmutableArray<DiagnosticDescriptor>>();

var csharpAnalyzers = analyzerReference.GetAnalyzers(LanguageNames.CSharp);
var visualBasicAnalyzers = analyzerReference.GetAnalyzers(LanguageNames.VisualBasic);

var dotnetAnalyzers = csharpAnalyzers.Intersect(visualBasicAnalyzers, DiagnosticAnalyzerComparer.Instance).ToImmutableArray();
csharpAnalyzers = [.. csharpAnalyzers.Except(dotnetAnalyzers, DiagnosticAnalyzerComparer.Instance)];
visualBasicAnalyzers = [.. visualBasicAnalyzers.Except(dotnetAnalyzers, DiagnosticAnalyzerComparer.Instance)];

mapBuilder.Add(s_csharpLanguageArray, GetDiagnosticDescriptors(csharpAnalyzers));
mapBuilder.Add(s_visualBasicLanguageArray, GetDiagnosticDescriptors(visualBasicAnalyzers));
mapBuilder.Add(s_csharpAndVisualBasicLanguageArray, GetDiagnosticDescriptors(dotnetAnalyzers));

return mapBuilder.ToImmutable();

ImmutableArray<DiagnosticDescriptor> GetDiagnosticDescriptors(ImmutableArray<DiagnosticAnalyzer> analyzers)
=> analyzers.SelectManyAsArray(this._analyzerInfoCache.GetDiagnosticDescriptors);
}

public async Task<ImmutableDictionary<string, DiagnosticDescriptor>> TryGetDiagnosticDescriptorsAsync(
Solution solution, ImmutableArray<string> diagnosticIds, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -478,38 +434,6 @@ public async Task<ImmutableArray<DiagnosticData>> ComputeDiagnosticsAsync(
incrementalAnalysis, logPerformanceInfo, cancellationToken).ConfigureAwait(false);
}

private sealed class DiagnosticAnalyzerComparer : IEqualityComparer<DiagnosticAnalyzer>
{
public static readonly DiagnosticAnalyzerComparer Instance = new();

public bool Equals(DiagnosticAnalyzer? x, DiagnosticAnalyzer? y)
=> (x, y) switch
{
(null, null) => true,
(null, _) => false,
(_, null) => false,
_ => GetAnalyzerIdAndLastWriteTime(x) == GetAnalyzerIdAndLastWriteTime(y)
};

public int GetHashCode(DiagnosticAnalyzer obj) => GetAnalyzerIdAndLastWriteTime(obj).GetHashCode();

private static (string analyzerId, DateTime lastWriteTime) GetAnalyzerIdAndLastWriteTime(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 typeInfo = analyzer.GetType().GetTypeInfo();
return (analyzer.GetAnalyzerId(), GetAnalyzerLastWriteTime(typeInfo.Assembly.Location));
}

private static DateTime GetAnalyzerLastWriteTime(string path)
{
if (path == null || !File.Exists(path))
return default;

return File.GetLastWriteTimeUtc(path);
}
}

public TestAccessor GetTestAccessor()
=> new(this);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ ValueTask<ImmutableArray<DiagnosticData>> ProduceProjectDiagnosticsAsync(
ValueTask<ImmutableArray<DiagnosticDescriptorData>> GetDiagnosticDescriptorsAsync(
Checksum solutionChecksum, ProjectId projectId, string analyzerReferenceFullPath, string language, CancellationToken cancellationToken);

ValueTask<ImmutableDictionary<ImmutableArray<string>, ImmutableArray<DiagnosticDescriptorData>>> GetLanguageKeyedDiagnosticDescriptorsAsync(
Checksum solutionChecksum, ProjectId projectId, string analyzerReferenceFullPath, CancellationToken cancellationToken);

ValueTask<ImmutableDictionary<string, DiagnosticDescriptorData>> TryGetDiagnosticDescriptorsAsync(
Checksum solutionChecksum, ImmutableArray<string> diagnosticIds, CancellationToken cancellationToken);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,30 +151,6 @@ public ValueTask<ImmutableArray<DiagnosticDescriptorData>> GetDiagnosticDescript
cancellationToken);
}

public ValueTask<ImmutableDictionary<ImmutableArray<string>, ImmutableArray<DiagnosticDescriptorData>>> GetLanguageKeyedDiagnosticDescriptorsAsync(
Checksum solutionChecksum,
ProjectId projectId,
string analyzerReferenceFullPath,
CancellationToken cancellationToken)
{
return RunWithSolutionAsync(
solutionChecksum,
async solution =>
{
var service = solution.Services.GetRequiredService<IDiagnosticAnalyzerService>();
var project = solution.GetRequiredProject(projectId);
var analyzerReference = project.AnalyzerReferences
.First(r => r.FullPath == analyzerReferenceFullPath);

var map = await service.GetLanguageKeyedDiagnosticDescriptorsAsync(
solution, projectId, analyzerReference, cancellationToken).ConfigureAwait(false);
return map.ToImmutableDictionary(
kvp => kvp.Key,
kvp => kvp.Value.SelectAsArray(DiagnosticDescriptorData.Create));
},
cancellationToken);
}

public ValueTask<ImmutableDictionary<string, DiagnosticDescriptorData>> TryGetDiagnosticDescriptorsAsync(
Checksum solutionChecksum,
ImmutableArray<string> diagnosticIds,
Expand Down
Loading