Repository: harttle/liquidjs
Pull request #807 (2025-05-14 17:42:03)

fix: block.super with strictVariables, #806
`block.super` used to return a generator which emit contents into passed in `emmiter` and returns `undefined`, triggering `strictVariables` check. Credits to @kamikadzem22 for the investigation.

Furthermore, `block.super` seems to be a string but it isn't, in this PR also changes it to an actual string so it's more intuitive especially when users try to transform it.


Patch:
diff --git a/.all-contributorsrc b/.all-contributorsrc
index 11f78bc21..b017e54c4 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -730,15 +730,6 @@
       "contributions": [
         "doc"
       ]
-    },
-    {
-      "login": "gurdiga",
-      "name": "Vlad GURDIGA",
-      "avatar_url": "https://avatars.githubusercontent.com/u/53922?v=4",
-      "profile": "https://gurdiga.com",
-      "contributions": [
-        "doc"
-      ]
     }
   ],
   "contributorsPerLine": 7,
diff --git a/README.md b/README.md
index 4cb7bf44d..3237cd2b3 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,7 @@ For more details, refer to the [Setup Guide][setup].
 ## Who's Using LiquidJS?
 
 - [Eleventy](https://www.11ty.dev/): Eleventy, a simpler static site generator.
+- [Github Docs](https://github.com/github/docs): The open-source repo for docs.github.com.
 - [Opensense](https://www.opensense.com/): The smarter way to send email.
 - [Directus](https://docs.directus.io/): an instant REST+GraphQL API and intuitive no-code data collaboration app for any SQL database.
 - [Semgrep](https://github.com/returntocorp/semgrep): Lightweight static analysis for many languages.
@@ -213,7 +214,6 @@ Want to contribute? see [Contribution Guidelines][contribution]. Thanks goes to
     <tr>
       <td align="center" valign="top" width="14.28%"><a href="https://tovd.dev"><img src="https://avatars.githubusercontent.com/u/35376389?v=4?s=100" width="100px;" alt="Tim van Dam"/><br /><sub><b>Tim van Dam</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=timvandam" title="Code">💻</a></td>
       <td align="center" valign="top" width="14.28%"><a href="https://github.com/edh649"><img src="https://avatars.githubusercontent.com/u/527604?v=4?s=100" width="100px;" alt="Ed Hanton"/><br /><sub><b>Ed Hanton</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=edh649" title="Documentation">📖</a></td>
-      <td align="center" valign="top" width="14.28%"><a href="https://gurdiga.com"><img src="https://avatars.githubusercontent.com/u/53922?v=4?s=100" width="100px;" alt="Vlad GURDIGA"/><br /><sub><b>Vlad GURDIGA</b></sub></a><br /><a href="https://github.com/harttle/liquidjs/commits?author=gurdiga" title="Documentation">📖</a></td>
     </tr>
   </tbody>
 </table>
diff --git a/docs/source/tags/echo.md b/docs/source/tags/echo.md
index 68e31d740..5de6dea02 100644
--- a/docs/source/tags/echo.md
+++ b/docs/source/tags/echo.md
@@ -4,7 +4,7 @@ title: Echo
 
 {% since %}v9.31.0{% endsince %}
 
-Outputs an expression in the rendered HTML. This is identical to wrapping an expression in <code>{{</code> and <code>}}</code>, but works inside liquid tags and supports filters.
+Outputs an expression in the rendered HTML. This is identical to wrapping an expression in `{{` and `}}`, but works inside liquid tags and supports filters.
 
 ## echo
 
diff --git a/docs/source/tutorials/options.md b/docs/source/tutorials/options.md
index f03a0a060..598ae66d8 100644
--- a/docs/source/tutorials/options.md
+++ b/docs/source/tutorials/options.md
@@ -116,7 +116,7 @@ it defaults to false.  For example, when set to true, a blank string would evalu
 
 **preserveTimezones** is a boolean effects only literal timestamps. When set to `true`, all literal timestamps will remain the same when output. This is a parser option, so Date objects passed to LiquidJS as data will not be affected. Note that `preserveTimezones` has a higher priority than `timezoneOffset`.
 
-**dateFormat** is used to specify a default format to output dates. `%A, %B %-e, %Y at %-l:%M %P %z` will be used if not specified. For example, set `dateFormat: %Y-%m-%dT%H:%M:%S:%LZ` to output all dates in [JavaScript Date.toJson()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toJSON) format.
+**dateFormat** is used to specify a default format to output dates. `%A, %B %-e, %Y at %-l:%M %P %z` will be used if not specified. For example, set `dateFormat: %Y-%m-%dT%H:%M:%S:%LZ` to output all dates in [JavaScript Date.toJson()][https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toJSON] format.
 
 ## Trimming
 
@@ -146,7 +146,7 @@ Nonexistent tags always throw errors during parsing and this behavior cannot be
 
 ## Parameter Order
 
-Parameter orders are ignored by default, for example `{% for i in (1..8) reversed limit:3 %}` will always perform `limit` before `reversed`, even if `reversed` occurs before `limit`. To make parameter order respected, set **orderedFilterParameters** to `true`. Its default value is `false`.
+Parameter orders are ignored by default, for ea `{% for i in (1..8) reversed limit:3 %}` will always perform `limit` before `reversed`, even if `reversed` occurs before `limit`. To make parameter order respected, set **orderedFilterParameters** to `true`. Its default value is `false`.
 
 [liquid]: /api/classes/Liquid.html
 [caching]: ./caching.html
diff --git a/src/drop/block-drop.ts b/src/drop/block-drop.ts
index 0e994aa92..2ff1aa297 100644
--- a/src/drop/block-drop.ts
+++ b/src/drop/block-drop.ts
@@ -1,9 +1,10 @@
+import { Emitter, SimpleEmitter } from '../emitters'
 import { Drop } from './drop'
 
 export class BlockDrop extends Drop {
   constructor (
     // the block render from layout template
-    private superBlockRender: () => Iterable<any> = () => ''
+    private superBlockRender: (emitter: Emitter) => IterableIterator<unknown> | string = () => ''
   ) {
     super()
   }
@@ -11,7 +12,9 @@ export class BlockDrop extends Drop {
    * Provide parent access in child block by
    * {{ block.super }}
    */
-  public super () {
-    return this.superBlockRender()
+  public * super (): IterableIterator<unknown> {
+    const emitter = new SimpleEmitter()
+    yield this.superBlockRender(emitter)
+    return emitter.buffer
   }
 }
diff --git a/src/tags/block.ts b/src/tags/block.ts
index 022c25100..6ae147ac0 100644
--- a/src/tags/block.ts
+++ b/src/tags/block.ts
@@ -39,7 +39,11 @@ export default class extends Tag {
       ctx.pop()
     }
     return renderChild
-      ? (superBlock: BlockDrop, emitter: Emitter) => renderChild(new BlockDrop(() => renderCurrent(superBlock, emitter)), emitter)
+      ? (superBlock: BlockDrop, emitter: Emitter) => renderChild(
+        new BlockDrop(
+          (emitter: Emitter) => renderCurrent(superBlock, emitter)
+        ),
+        emitter)
       : renderCurrent
   }
 


Test patch:
diff --git a/test/integration/tags/layout.spec.ts b/test/integration/tags/layout.spec.ts
index 7a944c588..5f0e4beb8 100644
--- a/test/integration/tags/layout.spec.ts
+++ b/test/integration/tags/layout.spec.ts
@@ -101,6 +101,26 @@ describe('tags/layout', function () {
     const output = '<link href="base.css" rel="stylesheet"><link href="extra.css" rel="stylesheet">'
     return expect(html).toBe(output)
   })
+  it('should pass block.super as a string', async function () {
+    mock({
+      '/parent.html': '{% block css %}<link href="base.css" rel="stylesheet">{% endblock %}'
+    })
+    const src = '{% layout "parent.html" %}' +
+      '{%block css%}{{block.super}}<meta basesize={{block.super | size}}>{%endblock%}'
+    const html = await liquid.parseAndRender(src)
+    const output = '<link href="base.css" rel="stylesheet"><meta basesize=39>'
+    return expect(html).toBe(output)
+  })
+  it('should support block.super while strictVariables', async function () {
+    mock({
+      '/parent.html': '{% block css %}<link href="base.css" rel="stylesheet">{% endblock %}'
+    })
+    const src = '{% layout "parent.html" %}' +
+      '{%block css%}{{block.super}}<link href="extra.css" rel="stylesheet">{%endblock%}'
+    const html = await liquid.parseAndRender(src, undefined, { strictVariables: true })
+    const output = '<link href="base.css" rel="stylesheet"><link href="extra.css" rel="stylesheet">'
+    return expect(html).toBe(output)
+  })
   it('should render block.super to empty if no parent exists', async function () {
     mock({
       '/parent.html': '{% block css %}{{block.super}}<link href="base.css" rel="stylesheet">{% endblock %}'