| File: JsonSourceGenerator.Roslyn4.0.cs | Web Access |
| Project: src\runtime\src\libraries\System.Text.Json\gen\System.Text.Json.SourceGeneration.Roslyn4.4.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.Immutable; using System.Globalization; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; #if !ROSLYN4_4_OR_GREATER using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; #endif namespace System.Text.Json.SourceGeneration { /// <summary> /// Generates source code to optimize serialization and deserialization with JsonSerializer. /// </summary> [Generator] public sealed partial class JsonSourceGenerator : IIncrementalGenerator { #if ROSLYN4_4_OR_GREATER public const string SourceGenerationSpecTrackingName = "SourceGenerationSpec"; #endif public void Initialize(IncrementalGeneratorInitializationContext context) { #if LAUNCH_DEBUGGER System.Diagnostics.Debugger.Launch(); #endif IncrementalValueProvider<KnownTypeSymbols> knownTypeSymbols = context.CompilationProvider .Select((compilation, _) => new KnownTypeSymbols(compilation)); IncrementalValuesProvider<(ContextGenerationSpec?, ImmutableArray<Diagnostic>)> contextGenerationSpecs = context.SyntaxProvider .ForAttributeWithMetadataName( #if !ROSLYN4_4_OR_GREATER context, #endif Parser.JsonSerializableAttributeFullName, (node, _) => node is ClassDeclarationSyntax, (context, _) => (ContextClass: (ClassDeclarationSyntax)context.TargetNode, context.SemanticModel)) .Combine(knownTypeSymbols) .Select(static (tuple, cancellationToken) => { // Ensure the source generator parses using invariant culture. // This prevents issues such as locale-specific negative signs (e.g., U+2212 in fi-FI) // from being written to generated source files. #pragma warning disable RS1035 // CultureInfo.CurrentCulture is banned in analyzers CultureInfo originalCulture = CultureInfo.CurrentCulture; CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; try { #pragma warning restore RS1035 Parser parser = new(tuple.Right); ContextGenerationSpec? contextGenerationSpec = parser.ParseContextGenerationSpec(tuple.Left.ContextClass, tuple.Left.SemanticModel, cancellationToken); ImmutableArray<Diagnostic> diagnostics = parser.Diagnostics.ToImmutableArray(); return (contextGenerationSpec, diagnostics); #pragma warning disable RS1035 } finally { CultureInfo.CurrentCulture = originalCulture; } #pragma warning restore RS1035 }) #if ROSLYN4_4_OR_GREATER .WithTrackingName(SourceGenerationSpecTrackingName) #endif ; // Project the combined pipeline result to just the equatable model, discarding diagnostics. // ContextGenerationSpec implements value equality, so Roslyn's Select operator will compare // successive model snapshots and only propagate changes downstream when the model structurally // differs. This ensures source generation is fully incremental: re-emitting code only when // the serialization spec actually changes, not on every keystroke or positional shift. IncrementalValuesProvider<ContextGenerationSpec?> sourceGenerationSpecs = contextGenerationSpecs.Select(static (t, _) => t.Item1); context.RegisterSourceOutput(sourceGenerationSpecs, EmitSource); // Project to just the diagnostics, discarding the model. ImmutableArray<Diagnostic> does not // implement value equality, so Roslyn's incremental pipeline uses reference equality for these // values — the callback fires on every compilation change. This is by design: diagnostic // emission is cheap, and we need fresh SourceLocation instances that are pragma-suppressible // (cf. https://github.com/dotnet/runtime/issues/92509). // No source code is generated from this pipeline — it exists solely to report diagnostics. IncrementalValuesProvider<ImmutableArray<Diagnostic>> diagnostics = contextGenerationSpecs.Select(static (t, _) => t.Item2); context.RegisterSourceOutput(diagnostics, EmitDiagnostics); } private void EmitSource(SourceProductionContext sourceProductionContext, ContextGenerationSpec? contextGenerationSpec) { if (contextGenerationSpec is null) { return; } OnSourceEmitting?.Invoke(ImmutableArray.Create(contextGenerationSpec)); // Ensure the source generator emits number literals using invariant culture. // This prevents issues such as locale-specific negative signs (e.g., U+2212 in fi-FI) // from being written to generated source files. #pragma warning disable RS1035 // CultureInfo.CurrentCulture is banned in analyzers CultureInfo originalCulture = CultureInfo.CurrentCulture; CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; try { Emitter emitter = new(sourceProductionContext); emitter.Emit(contextGenerationSpec); } finally { CultureInfo.CurrentCulture = originalCulture; } #pragma warning restore RS1035 } private static void EmitDiagnostics(SourceProductionContext context, ImmutableArray<Diagnostic> diagnostics) { foreach (Diagnostic diagnostic in diagnostics) { context.ReportDiagnostic(diagnostic); } } /// <summary> /// Instrumentation helper for unit tests. /// </summary> public Action<ImmutableArray<ContextGenerationSpec>>? OnSourceEmitting { get; init; } private partial class Emitter { private readonly SourceProductionContext _context; public Emitter(SourceProductionContext context) => _context = context; private partial void AddSource(string hintName, SourceText sourceText) => _context.AddSource(hintName, sourceText); } } }