afa5f54004 feat: Add support of inner expressions enclosed by parentheses (#863)
* Add support of inner expressions enclosed by parentheses

* Add support of inner expressions enclosed by parentheses

Made-with: Cursor

* simplify implementation

* fix lint

* fix test

* Enhance tests for parenthesized filter chains in Liquid tags. Added scenarios for enabled and disabled grouped expressions in case, for, if, unless tags, ensuring proper handling of expressions and error throwing for invalid syntax.

* test: remove duplicate readGroupedExpression test block

The readGroupedExpression() test suite was duplicated twice in the spec file. Removed the duplicate block to avoid redundant test execution.

* refactor: extract extractGroupedExpressionTokenVariables helper

Extract inline grouped expression variable extraction logic into a dedicated
function for consistency with other extractors (extractFilteredValueVariables,
extractPropertyAccessVariable).

This addresses PR #863 comment 7 - improves code organization and
maintainability.

* refactor(types): explicit type for collection in for tag

collection: ValueToken | GroupedExpressionToken

Addresses PR #863 comment 5.

* refactor: evaluate grouped expressions at render time with resolvedFilters

Addresses PR review comments 4, 6, 8, 9 - moves grouped expression evaluation
from parse-time resolution to render-time lazy evaluation following the
generator-based async/sync duality pattern used throughout liquidjs.

Key changes:
- Replace resolvedValue (Value instance) with resolvedFilters (Filter[])
- Rename resolveGroupedExpressions() to resolveGroupedExpressionFilters()
- Move evaluation logic to evalGroupedExpressionToken() at render time
- Build Filter instances at parse time (carry liquid reference for render)
- Evaluate expression and apply filters lazily via generators
- Add support for tablerow tag with grouped expressions
- Remove duplicate getFilter() method in Value class

Maintains proper layering (tokens → render → templates) and consistency
with Value.value() pattern. Filter resolution still happens at parse time
since it requires liquid.filters access, but actual evaluation is deferred
to render time.

Tags that store raw ValueToken (for, case when-values, tablerow) still need
explicit resolveGroupedExpressionFilters() calls. Tags that wrap with
new Value() get automatic recursive resolution via Value constructor.

* refactor: reuse FilteredValueToken and fix architectural layering

Replace GroupedExpressionToken with existing FilteredValueToken to avoid
code duplication and fix layering violation where tokens depended on
templates (Filter instances).

Key changes:
- Reuse FilteredValueToken instead of GroupedExpressionToken
- Simplify readGroupOrRange() to return FilteredValueToken | RangeToken
- Add liquid reference to Context for runtime filter resolution
- Build Filter instances at render time in evalFilteredValueToken()
- Remove resolveGroupedExpressionFilters() and parse-time resolution
- Remove explicit resolution calls from tag constructors

This maintains proper architectural layering (tokens → render → templates)
with no backward dependencies, as requested in PR review feedback.

All 1537 tests pass.

* revert redundant'

* refactor: make getFilter private and improve code organization

* test: fix test name in case.spec.ts for when disabled block

* refactor: no need for Deprecated flag

* test: fix test name and logic to properly test if tag with nested expressions

* feat: support real parenthesis grouping in grouped expressions

Allow arbitrary expressions inside parentheses (e.g. ((a | upcase) > 3)
and (1 < 3)) when groupedExpressions is enabled, reusing readFilteredValue
for the general case while keeping range and filter-chain fast paths.

* feat: enhance expression tokenization with new generator methods

Added `readExpressionTokensFromHere` and `readGroupedExpressionTokens` methods to improve the handling of expression tokens. This refactor simplifies the token reading process and maintains compatibility with existing grouped expressions, ensuring proper evaluation and filtering.

* add tests

* address comments

---------

Co-authored-by: Omri Rosner <[email protected]>
2026-08-01 18:15:54 +08:00
2024-08-28 00:31:59 +08:00
2021-06-20 13:25:08 +08:00
2026-07-09 15:18:38 +00:00
2022-07-08 02:56:39 +08:00
2016-06-13 15:39:31 +08:00
2026-07-09 15:18:38 +00:00

LiquidJS

A simple, expressive, extensible Liquid template engine for JavaScript — Shopify, Jekyll and GitHub Pages compatible, for Node.js, browsers, and the CLI, with TypeScript support.

npm version npm downloads Build Status Coverage License: MIT

Documentation · Playground · Setup guide · Contributing

LiquidJS playground: edit a template and context, see live HTML output

Try the online playground.

Quick start

import { Liquid } from 'liquidjs'

const engine = new Liquid()
const html = await engine.parseAndRender(
  'Hello, {{ name | capitalize }}!',
  { name: 'liquid' }
)
//=> 'Hello, Liquid!'

Installation

Node.js

npm install liquidjs

Browser (jsDelivr UMD bundle)

<script src="https://cdn.jsdelivr.net/npm/liquidjs/dist/liquid.browser.min.js"></script>

CLI

npx liquidjs --template 'Hello, {{ name }}!' --context '{"name": "Liquid"}'

See the setup guide for partials, layouts, caching, and other options.

Used by

Eleventy Opensense Power Pages, Azure API Management developer portal GitHub Docs Kibana Shopify CLI, Checkout Blocks Customer IO
Syntax Podcast TestMu AI Chudovo PakStyle.pk EscortA.com Touchless
Dropkiq Directus Builder.io, Mitosis Pattern Lab Rock RMS WISMOlabs Freshet

Products and projects running on LiquidJS. Open a PR to add yours.

Financial Support

If you personally love LiquidJS or it's benefiting your business, please consider financially support us via GitHub Sponsors. Special thanks to our sponsors!

Eleventy Opensense Inc. Microsoft Sentry Checkout Blocks Customer IO Syntax Podcast
TestMu AI Tal Chudovo Dailycontributors PakStyle.pk EscortA.com
Touchless Dropkiq Peter deHaan Serkan Holat amit777 Khaled Salem Emmanuel Cartelli Cartelli Emmanuel

Contributors

Want to contribute? see Contribution Guidelines. Thanks goes to these wonderful people:

Jun Yang
Jun Yang

🚧 💻
chenos
chenos

💻
Zach Leatherman
Zach Leatherman

🐛
Tim Hardy
Tim Hardy

💻
Paul Robert Lloyd
Paul Robert Lloyd

💻 🐛
Alec Larson
Alec Larson

💻
Patrick Malouin
Patrick Malouin

💻 📖
jaswrks
jaswrks

💻
三三
三三

💻 🤔
ssendev
ssendev

💻 📖
wojtask9
wojtask9

💻
Andrew Barclay
Andrew Barclay

💻
Cory Mawhorter
Cory Mawhorter

💻
Mehdi Jaffery
Mehdi Jaffery

💻
Robin Bijlani
Robin Bijlani

💻 🐛
Ryan Kennedy
Ryan Kennedy

💻
Sami Kukkonen
Sami Kukkonen

💻
Scott Santucci
Scott Santucci

💻
Steven
Steven

💡 💻
azu
azu

📖
Joonas
Joonas

💻
Jamel A.
Jamel A.

💻
Brandon Pittman
Brandon Pittman

💻
tgrandgent
tgrandgent

💻
Martin Schuster
Martin Schuster

💻
Ray
Ray

⚠️ 💻
Cristofer Gonzales
Cristofer Gonzales

💻
Raymond Camden
Raymond Camden

📖
Steve Stedman
Steve Stedman

📖
Anthony Ciccarello
Anthony Ciccarello

📖
Bogdan Chadkin
Bogdan Chadkin

💻
Tejas Manohar
Tejas Manohar

💻
Peter deHaan
Peter deHaan

📖
amit777
amit777

💻
Steffen Schuldenzucker
Steffen Schuldenzucker

💻
Pixcell
Pixcell

💻
Jason Etcovitch
Jason Etcovitch

💻
ZC
ZC

📖
Memmie Lenglet
Memmie Lenglet

💻
ilhamdev0
ilhamdev0

📖
一饮一啄皆是人生
一饮一啄皆是人生

📖
Amit Agarwal
Amit Agarwal

📖
Laurin Quast
Laurin Quast

💻
Matt Vague
Matt Vague

💻
Liam Bigelow
Liam Bigelow

💻
Jason Kurian
Jason Kurian

📖
d pham (they/them)
d pham (they/them)

📖
Aleksandr Hovhannisyan
Aleksandr Hovhannisyan

💻
jg-rp
jg-rp

💻
Ameya Apte
Ameya Apte

💻
tbdrz
tbdrz

📖
Santi Albo
Santi Albo

📖 💻
Yahang Wu
Yahang Wu

📖
hongl
hongl

📖
zxx-457
zxx-457

📖
prassie
prassie

📖
Slav Ivanov
Slav Ivanov

💻
Daniel Rosenberg
Daniel Rosenberg

💻
bobgubko
bobgubko

💻
BaNgan
BaNgan

📖
Mahyar Pasarzangene
Mahyar Pasarzangene

📖
Tomáš Hübelbauer
Tomáš Hübelbauer

💻 📖
Jason Garber
Jason Garber

💻
Nick Reilingh
Nick Reilingh

📖
Francisco Soto
Francisco Soto

💻
David LJ
David LJ

📖
Rasmus Wriedt Larsen
Rasmus Wriedt Larsen

📖
Bruno Carvalho
Bruno Carvalho

💻
傅鹏
傅鹏

💻
Joel Hamilton
Joel Hamilton

💻
Max Medve
Max Medve

💻
Cosmin Popovici
Cosmin Popovici

📖
Adam Tanner
Adam Tanner

💻
Guillermo Casal Caro
Guillermo Casal Caro

💻
Josh Soref
Josh Soref

📖
Koen
Koen

💻
Matthieu Bacconnier
Matthieu Bacconnier

📖
Tim van Dam
Tim van Dam

💻
Ed Hanton
Ed Hanton

📖
Vlad GURDIGA
Vlad GURDIGA

📖
裸奔狂甩丁丁
裸奔狂甩丁丁

📖
Ihor Panasiuk
Ihor Panasiuk

💻
Omri Rosner
Omri Rosner

💻
immerrr again
immerrr again

📖
Loo Rong Jie
Loo Rong Jie

💻
MorielHarush
MorielHarush

💻
Peak Twilight
Peak Twilight

💻
Joe Cottam
Joe Cottam

💻
Timmy Braun
Timmy Braun

💻
Tal
Tal

💻
Vladimir Filonov
Vladimir Filonov

💻
spokodev
spokodev

💻

License

MIT © Jun Yang

S
Description
No description provided
Readme MIT
65 MiB
Languages
TypeScript 94.8%
JavaScript 3.8%
Liquid 0.7%
SWIG 0.3%
Handlebars 0.3%