Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions packages/pretty-format/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,12 +408,15 @@ function printer(
}
}

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

Expand Down Expand Up @@ -528,7 +531,7 @@ function getConfig(options?: OptionsReceived): Config {
spacingInner: options?.min ? ' ' : '\n',
spacingOuter: options?.min ? '' : '\n',
maxOutputLength: options?.maxOutputLength ?? DEFAULT_OPTIONS.maxOutputLength,
outputLength: 0,
_outputLengthPerDepth: [],
}
}

Expand Down
13 changes: 12 additions & 1 deletion packages/pretty-format/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ export interface PrettyFormatOptions {
indent?: number
maxDepth?: number
maxWidth?: number
/**
* Approximate per-depth-level budget for output length.
* When the accumulated output at any single depth level exceeds this value,
* further nesting is collapsed. This is a heuristic safety valve, not a hard
* limit — total output can reach up to roughly `maxDepth × maxOutputLength`.
* @default 1_000_000
*/
maxOutputLength?: number
min?: boolean
printBasicPrototype?: boolean
Expand Down Expand Up @@ -73,7 +80,11 @@ export interface Config {
spacingInner: string
spacingOuter: string
maxOutputLength: number
outputLength: number
/**
* Per-depth budget accumulator for {@link maxOutputLength}.
* @internal
*/
_outputLengthPerDepth: number[]
}

export type Printer = (
Expand Down
28 changes: 22 additions & 6 deletions test/core/test/pretty-format.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,30 @@ describe('maxOutputLength', () => {
9729,
36659,
80789,
273009,
374009,
299009,
1056011,
1074009,
1088009,
]
`)

// depending on object/array shape, output can exceed the limit 1mb
// but the output size is proportional to the amount of objects and the size of array.
expect(format(createObjectGraph(10000).cats).length).toMatchInlineSnapshot(`936779`)
expect(format(createObjectGraph(20000).cats).length).toMatchInlineSnapshot(`1236779`)
expect(format(createObjectGraph(10000).cats).length).toMatchInlineSnapshot(`1377439`)
expect(format(createObjectGraph(20000).cats).length).toMatchInlineSnapshot(`1497738`)
})

test('budget should not truncate output shorter than maxOutputLength', () => {
const data = Array.from({ length: 50 }, (_, i) => ({ a: { b: { c: i } } }))
const full = format(data, { maxOutputLength: Infinity })
const limited = format(data, { maxOutputLength: full.length })
// this invariant should hold for any input
expect(limited.length).toBe(full.length)
expect({ limited: limited.length, full: full.length }).toMatchInlineSnapshot(`
{
"full": 4349,
"limited": 4349,
}
`)
})

test('early elements expanded, later elements folded after budget trips', () => {
Expand All @@ -175,7 +189,9 @@ describe('maxOutputLength', () => {
Object {
"i": 3,
},
[Object],
Object {
"i": 4,
},
[Object],
[Object],
[Object],
Expand Down
Loading