diff --git a/source/Handlebars.Test/InlinePartialTests.cs b/source/Handlebars.Test/InlinePartialTests.cs index 7d4a019f..569e862f 100644 --- a/source/Handlebars.Test/InlinePartialTests.cs +++ b/source/Handlebars.Test/InlinePartialTests.cs @@ -264,6 +264,18 @@ public void BasicBlockInlinePartial() var result2 = template(data); Assert.Equal("Hello, Pete Jones!", result2); + + source = "Hello, {{#>personInline}}{{/personInline}}!"; + template = Handlebars.Compile(source); + + var result3 = template(data); + Assert.Equal("Hello, !", result3); + + source = "{{#*inline \"personInline\"}}{{firstName}} {{lastName}}{{/inline}}" + source; + template = Handlebars.Compile(source); + + var result4 = template(data); + Assert.Equal("Hello, Pete Jones!", result4); } [Fact] @@ -413,6 +425,42 @@ public void RecursionBoundedAboveLimitInlinePartial() ex = Assert.IsType(ex.InnerException); Assert.Equal("Runtime error while rendering partial 'list', exceeded recursion depth limit of 100", ex.Message); } + + [Fact] + public void BlockInlinePartialWithInlinePartials() + { + string partialSource = "{{#*inline \"greeting\"}}{{#>salutation}}Dear{{/salutation}} {{#>name}}{{firstName}} {{lastName}}{{/name}}{{/inline}}"; + + var data = new + { + firstName = "Pete", + lastName = "Jones" + }; + + string source = partialSource + "{{#>greeting}}{{/greeting}}"; + var template = Handlebars.Compile(source); + + var result1 = template(data); + Assert.Equal("Dear Pete Jones", result1); + + source = partialSource + "{{#>greeting}}{{#*inline \"salutation\"}}Hello{{/inline}}{{/greeting}}"; + template = Handlebars.Compile(source); + + var result2 = template(data); + Assert.Equal("Hello Pete Jones", result2); + + source = partialSource + "{{#>greeting}}{{#*inline \"name\"}}Mr. {{lastName}}{{/inline}}{{/greeting}}"; + template = Handlebars.Compile(source); + + var result3 = template(data); + Assert.Equal("Dear Mr. Jones", result3); + + source = partialSource + "{{#>greeting}}{{#*inline \"salutation\"}}Hello{{/inline}}{{#*inline \"name\"}}{{firstName}}{{/inline}}{{/greeting}}"; + template = Handlebars.Compile(source); + + var result4 = template(data); + Assert.Equal("Hello Pete", result4); + } } } diff --git a/source/Handlebars.Test/IssueTests.cs b/source/Handlebars.Test/IssueTests.cs index de24f35d..2a69d416 100644 --- a/source/Handlebars.Test/IssueTests.cs +++ b/source/Handlebars.Test/IssueTests.cs @@ -1049,15 +1049,22 @@ public void Issue519_PartialBlockUsableAsBlockAndInIf() { var handlebars = Handlebars.Create(); handlebars.RegisterTemplate("myPartial", - @"Conditional:{{#if @partial-block}} {{> @partial-block}}{{/if}} -Plain: {{> @partial-block}} -Block:{{#> @partial-block }}{{/@partial-block}}"); + """ + Conditional: {{#if @partial-block}}{{> @partial-block}}{{/if}} + Plain: {{> @partial-block}} + Block with empty fallback: {{#> @partial-block }}{{/@partial-block}} + Block with non-empty fallback: {{#> @partial-block }}...{{/@partial-block}} + """); var render = handlebars.Compile("{{#> myPartial}}Block content{{/myPartial}}"); var actual = render(new { }); - Assert.Contains("Conditional: Block content", actual); - Assert.Contains("Plain: Block content", actual); - Assert.Contains("Block:Block content", actual); + Assert.Equal(""" + Conditional: Block content + Plain: Block content + Block with empty fallback: Block content + Block with non-empty fallback: Block content + """.ReplaceLineEndings("\n"), + actual); } // Issue: https://github.com/Handlebars-Net/Handlebars.Net/issues/458 diff --git a/source/Handlebars/Compiler/Lexer/Converter/BlockAccumulators/PartialBlockAccumulatorContext.cs b/source/Handlebars/Compiler/Lexer/Converter/BlockAccumulators/PartialBlockAccumulatorContext.cs index 4d6731b6..c1f30ada 100644 --- a/source/Handlebars/Compiler/Lexer/Converter/BlockAccumulators/PartialBlockAccumulatorContext.cs +++ b/source/Handlebars/Compiler/Lexer/Converter/BlockAccumulators/PartialBlockAccumulatorContext.cs @@ -24,7 +24,12 @@ public override void HandleElement(Expression item) public override Expression GetAccumulatedBlock() { - var fallback = _body.Count == 0 ? null : _body.Count == 1 ? _body.First() : Expression.Block(_body); + var fallback = _body.Count switch + { + 0 => Expression.Empty(), + 1 => _body[0], + _ => Expression.Block(_body) + }; return HandlebarsExpression.Partial(_startingNode.PartialName, _startingNode.Argument, fallback); } diff --git a/source/Handlebars/Compiler/Lexer/Converter/WhitespaceRemover.cs b/source/Handlebars/Compiler/Lexer/Converter/WhitespaceRemover.cs index e1051732..b36f67ab 100644 --- a/source/Handlebars/Compiler/Lexer/Converter/WhitespaceRemover.cs +++ b/source/Handlebars/Compiler/Lexer/Converter/WhitespaceRemover.cs @@ -71,6 +71,7 @@ private static void ProcessStatement(IList list, int index, StatementExp partialExpr.PartialName, partialExpr.Argument, partialExpr.Fallback, + partialExpr.IsBlock, indent); list[index] = HandlebarsExpression.Statement( indentedPartial, diff --git a/source/Handlebars/Compiler/Structure/HandlebarsExpression.cs b/source/Handlebars/Compiler/Structure/HandlebarsExpression.cs index 68909f7a..73794e70 100644 --- a/source/Handlebars/Compiler/Structure/HandlebarsExpression.cs +++ b/source/Handlebars/Compiler/Structure/HandlebarsExpression.cs @@ -94,17 +94,17 @@ public static PartialExpression Partial(Expression partialName) public static PartialExpression Partial(Expression partialName, Expression argument) { - return new PartialExpression(partialName, argument, null); + return new PartialExpression(partialName, argument, null, false); } public static PartialExpression Partial(Expression partialName, Expression argument, Expression fallback) { - return new PartialExpression(partialName, argument, fallback); + return new PartialExpression(partialName, argument, fallback, true); } - public static PartialExpression Partial(Expression partialName, Expression argument, Expression fallback, string indent) + public static PartialExpression Partial(Expression partialName, Expression argument, Expression fallback, bool isBlock, string indent) { - return new PartialExpression(partialName, argument, fallback, indent); + return new PartialExpression(partialName, argument, fallback, isBlock, indent); } public static BoolishExpression Boolish(Expression condition, HashParametersExpression hashParameters) diff --git a/source/Handlebars/Compiler/Structure/PartialExpression.cs b/source/Handlebars/Compiler/Structure/PartialExpression.cs index 187633bf..0b3ef979 100644 --- a/source/Handlebars/Compiler/Structure/PartialExpression.cs +++ b/source/Handlebars/Compiler/Structure/PartialExpression.cs @@ -4,11 +4,12 @@ namespace HandlebarsDotNet.Compiler { internal class PartialExpression : HandlebarsExpression { - public PartialExpression(Expression partialName, Expression argument, Expression fallback, string indent = null) + public PartialExpression(Expression partialName, Expression argument, Expression fallback, bool isBlock = false, string indent = null) { PartialName = partialName; Argument = argument; Fallback = fallback; + IsBlock = isBlock; Indent = indent; } @@ -20,6 +21,8 @@ public PartialExpression(Expression partialName, Expression argument, Expression public Expression Fallback { get; } + public bool IsBlock { get; } + /// /// The whitespace that preceded the partial tag on its line. /// When non-null/non-empty, this indentation is prepended to every line of the rendered partial output, diff --git a/source/Handlebars/Compiler/Translation/Expression/HandlebarsExpressionVisitor.cs b/source/Handlebars/Compiler/Translation/Expression/HandlebarsExpressionVisitor.cs index e158279f..94434867 100644 --- a/source/Handlebars/Compiler/Translation/Expression/HandlebarsExpressionVisitor.cs +++ b/source/Handlebars/Compiler/Translation/Expression/HandlebarsExpressionVisitor.cs @@ -105,7 +105,7 @@ protected virtual Expression VisitPartialExpression(PartialExpression pex) if (partialName != pex.PartialName || argument != pex.Argument) { - return HandlebarsExpression.Partial(partialName, argument, pex.Fallback); + return HandlebarsExpression.Partial(partialName, argument, pex.Fallback, pex.IsBlock, pex.Indent); } return pex; } diff --git a/source/Handlebars/Compiler/Translation/Expression/PartialBinder.cs b/source/Handlebars/Compiler/Translation/Expression/PartialBinder.cs index 56453b81..4016ad04 100644 --- a/source/Handlebars/Compiler/Translation/Expression/PartialBinder.cs +++ b/source/Handlebars/Compiler/Translation/Expression/PartialBinder.cs @@ -58,12 +58,13 @@ protected override Expression VisitPartialExpression(PartialExpression pex) var partialNameObj = Arg(pex.PartialName); var partialName = Call(() => ToPartialName(partialNameObj)); var configuration = Arg(CompilationContext.Configuration); + var isBlock = Arg(pex.IsBlock); var indent = Arg(pex.Indent); var templateDelegate = FunctionBuilder.Compile( new [] { Call(() => - InvokePartialWithFallback(partialName, bindingContext, writer, (ICompiledHandlebarsConfiguration) configuration, indent) // NOSONAR S1944 — ExpressionShortcuts operator; not a runtime hierarchy cast + InvokePartialWithFallback(partialName, bindingContext, writer, (ICompiledHandlebarsConfiguration) configuration, isBlock, indent) // NOSONAR S1944 — ExpressionShortcuts operator; not a runtime hierarchy cast ).Expression }, CompilationContext, @@ -92,10 +93,11 @@ out _ var partialNameObj = Arg(pex.PartialName); var partialName = Call(() => ToPartialName(partialNameObj)); var configuration = Arg(CompilationContext.Configuration); + var isBlock = Arg(pex.IsBlock); var indent = Arg(pex.Indent); return Call(() => - InvokePartialWithFallback(partialName, bindingContext, writer, (ICompiledHandlebarsConfiguration) configuration, indent) + InvokePartialWithFallback(partialName, bindingContext, writer, (ICompiledHandlebarsConfiguration) configuration, isBlock, indent) ); } } @@ -105,10 +107,11 @@ private static void InvokePartialWithFallback( BindingContext context, EncodedTextWriter writer, ICompiledHandlebarsConfiguration configuration, - string indent = null) + bool block, + string indent) { partialName = partialName != null ? ChainSegment.Create(partialName).TrimmedValue : null; - if (InvokePartial(partialName, context, writer, configuration, indent)) return; + if (InvokePartial(partialName, context, writer, configuration, block, indent)) return; if (context.PartialBlockTemplate == null) { if (configuration.MissingPartialTemplateHandler == null) @@ -173,16 +176,24 @@ private static bool InvokePartial( BindingContext context, EncodedTextWriter writer, ICompiledHandlebarsConfiguration configuration, - string indent = null) + bool block, + string indent) { if (partialName.Equals(SpecialPartialBlockName)) { - if (context.PartialBlockTemplate == null) + var partialBlockTemplate = context.PartialBlockTemplate; + + // If we are a block, our contents are the fallback and SpecialPartialBlockName refers to our parent + if (block) + { + partialBlockTemplate = context.ParentContext.PartialBlockTemplate; + } + + if (partialBlockTemplate == null) { return false; } - var partialBlockTemplate = context.PartialBlockTemplate; try { context.PartialBlockTemplate = context.ParentContext.PartialBlockTemplate;