File: JsonSourceGenerator.Roslyn3.11.cs
Web Access
Project: src\src\libraries\System.Text.Json\gen\System.Text.Json.SourceGeneration.Roslyn3.11.csproj (System.Text.Json.SourceGeneration)
// 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.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using SourceGenerators;
 
#pragma warning disable RS1035 // IIncrementalGenerator isn't available for the target configuration
 
namespace System.Text.Json.SourceGeneration
{
    /// <summary>
    /// Generates source code to optimize serialization and deserialization with JsonSerializer.
    /// </summary>
    [Generator]
    public sealed partial class JsonSourceGenerator : ISourceGenerator
    {
        /// <summary>
        /// Registers a syntax resolver to receive compilation units.
        /// </summary>
        /// <param name="context"></param>
        public void Initialize(GeneratorInitializationContext context)
        {
#if LAUNCH_DEBUGGER
            System.Diagnostics.Debugger.Launch();
#endif
 
            // Unfortunately, there is no cancellation token that can be passed here
            // (the one in GeneratorInitializationContext is not safe to capture).
            // In practice this should still be ok as the generator driver itself will
            // cancel after every file it processes.
            context.RegisterForSyntaxNotifications(static () => new SyntaxContextReceiver(CancellationToken.None));
        }
 
        /// <summary>
        /// Generates source code to optimize serialization and deserialization with JsonSerializer.
        /// </summary>
        /// <param name="executionContext"></param>
        public void Execute(GeneratorExecutionContext executionContext)
        {
            if (executionContext.SyntaxContextReceiver is not SyntaxContextReceiver receiver || receiver.ContextClassDeclarations == null)
            {
                // nothing to do yet
                return;
            }
 
            // Stage 1. Parse the identified JsonSerializerContext classes and store the model types.
            KnownTypeSymbols knownSymbols = new(executionContext.Compilation);
            Parser parser = new(knownSymbols);
 
            List<ContextGenerationSpec>? contextGenerationSpecs = null;
            foreach ((ClassDeclarationSyntax? contextClassDeclaration, SemanticModel semanticModel) in receiver.ContextClassDeclarations)
            {
                ContextGenerationSpec? contextGenerationSpec = parser.ParseContextGenerationSpec(contextClassDeclaration, semanticModel, executionContext.CancellationToken);
                if (contextGenerationSpec is null)
                {
                    continue;
                }
 
                (contextGenerationSpecs ??= new()).Add(contextGenerationSpec);
            }
 
            // Stage 2. Report any diagnostics gathered by the parser.
            foreach (DiagnosticInfo diagnosticInfo in parser.Diagnostics)
            {
                executionContext.ReportDiagnostic(diagnosticInfo.CreateDiagnostic());
            }
 
            if (contextGenerationSpecs is null)
            {
                return;
            }
 
            // Stage 3. Emit source code from the spec models.
            OnSourceEmitting?.Invoke(contextGenerationSpecs.ToImmutableArray());
            Emitter emitter = new(executionContext);
            foreach (ContextGenerationSpec contextGenerationSpec in contextGenerationSpecs)
            {
                emitter.Emit(contextGenerationSpec);
            }
        }
 
        private sealed class SyntaxContextReceiver : ISyntaxContextReceiver
        {
            private readonly CancellationToken _cancellationToken;
 
            public SyntaxContextReceiver(CancellationToken cancellationToken)
            {
                _cancellationToken = cancellationToken;
            }
 
            public List<(ClassDeclarationSyntax, SemanticModel)>? ContextClassDeclarations { get; private set; }
 
            public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
            {
                if (IsSyntaxTargetForGeneration(context.Node))
                {
                    ClassDeclarationSyntax? classSyntax = GetSemanticTargetForGeneration(context, _cancellationToken);
                    if (classSyntax != null)
                    {
                        (ContextClassDeclarations ??= new()).Add((classSyntax, context.SemanticModel));
                    }
                }
            }
 
            private static bool IsSyntaxTargetForGeneration(SyntaxNode node) => node is ClassDeclarationSyntax { AttributeLists.Count: > 0, BaseList.Types.Count: > 0 };
 
            private static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context, CancellationToken cancellationToken)
            {
                var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
 
                foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists)
                {
                    foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes)
                    {
                        cancellationToken.ThrowIfCancellationRequested();
 
                        IMethodSymbol? attributeSymbol = context.SemanticModel.GetSymbolInfo(attributeSyntax, cancellationToken).Symbol as IMethodSymbol;
                        if (attributeSymbol == null)
                        {
                            continue;
                        }
 
                        INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType;
                        string fullName = attributeContainingTypeSymbol.ToDisplayString();
 
                        if (fullName == Parser.JsonSerializableAttributeFullName)
                        {
                            return classDeclarationSyntax;
                        }
                    }
                }
 
                return null;
            }
        }
 
        /// <summary>
        /// Instrumentation helper for unit tests.
        /// </summary>
        public Action<ImmutableArray<ContextGenerationSpec>>? OnSourceEmitting { get; init; }
 
        private partial class Emitter
        {
            private readonly GeneratorExecutionContext _context;
 
            public Emitter(GeneratorExecutionContext context)
                => _context = context;
 
            private partial void AddSource(string hintName, SourceText sourceText)
                => _context.AddSource(hintName, sourceText);
        }
    }
}