diff --git a/src/context/context.ts b/src/context/context.ts index 372345a5d..5b0306dcc 100644 --- a/src/context/context.ts +++ b/src/context/context.ts @@ -3,7 +3,7 @@ import { Drop } from '../drop/drop' import { __assign } from 'tslib' import { NormalizedFullOptions, defaultOptions, RenderOptions } from '../liquid-options' import { Scope } from './scope' -import { hasOwnProperty, isArray, isNil, isUndefined, isString, isFunction, toLiquid, InternalUndefinedVariableError, toValueSync, isObject, Limiter } from '../util' +import { hasOwnProperty, isArray, isNil, isUndefined, isString, isFunction, toLiquid, InternalUndefinedVariableError, toValueSync, isObject, Limiter, toValue } from '../util' type PropertyKey = string | number; @@ -71,8 +71,8 @@ export class Context { public getSync (paths: PropertyKey[]): unknown { return toValueSync(this._get(paths)) } - public * _get (paths: PropertyKey[]): IterableIterator { - const scope = this.findScope(paths[0]) + public * _get (paths: (PropertyKey | Drop)[]): IterableIterator { + const scope = this.findScope(paths[0] as string) // first prop should always be a string return yield this._getFromScope(scope, paths) } /** @@ -81,7 +81,7 @@ export class Context { public getFromScope (scope: unknown, paths: PropertyKey[] | string): IterableIterator { return toValueSync(this._getFromScope(scope, paths)) } - public * _getFromScope (scope: unknown, paths: PropertyKey[] | string, strictVariables = this.strictVariables): IterableIterator { + 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) @@ -120,8 +120,9 @@ export class Context { } } -export function readProperty (obj: Scope, key: PropertyKey, ownPropertyOnly: boolean) { +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) diff --git a/src/render/expression.spec.ts b/src/render/expression.spec.ts index a1ad20936..8c73fdf07 100644 --- a/src/render/expression.spec.ts +++ b/src/render/expression.spec.ts @@ -50,6 +50,16 @@ describe('Expression', function () { expect(await toPromise(create('foo[doo["foo"]]').evaluate(ctx, false))).toBe('BAR') expect(await toPromise(create('doo[coo].foo').evaluate(ctx, false))).toBe('bar') }) + it('should support drops in property access', async function () { + class TemplateDrop extends Drop { + valueOf () { return 'bar' } + } + const ctx = new Context({ + foo: { bar: 'BAR' }, + coo: new TemplateDrop() + }) + expect(await toPromise(create('foo[coo]').evaluate(ctx, false))).toBe('BAR') + }) }) describe('simple expression', function () { diff --git a/src/render/expression.ts b/src/render/expression.ts index 9bfca3e30..4131cf6ef 100644 --- a/src/render/expression.ts +++ b/src/render/expression.ts @@ -2,6 +2,7 @@ import { QuotedToken, RangeToken, OperatorToken, Token, PropertyAccessToken, Ope import { isRangeToken, isPropertyAccessToken, UndefinedVariableError, range, isOperatorToken, assert } from '../util' import type { Context } from '../context' import type { UnaryOperatorHandler } from '../render' +import { Drop } from '../drop' export class Expression { private postfix: Token[] @@ -42,9 +43,9 @@ export function * evalToken (token: Token | undefined, ctx: Context, lenient = f } function * evalPropertyAccessToken (token: PropertyAccessToken, ctx: Context, lenient: boolean): IterableIterator { - const props: string[] = [] + const props: (string | number | Drop)[] = [] for (const prop of token.props) { - props.push((yield evalToken(prop, ctx, false)) as unknown as string) + props.push((yield evalToken(prop, ctx, false)) as unknown as string | number | Drop) } try { if (token.variable) {