@formfeed/engine 0.3.14 → 0.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs CHANGED
@@ -229,6 +229,15 @@ var EngineSyntaxError = class extends Error {
229
229
  return { line: this.line, column: this.column };
230
230
  }
231
231
  };
232
+ function notText(engine, source) {
233
+ const kind = source === null || source === void 0 ? String(source) : Array.isArray(source) ? "an array" : typeof source === "object" ? "an object" : `a ${typeof source}`;
234
+ return new EngineSyntaxError(
235
+ engine,
236
+ `A template must be text, not ${kind}`,
237
+ 1,
238
+ 1
239
+ );
240
+ }
232
241
  var RenderLimitError = class extends Error {
233
242
  constructor(limit, message) {
234
243
  super(message);
@@ -1842,7 +1851,8 @@ var builtinRoots = /* @__PURE__ */ new Set([
1842
1851
  ]);
1843
1852
  function withBrand(data, ctx) {
1844
1853
  const root = data ?? {};
1845
- if (!ctx.brand || typeof root !== "object" || Array.isArray(root)) return root;
1854
+ if (!ctx.brand || typeof root !== "object" || Array.isArray(root))
1855
+ return root;
1846
1856
  return { brand: ctx.brand, ...root };
1847
1857
  }
1848
1858
  function createInstance(ctx) {
@@ -2141,6 +2151,11 @@ var HandlebarsCompiled = class {
2141
2151
  }
2142
2152
  const src = ctx.partials(inc.name);
2143
2153
  if (src === void 0) continue;
2154
+ if (typeof src !== "string")
2155
+ throw new RenderError(
2156
+ "handlebars",
2157
+ `Partial "${inc.name}" is not text`
2158
+ );
2144
2159
  partials[inc.name] = src;
2145
2160
  collect(src, depth + 1);
2146
2161
  }
@@ -2168,6 +2183,7 @@ var HandlebarsCompiled = class {
2168
2183
  var handlebarsEngine = {
2169
2184
  id: "handlebars",
2170
2185
  compile(source, opts = {}) {
2186
+ if (typeof source !== "string") throw notText("handlebars", source);
2171
2187
  const name = opts.name ?? "template";
2172
2188
  try {
2173
2189
  createInstance(validationContext).precompile(source, { strict: false });
@@ -2436,7 +2452,8 @@ var stringMethods = {
2436
2452
  };
2437
2453
  function keywordArgs(args) {
2438
2454
  const last = args.at(-1);
2439
- if (!last || typeof last !== "object" || !Object.prototype.hasOwnProperty.call(last, "__keywords")) return void 0;
2455
+ if (!last || typeof last !== "object" || !Object.prototype.hasOwnProperty.call(last, "__keywords"))
2456
+ return void 0;
2440
2457
  const { __keywords: _marker, ...named } = last;
2441
2458
  return named;
2442
2459
  }
@@ -2450,11 +2467,15 @@ function namespace(...args) {
2450
2467
  return ns;
2451
2468
  }
2452
2469
  function setNamespaceAttribute(target, key, value) {
2453
- if (!(target instanceof JinjaNamespace)) throw new Error("Cannot assign attribute on non-namespace object");
2470
+ if (!(target instanceof JinjaNamespace))
2471
+ throw new Error("Cannot assign attribute on non-namespace object");
2454
2472
  target[String(key)] = value;
2455
2473
  }
2456
2474
  var toList = (v) => Array.isArray(v) ? v : v === void 0 || v === null ? [] : typeof v === "string" ? [...v] : [v];
2457
- var attributeOf = (item, path) => String(path ?? "").split(".").reduce((acc, key) => acc !== null && typeof acc === "object" ? acc[key] : void 0, item);
2475
+ var attributeOf = (item, path) => String(path ?? "").split(".").reduce(
2476
+ (acc, key) => acc !== null && typeof acc === "object" ? acc[key] : void 0,
2477
+ item
2478
+ );
2458
2479
  var jinjaTests = {
2459
2480
  none: (v) => v === null,
2460
2481
  true: (v) => v === true,
@@ -2475,50 +2496,65 @@ function runTest(self, name, value, arg) {
2475
2496
  function installJinjaFilters(env) {
2476
2497
  const lookup = env;
2477
2498
  const helperTruncate = lookup.getFilter("truncate");
2478
- lookup.addFilter("truncate", function(value, ...args) {
2479
- const named = keywordArgs(args);
2480
- const p = named ? args.slice(0, -1) : args;
2481
- if (!named && typeof p[1] !== "boolean" && p.length < 3) return helperTruncate.call(this, value, ...args);
2482
- const s = String(value ?? "");
2483
- const length = Number(p[0] ?? named?.["length"] ?? 255);
2484
- const killwords = Boolean(p[1] ?? named?.["killwords"] ?? false);
2485
- const end = String(p[2] ?? named?.["end"] ?? "...");
2486
- const leeway = Number(p[3] ?? named?.["leeway"] ?? 5);
2487
- if (s.length <= length + leeway) return s;
2488
- const cut = s.slice(0, Math.max(0, length - end.length));
2489
- if (killwords) return cut + end;
2490
- const space = cut.lastIndexOf(" ");
2491
- return (space >= 0 ? cut.slice(0, space) : cut) + end;
2492
- });
2499
+ lookup.addFilter(
2500
+ "truncate",
2501
+ function(value, ...args) {
2502
+ const named = keywordArgs(args);
2503
+ const p = named ? args.slice(0, -1) : args;
2504
+ if (!named && typeof p[1] !== "boolean" && p.length < 3)
2505
+ return helperTruncate.call(this, value, ...args);
2506
+ const s = String(value ?? "");
2507
+ const length = Number(p[0] ?? named?.["length"] ?? 255);
2508
+ const killwords = Boolean(p[1] ?? named?.["killwords"] ?? false);
2509
+ const end = String(p[2] ?? named?.["end"] ?? "...");
2510
+ const leeway = Number(p[3] ?? named?.["leeway"] ?? 5);
2511
+ if (s.length <= length + leeway) return s;
2512
+ const cut = s.slice(0, Math.max(0, length - end.length));
2513
+ if (killwords) return cut + end;
2514
+ const space = cut.lastIndexOf(" ");
2515
+ return (space >= 0 ? cut.slice(0, space) : cut) + end;
2516
+ }
2517
+ );
2493
2518
  const helperSum = lookup.getFilter("sum");
2494
- lookup.addFilter("sum", function(value, ...args) {
2495
- const named = keywordArgs(args);
2496
- if (!named) return helperSum.call(this, value, ...args);
2497
- const p = args.slice(0, -1);
2498
- const total = helperSum.call(this, value, p[0] ?? named["attribute"]);
2499
- return total + Number(p[1] ?? named["start"] ?? 0);
2500
- });
2519
+ lookup.addFilter(
2520
+ "sum",
2521
+ function(value, ...args) {
2522
+ const named = keywordArgs(args);
2523
+ if (!named) return helperSum.call(this, value, ...args);
2524
+ const p = args.slice(0, -1);
2525
+ const total = helperSum.call(
2526
+ this,
2527
+ value,
2528
+ p[0] ?? named["attribute"]
2529
+ );
2530
+ return total + Number(p[1] ?? named["start"] ?? 0);
2531
+ }
2532
+ );
2501
2533
  const helperMap = lookup.getFilter("map");
2502
- lookup.addFilter("map", function(value, ...args) {
2503
- const named = keywordArgs(args);
2504
- const p = named ? args.slice(0, -1) : args;
2505
- if (named && "attribute" in named)
2506
- return toList(value).map((item) => {
2507
- const v = attributeOf(item, named["attribute"]);
2508
- return v === void 0 && "default" in named ? named["default"] : v;
2509
- });
2510
- const items = toList(value);
2511
- if (typeof p[0] === "string" && !items.some((item) => item !== null && typeof item === "object")) {
2512
- let filter;
2513
- try {
2514
- filter = lookup.getFilter(p[0]);
2515
- } catch {
2516
- filter = void 0;
2534
+ lookup.addFilter(
2535
+ "map",
2536
+ function(value, ...args) {
2537
+ const named = keywordArgs(args);
2538
+ const p = named ? args.slice(0, -1) : args;
2539
+ if (named && "attribute" in named)
2540
+ return toList(value).map((item) => {
2541
+ const v = attributeOf(item, named["attribute"]);
2542
+ return v === void 0 && "default" in named ? named["default"] : v;
2543
+ });
2544
+ const items = toList(value);
2545
+ if (typeof p[0] === "string" && !items.some((item) => item !== null && typeof item === "object")) {
2546
+ let filter;
2547
+ try {
2548
+ filter = lookup.getFilter(p[0]);
2549
+ } catch {
2550
+ filter = void 0;
2551
+ }
2552
+ if (filter)
2553
+ return items.map((item) => filter.call(this, item, ...p.slice(1)));
2517
2554
  }
2518
- if (filter) return items.map((item) => filter.call(this, item, ...p.slice(1)));
2555
+ return helperMap.call(this, value, ...args);
2519
2556
  }
2520
- return helperMap.call(this, value, ...args);
2521
- });
2557
+ );
2522
2558
  const byAttribute = (keep) => function(value, attribute2, test, arg) {
2523
2559
  return toList(value).filter((item) => {
2524
2560
  const v = attributeOf(item, attribute2);
@@ -2547,14 +2583,16 @@ function pythonRepr(v) {
2547
2583
  if (v === null || v === void 0) return "None";
2548
2584
  if (v === true) return "True";
2549
2585
  if (v === false) return "False";
2550
- if (typeof v === "number") return Number.isNaN(v) ? "nan" : Number.isFinite(v) ? String(v) : v > 0 ? "inf" : "-inf";
2586
+ if (typeof v === "number")
2587
+ return Number.isNaN(v) ? "nan" : Number.isFinite(v) ? String(v) : v > 0 ? "inf" : "-inf";
2551
2588
  if (typeof v === "string") {
2552
2589
  const quote2 = v.includes("'") && !v.includes('"') ? '"' : "'";
2553
2590
  const escaped = v.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
2554
2591
  return quote2 + (quote2 === "'" ? escaped.replace(/'/g, "\\'") : escaped) + quote2;
2555
2592
  }
2556
2593
  if (Array.isArray(v)) return `[${v.map(pythonRepr).join(", ")}]`;
2557
- if (isPlainObject(v)) return `{${Object.entries(v).map(([k, item]) => `${pythonRepr(k)}: ${pythonRepr(item)}`).join(", ")}}`;
2594
+ if (isPlainObject(v))
2595
+ return `{${Object.entries(v).map(([k, item]) => `${pythonRepr(k)}: ${pythonRepr(item)}`).join(", ")}}`;
2558
2596
  return String(v);
2559
2597
  }
2560
2598
  var compatInstalled = false;
@@ -2571,7 +2609,8 @@ function installCompat() {
2571
2609
  import_nunjucks.default.runtime["formfeedSetAttribute"] = setNamespaceAttribute;
2572
2610
  import_nunjucks.default.compiler.Compiler.prototype.compileSet = function(node, frame) {
2573
2611
  const [target, ...more] = node.targets;
2574
- if (!target || more.length || target.typename !== "LookupVal") return compileSet.call(this, node, frame);
2612
+ if (!target || more.length || target.typename !== "LookupVal")
2613
+ return compileSet.call(this, node, frame);
2575
2614
  this._emit("runtime.formfeedSetAttribute(");
2576
2615
  this._compileExpression(target["target"], frame);
2577
2616
  this._emit(", ");
@@ -2582,7 +2621,10 @@ function installCompat() {
2582
2621
  this._emitLine(");");
2583
2622
  };
2584
2623
  const suppressValue = import_nunjucks.default.runtime.suppressValue;
2585
- import_nunjucks.default.runtime.suppressValue = (val2, autoescape) => suppressValue(Array.isArray(val2) || isPlainObject(val2) ? pythonRepr(val2) : val2, autoescape);
2624
+ import_nunjucks.default.runtime.suppressValue = (val2, autoescape) => suppressValue(
2625
+ Array.isArray(val2) || isPlainObject(val2) ? pythonRepr(val2) : val2,
2626
+ autoescape
2627
+ );
2586
2628
  const original = import_nunjucks.default.runtime.memberLookup;
2587
2629
  import_nunjucks.default.runtime.memberLookup = (obj, val2, ...rest) => {
2588
2630
  if (typeof obj === "string" && typeof val2 === "string" && Object.prototype.hasOwnProperty.call(stringMethods, val2)) {
@@ -2686,7 +2728,8 @@ function walk2(node, w) {
2686
2728
  case "Symbol":
2687
2729
  case "LookupVal": {
2688
2730
  const p = pathOf(node);
2689
- if (node.typename === "Symbol" && jinjaLiterals.has(String(node.value))) return;
2731
+ if (node.typename === "Symbol" && jinjaLiterals.has(String(node.value)))
2732
+ return;
2690
2733
  if (p) {
2691
2734
  const root = p.path[0] ?? "";
2692
2735
  const bound = inScope(w, root);
@@ -2741,7 +2784,9 @@ function walk2(node, w) {
2741
2784
  const nameNode = node["name"];
2742
2785
  const names = nameNode.typename === "Array" ? nameNode.children ?? [] : [nameNode];
2743
2786
  const root = source?.path[0] ?? "";
2744
- const items = !source ? null : inScope(w, root) ? ((s) => s ? [...s, ...source.path.slice(1)] : null)(sourceOf(w, root)) : source.path;
2787
+ const items = !source ? null : inScope(w, root) ? ((s) => s ? [...s, ...source.path.slice(1)] : null)(
2788
+ sourceOf(w, root)
2789
+ ) : source.path;
2745
2790
  const scope = /* @__PURE__ */ new Map();
2746
2791
  for (const n of names) {
2747
2792
  const name = String(n.value);
@@ -2769,7 +2814,8 @@ function walk2(node, w) {
2769
2814
  range: nodeRange(p.root, p.path.join(".").length),
2770
2815
  kind: "assigned"
2771
2816
  });
2772
- if (p.path.length === 1 || !inScope(w, p.path[0] ?? "")) w.scopes[0]?.set(p.path[0] ?? "", null);
2817
+ if (p.path.length === 1 || !inScope(w, p.path[0] ?? ""))
2818
+ w.scopes[0]?.set(p.path[0] ?? "", null);
2773
2819
  }
2774
2820
  }
2775
2821
  walk2(node["value"], w);
@@ -2960,6 +3006,7 @@ var Jinja2Template = class {
2960
3006
  var jinja2Engine = {
2961
3007
  id: "jinja2",
2962
3008
  compile(source, opts = {}) {
3009
+ if (typeof source !== "string") throw notText("jinja2", source);
2963
3010
  const name = opts.name ?? "template";
2964
3011
  try {
2965
3012
  installCompat();
@@ -3129,12 +3176,18 @@ function createLiquid(ctx) {
3129
3176
  parseLimit: 2e6
3130
3177
  });
3131
3178
  for (const [name, { fn }] of ctx.helpers.bind(ctx))
3132
- liquid.registerFilter(name, function(input, ...argv) {
3133
- const tokens = this.token?.args ?? [];
3134
- return fn(
3135
- ...collectKeywordArgs([input, ...argv], (i) => i > 0 && Array.isArray(tokens[i - 1]))
3136
- );
3137
- });
3179
+ liquid.registerFilter(
3180
+ name,
3181
+ function(input, ...argv) {
3182
+ const tokens = this.token?.args ?? [];
3183
+ return fn(
3184
+ ...collectKeywordArgs(
3185
+ [input, ...argv],
3186
+ (i) => i > 0 && Array.isArray(tokens[i - 1])
3187
+ )
3188
+ );
3189
+ }
3190
+ );
3138
3191
  return liquid;
3139
3192
  }
3140
3193
  var validationContext3 = {
@@ -3368,6 +3421,7 @@ var LiquidCompiled = class {
3368
3421
  var liquidEngine = {
3369
3422
  id: "liquid",
3370
3423
  compile(source, opts = {}) {
3424
+ if (typeof source !== "string") throw notText("liquid", source);
3371
3425
  const name = opts.name ?? "template";
3372
3426
  try {
3373
3427
  createLiquid(validationContext3).parse(source, name);
package/index.js CHANGED
@@ -11,6 +11,15 @@ var EngineSyntaxError = class extends Error {
11
11
  return { line: this.line, column: this.column };
12
12
  }
13
13
  };
14
+ function notText(engine, source) {
15
+ const kind = source === null || source === void 0 ? String(source) : Array.isArray(source) ? "an array" : typeof source === "object" ? "an object" : `a ${typeof source}`;
16
+ return new EngineSyntaxError(
17
+ engine,
18
+ `A template must be text, not ${kind}`,
19
+ 1,
20
+ 1
21
+ );
22
+ }
14
23
  var RenderLimitError = class extends Error {
15
24
  constructor(limit, message) {
16
25
  super(message);
@@ -1624,7 +1633,8 @@ var builtinRoots = /* @__PURE__ */ new Set([
1624
1633
  ]);
1625
1634
  function withBrand(data, ctx) {
1626
1635
  const root = data ?? {};
1627
- if (!ctx.brand || typeof root !== "object" || Array.isArray(root)) return root;
1636
+ if (!ctx.brand || typeof root !== "object" || Array.isArray(root))
1637
+ return root;
1628
1638
  return { brand: ctx.brand, ...root };
1629
1639
  }
1630
1640
  function createInstance(ctx) {
@@ -1923,6 +1933,11 @@ var HandlebarsCompiled = class {
1923
1933
  }
1924
1934
  const src = ctx.partials(inc.name);
1925
1935
  if (src === void 0) continue;
1936
+ if (typeof src !== "string")
1937
+ throw new RenderError(
1938
+ "handlebars",
1939
+ `Partial "${inc.name}" is not text`
1940
+ );
1926
1941
  partials[inc.name] = src;
1927
1942
  collect(src, depth + 1);
1928
1943
  }
@@ -1950,6 +1965,7 @@ var HandlebarsCompiled = class {
1950
1965
  var handlebarsEngine = {
1951
1966
  id: "handlebars",
1952
1967
  compile(source, opts = {}) {
1968
+ if (typeof source !== "string") throw notText("handlebars", source);
1953
1969
  const name = opts.name ?? "template";
1954
1970
  try {
1955
1971
  createInstance(validationContext).precompile(source, { strict: false });
@@ -2218,7 +2234,8 @@ var stringMethods = {
2218
2234
  };
2219
2235
  function keywordArgs(args) {
2220
2236
  const last = args.at(-1);
2221
- if (!last || typeof last !== "object" || !Object.prototype.hasOwnProperty.call(last, "__keywords")) return void 0;
2237
+ if (!last || typeof last !== "object" || !Object.prototype.hasOwnProperty.call(last, "__keywords"))
2238
+ return void 0;
2222
2239
  const { __keywords: _marker, ...named } = last;
2223
2240
  return named;
2224
2241
  }
@@ -2232,11 +2249,15 @@ function namespace(...args) {
2232
2249
  return ns;
2233
2250
  }
2234
2251
  function setNamespaceAttribute(target, key, value) {
2235
- if (!(target instanceof JinjaNamespace)) throw new Error("Cannot assign attribute on non-namespace object");
2252
+ if (!(target instanceof JinjaNamespace))
2253
+ throw new Error("Cannot assign attribute on non-namespace object");
2236
2254
  target[String(key)] = value;
2237
2255
  }
2238
2256
  var toList = (v) => Array.isArray(v) ? v : v === void 0 || v === null ? [] : typeof v === "string" ? [...v] : [v];
2239
- var attributeOf = (item, path) => String(path ?? "").split(".").reduce((acc, key) => acc !== null && typeof acc === "object" ? acc[key] : void 0, item);
2257
+ var attributeOf = (item, path) => String(path ?? "").split(".").reduce(
2258
+ (acc, key) => acc !== null && typeof acc === "object" ? acc[key] : void 0,
2259
+ item
2260
+ );
2240
2261
  var jinjaTests = {
2241
2262
  none: (v) => v === null,
2242
2263
  true: (v) => v === true,
@@ -2257,50 +2278,65 @@ function runTest(self, name, value, arg) {
2257
2278
  function installJinjaFilters(env) {
2258
2279
  const lookup = env;
2259
2280
  const helperTruncate = lookup.getFilter("truncate");
2260
- lookup.addFilter("truncate", function(value, ...args) {
2261
- const named = keywordArgs(args);
2262
- const p = named ? args.slice(0, -1) : args;
2263
- if (!named && typeof p[1] !== "boolean" && p.length < 3) return helperTruncate.call(this, value, ...args);
2264
- const s = String(value ?? "");
2265
- const length = Number(p[0] ?? named?.["length"] ?? 255);
2266
- const killwords = Boolean(p[1] ?? named?.["killwords"] ?? false);
2267
- const end = String(p[2] ?? named?.["end"] ?? "...");
2268
- const leeway = Number(p[3] ?? named?.["leeway"] ?? 5);
2269
- if (s.length <= length + leeway) return s;
2270
- const cut = s.slice(0, Math.max(0, length - end.length));
2271
- if (killwords) return cut + end;
2272
- const space = cut.lastIndexOf(" ");
2273
- return (space >= 0 ? cut.slice(0, space) : cut) + end;
2274
- });
2281
+ lookup.addFilter(
2282
+ "truncate",
2283
+ function(value, ...args) {
2284
+ const named = keywordArgs(args);
2285
+ const p = named ? args.slice(0, -1) : args;
2286
+ if (!named && typeof p[1] !== "boolean" && p.length < 3)
2287
+ return helperTruncate.call(this, value, ...args);
2288
+ const s = String(value ?? "");
2289
+ const length = Number(p[0] ?? named?.["length"] ?? 255);
2290
+ const killwords = Boolean(p[1] ?? named?.["killwords"] ?? false);
2291
+ const end = String(p[2] ?? named?.["end"] ?? "...");
2292
+ const leeway = Number(p[3] ?? named?.["leeway"] ?? 5);
2293
+ if (s.length <= length + leeway) return s;
2294
+ const cut = s.slice(0, Math.max(0, length - end.length));
2295
+ if (killwords) return cut + end;
2296
+ const space = cut.lastIndexOf(" ");
2297
+ return (space >= 0 ? cut.slice(0, space) : cut) + end;
2298
+ }
2299
+ );
2275
2300
  const helperSum = lookup.getFilter("sum");
2276
- lookup.addFilter("sum", function(value, ...args) {
2277
- const named = keywordArgs(args);
2278
- if (!named) return helperSum.call(this, value, ...args);
2279
- const p = args.slice(0, -1);
2280
- const total = helperSum.call(this, value, p[0] ?? named["attribute"]);
2281
- return total + Number(p[1] ?? named["start"] ?? 0);
2282
- });
2301
+ lookup.addFilter(
2302
+ "sum",
2303
+ function(value, ...args) {
2304
+ const named = keywordArgs(args);
2305
+ if (!named) return helperSum.call(this, value, ...args);
2306
+ const p = args.slice(0, -1);
2307
+ const total = helperSum.call(
2308
+ this,
2309
+ value,
2310
+ p[0] ?? named["attribute"]
2311
+ );
2312
+ return total + Number(p[1] ?? named["start"] ?? 0);
2313
+ }
2314
+ );
2283
2315
  const helperMap = lookup.getFilter("map");
2284
- lookup.addFilter("map", function(value, ...args) {
2285
- const named = keywordArgs(args);
2286
- const p = named ? args.slice(0, -1) : args;
2287
- if (named && "attribute" in named)
2288
- return toList(value).map((item) => {
2289
- const v = attributeOf(item, named["attribute"]);
2290
- return v === void 0 && "default" in named ? named["default"] : v;
2291
- });
2292
- const items = toList(value);
2293
- if (typeof p[0] === "string" && !items.some((item) => item !== null && typeof item === "object")) {
2294
- let filter;
2295
- try {
2296
- filter = lookup.getFilter(p[0]);
2297
- } catch {
2298
- filter = void 0;
2316
+ lookup.addFilter(
2317
+ "map",
2318
+ function(value, ...args) {
2319
+ const named = keywordArgs(args);
2320
+ const p = named ? args.slice(0, -1) : args;
2321
+ if (named && "attribute" in named)
2322
+ return toList(value).map((item) => {
2323
+ const v = attributeOf(item, named["attribute"]);
2324
+ return v === void 0 && "default" in named ? named["default"] : v;
2325
+ });
2326
+ const items = toList(value);
2327
+ if (typeof p[0] === "string" && !items.some((item) => item !== null && typeof item === "object")) {
2328
+ let filter;
2329
+ try {
2330
+ filter = lookup.getFilter(p[0]);
2331
+ } catch {
2332
+ filter = void 0;
2333
+ }
2334
+ if (filter)
2335
+ return items.map((item) => filter.call(this, item, ...p.slice(1)));
2299
2336
  }
2300
- if (filter) return items.map((item) => filter.call(this, item, ...p.slice(1)));
2337
+ return helperMap.call(this, value, ...args);
2301
2338
  }
2302
- return helperMap.call(this, value, ...args);
2303
- });
2339
+ );
2304
2340
  const byAttribute = (keep) => function(value, attribute2, test, arg) {
2305
2341
  return toList(value).filter((item) => {
2306
2342
  const v = attributeOf(item, attribute2);
@@ -2329,14 +2365,16 @@ function pythonRepr(v) {
2329
2365
  if (v === null || v === void 0) return "None";
2330
2366
  if (v === true) return "True";
2331
2367
  if (v === false) return "False";
2332
- if (typeof v === "number") return Number.isNaN(v) ? "nan" : Number.isFinite(v) ? String(v) : v > 0 ? "inf" : "-inf";
2368
+ if (typeof v === "number")
2369
+ return Number.isNaN(v) ? "nan" : Number.isFinite(v) ? String(v) : v > 0 ? "inf" : "-inf";
2333
2370
  if (typeof v === "string") {
2334
2371
  const quote2 = v.includes("'") && !v.includes('"') ? '"' : "'";
2335
2372
  const escaped = v.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
2336
2373
  return quote2 + (quote2 === "'" ? escaped.replace(/'/g, "\\'") : escaped) + quote2;
2337
2374
  }
2338
2375
  if (Array.isArray(v)) return `[${v.map(pythonRepr).join(", ")}]`;
2339
- if (isPlainObject(v)) return `{${Object.entries(v).map(([k, item]) => `${pythonRepr(k)}: ${pythonRepr(item)}`).join(", ")}}`;
2376
+ if (isPlainObject(v))
2377
+ return `{${Object.entries(v).map(([k, item]) => `${pythonRepr(k)}: ${pythonRepr(item)}`).join(", ")}}`;
2340
2378
  return String(v);
2341
2379
  }
2342
2380
  var compatInstalled = false;
@@ -2353,7 +2391,8 @@ function installCompat() {
2353
2391
  nunjucks.runtime["formfeedSetAttribute"] = setNamespaceAttribute;
2354
2392
  nunjucks.compiler.Compiler.prototype.compileSet = function(node, frame) {
2355
2393
  const [target, ...more] = node.targets;
2356
- if (!target || more.length || target.typename !== "LookupVal") return compileSet.call(this, node, frame);
2394
+ if (!target || more.length || target.typename !== "LookupVal")
2395
+ return compileSet.call(this, node, frame);
2357
2396
  this._emit("runtime.formfeedSetAttribute(");
2358
2397
  this._compileExpression(target["target"], frame);
2359
2398
  this._emit(", ");
@@ -2364,7 +2403,10 @@ function installCompat() {
2364
2403
  this._emitLine(");");
2365
2404
  };
2366
2405
  const suppressValue = nunjucks.runtime.suppressValue;
2367
- nunjucks.runtime.suppressValue = (val2, autoescape) => suppressValue(Array.isArray(val2) || isPlainObject(val2) ? pythonRepr(val2) : val2, autoescape);
2406
+ nunjucks.runtime.suppressValue = (val2, autoescape) => suppressValue(
2407
+ Array.isArray(val2) || isPlainObject(val2) ? pythonRepr(val2) : val2,
2408
+ autoescape
2409
+ );
2368
2410
  const original = nunjucks.runtime.memberLookup;
2369
2411
  nunjucks.runtime.memberLookup = (obj, val2, ...rest) => {
2370
2412
  if (typeof obj === "string" && typeof val2 === "string" && Object.prototype.hasOwnProperty.call(stringMethods, val2)) {
@@ -2468,7 +2510,8 @@ function walk2(node, w) {
2468
2510
  case "Symbol":
2469
2511
  case "LookupVal": {
2470
2512
  const p = pathOf(node);
2471
- if (node.typename === "Symbol" && jinjaLiterals.has(String(node.value))) return;
2513
+ if (node.typename === "Symbol" && jinjaLiterals.has(String(node.value)))
2514
+ return;
2472
2515
  if (p) {
2473
2516
  const root = p.path[0] ?? "";
2474
2517
  const bound = inScope(w, root);
@@ -2523,7 +2566,9 @@ function walk2(node, w) {
2523
2566
  const nameNode = node["name"];
2524
2567
  const names = nameNode.typename === "Array" ? nameNode.children ?? [] : [nameNode];
2525
2568
  const root = source?.path[0] ?? "";
2526
- const items = !source ? null : inScope(w, root) ? ((s) => s ? [...s, ...source.path.slice(1)] : null)(sourceOf(w, root)) : source.path;
2569
+ const items = !source ? null : inScope(w, root) ? ((s) => s ? [...s, ...source.path.slice(1)] : null)(
2570
+ sourceOf(w, root)
2571
+ ) : source.path;
2527
2572
  const scope = /* @__PURE__ */ new Map();
2528
2573
  for (const n of names) {
2529
2574
  const name = String(n.value);
@@ -2551,7 +2596,8 @@ function walk2(node, w) {
2551
2596
  range: nodeRange(p.root, p.path.join(".").length),
2552
2597
  kind: "assigned"
2553
2598
  });
2554
- if (p.path.length === 1 || !inScope(w, p.path[0] ?? "")) w.scopes[0]?.set(p.path[0] ?? "", null);
2599
+ if (p.path.length === 1 || !inScope(w, p.path[0] ?? ""))
2600
+ w.scopes[0]?.set(p.path[0] ?? "", null);
2555
2601
  }
2556
2602
  }
2557
2603
  walk2(node["value"], w);
@@ -2742,6 +2788,7 @@ var Jinja2Template = class {
2742
2788
  var jinja2Engine = {
2743
2789
  id: "jinja2",
2744
2790
  compile(source, opts = {}) {
2791
+ if (typeof source !== "string") throw notText("jinja2", source);
2745
2792
  const name = opts.name ?? "template";
2746
2793
  try {
2747
2794
  installCompat();
@@ -2911,12 +2958,18 @@ function createLiquid(ctx) {
2911
2958
  parseLimit: 2e6
2912
2959
  });
2913
2960
  for (const [name, { fn }] of ctx.helpers.bind(ctx))
2914
- liquid.registerFilter(name, function(input, ...argv) {
2915
- const tokens = this.token?.args ?? [];
2916
- return fn(
2917
- ...collectKeywordArgs([input, ...argv], (i) => i > 0 && Array.isArray(tokens[i - 1]))
2918
- );
2919
- });
2961
+ liquid.registerFilter(
2962
+ name,
2963
+ function(input, ...argv) {
2964
+ const tokens = this.token?.args ?? [];
2965
+ return fn(
2966
+ ...collectKeywordArgs(
2967
+ [input, ...argv],
2968
+ (i) => i > 0 && Array.isArray(tokens[i - 1])
2969
+ )
2970
+ );
2971
+ }
2972
+ );
2920
2973
  return liquid;
2921
2974
  }
2922
2975
  var validationContext3 = {
@@ -3150,6 +3203,7 @@ var LiquidCompiled = class {
3150
3203
  var liquidEngine = {
3151
3204
  id: "liquid",
3152
3205
  compile(source, opts = {}) {
3206
+ if (typeof source !== "string") throw notText("liquid", source);
3153
3207
  const name = opts.name ?? "template";
3154
3208
  try {
3155
3209
  createLiquid(validationContext3).parse(source, name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formfeed/engine",
3
- "version": "0.3.14",
3
+ "version": "0.3.15",
4
4
  "description": "The Formfeed template engine (Jinja2, Liquid, Handlebars) for the browser and Node.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -33,7 +33,7 @@
33
33
  "@date-fns/tz": "^1.5.0",
34
34
  "bwip-js": "^4.11.4",
35
35
  "date-fns": "^4.4.0",
36
- "handlebars": "^4.7.9",
36
+ "handlebars": "^4.7.10",
37
37
  "liquidjs": "^10.29.0",
38
38
  "marked": "^18.0.11",
39
39
  "n2words": "^6.1.2",
@@ -8,6 +8,13 @@ export declare class EngineSyntaxError extends Error {
8
8
  constructor(engine: EngineId, message: string, line: number, column: number);
9
9
  get position(): Position;
10
10
  }
11
+ /**
12
+ * A template source that is not text. JSON can carry an object where a source belongs (a version's
13
+ * settings, a render request's `settings`), and Handlebars compiles an object as a parsed syntax tree
14
+ * whose values went into the generated code unchecked up to 4.7.9 (GHSA-8r5x-fm3f-whwj): a header
15
+ * sent as a tree ran as JavaScript in the render-worker. Every engine takes text only.
16
+ */
17
+ export declare function notText(engine: EngineId, source: unknown): EngineSyntaxError;
11
18
  /** A render exceeded one of the limits in `RenderLimits` (spec 05 §7). */
12
19
  export declare class RenderLimitError extends Error {
13
20
  readonly limit: 'ms' | 'outputBytes' | 'includeDepth';
@@ -8,6 +8,13 @@ export declare class EngineSyntaxError extends Error {
8
8
  constructor(engine: EngineId, message: string, line: number, column: number);
9
9
  get position(): Position;
10
10
  }
11
+ /**
12
+ * A template source that is not text. JSON can carry an object where a source belongs (a version's
13
+ * settings, a render request's `settings`), and Handlebars compiles an object as a parsed syntax tree
14
+ * whose values went into the generated code unchecked up to 4.7.9 (GHSA-8r5x-fm3f-whwj): a header
15
+ * sent as a tree ran as JavaScript in the render-worker. Every engine takes text only.
16
+ */
17
+ export declare function notText(engine: EngineId, source: unknown): EngineSyntaxError;
11
18
  /** A render exceeded one of the limits in `RenderLimits` (spec 05 §7). */
12
19
  export declare class RenderLimitError extends Error {
13
20
  readonly limit: 'ms' | 'outputBytes' | 'includeDepth';