Skip to content

Add HybridTag base class and BlockBody reparenting#2059

Open
charlespwd wants to merge 1 commit intomainfrom
cp-theme-composition
Open

Add HybridTag base class and BlockBody reparenting#2059
charlespwd wants to merge 1 commit intomainfrom
cp-theme-composition

Conversation

@charlespwd
Copy link
Contributor

@charlespwd charlespwd commented Mar 18, 2026

Summary

Adds foundational support for "hybrid tags" — tags that can function in both self-closing form ({% section 'name' %}) and block form ({% section 'name' %}...{% endsection %}).

Changes

  • Liquid::HybridTag — New base class extending Liquid::Block that supports two rendering modes:

    • Self-closing: tag has no body, renders via render_self_closing_to_output_buffer
    • Block form: tag captures sibling nodes via end-tag-triggered reparenting, renders via render_block_form_to_output_buffer
    • Subclasses register which end tag triggers reparenting (e.g., endsection)
  • BlockBody#parse reparenting — When the parser encounters an end tag for a registered hybrid tag (e.g., {% endsection %}), it looks back through @nodelist, finds the matching hybrid tag, splices intervening nodes into its body, and flips it to block form. No tokenizer changes needed.

  • Parse-time nesting prevention — Hybrid tags of the same type cannot be nested (raises SyntaxError). Sequential hybrid tags and different types are allowed.

  • @blank recomputation — After reparenting, the parent BlockBody recomputes its @blank flag based on remaining nodes.

Design

Uses end-tag-triggered reparenting rather than tokenizer lookahead. When BlockBody#parse encounters {% endsection %}:

  1. Walks @nodelist backwards to find the matching section tag
  2. Splices all nodes between the hybrid tag and end tag
  3. Calls reparent_as_block(spliced_nodes) on the hybrid tag
  4. Recomputes @blank for the parent body

This approach requires zero tokenizer modifications.

Test plan

  • Unit tests for HybridTag self-closing form (no end tag → stays self-closing)
  • Unit tests for HybridTag block form (end tag → reparents siblings)
  • @blank recomputation after reparenting
  • Nesting prevention (same-type hybrid tags raise SyntaxError)
  • Sequential hybrid tags (allowed)
  • Mixed self-closing + block in same template
  • Full existing Liquid test suite passes — zero regressions

🤖 Generated with Claude Code

@charlespwd charlespwd force-pushed the cp-theme-composition branch from e7d47ed to f39e154 Compare March 19, 2026 13:36
@charlespwd charlespwd force-pushed the cp-theme-composition branch from de02dd6 to dae47e3 Compare March 19, 2026 14:07
@charlespwd charlespwd marked this pull request as ready for review March 19, 2026 14:23
@charlespwd charlespwd requested a review from ianks March 19, 2026 14:24
@charlespwd charlespwd force-pushed the cp-theme-composition branch from dae47e3 to 28b0cc2 Compare March 19, 2026 14:24
Introduces hybrid tags — tags that work in both self-closing and block
form. When BlockBody#parse encounters an end tag for a registered hybrid
tag, it walks backward through sibling nodes and reparents them into
the tag's body. No tokenizer changes needed.

- HybridTag subclasses must implement blank? (raises NotImplementedError)
- Block form is derived from @Body presence, no separate tracking flag
- Nested hybrid tags in block form are detected during the backward walk
- Parent BlockBody blank state is not recomputed since hybrid tags that
  render content already return blank? == false

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@charlespwd charlespwd force-pushed the cp-theme-composition branch from 28b0cc2 to cca902b Compare March 19, 2026 14:26
@charlespwd charlespwd requested a review from mmorissette March 19, 2026 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant