join/array_to_sentence_string/json/inspect charge memoryLimit by the
string they materialize (not element count), so discarded results like
{% assign out = a | join %}{{ out | size }} are still bounded.
Remove the emitter-level limiter added in 2f343f063; it cannot catch
materialized-but-not-emitted values.
Co-authored-by: Cursor <[email protected]>
Move output-length accounting into the emitters, which charge each
written chunk against ctx.memoryLimit right before it reaches the
result string or stream. Filters/tags now only pre-charge the extra
working memory they allocate apart from that output, so join drops its
bespoke output-size counting and charges array.length like its siblings.
The block.super capture emitter intentionally omits the limiter to
avoid double-counting content that is re-emitted through the final
emitter.
Co-authored-by: Cursor <[email protected]>
renderLimit was only checked inside the per-template loop, so
renderTemplates([], ...) skipped it. Empty {% for %} and {% tablerow %}
bodies call that path once per iteration (tablerow still does emitter
work for <tr>/<td>), bypassing the documented time budget. Check the
limiter at renderTemplates entry before the loop.
Add regression tests for empty for-body and empty tablerow-body.
Co-authored-by: Cursor <[email protected]>
fix: `Nil`(null, undefined) now renders as empty string
change: `parser.parseValue()` renamed to `parser.parseOutput`
change: registered tags/filters become static and shared across different liquid instances