Skip to content

Conversation

CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi commented Sep 4, 2025

@dotnet-policy-service dotnet-policy-service bot added VSCode Needs API Review Needs to be reviewed by the API review council labels Sep 4, 2025
namespace A
{
using System;
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 is the correct behavior. The test here is testing " 'usings' should be INSIDE the namespace, and it is an ERROR otherwise". So it was a bug this wasn't moving in in CodeCleanup.

Note: this differs from teh tests taht validate that nothing change when the option suggestions the change, but the severity is set to 'hidden'. in that case we do not make any change.

Copy link
Member

@jasonmalinowski jasonmalinowski Sep 12, 2025

Choose a reason for hiding this comment

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

Do you have a sense why this changed? (I agree the behavior looks right, but this means we had a bug somewhere...)

Copy link
Member Author

Choose a reason for hiding this comment

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

When i was debugging through, teh wrong options were coming through, and it meant that the default severity was getting pulled in (which is 'hidden'). Because of this, the codecleanup layer doesn't actually want to make a change due to 'hidden' effectively being equivalent to 'the user didn't actually set anything themselves'.

@CyrusNajmabadi
Copy link
Member Author

Draft until API is reviewed and approved.

@CyrusNajmabadi CyrusNajmabadi changed the title No compilation pair with api change Remove CompilationPair abstraction in diagnostic analyzer subsystem (with api change) Sep 4, 2025
{
// No specific options factory. Can use the shared context.
if (_analyzerToCachedOptions is null)
return context;
Copy link
Member Author

Choose a reason for hiding this comment

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

note: this is the common case for those not referencing hte roslyn sdk. So there should be almost no overhead in that common case.

internal SymbolAnalysisContext WithOptions(AnalyzerOptions options)
=> this.Options == options
? this
: new(this.Symbol, this.Compilation, options, _reportDiagnostic, _isSupportedDiagnostic, this.IsGeneratedCode, this.FilterTree, this.FilterSpan, this.CancellationToken);
Copy link
Member Author

Choose a reason for hiding this comment

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

note: these context types are structs. So this is not actually allocating a new instance on the heap. Just returning a tweaked version with the right options to pass along.

/// from the shared instance provided in <see cref="Options"/>. If <see langword="null"/> then <see cref="Options"/>
/// will be used for all analyzers.
/// </summary>
public Func<DiagnosticAnalyzer, AnalyzerOptions>? AnalyzerSpecificOptionsFactory
Copy link
Member Author

Choose a reason for hiding this comment

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

note: this prop doesn't need to be public (though that matches the reest here). The API review covers if we should expose this (since it was a constructor param) or if we should keep it internally since we hvve no actual public consumers.

bool logAnalyzerExecutionTime,
bool reportSuppressedDiagnostics,
Func<Exception, bool>? analyzerExceptionFilter,
Func<DiagnosticAnalyzer, AnalyzerOptions>? analyzerSpecificOptionsFactory)
Copy link
Member Author

Choose a reason for hiding this comment

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

new public API that needs API review.

analyzerExceptionFilter: exceptionFilter,
analyzerSpecificOptionsFactory));

(AnalyzerOptions sharedOptions, Func<DiagnosticAnalyzer, AnalyzerOptions>? analyzerSpecificOptionsFactory) GetOptions()
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 is hte meat of the change on the IDE side.

var map = new Dictionary<DiagnosticAnalyzer, AnalyzerOptions>(ReferenceEqualityComparer.Instance);

// Deduping map for the distinct AnalyzerConfigOptionsProvider we get back from analyzerSpecificOptionsFactory
var optionsProviderToOptions = new Dictionary<AnalyzerConfigOptionsProvider, AnalyzerOptions>(ReferenceEqualityComparer.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.

note: this deduping is tested. we make sure that if two analyzers return the same AnalyzerConfigOptionsProvider that they will then get passed the exact same AnalyzerOptions instance in callbacks.

@CyrusNajmabadi
Copy link
Member Author

API change was approved. @jasonmalinowski @dotnet/roslyn-compiler for eyes.

diagnostics.Verify(Diagnostic("ID0001", "B").WithLocation(1, 8));
}

private sealed class OptionsOverrideDiagnosticAnalyzer(AnalyzerConfigOptionsProvider customOptions) : DiagnosticAnalyzer
Copy link
Member Author

Choose a reason for hiding this comment

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

these tests validate that we see the different options through all the callbacks.

