Skip to content
Closed
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
30 changes: 30 additions & 0 deletions source/Handlebars.Test/ComplexIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,36 @@ public void IfImplicitIteratorHelper()
Assert.Equal("<a href='http://google.com/'>Google</a><a href='http://yahoo.com/'>Yahoo!</a>", result);
}

// the helper has priority
// https://handlebarsjs.com/guide/expressions.html#disambiguating-helpers-calls-and-property-lookup
[Fact]
public void HelperWithSameNameVariable()
{
Handlebars.RegisterHelper("foo", (writer, context, arguments) =>
{
writer.Write("Helper");
});

var template = Handlebars.Compile("{{foo}}");
var result = template(new { foo = "Variable" });
Assert.Equal("Helper", result);
}

[Fact]
public void LateBoundHelperWithSameNameVariable()
{
var template = Handlebars.Compile("{{amoeba}}");

Assert.Equal("Variable", template(new { amoeba = "Variable" }));

Handlebars.RegisterHelper("amoeba", (writer, context, arguments) =>
{
writer.Write("Helper");
});

Assert.Equal("Helper", template(new { amoeba = "Variable" }));
}

[Fact]
public void BlockHelperWithSameNameVariable()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections.Generic;
using System.IO;

namespace HandlebarsDotNet.Compiler
{
Expand Down Expand Up @@ -32,27 +32,31 @@ protected override Expression VisitStatementExpression(StatementExpression sex)

protected override Expression VisitHelperExpression(HelperExpression hex)
{
if (CompilationContext.Configuration.Helpers.ContainsKey(hex.HelperName))
var arguments = new Expression[]
{
var helper = CompilationContext.Configuration.Helpers[hex.HelperName];
var arguments = new Expression[]
{
Expression.Property(
CompilationContext.BindingContext,
Expression.Property(
CompilationContext.BindingContext,
#if netstandard
typeof(BindingContext).GetRuntimeProperty("TextWriter")),
typeof(BindingContext).GetRuntimeProperty("TextWriter")
#else
typeof(BindingContext).GetProperty("TextWriter")),
typeof(BindingContext).GetProperty("TextWriter")
#endif
Expression.Property(
CompilationContext.BindingContext,
),
Expression.Property(
CompilationContext.BindingContext,
#if netstandard
typeof(BindingContext).GetRuntimeProperty("Value")),
typeof(BindingContext).GetRuntimeProperty("Value")
#else
typeof(BindingContext).GetProperty("Value")),
typeof(BindingContext).GetProperty("Value")
#endif
Expression.NewArrayInit(typeof(object), hex.Arguments.Select(a => Visit(a)))
};
),
Expression.Constant(hex.HelperName),
Expression.NewArrayInit(typeof(object), hex.Arguments.Select(a => Visit(a)))
};

if (CompilationContext.Configuration.Helpers.ContainsKey(hex.HelperName))
{
var helper = GetHelperWithName(CompilationContext.Configuration.Helpers[hex.HelperName]);
if (helper.Target != null)
{
return Expression.Call(
Expand Down Expand Up @@ -80,30 +84,43 @@ protected override Expression VisitHelperExpression(HelperExpression hex)
return Expression.Call(
Expression.Constant(this),
#if netstandard
new Action<BindingContext, string, IEnumerable<object>>(LateBindHelperExpression).GetMethodInfo(),
new HandlebarsHelperWithName(InvokeLateBindHelper).GetMethodInfo(),
#else
new Action<BindingContext, string, IEnumerable<object>>(LateBindHelperExpression).Method,
new HandlebarsHelperWithName(InvokeLateBindHelper).Method,
#endif
CompilationContext.BindingContext,
Expression.Constant(hex.HelperName),
Expression.NewArrayInit(typeof(object), hex.Arguments));
arguments);
}
}

