fix(filters): charge json/inspect replacer by serialized node size

Replace the flat 1-unit charge for non-string JSON nodes with per-type
estimates (primitives via JSON.stringify length, containers by structure).

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Yang Jun
2026-07-06 23:51:42 +08:00
co-authored by Cursor
parent d6c952a6fc
commit 5c9532ada7
+22 -4
View File
@@ -2,6 +2,18 @@ import { isFalsy } from '../render/boolean'
import { identify, isArray, isString, toValue } from '../util/underscore'
import { FilterImpl } from '../template'
function chargeJsonReplacerValue (memoryLimit: { use(count: number): void }, val: unknown) {
if (typeof val === 'string') {
memoryLimit.use(val.length)
} else if (val === null || typeof val === 'number' || typeof val === 'boolean') {
memoryLimit.use(JSON.stringify(val).length)
} else if (Array.isArray(val)) {
memoryLimit.use(val.length + 1)
} else if (typeof val === 'object') {
memoryLimit.use(2)
}
}
function defaultFilter<T1 extends boolean, T2> (this: FilterImpl, value: T1, defaultValue: T2, ...args: Array<[string, any]>): T1 | T2 {
value = toValue(value)
if (isArray(value) || isString(value)) return value.length ? value : defaultValue
@@ -12,7 +24,7 @@ function defaultFilter<T1 extends boolean, T2> (this: FilterImpl, value: T1, def
function json (this: FilterImpl, value: any, space = 0) {
const memoryLimit = this.context.memoryLimit
return JSON.stringify(value, (_key, val) => {
memoryLimit.use(typeof val === 'string' ? val.length : 1)
chargeJsonReplacerValue(memoryLimit, val)
return val
}, space)
}
@@ -21,12 +33,18 @@ function inspect (this: FilterImpl, value: any, space = 0) {
const memoryLimit = this.context.memoryLimit
const ancestors: object[] = []
return JSON.stringify(value, function (this: unknown, _key: unknown, value: any) {
memoryLimit.use(typeof value === 'string' ? value.length : 1)
if (typeof value !== 'object' || value === null) return value
if (typeof value !== 'object' || value === null) {
chargeJsonReplacerValue(memoryLimit, value)
return value
}
// `this` is the object that value is contained in, i.e., its direct parent.
while (ancestors.length > 0 && ancestors[ancestors.length - 1] !== this) ancestors.pop()
if (ancestors.includes(value)) return '[Circular]'
if (ancestors.includes(value)) {
memoryLimit.use('[Circular]'.length)
return '[Circular]'
}
ancestors.push(value)
chargeJsonReplacerValue(memoryLimit, value)
return value
}, space)
}