diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index eed3ba9d02..5d03758f0e 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -4205,10 +4205,57 @@ namespace FourSlash { const actualMsg = "\x1b[1mActual\x1b[0m\x1b[31m"; const expectedString = quoted ? "\"" + expected + "\"" : expected; const actualString = quoted ? "\"" + actual + "\"" : actual; - return `\n${expectMsg}:\n${expectedString}\n\n${actualMsg}:\n${actualString}`; + return `\n${expectMsg}:\n${expectedString}\n\n${actualMsg}:\n${highlightDifferenceBetweenStrings(expected, actualString)}`; } function templateToRegExp(template: string) { return new RegExp(`^${ts.regExpEscape(template).replace(/\\\{\d+\\\}/g, ".*?")}$`); } + + function rangesOfDiffBetweenTwoStrings(source: string, target: string) { + const ranges = [] as { start: number; length: number }[]; + + const addToIndex = (index: number) => { + const closestIndex = ranges[ranges.length - 1]; + if (closestIndex) { + const doesAddToIndex = closestIndex.start + closestIndex.length === index - 1; + if (doesAddToIndex) { + closestIndex.length = closestIndex.length + 1; + } + else { + ranges.push({ start: index - 1, length: 1 }); + } + } + else { + ranges.push({ start: index - 1, length: 1 }); + } + }; + + for (let index = 0; index < Math.max(source.length, target.length); index++) { + const srcChar = source[index]; + const targetChar = target[index]; + if (srcChar !== targetChar) addToIndex(index); + } + + return ranges; + } + + // Adds an _ when the source string and the target string have a whitespace difference + function highlightDifferenceBetweenStrings(source: string, target: string) { + const ranges = rangesOfDiffBetweenTwoStrings(source, target); + let emTarget = target; + ranges.forEach((range, index) => { + const lhs = `\x1b[4m`; + const rhs = `\x1b[0m\x1b[31m`; + const additionalOffset = index * lhs.length + index * rhs.length; + const before = emTarget.slice(0, range.start + 1 + additionalOffset); + const between = emTarget.slice( + range.start + 1 + additionalOffset, + range.start + range.length + 1 + additionalOffset + ); + const after = emTarget.slice(range.start + range.length + 1 + additionalOffset, emTarget.length); + emTarget = before + lhs + between + rhs + after; + }); + return emTarget; + } }