Skip to content

Commit d3b7a40

Browse files
authored
fix(pretty-format): fix output limit over counting (#9965)
1 parent b3c992c commit d3b7a40

File tree

3 files changed

+44
-14
lines changed

3 files changed

+44
-14
lines changed

packages/pretty-format/src/index.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -408,12 +408,15 @@ function printer(
408408
}
409409
}
410410

411-
// Check string length budget:
412-
// accumulate output length and if exceeded,
413-
// force no further recursion by patching maxDepth.
414-
// Inspired by Node's util.inspect bail out approach.
415-
config.outputLength += result.length
416-
if (config.outputLength > config.maxOutputLength) {
411+
// Per-depth output budget (inspired by Node's util.inspect).
412+
// Each depth level tracks output independently, so nested results
413+
// don't inflate a single counter (which would undercount by ~Nx for
414+
// N levels of nesting). Nodes at the same depth produce disjoint spans
415+
// in the output string, so each bucket accurately reflects output at
416+
// that level. Total output is bounded by maxDepth × maxOutputLength.
417+
config._outputLengthPerDepth[depth] ??= 0
418+
config._outputLengthPerDepth[depth] += result.length
419+
if (config._outputLengthPerDepth[depth] > config.maxOutputLength) {
417420
config.maxDepth = 0
418421
}
419422

@@ -528,7 +531,7 @@ function getConfig(options?: OptionsReceived): Config {
528531
spacingInner: options?.min ? ' ' : '\n',
529532
spacingOuter: options?.min ? '' : '\n',
530533
maxOutputLength: options?.maxOutputLength ?? DEFAULT_OPTIONS.maxOutputLength,
531-
outputLength: 0,
534+
_outputLengthPerDepth: [],
532535
}
533536
}
534537

packages/pretty-format/src/types.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ export interface PrettyFormatOptions {
4545
indent?: number
4646
maxDepth?: number
4747
maxWidth?: number
48+
/**
49+
* Approximate per-depth-level budget for output length.
50+
* When the accumulated output at any single depth level exceeds this value,
51+
* further nesting is collapsed. This is a heuristic safety valve, not a hard
52+
* limit — total output can reach up to roughly `maxDepth × maxOutputLength`.
53+
* @default 1_000_000
54+
*/
4855
maxOutputLength?: number
4956
min?: boolean
5057
printBasicPrototype?: boolean
@@ -73,7 +80,11 @@ export interface Config {
7380
spacingInner: string
7481
spacingOuter: string
7582
maxOutputLength: number
76-
outputLength: number
83+
/**
84+
* Per-depth budget accumulator for {@link maxOutputLength}.
85+
* @internal
86+
*/
87+
_outputLengthPerDepth: number[]
7788
}
7889

7990
export type Printer = (

test/core/test/pretty-format.test.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -145,16 +145,30 @@ describe('maxOutputLength', () => {
145145
9729,
146146
36659,
147147
80789,
148-
273009,
149-
374009,
150-
299009,
148+
1056011,
149+
1074009,
150+
1088009,
151151
]
152152
`)
153153

154154
// depending on object/array shape, output can exceed the limit 1mb
155155
// but the output size is proportional to the amount of objects and the size of array.
156-
expect(format(createObjectGraph(10000).cats).length).toMatchInlineSnapshot(`936779`)
157-
expect(format(createObjectGraph(20000).cats).length).toMatchInlineSnapshot(`1236779`)
156+
expect(format(createObjectGraph(10000).cats).length).toMatchInlineSnapshot(`1377439`)
157+
expect(format(createObjectGraph(20000).cats).length).toMatchInlineSnapshot(`1497738`)
158+
})
159+
160+
test('budget should not truncate output shorter than maxOutputLength', () => {
161+
const data = Array.from({ length: 50 }, (_, i) => ({ a: { b: { c: i } } }))
162+
const full = format(data, { maxOutputLength: Infinity })
163+
const limited = format(data, { maxOutputLength: full.length })
164+
// this invariant should hold for any input
165+
expect(limited.length).toBe(full.length)
166+
expect({ limited: limited.length, full: full.length }).toMatchInlineSnapshot(`
167+
{
168+
"full": 4349,
169+
"limited": 4349,
170+
}
171+
`)
158172
})
159173

160174
test('early elements expanded, later elements folded after budget trips', () => {
@@ -175,7 +189,9 @@ describe('maxOutputLength', () => {
175189
Object {
176190
"i": 3,
177191
},
178-
[Object],
192+
Object {
193+
"i": 4,
194+
},
179195
[Object],
180196
[Object],
181197
[Object],

0 commit comments

Comments
 (0)