private void LateBindHelperExpression(
BindingContext context,
private HandlebarsHelperWithName GetHelperWithName(HandlebarsHelper helper)
=> (writer, context, name, arguments) => helper(writer, context, arguments);

private void InvokeLateBindHelper(
TextWriter writer,
dynamic bindingContext,
string helperName,
IEnumerable<object> arguments)
{
if (!TryInvokeLateBoundHelper(CompilationContext, writer, bindingContext, helperName, arguments))
throw new HandlebarsRuntimeException(string.Format(
"Template references a helper that is not registered. Could not find helper '{0}'", helperName));
}

public static bool TryInvokeLateBoundHelper(
CompilationContext compilationContext,
TextWriter writer,
dynamic bindingContext,
string helperName,
IEnumerable<object> arguments)
{
if (CompilationContext.Configuration.Helpers.ContainsKey(helperName))
if (compilationContext.Configuration.Helpers.ContainsKey(helperName))
{
var helper = CompilationContext.Configuration.Helpers[helperName];
helper(context.TextWriter, context.Value, arguments.ToArray());
}
else
{
throw new HandlebarsRuntimeException(string.Format("Template references a helper that is not registered. Could not find helper '{0}'", helperName));
var helper = compilationContext.Configuration.Helpers[helperName];
helper(writer, bindingContext, arguments.ToArray());
return true;
}

return false;
}
}
}
41 changes: 23 additions & 18 deletions source/Handlebars/Compiler/Translation/Expression/PathBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,34 @@ protected override Expression VisitStatementExpression(StatementExpression sex)
{
if (sex.Body is PathExpression)
{
return Expression.Call(
Expression.Constant(this),
#if netstandard
var writeMethod = typeof(TextWriter).GetRuntimeMethod("Write", new [] { typeof(object) });
new Action<BindingContext, string>(WriteLateBoundHelperResultOrResolvedPath).GetMethodInfo(),
#else
var writeMethod = typeof(TextWriter).GetMethod("Write", new[] { typeof(object) });
new Action<BindingContext, string>(WriteLateBoundHelperResultOrResolvedPath).Method,
#endif
return Expression.Call(
Expression.Property(
CompilationContext.BindingContext,
"TextWriter"),
writeMethod, Visit(sex.Body));
CompilationContext.BindingContext,
Expression.Constant(((PathExpression) sex.Body).Path));
}
else
{
return Visit(sex.Body);
}
}

private void WriteLateBoundHelperResultOrResolvedPath(BindingContext bindingContext, string path)
{
var writer = bindingContext.TextWriter;

if (HelperFunctionBinder.TryInvokeLateBoundHelper(CompilationContext, writer, bindingContext, path, new object[] { }))
{
return;
}

writer.Write(ResolvePath(bindingContext, path));
}

protected override Expression VisitPathExpression(PathExpression pex)
{
return Expression.Call(
Expand Down Expand Up @@ -103,25 +114,19 @@ private object ResolvePath(BindingContext context, string path)
}

list[list.Count - 1] = list[list.Count - 1] + "." + next;
return list;
}
else
{
if (next.StartsWith("["))
if (next.StartsWith("[") && !next.EndsWith("]"))
{
insideEscapeBlock = true;
}

if (next.EndsWith("]"))
{
insideEscapeBlock = false;
}

list.Add(next);
return list;
}
return list;
});

