diff --git a/src/libraries/Common/tests/SourceGenerators/GlobalOptionsOnlyProvider.cs b/src/libraries/Common/tests/SourceGenerators/GlobalOptionsOnlyProvider.cs
index 09b8b86af77d02..c44a9bf04ec75a 100644
--- a/src/libraries/Common/tests/SourceGenerators/GlobalOptionsOnlyProvider.cs
+++ b/src/libraries/Common/tests/SourceGenerators/GlobalOptionsOnlyProvider.cs
@@ -1,15 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System;
-using System.Diagnostics;
+using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
-using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
-using Microsoft.Interop;
-using Microsoft.Interop.UnitTests;
-using SourceGenerators.Tests;
namespace SourceGenerators.Tests
{
@@ -28,23 +23,30 @@ public GlobalOptionsOnlyProvider(AnalyzerConfigOptions globalOptions)
public sealed override AnalyzerConfigOptions GetOptions(SyntaxTree tree)
{
- return EmptyOptions.Instance;
+ return DictionaryAnalyzerConfigOptions.Empty;
}
public sealed override AnalyzerConfigOptions GetOptions(AdditionalText textFile)
{
- return EmptyOptions.Instance;
+ return DictionaryAnalyzerConfigOptions.Empty;
}
+ }
- private sealed class EmptyOptions : AnalyzerConfigOptions
- {
- public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
- {
- value = null;
- return false;
- }
+ ///
+ /// An implementation of backed by an .
+ ///
+ internal sealed class DictionaryAnalyzerConfigOptions : AnalyzerConfigOptions
+ {
+ public static readonly DictionaryAnalyzerConfigOptions Empty = new(ImmutableDictionary.Empty);
+
+ private readonly ImmutableDictionary _options;
- public static AnalyzerConfigOptions Instance = new EmptyOptions();
+ public DictionaryAnalyzerConfigOptions(ImmutableDictionary options)
+ {
+ _options = options;
}
+
+ public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value)
+ => _options.TryGetValue(key, out value);
}
}
diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Suppressor.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Suppressor.cs
index 6e9a1448fcbfb7..3806567ca6ac54 100644
--- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Suppressor.cs
+++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/ConfigurationBindingGenerator.Suppressor.cs
@@ -1,11 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Linq;
+using System.Threading;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
+using Microsoft.CodeAnalysis.Text;
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
@@ -35,6 +40,9 @@ public sealed class Suppressor : DiagnosticSuppressor
public override void ReportSuppressions(SuppressionAnalysisContext context)
{
+ // Lazily built set of locations that were actually intercepted by the generator.
+ HashSet? interceptedLocationKeys = null;
+
foreach (Diagnostic diagnostic in context.ReportedDiagnostics)
{
string diagnosticId = diagnostic.Id;
@@ -51,22 +59,146 @@ public override void ReportSuppressions(SuppressionAnalysisContext context)
// The trim analyzer changed from warning on the InvocationExpression to the MemberAccessExpression in https://github.com/dotnet/runtime/pull/110086
// In other words, the warning location went from from `{|Method1(arg1, arg2)|}` to `{|Method1|}(arg1, arg2)`
// To account for this, we need to check if the location is an InvocationExpression or a child of an InvocationExpression.
- bool shouldSuppressDiagnostic =
- location.SourceTree is SyntaxTree sourceTree &&
- sourceTree.GetRoot().FindNode(location.SourceSpan) is SyntaxNode syntaxNode &&
- (syntaxNode as InvocationExpressionSyntax ?? syntaxNode.Parent as InvocationExpressionSyntax) is InvocationExpressionSyntax invocation &&
- BinderInvocation.IsCandidateSyntaxNode(invocation) &&
- context.GetSemanticModel(sourceTree)
- .GetOperation(invocation, context.CancellationToken) is IInvocationOperation operation &&
- BinderInvocation.IsBindingOperation(operation);
-
- if (shouldSuppressDiagnostic)
+ // Use getInnermostNodeForTie to handle the case where the binding call is an
+ // argument to another method (e.g. Some.Method(config.Get())). In that case,
+ // ArgumentSyntax and the inner InvocationExpressionSyntax can share the same span.
+ if (location.SourceTree is not SyntaxTree sourceTree)
+ {
+ continue;
+ }
+
+ SyntaxNode syntaxNode = sourceTree.GetRoot(context.CancellationToken).FindNode(location.SourceSpan, getInnermostNodeForTie: true);
+ if ((syntaxNode as InvocationExpressionSyntax ?? syntaxNode.Parent as InvocationExpressionSyntax) is not InvocationExpressionSyntax invocation)
+ {
+ continue;
+ }
+
+ if (!BinderInvocation.IsCandidateSyntaxNode(invocation))
+ {
+ continue;
+ }
+
+ SemanticModel semanticModel = context.GetSemanticModel(sourceTree);
+ if (semanticModel.GetOperation(invocation, context.CancellationToken) is not IInvocationOperation operation ||
+ !BinderInvocation.IsBindingOperation(operation))
+ {
+ continue;
+ }
+
+ // Only suppress if the generator actually intercepted this call site.
+ // The generator may skip interception for unsupported types (https://github.com/dotnet/runtime/issues/96643).
+ interceptedLocationKeys ??= CollectInterceptedLocationKeys(context.Compilation, context.CancellationToken);
+
+ string? locationKey = GetInvocationLocationKey(invocation, semanticModel, context.CancellationToken);
+ if (locationKey is null || !interceptedLocationKeys.Contains(locationKey))
+ {
+ continue;
+ }
+
+ SuppressionDescriptor targetSuppression = diagnosticId == RUCDiagnostic.SuppressedDiagnosticId
+ ? RUCDiagnostic
+ : RDCDiagnostic;
+ context.ReportSuppression(Suppression.Create(targetSuppression, diagnostic));
+ }
+ }
+
+ ///
+ /// Scans the generated source trees for [InterceptsLocation] attributes and collects
+ /// all intercepted locations. For v0, locations are (filePath, line, column) tuples.
+ /// For v1, locations are the encoded data strings from the attribute.
+ ///
+ private static HashSet CollectInterceptedLocationKeys(Compilation compilation, CancellationToken cancellationToken)
+ {
+ var keys = new HashSet();
+
+ foreach (SyntaxTree tree in compilation.SyntaxTrees)
+ {
+ if (!tree.FilePath.EndsWith("BindingExtensions.g.cs", System.StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ SyntaxNode root = tree.GetRoot(cancellationToken);
+ foreach (AttributeSyntax attr in root.DescendantNodes().OfType())
+ {
+ // Matching the name like this is somewhat brittle, but it's okay as long as we match what the generator emits.
+ if (attr.Name.ToString() != "InterceptsLocation")
+ {
+ continue;
+ }
+
+ AttributeArgumentListSyntax? argList = attr.ArgumentList;
+ if (argList is null)
+ {
+ continue;
+ }
+
+ SeparatedSyntaxList args = argList.Arguments;
+
+ if (InterceptorVersion == 0)
+ {
+ // v0 format: [InterceptsLocation("filePath", line, column)]
+ if (args.Count == 3 &&
+ args[0].Expression is LiteralExpressionSyntax filePathLiteral &&
+ filePathLiteral.IsKind(SyntaxKind.StringLiteralExpression) &&
+ args[1].Expression is LiteralExpressionSyntax lineLiteral &&
+ lineLiteral.IsKind(SyntaxKind.NumericLiteralExpression) &&
+ args[2].Expression is LiteralExpressionSyntax colLiteral &&
+ colLiteral.IsKind(SyntaxKind.NumericLiteralExpression))
+ {
+ keys.Add(MakeV0Key(filePathLiteral.Token.ValueText, (int)lineLiteral.Token.Value!, (int)colLiteral.Token.Value!));
+ }
+ }
+ else
+ {
+ // v1 format: [InterceptsLocation(version, "data")]
+ if (args.Count == 2 &&
+ args[1].Expression is LiteralExpressionSyntax dataLiteral &&
+ dataLiteral.IsKind(SyntaxKind.StringLiteralExpression))
+ {
+ keys.Add(MakeV1Key(dataLiteral.Token.ValueText));
+ }
+ }
+ }
+ }
+
+ return keys;
+ }
+
+ private static string MakeV0Key(string filePath, int line, int column) => $"{filePath}|{line}|{column}";
+
+ private static string MakeV1Key(string data) => data;
+
+ private static string? GetInvocationLocationKey(InvocationExpressionSyntax invocation, SemanticModel semanticModel, CancellationToken cancellationToken)
+ {
+ if (invocation.Expression is not MemberAccessExpressionSyntax memberAccess)
+ {
+ return null;
+ }
+
+ if (InterceptorVersion == 0)
+ {
+ SyntaxTree syntaxTree = memberAccess.SyntaxTree;
+ TextSpan nameSpan = memberAccess.Name.Span;
+ FileLinePositionSpan lineSpan = syntaxTree.GetLineSpan(nameSpan, cancellationToken);
+
+ string filePath;
+ SourceReferenceResolver? resolver = semanticModel.Compilation.Options.SourceReferenceResolver;
+ filePath = resolver?.NormalizePath(syntaxTree.FilePath, baseFilePath: null) ?? syntaxTree.FilePath;
+
+ return MakeV0Key(filePath, lineSpan.StartLinePosition.Line + 1, lineSpan.StartLinePosition.Character + 1);
+ }
+ else
+ {
+ object? interceptableLocation = GetInterceptableLocationFunc?.Invoke(semanticModel, invocation, cancellationToken);
+ if (interceptableLocation is null)
{
- SuppressionDescriptor targetSuppression = diagnosticId == RUCDiagnostic.SuppressedDiagnosticId
- ? RUCDiagnostic
- : RDCDiagnostic;
- context.ReportSuppression(Suppression.Create(targetSuppression, diagnostic));
+ return null;
}
+
+ string data = (string)InterceptableLocationDataGetter!.Invoke(interceptableLocation, parameters: null)!;
+
+ return MakeV1Key(data);
}
}
}
diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs
index 5b4f903db7c210..a1e20eb7a796a3 100644
--- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs
+++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Specs/InterceptorInfo.cs
@@ -33,7 +33,7 @@ public sealed record InterceptorInfo
Debug.Assert((MethodsToGen.ConfigBinder_Bind & interceptor) is 0);
ImmutableEquatableArray? infoList;
- if ((MethodsToGen.ConfigBinder_Any ^ MethodsToGen.ConfigBinder_Bind & interceptor) is not 0)
+ if (((MethodsToGen.ConfigBinder_Any & ~MethodsToGen.ConfigBinder_Bind) & interceptor) is not 0)
{
infoList = ConfigBinder;
}
@@ -92,7 +92,7 @@ public void RegisterInterceptor(MethodsToGen overload, IInvocationOperation oper
{
Debug.Assert((MethodsToGen.ConfigBinder_Bind & overload) is 0);
- if ((MethodsToGen.ConfigBinder_Any ^ MethodsToGen.ConfigBinder_Bind & overload) is not 0)
+ if (((MethodsToGen.ConfigBinder_Any & ~MethodsToGen.ConfigBinder_Bind) & overload) is not 0)
{
RegisterInterceptor(ref _interceptors_configBinder);
}
diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs
index 1e245d516d2653..9dd14fd5dc2886 100644
--- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs
+++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Helpers.cs
@@ -99,6 +99,7 @@ private static async Task VerifyThatSourceIsGenerated(string testSourceCode)
Assert.NotNull(source);
Assert.Empty(result.Diagnostics);
Assert.True(source.Value.SourceText.Lines.Count > 10);
+ await VerifySuppressedCallsMatchInterceptedCalls(result);
}
private static bool s_initializedInterceptorVersion;
@@ -174,6 +175,8 @@ private static async Task VerifyAgainstBaselineUsingF
Assert.True(resultEqualsBaseline, errorMessage);
+ await VerifySuppressedCallsMatchInterceptedCalls(result);
+
return result;
}
diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs
index 3db19473fe2f48..d74ac253e616eb 100644
--- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs
+++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.cs
@@ -9,13 +9,18 @@
using System.Linq;
using System.Text;
using System.Text.Json;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
+using ILLink.RoslynAnalyzer;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Configuration.Binder.SourceGeneration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
+using SourceGenerators.Tests;
using Xunit;
namespace Microsoft.Extensions.SourceGeneration.Configuration.Binder.Tests
@@ -535,5 +540,245 @@ public static void Main()
Diagnostic diagnostic = Assert.Single(effective, d => d.Id == "SYSLIB1103");
Assert.False(diagnostic.IsSuppressed);
}
+
+ ///
+ /// Verifies that the suppressor suppresses IL2026/IL3050 when a ConfigurationBinder call
+ /// is passed directly as a method argument (e.g. Some.Method(config.Get<T>())).
+ /// Regression test for https://github.com/dotnet/runtime/issues/94544.
+ ///
+ [Fact]
+ public async Task Suppressor_SuppressesWarnings_WhenBindingCallIsMethodArgument()
+ {
+ string source = """
+ using Microsoft.Extensions.Configuration;
+
+ public class Program
+ {
+ public static void Main()
+ {
+ IConfigurationSection c = new ConfigurationBuilder().Build().GetSection("Options");
+ Some.Method(c.Get());
+ }
+ }
+
+ internal static class Some
+ {
+ public static void Method(MyOptions? options) { }
+ }
+
+ public class MyOptions
+ {
+ public int MaxRetries { get; set; }
+ }
+ """;
+
+ ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source);
+ Assert.NotNull(result.GeneratedSource);
+
+ await VerifySuppressedCallsMatchInterceptedCalls(result);
+ }
+
+ ///
+ /// Verifies that the suppressor also works for the straightforward assignment case,
+ /// ensuring no regression in existing behavior.
+ ///
+ [Fact]
+ public async Task Suppressor_SuppressesWarnings_ForSimpleBindingCall()
+ {
+ string source = """
+ using Microsoft.Extensions.Configuration;
+
+ public class Program
+ {
+ public static void Main()
+ {
+ IConfigurationSection c = new ConfigurationBuilder().Build().GetSection("Options");
+ var options = c.Get();
+ }
+ }
+
+ public class MyOptions
+ {
+ public int MaxRetries { get; set; }
+ }
+ """;
+
+ ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source);
+ Assert.NotNull(result.GeneratedSource);
+
+ await VerifySuppressedCallsMatchInterceptedCalls(result);
+ }
+
+ [Fact]
+ public async Task Suppressor_SuppressesWarnings_WithLineDirective()
+ {
+ string source = """
+ using Microsoft.Extensions.Configuration;
+
+ public class Program
+ {
+ public static void Main()
+ {
+ IConfigurationSection c = new ConfigurationBuilder().Build().GetSection("Options");
+ #line 100 "Remapped.cs"
+ var options = c.Get();
+ #line default
+ }
+ }
+
+ public class MyOptions
+ {
+ public int MaxRetries { get; set; }
+ }
+ """;
+
+ ConfigBindingGenRunResult result = await RunGeneratorAndUpdateCompilation(source);
+ Assert.NotNull(result.GeneratedSource);
+
+ await VerifySuppressedCallsMatchInterceptedCalls(result);
+ }
+
+ ///
+ /// Verifies that the set of IL2026/IL3050 diagnostics suppressed by the suppressor
+ /// matches exactly the set of calls intercepted by the source generator.
+ /// Catches both under-suppression (https://github.com/dotnet/runtime/issues/94544)
+ /// and over-suppression (https://github.com/dotnet/runtime/issues/96643).
+ ///
+ private static async Task VerifySuppressedCallsMatchInterceptedCalls(ConfigBindingGenRunResult result)
+ {
+ Assert.NotNull(result.GenerationSpec);
+
+ // Collect all intercepted (line, column) locations from the generator spec.
+ // The interceptor targets MemberAccessExpression.Name (e.g. "Get" in "c.Get()").
+ HashSet<(int Line, int Column)> interceptedLocations = GetInterceptedLocations(result.GenerationSpec);
+ Assert.NotEmpty(interceptedLocations);
+
+ // Run the ILLink analyzer + suppressor on the output compilation (which includes generated InterceptsLocation attributes).
+ ImmutableArray diagnostics = await GetDiagnosticsWithSuppressor(result.OutputCompilation);
+
+ // The ILLink analyzer must have produced at least one IL2026 or IL3050 that was suppressed.
+ // Without this, the assertions below would pass vacuously if the analyzer didn't fire.
+ Assert.Contains(diagnostics, d => (d.Id is "IL2026" or "IL3050") && d.IsSuppressed);
+
+ // Every suppressed IL2026/IL3050 diagnostic should be at an intercepted location.
+ foreach (Diagnostic d in diagnostics.Where(d => (d.Id is "IL2026" or "IL3050") && d.IsSuppressed))
+ {
+ (int line, int column) = GetMethodNameLocation(d);
+ Assert.True(interceptedLocations.Contains((line, column)),
+ $"Suppressed {d.Id} at ({line},{column}) but no interceptor was generated for that call site.");
+ }
+
+ // Every intercepted location should have its IL2026/IL3050 diagnostics suppressed.
+ foreach (Diagnostic d in diagnostics.Where(d => (d.Id is "IL2026" or "IL3050") && !d.IsSuppressed))
+ {
+ (int line, int column) = GetMethodNameLocation(d);
+ Assert.False(interceptedLocations.Contains((line, column)),
+ $"Unsuppressed {d.Id} at ({line},{column}) but an interceptor was generated for that call site.");
+ }
+ }
+
+ ///
+ /// Resolves a diagnostic's location to the method name position that the interceptor targets.
+ /// The ILLink analyzer reports on the MemberAccessExpression (e.g. "c.Get<T>"),
+ /// but the interceptor targets just the Name part (e.g. "Get"). This method walks from
+ /// the diagnostic location to the InvocationExpression's MemberAccessExpression.Name
+ /// to get the matching (line, column).
+ ///
+ private static (int Line, int Column) GetMethodNameLocation(Diagnostic diagnostic)
+ {
+ Location location = diagnostic.AdditionalLocations.Count > 0
+ ? diagnostic.AdditionalLocations[0]
+ : diagnostic.Location;
+ SyntaxTree sourceTree = location.SourceTree!;
+ SyntaxNode node = sourceTree.GetRoot().FindNode(location.SourceSpan, getInnermostNodeForTie: true);
+
+ InvocationExpressionSyntax invocation = (node as InvocationExpressionSyntax
+ ?? node.Parent as InvocationExpressionSyntax)!;
+
+ var memberAccess = (MemberAccessExpressionSyntax)invocation.Expression;
+ FileLinePositionSpan nameSpan = sourceTree.GetLineSpan(memberAccess.Name.Span);
+
+ return (nameSpan.StartLinePosition.Line + 1, nameSpan.StartLinePosition.Character + 1);
+ }
+
+ private static HashSet<(int Line, int Column)> GetInterceptedLocations(SourceGenerationSpec spec)
+ {
+ var locations = new HashSet<(int, int)>();
+ InterceptorInfo info = spec.InterceptorInfo;
+
+ AddLocations(info.ConfigBinder);
+ AddLocations(info.OptionsBuilderExt);
+ AddLocations(info.ServiceCollectionExt);
+ AddTypedLocations(info.ConfigBinder_Bind_instance);
+ AddTypedLocations(info.ConfigBinder_Bind_instance_BinderOptions);
+ AddTypedLocations(info.ConfigBinder_Bind_key_instance);
+
+ return locations;
+
+ void AddLocations(IEnumerable? locationInfos)
+ {
+ if (locationInfos is null)
+ return;
+
+ foreach (InvocationLocationInfo loc in locationInfos)
+ {
+ locations.Add(GetLocation(loc));
+ }
+ }
+
+ void AddTypedLocations(IEnumerable? typedInfos)
+ {
+ if (typedInfos is null)
+ return;
+
+ foreach (TypedInterceptorInvocationInfo typed in typedInfos)
+ {
+ AddLocations(typed.Locations);
+ }
+ }
+ }
+
+ private static (int Line, int Column) GetLocation(InvocationLocationInfo loc)
+ {
+ if (loc.LineNumber != 0)
+ {
+ return (loc.LineNumber, loc.CharacterNumber);
+ }
+
+ // v1 interceptor: parse from display location, e.g. "path(line,col)"
+ string display = loc.InterceptableLocationGetDisplayLocation();
+ Match match = Regex.Match(display, @"\((\d+),(\d+)\)$");
+ Assert.True(match.Success, $"Could not parse display location: {display}");
+
+ return (int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture),
+ int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture));
+ }
+
+ private static async Task> GetDiagnosticsWithSuppressor(Compilation compilation)
+ {
+ var analyzers = ImmutableArray.Create(
+ new DynamicallyAccessedMembersAnalyzer(),
+ new ConfigurationBindingGenerator.Suppressor());
+
+ var trimAotAnalyzerOptions = new DictionaryAnalyzerConfigOptions(
+ ImmutableDictionary.CreateRange(
+ StringComparer.OrdinalIgnoreCase,
+ [
+ new("build_property.EnableTrimAnalyzer", "true"),
+ new("build_property.EnableAotAnalyzer", "true"),
+ ]));
+ var analyzerOptions = new AnalyzerOptions(
+ ImmutableArray.Empty,
+ new GlobalOptionsOnlyProvider(trimAotAnalyzerOptions));
+ var options = new CompilationWithAnalyzersOptions(
+ analyzerOptions,
+ onAnalyzerException: null,
+ concurrentAnalysis: true,
+ logAnalyzerExecutionTime: false,
+ reportSuppressedDiagnostics: true);
+
+ return await new CompilationWithAnalyzers(compilation, analyzers, options)
+ .GetAllDiagnosticsAsync();
+ }
}
}
diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj
index ddc4d17d10f019..299c8afc31652e 100644
--- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj
+++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/Microsoft.Extensions.Configuration.Binder.SourceGeneration.Tests.csproj
@@ -1,4 +1,4 @@
-
+
$(NetCoreAppCurrent);$(NetFrameworkCurrent)
@@ -25,6 +25,7 @@
+
@@ -46,6 +47,7 @@
+
diff --git a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGenerator.Unit.Tests.csproj b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGenerator.Unit.Tests.csproj
index c612c394035dbd..9ce73c44b35c9e 100644
--- a/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGenerator.Unit.Tests.csproj
+++ b/src/libraries/System.Runtime.InteropServices/tests/ComInterfaceGenerator.Unit.Tests/ComInterfaceGenerator.Unit.Tests.csproj
@@ -11,7 +11,6 @@
-
diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/LibraryImportGenerator.Unit.Tests.csproj b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/LibraryImportGenerator.Unit.Tests.csproj
index 253e99c7fb0832..c86b834a9a2957 100644
--- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/LibraryImportGenerator.Unit.Tests.csproj
+++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.UnitTests/LibraryImportGenerator.Unit.Tests.csproj
@@ -15,7 +15,6 @@
-
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj
index 184616fc012c10..3f94c61f198d2f 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs
index 5ec67fd1f9742e..6e1d5b070749f9 100644
--- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs
+++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs
@@ -10,6 +10,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
+using SourceGenerators.Tests;
namespace ILLink.RoslynAnalyzer.Tests
{
@@ -87,7 +88,7 @@ public static (CompilationWithAnalyzers Compilation, SemanticModel SemanticModel
}));
var analyzerOptions = new AnalyzerOptions(
additionalFiles: additionalFiles?.ToImmutableArray() ?? ImmutableArray.Empty,
- new SimpleAnalyzerOptions(globalAnalyzerOptions));
+ new GlobalOptionsOnlyProvider(CreateGlobalOptions(globalAnalyzerOptions)));
var exceptionDiagnostics = new List();
@@ -103,40 +104,16 @@ public static (CompilationWithAnalyzers Compilation, SemanticModel SemanticModel
return (new CompilationWithAnalyzers(comp, SupportedDiagnosticAnalyzers, compWithAnalyzerOptions), comp.GetSemanticModel(src), exceptionDiagnostics);
}
- sealed class SimpleAnalyzerOptions : AnalyzerConfigOptionsProvider
+ private static DictionaryAnalyzerConfigOptions CreateGlobalOptions((string, string)[]? globalOptions)
{
- public SimpleAnalyzerOptions((string, string)[]? globalOptions)
+ if (globalOptions is null or { Length: 0 })
{
- globalOptions ??= Array.Empty<(string, string)>();
- GlobalOptions = new SimpleAnalyzerConfigOptions(ImmutableDictionary.CreateRange(
- StringComparer.OrdinalIgnoreCase,
- globalOptions.Select(x => new KeyValuePair(x.Item1, x.Item2))));
+ return DictionaryAnalyzerConfigOptions.Empty;
}
- public override AnalyzerConfigOptions GlobalOptions { get; }
-
- public override AnalyzerConfigOptions GetOptions(SyntaxTree tree)
- => SimpleAnalyzerConfigOptions.Empty;
-
- public override AnalyzerConfigOptions GetOptions(AdditionalText textFile)
- => SimpleAnalyzerConfigOptions.Empty;
-
- sealed class SimpleAnalyzerConfigOptions : AnalyzerConfigOptions
- {
- public static readonly SimpleAnalyzerConfigOptions Empty = new SimpleAnalyzerConfigOptions(ImmutableDictionary.Empty);
-
- private readonly ImmutableDictionary _dict;
- public SimpleAnalyzerConfigOptions(ImmutableDictionary dict)
- {
- _dict = dict;
- }
-
- // Suppress warning about missing nullable attributes
-#pragma warning disable 8765
- public override bool TryGetValue(string key, out string? value)
- => _dict.TryGetValue(key, out value);
-#pragma warning restore 8765
- }
+ return new DictionaryAnalyzerConfigOptions(ImmutableDictionary.CreateRange(
+ StringComparer.OrdinalIgnoreCase,
+ globalOptions.Select(x => new KeyValuePair(x.Item1, x.Item2))));
}
}
}