mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-16 04:40:39 -07:00
refactor: remove createScope helper
Drop the exported helper and finish migrating call sites. Revert incidental context/for/include/layout churn so behavior matches mainline aside from the removal. Trim duplicate e2e and heavy Object.prototype loops in registry tests. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -138,11 +138,6 @@ describe('Context', function () {
|
||||
ctx.push({ foo: [] })
|
||||
return expect(ctx.getSync(['foo', 'reduce'])).toEqual(undefined)
|
||||
})
|
||||
it('should return undefined for typical Object.prototype properties (e.g. constructor, valueOf)', function () {
|
||||
ctx.push({ obj: {} })
|
||||
expect(ctx.getSync(['obj', 'constructor'])).toEqual(undefined)
|
||||
expect(ctx.getSync(['obj', 'valueOf'])).toEqual(undefined)
|
||||
})
|
||||
it('should return undefined for function prototype property', function () {
|
||||
function Foo () {}
|
||||
Foo.prototype.bar = 'BAR'
|
||||
|
||||
@@ -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, createScope } from './scope'
|
||||
import { Scope } from './scope'
|
||||
import { hasOwnProperty, isArray, isNil, isUndefined, isString, isFunction, toLiquid, InternalUndefinedVariableError, toValueSync, isObject, Limiter, toValue } from '../util'
|
||||
|
||||
type PropertyKey = string | number;
|
||||
@@ -12,7 +12,7 @@ export class Context {
|
||||
* insert a Context-level empty scope,
|
||||
* for tags like `{% capture %}` `{% assign %}` to operate
|
||||
*/
|
||||
private scopes: Scope[] = [createScope()]
|
||||
private scopes: Scope[] = [{}]
|
||||
private registers = {}
|
||||
/**
|
||||
* user passed in scope
|
||||
@@ -62,7 +62,7 @@ export class Context {
|
||||
}
|
||||
public getAll () {
|
||||
return [this.globals, this.environments, ...this.scopes]
|
||||
.reduce((ctx, val) => __assign(ctx, val), createScope())
|
||||
.reduce((ctx, val) => __assign(ctx, val), {})
|
||||
}
|
||||
/**
|
||||
* @deprecated use `_get()` or `getSync()` instead
|
||||
@@ -102,7 +102,7 @@ export class Context {
|
||||
public bottom () {
|
||||
return this.scopes[0]
|
||||
}
|
||||
public spawn (scope: object = createScope()) {
|
||||
public spawn (scope = {}) {
|
||||
return new Context(scope, this.opts, {
|
||||
sync: this.sync,
|
||||
globals: this.globals,
|
||||
|
||||
@@ -5,13 +5,3 @@ 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)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { toArray, argumentsToValue, toValue, stringify, caseInsensitiveCompare,
|
||||
import { arrayIncludes, equals, evalToken, isTruthy } from '../render'
|
||||
import { Value, FilterImpl } from '../template'
|
||||
import { Tokenizer } from '../parser'
|
||||
import { createScope, type Scope } from '../context'
|
||||
import type { Scope } from '../context'
|
||||
import { EmptyDrop } from '../drop'
|
||||
|
||||
export const join = argumentsToValue(function (this: FilterImpl, v: any[], arg: string) {
|
||||
@@ -139,7 +139,7 @@ function * filter_exp<T extends object> (this: FilterImpl, include: boolean, arr
|
||||
const array = toArray(arr)
|
||||
this.context.memoryLimit.use(array.length)
|
||||
for (const item of array) {
|
||||
this.context.push(createScope({ [itemName]: item }))
|
||||
this.context.push({ [itemName]: item })
|
||||
const value = yield keyTemplate.value(this.context)
|
||||
this.context.pop()
|
||||
if (value === include) filtered.push(item)
|
||||
@@ -182,7 +182,7 @@ export function * group_by_exp<T extends object> (this: FilterImpl, arr: T[], it
|
||||
arr = toEnumerable(arr)
|
||||
this.context.memoryLimit.use(arr.length)
|
||||
for (const item of arr) {
|
||||
this.context.push(createScope({ [itemName]: item }))
|
||||
this.context.push({ [itemName]: item })
|
||||
const key = yield keyTemplate.value(this.context)
|
||||
this.context.pop()
|
||||
if (!map.has(key)) map.set(key, [])
|
||||
@@ -205,7 +205,7 @@ function * search_exp<T extends object> (this: FilterImpl, arr: T[], itemName: s
|
||||
const predicate = new Value(stringify(exp), this.liquid)
|
||||
const array = toArray(arr)
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
this.context.push(createScope({ [itemName]: array[index] }))
|
||||
this.context.push({ [itemName]: array[index] })
|
||||
const value = yield predicate.value(this.context)
|
||||
this.context.pop()
|
||||
if (value) return [index, array[index]]
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ export { Drop } from './drop'
|
||||
export type { Comparable } from './drop'
|
||||
export { Emitter } from './emitters'
|
||||
export { defaultOperators, Operators, evalToken, evalQuotedToken, Expression, isFalsy, isTruthy } from './render'
|
||||
export { Context, Scope, createScope } from './context'
|
||||
export { Context, Scope } from './context'
|
||||
export { Value, Hash, Template, FilterImplOptions, Tag, Filter, Output, Variable, VariableLocation, VariableSegments, Variables, StaticAnalysis, StaticAnalysisOptions, analyze, analyzeSync, Arguments, PartialScope } from './template'
|
||||
export type { TagRenderReturn } from './template'
|
||||
export { Token, TopLevelToken, TagToken, ValueToken } from './tokens'
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import { BlockMode, createScope } from '../context'
|
||||
import { BlockMode } from '../context'
|
||||
import { isTagToken } from '../util'
|
||||
import { BlockDrop } from '../drop'
|
||||
import { Liquid, TagToken, TopLevelToken, Template, Context, Emitter, Tag } from '..'
|
||||
@@ -38,7 +38,7 @@ export default class extends Tag {
|
||||
if (stack.includes(self)) throw new Error('block tag cannot be nested')
|
||||
|
||||
stack.push(self)
|
||||
ctx.push(createScope({ block: superBlock }))
|
||||
ctx.push({ block: superBlock })
|
||||
yield liquid.renderer.renderTemplates(templates, ctx, emitter)
|
||||
ctx.pop()
|
||||
stack.pop()
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
import { Hash, ValueToken, Liquid, Tag, evalToken, Emitter, TagToken, TopLevelToken, Context, Template, ParseStream, createScope } from '..'
|
||||
import { Hash, ValueToken, Liquid, Tag, evalToken, Emitter, TagToken, TopLevelToken, Context, Template, ParseStream } from '..'
|
||||
import { assertEmpty, isValueToken, toEnumerable } from '../util'
|
||||
import { ForloopDrop } from '../drop/forloop-drop'
|
||||
import { Parser } from '../parser'
|
||||
@@ -50,7 +50,7 @@ export default class extends Tag {
|
||||
}
|
||||
|
||||
const continueKey = 'continue-' + this.variable + '-' + this.collection.getText()
|
||||
ctx.push(createScope({ continue: ctx.getRegister(continueKey, 0) }))
|
||||
ctx.push({ continue: ctx.getRegister(continueKey, {}) })
|
||||
const hash = yield this.hash.render(ctx)
|
||||
ctx.pop()
|
||||
|
||||
@@ -65,7 +65,7 @@ export default class extends Tag {
|
||||
}, collection)
|
||||
|
||||
ctx.setRegister(continueKey, (hash['offset'] || 0) + collection.length)
|
||||
const scope = createScope({ forloop: new ForloopDrop(collection.length, this.collection.getText(), this.variable) })
|
||||
const scope = { forloop: new ForloopDrop(collection.length, this.collection.getText(), this.variable) }
|
||||
ctx.push(scope)
|
||||
for (const item of collection) {
|
||||
scope[this.variable] = item
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import { Template, ValueToken, TopLevelToken, Liquid, Tag, assert, evalToken, Hash, Emitter, TagToken, Context, createScope } from '..'
|
||||
import { Template, ValueToken, TopLevelToken, Liquid, Tag, assert, evalToken, Hash, Emitter, TagToken, Context } from '..'
|
||||
import { BlockMode, Scope } from '../context'
|
||||
import { Parser } from '../parser'
|
||||
import { Argument, Arguments, PartialScope } from '../template'
|
||||
@@ -37,7 +37,7 @@ export default class extends Tag {
|
||||
const scope = (yield hash.render(ctx)) as Scope
|
||||
if (withVar) scope[filepath] = yield evalToken(withVar, ctx)
|
||||
const templates = (yield liquid._parsePartialFile(filepath, ctx.sync, this['currentFile'])) as Template[]
|
||||
ctx.push(ctx.opts.jekyllInclude ? createScope({ include: scope }) : Object.assign(createScope(), scope))
|
||||
ctx.push(ctx.opts.jekyllInclude ? { include: scope } : scope)
|
||||
yield renderer.renderTemplates(templates, ctx, emitter)
|
||||
ctx.pop()
|
||||
ctx.restoreRegister(saved)
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import { Scope, Template, Liquid, Tag, assert, Emitter, Hash, TagToken, TopLevelToken, Context, createScope } from '..'
|
||||
import { Scope, Template, Liquid, Tag, assert, Emitter, Hash, TagToken, TopLevelToken, Context } from '..'
|
||||
import { BlockMode } from '../context'
|
||||
import { parseFilePath, renderFilePath, ParsedFileName } from './render'
|
||||
import { BlankDrop } from '../drop'
|
||||
@@ -39,7 +39,7 @@ export default class extends Tag {
|
||||
ctx.setRegister('blockMode', BlockMode.OUTPUT)
|
||||
|
||||
// render the layout file use stored blocks
|
||||
ctx.push(Object.assign(createScope(), (yield args.render(ctx)) as Scope))
|
||||
ctx.push((yield args.render(ctx)) as Scope)
|
||||
yield renderer.renderTemplates(templates, ctx, emitter)
|
||||
ctx.pop()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { isValueToken, toEnumerable } from '../util'
|
||||
import { ValueToken, Liquid, Tag, evalToken, Emitter, Hash, TagToken, TopLevelToken, Context, Template, ParseStream, createScope } from '..'
|
||||
import { ValueToken, Liquid, Tag, evalToken, Emitter, Hash, TagToken, TopLevelToken, Context, Template, ParseStream } from '..'
|
||||
import { TablerowloopDrop } from '../drop/tablerowloop-drop'
|
||||
import { Parser } from '../parser'
|
||||
import { Arguments } from '../template'
|
||||
@@ -48,7 +48,7 @@ export default class extends Tag {
|
||||
|
||||
const r = this.liquid.renderer
|
||||
const tablerowloop = new TablerowloopDrop(collection.length, cols, this.collection.getText(), this.variable)
|
||||
const scope = createScope({ tablerowloop })
|
||||
const scope = { tablerowloop }
|
||||
ctx.push(scope)
|
||||
|
||||
for (let idx = 0; idx < collection.length; idx++, tablerowloop.next()) {
|
||||
|
||||
@@ -77,22 +77,6 @@ describe('Issues', function () {
|
||||
)
|
||||
expect(html).toBe('BAR')
|
||||
})
|
||||
it('filter/tag maps are null-prototype (node + UMD)', async () => {
|
||||
const nodeEngine = new Liquid()
|
||||
const umdEngine = new LiquidUMD()
|
||||
expect(Object.getPrototypeOf(nodeEngine.filters)).toBeNull()
|
||||
expect(Object.getPrototypeOf(nodeEngine.tags)).toBeNull()
|
||||
expect(Object.getPrototypeOf(umdEngine.filters)).toBeNull()
|
||||
expect(Object.getPrototypeOf(umdEngine.tags)).toBeNull()
|
||||
})
|
||||
it('filter/tag lookups ignore Object.prototype keys unless registered (node + UMD)', async () => {
|
||||
for (const LiquidClass of [Liquid, LiquidUMD]) {
|
||||
const engine = new LiquidClass()
|
||||
await expect(engine.parseAndRender('{{ x | constructor }}', { x: 'OK' })).resolves.toBe('OK')
|
||||
await expect(new LiquidClass({ strictFilters: true }).parseAndRender('{{ x | constructor }}', { x: 'OK' })).rejects.toThrow(/undefined filter/)
|
||||
expect(() => engine.parse('{% constructor %}')).toThrow('tag "constructor" not found')
|
||||
}
|
||||
})
|
||||
it('lenientIf not working as expected in umd #313', async () => {
|
||||
const engine = new LiquidUMD({
|
||||
strictVariables: true,
|
||||
|
||||
@@ -61,19 +61,9 @@ describe('liquid#registerFilter()', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('filter registry storage', () => {
|
||||
it('should use a null-prototype map for filters', () => {
|
||||
expect(Object.getPrototypeOf(liquid.filters)).toBeNull()
|
||||
})
|
||||
it('should treat Object.prototype keys as unregistered unless explicitly registered', async () => {
|
||||
const registered = new Set(Object.keys(liquid.filters))
|
||||
const strict = new Liquid({ strictFilters: true })
|
||||
for (const name of Object.getOwnPropertyNames(Object.prototype)) {
|
||||
if (registered.has(name)) continue
|
||||
const out = await liquid.parseAndRender(`{{ x | ${name} }}`, { x: 42 })
|
||||
expect(out).toBe('42')
|
||||
await expect(strict.parseAndRender(`{{ 1 | ${name} }}`)).rejects.toThrow('undefined filter')
|
||||
}
|
||||
})
|
||||
it('should not treat Object.prototype names as registered filters', async () => {
|
||||
expect(Object.getPrototypeOf(liquid.filters)).toBeNull()
|
||||
await expect(liquid.parseAndRender('{{ x | constructor }}', { x: 42 })).resolves.toBe('42')
|
||||
await expect(new Liquid({ strictFilters: true }).parseAndRender('{{ 1 | constructor }}')).rejects.toThrow('undefined filter')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -39,17 +39,9 @@ describe('liquid#registerTag()', function () {
|
||||
return expect(html).toBe('ABC')
|
||||
})
|
||||
|
||||
describe('tag registry storage', () => {
|
||||
it('should use a null-prototype map for tags', () => {
|
||||
expect(Object.getPrototypeOf(new Liquid().tags)).toBeNull()
|
||||
})
|
||||
it('should not resolve names that exist only on Object.prototype', () => {
|
||||
const l = new Liquid()
|
||||
const registered = new Set(Object.keys(l.tags))
|
||||
for (const name of Object.getOwnPropertyNames(Object.prototype)) {
|
||||
if (registered.has(name)) continue
|
||||
expect(() => l.parse(`{% ${name} %}`)).toThrow(`tag "${name}" not found`)
|
||||
}
|
||||
})
|
||||
it('should not treat Object.prototype names as registered tags', () => {
|
||||
const l = new Liquid()
|
||||
expect(Object.getPrototypeOf(l.tags)).toBeNull()
|
||||
expect(() => l.parse('{% constructor %}')).toThrow('tag "constructor" not found')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user