@skeptools/skep-organization-project 0.0.16 → 0.0.17

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.
Files changed (48) hide show
  1. package/.jsii +28 -3
  2. package/API.md +63 -1
  3. package/lib/skep-organization-project.js +1 -1
  4. package/node_modules/liquidjs/README.md +15 -10
  5. package/node_modules/liquidjs/dist/context/context.d.ts +6 -3
  6. package/node_modules/liquidjs/dist/drop/blank-drop.d.ts +1 -0
  7. package/node_modules/liquidjs/dist/drop/empty-drop.d.ts +1 -0
  8. package/node_modules/liquidjs/dist/index.d.ts +2 -2
  9. package/node_modules/liquidjs/dist/liquid-options.d.ts +2 -0
  10. package/node_modules/liquidjs/dist/liquid.browser.min.js +1 -1
  11. package/node_modules/liquidjs/dist/liquid.browser.min.js.map +1 -1
  12. package/node_modules/liquidjs/dist/liquid.browser.mjs +712 -45
  13. package/node_modules/liquidjs/dist/liquid.browser.umd.js +1573 -215
  14. package/node_modules/liquidjs/dist/liquid.browser.umd.js.map +1 -1
  15. package/node_modules/liquidjs/dist/liquid.d.ts +29 -1
  16. package/node_modules/liquidjs/dist/liquid.node.js +699 -45
  17. package/node_modules/liquidjs/dist/liquid.node.mjs +712 -45
  18. package/node_modules/liquidjs/dist/render/expression.d.ts +1 -1
  19. package/node_modules/liquidjs/dist/render/operator.d.ts +1 -0
  20. package/node_modules/liquidjs/dist/tags/assign.d.ts +5 -0
  21. package/node_modules/liquidjs/dist/tags/block.d.ts +2 -0
  22. package/node_modules/liquidjs/dist/tags/break.d.ts +1 -1
  23. package/node_modules/liquidjs/dist/tags/capture.d.ts +5 -1
  24. package/node_modules/liquidjs/dist/tags/case.d.ts +3 -0
  25. package/node_modules/liquidjs/dist/tags/continue.d.ts +1 -1
  26. package/node_modules/liquidjs/dist/tags/cycle.d.ts +2 -0
  27. package/node_modules/liquidjs/dist/tags/decrement.d.ts +3 -0
  28. package/node_modules/liquidjs/dist/tags/echo.d.ts +2 -0
  29. package/node_modules/liquidjs/dist/tags/for.d.ts +4 -0
  30. package/node_modules/liquidjs/dist/tags/if.d.ts +3 -0
  31. package/node_modules/liquidjs/dist/tags/include.d.ts +5 -1
  32. package/node_modules/liquidjs/dist/tags/increment.d.ts +3 -0
  33. package/node_modules/liquidjs/dist/tags/layout.d.ts +4 -0
  34. package/node_modules/liquidjs/dist/tags/liquid.d.ts +1 -0
  35. package/node_modules/liquidjs/dist/tags/render.d.ts +4 -0
  36. package/node_modules/liquidjs/dist/tags/tablerow.d.ts +4 -0
  37. package/node_modules/liquidjs/dist/tags/unless.d.ts +3 -0
  38. package/node_modules/liquidjs/dist/template/analysis.d.ts +85 -0
  39. package/node_modules/liquidjs/dist/template/analysis.spec.d.ts +1 -0
  40. package/node_modules/liquidjs/dist/template/hash.d.ts +2 -1
  41. package/node_modules/liquidjs/dist/template/index.d.ts +1 -0
  42. package/node_modules/liquidjs/dist/template/output.d.ts +2 -1
  43. package/node_modules/liquidjs/dist/template/template.d.ts +29 -0
  44. package/node_modules/liquidjs/dist/tokens/liquid-tag-token.d.ts +1 -1
  45. package/node_modules/liquidjs/dist/util/type-guards.d.ts +2 -1
  46. package/node_modules/liquidjs/dist/util/underscore.d.ts +3 -0
  47. package/node_modules/liquidjs/package.json +2 -1
  48. package/package.json +2 -2
@@ -1,6 +1,6 @@
1
1
  /*
2
- * liquidjs@10.18.0, https://github.com/harttle/liquidjs
3
- * (c) 2016-2024 harttle
2
+ * liquidjs@10.20.1, https://github.com/harttle/liquidjs
3
+ * (c) 2016-2025 harttle
4
4
  * Released under the MIT License.
5
5
  */
6
6
  'use strict';
@@ -128,6 +128,9 @@ function isArray(value) {
128
128
  // be compatible with IE 8
129
129
  return toString$1.call(value) === '[object Array]';
130
130
  }
131
+ function isArrayLike(value) {
132
+ return value && isNumber(value.length);
133
+ }
131
134
  function isIterable(value) {
132
135
  return isObject(value) && Symbol.iterator in value;
133
136
  }
@@ -213,6 +216,17 @@ function argumentsToValue(fn) {
213
216
  }
214
217
  function escapeRegExp(text) {
215
218
  return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
219
+ }
220
+ /** Return an array containing unique elements from _array_. Works with nested arrays and objects. */
221
+ function* strictUniq(array) {
222
+ const seen = new Set();
223
+ for (const element of array) {
224
+ const key = JSON.stringify(element);
225
+ if (!seen.has(key)) {
226
+ seen.add(key);
227
+ yield element;
228
+ }
229
+ }
216
230
  }
217
231
 
