File: Language\Components\ComponentRenderModeDirectivePass.cs
Web Access
Project: src\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor.Compiler\src\Microsoft.CodeAnalysis.Razor.Compiler.csproj (Microsoft.CodeAnalysis.Razor.Compiler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
 
namespace Microsoft.AspNetCore.Razor.Language.Components;
 
internal sealed class ComponentRenderModeDirectivePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
{
    private const string GeneratedRenderModeAttributeName = "__PrivateComponentRenderModeAttribute";
 
    protected override void ExecuteCore(
        RazorCodeDocument codeDocument,
        DocumentIntermediateNode documentNode,
        CancellationToken cancellationToken)
    {
        var @namespace = documentNode.FindPrimaryNamespace();
        var @class = documentNode.FindPrimaryClass();
        if (@namespace == null || @class == null)
        {
            return;
        }
 
        var directives = documentNode.FindDirectiveReferences(ComponentRenderModeDirective.Directive);
        if (directives.Length == 0)
        {
            return;
        }
 
        // We don't need to worry about duplicate attributes as we have already replaced any multiples with MalformedDirective
        Debug.Assert(directives.Length == 1);
 
        var child = directives[0].Node.Children.FirstOrDefault();
        if (child == null)
        {
            return;
        }
 
        // If the user is Razor 10 or higher, C# 11 or higher, and has a generic compoment, then we can use a file-scoped class for the generated attribute
        // so everything compiles correctly.
        var useFileScopedClass = codeDocument.ParserOptions.CSharpParseOptions.LanguageVersion >= CodeAnalysis.CSharp.LanguageVersion.CSharp11 &&
            codeDocument.ParserOptions.LanguageVersion >= RazorLanguageVersion.Version_11_0 &&
            @class.TypeParameters.Length > 0;
 
        // generate the inner attribute class
        var classDecl = new ClassDeclarationIntermediateNode
        {
            Name = GeneratedRenderModeAttributeName,
            BaseType = new BaseTypeWithModel($"global::{ComponentsApi.RenderModeAttribute.FullTypeName}"),
            Modifiers = useFileScopedClass
                ? CommonModifiers.FileSealed
                : CommonModifiers.PrivateSealed
        };
 
        classDecl.Children.Add(new CSharpCodeIntermediateNode()
        {
            Children =
            {
                IntermediateNodeFactory.CSharpToken($"private static global::{ComponentsApi.IComponentRenderMode.FullTypeName} ModeImpl => "),
                new CSharpCodeIntermediateNode()
                {
                    Source = child.Source,
                    Children =
                    {
                         child is not DirectiveTokenIntermediateNode directiveToken
                             ? child
                             : IntermediateNodeFactory.CSharpToken(
                                 content: directiveToken.Content,
                                 // To avoid breaking hot reload, we don't map the content back to the source unless we're on Razor 11 or higher
                                 source: codeDocument.ParserOptions.LanguageVersion >= RazorLanguageVersion.Version_11_0
                                    ? directiveToken.Source
                                    : null)
                    }
                },
                IntermediateNodeFactory.CSharpToken(";")
            }
        });
 
        classDecl.Children.Add(new CSharpCodeIntermediateNode()
        {
            Children =
            {
                IntermediateNodeFactory.CSharpToken($"public override global::{ComponentsApi.IComponentRenderMode.FullTypeName} Mode => ModeImpl;")
            }
        });
 
        if (useFileScopedClass)
        {
            @namespace.Children.Add(classDecl);
        }
        else
        {
            @class.Children.Add(classDecl);
        }
 
        // generate the attribute usage on top of the class
        var attributeNode = new CSharpCodeIntermediateNode();
        var namespaceSeparator = string.IsNullOrEmpty(@namespace.Name) ? string.Empty : ".";
        var attributeContents = useFileScopedClass
            ? GeneratedRenderModeAttributeName
            : $"global::{@namespace.Name}{namespaceSeparator}{@class.Name}.{GeneratedRenderModeAttributeName}";
        attributeNode.Children.Add(
            IntermediateNodeFactory.CSharpToken($"[{attributeContents}]"));
 
        // Insert the new attribute on top of the class
        var childCount = @namespace.Children.Count;
        for (var i = 0; i < childCount; i++)
        {
            if (object.ReferenceEquals(@namespace.Children[i], @class))
            {
                @namespace.Children.Insert(i, attributeNode);
                break;
            }
        }
 
        Debug.Assert(@namespace.Children.Count == childCount + 1);
    }
}