From a96b49ec2db90101b49d4ee0c0efe48977f35f9b Mon Sep 17 00:00:00 2001 From: Harttle Date: Sun, 22 Dec 2024 15:55:07 +0800 Subject: [PATCH] fix: inconsistent continue behaviour, fixes #779 --- src/context/context.ts | 2 ++ src/render/render.ts | 2 +- src/tags/break.ts | 4 ++-- src/tags/continue.ts | 4 ++-- src/tags/for.ts | 7 ++----- test/e2e/issues.spec.ts | 6 ++++-- test/integration/tags/for.spec.ts | 24 ++++++++++++++++++++++++ 7 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/context/context.ts b/src/context/context.ts index 5b0306dcc..d23381b6a 100644 --- a/src/context/context.ts +++ b/src/context/context.ts @@ -25,6 +25,8 @@ export class Context { */ public globals: Scope public sync: boolean + public breakCalled = false + public continueCalled = false /** * The normalized liquid options object */ diff --git a/src/render/render.ts b/src/render/render.ts index bebc8f366..6cfc9a99a 100644 --- a/src/render/render.ts +++ b/src/render/render.ts @@ -23,7 +23,7 @@ export class Render { const html = yield tpl.render(ctx, emitter) // if not, it'll return an `html`, write to the emitter for it html && emitter.write(html) - if (emitter['break'] || emitter['continue']) break + if (ctx.breakCalled || ctx.continueCalled) break } catch (e) { const err = LiquidError.is(e) ? e : new RenderError(e as Error, tpl) if (ctx.opts.catchAllErrors) errors.push(err) diff --git a/src/tags/break.ts b/src/tags/break.ts index 0ed79388c..4a68a1839 100644 --- a/src/tags/break.ts +++ b/src/tags/break.ts @@ -1,7 +1,7 @@ import { Context, Emitter, Tag } from '..' export default class extends Tag { - render (ctx: Context, emitter: Emitter) { - emitter['break'] = true + render (ctx: Context, _emitter: Emitter) { + ctx.breakCalled = true } } diff --git a/src/tags/continue.ts b/src/tags/continue.ts index 914b8e3a0..0a886242a 100644 --- a/src/tags/continue.ts +++ b/src/tags/continue.ts @@ -1,7 +1,7 @@ import { Tag, Emitter, Context } from '..' export default class extends Tag { - render (ctx: Context, emitter: Emitter) { - emitter['continue'] = true + render (ctx: Context, _emitter: Emitter) { + ctx.continueCalled = true } } diff --git a/src/tags/for.ts b/src/tags/for.ts index de05a1095..4262ce5dd 100644 --- a/src/tags/for.ts +++ b/src/tags/for.ts @@ -68,12 +68,9 @@ export default class extends Tag { ctx.push(scope) for (const item of collection) { scope[this.variable] = item + ctx.continueCalled = ctx.breakCalled = false yield r.renderTemplates(this.templates, ctx, emitter) - if (emitter['break']) { - emitter['break'] = false - break - } - emitter['continue'] = false + if (ctx.breakCalled) break scope.forloop.next() } ctx.pop() diff --git a/test/e2e/issues.spec.ts b/test/e2e/issues.spec.ts index ae0e9cc03..d437379fd 100644 --- a/test/e2e/issues.spec.ts +++ b/test/e2e/issues.spec.ts @@ -525,8 +525,10 @@ describe('Issues', function () { expect(result).toEqual('\n[2,12] foo') }) it("memoryLimit doesn't work in for tag #776", () => { - const engine = new Liquid() + const engine = new Liquid({ + memoryLimit: 1e5 + }) const tpl = `{% for i in (1..1000000000) %} {{'a'}} {% endfor %}` - expect(() => engine.parseAndRenderSync(tpl)).toThrow(/memory out/) + expect(() => engine.parseAndRenderSync(tpl)).toThrow('memory alloc limit exceeded, line:1, col:1') }) }) diff --git a/test/integration/tags/for.spec.ts b/test/integration/tags/for.spec.ts index fe16deb37..53a8678f1 100644 --- a/test/integration/tags/for.spec.ts +++ b/test/integration/tags/for.spec.ts @@ -1,6 +1,7 @@ import { Liquid } from '../../../src/liquid' import { Drop } from '../../../src/drop/drop' import { Scope } from '../../../src/context/scope' +import { mock, restore } from '../../stub/mockfs' describe('tags/for', function () { let liquid: Liquid, scope: Scope @@ -139,6 +140,7 @@ describe('tags/for', function () { }) describe('continue', function () { + afterEach(restore) it('should support for with continue', async function () { const src = '{% for i in (1..5) %}' + '{% if i == 4 %}continue{% continue %}{% endif %}{{i}}' + @@ -154,6 +156,28 @@ describe('tags/for', function () { const html = await liquid.parseAndRender(src, scope) return expect(html).toBe('123continue5') }) + it('should skip snippet for rendered continue', async function () { + mock({ + 'snippet.liquid': ' before{% continue %}skipped' + }) + const src = '{% for i in (1..2) %}' + + '{% render "snippet.liquid" %}' + + ' after' + + '{% endfor %}' + const html = await liquid.parseAndRender(src, scope) + return expect(html).toBe(' before after before after') + }) + it('should skip `for` body for included continue', async function () { + mock({ + 'snippet.liquid': ' before{% continue %}skipped' + }) + const src = '{% for i in (1..2) %}' + + '{% include "snippet.liquid" %}' + + ' after' + + '{% endfor %}' + const html = await liquid.parseAndRender(src, scope) + return expect(html).toBe(' before before') + }) }) describe('break', function () { it('should support break', async function () {