From f15e642b6402c602200f109b3ce89eabfe58d7a7 Mon Sep 17 00:00:00 2001 From: Yang Jun Date: Mon, 6 Oct 2025 18:33:03 +0800 Subject: [PATCH] feat: allow context access in liquidMethodMissing, #808 --- src/context/context.ts | 28 +++++++++++++-------------- src/drop/drop.ts | 4 +++- test/e2e/drop.spec.ts | 44 +++++++++++++++++++++++++++--------------- 3 files changed, 45 insertions(+), 31 deletions(-) diff --git a/src/context/context.ts b/src/context/context.ts index d23381b6a7..ce396fe36e 100644 --- a/src/context/context.ts +++ b/src/context/context.ts @@ -86,7 +86,7 @@ export class Context { public * _getFromScope (scope: unknown, paths: (PropertyKey | Drop)[] | string, strictVariables = this.strictVariables): IterableIterator { if (isString(paths)) paths = paths.split('.') for (let i = 0; i < paths.length; i++) { - scope = yield readProperty(scope as object, paths[i], this.ownPropertyOnly) + scope = yield this.readProperty(scope as object, paths[i]) if (strictVariables && isUndefined(scope)) { throw new InternalUndefinedVariableError((paths as string[]).slice(0, i + 1).join!('.')) } @@ -120,21 +120,21 @@ export class Context { if (key in this.environments) return this.environments return this.globals } + readProperty (obj: Scope, key: (PropertyKey | Drop)) { + obj = toLiquid(obj) + key = toValue(key) as PropertyKey + if (isNil(obj)) return obj + if (isArray(obj) && (key as number) < 0) return obj[obj.length + +key] + const value = readJSProperty(obj, key, this.ownPropertyOnly) + if (value === undefined && obj instanceof Drop) return obj.liquidMethodMissing(key, this) + if (isFunction(value)) return value.call(obj) + if (key === 'size') return readSize(obj) + else if (key === 'first') return readFirst(obj) + else if (key === 'last') return readLast(obj) + return value + } } -export function readProperty (obj: Scope, key: (PropertyKey | Drop), ownPropertyOnly: boolean) { - obj = toLiquid(obj) - key = toValue(key) as PropertyKey - if (isNil(obj)) return obj - if (isArray(obj) && (key as number) < 0) return obj[obj.length + +key] - const value = readJSProperty(obj, key, ownPropertyOnly) - if (value === undefined && obj instanceof Drop) return obj.liquidMethodMissing(key) - if (isFunction(value)) return value.call(obj) - if (key === 'size') return readSize(obj) - else if (key === 'first') return readFirst(obj) - else if (key === 'last') return readLast(obj) - return value -} export function readJSProperty (obj: Scope, key: PropertyKey, ownPropertyOnly: boolean) { if (ownPropertyOnly && !hasOwnProperty.call(obj, key) && !(obj instanceof Drop)) return undefined return obj[key] diff --git a/src/drop/drop.ts b/src/drop/drop.ts index 74b4e152fd..c62768dc2f 100644 --- a/src/drop/drop.ts +++ b/src/drop/drop.ts @@ -1,5 +1,7 @@ +import { Context } from '../context' + export abstract class Drop { - public liquidMethodMissing (key: string | number): Promise | any { + public liquidMethodMissing (key: string | number, context: Context): Promise | any { return undefined } } diff --git a/test/e2e/drop.spec.ts b/test/e2e/drop.spec.ts index d60b425a54..fe35f78c6c 100644 --- a/test/e2e/drop.spec.ts +++ b/test/e2e/drop.spec.ts @@ -1,25 +1,37 @@ -import { Drop, Liquid } from '../..' - -class SettingsDrop extends Drop { - public foo = 'FOO' - public bar () { - return 'BAR' - } - public liquidMethodMissing (key: string) { - return key.toUpperCase() - } -} +import { Context, Drop, Liquid } from '../..' describe('drop', function () { - const settings = new SettingsDrop() let engine: Liquid beforeEach(function () { engine = new Liquid() }) - it('should support liquidMethodMissing', async function () { - const src = `{{settings.foo}},{{settings.bar}},{{settings.coo}}` - const html = await engine.parseAndRender(src, { settings }) - return expect(html).toBe('FOO,BAR,COO') + + describe('liquidMethodMissing', () => { + it('should support liquidMethodMissing', async function () { + class SettingsDrop extends Drop { + public foo = 'FOO' + public bar () { + return 'BAR' + } + public liquidMethodMissing (key: string) { + return key.toUpperCase() + } + } + const src = `{{settings.foo}},{{settings.bar}},{{settings.coo}}` + const html = await engine.parseAndRender(src, { settings: new SettingsDrop() }) + return expect(html).toBe('FOO,BAR,COO') + }) + + it('should expose context', async function () { + class SettingsDrop extends Drop { + public liquidMethodMissing (key: string, context: Context) { + return key + ':' + context.getSync([key]) + } + } + const src = `{{settings.foo}}` + const html = await engine.parseAndRender(src, { settings: new SettingsDrop(), foo: 'FOO' }) + return expect(html).toBe('foo:FOO') + }) }) describe('BlandDrop', function () {