// 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
{
///
/// 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.
///
internal class ScopeStack
{
private readonly Stack _stack = new Stack();
private int _builderVarNumber = 1;
public string BuilderVarName { get; private set; } = "builder";
public void OpenElementScope(string tagName)
{
_stack.Push(new ScopeEntry(tagName, ScopeKind.Element));
}
public void OpenComponentScope(CodeRenderingContext context, string name, string type, string parameterName)
{
var scope = new ScopeEntry(name, ScopeKind.Component);
_stack.Push(scope);
var blazorNodeWriter = (BlazorNodeWriter)context.NodeWriter;
blazorNodeWriter.BeginWriteAttribute(context.CodeWriter, name);
OffsetBuilderVarNumber(1);
// Writes code that looks like:
//
// builder.AddAttribute(0, "{name}", ({type})((__builder) => { ... }));
// OR
// builder.AddAttribute(0, "{name}", ({type})((context) => (__builder) => { ... }));
context.CodeWriter.Write($"({type})(");
if (parameterName != null)
{
context.CodeWriter.Write($"({parameterName}) => ");
}
scope.LambdaScope = context.CodeWriter.BuildLambda(BuilderVarName);
}
public void OpenTemplateScope(CodeRenderingContext context)
{
var currentScope = new ScopeEntry("__template", ScopeKind.Template);
_stack.Push(currentScope);
// Templates always get a lambda scope, because they are defined as a lambda.
OffsetBuilderVarNumber(1);
currentScope.LambdaScope = context.CodeWriter.BuildLambda(BuilderVarName);
}
public void CloseScope(CodeRenderingContext context)
{
var currentScope = _stack.Pop();
// When closing the scope for a component with children, it's time to close the lambda
if (currentScope.LambdaScope != null)
{
currentScope.LambdaScope.Dispose();
if (currentScope.Kind == ScopeKind.Component)
{
context.CodeWriter.Write(")");
context.CodeWriter.WriteEndMethodInvocation();
}
OffsetBuilderVarNumber(-1);
}
}
private void OffsetBuilderVarNumber(int delta)
{
_builderVarNumber += delta;
BuilderVarName = _builderVarNumber == 1
? "builder"
: $"builder{_builderVarNumber}";
}
private class ScopeEntry
{
public readonly string Name;
public ScopeKind Kind;
public int ChildCount;
public IDisposable LambdaScope;
public ScopeEntry(string name, ScopeKind kind)
{
Name = name;
Kind = kind;
ChildCount = 0;
}
public override string ToString() => $"<{Name}> ({Kind})";
}
private enum ScopeKind
{
Element,
Component,
Template,
}
}
}