Skip to content

Commit 47d3f1b

Browse files
harttlecursoragent
andauthored
feat(context): null-prototype scope frames via createScope (#899)
- Add createScope() building Object.create(null) with optional own props - Initialize context stack bottom with createScope() for assign/capture - Push null-proto scopes from for, tablerow, block, layout, include (incl. Jekyll) Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent c20c0af commit 47d3f1b

7 files changed

Lines changed: 21 additions & 13 deletions

File tree

src/context/context.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { getPerformance } from '../util/performance'
22
import { Drop } from '../drop/drop'
33
import { __assign } from 'tslib'
44
import { NormalizedFullOptions, defaultOptions, RenderOptions } from '../liquid-options'
5-
import { Scope } from './scope'
5+
import { createScope, Scope } from './scope'
66
import { hasOwnProperty, isArray, isNil, isUndefined, isString, isFunction, toLiquid, InternalUndefinedVariableError, toValueSync, isObject, Limiter, toValue } from '../util'
77

88
type PropertyKey = string | number;
@@ -12,7 +12,7 @@ export class Context {
1212
* insert a Context-level empty scope,
1313
* for tags like `{% capture %}` `{% assign %}` to operate
1414
*/
15-
private scopes: Scope[] = [{}]
15+
private scopes: Scope[] = [createScope()]
1616
private registers = {}
1717
/**
1818
* user passed in scope

src/context/scope.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { Drop } from '../drop/drop'
22

3-
interface ScopeObject extends Record<string | number | symbol, any> {
3+
export interface ScopeObject extends Record<string | number | symbol, any> {
44
toLiquid?: () => any;
55
}
66

77
export type Scope = ScopeObject | Drop
8+
9+
export function createScope (from?: ScopeObject): ScopeObject {
10+
const scope = Object.create(null)
11+
if (from) Object.assign(scope, from)
12+
return scope
13+
}

src/tags/block.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { BlockMode } from '../context'
1+
import { BlockMode, createScope } from '../context'
22
import { isTagToken } from '../util'
33
import { BlockDrop } from '../drop'
44
import { Liquid, TagToken, TopLevelToken, Template, Context, Emitter, Tag } from '..'
@@ -38,7 +38,7 @@ export default class extends Tag {
3838
if (stack.includes(self)) throw new Error('block tag cannot be nested')
3939

4040
stack.push(self)
41-
ctx.push({ block: superBlock })
41+
ctx.push(createScope({ block: superBlock }))
4242
yield liquid.renderer.renderTemplates(templates, ctx, emitter)
4343
ctx.pop()
4444
stack.pop()

src/tags/for.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Hash, ValueToken, Liquid, Tag, evalToken, Emitter, TagToken, TopLevelToken, Context, Template, ParseStream } from '..'
22
import { assertEmpty, isValueToken, toEnumerable } from '../util'
3+
import { createScope } from '../context/scope'
34
import { ForloopDrop } from '../drop/forloop-drop'
45
import { Parser } from '../parser'
56
import { Arguments } from '../template'
@@ -50,7 +51,7 @@ export default class extends Tag {
5051
}
5152

5253
const continueKey = 'continue-' + this.variable + '-' + this.collection.getText()
53-
ctx.push({ continue: ctx.getRegister(continueKey, {}) })
54+
ctx.push(createScope({ continue: ctx.getRegister(continueKey, {}) }))
5455
const hash = yield this.hash.render(ctx)
5556
ctx.pop()
5657

@@ -65,7 +66,7 @@ export default class extends Tag {
6566
}, collection)
6667

6768
ctx.setRegister(continueKey, (hash['offset'] || 0) + collection.length)
68-
const scope = { forloop: new ForloopDrop(collection.length, this.collection.getText(), this.variable) }
69+
const scope = createScope({ forloop: new ForloopDrop(collection.length, this.collection.getText(), this.variable) })
6970
ctx.push(scope)
7071
for (const item of collection) {
7172
scope[this.variable] = item

src/tags/include.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Template, ValueToken, TopLevelToken, Liquid, Tag, assert, evalToken, Hash, Emitter, TagToken, Context } from '..'
2-
import { BlockMode, Scope } from '../context'
2+
import { BlockMode, createScope, Scope } from '../context'
33
import { Parser } from '../parser'
44
import { Argument, Arguments, PartialScope } from '../template'
55
import { isString, isValueToken } from '../util'
@@ -34,10 +34,10 @@ export default class extends Tag {
3434
const saved = ctx.saveRegister('blocks', 'blockMode')
3535
ctx.setRegister('blocks', {})
3636
ctx.setRegister('blockMode', BlockMode.OUTPUT)
37-
const scope = (yield hash.render(ctx)) as Scope
37+
const scope = createScope((yield hash.render(ctx)) as Scope)
3838
if (withVar) scope[filepath] = yield evalToken(withVar, ctx)
3939
const templates = (yield liquid._parsePartialFile(filepath, ctx.sync, this['currentFile'])) as Template[]
40-
ctx.push(ctx.opts.jekyllInclude ? { include: scope } : scope)
40+
ctx.push(ctx.opts.jekyllInclude ? createScope({ include: scope }) : scope)
4141
yield renderer.renderTemplates(templates, ctx, emitter)
4242
ctx.pop()
4343
ctx.restoreRegister(saved)

src/tags/layout.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Scope, Template, Liquid, Tag, assert, Emitter, Hash, TagToken, TopLevelToken, Context } from '..'
2-
import { BlockMode } from '../context'
2+
import { BlockMode, createScope } from '../context'
33
import { parseFilePath, renderFilePath, ParsedFileName } from './render'
44
import { BlankDrop } from '../drop'
55
import { Parser } from '../parser'
@@ -39,7 +39,7 @@ export default class extends Tag {
3939
ctx.setRegister('blockMode', BlockMode.OUTPUT)
4040

4141
// render the layout file use stored blocks
42-
ctx.push((yield args.render(ctx)) as Scope)
42+
ctx.push(createScope((yield args.render(ctx)) as Scope))
4343
yield renderer.renderTemplates(templates, ctx, emitter)
4444
ctx.pop()
4545
}

src/tags/tablerow.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isValueToken, toEnumerable } from '../util'
2+
import { createScope } from '../context/scope'
23
import { ValueToken, Liquid, Tag, evalToken, Emitter, Hash, TagToken, TopLevelToken, Context, Template, ParseStream } from '..'
34
import { TablerowloopDrop } from '../drop/tablerowloop-drop'
45
import { Parser } from '../parser'
@@ -48,7 +49,7 @@ export default class extends Tag {
4849

4950
const r = this.liquid.renderer
5051
const tablerowloop = new TablerowloopDrop(collection.length, cols, this.collection.getText(), this.variable)
51-
const scope = { tablerowloop }
52+
const scope = createScope({ tablerowloop })
5253
ctx.push(scope)
5354

5455
for (let idx = 0; idx < collection.length; idx++, tablerowloop.next()) {

0 commit comments

Comments
 (0)