Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add configuration for diff truncation (#5073) #5333

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/config/index.md
Expand Up @@ -2031,6 +2031,28 @@ export default defineConfig({
```
:::

#### diff.truncateThreshold

- **Type**: `number`
- **Default**: `0`

The maximum length of diff result to be displayed. Diffs above this threshold will be truncated.
Truncation won't take effect with default value 0.

#### diff.truncateAnnotation

- **Type**: `string`
- **Default**: `'... Diff result is truncated'`

Annotation that is output at the end of diff result if it's truncated.

#### diff.truncateAnnotationColor

- **Type**: `DiffOptionsColor = (arg: string) => string`
- **Default**: `noColor = (string: string): string => string`

Color of truncate annotation, default is output with no color.

### fakeTimers

- **Type:** `FakeTimerInstallOpts`
Expand Down
36 changes: 21 additions & 15 deletions packages/utils/src/diff/diffLines.ts
Expand Up @@ -81,21 +81,24 @@ function printAnnotation({
return `${aColor(a)}\n${bColor(b)}\n\n`
}

export function printDiffLines(diffs: Array<Diff>, options: DiffOptionsNormalized): string {
export function printDiffLines(diffs: Array<Diff>, truncated: boolean, options: DiffOptionsNormalized): string {
return printAnnotation(options, countChanges(diffs))
+ (options.expand
? joinAlignedDiffsExpand(diffs, options)
: joinAlignedDiffsNoExpand(diffs, options))
+ (options.expand ? joinAlignedDiffsExpand(diffs, options) : joinAlignedDiffsNoExpand(diffs, options))
+ (truncated ? options.truncateAnnotationColor(`\n${options.truncateAnnotation}`) : '')
}

// Compare two arrays of strings line-by-line. Format as comparison lines.
export function diffLinesUnified(aLines: Array<string>, bLines: Array<string>, options?: DiffOptions): string {
const normalizedOptions = normalizeDiffOptions(options)
const [diffs, truncated] = diffLinesRaw(
isEmptyString(aLines) ? [] : aLines,
isEmptyString(bLines) ? [] : bLines,
normalizedOptions,
)
return printDiffLines(
diffLinesRaw(
isEmptyString(aLines) ? [] : aLines,
isEmptyString(bLines) ? [] : bLines,
),
normalizeDiffOptions(options),
diffs,
truncated,
normalizedOptions,
)
}

Expand All @@ -120,7 +123,7 @@ export function diffLinesUnified2(aLinesDisplay: Array<string>, bLinesDisplay: A
return diffLinesUnified(aLinesDisplay, bLinesDisplay, options)
}

const diffs = diffLinesRaw(aLinesCompare, bLinesCompare)
const [diffs, truncated] = diffLinesRaw(aLinesCompare, bLinesCompare, options)

// Replace comparison lines with displayable lines.
let aIndex = 0
Expand All @@ -144,13 +147,16 @@ export function diffLinesUnified2(aLinesDisplay: Array<string>, bLinesDisplay: A
}
})

return printDiffLines(diffs, normalizeDiffOptions(options))
return printDiffLines(diffs, truncated, normalizeDiffOptions(options))
}

// Compare two arrays of strings line-by-line.
export function diffLinesRaw(aLines: Array<string>, bLines: Array<string>): Array<Diff> {
const aLength = aLines.length
const bLength = bLines.length
export function diffLinesRaw(aLines: Array<string>, bLines: Array<string>, options?: DiffOptions): [Array<Diff>, boolean] {
const truncate = options?.truncateThreshold ?? false
const truncateThreshold = Math.max(Math.floor(options?.truncateThreshold ?? 0), 0)
const aLength = truncate ? Math.min(aLines.length, truncateThreshold) : aLines.length
const bLength = truncate ? Math.min(bLines.length, truncateThreshold) : bLines.length
const truncated = aLength !== aLines.length || bLength !== bLines.length

const isCommon = (aIndex: number, bIndex: number) => aLines[aIndex] === bLines[bIndex]

Expand Down Expand Up @@ -185,5 +191,5 @@ export function diffLinesRaw(aLines: Array<string>, bLines: Array<string>): Arra
for (; bIndex !== bLength; bIndex += 1)
diffs.push(new Diff(DIFF_INSERT, bLines[bIndex]))

return diffs
return [diffs, truncated]
}
33 changes: 28 additions & 5 deletions packages/utils/src/diff/diffStrings.ts
Expand Up @@ -7,8 +7,31 @@

import * as diff from 'diff-sequences'
import { DIFF_DELETE, DIFF_EQUAL, DIFF_INSERT, Diff } from './cleanupSemantic'
import type { DiffOptions } from './types'

// platforms compatible
function getNewLineSymbol(string: string) {
return string.includes('\r\n') ? '\r\n' : '\n'
}

function diffStrings(a: string, b: string, options?: DiffOptions): [Array<Diff>, boolean] {
const truncate = options?.truncateThreshold ?? false
const truncateThreshold = Math.max(Math.floor(options?.truncateThreshold ?? 0), 0)
let aLength = a.length
let bLength = b.length
if (truncate) {
const aMultipleLines = a.includes('\n')
const bMultipleLines = b.includes('\n')
const aNewLineSymbol = getNewLineSymbol(a)
const bNewLineSymbol = getNewLineSymbol(b)
// multiple-lines string expects a newline to be appended at the end
const _a = aMultipleLines ? `${a.split(aNewLineSymbol, truncateThreshold).join(aNewLineSymbol)}\n` : a
const _b = bMultipleLines ? `${b.split(bNewLineSymbol, truncateThreshold).join(bNewLineSymbol)}\n` : b
aLength = _a.length
bLength = _b.length
}
const truncated = aLength !== a.length || bLength !== b.length

function diffStrings(a: string, b: string): Array<Diff> {
const isCommon = (aIndex: number, bIndex: number) => a[aIndex] === b[bIndex]

let aIndex = 0
Expand All @@ -34,16 +57,16 @@ function diffStrings(a: string, b: string): Array<Diff> {
// @ts-expect-error wrong bundling
const diffSequences = diff.default.default || diff.default

diffSequences(a.length, b.length, isCommon, foundSubsequence)
diffSequences(aLength, bLength, isCommon, foundSubsequence)

// After the last common subsequence, push remaining change items.
if (aIndex !== a.length)
if (aIndex !== aLength)
diffs.push(new Diff(DIFF_DELETE, a.slice(aIndex)))

if (bIndex !== b.length)
if (bIndex !== bLength)
diffs.push(new Diff(DIFF_INSERT, b.slice(bIndex)))

return diffs
return [diffs, truncated]
}

export default diffStrings
4 changes: 4 additions & 0 deletions packages/utils/src/diff/normalizeDiffOptions.ts
Expand Up @@ -12,6 +12,7 @@ import type { DiffOptions, DiffOptionsNormalized } from './types'
export const noColor = (string: string): string => string

const DIFF_CONTEXT_DEFAULT = 5
const DIFF_TRUNCATE_THRESHOLD_DEFAULT = 0 // not truncate

function getDefaultOptions(): DiffOptionsNormalized {
const c = getColors()
Expand All @@ -35,6 +36,9 @@ function getDefaultOptions(): DiffOptionsNormalized {
includeChangeCounts: false,
omitAnnotationLines: false,
patchColor: c.yellow,
truncateThreshold: DIFF_TRUNCATE_THRESHOLD_DEFAULT,
truncateAnnotation: '... Diff result is truncated',
truncateAnnotationColor: noColor,
}
}

Expand Down
11 changes: 6 additions & 5 deletions packages/utils/src/diff/printDiffs.ts
Expand Up @@ -32,16 +32,17 @@ export function diffStringsUnified(a: string, b: string, options?: DiffOptions):
const isMultiline = a.includes('\n') || b.includes('\n')

// getAlignedDiffs assumes that a newline was appended to the strings.
const diffs = diffStringsRaw(
const [diffs, truncated] = diffStringsRaw(
isMultiline ? `${a}\n` : a,
isMultiline ? `${b}\n` : b,
true, // cleanupSemantic
options,
)

if (hasCommonDiff(diffs, isMultiline)) {
const optionsNormalized = normalizeDiffOptions(options)
const lines = getAlignedDiffs(diffs, optionsNormalized.changeColor)
return printDiffLines(lines, optionsNormalized)
return printDiffLines(lines, truncated, optionsNormalized)
}
}

Expand All @@ -51,11 +52,11 @@ export function diffStringsUnified(a: string, b: string, options?: DiffOptions):

// Compare two strings character-by-character.
// Optionally clean up small common substrings, also known as chaff.
export function diffStringsRaw(a: string, b: string, cleanup: boolean): Array<Diff> {
const diffs = diffStrings(a, b)
export function diffStringsRaw(a: string, b: string, cleanup: boolean, options?: DiffOptions): [Array<Diff>, boolean] {
const [diffs, truncated] = diffStrings(a, b, options)

if (cleanup)
cleanupSemantic(diffs) // impure function

return diffs
return [diffs, truncated]
}
6 changes: 6 additions & 0 deletions packages/utils/src/diff/types.ts
Expand Up @@ -27,6 +27,9 @@ export interface DiffOptions {
omitAnnotationLines?: boolean
patchColor?: DiffOptionsColor
compareKeys?: CompareKeys
truncateThreshold?: number
truncateAnnotation?: string
truncateAnnotationColor?: DiffOptionsColor
}

export interface DiffOptionsNormalized {
Expand All @@ -48,4 +51,7 @@ export interface DiffOptionsNormalized {
includeChangeCounts: boolean
omitAnnotationLines: boolean
patchColor: DiffOptionsColor
truncateThreshold: number
truncateAnnotation: string
truncateAnnotationColor: DiffOptionsColor
}
3 changes: 3 additions & 0 deletions packages/vitest/src/types/matcher-utils.ts
Expand Up @@ -31,4 +31,7 @@ export interface DiffOptions {
patchColor?: Formatter
// pretty-format type
compareKeys?: any
truncateThreshold?: number
truncateAnnotation?: string
truncateAnnotationColor?: Formatter
}