diff --git a/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs index 13e9c7437391d..b88bd91867200 100644 --- a/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/ContextGenerationSpec.cs @@ -5,6 +5,7 @@ using System.Text.Json.Serialization; using System.Text.Json.Reflection; using System.Diagnostics; +using Microsoft.CodeAnalysis; namespace System.Text.Json.SourceGeneration { @@ -15,6 +16,8 @@ namespace System.Text.Json.SourceGeneration [DebuggerDisplay("ContextTypeRef={ContextTypeRef}")] internal sealed class ContextGenerationSpec { + public Location Location { get; init; } + public JsonSourceGenerationOptionsAttribute GenerationOptions { get; init; } public Type ContextType { get; init; } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs index ec6124a8222be..087f3e2dc4095 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs @@ -274,8 +274,9 @@ private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec) break; case ClassType.TypeUnsupportedBySourceGen: { + Location location = typeGenerationSpec.Type.GetDiagnosticLocation() ?? typeGenerationSpec.AttributeLocation ?? _currentContext.Location; _sourceGenerationContext.ReportDiagnostic( - Diagnostic.Create(TypeNotSupported, Location.None, new string[] { typeGenerationSpec.TypeRef })); + Diagnostic.Create(TypeNotSupported, location, new string[] { typeGenerationSpec.TypeRef })); return; } default: @@ -293,7 +294,8 @@ private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec) } else { - _sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(DuplicateTypeName, Location.None, new string[] { typeGenerationSpec.TypeInfoPropertyName })); + Location location = typeGenerationSpec.AttributeLocation ?? _currentContext.Location; + _sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(DuplicateTypeName, location, new string[] { typeGenerationSpec.TypeInfoPropertyName })); } } diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 2785e66c7cd3a..fd190452e294f 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -116,6 +116,14 @@ private sealed class Parser private readonly HashSet _implicitlyRegisteredTypes = new(); + /// + /// A list of diagnostics emitted at the type level. This is cached and emitted between the processing of context types + /// in order to properly retrieve [JsonSerializable] attribute applications for top-level types (since a type might occur + /// both at top-level and in nested object graphs, with no guarantee of the order that we will see the type). + /// The element tuple types specifies the serializable type, the kind of diagnostic to emit, and the diagnostic message. + /// + private readonly List<(Type, DiagnosticDescriptor, string[])> _typeLevelDiagnostics = new(); + private JsonKnownNamingPolicy _currentContextNamingPolicy; private static DiagnosticDescriptor ContextClassesMustBePartial { get; } = new DiagnosticDescriptor( @@ -244,6 +252,11 @@ public Parser(Compilation compilation, in JsonSourceGenerationContext sourceGene foreach (ClassDeclarationSyntax classDeclarationSyntax in classDeclarationSyntaxList) { + // Ensure context-scoped metadata caches are empty. + Debug.Assert(_typeGenerationSpecCache.Count == 0); + Debug.Assert(_implicitlyRegisteredTypes.Count == 0); + Debug.Assert(_typeLevelDiagnostics.Count == 0); + CompilationUnitSyntax compilationUnitSyntax = classDeclarationSyntax.FirstAncestorOrSelf(); SemanticModel compilationSemanticModel = compilation.GetSemanticModel(compilationUnitSyntax.SyntaxTree); @@ -285,15 +298,18 @@ public Parser(Compilation compilation, in JsonSourceGenerationContext sourceGene INamedTypeSymbol contextTypeSymbol = (INamedTypeSymbol)compilationSemanticModel.GetDeclaredSymbol(classDeclarationSyntax); Debug.Assert(contextTypeSymbol != null); + Location contextLocation = contextTypeSymbol.Locations.Length > 0 ? contextTypeSymbol.Locations[0] : Location.None; + if (!TryGetClassDeclarationList(contextTypeSymbol, out List classDeclarationList)) { // Class or one of its containing types is not partial so we can't add to it. - _sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(ContextClassesMustBePartial, Location.None, new string[] { contextTypeSymbol.Name })); + _sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(ContextClassesMustBePartial, contextLocation, new string[] { contextTypeSymbol.Name })); continue; } ContextGenerationSpec contextGenSpec = new() { + Location = contextLocation, GenerationOptions = options ?? new JsonSourceGenerationOptionsAttribute(), ContextType = contextTypeSymbol.AsType(_metadataLoadContext), ContextClassDeclarationList = classDeclarationList @@ -316,14 +332,31 @@ public Parser(Compilation compilation, in JsonSourceGenerationContext sourceGene continue; } + // Emit type-level diagnostics + foreach ((Type Type, DiagnosticDescriptor Descriptor, string[] MessageArgs) diagnostic in _typeLevelDiagnostics) + { + Type type = diagnostic.Type; + Location location = type.GetDiagnosticLocation(); + + if (location == null) + { + TypeGenerationSpec spec = _typeGenerationSpecCache[type]; + location = spec.AttributeLocation; + } + + location ??= contextLocation; + _sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(diagnostic.Descriptor, location, diagnostic.MessageArgs)); + } + contextGenSpec.ImplicitlyRegisteredTypes.UnionWith(_implicitlyRegisteredTypes); contextGenSpecList ??= new List(); contextGenSpecList.Add(contextGenSpec); - // Clear the cache of generated metadata between the processing of context classes. + // Clear the caches of generated metadata between the processing of context classes. _typeGenerationSpecCache.Clear(); _implicitlyRegisteredTypes.Clear(); + _typeLevelDiagnostics.Clear(); } if (contextGenSpecList == null) @@ -487,6 +520,8 @@ private static bool TryGetClassDeclarationList(INamedTypeSymbol typeSymbol, [Not typeGenerationSpec.GenerationMode = generationMode; } + typeGenerationSpec.AttributeLocation = attributeSyntax.GetLocation(); + return typeGenerationSpec; } @@ -877,7 +912,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener if (!type.TryGetDeserializationConstructor(useDefaultCtorInAnnotatedStructs, out ConstructorInfo? constructor)) { classType = ClassType.TypeUnsupportedBySourceGen; - _sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(MultipleJsonConstructorAttribute, Location.None, new string[] { $"{type}" })); + _typeLevelDiagnostics.Add((type, MultipleJsonConstructorAttribute, new string[] { $"{type}" })); } else { @@ -944,7 +979,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener } spec = GetPropertyGenerationSpec(propertyInfo, isVirtual, generationMode); - CacheMemberHelper(); + CacheMemberHelper(propertyInfo.GetDiagnosticLocation()); } foreach (FieldInfo fieldInfo in currentType.GetFields(bindingFlags)) @@ -955,10 +990,10 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener } spec = GetPropertyGenerationSpec(fieldInfo, isVirtual: false, generationMode); - CacheMemberHelper(); + CacheMemberHelper(fieldInfo.GetDiagnosticLocation()); } - void CacheMemberHelper() + void CacheMemberHelper(Location memberLocation) { CacheMember(spec, ref propGenSpecList, ref ignoredMembers); @@ -972,13 +1007,13 @@ void CacheMemberHelper() { if (dataExtensionPropGenSpec != null) { - _sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(MultipleJsonExtensionDataAttribute, Location.None, new string[] { type.Name })); + _typeLevelDiagnostics.Add((type, MultipleJsonExtensionDataAttribute, new string[] { type.Name })); } Type propType = spec.TypeGenerationSpec.Type; if (!IsValidDataExtensionPropertyType(propType)) { - _sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(DataExtensionPropertyInvalid, Location.None, new string[] { type.Name, spec.ClrName })); + _sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(DataExtensionPropertyInvalid, memberLocation, new string[] { type.Name, spec.ClrName })); } dataExtensionPropGenSpec = GetOrAddTypeGenerationSpec(propType, generationMode); @@ -987,13 +1022,13 @@ void CacheMemberHelper() if (!hasInitOnlyProperties && spec.CanUseSetter && spec.IsInitOnlySetter) { - _sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(InitOnlyPropertyDeserializationNotSupported, Location.None, new string[] { type.Name })); + _sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(InitOnlyPropertyDeserializationNotSupported, memberLocation, new string[] { type.Name })); hasInitOnlyProperties = true; } if (spec.HasJsonInclude && (!spec.CanUseGetter || !spec.CanUseSetter || !spec.IsPublic)) { - _sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(InaccessibleJsonIncludePropertiesNotSupported, Location.None, new string[] { type.Name, spec.ClrName })); + _sourceGenerationContext.ReportDiagnostic(Diagnostic.Create(InaccessibleJsonIncludePropertiesNotSupported, memberLocation, new string[] { type.Name, spec.ClrName })); } } } diff --git a/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs index 66e00c0de20bb..d06c264ab0456 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/FieldInfoWrapper.cs @@ -100,5 +100,7 @@ public override void SetValue(object obj, object value, BindingFlags invokeAttr, { throw new NotImplementedException(); } + + public Location? Location => _field.Locations.Length > 0 ? _field.Locations[0] : null; } } diff --git a/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs index bfb593f992fbd..d7eea3323416b 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/PropertyInfoWrapper.cs @@ -92,5 +92,7 @@ public override void SetValue(object obj, object value, BindingFlags invokeAttr, { throw new NotSupportedException(); } + + public Location? Location => _property.Locations.Length > 0 ? _property.Locations[0] : null; } } diff --git a/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs index 101f3e48fbfb7..d0bc8e59b147c 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/ReflectionExtensions.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection; +using Microsoft.CodeAnalysis; namespace System.Text.Json.Reflection { @@ -45,5 +46,23 @@ private static bool HasJsonConstructorAttribute(ConstructorInfo constructorInfo) return false; } + + public static Location? GetDiagnosticLocation(this Type type) + { + Debug.Assert(type is TypeWrapper); + return ((TypeWrapper)type).Location; + } + + public static Location? GetDiagnosticLocation(this PropertyInfo propertyInfo) + { + Debug.Assert(propertyInfo is PropertyInfoWrapper); + return ((PropertyInfoWrapper)propertyInfo).Location; + } + + public static Location? GetDiagnosticLocation(this FieldInfo fieldInfo) + { + Debug.Assert(fieldInfo is FieldInfoWrapper); + return ((FieldInfoWrapper)fieldInfo).Location; + } } } diff --git a/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs index 5d4d778bc6348..2d06d9ec50cd9 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs @@ -600,5 +600,7 @@ public override bool Equals(Type o) } return base.Equals(o); } + + public Location? Location => _typeSymbol.Locations.Length > 0 ? _typeSymbol.Locations[0] : null; } } diff --git a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs index a85bdd05b914c..39fcd9827b38e 100644 --- a/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs +++ b/src/libraries/System.Text.Json/gen/TypeGenerationSpec.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Text.Json.Reflection; using System.Text.Json.Serialization; +using Microsoft.CodeAnalysis; namespace System.Text.Json.SourceGeneration { @@ -18,6 +19,11 @@ internal class TypeGenerationSpec /// public string TypeRef { get; private set; } + /// + /// If specified as a root type via JsonSerializableAttribute, specifies the location of the attribute application. + /// + public Location? AttributeLocation { get; set; } + /// /// The name of the public JsonTypeInfo<T> property for this type on the generated context class. /// For example, if the context class is named MyJsonContext, and the value of this property is JsonMessage; diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs index b84c518275ffc..8e3105e119238 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs @@ -336,22 +336,32 @@ public partial class MyJsonContext : JsonSerializerContext return CreateCompilation(source); } - internal static void CheckDiagnosticMessages(ImmutableArray diagnostics, DiagnosticSeverity level, string[] expectedMessages) + internal static void CheckDiagnosticMessages( + DiagnosticSeverity level, + ImmutableArray diagnostics, + (Location Location, string Message)[] expectedDiags, + bool sort = true) { - string[] actualMessages = diagnostics.Where(diagnostic => diagnostic.Severity == level).Select(diagnostic => diagnostic.GetMessage()).ToArray(); + (Location Location, string Message)[] actualDiags = diagnostics + .Where(diagnostic => diagnostic.Severity == level) + .Select(diagnostic => (diagnostic.Location, diagnostic.GetMessage())) + .ToArray(); - // Can't depend on reflection order when generating type metadata. - Array.Sort(actualMessages); - Array.Sort(expectedMessages); + if (sort) + { + // Can't depend on reflection order when generating type metadata. + Array.Sort(actualDiags); + Array.Sort(expectedDiags); + } if (CultureInfo.CurrentUICulture.Name.StartsWith("en", StringComparison.OrdinalIgnoreCase)) { - Assert.Equal(expectedMessages, actualMessages); + Assert.Equal(expectedDiags, actualDiags); } else { // for non-English runs, just compare the number of messages are the same - Assert.Equal(expectedMessages.Length, actualMessages.Length); + Assert.Equal(expectedDiags.Length, actualDiags.Length); } } } 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 a25196a34fe07..d3378b84f2f83 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 @@ -1,7 +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.Collections.Generic; +using System.Linq; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; using Xunit; namespace System.Text.Json.SourceGeneration.UnitTests @@ -9,7 +12,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests public class JsonSourceGeneratorDiagnosticsTests { [Fact] - [ActiveIssue("Figure out issue with CampaignSummaryViewModel namespace.")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/58226", TestPlatforms.Browser)] public void SuccessfulSourceGeneration() { // Compile the referenced assembly first. @@ -26,10 +29,13 @@ public void SuccessfulSourceGeneration() using System.Text.Json.Serialization; using ReferencedAssembly; - [assembly: JsonSerializable(typeof(JsonSourceGenerator.IndexViewModel)] - namespace JsonSourceGenerator { + [JsonSerializable(typeof(JsonSourceGenerator.IndexViewModel)] + public partial class JsonContext : JsonSerializerContext + { + } + public class IndexViewModel { public List ActiveOrUpcomingEvents { get; set; } @@ -50,78 +56,88 @@ public class IndexViewModel CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); - // Expected info logs. - string[] expectedInfoDiagnostics = new string[] { - "Generated serialization metadata for type System.Collections.Generic.List", - "Generated serialization metadata for type System.Int32", - "Generated serialization metadata for type System.String", - "Generated serialization metadata for type System.DateTimeOffset", - "Generated serialization metadata for type System.Boolean", - "Generated serialization metadata for type ReferencedAssembly.ActiveOrUpcomingEvent", - "Generated serialization metadata for type ReferencedAssembly.CampaignSummaryViewModel", - "Generated serialization metadata for type JsonSourceGenerator.IndexViewModel", - }; - - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, expectedInfoDiagnostics); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, new string[] { }); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, new string[] { }); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>()); } [Fact] - [ActiveIssue("Figure out issue with CampaignSummaryViewModel namespace.")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/58226", TestPlatforms.Browser)] public void UnsuccessfulSourceGeneration() { - // Compile the referenced assembly first. - Compilation campaignCompilation = CompilationHelper.CreateCampaignSummaryViewModelCompilation(); - Compilation eventCompilation = CompilationHelper.CreateActiveOrUpcomingEventCompilation(); + static void RunTest(bool explicitRef) + { + // Compile the referenced assembly first. + Compilation campaignCompilation = CompilationHelper.CreateCampaignSummaryViewModelCompilation(); + Compilation eventCompilation = CompilationHelper.CreateActiveOrUpcomingEventCompilation(); - // Emit the image of the referenced assembly. - byte[] campaignImage = CompilationHelper.CreateAssemblyImage(campaignCompilation); - byte[] eventImage = CompilationHelper.CreateAssemblyImage(eventCompilation); + // Emit the image of the referenced assembly. + byte[] campaignImage = CompilationHelper.CreateAssemblyImage(campaignCompilation); + byte[] eventImage = CompilationHelper.CreateAssemblyImage(eventCompilation); - // Main source for current compilation. - string source = @" + string optionalAttribute = explicitRef ? "[JsonSerializable(typeof(ActiveOrUpcomingEvent[,])]" : null; + + // Main source for current compilation. + string source = @$" using System.Collections.Generic; using System.Text.Json.Serialization; using ReferencedAssembly; - [assembly: JsonSerializable(typeof(JsonSourceGenerator.IndexViewModel)] - namespace JsonSourceGenerator - { + {{ + {optionalAttribute} + [JsonSerializable(typeof(JsonSourceGenerator.IndexViewModel)] + public partial class JsonContext : JsonSerializerContext + {{ + }} + public class IndexViewModel - { - public ISet ActiveOrUpcomingEvents { get; set; } - public CampaignSummaryViewModel FeaturedCampaign { get; set; } - public bool IsNewAccount { get; set; } + {{ + public ActiveOrUpcomingEvent[,] ActiveOrUpcomingEvents {{ get; set; }} + public CampaignSummaryViewModel FeaturedCampaign {{ get; set; }} + public bool IsNewAccount {{ get; set; }} public bool HasFeaturedCampaign => FeaturedCampaign != null; - } - }"; + }} + }}"; - MetadataReference[] additionalReferences = { + MetadataReference[] additionalReferences = { MetadataReference.CreateFromImage(campaignImage), MetadataReference.CreateFromImage(eventImage), }; - Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences); + Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences); - JsonSourceGenerator generator = new JsonSourceGenerator(); + JsonSourceGenerator generator = new JsonSourceGenerator(); + CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); - CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); + Location location; + if (explicitRef) + { + // Unsupported type is not in compiling assembly, but is indicated directly with [JsonSerializable], so location points to attribute application. + INamedTypeSymbol symbol = (INamedTypeSymbol)compilation.GetSymbolsWithName("JsonContext").FirstOrDefault(); + SyntaxReference syntaxReference = symbol.GetAttributes().First().ApplicationSyntaxReference; + TextSpan textSpan = syntaxReference.Span; + location = syntaxReference.SyntaxTree.GetLocation(textSpan)!; + } + else + { + // Unsupported type is not in compiling assembly, and isn't indicated directly with [JsonSerializable], so location points to context type. + location = compilation.GetSymbolsWithName("JsonContext").First().Locations[0]; + } - // Expected success info logs. - string[] expectedInfoDiagnostics = new string[] { - "Generated serialization metadata for type JsonSourceGeneration.IndexViewModel", - "Generated serialization metadata for type System.Boolean", - "Generated serialization metadata for type ReferencedAssembly.CampaignSummaryViewModel" - }; + // Expected warning logs. + (Location, string)[] expectedWarningDiagnostics = new (Location, string)[] + { + (location, "Did not generate serialization metadata for type 'global::ReferencedAssembly.ActiveOrUpcomingEvent[]'.") + }; - // Expected warning logs. - string[] expectedWarningDiagnostics = new string[] { "Did not generate serialization metadata for type System.Collections.Generic.ISet" }; + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, expectedWarningDiagnostics); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>()); + } - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, expectedInfoDiagnostics); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, expectedWarningDiagnostics); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, new string[] { }); + RunTest(explicitRef: true); + RunTest(false); } [Fact] @@ -133,20 +149,28 @@ public void NameClashSourceGeneration() JsonSourceGenerator generator = new JsonSourceGenerator(); CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); - string[] expectedWarningDiagnostics = new string[] { "There are multiple types named Location. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision." }; + INamedTypeSymbol symbol = (INamedTypeSymbol)compilation.GetSymbolsWithName("JsonContext").FirstOrDefault(); + SyntaxReference syntaxReference = new List(symbol.GetAttributes())[1].ApplicationSyntaxReference; + TextSpan textSpan = syntaxReference.Span; + Location location = syntaxReference.SyntaxTree.GetLocation(textSpan)!; + + (Location, string)[] expectedWarningDiagnostics = new (Location, string)[] + { + (location, "There are multiple types named Location. Source was generated for the first one detected. Use 'JsonSerializableAttribute.TypeInfoPropertyName' to resolve this collision.") + }; - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, expectedWarningDiagnostics); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, expectedWarningDiagnostics); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>()); // With resolution. compilation = CompilationHelper.CreateRepeatedLocationsWithResolutionCompilation(); generator = new JsonSourceGenerator(); CompilationHelper.RunGenerators(compilation, out generatorDiags, generator); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, Array.Empty()); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>()); } [Fact] @@ -156,41 +180,40 @@ public void ProgramsThatDontUseGeneratorCompile() // No STJ usage. string source = @"using System; -public class Program -{ - public static void Main() - { - Console.WriteLine(""Hello World""); - - } -} -"; + public class Program + { + public static void Main() + { + Console.WriteLine(""Hello World""); + } + } + "; Compilation compilation = CompilationHelper.CreateCompilation(source); JsonSourceGenerator generator = new JsonSourceGenerator(); CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, Array.Empty()); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>()); // With STJ usage. source = @"using System.Text.Json; -public class Program -{ - public static void Main() - { - JsonSerializer.Serialize(""Hello World""); - } -} -"; + public class Program + { + public static void Main() + { + JsonSerializer.Serialize(""Hello World""); + } + } + "; compilation = CompilationHelper.CreateCompilation(source); generator = new JsonSourceGenerator(); CompilationHelper.RunGenerators(compilation, out generatorDiags, generator); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, Array.Empty()); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>()); } [Fact] @@ -201,11 +224,16 @@ public void WarnOnClassesWithInitOnlyProperties() JsonSourceGenerator generator = new JsonSourceGenerator(); CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); - string[] expectedWarningDiagnostics = new string[] { "The type 'Location' defines init-only properties, deserialization of which is currently not supported in source generation mode." }; + Location location = compilation.GetSymbolsWithName("Id").First().Locations[0]; - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, expectedWarningDiagnostics); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + (Location, string)[] expectedWarningDiagnostics = new (Location, string)[] + { + (location, "The type 'Location' defines init-only properties, deserialization of which is currently not supported in source generation mode.") + }; + + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, expectedWarningDiagnostics); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>()); } [Fact] @@ -216,16 +244,20 @@ public void WarnOnClassesWithInaccessibleJsonIncludeProperties() JsonSourceGenerator generator = new JsonSourceGenerator(); CompilationHelper.RunGenerators(compilation, out var generatorDiags, generator); - string[] expectedWarningDiagnostics = new string[] + Location idLocation = compilation.GetSymbolsWithName("Id").First().Locations[0]; + Location address2Location = compilation.GetSymbolsWithName("Address2").First().Locations[0]; + Location countryLocation = compilation.GetSymbolsWithName("Country").First().Locations[0]; + + (Location, string)[] expectedWarningDiagnostics = new (Location, string)[] { - "The member 'Location.Id' has been annotated with the JsonIncludeAttribute but is not visible to the source generator.", - "The member 'Location.Address2' has been annotated with the JsonIncludeAttribute but is not visible to the source generator.", - "The member 'Location.Country' has been annotated with the JsonIncludeAttribute but is not visible to the source generator." + (idLocation, "The member 'Location.Id' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), + (address2Location, "The member 'Location.Address2' has been annotated with the JsonIncludeAttribute but is not visible to the source generator."), + (countryLocation, "The member 'Location.Country' has been annotated with the JsonIncludeAttribute but is not visible to the source generator.") }; - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, expectedWarningDiagnostics); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, expectedWarningDiagnostics, sort: false); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>()); } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs index 08519e16fde39..2cb749bb40bc6 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.IO; using System.Linq; using System.Reflection; using Microsoft.CodeAnalysis; @@ -70,7 +69,7 @@ public void UsePrivates() Assert.Equal("HelloWorld.MyType", myType.FullName); // Check for received fields, properties and methods in created type. - string[] expectedPropertyNames = { "PublicPropertyInt", "PublicPropertyString",}; + string[] expectedPropertyNames = { "PublicPropertyInt", "PublicPropertyString", }; string[] expectedFieldNames = { "PublicChar", "PublicDouble" }; string[] expectedMethodNames = { "get_PrivatePropertyInt", "get_PrivatePropertyString", "get_PublicPropertyInt", "get_PublicPropertyString", "MyMethod", "MySecondMethod", "set_PrivatePropertyInt", "set_PrivatePropertyString", "set_PublicPropertyInt", "set_PublicPropertyString", "UsePrivates" }; CheckFieldsPropertiesMethods(myType, expectedFieldNames, expectedPropertyNames, expectedMethodNames); @@ -245,7 +244,7 @@ public void UsePrivates() string[] expectedPropertyNamesNotMyType = { "Address1", "Address2", "City", "Country", "Id", "Name", "PhoneNumber", "PostalCode", "State" }; string[] expectedMethodNamesNotMyType = { "get_Address1", "get_Address2", "get_City", "get_Country", "get_Id", "get_Name", "get_PhoneNumber", "get_PostalCode", "get_State", "set_Address1", "set_Address2", "set_City", "set_Country", "set_Id", "set_Name", "set_PhoneNumber", "set_PostalCode", "set_State" }; - CheckFieldsPropertiesMethods(notMyType, expectedFieldNamesNotMyType, expectedPropertyNamesNotMyType, expectedMethodNamesNotMyType ); + CheckFieldsPropertiesMethods(notMyType, expectedFieldNamesNotMyType, expectedPropertyNamesNotMyType, expectedMethodNamesNotMyType); } [Theory] @@ -291,9 +290,9 @@ public JsonSerializableAttribute(Type type) { } Assert.Null(types); } - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, Array.Empty()); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>()); } [Theory] @@ -323,9 +322,9 @@ public JsonSerializableAttribute(string typeInfoPropertyName, Type type) { } CompilationHelper.RunGenerators(compilation, out ImmutableArray generatorDiags, generator); Assert.Null(generator.GetSerializableTypes()); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Info, Array.Empty()); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Warning, Array.Empty()); - CompilationHelper.CheckDiagnosticMessages(generatorDiags, DiagnosticSeverity.Error, Array.Empty()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, generatorDiags, Array.Empty<(Location, string)>()); + CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, generatorDiags, Array.Empty<(Location, string)>()); } [Fact]