Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion source/Handlebars.Test/ExceptionTests.cs
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,5 +12,50 @@ public void TestNonClosingBlockExpressionException()
Handlebars.Compile("{{#if 0}}test")(new { });
});
}
}

[Fact]
public void TestLooseClosingBlockExpressionException()
{
Assert.Throws<HandlebarsCompilerException>(() =>
{
Handlebars.Compile("{{#if 0}}test{{/if}}{{/unless}}")(new { });
});
}

[Fact]
public void TestNestedLooseClosingBlockExpressionException()
{
Assert.Throws<HandlebarsCompilerException>(() =>
{
Handlebars.Compile("{{#if 1}}{{#unless 0}}test{{/if}}{{/unless}}{{/if}}")(new { });
});
}

[Fact]
public void TestUnmatchedClosingBlockExpressionException()
{
Assert.Throws<HandlebarsCompilerException>(() =>
{
Handlebars.Compile("{{#if 0}}test{{/unless}}")(new { });
});
}

[Fact]
public void TestLooseClosingBlockInIteratorExpressionException()
{
var data = new
{
enumerateMe = new
{
foo = "hello",
bar = "world"
}
};

Assert.Throws<HandlebarsCompilerException>(() =>
{
Handlebars.Compile("{{#each enumerateMe}}test{{/if}}{{/each}}")(data);
});
}
}
}
12 changes: 6 additions & 6 deletions source/Handlebars/Compiler/Lexer/Converter/BlockAccumulator.cs
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

Expand DownExpand Up@@ -27,10 +26,10 @@ public override IEnumerable<object> ConvertTokens(IEnumerable<object> sequence)
while (enumerator.MoveNext())
{
var item = (Expression)enumerator.Current;
var context = BlockAccumulatorContext.Create(item, _configuration);
var context = BlockAccumulatorContext.Create(item, null, _configuration);
if (context != null)
{
yield return AccumulateBlock(enumerator, context);
yield return AccumulateBlock(item, enumerator, context);
}
else
{
Expand All@@ -40,16 +39,17 @@ public override IEnumerable<object> ConvertTokens(IEnumerable<object> sequence)
}

private Expression AccumulateBlock(
Expression parentItem,
IEnumerator<object> enumerator,
BlockAccumulatorContext context)
{
while (enumerator.MoveNext())
{
var item = (Expression)enumerator.Current;
var innerContext = BlockAccumulatorContext.Create(item, _configuration);
var innerContext = BlockAccumulatorContext.Create(item, parentItem, _configuration);
if (innerContext != null)
{
context.HandleElement(AccumulateBlock(enumerator, innerContext));
context.HandleElement(AccumulateBlock(item, enumerator, innerContext));
}
else if (context.IsClosingElement(item))
{
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq.Expressions;
using HandlebarsDotNet.PathStructure;
using HandlebarsDotNet.StringUtils;

namespace HandlebarsDotNet.Compiler
{
Expand All@@ -10,7 +11,7 @@ internal abstract class BlockAccumulatorContext
private static readonly HashSet<string> ConditionHelpers = new HashSet<string>(StringComparer.OrdinalIgnoreCase){ "#if", "#unless", "^if", "^unless" };
private static readonly HashSet<string> IteratorHelpers = new HashSet<string>(StringComparer.OrdinalIgnoreCase){ "#each", "^each" };

public static BlockAccumulatorContext Create(Expression item, ICompiledHandlebarsConfiguration configuration)
public static BlockAccumulatorContext Create(Expression item, Expression parentItem, ICompiledHandlebarsConfiguration configuration)
{
BlockAccumulatorContext context = null;
if (IsConditionalBlock(item))
Expand All@@ -29,6 +30,10 @@ public static BlockAccumulatorContext Create(Expression item, ICompiledHandlebar
{
context = new BlockHelperAccumulatorContext(item);
}
else if (IsDetachedClosingElement(item, parentItem, out var closingElement))
{
throw new HandlebarsCompilerException($"A closing element '{closingElement}' was found without a matching open element");
}

return context;
}
Expand DownExpand Up@@ -64,17 +69,55 @@ private static bool IsIteratorBlock(Expression item)
private static bool IsPartialBlock (Expression item)
{
item = UnwrapStatement (item);
switch (item)
return item switch
{
PathExpression expression => expression.Path.StartsWith("#>"),
HelperExpression helperExpression => helperExpression.HelperName.StartsWith("#>"),
_ => false,
};
}

private static bool IsDetachedClosingElement(Expression item, Expression parentItem, out string closingElement)
{
closingElement = null;

var itemElement = GetItemElement(item);

if (itemElement == null) return false;

var parentItemElement = GetItemElement(parentItem);

if (!itemElement.StartsWith("/")) return false;

if (parentItemElement == null || IsClosingElementNotMatchOpenElement(itemElement, parentItemElement))
{
case PathExpression expression:
return expression.Path.StartsWith("#>");

case HelperExpression helperExpression:
return helperExpression.HelperName.StartsWith("#>");

default:
return false;
closingElement = itemElement;

return true;
}

return false;
}

private static bool IsClosingElementNotMatchOpenElement(string closingElement, string openElement)
{
if (closingElement == null) throw new ArgumentNullException(nameof(closingElement));
if (openElement == null) throw new ArgumentNullException(nameof(openElement));

if (!openElement.StartsWith("#") || openElement.StartsWith("#>") || openElement.StartsWith("#*")) return false;

return new Substring(openElement, 1) != new Substring(closingElement, 1);
}

private static string GetItemElement(Expression item)
{
item = UnwrapStatement(item);
return item switch
{
PathExpression pathExpression => pathExpression.Path,
HelperExpression helperExpression => helperExpression.HelperName,
_ => null,
};
}

protected static Expression UnwrapStatement(Expression item)
Expand Down