From 20198c7f19b09dbf71113537f68bb5a5f739698d Mon Sep 17 00:00:00 2001 From: Yang Jun Date: Tue, 14 Jul 2026 21:48:17 +0800 Subject: [PATCH] refactor: track maxDepth via depthLimit Limiter on Context Replace increaseDepth/decreaseDepth with a shared Limiter that supports paired use/release, matching templateLimit and outputLengthLimit patterns. Co-authored-by: Cursor --- src/context/context.ts | 17 +++++------------ src/tags/include.ts | 4 ++-- src/tags/layout.ts | 4 ++-- src/tags/render.ts | 4 ++-- src/util/limiter.ts | 5 +++++ 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/context/context.ts b/src/context/context.ts index 63af8c993..fbc17f8d8 100644 --- a/src/context/context.ts +++ b/src/context/context.ts @@ -2,7 +2,7 @@ import { Drop } from '../drop/drop' import { __assign } from 'tslib' import { NormalizedFullOptions, defaultOptions, RenderOptions } from '../liquid-options' import { createScope, Scope } from './scope' -import { hasOwnProperty, isArray, isNil, isUndefined, isString, isFunction, isNumber, toLiquid, InternalUndefinedVariableError, toValueSync, isObject, Limiter, toValue, readArrayElement, assert } from '../util' +import { hasOwnProperty, isArray, isNil, isUndefined, isString, isFunction, isNumber, toLiquid, InternalUndefinedVariableError, toValueSync, isObject, Limiter, toValue, readArrayElement } from '../util' type PropertyKey = string | number; @@ -37,8 +37,8 @@ export class Context { public ownPropertyOnly: boolean; public templateLimit: Limiter; public outputLengthLimit: Limiter; - public depth: number; - public constructor (env: object = {}, opts: NormalizedFullOptions = defaultOptions, renderOptions: RenderOptions = {}, { templateLimit, outputLengthLimit, depth }: { templateLimit?: Limiter, outputLengthLimit?: Limiter, depth?: number } = {}) { + public depthLimit: Limiter; + public constructor (env: object = {}, opts: NormalizedFullOptions = defaultOptions, renderOptions: RenderOptions = {}, { templateLimit, outputLengthLimit, depthLimit }: { templateLimit?: Limiter, outputLengthLimit?: Limiter, depthLimit?: Limiter } = {}) { this.sync = !!renderOptions.sync this.opts = opts this.globals = renderOptions.globals ?? opts.globals @@ -47,14 +47,7 @@ export class Context { this.ownPropertyOnly = renderOptions.ownPropertyOnly ?? opts.ownPropertyOnly this.templateLimit = templateLimit ?? new Limiter('template', renderOptions.templateLimit ?? opts.templateLimit) this.outputLengthLimit = outputLengthLimit ?? new Limiter('output length', renderOptions.outputLengthLimit ?? opts.outputLengthLimit) - this.depth = depth ?? 0 - } - public increaseDepth () { - assert(this.depth < this.opts.maxDepth, 'template depth limit exceeded') - this.depth++ - } - public decreaseDepth () { - this.depth-- + this.depthLimit = depthLimit ?? new Limiter('template depth', opts.maxDepth) } public getRegister (key: string, defaultValue: T = undefined as T): T { return (this.registers[key] = this.registers[key] || defaultValue) @@ -119,7 +112,7 @@ export class Context { }, { templateLimit: this.templateLimit, outputLengthLimit: this.outputLengthLimit, - depth: this.depth + depthLimit: this.depthLimit }) } private findScope (key: string | number) { diff --git a/src/tags/include.ts b/src/tags/include.ts index 0a98ee989..3b53d0ad6 100644 --- a/src/tags/include.ts +++ b/src/tags/include.ts @@ -28,7 +28,7 @@ export default class extends Tag { this.hash = new Hash(tokenizer, liquid.options.jekyllInclude || liquid.options.keyValueSeparator) } * render (ctx: Context, emitter: Emitter): Generator { - ctx.increaseDepth() + ctx.depthLimit.use(1) try { const { liquid, hash, withVar } = this const { renderer } = liquid @@ -46,7 +46,7 @@ export default class extends Tag { ctx.pop() ctx.restoreRegister(saved) } finally { - ctx.decreaseDepth() + ctx.depthLimit.release(1) } } diff --git a/src/tags/layout.ts b/src/tags/layout.ts index bff09cfae..3c1de4a02 100644 --- a/src/tags/layout.ts +++ b/src/tags/layout.ts @@ -26,7 +26,7 @@ export default class extends Tag { yield renderer.renderTemplates(this.templates, ctx, emitter) return } - ctx.increaseDepth() + ctx.depthLimit.use(1) try { const filepath = (yield renderFilePath(this.file, ctx, liquid)) as string assert(filepath, () => `illegal file path "${filepath}"`) @@ -46,7 +46,7 @@ export default class extends Tag { yield renderer.renderTemplates(templates, ctx, emitter) ctx.pop() } finally { - ctx.decreaseDepth() + ctx.depthLimit.release(1) } } diff --git a/src/tags/render.ts b/src/tags/render.ts index 221d15b16..311377a22 100644 --- a/src/tags/render.ts +++ b/src/tags/render.ts @@ -55,7 +55,7 @@ export default class extends Tag { this.hash = new Hash(tokenizer, liquid.options.keyValueSeparator) } * render (ctx: Context, emitter: Emitter): Generator { - ctx.increaseDepth() + ctx.depthLimit.use(1) try { const { liquid, hash } = this const filepath = (yield renderFilePath(this.file, ctx, liquid)) as string @@ -84,7 +84,7 @@ export default class extends Tag { yield liquid.renderer.renderTemplates(templates, childCtx, emitter) } } finally { - ctx.decreaseDepth() + ctx.depthLimit.release(1) } } diff --git a/src/util/limiter.ts b/src/util/limiter.ts index f241f5e53..b5b5ae299 100644 --- a/src/util/limiter.ts +++ b/src/util/limiter.ts @@ -14,6 +14,11 @@ export class Limiter { this.base += +count } } + release (count: number) { + if (+count > 0) { + this.base -= +count + } + } check (count: number) { if (+count > 0) { assert(+count <= this.limit, this.message)