Microsoft.CodeAnalysis.CommandLineResource.LinkedResourceFileName.get -> string?
Microsoft.CodeAnalysis.CommandLineResource.ResourceName.get -> string!
Microsoft.CodeAnalysis.Compilation.EmitDifference(Microsoft.CodeAnalysis.Emit.EmitBaseline! baseline, System.Collections.Generic.IEnumerable<Microsoft.CodeAnalysis.Emit.SemanticEdit>! edits, System.Func<Microsoft.CodeAnalysis.ISymbol!, bool>! isAddedSymbol, System.IO.Stream! metadataStream, System.IO.Stream! ilStream, System.IO.Stream! pdbStream, Microsoft.CodeAnalysis.Emit.EmitDifferenceOptions options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Microsoft.CodeAnalysis.Emit.EmitDifferenceResult!
Microsoft.CodeAnalysis.Diagnostics.CompilationWithAnalyzersOptions.CompilationWithAnalyzersOptions(Microsoft.CodeAnalysis.Diagnostics.AnalyzerOptions? options, System.Action<System.Exception!, Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer!, Microsoft.CodeAnalysis.Diagnostic!>? onAnalyzerException, bool concurrentAnalysis, bool logAnalyzerExecutionTime, bool reportSuppressedDiagnostics, System.Func<System.Exception!, bool>? analyzerExceptionFilter, System.Func<Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer!, Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider!>? getAnalyzerConfigOptionsProvider) -> void
Copy link
Member Author

Choose a reason for hiding this comment

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

only public change is this one constructor.

@CyrusNajmabadi CyrusNajmabadi marked this pull request as draft September 9, 2025 21:11
@CyrusNajmabadi CyrusNajmabadi changed the title Remove CompilationPair abstraction in diagnostic analyzer subsystem (with api change) Remove CompilationPair abstraction in diagnostic analyzer subsystem Sep 12, 2025
@CyrusNajmabadi CyrusNajmabadi marked this pull request as ready for review September 12, 2025 22:01
@CyrusNajmabadi
Copy link
Member Author

@jasonmalinowski this is ready for review. I'm also going to kick off another insertion run to ensure everything is as expected.

@CyrusNajmabadi CyrusNajmabadi removed request for a team September 12, 2025 22:06
Copy link
Member

Choose a reason for hiding this comment

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

I assume you'll be renaming the file as a follow-up.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup. I'm even working on making the "make filename match type" fixer able to fix-all. So i can do these in bulk.

CancellationToken cancellationToken)
{
var hostAnalyzers = documentAnalysisScope?.HostAnalyzers ?? compilationWithAnalyzers.HostAnalyzers;
var hostAnalyzers = documentAnalysisScope?.Analyzers ?? compilationWithAnalyzers.Analyzers;
Copy link
Member

Choose a reason for hiding this comment

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

Not sure if local needs a rename, because it's suspicious it was 'hostAnalyzers' before?

Copy link
Member Author

Choose a reason for hiding this comment

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

will do. i agree it's suspicious. can you think of a reason we'd only be using hostanalyzers for suppression analyzers? @mavasani do you perhaps knows?

i'm tempted to leave this as is (with the rename). but if there's a sound reason for only using host analyzers, we could always filter these down to those.

Unfortunately, we don't have any tests indicating if this is intentional or errant.

namespace A
{
using System;
Copy link
Member

@jasonmalinowski jasonmalinowski Sep 12, 2025

Choose a reason for hiding this comment

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

Do you have a sense why this changed? (I agree the behavior looks right, but this means we had a bug somewhere...)

Copy link
Member Author

@CyrusNajmabadi CyrusNajmabadi left a comment

Choose a reason for hiding this comment

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

comment

RoslynDebug.Assert(AnalysisScope.TextDocument is Document);
Contract.ThrowIfFalse(analyzer.IsCompilerAnalyzer());
Contract.ThrowIfNull(_compilationWithAnalyzers);
Contract.ThrowIfFalse(_compilationBasedAnalyzersInAnalysisScope.Contains(analyzer));
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 even already had this assert here. so it massively simplifies the code below.

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.

I sign off; obviously that question is still pending but I'm not sure I can figure that one out...

filteredHostAnalyzers = filteredHostAnalyzers.AddRange(filteredProjectSuppressors);
// Ensure we filter out DocumentDiagnosticAnalyzers (they're used to get diagnostics, without involving a
// compilation), and also ensure the list has no duplicates.
analyzers = analyzers.WhereAsArray(static a => !a.IsWorkspaceDiagnosticAnalyzer()).Distinct();
Copy link
Contributor

@ToddGrun ToddGrun Sep 13, 2025

Choose a reason for hiding this comment

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

Distinct

Huh, I would have thought the IA.Distinct extension method would have used a PooledHashSet (in the default comparer case)

@CyrusNajmabadi CyrusNajmabadi merged commit 95b192d into dotnet:main Sep 16, 2025
25 checks passed
@CyrusNajmabadi CyrusNajmabadi deleted the noCompilationPairWithApiChange branch September 16, 2025 11:54
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Sep 16, 2025
@akhera99 akhera99 modified the milestones: Next, 18.0 P1, 18.0 P2 Sep 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Analyzers Needs API Review Needs to be reviewed by the API review council VSCode
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants