Fix: Diff view renders "undefinedundefined..." suffix on lines with apostrophes#88
Open
wbcustc wants to merge 1 commit intoAeolun:mainfrom
Open
Fix: Diff view renders "undefinedundefined..." suffix on lines with apostrophes#88wbcustc wants to merge 1 commit intoAeolun:mainfrom
wbcustc wants to merge 1 commit intoAeolun:mainfrom
Conversation
…ffix highlight.js encodes apostrophes as ' (hex entity), but decodeEntities only handled &Aeolun#39; (decimal entity). The unrecognized entity caused a character count mismatch in applyDiffToHighlightedHtml, producing out-of-bounds reads that appended "undefined" strings to rendered lines. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix: Diff view renders "undefinedundefined…" suffix on lines with apostrophes
Problem
When using
renderContentwith a syntax highlighter (e.g. highlight.js) andDiffMethod.WORDS_WITH_SPACE, any line containing apostrophes renders a spuriousundefinedundefinedundefined…suffix. For a line with N apostrophes, exactly 8 × N"undefined"strings are appended.Example: a diff of
const x = 'hello'→const x = 'world'renders as:Root Cause
The full bug chain:
WORDS_WITH_SPACEtriggers per-word diff rendering viarenderWordDiff, which calls the user-suppliedrenderContentcallback with the full reconstructed line.renderContenttypically callshljs.highlight(), which internally encodes apostrophes using the hex HTML entity':applyDiffToHighlightedHtml, which calls itsdecodeEntitieshelper to normalize entity lengths back to plain-text character counts:'(6 encoded chars) is not decoded to'(1 char),decodedText.lengthis 5 characters larger than the actual plain-text length per apostrophe.fullLine, covering onlyfullLine.lengthcharacters.applyDiffToHighlightedHtmladvancestextPos += decodedText.lengthafter each segment, causingglobalPosto run past the end of all ranges while the outer loop still has remaining iterations.!rangefallback branch, the code reads:'hello'(2 apostrophes):decodedText.length = 17,charsToTake = 9, leaving17 − 9 = 8out-of-bounds iterations → exactly 8 ×"undefined".Fix
One-line change in
src/index.tsx— add'(hex entity) decoding todecodeEntitiesinsideapplyDiffToHighlightedHtml:.replace(/"/g, '"') .replace(/'/g, "'") + .replace(/'/g, "'") .replace(/ /g, "\u00A0");This ensures the hex-encoded apostrophe from highlight.js is decoded to the same single character that the plain-text diff ranges expect, keeping
decodedText.lengthin sync with the actual character count.Tests Added
Two new integration tests in
test/react-diff-viewer.test.tsx:Should not render 'undefined' when renderContent returns HTML with ' entities'hello'→'world') withWORDS_WITH_SPACEand arenderContentthat encodes'as'— asserts no"undefined"in outputShould not render 'undefined' with multiple apostrophes in renderContent HTMLit's a 'test' isn't it→it's a 'change' isn't it) — asserts no"undefined"in outputBoth tests fail without the fix and pass with it.
Verification