From d1e3a906132e22fcc0da69ba1f5a97d60cc6f1c5 Mon Sep 17 00:00:00 2001 From: Mike Bridge Date: Thu, 22 Oct 2015 22:50:59 -0600 Subject: [PATCH 1/4] partial implementation of error on dereference missing var --- .../Constants/MissingValueTests.cs | 64 +++++++++++++++++++ .../Expressions/VariableReferenceTests.cs | 15 +++++ .../Filters/Strings/RemoveFilterTests.cs | 2 +- Liquid.NET.Tests/Liquid.NET.Tests.csproj | 1 + .../Symbols/SymbolTableStackTests.cs | 4 +- Liquid.NET/src/Constants/DictionaryValue.cs | 6 +- Liquid.NET/src/Constants/IndexDereferencer.cs | 7 +- .../src/Expressions/VariableReference.cs | 17 ++++- .../src/Expressions/VariableReferenceTree.cs | 13 +--- Liquid.NET/src/RenderingVisitor.cs | 23 +++++-- Liquid.NET/src/Symbols/IASTVisitor.cs | 2 +- Liquid.NET/src/Symbols/SymbolTable.cs | 8 ++- Liquid.NET/src/Symbols/SymbolTableStack.cs | 5 +- Liquid.NET/src/TemplateContext.cs | 9 ++- 14 files changed, 138 insertions(+), 38 deletions(-) create mode 100755 Liquid.NET.Tests/Constants/MissingValueTests.cs diff --git a/Liquid.NET.Tests/Constants/MissingValueTests.cs b/Liquid.NET.Tests/Constants/MissingValueTests.cs new file mode 100755 index 0000000..c465240 --- /dev/null +++ b/Liquid.NET.Tests/Constants/MissingValueTests.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Liquid.NET.Constants; +using NUnit.Framework; + +namespace Liquid.NET.Tests.Constants +{ + [TestFixture] + public class MissingValueTests + { + [Test] + [TestCase("x")] + [TestCase("e[1]")] + [TestCase("e.x")] + [TestCase("d.x")] + [TestCase("d[x]")] + [TestCase("a.b.c")] + public void It_Should_Display_An_Error_When_Dereferencing_Missing_Value(String varname) + { + // Arrange + + TemplateContext ctx = new TemplateContext() + .ErrorWhenValueMissing(); + ctx.DefineLocalVariable("e", new ArrayValue(new List())); + ctx.DefineLocalVariable("d", new DictionaryValue(new Dictionary())); + + // Act + var result = RenderingHelper.RenderTemplate("Result : {{ "+varname+" }}", ctx); + + // Assert + Assert.That(result, Is.EqualTo("Result : ERROR: "+varname+" is undefined")); + + } + + [Test] + [TestCase("x")] + [TestCase("e[1]")] + [TestCase("e.x")] + [TestCase("d.x")] + [TestCase("d[x]")] + [TestCase("a.b.c")] + public void It_Should_Not_Display_An_Error_When_Dereferencing_Missing_Value(String varname) + { + // Arrange + + TemplateContext ctx = new TemplateContext(); + ctx.DefineLocalVariable("e", new ArrayValue(new List())); + ctx.DefineLocalVariable("d", new DictionaryValue(new Dictionary())); + + // Act + var result = RenderingHelper.RenderTemplate("Result : {{ " + varname + " }}", ctx); + + // Assert + Assert.That(result, Is.EqualTo("Result : ")); + + } + + + + } +} diff --git a/Liquid.NET.Tests/Expressions/VariableReferenceTests.cs b/Liquid.NET.Tests/Expressions/VariableReferenceTests.cs index 3931995..7132b62 100755 --- a/Liquid.NET.Tests/Expressions/VariableReferenceTests.cs +++ b/Liquid.NET.Tests/Expressions/VariableReferenceTests.cs @@ -27,8 +27,23 @@ public void It_Should_Derefence_A_Variable() // Assert Assert.That(result.Value, Is.EqualTo("HELLO")); + } + + [Test] + public void It_Should_Derefence_A_Variable_Missing_Variable_As_None() + { + // Arrange + var variableReference = new VariableReference("myvar"); + var templateContext = new TemplateContext(); + + // Act + var result = variableReference.Eval(templateContext, new List>()); + + // Assert + Assert.That(result.SuccessResult.HasValue, Is.False); } + } } diff --git a/Liquid.NET.Tests/Filters/Strings/RemoveFilterTests.cs b/Liquid.NET.Tests/Filters/Strings/RemoveFilterTests.cs index f1cc693..f7384be 100755 --- a/Liquid.NET.Tests/Filters/Strings/RemoveFilterTests.cs +++ b/Liquid.NET.Tests/Filters/Strings/RemoveFilterTests.cs @@ -48,7 +48,7 @@ public void It_Should_Remove_Nil_From_A_String() var result = RenderingHelper.RenderTemplate("Result : {{ \"test\" | remove : x }}"); // Assert - Assert.That(result, Is.EqualTo("Result : Please specify a replacement string.")); + Assert.That(result, Is.EqualTo("Result : ERROR: Please specify a replacement string.")); } diff --git a/Liquid.NET.Tests/Liquid.NET.Tests.csproj b/Liquid.NET.Tests/Liquid.NET.Tests.csproj index 43346db..ac9ea7a 100755 --- a/Liquid.NET.Tests/Liquid.NET.Tests.csproj +++ b/Liquid.NET.Tests/Liquid.NET.Tests.csproj @@ -65,6 +65,7 @@ + diff --git a/Liquid.NET.Tests/Symbols/SymbolTableStackTests.cs b/Liquid.NET.Tests/Symbols/SymbolTableStackTests.cs index e300056..43ee042 100755 --- a/Liquid.NET.Tests/Symbols/SymbolTableStackTests.cs +++ b/Liquid.NET.Tests/Symbols/SymbolTableStackTests.cs @@ -49,7 +49,7 @@ public void It_Should_Retrieve_A_Defined_Local_Registry_Value() [Test] - public void It_Should_Retrieve_An_Undefined_Value_When_Missing() + public void It_Should_Retrieve_An_Error_When_Missing() { // Arrange var stack = StackHelper.CreateSymbolTableStack(); @@ -59,7 +59,7 @@ public void It_Should_Retrieve_An_Undefined_Value_When_Missing() // Assert Assert.That(result, Is.Not.Null); - Assert.That(result.SuccessResult.HasValue, Is.False); + Assert.That(result.IsError, Is.True); //Assert.That(result, Is.TypeOf()); } diff --git a/Liquid.NET/src/Constants/DictionaryValue.cs b/Liquid.NET/src/Constants/DictionaryValue.cs index 50614fc..c2c64e6 100755 --- a/Liquid.NET/src/Constants/DictionaryValue.cs +++ b/Liquid.NET/src/Constants/DictionaryValue.cs @@ -41,11 +41,7 @@ public override string LiquidTypeName public Option ValueAt(String key) { - //Console.WriteLine("VALUE AT " + key); - // TODO: Fix this. - var result = _value.ContainsKey(key) ? _value[key] : new None(); // new Undefined(key); - //var result = _value.ContainsKey(key) ? _value[key] : FilterFactory.CreateUndefinedForType(typeof(StringValue)) - //Console.WriteLine("IS " + result); + var result = _value.ContainsKey(key) ? _value[key] : new None(); return result; } diff --git a/Liquid.NET/src/Constants/IndexDereferencer.cs b/Liquid.NET/src/Constants/IndexDereferencer.cs index dc72b06..4c2c624 100755 --- a/Liquid.NET/src/Constants/IndexDereferencer.cs +++ b/Liquid.NET/src/Constants/IndexDereferencer.cs @@ -1,10 +1,5 @@ using System; -using System.CodeDom; -using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; using Liquid.NET.Utils; namespace Liquid.NET.Constants @@ -99,7 +94,7 @@ private LiquidExpressionResult DoLookup(ITemplateContext ctx, DictionaryValue di String propertyNameString = ValueCaster.RenderAsString(indexProperty); if (propertyNameString.ToLower().Equals("size")) { - return LiquidExpressionResult.Success(NumericValue.Create(dictionaryValue.DictValue.Keys.Count())); + return LiquidExpressionResult.Success(NumericValue.Create(dictionaryValue.DictValue.Keys.Count)); } return LiquidExpressionResult.Success(dictionaryValue.ValueAt(indexProperty.Value.ToString())); diff --git a/Liquid.NET/src/Expressions/VariableReference.cs b/Liquid.NET/src/Expressions/VariableReference.cs index d20408d..063e5b5 100755 --- a/Liquid.NET/src/Expressions/VariableReference.cs +++ b/Liquid.NET/src/Expressions/VariableReference.cs @@ -25,9 +25,22 @@ public override void Accept(IExpressionDescriptionVisitor visitor) public override LiquidExpressionResult Eval(ITemplateContext templateContext, IEnumerable> childresults) { - return templateContext.SymbolTableStack.Reference(Name); + var lookupResult= templateContext.SymbolTableStack.Reference(Name); + return lookupResult.IsSuccess ? + lookupResult : + ErrorOrNone(templateContext, lookupResult); } - + private LiquidExpressionResult ErrorOrNone(ITemplateContext templateContext, LiquidExpressionResult failureResult) + { + if (templateContext.Options.ErrorWhenValueMissing) + { + return failureResult; + } + else + { + return LiquidExpressionResult.Success(new None()); + } + } } } diff --git a/Liquid.NET/src/Expressions/VariableReferenceTree.cs b/Liquid.NET/src/Expressions/VariableReferenceTree.cs index cfc2f5a..52a1a22 100755 --- a/Liquid.NET/src/Expressions/VariableReferenceTree.cs +++ b/Liquid.NET/src/Expressions/VariableReferenceTree.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using Liquid.NET.Constants; +using Liquid.NET.Symbols; using Liquid.NET.Utils; namespace Liquid.NET.Expressions @@ -24,18 +24,9 @@ public void Accept(IExpressionDescriptionVisitor expressionDescriptionVisitor) public LiquidExpressionResult Eval(ITemplateContext templateContext, IEnumerable> childresults) { - //return EvalExpression(templateContext, (dynamic) this, childresults); return EvalExpression(templateContext, this, childresults); } -// private LiquidExpressionResult EvalExpression( -// ITemplateContext templateContext, -// IExpressionDescription o, -// IEnumerable> childresults) -// { -// return o.Eval(templateContext, childresults); -// } - private LiquidExpressionResult EvalExpression( ITemplateContext templateContext, VariableReferenceTree o, @@ -63,7 +54,7 @@ private LiquidExpressionResult EvalExpression( if (!valueResult.SuccessResult.HasValue) { return LiquidExpressionResult.Success(new None()); - //return LiquidExpressionResult.Error("ERROR: there is no value"); + //return LiquidExpressionResult.Error(SymbolTable.NotFoundError(valueResult)); } if (!indexResult.SuccessResult.HasValue) { diff --git a/Liquid.NET/src/RenderingVisitor.cs b/Liquid.NET/src/RenderingVisitor.cs index 34e29b9..c4438fe 100755 --- a/Liquid.NET/src/RenderingVisitor.cs +++ b/Liquid.NET/src/RenderingVisitor.cs @@ -113,7 +113,20 @@ private void RenderErrors(IEnumerable liquidErrors) private String FormatErrors(IEnumerable liquidErrors) { //return "ERROR: " + String.Join("; ", liquidErrors.Select(x => x.Message)); - return String.Join("; ", liquidErrors.Select(x => x.Message)); + return String.Join("; ", liquidErrors.Select(x => FormatError(x))); + } + + private static string FormatError(LiquidError x) + { + // to remain backwards-compatible, this leaves "Liquid Error:" alone. + if (x.Message.IndexOf("Liquid error") >= 0) + { + return x.Message; + } + else + { + return "ERROR: " + x.Message; + } } private void RenderErrors(IEnumerable liquidErrors) @@ -400,10 +413,10 @@ public void Visit(RootDocumentNode rootDocumentNode) // noop } - public void Visit(VariableReference variableReference) - { - variableReference.Eval(_templateContext, new List>()); - } +// public void Visit(VariableReference variableReference) +// { +// variableReference.Eval(_templateContext, new List>()); +// } public void Visit(StringValue stringValue) { diff --git a/Liquid.NET/src/Symbols/IASTVisitor.cs b/Liquid.NET/src/Symbols/IASTVisitor.cs index afdaabd..f1841c2 100755 --- a/Liquid.NET/src/Symbols/IASTVisitor.cs +++ b/Liquid.NET/src/Symbols/IASTVisitor.cs @@ -21,7 +21,7 @@ public interface IASTVisitor void Visit(RootDocumentNode rootDocumentNode); - void Visit(VariableReference variableReference); + //void Visit(VariableReference variableReference); void Visit(StringValue stringValue); diff --git a/Liquid.NET/src/Symbols/SymbolTable.cs b/Liquid.NET/src/Symbols/SymbolTable.cs index 8f9b8b0..89a1b0d 100755 --- a/Liquid.NET/src/Symbols/SymbolTable.cs +++ b/Liquid.NET/src/Symbols/SymbolTable.cs @@ -181,10 +181,16 @@ public LiquidExpressionResult ReferenceLocalVariable(String key) } else { - return LiquidExpressionResult.Success(new None()); + //return LiquidExpressionResult.Success(new None()); + return LiquidExpressionResult.Error(NotFoundError(key)); } } + public static String NotFoundError(String key) + { + return key + " is undefined"; + } + public Object ReferenceLocalRegistryVariable(string key) { if (HasLocalRegistryVariableReference(key)) diff --git a/Liquid.NET/src/Symbols/SymbolTableStack.cs b/Liquid.NET/src/Symbols/SymbolTableStack.cs index dbd280c..8ae03fb 100755 --- a/Liquid.NET/src/Symbols/SymbolTableStack.cs +++ b/Liquid.NET/src/Symbols/SymbolTableStack.cs @@ -32,7 +32,7 @@ public SymbolTable Pop() /// public LiquidExpressionResult Reference(String reference, int skiplevels = 0) { - for (int i = _symbolTables.Count() - 1 - skiplevels; i >= 0; i--) + for (int i = _symbolTables.Count - 1 - skiplevels; i >= 0; i--) { //Console.WriteLine("Looking up" + reference); if (_symbolTables[i].HasVariableReference(reference)) @@ -40,7 +40,8 @@ public LiquidExpressionResult Reference(String reference, int skiplevels = 0) return _symbolTables[i].ReferenceLocalVariable(reference); } } - return LiquidExpressionResult.Success(new None()); + //return LiquidExpressionResult.Success(new None()); + return LiquidExpressionResult.Error(SymbolTable.NotFoundError(reference)); } diff --git a/Liquid.NET/src/TemplateContext.cs b/Liquid.NET/src/TemplateContext.cs index 31e3455..7ae962d 100755 --- a/Liquid.NET/src/TemplateContext.cs +++ b/Liquid.NET/src/TemplateContext.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq.Expressions; + using Liquid.NET.Constants; using Liquid.NET.Filters; using Liquid.NET.Filters.Array; @@ -252,12 +252,17 @@ public ITemplateContext WithNoForLimit() return this; } + public TemplateContext ErrorWhenValueMissing() + { + _options.ErrorWhenValueMissing = true; + return this; + } } public class LiquidOptions { public bool NoForLimit { get; internal set; } - + public bool ErrorWhenValueMissing { get; set; } } } From 28adaad2c657a0b9b050120d20e7bf0aa4d9c4a7 Mon Sep 17 00:00:00 2001 From: Mike Bridge Date: Sun, 25 Oct 2015 23:26:44 -0600 Subject: [PATCH 2/4] ... --- .../Constants/DictionaryValueTests.cs | 13 +++++++++++++ .../Constants/MissingValueTests.cs | 2 +- Liquid.NET.Tests/Constants/ReflectorTests.cs | 4 ---- Liquid.NET/src/Constants/ArrayValue.cs | 3 --- Liquid.NET/src/Constants/DictionaryValue.cs | 2 +- Liquid.NET/src/Constants/IndexDereferencer.cs | 19 +++++++++++++------ 6 files changed, 28 insertions(+), 15 deletions(-) diff --git a/Liquid.NET.Tests/Constants/DictionaryValueTests.cs b/Liquid.NET.Tests/Constants/DictionaryValueTests.cs index 540ea2f..d30d151 100755 --- a/Liquid.NET.Tests/Constants/DictionaryValueTests.cs +++ b/Liquid.NET.Tests/Constants/DictionaryValueTests.cs @@ -58,5 +58,18 @@ public void It_Should_Dereference_A_Nested_Dictionary() Assert.That(result, Is.EqualTo("Result : Dict 3->Dict3")); } + [Test] + public void It_Should_Fail_When_Dereferencing_A_Missing_Property() + { + // Arrange + var dict = new Dictionary + { + }; + DictionaryValue dictValue = new DictionaryValue(dict); + + // Assert + Assert.That(dictValue.ValueAt("string1").HasValue, Is.False); + + } } } diff --git a/Liquid.NET.Tests/Constants/MissingValueTests.cs b/Liquid.NET.Tests/Constants/MissingValueTests.cs index c465240..c3f4c3e 100755 --- a/Liquid.NET.Tests/Constants/MissingValueTests.cs +++ b/Liquid.NET.Tests/Constants/MissingValueTests.cs @@ -45,7 +45,7 @@ public void It_Should_Display_An_Error_When_Dereferencing_Missing_Value(String v public void It_Should_Not_Display_An_Error_When_Dereferencing_Missing_Value(String varname) { // Arrange - + Console.WriteLine(varname); TemplateContext ctx = new TemplateContext(); ctx.DefineLocalVariable("e", new ArrayValue(new List())); ctx.DefineLocalVariable("d", new DictionaryValue(new Dictionary())); diff --git a/Liquid.NET.Tests/Constants/ReflectorTests.cs b/Liquid.NET.Tests/Constants/ReflectorTests.cs index 90752eb..8a95c63 100755 --- a/Liquid.NET.Tests/Constants/ReflectorTests.cs +++ b/Liquid.NET.Tests/Constants/ReflectorTests.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Liquid.NET.Constants; using NUnit.Framework; diff --git a/Liquid.NET/src/Constants/ArrayValue.cs b/Liquid.NET/src/Constants/ArrayValue.cs index 8dfa5d6..62d3ea0 100755 --- a/Liquid.NET/src/Constants/ArrayValue.cs +++ b/Liquid.NET/src/Constants/ArrayValue.cs @@ -1,10 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.PerformanceData; using System.Linq; -using Liquid.NET.Symbols; using Liquid.NET.Utils; namespace Liquid.NET.Constants diff --git a/Liquid.NET/src/Constants/DictionaryValue.cs b/Liquid.NET/src/Constants/DictionaryValue.cs index c2c64e6..5f4129a 100755 --- a/Liquid.NET/src/Constants/DictionaryValue.cs +++ b/Liquid.NET/src/Constants/DictionaryValue.cs @@ -58,7 +58,7 @@ public override string ToString() private static String FormatKvPair(string key, Option expressionConstant) { Type wrappedType = GetWrappedType(expressionConstant); - String exprConstantAsString = expressionConstant.HasValue? expressionConstant.Value.ToString() : "null"; + String exprConstantAsString = expressionConstant.HasValue ? expressionConstant.Value.ToString() : "null"; return Quote(typeof(StringValue), key) + " : " + Quote(wrappedType, exprConstantAsString); } diff --git a/Liquid.NET/src/Constants/IndexDereferencer.cs b/Liquid.NET/src/Constants/IndexDereferencer.cs index 4c2c624..47a46ec 100755 --- a/Liquid.NET/src/Constants/IndexDereferencer.cs +++ b/Liquid.NET/src/Constants/IndexDereferencer.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Liquid.NET.Symbols; using Liquid.NET.Utils; namespace Liquid.NET.Constants @@ -43,11 +44,6 @@ public LiquidExpressionResult Lookup( return LiquidExpressionResult.Error("ERROR : cannot apply an index to a " + value.LiquidTypeName + "."); } - private LiquidExpressionResult DoLookup(ITemplateContext ctx, IExpressionConstant c, IExpressionConstant indexProperty) - { - return LiquidExpressionResult.Error("ERROR : cannot apply an index to a "+c.LiquidTypeName+"."); - - } private LiquidExpressionResult DoLookup(ITemplateContext ctx, ArrayValue arrayValue, IExpressionConstant indexProperty) { @@ -85,6 +81,7 @@ private LiquidExpressionResult DoLookup(ITemplateContext ctx, ArrayValue arrayVa return LiquidExpressionResult.Success(new None()); // not an error in Ruby liquid. } var result = arrayValue.ValueAt(index); + return LiquidExpressionResult.Success(result); } @@ -97,7 +94,17 @@ private LiquidExpressionResult DoLookup(ITemplateContext ctx, DictionaryValue di return LiquidExpressionResult.Success(NumericValue.Create(dictionaryValue.DictValue.Keys.Count)); } - return LiquidExpressionResult.Success(dictionaryValue.ValueAt(indexProperty.Value.ToString())); + var valueAt = dictionaryValue.ValueAt(indexProperty.Value.ToString()); + if (valueAt.HasValue) + { + return LiquidExpressionResult.Success(valueAt); + } + else + { + return ctx.Options.ErrorWhenValueMissing + ? LiquidExpressionResult.Error(SymbolTable.NotFoundError(indexProperty.ToString())) + : LiquidExpressionResult.Success(new None()); + } } // TODO: this is inefficient and ugly and duplicates much of ArrayValue From 7e70351f47a79d10e4e4b33181b944289c414f1e Mon Sep 17 00:00:00 2001 From: Mike Bridge Date: Wed, 4 Nov 2015 23:44:57 -0700 Subject: [PATCH 3/4] can toggle visual errors on missing variables --- .../Constants/MissingValueTests.cs | 53 +++++++++++++--- .../Filters/Array/MapFilterTests.cs | 63 ++++++++++++------- .../Filters/Array/SortFilterTests.cs | 61 +++++++++++++++--- Liquid.NET.Tests/Ruby/FilterTests.cs | 3 + Liquid.NET/Liquid.NET.csproj | 1 + Liquid.NET/src/Constants/ArrayValue.cs | 2 +- Liquid.NET/src/Constants/DictionaryValue.cs | 14 ++++- Liquid.NET/src/Constants/FieldAccessor.cs | 15 +++-- Liquid.NET/src/Constants/IndexDereferencer.cs | 48 ++++++++++---- Liquid.NET/src/Constants/MissingValueTest.cs | 12 ++++ Liquid.NET/src/Filters/Array/MapFilter.cs | 25 ++++---- Liquid.NET/src/Filters/Array/SortFilter.cs | 24 ++++--- Liquid.NET/src/TemplateContext.cs | 2 +- .../src/Utils/LiquidExpressionResult.cs | 13 ++++ Liquid.Ruby/tests/filters.txt | 6 ++ 15 files changed, 262 insertions(+), 80 deletions(-) create mode 100755 Liquid.NET/src/Constants/MissingValueTest.cs diff --git a/Liquid.NET.Tests/Constants/MissingValueTests.cs b/Liquid.NET.Tests/Constants/MissingValueTests.cs index c3f4c3e..d03063e 100755 --- a/Liquid.NET.Tests/Constants/MissingValueTests.cs +++ b/Liquid.NET.Tests/Constants/MissingValueTests.cs @@ -12,26 +12,59 @@ namespace Liquid.NET.Tests.Constants public class MissingValueTests { [Test] - [TestCase("x")] - [TestCase("e[1]")] - [TestCase("e.x")] - [TestCase("d.x")] - [TestCase("d[x]")] - [TestCase("a.b.c")] - public void It_Should_Display_An_Error_When_Dereferencing_Missing_Value(String varname) + [TestCase("x", "x")] + [TestCase("d.x", "x")] + [TestCase("d[x]", "x")] + [TestCase("a.b.c", "a")] + public void It_Should_Display_An_Error_When_Dereferencing_Missing_Value(String varname, String missingVar) { // Arrange - TemplateContext ctx = new TemplateContext() + ITemplateContext ctx = new TemplateContext() .ErrorWhenValueMissing(); - ctx.DefineLocalVariable("e", new ArrayValue(new List())); + //ctx.DefineLocalVariable("e", new ArrayValue(new List())); ctx.DefineLocalVariable("d", new DictionaryValue(new Dictionary())); // Act var result = RenderingHelper.RenderTemplate("Result : {{ "+varname+" }}", ctx); // Assert - Assert.That(result, Is.EqualTo("Result : ERROR: "+varname+" is undefined")); + Assert.That(result, Is.EqualTo("Result : ERROR: " + missingVar + " is undefined")); + + } + + [Test] + [TestCase("e[1]")] + [TestCase("e.first")] + [TestCase("e.last")] + public void It_Should_Display_An_Error_When_Dereferencing_Empty_Array(String varname) + { + // Arrange + ITemplateContext ctx = new TemplateContext() + .ErrorWhenValueMissing(); + ctx.DefineLocalVariable("e", new ArrayValue(new List())); + + // Act + var result = RenderingHelper.RenderTemplate("Result : {{ " + varname + " }}", ctx); + + // Assert + Assert.That(result, Is.EqualTo("Result : ERROR: cannot dereference empty array")); + + } + + [Test] + public void It_Should_Display_Error_When_Dereferencing_Array_With_Non_Int(String varname) + { + // Arrange + ITemplateContext ctx = new TemplateContext() + .ErrorWhenValueMissing(); + ctx.DefineLocalVariable("e", new ArrayValue(new List())); + + // Act + var result = RenderingHelper.RenderTemplate("Result : {{ e.x }}", ctx); + + // Assert + Assert.That(result, Is.StringContaining("invalid index: 'x'")); } diff --git a/Liquid.NET.Tests/Filters/Array/MapFilterTests.cs b/Liquid.NET.Tests/Filters/Array/MapFilterTests.cs index cc39f88..9e0f541 100755 --- a/Liquid.NET.Tests/Filters/Array/MapFilterTests.cs +++ b/Liquid.NET.Tests/Filters/Array/MapFilterTests.cs @@ -3,6 +3,7 @@ using System.Linq; using Liquid.NET.Constants; using Liquid.NET.Filters.Array; +using Liquid.NET.Utils; using NUnit.Framework; namespace Liquid.NET.Tests.Filters.Array @@ -45,25 +46,7 @@ public void It_Should_Return_None_Where_A_Field_Is_Missing() // Act var result = (mapFilter.Apply(new TemplateContext(), array).SuccessValue()).ToList(); - - - // Assert - //IEnumerable expected = dictionaryValues.Select(x => x.DictValue[field].; -// var expected = array.ArrValue.Select(x => x.Value.ValueAs().DictValue) -// .Select(x => x.ContainsKey(field) ? x[field].Value.ToString() : ""); - - -// var expected = array.ArrValue.Cast().Select( -// x => x.DictValue.ContainsKey(field) ? -// x.DictValue[field].Value.ToString() : -// UndefinedMessage(field)).ToList(); - - //Logger.Log("EXPECTED: " + String.Join(",", expected)); Assert.That(result.Count(x => !x.HasValue), Is.EqualTo(1)); - //var actual = result.Select(x => x.Value.ToString()); - - //Logger.Log("ACTUAL: " + String.Join(",", actual)); - //Assert.That(actual, Is.EquivalentTo(expected)); } @@ -101,10 +84,46 @@ public void It_Should_Do_The_Same_As_Lookup_When_Dictionary() Assert.That(result, Is.EquivalentTo("Value 1 A")); } -// private static string UndefinedMessage(string field) -// { -// return Undefined.CreateUndefinedMessage(field).ToString(); -// } + + [Test] + public void It_Should_Render_The_Fields() + { + // Arrange + var array = CreateArray(); + ITemplateContext ctx = new TemplateContext() + .WithAllFilters().DefineLocalVariable("arr", array); + var result = RenderingHelper.RenderTemplate("Result : {{ arr | map: \"field1\" }}",ctx); + + // Assert + Assert.That(result, Is.EqualTo("Result : Value 1 AValue 2 AValue 3 AValue 4 A")); + } + + [Test] + public void It_Should_Render_Missing_Fields_When_ErrorsOff() + { + // Arrange + var array = CreateArray(); + ITemplateContext ctx = new TemplateContext() + .WithAllFilters().DefineLocalVariable("arr", array); + var result = RenderingHelper.RenderTemplate("Result : {{ arr | map: \"awefwef\" }}", ctx); + + // Assert + Assert.That(result, Is.EqualTo("Result : ")); + } + + [Test] + public void It_Should_Error_When_No_Field_When_ErrorsOn() + { + // Arrange + var array = CreateArray(); + ITemplateContext ctx = new TemplateContext() + .ErrorWhenValueMissing() + .WithAllFilters().DefineLocalVariable("arr", array); + var result = RenderingHelper.RenderTemplate("Result : {{ arr | map: \"missing\" }}", ctx); + Console.WriteLine("Result "+result); + // Assert + Assert.That(result, Is.StringContaining("missing is undefined")); + } public ArrayValue CreateArray() { diff --git a/Liquid.NET.Tests/Filters/Array/SortFilterTests.cs b/Liquid.NET.Tests/Filters/Array/SortFilterTests.cs index a1ef8a7..a977cbd 100755 --- a/Liquid.NET.Tests/Filters/Array/SortFilterTests.cs +++ b/Liquid.NET.Tests/Filters/Array/SortFilterTests.cs @@ -38,13 +38,7 @@ public void It_Should_Sort_An_Array_By_StringValues() public void It_Should_Sort_Dictionaries_By_Field() { // Arrange - IList objlist = new List - { - DataFixtures.CreateDictionary(1, "Aa", "Value 1 B"), - DataFixtures.CreateDictionary(2, "Z", "Value 2 B"), - DataFixtures.CreateDictionary(3, "ab", "Value 3 B"), - DataFixtures.CreateDictionary(4, "b", "Value 4 B"), - }; + IList objlist = CreateObjList(); ArrayValue arrayValue = new ArrayValue(objlist); SortFilter sizeFilter = new SortFilter(new StringValue("field1")); @@ -56,7 +50,60 @@ public void It_Should_Sort_Dictionaries_By_Field() Assert.That(IdAt(result.SuccessValue(), 1, "field1").Value, Is.EqualTo("ab")); Assert.That(IdAt(result.SuccessValue(), 2, "field1").Value, Is.EqualTo("b")); Assert.That(IdAt(result.SuccessValue(), 3, "field1").Value, Is.EqualTo("Z")); + } + [Test] + public void It_Should_Sort_Dictionaries_By_Field_From_Template() + { + // Arrange + ITemplateContext ctx = new TemplateContext() + .WithAllFilters().DefineLocalVariable("arr", new ArrayValue(CreateObjList())); + + // Act + var result = RenderingHelper.RenderTemplate("Result : {% assign x = arr | sort: \"field1\" %}{{ x | map: \"field1\" }}", ctx); + + // Assert + Assert.That(result, Is.EqualTo("Result : AaabbZ")); + } + + [Test] + public void It_Should_Ignore_Dictionaries_With_Missing_Fields() + { + // Arrange + ITemplateContext ctx = new TemplateContext() + .WithAllFilters().DefineLocalVariable("arr", new ArrayValue(CreateObjList())); + + // Act + var result = RenderingHelper.RenderTemplate("Result : {% assign x = arr | sort: \"test\" %}{{ x | map: \"id\" }}", ctx); + + // Assert + Assert.That(result, Is.EqualTo("Result : 1234")); + } + + [Test] + public void It_Should_Error_With_Dictionaries_With_Missing_Fields_When_Errors_On() + { + // Arrange + ITemplateContext ctx = new TemplateContext().ErrorWhenValueMissing() + .WithAllFilters().DefineLocalVariable("arr", new ArrayValue(CreateObjList())); + + // Act + var result = RenderingHelper.RenderTemplate("Result : {% assign x = arr | sort: \"test\" %}", ctx); + + // Assert + Assert.That(result, Is.StringContaining("an array element is missing the field \'test\'")); + } + + + private IList CreateObjList() + { + return new List + { + DataFixtures.CreateDictionary(1, "Aa", "Value 1 B"), + DataFixtures.CreateDictionary(2, "Z", "Value 2 B"), + DataFixtures.CreateDictionary(3, "ab", "Value 3 B"), + DataFixtures.CreateDictionary(4, "b", "Value 4 B"), + }; } private static IExpressionConstant IdAt(ArrayValue result, int index, String field) diff --git a/Liquid.NET.Tests/Ruby/FilterTests.cs b/Liquid.NET.Tests/Ruby/FilterTests.cs index 4f2c9de..352dfad 100755 --- a/Liquid.NET.Tests/Ruby/FilterTests.cs +++ b/Liquid.NET.Tests/Ruby/FilterTests.cs @@ -55,6 +55,9 @@ public class FilterTests { [TestCase(@"{{ """" | append: x }}", @"", @"")] [TestCase(@"{{ nil | default: ""NIL TEST"" }}", @"", @"NIL TEST")] [TestCase(@"{{ """" | default: ""ES TEST"" }}", @"", @"ES TEST")] + [TestCase(@"{{ nil | map: ""test"" }}", @"", @"")] + [TestCase(@"{% assign myarray = ""1,2,3,4"" |split: "","" %}{{ myarray.x }}", @"", @"")] + [TestCase(@"{% assign myarray = ""1,2,3,4"" |split: "","" %}{{ myarray[x] }}", @"", @"")] public void It_Should_Match_Ruby_Output(String input, String assigns, String expected) { // Arrange diff --git a/Liquid.NET/Liquid.NET.csproj b/Liquid.NET/Liquid.NET.csproj index af1fe0e..c6b4301 100755 --- a/Liquid.NET/Liquid.NET.csproj +++ b/Liquid.NET/Liquid.NET.csproj @@ -80,6 +80,7 @@ + diff --git a/Liquid.NET/src/Constants/ArrayValue.cs b/Liquid.NET/src/Constants/ArrayValue.cs index 4922135..e6c4299 100755 --- a/Liquid.NET/src/Constants/ArrayValue.cs +++ b/Liquid.NET/src/Constants/ArrayValue.cs @@ -63,7 +63,7 @@ public override string ToString() //return "[ " + String.Join(", ", strs) + " ]"; // The Concatenated way: - var strs = ArrValue.Select(x => x.Value.ToString()); + var strs = ArrValue.Where(x => x.HasValue).Select(x => x.Value.ToString()); return String.Join("", strs); } } diff --git a/Liquid.NET/src/Constants/DictionaryValue.cs b/Liquid.NET/src/Constants/DictionaryValue.cs index 4522be3..00891b5 100755 --- a/Liquid.NET/src/Constants/DictionaryValue.cs +++ b/Liquid.NET/src/Constants/DictionaryValue.cs @@ -27,11 +27,9 @@ public override object Value public IDictionary> DictValue { get { return _value; } } - // TODO: not sure what this should do public override bool IsTrue { get { return _value != null; } - //get { return _value.Keys.Any(); } } public override string LiquidTypeName @@ -39,9 +37,19 @@ public override string LiquidTypeName get { return "hash"; } } + /// + /// This will return None if the key is missing, even if ErrorOnMissing is enabled. + /// + /// + /// public Option ValueAt(String key) { - return _value.ContainsKey(key) ? _value[key] : new None(); + return ContainsKey(key) ? _value[key] : new None(); + } + + public bool ContainsKey(string key) + { + return _value.ContainsKey(key); } public override string ToString() diff --git a/Liquid.NET/src/Constants/FieldAccessor.cs b/Liquid.NET/src/Constants/FieldAccessor.cs index 89fa3b5..92276b9 100755 --- a/Liquid.NET/src/Constants/FieldAccessor.cs +++ b/Liquid.NET/src/Constants/FieldAccessor.cs @@ -4,15 +4,22 @@ namespace Liquid.NET.Constants { public static class FieldAccessor { - public static Option TryField(IExpressionConstant expressionConstant, string stringVal) + public static LiquidExpressionResult TryField( + ITemplateContext ctx, + IExpressionConstant expressionConstant, + string index) { + var dict = expressionConstant as DictionaryValue; if (dict == null) { - return new None(); - + return LiquidExpressionResult.ErrorOrNone(ctx, index); + } - return dict.DictValue.ContainsKey(stringVal) ? dict.DictValue[stringVal] : new None(); + + return dict.DictValue.ContainsKey(index) + ? LiquidExpressionResult.Success(dict.DictValue[index]) + : LiquidExpressionResult.ErrorOrNone(ctx, index); } } } diff --git a/Liquid.NET/src/Constants/IndexDereferencer.cs b/Liquid.NET/src/Constants/IndexDereferencer.cs index c0da8d2..22fe8c6 100755 --- a/Liquid.NET/src/Constants/IndexDereferencer.cs +++ b/Liquid.NET/src/Constants/IndexDereferencer.cs @@ -42,15 +42,26 @@ public LiquidExpressionResult Lookup( private LiquidExpressionResult DoLookup(ITemplateContext ctx, ArrayValue arrayValue, IExpressionConstant indexProperty) { + bool errorOnEmpty = ctx.Options.ErrorWhenValueMissing && arrayValue.ArrValue.Count == 0; + + String propertyNameString = ValueCaster.RenderAsString(indexProperty); int index; if (propertyNameString.ToLower().Equals("first")) { + if (errorOnEmpty) + { + return LiquidExpressionResult.Error("cannot dereference empty array"); + } index = 0; } else if (propertyNameString.ToLower().Equals("last")) { + if (errorOnEmpty) + { + return LiquidExpressionResult.Error("cannot dereference empty array"); + } index = arrayValue.ArrValue.Count - 1; } else if (propertyNameString.ToLower().Equals("size")) @@ -59,20 +70,36 @@ private LiquidExpressionResult DoLookup(ITemplateContext ctx, ArrayValue arrayVa } else { - var maybeIndexResult = ValueCaster.Cast(indexProperty); - if (maybeIndexResult.IsError || !maybeIndexResult.SuccessResult.HasValue) - { - return LiquidExpressionResult.Error("invalid array index: " + propertyNameString); - } - else + var success = Int32.TryParse(propertyNameString, out index); + //var maybeIndexResult = ValueCaster.Cast(indexProperty); + + if (!success) { - index = maybeIndexResult.SuccessValue().IntValue; + if (ctx.Options.ErrorWhenValueMissing) + { + return LiquidExpressionResult.Error("invalid index: '" + propertyNameString + "'"); + } + else + { + return LiquidExpressionResult.Success(new None());// liquid seems to return nothing when non-int index. + } } + +// if (maybeIndexResult.IsError || !maybeIndexResult.SuccessResult.HasValue) +// { +// return LiquidExpressionResult.Error("invalid array index: " + propertyNameString); +// } +// else +// { +// index = maybeIndexResult.SuccessValue().IntValue; +// } } if (arrayValue.ArrValue.Count == 0) { - return LiquidExpressionResult.Success(new None()); // not an error in Ruby liquid. + return errorOnEmpty ? + LiquidExpressionResult.Error("cannot dereference empty array") : + LiquidExpressionResult.Success(new None()); } var result = arrayValue.ValueAt(index); @@ -95,9 +122,8 @@ private LiquidExpressionResult DoLookup(ITemplateContext ctx, DictionaryValue di } else { - return ctx.Options.ErrorWhenValueMissing - ? LiquidExpressionResult.Error(SymbolTable.NotFoundError(indexProperty.ToString())) - : LiquidExpressionResult.Success(new None()); + return LiquidExpressionResult.ErrorOrNone(ctx, indexProperty.ToString()); + } } diff --git a/Liquid.NET/src/Constants/MissingValueTest.cs b/Liquid.NET/src/Constants/MissingValueTest.cs new file mode 100755 index 0000000..2d95901 --- /dev/null +++ b/Liquid.NET/src/Constants/MissingValueTest.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Liquid.NET.src.Constants +{ + class MissingValueTest + { + } +} diff --git a/Liquid.NET/src/Filters/Array/MapFilter.cs b/Liquid.NET/src/Filters/Array/MapFilter.cs index e791043..58bd5b5 100755 --- a/Liquid.NET/src/Filters/Array/MapFilter.cs +++ b/Liquid.NET/src/Filters/Array/MapFilter.cs @@ -1,11 +1,12 @@ using System; using System.Linq; +using System.Security.Cryptography.X509Certificates; using Liquid.NET.Constants; using Liquid.NET.Utils; namespace Liquid.NET.Filters.Array { - public class MapFilter: FilterExpression// : FilterExpression + public class MapFilter: FilterExpression { private readonly StringValue _selector; @@ -22,24 +23,20 @@ public override LiquidExpressionResult ApplyTo(ITemplateContext ctx, IExpression public override LiquidExpressionResult ApplyTo(ITemplateContext ctx, ArrayValue liquidArrayExpression) { - if (liquidArrayExpression == null || liquidArrayExpression.Value == null) - { - return LiquidExpressionResult.Error("Array is nil"); - } - var list = liquidArrayExpression.ArrValue.Select(x => x.HasValue ? - FieldAccessor.TryField(x.Value, _selector.StringVal) : - new None()).ToList(); - return LiquidExpressionResult.Success(new ArrayValue(list)); + var list = liquidArrayExpression.ArrValue.Select(x => x.HasValue + ? FieldAccessor.TryField(ctx, x.Value, _selector.StringVal) + : LiquidExpressionResult.ErrorOrNone(ctx, _selector.StringVal)).ToList(); + //new None()).ToList(); + return list.Any(x => x.IsError) ? + list.First(x => x.IsError) : + LiquidExpressionResult.Success(new ArrayValue(list.Select(x => x.SuccessResult).ToList())); } public override LiquidExpressionResult ApplyTo(ITemplateContext ctx, DictionaryValue liquidDictionaryValue) { - if (liquidDictionaryValue == null || liquidDictionaryValue.Value == null) - { - return LiquidExpressionResult.Error("Hash is nil"); - } String propertyNameString = ValueCaster.RenderAsString((IExpressionConstant)_selector); - return LiquidExpressionResult.Success(liquidDictionaryValue.ValueAt(propertyNameString)); + + return LiquidExpressionResult.Success(liquidDictionaryValue.ValueAt(propertyNameString)); } diff --git a/Liquid.NET/src/Filters/Array/SortFilter.cs b/Liquid.NET/src/Filters/Array/SortFilter.cs index 8627ccc..43d3797 100755 --- a/Liquid.NET/src/Filters/Array/SortFilter.cs +++ b/Liquid.NET/src/Filters/Array/SortFilter.cs @@ -30,16 +30,20 @@ public override LiquidExpressionResult ApplyTo(ITemplateContext ctx, ArrayValue } else { - return LiquidExpressionResult.Success(SortByProperty(liquidArrayExpression, sortfield)); + return SortByProperty(ctx, liquidArrayExpression, sortfield); } } - private ArrayValue SortByProperty(ArrayValue val, string sortfield) + private LiquidExpressionResult SortByProperty(ITemplateContext ctx, ArrayValue val, string sortfield) { + if (ctx.Options.ErrorWhenValueMissing && + val.ArrValue.Any(x => FieldAccessor.TryField(ctx, x.Value, sortfield).IsError)) + { + return LiquidExpressionResult.Error("an array element is missing the field '" + sortfield + "'"); + } + var ordered = val.ArrValue.OrderBy(x => AsString(ctx, x, sortfield)); - var ordered = val.ArrValue.OrderBy(x => AsString(x, sortfield)); - // TODO: ThenBy - return new ArrayValue(ordered.ToList()); + return LiquidExpressionResult.Success(new ArrayValue(ordered.ToList())); } private static ArrayValue SortAsArrayOfStrings(ArrayValue val) @@ -48,13 +52,19 @@ private static ArrayValue SortAsArrayOfStrings(ArrayValue val) return new ArrayValue(result.ToList()); } - private String AsString(Option x, string field) + private String AsString(ITemplateContext ctx, Option x, string field) { if (!x.HasValue) { return ""; } - return ValueCaster.RenderAsString(FieldAccessor.TryField(x.Value, field)); + + var liquidExpressionResult = FieldAccessor.TryField(ctx, x.Value, field); + if (liquidExpressionResult.IsError || !liquidExpressionResult.SuccessResult.HasValue) + { + return ""; + } + return ValueCaster.RenderAsString(liquidExpressionResult.SuccessResult.Value); } } diff --git a/Liquid.NET/src/TemplateContext.cs b/Liquid.NET/src/TemplateContext.cs index 03bed04..5e64c9f 100755 --- a/Liquid.NET/src/TemplateContext.cs +++ b/Liquid.NET/src/TemplateContext.cs @@ -251,7 +251,7 @@ public ITemplateContext WithNoForLimit() return this; } - public TemplateContext ErrorWhenValueMissing() + public ITemplateContext ErrorWhenValueMissing() { _options.ErrorWhenValueMissing = true; return this; diff --git a/Liquid.NET/src/Utils/LiquidExpressionResult.cs b/Liquid.NET/src/Utils/LiquidExpressionResult.cs index 7740eef..6c60f88 100755 --- a/Liquid.NET/src/Utils/LiquidExpressionResult.cs +++ b/Liquid.NET/src/Utils/LiquidExpressionResult.cs @@ -1,5 +1,6 @@ using System; using Liquid.NET.Constants; +using Liquid.NET.Symbols; namespace Liquid.NET.Utils { @@ -14,6 +15,18 @@ internal LiquidExpressionResult(Option success) { } + public static LiquidExpressionResult ErrorOrNone(ITemplateContext ctx, String varname) + { + if (ctx.Options.ErrorWhenValueMissing) + { + return Error(SymbolTable.NotFoundError(varname)); + } + else + { + return Success(new None()); + } + } + public LiquidExpressionResult WhenError(Action fn) { if (IsLeft) diff --git a/Liquid.Ruby/tests/filters.txt b/Liquid.Ruby/tests/filters.txt index 95e24e9..345cd34 100755 --- a/Liquid.Ruby/tests/filters.txt +++ b/Liquid.Ruby/tests/filters.txt @@ -60,3 +60,9 @@ {{ nil | default: "NIL TEST" }} # {{ "" | default: "ES TEST" }} +# +{{ nil | map: "test" }} +# +{% assign myarray = "1,2,3,4" |split: "," %}{{ myarray.x }} +# +{% assign myarray = "1,2,3,4" |split: "," %}{{ myarray[x] }} \ No newline at end of file From 65a5fba80ed259ff19f009495cec036a9dea9cc0 Mon Sep 17 00:00:00 2001 From: Mike Bridge Date: Wed, 4 Nov 2015 23:50:47 -0700 Subject: [PATCH 4/4] remove unused parameter from nunit test --- Liquid.NET.Tests/Constants/MissingValueTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Liquid.NET.Tests/Constants/MissingValueTests.cs b/Liquid.NET.Tests/Constants/MissingValueTests.cs index d03063e..1de9719 100755 --- a/Liquid.NET.Tests/Constants/MissingValueTests.cs +++ b/Liquid.NET.Tests/Constants/MissingValueTests.cs @@ -53,7 +53,7 @@ public void It_Should_Display_An_Error_When_Dereferencing_Empty_Array(String var } [Test] - public void It_Should_Display_Error_When_Dereferencing_Array_With_Non_Int(String varname) + public void It_Should_Display_Error_When_Dereferencing_Array_With_Non_Int() { // Arrange ITemplateContext ctx = new TemplateContext()