Skip to content

Commit

Permalink
Invert string diffs
Browse files Browse the repository at this point in the history
Allows formatting strings nested inside items, mapEntries, and
properties.

Fixes #40.
  • Loading branch information
ninevra committed Mar 2, 2021
1 parent 53f11aa commit b2ad842
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 23 deletions.
2 changes: 1 addition & 1 deletion lib/diff.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ function diffDescriptors (lhs, rhs, options) {
topIndex++
} else {
const diffed = typeof lhs.diffDeep === 'function'
? lhs.diffDeep(rhs, themeUtils.applyModifiers(lhs, theme), indent)
? lhs.diffDeep(rhs, themeUtils.applyModifiers(lhs, theme), indent, invert)
: null

if (diffed === null) {
Expand Down
4 changes: 2 additions & 2 deletions lib/metaDescriptors/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ class PrimitiveItem {
} while (true)
}

diffDeep (expected, theme, indent) {
diffDeep (expected, theme, indent, invert) {
// Verify a diff can be returned.
if (this.tag !== expected.tag || typeof this.value.diffDeep !== 'function') return null

Expand All @@ -236,7 +236,7 @@ class PrimitiveItem {

// Since the value is diffed directly, modifiers are not applied. Apply
// modifiers to the item descriptor instead.
const diff = this.value.diffDeep(expected.value, theme, valueIndent)
const diff = this.value.diffDeep(expected.value, theme, valueIndent, invert)
if (diff === null) return null

if (typeof theme.item.customFormat === 'function') {
Expand Down
4 changes: 2 additions & 2 deletions lib/metaDescriptors/mapEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class MapEntry {
}
}

diffDeep (expected, theme, indent) {
diffDeep (expected, theme, indent, invert) {
// Verify a diff can be returned.
if (this.tag !== expected.tag || typeof this.value.diffDeep !== 'function') return null
// Only use this logic to format value diffs when the keys are primitive and equal.
Expand All @@ -127,7 +127,7 @@ class MapEntry {

// Since formatShallow() would result in theme modifiers being applied
// before the key and value are formatted, do the same here.
const diff = this.value.diffDeep(expected.value, themeUtils.applyModifiersToOriginal(this.value, theme), indent)
const diff = this.value.diffDeep(expected.value, themeUtils.applyModifiersToOriginal(this.value, theme), indent, invert)
if (diff === null) return null

const key = this.key.formatDeep(themeUtils.applyModifiersToOriginal(this.key, theme), indent, '')
Expand Down
4 changes: 2 additions & 2 deletions lib/metaDescriptors/property.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ class PrimitiveProperty extends Property {
.withLastPostfixed(theme.property.after)
}

diffDeep (expected, theme, indent) {
diffDeep (expected, theme, indent, invert) {
// Verify a diff can be returned.
if (this.tag !== expected.tag || typeof this.value.diffDeep !== 'function') return null
// Only use this logic to diff values when the keys are the same.
Expand All @@ -171,7 +171,7 @@ class PrimitiveProperty extends Property {

// Since the key and value are diffed directly, modifiers are not
// applied. Apply modifiers to the property descriptor instead.
const diff = this.value.diffDeep(expected.value, theme, valueIndent)
const diff = this.value.diffDeep(expected.value, theme, valueIndent, invert)
if (diff === null) return null

if (typeof theme.property.customFormat === 'function') {
Expand Down
32 changes: 22 additions & 10 deletions lib/primitiveValues/string.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function includesLinebreaks (string) {
return string.includes('\r') || string.includes('\n')
}

function diffLine (theme, actual, expected) {
function diffLine (theme, actual, expected, invert) {
const outcome = fastDiff(actual, expected)

// TODO: Compute when line is mostly unequal (80%? 90%?) and treat it as being
Expand All @@ -83,9 +83,14 @@ function diffLine (theme, actual, expected) {
let stringExpected = ''

const noopWrap = { open: '', close: '' }
const deleteWrap = isPartiallyEqual ? theme.string.diff.delete : noopWrap
const insertWrap = isPartiallyEqual ? theme.string.diff.insert : noopWrap
let deleteWrap = isPartiallyEqual ? theme.string.diff.delete : noopWrap
let insertWrap = isPartiallyEqual ? theme.string.diff.insert : noopWrap
const equalWrap = isPartiallyEqual ? theme.string.diff.equal : noopWrap

if (invert) {
[deleteWrap, insertWrap] = [insertWrap, deleteWrap]
}

for (const diff of outcome) {
if (diff[0] === fastDiff.DELETE) {
stringActual += formatUtils.wrap(deleteWrap, diff[1])
Expand All @@ -99,8 +104,11 @@ function diffLine (theme, actual, expected) {
}

if (!isPartiallyEqual) {
stringActual = formatUtils.wrap(theme.string.diff.deleteLine, stringActual)
stringExpected = formatUtils.wrap(theme.string.diff.insertLine, stringExpected)
const deleteLineWrap = invert ? theme.string.diff.insertLine : theme.string.diff.deleteLine
const insertLineWrap = invert ? theme.string.diff.deleteLine : theme.string.diff.insertLine

stringActual = formatUtils.wrap(deleteLineWrap, stringActual)
stringExpected = formatUtils.wrap(insertLineWrap, stringExpected)
}

return [stringActual, stringExpected]
Expand Down Expand Up @@ -170,7 +178,7 @@ class StringValue {
return formatUtils.wrap(theme.string.line, formatUtils.wrap(theme.string, escaped))
}

diffDeep (expected, theme, indent) {
diffDeep (expected, theme, indent, invert) {
if (expected.tag !== tag) return null

const escapedActual = basicEscape(this.value)
Expand All @@ -179,7 +187,9 @@ class StringValue {
if (!includesLinebreaks(escapedActual) && !includesLinebreaks(escapedExpected)) {
const result = diffLine(theme,
escapeQuotes(theme.string.line, escapedActual),
escapeQuotes(theme.string.line, escapedExpected))
escapeQuotes(theme.string.line, escapedExpected),
invert,
)

return lineBuilder.actual.single(formatUtils.wrap(theme.string.line, result[0]))
.concat(lineBuilder.expected.single(formatUtils.wrap(theme.string.line, result[1])))
Expand Down Expand Up @@ -241,7 +251,8 @@ class StringValue {
}

if (actualIsExtraneous && !expectedIsMissing) {
const string = formatUtils.wrap(theme.string.diff.deleteLine, actualLines[actualIndex])
const wrap = invert ? theme.string.diff.insertLine : theme.string.diff.deleteLine
const string = formatUtils.wrap(wrap, actualLines[actualIndex])

if (actualIndex === 0) {
actualBuffer.push(lineBuilder.actual.first(theme.string.multiline.start + string))
Expand All @@ -255,7 +266,8 @@ class StringValue {
actualIndex++
extraneousOffset++
} else if (expectedIsMissing && !actualIsExtraneous) {
const string = formatUtils.wrap(theme.string.diff.insertLine, expectedLines[expectedIndex])
const wrap = invert ? theme.string.diff.deleteLine : theme.string.diff.insertLine
const string = formatUtils.wrap(wrap, expectedLines[expectedIndex])

if (mustOpenNextExpected) {
expectedBuffer.push(lineBuilder.expected.first(theme.string.multiline.start + string))
Expand All @@ -268,7 +280,7 @@ class StringValue {

expectedIndex++
} else {
const result = diffLine(theme, actualLines[actualIndex], expectedLines[expectedIndex])
const result = diffLine(theme, actualLines[actualIndex], expectedLines[expectedIndex], invert)

if (actualIndex === 0) {
actualBuffer.push(lineBuilder.actual.first(theme.string.multiline.start + result[0]))
Expand Down
19 changes: 19 additions & 0 deletions test/diff.js
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,25 @@ test('inverted diffs', t => {
}, { invert: true }))
})

test('inverts string diffs', t => {
t.snapshot(diff('foo', 'bar', { invert: true }))
t.snapshot(diff('foo bar baz', 'foo baz quux', { invert: true }))
t.snapshot(diff('foo\nbar\nbaz', 'foobarbaz', { invert: true }))
})

test('inverts string diffs in containers', t => {
const actual = 'foo\nbar\nbaz'
const expected = 'foo\nbaz\nquux'

t.snapshot(diff({ a: actual }, { a: expected }, { invert: true }))
t.snapshot(diff([actual], [expected], { invert: true }))
t.snapshot(diff(new Map().set('a', actual), new Map().set('a', expected), { invert: true }))
t.snapshot(diff(new Set().add(actual), new Set().add(expected), { invert: true }))
t.snapshot(diff(new String(actual), new String(expected), { invert: true })) // eslint-disable-line unicorn/new-for-builtins, no-new-wrappers

t.snapshot(diff({ a: { b: actual } }, { a: { b: expected } }, { invert: true }))
})

test('lists: effectively resets depth when formatting differences', t => {
const l1 = [
{
Expand Down
95 changes: 89 additions & 6 deletions test/snapshots/diff.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -1139,12 +1139,95 @@ Generated by [AVA](https://avajs.dev).
`%diffGutters.padding# %%object.openBracket#{%␊
%diffGutters.padding# % baz%property.separator#: %%string.multiline.start#`%%string.diff.equal.open%qux%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.actual#- % %string.diff.insertLine.open%corge%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.insertLine.close%␊
%diffGutters.actual#- % %string.diff.insertLine.open%quux%string.diff.insertLine.close%%string.multiline.end#`%%property.after#,%␊
%diffGutters.expected#+ % %string.diff.deleteLine.open%quux%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.deleteLine.close%␊
%diffGutters.expected#+ % %string.diff.deleteLine.open%corge%string.diff.deleteLine.close%%string.multiline.end#`%%property.after#,%␊
%diffGutters.actual#- % foo%property.separator#: %%string.line.open#'%%string.diff.insertLine.open%BAR%string.diff.insertLine.close%%string.line.close#'%%property.after#,%␊
%diffGutters.expected#+ % foo%property.separator#: %%string.line.open#'%%string.diff.deleteLine.open%bar%string.diff.deleteLine.close%%string.line.close#'%%property.after#,%␊
%diffGutters.actual#- % %string.diff.deleteLine.open%corge%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.deleteLine.close%␊
%diffGutters.actual#- % %string.diff.deleteLine.open%quux%string.diff.deleteLine.close%%string.multiline.end#`%%property.after#,%␊
%diffGutters.expected#+ % %string.diff.insertLine.open%quux%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.insertLine.close%␊
%diffGutters.expected#+ % %string.diff.insertLine.open%corge%string.diff.insertLine.close%%string.multiline.end#`%%property.after#,%␊
%diffGutters.actual#- % foo%property.separator#: %%string.line.open#'%%string.diff.deleteLine.open%BAR%string.diff.deleteLine.close%%string.line.close#'%%property.after#,%␊
%diffGutters.expected#+ % foo%property.separator#: %%string.line.open#'%%string.diff.insertLine.open%bar%string.diff.insertLine.close%%string.line.close#'%%property.after#,%␊
%diffGutters.padding# %%object.closeBracket#}%`

## inverts string diffs

> Snapshot 1
`%diffGutters.actual#- %%string.line.open#'%%string.diff.deleteLine.open%bar%string.diff.deleteLine.close%%string.line.close#'%␊
%diffGutters.expected#+ %%string.line.open#'%%string.diff.insertLine.open%foo%string.diff.insertLine.close%%string.line.close#'%`

> Snapshot 2
`%diffGutters.actual#- %%string.line.open#'%%string.diff.equal.open%foo ba%string.diff.equal.close%%string.diff.delete.open%z%string.diff.delete.close%%string.diff.equal.open% %string.diff.equal.close%%string.diff.delete.open%quux%string.diff.delete.close%%string.line.close#'%␊
%diffGutters.expected#+ %%string.line.open#'%%string.diff.equal.open%foo ba%string.diff.equal.close%%string.diff.insert.open%r%string.diff.insert.close%%string.diff.equal.open% %string.diff.equal.close%%string.diff.insert.open%baz%string.diff.insert.close%%string.line.close#'%`

> Snapshot 3
`%diffGutters.actual#- %%string.multiline.start#`%%string.diff.delete.open%foobar%string.diff.delete.close%%string.diff.equal.open%baz%string.diff.equal.close%␊
%diffGutters.expected#+ %%string.multiline.start#`%%string.diff.insertLine.open%foo␊%string.diff.insertLine.close%␊
%diffGutters.expected#+ %%string.diff.insertLine.open%bar␊%string.diff.insertLine.close%␊
%diffGutters.expected#+ %%string.diff.equal.open%baz%string.diff.equal.close%%string.multiline.end#`%`

## inverts string diffs in containers

> Snapshot 1
`%diffGutters.padding# %%object.openBracket#{%␊
%diffGutters.padding# % a%property.separator#: %%string.multiline.start#`%%string.diff.equal.open%foo%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.actual#- % %string.diff.equal.open%ba%string.diff.equal.close%%string.diff.delete.open%z%string.diff.delete.close%%string.diff.equal.open%%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.actual#- % %string.diff.deleteLine.open%quux%string.diff.deleteLine.close%%string.multiline.end#`%%property.after#,%␊
%diffGutters.expected#+ % %string.diff.equal.open%ba%string.diff.equal.close%%string.diff.insert.open%r%string.diff.insert.close%%string.diff.equal.open%%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.expected#+ % %string.diff.insertLine.open%baz%string.diff.insertLine.close%%string.multiline.end#`%%property.after#,%␊
%diffGutters.padding# %%object.closeBracket#}%`

> Snapshot 2
`%diffGutters.padding# %%list.openBracket#[%␊
%diffGutters.padding# % %string.multiline.start#`%%string.diff.equal.open%foo%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.actual#- % %string.diff.equal.open%ba%string.diff.equal.close%%string.diff.delete.open%z%string.diff.delete.close%%string.diff.equal.open%%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.actual#- % %string.diff.deleteLine.open%quux%string.diff.deleteLine.close%%string.multiline.end#`%%item.after#,%␊
%diffGutters.expected#+ % %string.diff.equal.open%ba%string.diff.equal.close%%string.diff.insert.open%r%string.diff.insert.close%%string.diff.equal.open%%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.expected#+ % %string.diff.insertLine.open%baz%string.diff.insertLine.close%%string.multiline.end#`%%item.after#,%␊
%diffGutters.padding# %%list.closeBracket#]%`

> Snapshot 3
`%diffGutters.padding# %%object.ctor.open%Map%object.ctor.close% %object.openBracket#{%␊
%diffGutters.padding# % %string.line.open#'%%string.open%a%string.close%%string.line.close#'%%mapEntry.separator# => %%string.multiline.start#`%%string.diff.equal.open%foo%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.actual#- % %string.diff.equal.open%ba%string.diff.equal.close%%string.diff.delete.open%z%string.diff.delete.close%%string.diff.equal.open%%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.actual#- % %string.diff.deleteLine.open%quux%string.diff.deleteLine.close%%string.multiline.end#`%%mapEntry.after#,%␊
%diffGutters.expected#+ % %string.diff.equal.open%ba%string.diff.equal.close%%string.diff.insert.open%r%string.diff.insert.close%%string.diff.equal.open%%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.expected#+ % %string.diff.insertLine.open%baz%string.diff.insertLine.close%%string.multiline.end#`%%mapEntry.after#,%␊
%diffGutters.padding# %%object.closeBracket#}%`

> Snapshot 4
`%diffGutters.padding# %%object.ctor.open%Set%object.ctor.close% %object.openBracket#{%␊
%diffGutters.padding# % %string.multiline.start#`%%string.diff.equal.open%foo%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.actual#- % %string.diff.equal.open%ba%string.diff.equal.close%%string.diff.delete.open%z%string.diff.delete.close%%string.diff.equal.open%%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.actual#- % %string.diff.deleteLine.open%quux%string.diff.deleteLine.close%%string.multiline.end#`%%item.after#,%␊
%diffGutters.expected#+ % %string.diff.equal.open%ba%string.diff.equal.close%%string.diff.insert.open%r%string.diff.insert.close%%string.diff.equal.open%%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.expected#+ % %string.diff.insertLine.open%baz%string.diff.insertLine.close%%string.multiline.end#`%%item.after#,%␊
%diffGutters.padding# %%object.closeBracket#}%`

> Snapshot 5
`%diffGutters.padding# %%object.ctor.open%String%object.ctor.close% %object.openBracket#{%␊
%diffGutters.padding# % %string.multiline.start#`%%string.diff.equal.open%foo%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.actual#- % %string.diff.equal.open%ba%string.diff.equal.close%%string.diff.delete.open%z%string.diff.delete.close%%string.diff.equal.open%%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.actual#- % %string.diff.deleteLine.open%quux%string.diff.deleteLine.close%%string.multiline.end#`%␊
%diffGutters.expected#+ % %string.diff.equal.open%ba%string.diff.equal.close%%string.diff.insert.open%r%string.diff.insert.close%%string.diff.equal.open%%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.expected#+ % %string.diff.insertLine.open%baz%string.diff.insertLine.close%%string.multiline.end#`%␊
%diffGutters.padding# %%object.closeBracket#}%`

> Snapshot 6
`%diffGutters.padding# %%object.openBracket#{%␊
%diffGutters.padding# % a%property.separator#: %%object.openBracket#{%␊
%diffGutters.padding# % b%property.separator#: %%string.multiline.start#`%%string.diff.equal.open%foo%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.actual#- % %string.diff.equal.open%ba%string.diff.equal.close%%string.diff.delete.open%z%string.diff.delete.close%%string.diff.equal.open%%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.actual#- % %string.diff.deleteLine.open%quux%string.diff.deleteLine.close%%string.multiline.end#`%%property.after#,%␊
%diffGutters.expected#+ % %string.diff.equal.open%ba%string.diff.equal.close%%string.diff.insert.open%r%string.diff.insert.close%%string.diff.equal.open%%string.diff.equal.close%%string.controlPicture.open%␊%string.controlPicture.close%%string.diff.equal.open%%string.diff.equal.close%␊
%diffGutters.expected#+ % %string.diff.insertLine.open%baz%string.diff.insertLine.close%%string.multiline.end#`%%property.after#,%␊
%diffGutters.padding# % %object.closeBracket#}%%property.after#,%␊
%diffGutters.padding# %%object.closeBracket#}%`

## lists: effectively resets depth when formatting differences
Expand Down
Binary file modified test/snapshots/diff.js.snap
Binary file not shown.

0 comments on commit b2ad842

Please sign in to comment.