mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-16 12:50:38 -07:00
fix(context): use null-prototype scope and register objects
Add createScope(); use for bottom scope, spawn default, getAll merge, ctx.push frames, filter loops, include/layout blocks registers, and cycle groups. registers uses Object.create(null) and getRegister uses ??.
For-loop continue register defaults to 0 (not {}): Array.slice coerces plain {} but not null-prototype objects.
Export createScope from the package entry.
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -216,4 +216,12 @@ describe('Context', function () {
|
||||
expect(ctx.getSync(['foo'])).toEqual('zoo')
|
||||
})
|
||||
})
|
||||
describe('scope storage', function () {
|
||||
it('should use null prototype for bottom scope', function () {
|
||||
expect(Object.getPrototypeOf(new Context().bottom())).toBeNull()
|
||||
})
|
||||
it('should use null prototype for getAll() merge result', function () {
|
||||
expect(Object.getPrototypeOf(new Context({ a: 1 }).getAll())).toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@ import { getPerformance } from '../util/performance'
|
||||
import { Drop } from '../drop/drop'
|
||||
import { __assign } from 'tslib'
|
||||
import { NormalizedFullOptions, defaultOptions, RenderOptions } from '../liquid-options'
|
||||
import { Scope } from './scope'
|
||||
import { Scope, createScope } from './scope'
|
||||
import { hasOwnProperty, isArray, isNil, isUndefined, isString, isFunction, toLiquid, InternalUndefinedVariableError, toValueSync, isObject, Limiter, toValue } from '../util'
|
||||
|
||||
type PropertyKey = string | number;
|
||||
@@ -12,8 +12,8 @@ export class Context {
|
||||
* insert a Context-level empty scope,
|
||||
* for tags like `{% capture %}` `{% assign %}` to operate
|
||||
*/
|
||||
private scopes: Scope[] = [{}]
|
||||
private registers = {}
|
||||
private scopes: Scope[] = [createScope()]
|
||||
private registers: Record<string, any> = Object.create(null)
|
||||
/**
|
||||
* user passed in scope
|
||||
* `{% increment %}`, `{% decrement %}` changes this scope,
|
||||
@@ -49,7 +49,7 @@ export class Context {
|
||||
this.renderLimit = renderLimit ?? new Limiter('template render', getPerformance().now() + (renderOptions.renderLimit ?? opts.renderLimit))
|
||||
}
|
||||
public getRegister<T> (key: string, defaultValue: T = undefined as T): T {
|
||||
return (this.registers[key] = this.registers[key] || defaultValue)
|
||||
return (this.registers[key] = this.registers[key] ?? defaultValue)
|
||||
}
|
||||
public setRegister (key: string, value: any) {
|
||||
return (this.registers[key] = value)
|
||||
@@ -62,7 +62,7 @@ export class Context {
|
||||
}
|
||||
public getAll () {
|
||||
return [this.globals, this.environments, ...this.scopes]
|
||||
.reduce((ctx, val) => __assign(ctx, val), {})
|
||||
.reduce((ctx, val) => __assign(ctx, val), createScope())
|
||||
}
|
||||
/**
|
||||
* @deprecated use `_get()` or `getSync()` instead
|
||||
@@ -102,7 +102,7 @@ export class Context {
|
||||
public bottom () {
|
||||
return this.scopes[0]
|
||||
}
|
||||
public spawn (scope = {}) {
|
||||
public spawn (scope: object = createScope()) {
|
||||
return new Context(scope, this.opts, {
|
||||
sync: this.sync,
|
||||
globals: this.globals,
|
||||
|
||||
@@ -5,3 +5,13 @@ interface ScopeObject extends Record<string | number | symbol, any> {
|
||||
}
|
||||
|
||||
export type Scope = ScopeObject | Drop
|
||||
|
||||
/**
|
||||
* Plain scope bag with a null prototype so lookups like `__proto__` are not the
|
||||
* Object.prototype accessor unless explicitly assigned as an own property.
|
||||
*/
|
||||
export function createScope (props?: Record<PropertyKey, any>): ScopeObject {
|
||||
return props == null
|
||||
? Object.create(null)
|
||||
: Object.assign(Object.create(null), props)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user