foreach (var memberName in pathChain)
{
instance = ResolveValue(context, instance, memberName);
Expand Down Expand Up @@ -172,12 +177,12 @@ private object ResolveValue(BindingContext context, object instance, string segm
}

private static readonly Regex IndexRegex = new Regex(@"^\[?(?<index>\d+)\]?$", RegexOptions.None);

private object AccessMember(object instance, string memberName)
{
if (instance == null)
return new UndefinedBindingResult(memberName, CompilationContext.Configuration);

var resolvedMemberName = ResolveMemberName(instance, memberName);
var instanceType = instance.GetType();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System;
using System.Linq.Expressions;
using System.IO;
using System.Text;
using System.Reflection;
using System.Text;

namespace HandlebarsDotNet.Compiler
{
Expand All @@ -25,22 +25,23 @@ protected override Expression VisitSubExpression(SubExpressionExpression subex)
{
throw new HandlebarsCompilerException("Sub-expression does not contain a converted MethodCall expression");
}
HandlebarsHelper helper = GetHelperDelegateFromMethodCallExpression(helperCall);
HandlebarsHelperWithName helper = GetHelperDelegateFromMethodCallExpression(helperCall);
return Expression.Call(
#if netstandard
new Func<HandlebarsHelper, object, object[], string>(CaptureTextWriterOutputFromHelper).GetMethodInfo(),
new Func<HandlebarsHelperWithName, dynamic, string, object[], string>(CaptureTextWriterOutputFromHelper).GetMethodInfo(),
#else
new Func<HandlebarsHelper, object, object[], string>(CaptureTextWriterOutputFromHelper).Method,
new Func<HandlebarsHelperWithName, dynamic, string, object[], string>(CaptureTextWriterOutputFromHelper).Method,
#endif
Expression.Constant(helper),
Visit(helperCall.Arguments[1]),
Visit(helperCall.Arguments[2]));
Visit(helperCall.Arguments[2]),
Visit(helperCall.Arguments[3]));
}

private static HandlebarsHelper GetHelperDelegateFromMethodCallExpression(MethodCallExpression helperCall)
private static HandlebarsHelperWithName GetHelperDelegateFromMethodCallExpression(MethodCallExpression helperCall)
{
object target = helperCall.Object;
HandlebarsHelper helper;
HandlebarsHelperWithName helper;
if (target != null)
{
if (target is ConstantExpression)
Expand All @@ -52,31 +53,32 @@ private static HandlebarsHelper GetHelperDelegateFromMethodCallExpression(Method
throw new NotSupportedException("Helper method instance target must be reduced to a ConstantExpression");
}
#if netstandard
helper = (HandlebarsHelper)helperCall.Method.CreateDelegate(typeof(HandlebarsHelper), target);
helper = (HandlebarsHelperWithName)helperCall.Method.CreateDelegate(typeof(HandlebarsHelperWithName), target);
#else
helper = (HandlebarsHelper)Delegate.CreateDelegate(typeof(HandlebarsHelper), target, helperCall.Method);
helper = (HandlebarsHelperWithName)Delegate.CreateDelegate(typeof(HandlebarsHelperWithName), target, helperCall.Method);
#endif
}
else
{
#if netstandard
helper = (HandlebarsHelper)helperCall.Method.CreateDelegate(typeof(HandlebarsHelper));
helper = (HandlebarsHelperWithName)helperCall.Method.CreateDelegate(typeof(HandlebarsHelperWithName));
#else
helper = (HandlebarsHelper)Delegate.CreateDelegate(typeof(HandlebarsHelper), helperCall.Method);
helper = (HandlebarsHelperWithName)Delegate.CreateDelegate(typeof(HandlebarsHelperWithName), helperCall.Method);
#endif
}
return helper;
}

private static string CaptureTextWriterOutputFromHelper(
HandlebarsHelper helper,
object context,
HandlebarsHelperWithName helper,
dynamic context,
string name,
object[] arguments)
{
var builder = new StringBuilder();
using (var writer = new StringWriter(builder))
{
helper(writer, context, arguments);
helper(writer, context, name, arguments);
}
return builder.ToString();
}
Expand Down
3 changes: 3 additions & 0 deletions source/Handlebars/Handlebars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ namespace HandlebarsDotNet
public delegate void HandlebarsHelper(TextWriter output, dynamic context, params object[] arguments);
public delegate void HandlebarsBlockHelper(TextWriter output, HelperOptions options, dynamic context, params object[] arguments);

// a HandlebarsHelper that also stores the name of the helper for later use
internal delegate void HandlebarsHelperWithName(TextWriter output, dynamic context, string name, params object[] arguments);

public sealed partial class Handlebars
{
// Lazy-load Handlebars environment to ensure thread safety. See Jon Skeet's excellent article on this for more info. http://csharpindepth.com/Articles/General/Singleton.aspx
Expand Down