System.Text.Json - Emit XML comments for public source-generated APIs (#72761)
authorJake Yallop <30874283+JakeYallop@users.noreply.github.com>
Tue, 26 Jul 2022 17:54:51 +0000 (18:54 +0100)
committerGitHub <noreply@github.com>
Tue, 26 Jul 2022 17:54:51 +0000 (18:54 +0100)
* Emit XML comments for public source-generated APIs

* Rename tildeIndex to backTickIndex for consistency

* Simplify XML comments

* Update JsonTypeInfo<T> XML comment

src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/CompilationHelper.cs
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorDiagnosticsTests.cs

index 379923d..b516bd1 100644 (file)
@@ -57,6 +57,7 @@ namespace System.Text.Json.SourceGeneration
             private const string JsonNamingPolicyTypeRef = "global::System.Text.Json.JsonNamingPolicy";
             private const string JsonSerializerTypeRef = "global::System.Text.Json.JsonSerializer";
             private const string JsonSerializerOptionsTypeRef = "global::System.Text.Json.JsonSerializerOptions";
+            private const string JsonSerializerContextTypeRef = "global::System.Text.Json.Serialization.JsonSerializerContext";
             private const string Utf8JsonWriterTypeRef = "global::System.Text.Json.Utf8JsonWriter";
             private const string JsonConverterTypeRef = "global::System.Text.Json.Serialization.JsonConverter";
             private const string JsonConverterFactoryTypeRef = "global::System.Text.Json.Serialization.JsonConverterFactory";
@@ -1071,6 +1072,9 @@ private void {serializeMethodName}({Utf8JsonWriterTypeRef} {WriterVarName}, {val
                 string typeInfoPropertyTypeRef = $"{JsonTypeInfoTypeRef}<{typeCompilableName}>";
 
                 return @$"private {typeInfoPropertyTypeRef}? _{typeFriendlyName};
+/// <summary>
+/// Defines the source generated JSON serialization contract metadata for a given type.
+/// </summary>
 public {typeInfoPropertyTypeRef} {typeFriendlyName}
 {{
     get => _{typeFriendlyName} ??= {typeMetadata.CreateTypeInfoMethodName}({OptionsInstanceVariableName});
@@ -1103,6 +1107,7 @@ private {typeInfoPropertyTypeRef} {typeMetadata.CreateTypeInfoMethodName}({JsonS
             {
                 string contextTypeRef = _currentContext.ContextTypeRef;
                 string contextTypeName = _currentContext.ContextType.Name;
+
                 int backTickIndex = contextTypeName.IndexOf('`');
                 if (backTickIndex != -1)
                 {
@@ -1114,14 +1119,23 @@ private {typeInfoPropertyTypeRef} {typeMetadata.CreateTypeInfoMethodName}({JsonS
                 sb.Append(@$"{GetLogicForDefaultSerializerOptionsInit()}
 
 private static {contextTypeRef}? {DefaultContextBackingStaticVarName};
+
+/// <summary>
+/// The default <see cref=""{JsonSerializerContextTypeRef}""/> associated with a default <see cref=""{JsonSerializerOptionsTypeRef}""/> instance.
+/// </summary>
 public static {contextTypeRef} Default => {DefaultContextBackingStaticVarName} ??= new {contextTypeRef}(new {JsonSerializerOptionsTypeRef}({DefaultOptionsStaticVarName}));
 
+/// <summary>
+/// The source-generated options associated with this context.
+/// </summary>
 protected override {JsonSerializerOptionsTypeRef}? GeneratedSerializerOptions {{ get; }} = {DefaultOptionsStaticVarName};
 
+/// <inheritdoc/>
 public {contextTypeName}() : base(null)
 {{
 }}
 
+/// <inheritdoc/>
 public {contextTypeName}({JsonSerializerOptionsTypeRef} {OptionsLocalVariableName}) : base({OptionsLocalVariableName})
 {{
 }}
@@ -1221,7 +1235,9 @@ private static {JsonConverterTypeRef} {GetConverterFromFactoryMethodName}({JsonS
             {
                 StringBuilder sb = new();
 
-                sb.Append(@$"public override {JsonTypeInfoTypeRef} GetTypeInfo({TypeTypeRef} type)
+                sb.Append(
+@$"/// <inheritdoc/>
+public override {JsonTypeInfoTypeRef} GetTypeInfo({TypeTypeRef} type)
 {{");
 
                 HashSet<TypeGenerationSpec> types = new(_currentContext.TypesWithMetadataGenerated);
index 678619c..61b07a4 100644 (file)
@@ -31,7 +31,8 @@ namespace System.Text.Json.SourceGeneration.UnitTests
             string source,
             MetadataReference[] additionalReferences = null,
             string assemblyName = "TestAssembly",
-            bool includeSTJ = true)
+            bool includeSTJ = true,
+            Func<CSharpParseOptions, CSharpParseOptions> configureParseOptions = null)
         {
 
             List<MetadataReference> references = new List<MetadataReference> {
@@ -63,9 +64,11 @@ namespace System.Text.Json.SourceGeneration.UnitTests
                 }
             }
 
+            configureParseOptions ??= (options) => options;
+            var parseOptions = configureParseOptions(s_parseOptions);
             return CSharpCompilation.Create(
                 assemblyName,
-                syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source, s_parseOptions) },
+                syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source, parseOptions) },
                 references: references.ToArray(),
                 options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
             );
@@ -278,7 +281,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests
 
             return CreateCompilation(source);
         }
-        
+
         public static Compilation CreateCompilationWithInitOnlyProperties()
         {
             string source = @"
@@ -335,7 +338,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests
 
             return CreateCompilation(source);
         }
-        
+
         public static Compilation CreateCompilationWithMixedInitOnlyProperties()
         {
             string source = @"
@@ -363,7 +366,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests
 
             return CreateCompilation(source);
         }
-        
+
         public static Compilation CreateCompilationWithRecordPositionalParameters()
         {
             string source = @"
@@ -393,7 +396,7 @@ namespace System.Text.Json.SourceGeneration.UnitTests
 
             return CreateCompilation(source);
         }
-        
+
         public static Compilation CreateCompilationWithInaccessibleJsonIncludeProperties()
         {
             string source = @"
@@ -451,9 +454,9 @@ namespace System.Text.Json.SourceGeneration.UnitTests
             return CreateCompilation(source);
         }
 
-            public static Compilation CreateReferencedSimpleLibRecordCompilation()
-            {
-                string source = @"
+        public static Compilation CreateReferencedSimpleLibRecordCompilation()
+        {
+            string source = @"
             using System.Text.Json.Serialization;
 
             namespace ReferencedAssembly
@@ -475,7 +478,32 @@ namespace System.Text.Json.SourceGeneration.UnitTests
             }
 ";
 
-                return CreateCompilation(source);
+            return CreateCompilation(source);
+        }
+
+        public static Compilation CreateReferencedModelWithFullyDocumentedProperties()
+        {
+            string source = @"
+            namespace ReferencedAssembly
+            {
+                /// <summary>
+                /// Documentation
+                /// </summary>
+                public class Model
+                {
+                    /// <summary>
+                    /// Documentation
+                    /// </summary>
+                    public int Property1 { get; set; }
+
+                    /// <summary>
+                    /// Documentation
+                    /// </summary>
+                    public int Property2 { get; set; }
+                }
+            }";
+
+            return CreateCompilation(source);
         }
 
         internal static void CheckDiagnosticMessages(
index 77f76fa..3469a1a 100644 (file)
@@ -2,8 +2,10 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
 using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
 using Microsoft.CodeAnalysis.Text;
 using Xunit;
 
@@ -11,6 +13,81 @@ namespace System.Text.Json.SourceGeneration.UnitTests
 {
     public class JsonSourceGeneratorDiagnosticsTests
     {
+        /// <summary>
+        /// https://github.com/dotnet/runtime/issues/61379
+        /// </summary>
+        [Fact]
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/58226", TestPlatforms.Browser)]
+        public void EmitsDocumentationOnPublicMembersAndDoesNotCauseCS1591()
+        {
+            // Compile the referenced assembly first.
+            Compilation documentedCompilation = CompilationHelper.CreateReferencedModelWithFullyDocumentedProperties();
+
+            // Emit the image of the referenced assembly.
+            byte[] documentedImage = CompilationHelper.CreateAssemblyImage(documentedCompilation);
+
+            // Main source for current compilation.
+            string source = @"
+            using System.Collections.Generic;
+            using System.Text.Json.Serialization;
+            using ReferencedAssembly;
+
+            namespace JsonSourceGenerator
+            {
+                /// <summary>
+                /// Documentation
+                /// </summary>
+                [JsonSerializable(typeof(DocumentedModel))]
+                [JsonSerializable(typeof(DocumentedModel2<string>))]
+                public partial class JsonContext : JsonSerializerContext
+                {
+                }
+
+                /// <summary>
+                /// Documentation
+                /// </summary>
+                public class DocumentedModel2<T>
+                {
+                    /// <summary>
+                    /// Documentation
+                    /// </summary>
+                    public List<Model> Models { get; set; }
+                    /// documentation
+                    public T Prop { get; set; }
+                }
+
+                /// <summary>
+                /// Documentation
+                /// </summary>
+                public class DocumentedModel
+                {
+                    /// <summary>
+                    /// Documentation
+                    /// </summary>
+                    public List<Model> Models { get; set; }
+                }
+            }";
+
+            MetadataReference[] additionalReferences = {
+                MetadataReference.CreateFromImage(documentedImage),
+            };
+
+            Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences, configureParseOptions: options => options.WithDocumentationMode(DocumentationMode.Diagnose));
+
+            JsonSourceGenerator generator = new JsonSourceGenerator();
+
+            compilation = CompilationHelper.RunGenerators(compilation, out var _, generator);
+
+            using var emitStream = new MemoryStream();
+            using var xmlStream = new MemoryStream();
+            var result = compilation.Emit(emitStream, xmlDocumentationStream: xmlStream);
+            var diagnostics = result.Diagnostics;
+
+            CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Info, diagnostics, Array.Empty<(Location, string)>());
+            CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Warning, diagnostics, Array.Empty<(Location, string)>());
+            CompilationHelper.CheckDiagnosticMessages(DiagnosticSeverity.Error, diagnostics, Array.Empty<(Location, string)>());
+        }
+
         [Fact]
         [ActiveIssue("https://github.com/dotnet/runtime/issues/58226", TestPlatforms.Browser)]
         public void SuccessfulSourceGeneration()