diff --git a/.claude/rules/filters/lua-development.md b/.claude/rules/filters/lua-development.md index 2bb1ab3a166..19a5ff89cb9 100644 --- a/.claude/rules/filters/lua-development.md +++ b/.claude/rules/filters/lua-development.md @@ -79,6 +79,36 @@ if node.is_custom_node then end ``` +### AST Pattern Matching with `quarto.utils.match` + +Use `quarto.utils.match()` for safe structural checks on AST nodes. It handles nil/empty content internally — no manual length or nil guards needed. Defined in `src/resources/pandoc/datadir/_utils.lua`. + +```lua +-- ✅ Correct - safe, handles empty content +if quarto.utils.match("[1]/BulletList")(b) then ... + +-- ❌ Wrong - crashes on empty content (0 is truthy in Lua) +if #b.content and b.content[1].t == "BulletList" then ... + +-- ❌ Fragile - manual nil guard, verbose +if #b.content > 0 and b.content[1].t == "BulletList" then ... +``` + +Selector syntax supports `/` child traversal, `[n]` nth-child, `{Type}` capture, `:child`/`:descendant` search, and CSS-like class matching: + +```lua +-- Check nested structure +quarto.utils.match("Figure/[1]/Plain/[1]/Image")(fig) + +-- Capture matched nodes (returned in a list) +quarto.utils.match("[1]/{Plain}")(content) + +-- Search direct children for class +quarto.utils.match(".cell-output-display/:child/{Para}")(div) +``` + +Prefer `match()` over manual `.content[1].t ==` checks — it's nil-safe, readable, and already used in 20+ filter callsites. + ### Slot Assignment Use the proxy pattern for slot modification: @@ -285,3 +315,4 @@ These type definition files document the complete API surface. 8. **Gate format inside element fns** - Constructor runs before format is resolved 9. **Detect meta shape with `quarto.utils.type`** - Meta values have no `.t` tag 10. **Gate expensive filters with `flags`** - Skips the whole pass when unneeded +11. **Use `quarto.utils.match()` for content checks** - Nil-safe, prefer over manual `.content[1].t` diff --git a/news/changelog-1.10.md b/news/changelog-1.10.md index 529b08cc4e8..374920714e4 100644 --- a/news/changelog-1.10.md +++ b/news/changelog-1.10.md @@ -39,6 +39,7 @@ All changes included in 1.10: ### `revealjs` - ([#14354](https://github.com/quarto-dev/quarto-cli/pull/14354)): Fix trailing whitespace after author name on title slide when ORCID is not set. (author: @jnkatz) +- ([#14585](https://github.com/quarto-dev/quarto-cli/issues/14585)): Fix empty blockquote (`> `) crashing render for revealjs format. ## Projects diff --git a/src/resources/filters/quarto-post/reveal.lua b/src/resources/filters/quarto-post/reveal.lua index 4f3dc6c2940..de21831cde3 100644 --- a/src/resources/filters/quarto-post/reveal.lua +++ b/src/resources/filters/quarto-post/reveal.lua @@ -61,7 +61,7 @@ function render_reveal_fixups() -- Prevent BulletList in blockquote to be made incremental with .fragment class -- https://github.com/quarto-dev/quarto-cli/issues/7715 BlockQuote = function(b) - if #b.content and b.content[1].t == "BulletList" then + if quarto.utils.match("[1]/BulletList")(b) then b.content = pandoc.Div(b.content, pandoc.Attr('', {'blockquote-list-scaffold'})) return b end diff --git a/tests/docs/smoke-all/revealjs/empty-blockquote.qmd b/tests/docs/smoke-all/revealjs/empty-blockquote.qmd new file mode 100644 index 00000000000..47cb1a18f31 --- /dev/null +++ b/tests/docs/smoke-all/revealjs/empty-blockquote.qmd @@ -0,0 +1,18 @@ +--- +title: "Empty blockquote" +format: revealjs +_quarto: + tests: + revealjs: + ensureHtmlElements: + - ["blockquote"] + noErrors: [] +--- + +## Slide with empty blockquote + +> + +## Slide with content blockquote + +> This is a normal blockquote