Skip to content

Conversation

@CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi commented Aug 6, 2025

Followup to #79802.

if (x is null || y is null)
return false;

return x.GetAnalyzerIdAndVersion().GetHashCode() == y.GetAnalyzerIdAndVersion().GetHashCode();
Copy link
Member Author

Choose a reason for hiding this comment

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

aside: taht this is using HashCodes to determine equality is suppppper sus.

private ImmutableArray<AnalyzerSetting> GetSettings(
Solution solution, AnalyzerReference analyzerReference, AnalyzerConfigOptions editorConfigOptions)
{
IEnumerable<DiagnosticAnalyzer> csharpAnalyzers = analyzerReference.GetAnalyzers(LanguageNames.CSharp);
Copy link
Member Author

Choose a reason for hiding this comment

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

instead of realiing DiagAnalyzers here,e it gets moved into the IDiagService, which can then be the one stop shop for asking these questions in a general fashion. IN the future, IDiagService can then forward thse calls to OOP to do the actual work.

// DiagnosticAnalyzerInfoCache AnalyzerInfoCache { get; }

ImmutableArray<DiagnosticDescriptor> GetDiagnosticDescriptors(
Solution solution, AnalyzerReference analyzerReference, string language);
Copy link
Member Author

Choose a reason for hiding this comment

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

the IDiagService now gets data-oriented apis that were previous based on DiagAnalyzer and exposed off of DiagnosticAnalyzerInfoCache . This will allow these to, in the future, call over to oop.

Note: they will need to be made async first.

if (x is null || y is null)
return false;

return x.GetAnalyzerIdAndVersion().GetHashCode() == y.GetAnalyzerIdAndVersion().GetHashCode();
Copy link
Member Author

Choose a reason for hiding this comment

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

if it passes, i'm changing this equality to not use GetHashCode.

GetDiagnosticSeverity(error),
_language,
new FileLinePositionSpan(project.FilePath ?? "", span: default),
AnalyzerInfoCache));
Copy link
Member Author

Choose a reason for hiding this comment

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

this code used to directly access this cache. now, we just call into IDiagService up front to get the info we used to try to get inside GetDiagnosticData

ImmutableArray<string> customTags;

if (analyzerInfoCache != null && analyzerInfoCache.TryGetDescriptorForDiagnosticId(errorId, out var descriptor))
if (errorIdToDescriptor.TryGetValue(errorId, out var descriptor))
Copy link
Member Author

Choose a reason for hiding this comment

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

all this was previously trying to do was map from errorid to a DiagDescriptor. so i made an api to do that (in bulk) so this can work in oop as well.

@CyrusNajmabadi CyrusNajmabadi requested review from AbhitejJohn and jasonmalinowski and removed request for AbhitejJohn August 6, 2025 15:38
@CyrusNajmabadi CyrusNajmabadi marked this pull request as ready for review August 6, 2025 15:40
@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner August 6, 2025 15:40

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

Choose a reason for hiding this comment

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

all the acutal logic was moved here from the consuming client code on the outside.

@CyrusNajmabadi
Copy link
Member Author

@jasonmalinowski this is ready for review.

IEnumerable<DiagnosticAnalyzer> visualBasicAnalyzers = analyzerReference.GetAnalyzers(LanguageNames.VisualBasic);
var dotnetAnalyzers = csharpAnalyzers.Intersect(visualBasicAnalyzers, DiagnosticAnalyzerComparer.Instance);
csharpAnalyzers = csharpAnalyzers.Except(dotnetAnalyzers, DiagnosticAnalyzerComparer.Instance);
visualBasicAnalyzers = visualBasicAnalyzers.Except(dotnetAnalyzers, DiagnosticAnalyzerComparer.Instance);
Copy link
Member Author

Choose a reason for hiding this comment

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

this logic moved into DiagAnalService


var descriptors = analyzerReference
.GetAnalyzers(project.Language)
.SelectManyAsArray(a => diagnosticAnalyzerService.AnalyzerInfoCache.GetDiagnosticDescriptors(a));
Copy link
Member Author

