File: Language\DefaultDirectiveSyntaxTreePass.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.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using Microsoft.AspNetCore.Razor.Language.Extensions;
using Microsoft.AspNetCore.Razor.Language.Syntax;
 
namespace Microsoft.AspNetCore.Razor.Language;
 
internal class DefaultDirectiveSyntaxTreePass : RazorEngineFeatureBase, IRazorSyntaxTreePass
{
    public int Order => 75;
 
    public RazorSyntaxTree Execute(
        RazorCodeDocument codeDocument,
        RazorSyntaxTree syntaxTree,
        CancellationToken cancellationToken = default)
    {
        if (codeDocument.FileKind.IsComponent())
        {
            // Nothing to do here.
            return syntaxTree;
        }
 
        var sectionVerifier = new NestedSectionVerifier(syntaxTree, cancellationToken);
        return sectionVerifier.Verify();
    }
 
    private sealed class NestedSectionVerifier(RazorSyntaxTree syntaxTree, CancellationToken cancellationToken) : SyntaxRewriter
    {
        private readonly RazorSyntaxTree _syntaxTree = syntaxTree;
        private readonly CancellationToken _cancellationToken = cancellationToken;
 
        private ImmutableArray<RazorDiagnostic>.Builder? _diagnostics;
        private int _nestedLevel;
 
        public RazorSyntaxTree Verify()
        {
            var root = Visit(_syntaxTree.Root);
            var diagnostics = _diagnostics?.ToImmutableAndClear() ?? _syntaxTree.Diagnostics;
 
            return new RazorSyntaxTree(root, _syntaxTree.Source, diagnostics, _syntaxTree.Options);
        }
 
        [return: NotNullIfNotNull(nameof(node))]
        public override SyntaxNode? Visit(SyntaxNode? node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            try
            {
                return base.Visit(node);
            }
            catch (InsufficientExecutionStackException)
            {
                // We're very close to reaching the stack limit. Let's not go any deeper.
                // It's okay to not show nested section errors in deeply nested cases instead of crashing.
                _diagnostics ??= _syntaxTree.Diagnostics.ToBuilder();
                _diagnostics.Add(RazorDiagnosticFactory.CreateRewriter_InsufficientStack());
 
                return node;
            }
        }
 
        public override SyntaxNode VisitRazorDirective(RazorDirectiveSyntax node)
        {
            _cancellationToken.ThrowIfCancellationRequested();
 
            if (!node.IsDirective(SectionDirective.Directive))
            {
                // We only want to track the nesting of section directives.
                return base.VisitRazorDirective(node);
            }
 
            _nestedLevel++;
            var result = (RazorDirectiveSyntax)base.VisitRazorDirective(node);
 
            if (_nestedLevel > 1)
            {
                var directiveStart = node.Transition.GetSourceLocation(_syntaxTree.Source);
                var errorLength = /* @ */ 1 + SectionDirective.Directive.Directive.Length;
                var error = RazorDiagnosticFactory.CreateParsing_SectionsCannotBeNested(new SourceSpan(directiveStart, errorLength));
                result = result.AppendDiagnostic(error);
            }
 
            _nestedLevel--;
 
            return result;
        }
    }
}