fix: enumerate plain objects in where/where_exp, #785 (#788)

This commit is contained in:
Jun Yang
2025-01-04 23:41:25 +08:00
committed by GitHub
parent 59cf3c08db
commit 25ef104446
4 changed files with 60 additions and 3 deletions
+1
View File
@@ -1,6 +1,7 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */ /** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = { module.exports = {
preset: 'ts-jest', preset: 'ts-jest',
maxWorkers: '50%',
testEnvironment: 'node', testEnvironment: 'node',
testMatch: ['**/*.spec.ts'], testMatch: ['**/*.spec.ts'],
collectCoverageFrom: [ collectCoverageFrom: [
+3 -3
View File
@@ -1,4 +1,4 @@
import { toArray, argumentsToValue, toValue, stringify, caseInsensitiveCompare, isArray, isNil, last as arrayLast, isArrayLike } from '../util' import { toArray, argumentsToValue, toValue, stringify, caseInsensitiveCompare, isArray, isNil, last as arrayLast, isArrayLike, toEnumerable } from '../util'
import { arrayIncludes, equals, evalToken, isTruthy } from '../render' import { arrayIncludes, equals, evalToken, isTruthy } from '../render'
import { Value, FilterImpl } from '../template' import { Value, FilterImpl } from '../template'
import { Tokenizer } from '../parser' import { Tokenizer } from '../parser'
@@ -148,7 +148,7 @@ export function * where_exp<T extends object> (this: FilterImpl, arr: T[], itemN
export function * group_by<T extends object> (this: FilterImpl, arr: T[], property: string): IterableIterator<unknown> { export function * group_by<T extends object> (this: FilterImpl, arr: T[], property: string): IterableIterator<unknown> {
const map = new Map() const map = new Map()
arr = toArray(arr) arr = toEnumerable(arr)
const token = new Tokenizer(stringify(property)).readScopeValue() const token = new Tokenizer(stringify(property)).readScopeValue()
this.context.memoryLimit.use(arr.length) this.context.memoryLimit.use(arr.length)
for (const item of arr) { for (const item of arr) {
@@ -162,7 +162,7 @@ export function * group_by<T extends object> (this: FilterImpl, arr: T[], proper
export function * group_by_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> { export function * group_by_exp<T extends object> (this: FilterImpl, arr: T[], itemName: string, exp: string): IterableIterator<unknown> {
const map = new Map() const map = new Map()
const keyTemplate = new Value(stringify(exp), this.liquid) const keyTemplate = new Value(stringify(exp), this.liquid)
arr = toArray(arr) arr = toEnumerable(arr)
this.context.memoryLimit.use(arr.length) this.context.memoryLimit.use(arr.length)
for (const item of arr) { for (const item of arr) {
const key = yield keyTemplate.value(this.context.spawn({ [itemName]: item })) const key = yield keyTemplate.value(this.context.spawn({ [itemName]: item }))
+27
View File
@@ -531,4 +531,31 @@ describe('Issues', function () {
const tpl = `{% for i in (1..1000000000) %} {{'a'}} {% endfor %}` const tpl = `{% for i in (1..1000000000) %} {{'a'}} {% endfor %}`
expect(() => engine.parseAndRenderSync(tpl)).toThrow('memory alloc limit exceeded, line:1, col:1') expect(() => engine.parseAndRenderSync(tpl)).toThrow('memory alloc limit exceeded, line:1, col:1')
}) })
it('group_by_exp fails with object as input #785', () => {
const site = {
tags: {
CPP: [ 'page0' ],
PHP: [ 'page0', 'page2' ],
JavaScript: [ 'page1', 'page2', 'page3' ],
CSharp: [ 'page2', 'page4' ]
}
}
const tpl = `
{%- assign tags_by_size = site.tags | group_by_exp: 'tag', 'tag[1].size' | sort: 'name' | reverse -%}
{%- for tags_with_size in tags_by_size -%}
{%- for tag in tags_with_size.items -%}
{%- assign tag_name = tag[0] %}
{{ tag_name }} <sup>{{ tags_with_size.name }}</sup> Posts:
{%- for post in tag[1] -%}{{post}},{%- endfor -%}
{%- endfor -%}
{%- endfor -%}
`
const engine = new Liquid()
const html = engine.parseAndRenderSync(tpl, { site })
expect(html).toEqual(`
JavaScript <sup>3</sup> Posts:page1,page2,page3,
PHP <sup>2</sup> Posts:page0,page2,
CSharp <sup>2</sup> Posts:page2,page4,
CPP <sup>1</sup> Posts:page0,`)
})
}) })
+29
View File
@@ -554,6 +554,12 @@ describe('filters/array', function () {
{ graduation_year: 2014, name: 'John' }, { graduation_year: 2014, name: 'John' },
{ graduation_year: 2009, name: 'Jack' } { graduation_year: 2009, name: 'Jack' }
] ]
const postsByTags = {
CPP: [ 'page0' ],
PHP: [ 'page0', 'page2' ],
JavaScript: [ 'page1', 'page2', 'page3' ],
CSharp: [ 'page2', 'page4' ]
}
it('should support group by expression', function () { it('should support group by expression', function () {
const expected = [{ const expected = [{
name: '201', name: '201',
@@ -572,6 +578,29 @@ describe('filters/array', function () {
{ members }, { members },
JSON.stringify(expected)) JSON.stringify(expected))
}) })
it('should group key/values in plain object', function () {
const expected = [{
name: 3,
items: [
['JavaScript', ['page1', 'page2', 'page3']]
]
}, {
name: 2,
items: [
['PHP', ['page0', 'page2']],
['CSharp', ['page2', 'page4']]
]
}, {
name: 1,
items: [
['CPP', ['page0']]
]
}]
return test(
`{{ postsByTags | group_by_exp: "tag", "tag[1].size" | sort: 'name' | reverse | json}}`,
{ postsByTags },
JSON.stringify(expected))
})
}) })
describe('find', function () { describe('find', function () {
const members = [ const members = [