Choose a reason for hiding this comment

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

this logic moved into DiagAnalService

@CyrusNajmabadi
Copy link
Member Author

@jasonmalinowski ptal.

@CyrusNajmabadi
Copy link
Member Author

@jasonmalinowski Also, if the feedback is non-critical, let me know and ideally make non-blocking. There are multiple followup PRs, so it would be nicer to just roll the feedback into those.

csharpAnalyzers = csharpAnalyzers.Except(dotnetAnalyzers, DiagnosticAnalyzerComparer.Instance);
visualBasicAnalyzers = visualBasicAnalyzers.Except(dotnetAnalyzers, DiagnosticAnalyzerComparer.Instance);
var service = solution.Services.GetRequiredService<IDiagnosticAnalyzerService>();
var map = service.GetDiagnosticDescriptors(solution, analyzerReference);
Copy link
Member Author

Choose a reason for hiding this comment

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

general pattern is takign the code that actually operates on DiagAnalyzers and push that into the IDiagnosticAnalyzerService impl. Then the code that processes the computed data stays at the feature level.

Copy link
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

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

This all looks good except for the race being introduced into the external error reporting. Now that we're processing stuff asynchronously and adding it after the call, it would mean a later call to the Clear() methods won't clear them, and worse they might live around once they complete later.

I think a quick use of CancellationTokenSeries should fix up the issue though.

internal interface IDiagnosticAnalyzerService : IWorkspaceService
{
ImmutableArray<DiagnosticDescriptor> GetDiagnosticDescriptors(
Solution solution, AnalyzerReference analyzerReference, string language);
Copy link
Member

Choose a reason for hiding this comment

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

Should this be taking a Project rather than Solution + language name?

Copy link
Member Author

Choose a reason for hiding this comment

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

actually no. this is called from clients who take the same AnalyzerRef (which might be in multiple projects) and try to get both the C# and VB descriptors. Those could pass projects, but it would take more effort on that side to keep track of the originating project. so i didn't do that for now.


return allSettings.ToImmutableAndClear();

Language ConvertToLanguage(ImmutableArray<string> languages)
Copy link
Member

Choose a reason for hiding this comment

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

Can we do static extension methods on the enum? (OK to not actually do this -- this code might be going away soon enough)

Copy link
Member Author

Choose a reason for hiding this comment

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

we can. but i also don't mind this just being local here as it is the only place needed.

Comment on lines +21 to +24
/// 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.
Copy link
Member

Choose a reason for hiding this comment

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

If the .editorconfig editor was the only thing that needed this crazy form, I'm content to say maybe we just tweak it to use this other form. I appreciate the comment but it's still a bit mind-bending!

If we do need to keep it around, maybe switching it to be a DiagnosticDescriptor -> array of languages it applies to) might make it clearer.

Copy link
Member Author

Choose a reason for hiding this comment

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

i'm ok doing that later :)
my goals here were: get this OOPable, without changing semantics :)

i def thing later cleanup is a good idea.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed fully.

public ImmutableDictionary<string, ImmutableArray<DiagnosticDescriptor>> GetDiagnosticDescriptorsPerReference(Project project)
=> project.Solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(this._analyzerInfoCache, project);

private sealed class DiagnosticAnalyzerComparer : IEqualityComparer<DiagnosticAnalyzer>
Copy link
Member

Choose a reason for hiding this comment

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

Did you get any sense why this was being done this way versus just object identity?

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah. the more i think about it, the more this seems like nonsense.

@CyrusNajmabadi CyrusNajmabadi merged commit ff3da04 into dotnet:main Aug 9, 2025
24 of 25 checks passed
@CyrusNajmabadi CyrusNajmabadi deleted the hideAnalyzerCache branch August 9, 2025 01:43
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Aug 9, 2025
@RikkiGibson RikkiGibson modified the milestones: Next, 18.0 P1 Aug 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants