File: Language\DefaultRazorCSharpLoweringPhase.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;
using System.Security.Cryptography;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
 
namespace Microsoft.AspNetCore.Razor.Language;
 
internal class DefaultRazorCSharpLoweringPhase : RazorEnginePhaseBase, IRazorCSharpLoweringPhase
{
    protected override RazorCodeDocument ExecuteCore(RazorCodeDocument codeDocument, CancellationToken cancellationToken)
    {
        var documentNode = codeDocument.GetDocumentNode();
        ThrowForMissingDocumentDependency(documentNode);
 
        var target = documentNode.Target;
        if (target == null)
        {
            var message = Resources.FormatDocumentMissingTarget(
                documentNode.DocumentKind,
                nameof(CodeTarget),
                nameof(DocumentIntermediateNode.Target));
            throw new InvalidOperationException(message);
        }
 
        var csharpDocument = WriteDocument(codeDocument, cancellationToken);
        return codeDocument.WithCSharpDocument(csharpDocument);
    }
 
    private static RazorCSharpDocument WriteDocument(RazorCodeDocument codeDocument, CancellationToken cancellationToken = default)
    {
        ArgHelper.ThrowIfNull(codeDocument);
 
        var documentNode = codeDocument.GetRequiredDocumentNode();
        var codeTarget = documentNode.Target;
 
        using var context = new CodeRenderingContext(
            codeTarget.CreateNodeWriter(),
            codeDocument.Source,
            documentNode,
            codeDocument.CodeGenerationOptions);
 
        context.SetVisitor(new Visitor(context, codeTarget, cancellationToken));
 
        context.Visitor.VisitDocument(documentNode);
 
        var text = context.CodeWriter.GetText();
 
        return new RazorCSharpDocument(
            codeDocument,
            text,
            context.GetDiagnostics(),
            context.GetSourceMappings(),
            context.GetLinePragmas());
    }
 
    private sealed class Visitor(
        CodeRenderingContext context,
        CodeTarget codeTarget,
        CancellationToken cancellationToken) : IntermediateNodeVisitor
    {
        private readonly CodeRenderingContext _context = context;
        private readonly CodeTarget _codeTarget = codeTarget;
        private readonly CancellationToken _cancellationToken = cancellationToken;
 
        private CodeWriter CodeWriter => _context.CodeWriter;
        private IntermediateNodeWriter NodeWriter => _context.NodeWriter;
        private RazorCodeGenerationOptions Options => _context.Options;
 
        public override void VisitDocument(DocumentIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            var writer = CodeWriter;
 
            if (!Options.SuppressChecksum)
            {
                // See http://msdn.microsoft.com/en-us/library/system.codedom.codechecksumpragma.checksumalgorithmid.aspx
                // And https://github.com/dotnet/roslyn/blob/614299ff83da9959fa07131c6d0ffbc58873b6ae/src/Compilers/Core/Portable/PEWriter/DebugSourceDocument.cs#L67
                //
                // We only support algorithms that the debugger understands, which is currently SHA1 and SHA256.
 
                string algorithmId;
                var algorithm = _context.SourceDocument.Text.ChecksumAlgorithm;
                if (algorithm == CodeAnalysis.Text.SourceHashAlgorithm.Sha256)
                {
                    algorithmId = "{8829d00f-11b8-4213-878b-770e8597ac16}";
                }
                else if (algorithm == CodeAnalysis.Text.SourceHashAlgorithm.Sha1)
                {
                    algorithmId = "{ff1816ec-aa5e-4d10-87f7-6f4963833460}";
                }
                else
                {
                    // CodeQL [SM02196] This is supported by the underlying Roslyn APIs and as consumers we must also support it.
                    var message = Resources.FormatUnsupportedChecksumAlgorithm(
                        algorithm,
                        $"{HashAlgorithmName.SHA1.Name} {HashAlgorithmName.SHA256.Name}",
                        $"{nameof(RazorCodeGenerationOptions)}.{nameof(RazorCodeGenerationOptions.SuppressChecksum)}",
                        bool.TrueString);
 
                    throw new InvalidOperationException(message);
                }
 
                var sourceDocument = _context.SourceDocument;
 
                var checksum = ChecksumUtilities.BytesToString(sourceDocument.Text.GetChecksum());
                var filePath = sourceDocument.FilePath.AssumeNotNull();
 
                if (checksum.Length > 0)
                {
                    writer.WriteLine($"#pragma checksum \"{filePath}\" \"{algorithmId}\" \"{checksum}\"");
                }
            }
 
            writer
                .WriteLine("// <auto-generated/>")
                .WriteLine("#pragma warning disable 1591");
 
            VisitDefault(node);
 
            writer.WriteLine("#pragma warning restore 1591");
        }
 
        public override void VisitUsingDirective(UsingDirectiveIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteUsingDirective(_context, node);
        }
 
        public override void VisitNamespaceDeclaration(NamespaceDeclarationIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            var writer = CodeWriter;
 
            using (writer.BuildNamespace(node.Name, node.Source, _context))
            {
                var hasUsingDirectives = false;
 
                foreach (var child in node.Children)
                {
                    if (child is UsingDirectiveIntermediateNode)
                    {
                        hasUsingDirectives = true;
                        break;
                    }
                }
 
                if (hasUsingDirectives)
                {
                    // Tooling needs at least one line directive before using directives, otherwise Roslyn will
                    // not offer to create a new one. The last using in the group will output a hidden line
                    // directive after itself.
                    writer.WriteLine("#line default");
                }
                else
                {
                    // If there are no using directives, we output the hidden directive here.
                    writer.WriteLine("#line hidden");
                }
 
                VisitDefault(node);
            }
        }
 
        public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            using (CodeWriter.BuildClassDeclaration(
                node.Modifiers,
                node.Name,
                node.BaseType,
                node.Interfaces,
                node.TypeParameters,
                _context,
                useNullableContext: !Options.SuppressNullabilityEnforcement && node.NullableContext))
            {
                VisitDefault(node);
            }
        }
 
        public override void VisitMethodDeclaration(MethodDeclarationIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            var writer = CodeWriter;
 
            writer.WriteLine("#pragma warning disable 1998");
 
            using (CodeWriter.BuildMethodDeclaration(
                node.Modifiers,
                node.ReturnType,
                node.Name,
                node.Parameters))
            {
                VisitDefault(node);
            }
 
            writer.WriteLine("#pragma warning restore 1998");
        }
 
        public override void VisitFieldDeclaration(FieldDeclarationIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            CodeWriter.WriteField(node.SuppressWarnings, node.Modifiers, node.Type, node.Name);
        }
 
        public override void VisitPropertyDeclaration(PropertyDeclarationIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            CodeWriter.WritePropertyDeclaration(node.Modifiers, node.Type, node.Name, node.ExpressionBody, _context);
        }
 
        public override void VisitExtension(ExtensionIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            node.WriteNode(_codeTarget, _context);
        }
 
        public override void VisitCSharpExpression(CSharpExpressionIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteCSharpExpression(_context, node);
        }
 
        public override void VisitCSharpCode(CSharpCodeIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteCSharpCode(_context, node);
        }
 
        public override void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteHtmlAttribute(_context, node);
        }
 
        public override void VisitHtmlAttributeValue(HtmlAttributeValueIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteHtmlAttributeValue(_context, node);
        }
 
        public override void VisitCSharpExpressionAttributeValue(CSharpExpressionAttributeValueIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteCSharpExpressionAttributeValue(_context, node);
        }
 
        public override void VisitCSharpCodeAttributeValue(CSharpCodeAttributeValueIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteCSharpCodeAttributeValue(_context, node);
        }
 
        public override void VisitHtml(HtmlContentIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteHtmlContent(_context, node);
        }
 
        public override void VisitTagHelper(TagHelperIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            VisitDefault(node);
        }
 
        public override void VisitComponent(ComponentIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteComponent(_context, node);
        }
 
        public override void VisitComponentAttribute(ComponentAttributeIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteComponentAttribute(_context, node);
        }
 
        public override void VisitComponentChildContent(ComponentChildContentIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteComponentChildContent(_context, node);
        }
 
        public override void VisitComponentTypeArgument(ComponentTypeArgumentIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteComponentTypeArgument(_context, node);
        }
 
        public override void VisitComponentTypeInferenceMethod(ComponentTypeInferenceMethodIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteComponentTypeInferenceMethod(_context, node);
        }
 
        public override void VisitMarkupElement(MarkupElementIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteMarkupElement(_context, node);
        }
 
        public override void VisitMarkupBlock(MarkupBlockIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteMarkupBlock(_context, node);
        }
 
        public override void VisitReferenceCapture(ReferenceCaptureIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteReferenceCapture(_context, node);
        }
 
        public override void VisitSetKey(SetKeyIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteSetKey(_context, node);
        }
 
        public override void VisitSplat(SplatIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteSplat(_context, node);
        }
 
        public override void VisitRenderMode(RenderModeIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteRenderMode(_context, node);
        }
 
        public override void VisitFormName(FormNameIntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            NodeWriter.WriteFormName(_context, node);
        }
 
        public override void VisitDefault(IntermediateNode node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            _context.RenderChildren(node);
        }
    }
}