218
232
  /**
@@ -413,6 +427,9 @@ class EmptyDrop extends Drop {
413
427
  valueOf() {
414
428
  return '';
415
429
  }
430
+ static is(value) {
431
+ return value instanceof EmptyDrop;
432
+ }
416
433
  }
417
434
 
418
435
  class BlankDrop extends EmptyDrop {
@@ -425,6 +442,9 @@ class BlankDrop extends EmptyDrop {
425
442
  return /^\s*$/.test(value);
426
443
  return super.equals(value);
427
444
  }
445
+ static is(value) {
446
+ return value instanceof BlankDrop;
447
+ }
428
448
  }
429
449
 
430
450
  class ForloopDrop extends Drop {
@@ -711,7 +731,11 @@ function getDateTimeFormat() {
711
731
 
712
732
  // one minute in milliseconds
713
733
  const OneMinute = 60000;
714
- const ISO8601_TIMEZONE_PATTERN = /([zZ]|([+-])(\d{2}):(\d{2}))$/;
734
+ /**
735
+ * Need support both ISO8601 and RFC2822 as in major browsers & NodeJS
736
+ * RFC2822: https://datatracker.ietf.org/doc/html/rfc2822#section-3.3
737
+ */
738
+ const TIMEZONE_PATTERN = /([zZ]|([+-])(\d{2}):?(\d{2}))$/;
715
739
  const monthNames = [
716
740
  'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
717
741
  'September', 'October', 'November', 'December'
@@ -824,7 +848,7 @@ class LiquidDate {
824
848
  * Given that a template is expected to be parsed fewer times than rendered.
825
849
  */
826
850
  static createDateFixedToTimezone(dateString, locale) {
827
- const m = dateString.match(ISO8601_TIMEZONE_PATTERN);
851
+ const m = dateString.match(TIMEZONE_PATTERN);
828
852
  // representing a UTC timestamp
829
853
  if (m && m[1] === 'Z') {
830
854
  return new LiquidDate(+new Date(dateString), locale, 0);
@@ -1110,7 +1134,9 @@ class LiquidTagToken extends DelimitedToken {
1110
1134
  this.name = this.tokenizer.readTagName();
1111
1135
  this.tokenizer.assert(this.name, 'illegal liquid tag syntax');
1112
1136
  this.tokenizer.skipBlank();
1113
- this.args = this.tokenizer.remaining();
1137
+ }
1138
+ get args() {
1139
+ return this.tokenizer.input.slice(this.tokenizer.p, this.contentRange[1]);
1114
1140
  }
1115
1141
  }
1116
1142
 
@@ -1203,7 +1229,7 @@ class Render {
1203
1229
  const html = yield tpl.render(ctx, emitter);
1204
1230
  // if not, it'll return an `html`, write to the emitter for it
1205
1231
  html && emitter.write(html);
1206
- if (emitter['break'] || emitter['continue'])
1232
+ if (ctx.breakCalled || ctx.continueCalled)
1207
1233
  break;
1208
1234
  }
1209
1235
  catch (e) {
@@ -1287,6 +1313,7 @@ function evalQuotedToken(token) {
1287
1313
  function* evalRangeToken(token, ctx) {
1288
1314
  const low = yield evalToken(token.lhs, ctx);
1289
1315
  const high = yield evalToken(token.rhs, ctx);
1316
+ ctx.memoryLimit.use(high - low + 1);
1290
1317
  return range(+low, +high + 1);
1291
1318
  }
1292
1319
  function* toPostfix(tokens) {
@@ -1378,6 +1405,9 @@ function arrayEquals(lhs, rhs) {
1378
1405
  if (lhs.length !== rhs.length)
1379
1406
  return false;
1380
1407
  return !lhs.some((value, i) => !equals(value, rhs[i]));
1408
+ }
1409
+ function arrayIncludes(arr, item) {
1410
+ return arr.some(value => equals(value, item));
1381
1411
  }
1382
1412
 
1383
1413
  class Node {
@@ -2264,9 +2294,9 @@ class Tag extends TemplateImpl {
2264
2294
  * hash['reversed'] === undefined
2265
2295
  */
2266
2296
  class Hash {
2267
- constructor(markup, jekyllStyle) {
2297
+ constructor(input, jekyllStyle) {
2268
2298
  this.hash = {};
2269
- const tokenizer = new Tokenizer(markup, {});
2299
+ const tokenizer = input instanceof Tokenizer ? input : new Tokenizer(input, {});
2270
2300
  for (const hash of tokenizer.readHashes(jekyllStyle)) {
2271
2301
  this.hash[hash.name.content] = hash.value;
2272
2302
  }
@@ -2365,6 +2395,9 @@ class Output extends TemplateImpl {
2365
2395
  const val = yield this.value.value(ctx, false);
2366
2396
  emitter.write(val);
2367
2397
  }
2398
+ *arguments() {
2399
+ yield this.value;
2400
+ }
2368
2401
  }
2369
2402
 
2370
2403
  class HTML extends TemplateImpl {
@@ -2377,6 +2410,335 @@ class HTML extends TemplateImpl {
2377
2410
  }
2378
2411
  }
2379
2412
 
2413
+ /**
2414
+ * A variable's segments and location, which can be coerced to a string.
2415
+ */
2416
+ class Variable {
2417
+ constructor(segments, location) {
2418
+ this.segments = segments;
2419
+ this.location = location;
2420
+ }
2421
+ toString() {
2422
+ return segmentsString(this.segments, true);
2423
+ }
2424
+ /** Return this variable's segments as an array, possibly with nested arrays for nested paths. */
2425
+ toArray() {
2426
+ function* _visit(...segments) {
2427
+ for (const segment of segments) {
2428
+ if (segment instanceof Variable) {
2429
+ yield Array.from(_visit(...segment.segments));
2430
+ }
2431
+ else {
2432
+ yield segment;
2433
+ }
2434
+ }
2435
+ }
2436
+ return Array.from(_visit(...this.segments));
2437
+ }
2438
+ }
2439
+ /**
2440
+ * Group variables by the string representation of their root segment.
2441
+ */
2442
+ class VariableMap {
2443
+ constructor() {
2444
+ this.map = new Map();
2445
+ }
2446
+ get(key) {
2447
+ const k = segmentsString([key.segments[0]]);
2448
+ if (!this.map.has(k)) {
2449
+ this.map.set(k, []);
2450
+ }
2451
+ return this.map.get(k);
2452
+ }
2453
+ has(key) {
2454
+ return this.map.has(segmentsString([key.segments[0]]));
2455
+ }
2456
+ push(variable) {
2457
+ this.get(variable).push(variable);
2458
+ }
2459
+ asObject() {
2460
+ return Object.fromEntries(this.map);
2461
+ }
2462
+ }
2463
+ const defaultStaticAnalysisOptions = {
2464
+ partials: true
2465
+ };
2466
+ function* _analyze(templates, partials, sync) {
2467
+ const variables = new VariableMap();
2468
+ const globals = new VariableMap();
2469
+ const locals = new VariableMap();
2470
+ const rootScope = new DummyScope(new Set());
2471
+ // Names of partial templates that we've already analyzed.
2472
+ const seen = new Set();
2473
+ function updateVariables(variable, scope) {
2474
+ variables.push(variable);
2475
+ const aliased = scope.alias(variable);
2476
+ if (aliased !== undefined) {
2477
+ const root = aliased.segments[0];
2478
+ // TODO: What if a a template renders a rendered template? Do we need scope.parent?
2479
+ if (isString(root) && !rootScope.has(root)) {
2480
+ globals.push(aliased);
2481
+ }
2482
+ }
2483
+ else {
2484
+ const root = variable.segments[0];
2485
+ if (isString(root) && !scope.has(root)) {
2486
+ globals.push(variable);
2487
+ }
2488
+ }
2489
+ // Recurse for nested Variables
2490
+ for (const segment of variable.segments) {
2491
+ if (segment instanceof Variable) {
2492
+ updateVariables(segment, scope);
2493
+ }
2494
+ }
2495
+ }
2496
+ function* visit(template, scope) {
2497
+ if (template.arguments) {
2498
+ for (const arg of template.arguments()) {
2499
+ for (const variable of extractVariables(arg)) {
2500
+ updateVariables(variable, scope);
2501
+ }
2502
+ }
2503
+ }
2504
+ if (template.localScope) {
2505
+ for (const ident of template.localScope()) {
2506
+ scope.add(ident.content);
2507
+ scope.deleteAlias(ident.content);
2508
+ const [row, col] = ident.getPosition();
2509
+ locals.push(new Variable([ident.content], { row, col, file: ident.file }));
2510
+ }
2511
+ }
2512
+ if (template.children) {
2513
+ if (template.partialScope) {
2514
+ const partial = template.partialScope();
2515
+ if (partial === undefined) {
2516
+ // Layouts, for example, can have children that are not partials.
2517
+ for (const child of (yield template.children(partials, sync))) {
2518
+ yield visit(child, scope);
2519
+ }
2520
+ return;
2521
+ }
2522
+ if (seen.has(partial.name))
2523
+ return;
2524
+ const partialScopeNames = new Set();
2525
+ const partialScope = partial.isolated
2526
+ ? new DummyScope(partialScopeNames)
2527
+ : scope.push(partialScopeNames);
2528
+ for (const name of partial.scope) {
2529
+ if (isString(name)) {
2530
+ partialScopeNames.add(name);
2531
+ }
2532
+ else {
2533
+ const [alias, argument] = name;
2534
+ partialScopeNames.add(alias);
2535
+ const variables = Array.from(extractVariables(argument));
2536
+ if (variables.length) {
2537
+ partialScope.setAlias(alias, variables[0].segments);
2538
+ }
2539
+ }
2540
+ }
2541
+ for (const child of (yield template.children(partials, sync))) {
2542
+ yield visit(child, partialScope);
2543
+ seen.add(partial.name);
2544
+ }
2545
+ partialScope.pop();
2546
+ }
2547
+ else {
2548
+ if (template.blockScope) {
2549
+ scope.push(new Set(template.blockScope()));
2550
+ }
2551
+ for (const child of (yield template.children(partials, sync))) {
2552
+ yield visit(child, scope);
2553
+ }
2554
+ if (template.blockScope) {
2555
+ scope.pop();
2556
+ }
2557
+ }
2558
+ }
2559
+ }
2560
+ for (const template of templates) {
2561
+ yield visit(template, rootScope);
2562
+ }
2563
+ return {
2564
+ variables: variables.asObject(),
2565
+ globals: globals.asObject(),
2566
+ locals: locals.asObject()
2567
+ };
2568
+ }
2569
+ /**
2570
+ * Statically analyze a template and report variable usage.
2571
+ */
2572
+ function analyze(template, options = {}) {
2573
+ const opts = { ...defaultStaticAnalysisOptions, ...options };
2574
+ return toPromise(_analyze(template, opts.partials, false));
2575
+ }
2576
+ /**
2577
+ * Statically analyze a template and report variable usage.
2578
+ */
2579
+ function analyzeSync(template, options = {}) {
2580
+ const opts = { ...defaultStaticAnalysisOptions, ...options };
2581
+ return toValueSync(_analyze(template, opts.partials, true));
2582
+ }
2583
+ /**
2584
+ * A stack to manage scopes while traversing templates during static analysis.
2585
+ */
2586
+ class DummyScope {
2587
+ constructor(globals) {
2588
+ this.stack = [{ names: globals, aliases: new Map() }];
2589
+ }
2590
+ /** Return true if `name` is in scope. */
2591
+ has(name) {
2592
+ for (const scope of this.stack) {
2593
+ if (scope.names.has(name)) {
2594
+ return true;
2595
+ }
2596
+ }
2597
+ return false;
2598
+ }
2599
+ push(scope) {
2600
+ this.stack.push({ names: scope, aliases: new Map() });
2601
+ return this;
2602
+ }
2603
+ pop() {
2604
+ return this.stack.pop()?.names;
2605
+ }
2606
+ // Add a name to the template scope.
2607
+ add(name) {
2608
+ this.stack[0].names.add(name);
2609
+ }
2610
+ /** Return the variable that `variable` aliases, or `variable` if it doesn't alias anything. */
2611
+ alias(variable) {
2612
+ const root = variable.segments[0];
2613
+ if (!isString(root))
2614
+ return undefined;
2615
+ const alias = this.getAlias(root);
2616
+ if (alias === undefined)
2617
+ return undefined;
2618
+ return new Variable([...alias, ...variable.segments.slice(1)], variable.location);
2619
+ }
2620
+ // TODO: `from` could be a path with multiple segments, like `include.x`.
2621
+ setAlias(from, to) {
2622
+ this.stack[this.stack.length - 1].aliases.set(from, to);
2623
+ }
2624
+ deleteAlias(name) {
2625
+ this.stack[this.stack.length - 1].aliases.delete(name);
2626
+ }
2627
+ getAlias(name) {
2628
+ for (const scope of this.stack) {
2629
+ if (scope.aliases.has(name)) {
2630
+ return scope.aliases.get(name);
2631
+ }
2632
+ // If a scope has defined `name`, then it masks aliases in parent scopes.
2633
+ if (scope.names.has(name)) {
2634
+ return undefined;
2635
+ }
2636
+ }
2637
+ return undefined;
2638
+ }
2639
+ }
2640
+ function* extractVariables(value) {
2641
+ if (isValueToken(value)) {
2642
+ yield* extractValueTokenVariables(value);
2643
+ }
2644
+ else if (value instanceof Value) {
2645
+ yield* extractFilteredValueVariables(value);
2646
+ }
2647
+ }
2648
+ function* extractFilteredValueVariables(value) {
2649
+ for (const token of value.initial.postfix) {
2650
+ if (isValueToken(token)) {
2651
+ yield* extractValueTokenVariables(token);
2652
+ }
2653
+ }
2654
+ for (const filter of value.filters) {
2655
+ for (const arg of filter.args) {
2656
+ if (isKeyValuePair(arg) && arg[1]) {
2657
+ yield* extractValueTokenVariables(arg[1]);
2658
+ }
2659
+ else if (isValueToken(arg)) {
2660
+ yield* extractValueTokenVariables(arg);
2661
+ }
2662
+ }
2663
+ }
2664
+ }
2665
+ function* extractValueTokenVariables(token) {
2666
+ if (isRangeToken(token)) {
2667
+ yield* extractValueTokenVariables(token.lhs);
2668
+ yield* extractValueTokenVariables(token.rhs);
2669
+ }
2670
+ else if (isPropertyAccessToken(token)) {
2671
+ yield extractPropertyAccessVariable(token);
2672
+ }
2673
+ }
2674
+ function extractPropertyAccessVariable(token) {
2675
+ const segments = [];
2676
+ // token is not guaranteed to have `file` set. We'll try to get it from a prop if not.
2677
+ let file = token.file;
2678
+ // Here we're flattening the first segment of a path if it is a nested path.
2679
+ const root = token.props[0];
2680
+ file = file || root.file;
2681
+ if (isQuotedToken(root) || isNumberToken(root) || isWordToken(root)) {
2682
+ segments.push(root.content);
2683
+ }
2684
+ else if (isPropertyAccessToken(root)) {
2685
+ // Flatten paths that start with a nested path.
2686
+ segments.push(...extractPropertyAccessVariable(root).segments);
2687
+ }
2688
+ for (const prop of token.props.slice(1)) {
2689
+ file = file || prop.file;
2690
+ if (isQuotedToken(prop) || isNumberToken(prop) || isWordToken(prop)) {
2691
+ segments.push(prop.content);
2692
+ }
2693
+ else if (isPropertyAccessToken(prop)) {
2694
+ segments.push(extractPropertyAccessVariable(prop));
2695
+ }
2696
+ }
2697
+ const [row, col] = token.getPosition();
2698
+ return new Variable(segments, {
2699
+ row,
2700
+ col,
2701
+ file
2702
+ });
2703
+ }
2704
+ // This is used to detect segments that can be represented with dot notation
2705
+ // when creating a string representation of VariableSegments.
2706
+ const RE_PROPERTY = /^[\u0080-\uFFFFa-zA-Z_][\u0080-\uFFFFa-zA-Z0-9_-]*$/;
2707
+ /**
2708
+ * Return a string representation of segments using dot notation where possible.
2709
+ * @param segments - The property names and array indices that make up a path to a variable.
2710
+ * @param bracketedRoot - If false (the default), don't surround the root segment with square brackets.
2711
+ */
2712
+ function segmentsString(segments, bracketedRoot = false) {
2713
+ const buf = [];
2714
+ const root = segments[0];
2715
+ if (isString(root)) {
2716
+ if (!bracketedRoot || root.match(RE_PROPERTY)) {
2717
+ buf.push(`${root}`);
2718
+ }
2719
+ else {
2720
+ buf.push(`['${root}']`);
2721
+ }
2722
+ }
2723
+ for (const segment of segments.slice(1)) {
2724
+ if (segment instanceof Variable) {
2725
+ buf.push(`[${segmentsString(segment.segments)}]`);
2726
+ }
2727
+ else if (isString(segment)) {
2728
+ if (segment.match(RE_PROPERTY)) {
2729
+ buf.push(`.${segment}`);
2730
+ }
2731
+ else {
2732
+ buf.push(`['${segment}']`);
2733
+ }
2734
+ }
2735
+ else {
2736
+ buf.push(`[${segment}]`);
2737
+ }
2738
+ }
2739
+ return buf.join('');
2740
+ }
2741
+
2380
2742
  var LookupType;
2381
2743
  (function (LookupType) {
2382
2744
  LookupType["Partials"] = "partials";
@@ -2575,6 +2937,10 @@ function isWordToken(val) {
2575
2937
  function isRangeToken(val) {
2576
2938
  return getKind(val) === exports.TokenKind.Range;
2577
2939
  }
2940
+ function isValueToken(val) {
2941
+ // valueTokenBitMask = TokenKind.Number | TokenKind.Literal | TokenKind.Quoted | TokenKind.PropertyAccess | TokenKind.Range
2942
+ return (getKind(val) & 1667) > 0;
2943
+ }
2578
2944
  function getKind(val) {
2579
2945
  return val ? val.kind : -1;
2580
2946
  }
@@ -2591,7 +2957,8 @@ var typeGuards = /*#__PURE__*/Object.freeze({
2591
2957
  isNumberToken: isNumberToken,
2592
2958
  isPropertyAccessToken: isPropertyAccessToken,
2593
2959
  isWordToken: isWordToken,
2594
- isRangeToken: isRangeToken
2960
+ isRangeToken: isRangeToken,
2961
+ isValueToken: isValueToken
2595
2962
  });
2596
2963
 
2597
2964
  /******************************************************************************
@@ -2628,6 +2995,8 @@ class Context {
2628
2995
  */
2629
2996
  this.scopes = [{}];
2630
2997
  this.registers = {};
2998
+ this.breakCalled = false;
2999
+ this.continueCalled = false;
2631
3000
  this.sync = !!renderOptions.sync;
2632
3001
  this.opts = opts;
2633
3002
  this.globals = renderOptions.globals ?? opts.globals;
@@ -2663,7 +3032,7 @@ class Context {
2663
3032
  return toValueSync(this._get(paths));
2664
3033
  }
2665
3034
  *_get(paths) {
2666
- const scope = this.findScope(paths[0]);
3035
+ const scope = this.findScope(paths[0]); // first prop should always be a string
2667
3036
  return yield this._getFromScope(scope, paths);
2668
3037
  }
2669
3038
  /**
@@ -2715,6 +3084,7 @@ class Context {
2715
3084
  }
2716
3085
  function readProperty(obj, key, ownPropertyOnly) {
2717
3086
  obj = toLiquid(obj);
3087
+ key = toValue(key);
2718
3088
  if (isNil(obj))
2719
3089
  return obj;
2720
3090
  if (isArray(obj) && key < 0)
@@ -2862,8 +3232,8 @@ const join = argumentsToValue(function (v, arg) {
2862
3232
  this.context.memoryLimit.use(complexity);
2863
3233
  return array.join(sep);
2864
3234
  });
2865
- const last$1 = argumentsToValue((v) => isArray(v) ? last(v) : '');
2866
- const first = argumentsToValue((v) => isArray(v) ? v[0] : '');
3235
+ const last$1 = argumentsToValue((v) => isArrayLike(v) ? last(v) : '');
3236
+ const first = argumentsToValue((v) => isArrayLike(v) ? v[0] : '');
2867
3237
  const reverse = argumentsToValue(function (v) {
2868
3238
  const array = toArray(v);
2869
3239
  this.context.memoryLimit.use(array.length);
@@ -2964,10 +3334,13 @@ function* where(arr, property, expected) {
2964
3334
  for (const item of arr) {
2965
3335
  values.push(yield evalToken(token, this.context.spawn(item)));
2966
3336
  }
3337
+ const matcher = this.context.opts.jekyllWhere
3338
+ ? (v) => EmptyDrop.is(expected) ? equals(v, expected) : (isArray(v) ? arrayIncludes(v, expected) : equals(v, expected))
3339
+ : (v) => equals(v, expected);
2967
3340
  return arr.filter((_, i) => {
2968
3341
  if (expected === undefined)
2969
3342
  return isTruthy(values[i], this.context);
2970
- return equals(values[i], expected);
3343
+ return matcher(values[i]);
2971
3344
  });
2972
3345
  }
2973
3346
  function* where_exp(arr, itemName, exp) {
@@ -2984,7 +3357,7 @@ function* where_exp(arr, itemName, exp) {
2984
3357
  }
2985
3358
  function* group_by(arr, property) {
2986
3359
  const map = new Map();
2987
- arr = toArray(arr);
3360
+ arr = toEnumerable(arr);
2988
3361
  const token = new Tokenizer(stringify(property)).readScopeValue();
2989
3362
  this.context.memoryLimit.use(arr.length);
2990
3363
  for (const item of arr) {
@@ -2998,7 +3371,7 @@ function* group_by(arr, property) {
2998
3371
  function* group_by_exp(arr, itemName, exp) {
2999
3372
  const map = new Map();
3000
3373
  const keyTemplate = new Value(stringify(exp), this.liquid);
3001
- arr = toArray(arr);
3374
+ arr = toEnumerable(arr);
3002
3375
  this.context.memoryLimit.use(arr.length);
3003
3376
  for (const item of arr) {
3004
3377
  const key = yield keyTemplate.value(this.context.spawn({ [itemName]: item }));
@@ -3358,7 +3731,8 @@ const filters = {
3358
3731
  class AssignTag extends Tag {
3359
3732
  constructor(token, remainTokens, liquid) {
3360
3733
  super(token, remainTokens, liquid);
3361
- this.key = this.tokenizer.readIdentifier().content;
3734
+ this.identifier = this.tokenizer.readIdentifier();
3735
+ this.key = this.identifier.content;
3362
3736
  this.tokenizer.assert(this.key, 'expected variable name');
3363
3737
  this.tokenizer.skipBlank();
3364
3738
  this.tokenizer.assert(this.tokenizer.peek() === '=', 'expected "="');
@@ -3368,6 +3742,12 @@ class AssignTag extends Tag {
3368
3742
  *render(ctx) {
3369
3743
  ctx.bottom()[this.key] = yield this.value.value(ctx, this.liquid.options.lenientIf);
3370
3744
  }
3745
+ *arguments() {
3746
+ yield this.value;
3747
+ }
3748
+ *localScope() {
3749
+ yield this.identifier;
3750
+ }
3371
3751
  }
3372
3752
 
3373
3753
  const MODIFIERS = ['offset', 'limit', 'reversed'];
@@ -3382,7 +3762,7 @@ class ForTag extends Tag {
3382
3762
  }
3383
3763
  this.variable = variable.content;
3384
3764
  this.collection = collection;
3385
- this.hash = new Hash(this.tokenizer.remaining(), liquid.options.keyValueSeparator);
3765
+ this.hash = new Hash(this.tokenizer, liquid.options.keyValueSeparator);
3386
3766
  this.templates = [];
3387
3767
  this.elseTemplates = [];
3388
3768
  let p;
@@ -3420,16 +3800,33 @@ class ForTag extends Tag {
3420
3800
  ctx.push(scope);
3421
3801
  for (const item of collection) {
3422
3802
  scope[this.variable] = item;
3803
+ ctx.continueCalled = ctx.breakCalled = false;
3423
3804
  yield r.renderTemplates(this.templates, ctx, emitter);
3424
- if (emitter['break']) {
3425
- emitter['break'] = false;
3805
+ if (ctx.breakCalled)
3426
3806
  break;
3427
- }
3428
- emitter['continue'] = false;
3429
3807
  scope.forloop.next();
3430
3808
  }
3809
+ ctx.continueCalled = ctx.breakCalled = false;
3431
3810
  ctx.pop();
3432
3811
  }
3812
+ *children() {
3813
+ const templates = this.templates.slice();
3814
+ if (this.elseTemplates) {
3815
+ templates.push(...this.elseTemplates);
3816
+ }
3817
+ return templates;
3818
+ }
3819
+ *arguments() {
3820
+ yield this.collection;
3821
+ for (const v of Object.values(this.hash.hash)) {
3822
+ if (isValueToken(v)) {
3823
+ yield v;
3824
+ }
3825
+ }
3826
+ }
3827
+ blockScope() {
3828
+ return [this.variable, 'forloop'];
3829
+ }
3433
3830
  }
3434
3831
  function reversed(arr) {
3435
3832
  return [...arr].reverse();
@@ -3445,7 +3842,8 @@ class CaptureTag extends Tag {
3445
3842
  constructor(tagToken, remainTokens, liquid, parser) {
3446
3843
  super(tagToken, remainTokens, liquid);
3447
3844
  this.templates = [];
3448
- this.variable = this.readVariableName();
3845
+ this.identifier = this.readVariable();
3846
+ this.variable = this.identifier.content;
3449
3847
  while (remainTokens.length) {
3450
3848
  const token = remainTokens.shift();
3451
3849
  if (isTagToken(token) && token.name === 'endcapture')
@@ -3454,19 +3852,25 @@ class CaptureTag extends Tag {
3454
3852
  }
3455
3853
  throw new Error(`tag ${tagToken.getText()} not closed`);
3456
3854
  }
3855
+ readVariable() {
3856
+ let ident = this.tokenizer.readIdentifier();
3857
+ if (ident.content)
3858
+ return ident;
3859
+ ident = this.tokenizer.readQuoted();
3860
+ if (ident)
3861
+ return ident;
3862
+ throw this.tokenizer.error('invalid capture name');
3863
+ }
3457
3864
  *render(ctx) {
3458
3865
  const r = this.liquid.renderer;
3459
3866
  const html = yield r.renderTemplates(this.templates, ctx);
3460
3867
  ctx.bottom()[this.variable] = html;
3461
3868
  }
3462
- readVariableName() {
3463
- const word = this.tokenizer.readIdentifier().content;
3464
- if (word)
3465
- return word;
3466
- const quoted = this.tokenizer.readQuoted();
3467
- if (quoted)
3468
- return evalQuotedToken(quoted);
3469
- throw this.tokenizer.error('invalid capture name');
3869
+ *children() {
3870
+ return this.templates;
3871
+ }
3872
+ *localScope() {
3873
+ yield this.identifier;
3470
3874
  }
3471
3875
  }
3472
3876
 
@@ -3534,6 +3938,17 @@ class CaseTag extends Tag {
3534
3938
  yield r.renderTemplates(this.elseTemplates, ctx, emitter);
3535
3939
  }
3536
3940
  }
3941
+ *arguments() {
3942
+ yield this.value;
3943
+ yield* this.branches.flatMap(b => b.values);
3944
+ }
3945
+ *children() {
3946
+ const templates = this.branches.flatMap(b => b.templates);
3947
+ if (this.elseTemplates) {
3948
+ templates.push(...this.elseTemplates);
3949
+ }
3950
+ return templates;
3951
+ }
3537
3952
  }
3538
3953
 
3539
3954
  class CommentTag extends Tag {
@@ -3587,7 +4002,7 @@ class RenderTag extends Tag {
3587
4002
  tokenizer.p = begin;
3588
4003
  break;
3589
4004
  }
3590
- this.hash = new Hash(tokenizer.remaining(), liquid.options.keyValueSeparator);
4005
+ this.hash = new Hash(tokenizer, liquid.options.keyValueSeparator);
3591
4006
  }
3592
4007
  *render(ctx, emitter) {
3593
4008
  const { liquid, hash } = this;
@@ -3616,6 +4031,55 @@ class RenderTag extends Tag {
3616
4031
  yield liquid.renderer.renderTemplates(templates, childCtx, emitter);
3617
4032
  }
3618
4033
  }
4034
+ *children(partials, sync) {
4035
+ if (partials && isString(this['file'])) {
4036
+ return (yield this.liquid._parsePartialFile(this['file'], sync, this['currentFile']));
4037
+ }
4038
+ return [];
4039
+ }
4040
+ partialScope() {
4041
+ if (isString(this['file'])) {
4042
+ const names = Object.keys(this.hash.hash);
4043
+ if (this['with']) {
4044
+ const { value, alias } = this['with'];
4045
+ if (isString(alias)) {
4046
+ names.push([alias, value]);
4047
+ }
4048
+ else if (isString(this.file)) {
4049
+ names.push([this.file, value]);
4050
+ }
4051
+ }
4052
+ if (this['for']) {
4053
+ const { value, alias } = this['for'];
4054
+ if (isString(alias)) {
4055
+ names.push([alias, value]);
4056
+ }
4057
+ else if (isString(this.file)) {
4058
+ names.push([this.file, value]);
4059
+ }
4060
+ }
4061
+ return { name: this['file'], isolated: true, scope: names };
4062
+ }
4063
+ }
4064
+ *arguments() {
4065
+ for (const v of Object.values(this.hash.hash)) {
4066
+ if (isValueToken(v)) {
4067
+ yield v;
4068
+ }
4069
+ }
4070
+ if (this['with']) {
4071
+ const { value } = this['with'];
4072
+ if (isValueToken(value)) {
4073
+ yield value;
4074
+ }
4075
+ }
4076
+ if (this['for']) {
4077
+ const { value } = this['for'];
4078
+ if (isValueToken(value)) {
4079
+ yield value;
4080
+ }
4081
+ }
4082
+ }
3619
4083
  }
3620
4084
  /**
3621
4085
  * @return null for "none",
@@ -3672,7 +4136,7 @@ class IncludeTag extends Tag {
3672
4136
  }
3673
4137
  else
3674
4138
  tokenizer.p = begin;
3675
- this.hash = new Hash(tokenizer.remaining(), liquid.options.jekyllInclude || liquid.options.keyValueSeparator);
4139
+ this.hash = new Hash(tokenizer, liquid.options.jekyllInclude || liquid.options.keyValueSeparator);
3676
4140
  }
3677
4141
  *render(ctx, emitter) {
3678
4142
  const { liquid, hash, withVar } = this;
@@ -3691,12 +4155,43 @@ class IncludeTag extends Tag {
3691
4155
  ctx.pop();
3692
4156
  ctx.restoreRegister(saved);
3693
4157
  }
4158
+ *children(partials, sync) {
4159
+ if (partials && isString(this['file'])) {
4160
+ return (yield this.liquid._parsePartialFile(this['file'], sync, this['currentFile']));
4161
+ }
4162
+ return [];
4163
+ }
4164
+ partialScope() {
4165
+ if (isString(this['file'])) {
4166
+ let names;
4167
+ if (this.liquid.options.jekyllInclude) {
4168
+ names = ['include'];
4169
+ }
4170
+ else {
4171
+ names = Object.keys(this.hash.hash);
4172
+ if (this.withVar) {
4173
+ names.push([this['file'], this.withVar]);
4174
+ }
4175
+ }
4176
+ return { name: this['file'], isolated: false, scope: names };
4177
+ }
4178
+ }
4179
+ *arguments() {
4180
+ yield* Object.values(this.hash.hash).filter(isValueToken);
4181
+ if (isValueToken(this['file'])) {
4182
+ yield this['file'];
4183
+ }
4184
+ if (isValueToken(this.withVar)) {
4185
+ yield this.withVar;
4186
+ }
4187
+ }
3694
4188
  }
3695
4189
 
3696
4190
  class DecrementTag extends Tag {
3697
4191
  constructor(token, remainTokens, liquid) {
3698
4192
  super(token, remainTokens, liquid);
3699
- this.variable = this.tokenizer.readIdentifier().content;
4193
+ this.identifier = this.tokenizer.readIdentifier();
4194
+ this.variable = this.identifier.content;
3700
4195
  }
3701
4196
  render(context, emitter) {
3702
4197
  const scope = context.environments;
@@ -3705,6 +4200,9 @@ class DecrementTag extends Tag {
3705
4200
  }
3706
4201
  emitter.write(stringify(--scope[this.variable]));
3707
4202
  }
4203
+ *localScope() {
4204
+ yield this.identifier;
4205
+ }
3708
4206
  }
3709
4207
 
3710
4208
  class CycleTag extends Tag {
@@ -3742,6 +4240,12 @@ class CycleTag extends Tag {
3742
4240
  groups[fingerprint] = idx;
3743
4241
  return yield evalToken(candidate, ctx);
3744
4242
  }
4243
+ *arguments() {
4244
+ yield* this.candidates;
4245
+ if (this.group) {
4246
+ yield this.group;
4247
+ }
4248
+ }
3745
4249
  }
3746
4250
 
3747
4251
  class IfTag extends Tag {
@@ -3751,13 +4255,13 @@ class IfTag extends Tag {
3751
4255
  let p = [];
3752
4256
  parser.parseStream(remainTokens)
3753
4257
  .on('start', () => this.branches.push({
3754
- value: new Value(tagToken.args, this.liquid),
4258
+ value: new Value(tagToken.tokenizer.readFilteredValue(), this.liquid),
3755
4259
  templates: (p = [])
3756
4260
  }))
3757
4261
  .on('tag:elsif', (token) => {
3758
4262
  assert(!this.elseTemplates, 'unexpected elsif after else');
3759
4263
  this.branches.push({
3760
- value: new Value(token.args, this.liquid),
4264
+ value: new Value(token.tokenizer.readFilteredValue(), this.liquid),
3761
4265
  templates: (p = [])
3762
4266
  });
3763
4267
  })
@@ -3782,12 +4286,23 @@ class IfTag extends Tag {
3782
4286
  }
3783
4287
  yield r.renderTemplates(this.elseTemplates || [], ctx, emitter);
3784
4288
  }
4289
+ *children() {
4290
+ const templates = this.branches.flatMap(b => b.templates);
4291
+ if (this.elseTemplates) {
4292
+ templates.push(...this.elseTemplates);
4293
+ }
4294
+ return templates;
4295
+ }
4296
+ arguments() {
4297
+ return this.branches.map(b => b.value);
4298
+ }
3785
4299
  }
3786
4300
 
3787
4301
  class IncrementTag extends Tag {
3788
4302
  constructor(token, remainTokens, liquid) {
3789
4303
  super(token, remainTokens, liquid);
3790
- this.variable = this.tokenizer.readIdentifier().content;
4304
+ this.identifier = this.tokenizer.readIdentifier();
4305
+ this.variable = this.identifier.content;
3791
4306
  }
3792
4307
  render(context, emitter) {
3793
4308
  const scope = context.environments;
@@ -3798,6 +4313,9 @@ class IncrementTag extends Tag {
3798
4313
  scope[this.variable]++;
3799
4314
  emitter.write(stringify(val));
3800
4315
  }
4316
+ *localScope() {
4317
+ yield this.identifier;
4318
+ }
3801
4319
  }
3802
4320
 
3803
4321
  class LayoutTag extends Tag {
@@ -3805,7 +4323,7 @@ class LayoutTag extends Tag {
3805
4323
  super(token, remainTokens, liquid);
3806
4324
  this.file = parseFilePath(this.tokenizer, this.liquid, parser);
3807
4325
  this['currentFile'] = token.file;
3808
- this.args = new Hash(this.tokenizer.remaining(), liquid.options.keyValueSeparator);
4326
+ this.args = new Hash(this.tokenizer, liquid.options.keyValueSeparator);
3809
4327
  this.templates = parser.parseTokens(remainTokens);
3810
4328
  }
3811
4329
  *render(ctx, emitter) {
@@ -3832,6 +4350,28 @@ class LayoutTag extends Tag {
3832
4350
  yield renderer.renderTemplates(templates, ctx, emitter);
3833
4351
  ctx.pop();
3834
4352
  }
4353
+ *children(partials) {
4354
+ const templates = this.templates.slice();
4355
+ if (partials && isString(this.file)) {
4356
+ templates.push(...(yield this.liquid._parsePartialFile(this.file, true, this['currentFile'])));
4357
+ }
4358
+ return templates;
4359
+ }
4360
+ *arguments() {
4361
+ for (const v of Object.values(this.args.hash)) {
4362
+ if (isValueToken(v)) {
4363
+ yield v;
4364
+ }
4365
+ }
4366
+ if (isValueToken(this.file)) {
4367
+ yield this.file;
4368
+ }
4369
+ }
4370
+ partialScope() {
4371
+ if (isString(this.file)) {
4372
+ return { name: this.file, isolated: false, scope: Object.keys(this.args.hash) };
4373
+ }
4374
+ }
3835
4375
  }
3836
4376
 
3837
4377
  class BlockTag extends Tag {
@@ -3871,6 +4411,12 @@ class BlockTag extends Tag {
3871
4411
  ? (superBlock, emitter) => renderChild(new BlockDrop(() => renderCurrent(superBlock, emitter)), emitter)
3872
4412
  : renderCurrent;
3873
4413
  }
4414
+ *children() {
4415
+ return this.templates;
4416
+ }
4417
+ blockScope() {
4418
+ return ['block'];
4419
+ }
3874
4420
  }
3875
4421
 
3876
4422
  class RawTag extends Tag {
@@ -3925,7 +4471,7 @@ class TablerowTag extends Tag {
3925
4471
  }
3926
4472
  this.variable = variable.content;
3927
4473
  this.collection = collectionToken;
3928
- this.args = new Hash(this.tokenizer.remaining(), liquid.options.keyValueSeparator);
4474
+ this.args = new Hash(this.tokenizer, liquid.options.keyValueSeparator);
3929
4475
  this.templates = [];
3930
4476
  let p;
3931
4477
  const stream = parser.parseStream(remainTokens)
@@ -3963,6 +4509,20 @@ class TablerowTag extends Tag {
3963
4509
  emitter.write('</tr>');
3964
4510
  ctx.pop();
3965
4511
  }
4512
+ *children() {
4513
+ return this.templates;
4514
+ }
4515
+ *arguments() {
4516
+ yield this.collection;
4517
+ for (const v of Object.values(this.args.hash)) {
4518
+ if (isValueToken(v)) {
4519
+ yield v;
4520
+ }
4521
+ }
4522
+ }
4523
+ blockScope() {
4524
+ return [this.variable, 'tablerowloop'];
4525
+ }
3966
4526
  }
3967
4527
 
3968
4528
  class UnlessTag extends Tag {
@@ -3974,7 +4534,7 @@ class UnlessTag extends Tag {
3974
4534
  let elseCount = 0;
3975
4535
  parser.parseStream(remainTokens)
3976
4536
  .on('start', () => this.branches.push({
3977
- value: new Value(tagToken.args, this.liquid),
4537
+ value: new Value(tagToken.tokenizer.readFilteredValue(), this.liquid),
3978
4538
  test: isFalsy,
3979
4539
  templates: (p = [])
3980
4540
  }))
@@ -3984,7 +4544,7 @@ class UnlessTag extends Tag {
3984
4544
  return;
3985
4545
  }
3986
4546
  this.branches.push({
3987
- value: new Value(token.args, this.liquid),
4547
+ value: new Value(token.tokenizer.readFilteredValue(), this.liquid),
3988
4548
  test: isTruthy,
3989
4549
  templates: (p = [])
3990
4550
  });
@@ -4013,17 +4573,27 @@ class UnlessTag extends Tag {
4013
4573
  }
4014
4574
  yield r.renderTemplates(this.elseTemplates, ctx, emitter);
4015
4575
  }
4576
+ *children() {
4577
+ const children = this.branches.flatMap(b => b.templates);
4578
+ if (this.elseTemplates) {
4579
+ children.push(...this.elseTemplates);
4580
+ }
4581
+ return children;
4582
+ }
4583
+ arguments() {
4584
+ return this.branches.map(b => b.value);
4585
+ }
4016
4586
  }
4017
4587
 
4018
4588
  class BreakTag extends Tag {
4019
- render(ctx, emitter) {
4020
- emitter['break'] = true;
4589
+ render(ctx, _emitter) {
4590
+ ctx.breakCalled = true;
4021
4591
  }
4022
4592
  }
4023
4593
 
4024
4594
  class ContinueTag extends Tag {
4025
- render(ctx, emitter) {
4026
- emitter['continue'] = true;
4595
+ render(ctx, _emitter) {
4596
+ ctx.continueCalled = true;
4027
4597
  }
4028
4598
  }
4029
4599
 
@@ -4041,6 +4611,11 @@ class EchoTag extends Tag {
4041
4611
  const val = yield this.value.value(ctx, false);
4042
4612
  emitter.write(val);
4043
4613
  }
4614
+ *arguments() {
4615
+ if (this.value) {
4616
+ yield this.value;
4617
+ }
4618
+ }
4044
4619
  }
4045
4620
 
4046
4621
  class LiquidTag extends Tag {
@@ -4052,6 +4627,9 @@ class LiquidTag extends Tag {
4052
4627
  *render(ctx, emitter) {
4053
4628
  yield this.liquid.renderer.renderTemplates(this.templates, ctx, emitter);
4054
4629
  }
4630
+ *children() {
4631
+ return this.templates;
4632
+ }
4055
4633
  }
4056
4634
 
4057
4635
  class InlineCommentTag extends Tag {
@@ -4190,10 +4768,82 @@ class Liquid {
4190
4768
  self.renderFile(filePath, ctx).then(html => callback(null, html), callback);
4191
4769
  };
4192
4770
  }
4771
+ async analyze(template, options = {}) {
4772
+ return analyze(template, options);
4773
+ }
4774
+ analyzeSync(template, options = {}) {
4775
+ return analyzeSync(template, options);
4776
+ }
4777
+ async parseAndAnalyze(html, filename, options = {}) {
4778
+ return analyze(this.parse(html, filename), options);
4779
+ }
4780
+ parseAndAnalyzeSync(html, filename, options = {}) {
4781
+ return analyzeSync(this.parse(html, filename), options);
4782
+ }
4783
+ /** Return an array of all variables without their properties. */
4784
+ async variables(template, options = {}) {
4785
+ const analysis = await analyze(isString(template) ? this.parse(template) : template, options);
4786
+ return Object.keys(analysis.variables);
4787
+ }
4788
+ /** Return an array of all variables without their properties. */
4789
+ variablesSync(template, options = {}) {
4790
+ const analysis = analyzeSync(isString(template) ? this.parse(template) : template, options);
4791
+ return Object.keys(analysis.variables);
4792
+ }
4793
+ /** Return an array of all variables including their properties/paths. */
4794
+ async fullVariables(template, options = {}) {
4795
+ const analysis = await analyze(isString(template) ? this.parse(template) : template, options);
4796
+ return Array.from(new Set(Object.values(analysis.variables).flatMap((a) => a.map((v) => String(v)))));
4797
+ }
4798
+ /** Return an array of all variables including their properties/paths. */
4799
+ fullVariablesSync(template, options = {}) {
4800
+ const analysis = analyzeSync(isString(template) ? this.parse(template) : template, options);
4801
+ return Array.from(new Set(Object.values(analysis.variables).flatMap((a) => a.map((v) => String(v)))));
4802
+ }
4803
+ /** Return an array of all variables, each as an array of properties/segments. */
4804
+ async variableSegments(template, options = {}) {
4805
+ const analysis = await analyze(isString(template) ? this.parse(template) : template, options);
4806
+ return Array.from(strictUniq(Object.values(analysis.variables).flatMap((a) => a.map((v) => v.toArray()))));
4807
+ }
4808
+ /** Return an array of all variables, each as an array of properties/segments. */
4809
+ variableSegmentsSync(template, options = {}) {
4810
+ const analysis = analyzeSync(isString(template) ? this.parse(template) : template, options);
4811
+ return Array.from(strictUniq(Object.values(analysis.variables).flatMap((a) => a.map((v) => v.toArray()))));
4812
+ }
4813
+ /** Return an array of all expected context variables without their properties. */
4814
+ async globalVariables(template, options = {}) {
4815
+ const analysis = await analyze(isString(template) ? this.parse(template) : template, options);
4816
+ return Object.keys(analysis.globals);
4817
+ }
4818
+ /** Return an array of all expected context variables without their properties. */
4819
+ globalVariablesSync(template, options = {}) {
4820
+ const analysis = analyzeSync(isString(template) ? this.parse(template) : template, options);
4821
+ return Object.keys(analysis.globals);
4822
+ }
4823
+ /** Return an array of all expected context variables including their properties/paths. */
4824
+ async globalFullVariables(template, options = {}) {
4825
+ const analysis = await analyze(isString(template) ? this.parse(template) : template, options);
4826
+ return Array.from(new Set(Object.values(analysis.globals).flatMap((a) => a.map((v) => String(v)))));
4827
+ }
4828
+ /** Return an array of all expected context variables including their properties/paths. */
4829
+ globalFullVariablesSync(template, options = {}) {
4830
+ const analysis = analyzeSync(isString(template) ? this.parse(template) : template, options);
4831
+ return Array.from(new Set(Object.values(analysis.globals).flatMap((a) => a.map((v) => String(v)))));
4832
+ }
4833
+ /** Return an array of all expected context variables, each as an array of properties/segments. */
4834
+ async globalVariableSegments(template, options = {}) {
4835
+ const analysis = await analyze(isString(template) ? this.parse(template) : template, options);
4836
+ return Array.from(strictUniq(Object.values(analysis.globals).flatMap((a) => a.map((v) => v.toArray()))));
4837
+ }
4838
+ /** Return an array of all expected context variables, each as an array of properties/segments. */
4839
+ globalVariableSegmentsSync(template, options = {}) {
4840
+ const analysis = analyzeSync(isString(template) ? this.parse(template) : template, options);
4841
+ return Array.from(strictUniq(Object.values(analysis.globals).flatMap((a) => a.map((v) => v.toArray()))));
4842
+ }
4193
4843
  }
4194
4844
 
4195
4845
  /* istanbul ignore file */
4196
- const version = '10.18.0';
4846
+ const version = '10.20.1';
4197
4847
 
4198
4848
  exports.AssertionError = AssertionError;
4199
4849
  exports.AssignTag = AssignTag;
@@ -4223,6 +4873,7 @@ exports.LiquidTag = LiquidTag;
4223
4873
  exports.Output = Output;
4224
4874
  exports.ParseError = ParseError;
4225
4875
  exports.ParseStream = ParseStream;
4876
+ exports.Parser = Parser;
4226
4877
  exports.RawTag = RawTag;
4227
4878
  exports.RenderError = RenderError;
4228
4879
  exports.RenderTag = RenderTag;
@@ -4236,6 +4887,9 @@ exports.TypeGuards = typeGuards;
4236
4887
  exports.UndefinedVariableError = UndefinedVariableError;
4237
4888
  exports.UnlessTag = UnlessTag;
4238
4889
  exports.Value = Value;
4890
+ exports.Variable = Variable;
4891
+ exports.analyze = analyze;
4892
+ exports.analyzeSync = analyzeSync;
4239
4893
  exports.assert = assert;
4240
4894
  exports.createTrie = createTrie;
4241
4895
  exports.defaultOperators = defaultOperators;