diff --git a/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs b/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs index 74f44f99c62baa..e59ef1fe57f92b 100644 --- a/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs +++ b/src/libraries/Common/src/SourceGenerators/DiagnosticInfo.cs @@ -4,7 +4,9 @@ using System; using System.Linq; using System.Numerics.Hashing; +using System.Reflection; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; namespace SourceGenerators; @@ -14,6 +16,9 @@ namespace SourceGenerators; /// internal readonly struct DiagnosticInfo : IEquatable { + private static readonly Lazy s_dummySyntaxTree = new Lazy(GetDummySyntaxTree); + private static readonly Lazy s_sourceTreeBackingFieldInfo = new Lazy(GetSourceTreeBackingFieldInfo); + public DiagnosticDescriptor Descriptor { get; private init; } public object?[] MessageArgs { get; private init; } public Location? Location { get; private init; } @@ -30,8 +35,35 @@ public static DiagnosticInfo Create(DiagnosticDescriptor descriptor, Location? l }; // Creates a copy of the Location instance that does not capture a reference to Compilation. - static Location GetTrimmedLocation(Location location) - => Location.Create(location.SourceTree?.FilePath ?? "", location.SourceSpan, location.GetLineSpan().Span); + static Location? GetTrimmedLocation(Location? sourceLocation) + { + if (sourceLocation is null) + { + return null; + } + + Location trimmedLocation = Location.Create(sourceLocation.SourceTree?.FilePath ?? "", sourceLocation.SourceSpan, sourceLocation.GetLineSpan().Span); + + if (sourceLocation.IsInSource && + !trimmedLocation.IsInSource && + s_sourceTreeBackingFieldInfo.Value is FieldInfo sourceTreeField && + s_dummySyntaxTree.Value is CSharpSyntaxTree syntaxTree) + { + // Attempt to mark this as a source location, so that it is suppressible with #pragma. + try + { + sourceTreeField.SetValue(trimmedLocation, syntaxTree); + + if (!trimmedLocation.IsInSource) + { + sourceTreeField.SetValue(trimmedLocation, null); + } + } + catch { } + } + + return trimmedLocation; + } } public Diagnostic CreateDiagnostic() @@ -57,4 +89,34 @@ public override readonly int GetHashCode() hashCode = HashHelpers.Combine(hashCode, Location?.GetHashCode() ?? 0); return hashCode; } + + private static FieldInfo? GetSourceTreeBackingFieldInfo() + { + FieldInfo? info; + + try + { + FieldInfo[] fields = typeof(Location).GetFields(BindingFlags.NonPublic); + info = typeof(Location).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance); + } + catch + { + info = null; + } + + return info; + } + + private static CSharpSyntaxTree? GetDummySyntaxTree() + { + try + { + Type? dummySyntaxTree = typeof(CSharpSyntaxTree).Assembly.GetType("Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.DummySyntaxTree", throwOnError: false); + return dummySyntaxTree is null ? null : (CSharpSyntaxTree?)Activator.CreateInstance(dummySyntaxTree); + } + catch + { + return null; + } + } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs index e05a7737137128..db631234f95c40 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/SourceGenerationTests/GeneratorTests.Baselines.cs @@ -769,7 +769,7 @@ public class MyClassWithCustomCollections public ICustomDictionary ICustomDictionary { get; set; } public ICustomSet ICustomCollection { get; set; } public IReadOnlyList IReadOnlyList { get; set; } - // Diagnostic warning because we don't know how to instantiate the property type. + // Built in collection: diagnostic warning because the key isn't supported for dictionary binding (only string-parsable types are). public IReadOnlyDictionary UnsupportedIReadOnlyDictionaryUnsupported { get; set; } public IReadOnlyDictionary IReadOnlyDictionary { get; set; } } @@ -804,6 +804,60 @@ public interface ICustomSet : ISet Assert.Equal(3, diagnostics.Where(diag => diag.Id == Diagnostics.PropertyNotSupported.Id).Count()); } + [Fact] + public async Task DiagnosticsAreSuppressibleWithPragma() + { + string source = $$""" + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class Program + { + public static void Main() + { + ConfigurationBuilder configurationBuilder = new(); + IConfiguration config = configurationBuilder.Build(); + IConfigurationSection section = config.GetSection("MySection"); + + #pragma warning disable {{Diagnostics.TypeNotSupported.Id}} + #pragma warning disable {{Diagnostics.PropertyNotSupported.Id}} + section.Get(); + #pragma warning restore {{Diagnostics.TypeNotSupported.Id}} + #pragma warning restore {{Diagnostics.PropertyNotSupported.Id}} + } + + // Diagnostic warning because we don't know how to instantiate two properties on this type. + public class MyClassWithCustomCollections + { + public string MyString { get; set; } + public ClassWithoutCtor ClassWithoutCtor { get; set; } + public ICustomSet ICustomCollection { get; set; } + // Built in collection: diagnostic warning because the key isn't supported for dictionary binding (only string-parsable types are). + public IReadOnlyDictionary UnsupportedDictionary { get; set; } + } + + // Diagnostic warning because we don't know how to instantiate this type. + public interface ICustomSet : ISet + { + } + + public abstract class ClassWithoutCtor { } + + public interface InterfaceWithoutCtor { } + } + """; + + ConfigBindingGenRunResult result = await VerifyAgainstBaselineUsingFile( + "Collections.generated.txt", + source, + expectedDiags: ExpectedDiagnostics.FromGeneratorOnly); + + foreach (Diagnostic diagnostic in result.Diagnostics) + { + Assert.True(diagnostic.IsSuppressed); + } + } + [Fact] public async Task MinimalGenerationIfNoBindableMembers() { 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 848d93b32a475a..1285fa4bddf548 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 @@ -4,8 +4,6 @@ true $(NoWarn);SYSLIB1100,SYSLIB1101 - - $(NoWarn);SYSLIB1103,SYSLIB1104 $(Features);InterceptorsPreview $(InterceptorsPreviewNamespaces);Microsoft.Extensions.Configuration.Binder.SourceGeneration diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/Microsoft.Extensions.Logging.Console.csproj b/src/libraries/Microsoft.Extensions.Logging.Console/src/Microsoft.Extensions.Logging.Console.csproj index ee8bc96621436e..580592da4698a6 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/Microsoft.Extensions.Logging.Console.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/Microsoft.Extensions.Logging.Console.csproj @@ -11,8 +11,6 @@ $(InterceptorsPreviewNamespaces);Microsoft.Extensions.Configuration.Binder.SourceGeneration true - - $(NoWarn);SYSLIB1100;SYSLIB1101 Console logger provider implementation for Microsoft.Extensions.Logging. diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs index a554d2681d43d1..fc7ce47300c173 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs @@ -601,14 +601,20 @@ public partial class MyContext : JsonSerializerContext } [Fact] - public void JsonSerializableAttributeOnNonContextClass() + public void JsonSerializableAttributeOnNonContextClass() => JsonSerializableAttributeOnNonContextClassCore(suppressDiagnostics: false); + + private JsonSourceGeneratorResult JsonSerializableAttributeOnNonContextClassCore(bool suppressDiagnostics) { - Compilation compilation = CompilationHelper.CreateCompilation(""" + string? GetSuppressionString(string action) => suppressDiagnostics ? $"#pragma warning {action} SYSLIB1224" : null; + + Compilation compilation = CompilationHelper.CreateCompilation($$""" using System.Text.Json.Serialization; namespace Application { + {{GetSuppressionString("disable")}} [JsonSerializable(typeof(MyPoco))] + {{GetSuppressionString("restore")}} public partial class MyContext : IDisposable { public void Dispose() { } @@ -628,6 +634,25 @@ public class MyPoco { } }; CompilationHelper.AssertEqualDiagnosticMessages(expectedDiagnostics, result.Diagnostics); + + bool suppressionStateIsCorrect = suppressDiagnostics; + foreach (Diagnostic diagnostic in result.Diagnostics) + { + Assert.Equal(suppressionStateIsCorrect, diagnostic.IsSuppressed); + } + + return result; + } + + [Fact] + public void DiagnosticsAreSuppressibleWithPragma() + { + JsonSourceGeneratorResult result = JsonSerializableAttributeOnNonContextClassCore(suppressDiagnostics: true); + + foreach (Diagnostic diagnostic in result.Diagnostics) + { + Assert.True(diagnostic.IsSuppressed); + } } } }