110 lines
4.4 KiB
C#
110 lines
4.4 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using Microsoft.AspNetCore.Blazor.Shared;
|
|
using Microsoft.AspNetCore.Razor.Language;
|
|
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Microsoft.AspNetCore.Blazor.Razor
|
|
{
|
|
/// <summary>
|
|
/// Keeps track of the nesting of elements/containers while writing out the C# source code
|
|
/// for a component. This allows us to detect mismatched start/end tags, as well as inject
|
|
/// additional C# source to capture component descendants in a lambda.
|
|
/// </summary>
|
|
internal class ScopeStack
|
|
{
|
|
private readonly Stack<ScopeEntry> _stack = new Stack<ScopeEntry>();
|
|
private int _builderVarNumber = 1;
|
|
|
|
public string BuilderVarName { get; private set; } = "builder";
|
|
|
|
public void OpenScope(string tagName, bool isComponent)
|
|
{
|
|
_stack.Push(new ScopeEntry(tagName, isComponent));
|
|
}
|
|
|
|
public void CloseScope(CodeRenderingContext context, string tagName, bool isComponent, SourceSpan? source)
|
|
{
|
|
if (_stack.Count == 0)
|
|
{
|
|
var diagnostic = BlazorDiagnosticFactory.Create_UnexpectedClosingTag(source ?? SourceSpan.Undefined, tagName);
|
|
throw new RazorCompilerException(diagnostic);
|
|
}
|
|
|
|
var currentScope = _stack.Pop();
|
|
if (!tagName.Equals(currentScope.TagName, StringComparison.Ordinal))
|
|
{
|
|
var diagnostic = BlazorDiagnosticFactory.Create_MismatchedClosingTag(source, currentScope.TagName, tagName);
|
|
throw new RazorCompilerException(diagnostic);
|
|
}
|
|
|
|
// Note: there's no unit test to cover the following, because there's no known way of
|
|
// triggering it from user code (i.e., Razor source code). But the check is here anyway
|
|
// just in case one day it turns out there is some way of causing this error.
|
|
if (isComponent != currentScope.IsComponent)
|
|
{
|
|
var kind = isComponent ? "component" : "element";
|
|
var expectedKind = currentScope.IsComponent ? "component" : "element";
|
|
var diagnostic = BlazorDiagnosticFactory.Create_MismatchedClosingTagKind(source, tagName, kind, expectedKind);
|
|
throw new RazorCompilerException(diagnostic);
|
|
}
|
|
|
|
// When closing the scope for a component with children, it's time to close the lambda
|
|
if (currentScope.LambdaScope != null)
|
|
{
|
|
currentScope.LambdaScope.Dispose();
|
|
context.CodeWriter.Write(")");
|
|
context.CodeWriter.WriteEndMethodInvocation();
|
|
OffsetBuilderVarNumber(-1);
|
|
}
|
|
}
|
|
|
|
public void IncrementCurrentScopeChildCount(CodeRenderingContext context)
|
|
{
|
|
if (_stack.Count > 0)
|
|
{
|
|
var currentScope = _stack.Peek();
|
|
|
|
if (currentScope.IsComponent && currentScope.ChildCount == 0)
|
|
{
|
|
// When we're about to insert the first child into a component,
|
|
// it's time to open a new lambda
|
|
var blazorNodeWriter = (BlazorNodeWriter)context.NodeWriter;
|
|
blazorNodeWriter.BeginWriteAttribute(context.CodeWriter, BlazorApi.RenderTreeBuilder.ChildContent);
|
|
OffsetBuilderVarNumber(1);
|
|
context.CodeWriter.Write($"({BlazorApi.RenderFragment.FullTypeName})(");
|
|
currentScope.LambdaScope = context.CodeWriter.BuildLambda(BuilderVarName);
|
|
}
|
|
|
|
currentScope.ChildCount++;
|
|
}
|
|
}
|
|
|
|
private void OffsetBuilderVarNumber(int delta)
|
|
{
|
|
_builderVarNumber += delta;
|
|
BuilderVarName = _builderVarNumber == 1
|
|
? "builder"
|
|
: $"builder{_builderVarNumber}";
|
|
}
|
|
|
|
private class ScopeEntry
|
|
{
|
|
public readonly string TagName;
|
|
public readonly bool IsComponent;
|
|
public int ChildCount;
|
|
public IDisposable LambdaScope;
|
|
|
|
public ScopeEntry(string tagName, bool isComponent)
|
|
{
|
|
TagName = tagName;
|
|
IsComponent = isComponent;
|
|
ChildCount = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|