| File: Language\Components\ComponentRenderModeDirectivePass.cs | Web Access |
| Project: src\roslyn\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); } }