Skip to content

Bug report: Why doesn't jsdiff show leading/trailing whitespace differences? #414

Closed
@DanKaplanSES

Description

@DanKaplanSES

This is a stackoverflow question I copied over to github per the recommendation of a commenter that believes this is a bug.

  • This may be a duplicate of diffWords is not detecting a deleted space at the end of text. #402 but it's hard to say because it doesn't go into detail. That issue was closed with a comment suggesting these are unrelated issues.
  • I have tested and reproduced this with diffChars, diffWordsWithSpace, and diffLines.
  • I've toggled every option I've seen documented, but it has no affect with respect to this issue.
  • I am running this code in node v20.10.0 on Windows 10.
  • I've tested and reproduced it in JavaScript and TypeScript.
  • I've tested and reproduced it in jsdiff 5.1.0, 5.0.0, 4.0.2, and 4.0.1.

Question:

Why doesn't jsdiff show leading/trailing whitespace differences?

Details:

I found this popular npm library named jsdiff (github). I'm using it in a node.js environment and it provides this example on how to do so (note: I've modified commonjs requires to esm imports):

import 'colors';
import * as Diff from 'diff';

const one = 'beep boop';
const other = 'beep boob blah';

const diff = Diff.diffChars(one, other);

diff.forEach((part) => {
  // green for additions, red for deletions
  // grey for common parts
  const color = part.added ? 'green' : part.removed ? 'red' : 'grey';
  process.stderr.write(part.value[color]);
});

console.log();

Output:

enter image description here

But if I modify one and/or other to have trailing whitespace, it isn't printed as a difference between the strings:

import 'colors';
import * as Diff from 'diff';

const one = 'beep boop ';
const other = '\nbeep boob blah\t';

const diff = Diff.diffChars(one, other);

diff.forEach((part) => {
  // green for additions, red for deletions
  // grey for common parts
  const color = part.added ? 'green' : part.removed ? 'red' : 'grey';
  process.stderr.write(part.value[color]);
});

console.log();

Output:

enter image description here

The README doesn't say whether this is expected or unexpected behavior of Diff.diffChars, but trailing whitespace is ignored when I change Diff.diffChars to Diff.diffWordsWithSpace:

enter image description here

The documentation for Diff.diffWordsWithSpace is more clear on the matter (emphasis mine):

diffs two blocks of text, comparing word by word, treating whitespace as significant.

I thought it could have something to do with the colors modifying the font foreground instead of background; whitespace has no foreground, so it wouldn't show up. To test that hypothesis, I switched from colors to chalk (after reading many recommendations to do so) and changed the background instead of foreground:

import chalk from 'chalk';
import * as Diff from 'diff';

const one = 'beep boop ';
const other = '\nbeep boob blah\t';

const diff = Diff.diffChars(one, other); // note: I went back to diffChars here

diff.forEach((part) => {
  // green for additions, red for deletions
  // grey for common parts
  const color = part.added
    ? chalk.bgGreen
    : part.removed
      ? chalk.bgRed
      : (x) => x; // identity function, return with normal color
  const coloredText = color(part.value);
  process.stderr.write(coloredText);
});

console.log();

But this didn't make a difference in output:

enter image description here

Why doesn't jsdiff print leading/trailing whitespace differences?

Notes:

You can find a working repo with this last example here: https://github.com/DanKaplanSES/jsdom-sandbox/tree/jsdiff-trailing-whitespace-so

Workaround:

This will print differences in leading and trailing whitespace:

import chalk from 'chalk';
import * as Diff from 'diff';

const one = 'beep boop ';
const other = '\nbeep boob blah\t';

const diff = Diff.diffChars(JSON.stringify(one), JSON.stringify( other));

diff.forEach((part, index) => {
  // green for additions, red for deletions
  // grey for common parts
  const color = part.added
    ? chalk.bgGreen
    : part.removed
      ? chalk.bgRed
      : (x) => x; // identity function, return with normal color
  const coloredText = color(part.value);
  process.stderr.write(coloredText);
});

console.log();

Output:

enter image description here

I consider this to be a workaround, not a solution: JSON.stringify converts whitespace into character escape codes (except for the space character) and that's the only reason this library prints a difference.


Better logs:

I modified the example to print better logs. This shows that the differences are recognized, they just aren't displayed:

import chalk from 'chalk';
import * as Diff from 'diff';

const one = 'beep  boop ';
const other = '\nbeep boob blah\t';

const diff = Diff.diffChars(one, other);

let buffer = "";
diff.forEach((part, index) => {
  // green for additions, red for deletions
  // grey for common parts
  const color = part.added
    ? chalk.bgGreen
    : part.removed
      ? chalk.bgRed
      : (x) => x; // identity function, return with normal color
  const coloredText = color(part.value);
  console.log(`JSON.stringify(part.value)`, JSON.stringify(part.value), `part.added`, part.added, `part.removed`, part.removed);
  buffer += (coloredText);
});

console.log(buffer);

Output:

enter image description here

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions