@@ -606,7 +606,7 @@ find_position_in_text(
606
606
if (res.line == 1 )
607
607
res.column = res.pos ;
608
608
else
609
- res.column = res.pos - text.rfind (' \n ' , res.pos );
609
+ res.column = res.pos - text.rfind (' \n ' , res.pos ) - 1 ;
610
610
}
611
611
return res;
612
612
}
@@ -648,7 +648,8 @@ std::pair<dom::Value, bool>
648
648
lookupPropertyImpl (
649
649
dom::Object const & context,
650
650
std::string_view path,
651
- detail::RenderState const & state)
651
+ detail::RenderState const & state,
652
+ HandlebarsOptions const & opt)
652
653
{
653
654
// Get first value from Object
654
655
std::string_view segment = popFirstSegment (path);
@@ -661,7 +662,21 @@ lookupPropertyImpl(
661
662
}
662
663
else if (!context.exists (literalSegment))
663
664
{
664
- return {nullptr , false };
665
+ if (opt.strict || (opt.assumeObjects && !path.empty ()))
666
+ {
667
+ std::string msg = fmt::format (
668
+ " \" {}\" not defined in {}" , literalSegment, toString (context));
669
+ auto res = find_position_in_text (state.templateText0 , literalSegment);
670
+ if (res)
671
+ {
672
+ throw HandlebarsError (msg, res.line , res.column , res.pos );
673
+ }
674
+ throw HandlebarsError (msg);
675
+ }
676
+ else
677
+ {
678
+ return {nullptr , false };
679
+ }
665
680
}
666
681
else
667
682
{
@@ -679,9 +694,27 @@ lookupPropertyImpl(
679
694
{
680
695
auto obj = cur.getObject ();
681
696
if (obj.exists (literalSegment))
697
+ {
682
698
cur = obj.find (literalSegment);
699
+ }
683
700
else
684
- return {nullptr , false };
701
+ {
702
+ if (opt.strict )
703
+ {
704
+ std::string msg = fmt::format (
705
+ " \" {}\" not defined in {}" , literalSegment, toString (cur));
706
+ auto res = find_position_in_text (state.templateText0 , literalSegment);
707
+ if (res)
708
+ {
709
+ throw HandlebarsError (msg, res.line , res.column , res.pos );
710
+ }
711
+ throw HandlebarsError (msg);
712
+ }
713
+ else
714
+ {
715
+ return {nullptr , false };
716
+ }
717
+ }
685
718
}
686
719
// If current value is an Array, get the next value the stripped index
687
720
else if (cur.isArray ())
@@ -717,7 +750,9 @@ std::pair<dom::Value, bool>
717
750
lookupPropertyImpl (
718
751
dom::Value const & context,
719
752
std::string_view path,
720
- detail::RenderState const & state) {
753
+ detail::RenderState const & state,
754
+ HandlebarsOptions const & opt)
755
+ {
721
756
checkPath (path, state);
722
757
// ==============================================================
723
758
// "." / "this"
@@ -728,40 +763,52 @@ lookupPropertyImpl(
728
763
// Non-object key
729
764
// ==============================================================
730
765
if (context.kind () != dom::Kind::Object) {
766
+ if (opt.strict || opt.assumeObjects )
767
+ {
768
+ std::string msg = fmt::format (" \" {}\" not defined in {}" , path, context);
769
+ auto res = find_position_in_text (state.templateText0 , path);
770
+ if (res)
771
+ {
772
+ throw HandlebarsError (msg, res.line , res.column , res.pos );
773
+ }
774
+ throw HandlebarsError (msg);
775
+ }
731
776
return {nullptr , false };
732
777
}
733
778
// ==============================================================
734
779
// Object path
735
780
// ==============================================================
736
- return lookupPropertyImpl (context.getObject (), path, state);
781
+ return lookupPropertyImpl (context.getObject (), path, state, opt );
737
782
}
738
783
739
784
template <std::convertible_to<std::string_view> S>
740
785
std::pair<dom::Value, bool >
741
786
lookupPropertyImpl (
742
787
dom::Value const & data,
743
788
S const & path,
744
- detail::RenderState const & state)
789
+ detail::RenderState const & state,
790
+ HandlebarsOptions const & opt)
745
791
{
746
- return lookupPropertyImpl (data, std::string_view (path), state);
792
+ return lookupPropertyImpl (data, std::string_view (path), state, opt );
747
793
}
748
794
749
795
std::pair<dom::Value, bool >
750
796
lookupPropertyImpl (
751
797
dom::Value const & context,
752
798
dom::Value const & path,
753
- detail::RenderState const & state)
799
+ detail::RenderState const & state,
800
+ HandlebarsOptions const & opt)
754
801
{
755
802
if (path.isString ())
756
- return lookupPropertyImpl (context, path.getString (), state);
803
+ return lookupPropertyImpl (context, path.getString (), state, opt );
757
804
if (path.isInteger ()) {
758
805
if (context.isArray ()) {
759
806
auto & arr = context.getArray ();
760
807
if (path.getInteger () >= static_cast <std::int64_t >(arr.size ()))
761
808
return {nullptr , false };
762
809
return {arr.at (path.getInteger ()), true };
763
810
}
764
- return lookupPropertyImpl (context, std::to_string (path.getInteger ()), state);
811
+ return lookupPropertyImpl (context, std::to_string (path.getInteger ()), state, opt );
765
812
}
766
813
return {nullptr , false };
767
814
}
@@ -772,7 +819,7 @@ lookupProperty(
772
819
dom::Value const & context,
773
820
dom::Value const & path) const
774
821
{
775
- return lookupPropertyImpl (context, path, *renderState_);
822
+ return lookupPropertyImpl (context, path, *renderState_, *opt_ );
776
823
}
777
824
778
825
@@ -1679,12 +1726,15 @@ evalExpr(
1679
1726
break ;
1680
1727
}
1681
1728
}
1682
- auto [res, found] = lookupPropertyImpl (data, expression, state);
1729
+ auto [res, found] = lookupPropertyImpl (data, expression, state, opt );
1683
1730
return {res, found, false };
1684
1731
}
1685
1732
// ==============================================================
1686
1733
// Dotdot context path
1687
1734
// ==============================================================
1735
+ HandlebarsOptions noStrict = opt;
1736
+ noStrict.strict = false ;
1737
+ noStrict.assumeObjects = false ;
1688
1738
if (expression.starts_with (" .." )) {
1689
1739
// Get value from parent helper contexts
1690
1740
std::size_t dotdots = 1 ;
@@ -1704,7 +1754,7 @@ evalExpr(
1704
1754
}
1705
1755
dom::Value parentCtx =
1706
1756
state.parentContext [state.parentContext .size () - dotdots];
1707
- auto [res, found] = lookupPropertyImpl (parentCtx, expression, state);
1757
+ auto [res, found] = lookupPropertyImpl (parentCtx, expression, state, noStrict );
1708
1758
return {res, found, false };
1709
1759
}
1710
1760
// ==============================================================
@@ -1730,7 +1780,7 @@ evalExpr(
1730
1780
bool defined;
1731
1781
if (isPathedValue)
1732
1782
{
1733
- std::tie (r, defined) = lookupPropertyImpl (context, expression, state);
1783
+ std::tie (r, defined) = lookupPropertyImpl (context, expression, state, noStrict );
1734
1784
if (defined) {
1735
1785
return {r, defined, false };
1736
1786
}
@@ -1739,7 +1789,7 @@ evalExpr(
1739
1789
// ==============================================================
1740
1790
// Block values
1741
1791
// ==============================================================
1742
- std::tie (r, defined) = lookupPropertyImpl (state.blockValues , expression, state);
1792
+ std::tie (r, defined) = lookupPropertyImpl (state.blockValues , expression, state, noStrict );
1743
1793
if (defined)
1744
1794
{
1745
1795
return {r, defined, false , false , true };
@@ -1759,7 +1809,10 @@ evalExpr(
1759
1809
// ==============================================================
1760
1810
// Context values
1761
1811
// ==============================================================
1762
- std::tie (r, defined) = lookupPropertyImpl (context, expression, state);
1812
+ HandlebarsOptions strictOpt = opt;
1813
+ strictOpt.strict = opt.strict && !opt.compat ;
1814
+ strictOpt.assumeObjects = opt.assumeObjects && !opt.compat ;
1815
+ std::tie (r, defined) = lookupPropertyImpl (context, expression, state, strictOpt);
1763
1816
if (defined) {
1764
1817
return {r, defined, false };
1765
1818
}
@@ -1772,13 +1825,19 @@ evalExpr(
1772
1825
auto parentContexts = std::ranges::views::reverse (state.parentContext );
1773
1826
for (auto parentContext: parentContexts)
1774
1827
{
1775
- std::tie (r, defined) = lookupPropertyImpl (parentContext, expression, state);
1828
+ std::tie (r, defined) = lookupPropertyImpl (parentContext, expression, state, noStrict );
1776
1829
if (defined)
1777
1830
{
1778
1831
return {r, defined, false };
1779
1832
}
1780
1833
}
1781
1834
}
1835
+
1836
+ if (opt.strict )
1837
+ {
1838
+ std::string msg = fmt::format (" \" {}\" not defined" , expression);
1839
+ throw HandlebarsError (msg);
1840
+ }
1782
1841
return {nullptr , false , false };
1783
1842
}
1784
1843
@@ -2093,7 +2152,9 @@ renderExpression(
2093
2152
cb.context_ = &context;
2094
2153
cb.data_ = &state.data ;
2095
2154
cb.logger_ = &logger_;
2096
- setupArgs (tag.arguments , context, state, args, cb, opt);
2155
+ HandlebarsOptions noStrict = opt;
2156
+ noStrict.strict = false ;
2157
+ setupArgs (tag.arguments , context, state, args, cb, noStrict);
2097
2158
auto [res, render] = fn (args, cb);
2098
2159
if (render == HelperBehavior::RENDER_RESULT) {
2099
2160
format_to (out, res, opt2);
@@ -2125,7 +2186,9 @@ renderExpression(
2125
2186
dom::Array args = dom::newArray<dom::DefaultArrayImpl>();
2126
2187
HandlebarsCallback cb;
2127
2188
cb.name_ = helper_expr;
2128
- setupArgs (tag.arguments , context, state, args, cb, opt);
2189
+ HandlebarsOptions noStrict = opt;
2190
+ noStrict.strict = false ;
2191
+ setupArgs (tag.arguments , context, state, args, cb, noStrict);
2129
2192
auto v2 = resV.value .getFunction ().call (args).value ();
2130
2193
format_to (out, v2, opt2);
2131
2194
}
@@ -2135,6 +2198,11 @@ renderExpression(
2135
2198
}
2136
2199
return ;
2137
2200
}
2201
+ else if (opt.strict )
2202
+ {
2203
+ std::string msg = fmt::format (" \" {}\" not defined in {}" , helper_expr, toString (context));
2204
+ throw HandlebarsError (msg);
2205
+ }
2138
2206
2139
2207
// ==============================================================
2140
2208
// helperMissing hook
@@ -2252,6 +2320,7 @@ setupArgs(
2252
2320
}
2253
2321
}
2254
2322
cb.renderState_ = &state;
2323
+ cb.opt_ = &opt;
2255
2324
}
2256
2325
2257
2326
void
@@ -2604,7 +2673,20 @@ renderBlock(
2604
2673
arguments = tag.helper ;
2605
2674
}
2606
2675
}
2607
- setupArgs (arguments, context, state, args, cb, opt);
2676
+ else if (opt.strict && !found)
2677
+ {
2678
+ std::string msg = fmt::format (
2679
+ " \" {}\" not defined in {}" , tag.helper , toString (context));
2680
+ auto res = find_position_in_text (state.templateText0 , tag.helper );
2681
+ if (res)
2682
+ {
2683
+ throw HandlebarsError (msg, res.line , res.column , res.pos );
2684
+ }
2685
+ throw HandlebarsError (msg);
2686
+ }
2687
+ HandlebarsOptions noStrict = opt;
2688
+ noStrict.strict = opt.strict && emulateMustache;
2689
+ setupArgs (arguments, context, state, args, cb, noStrict);
2608
2690
2609
2691
// ==============================================================
2610
2692
// Setup block parameters
0 commit comments