Skip to content

[diffs/edit] Sanitize malformed position components in normalizePosition#986

Merged
fat merged 1 commit into
beta-1.3from
fat/sanitize-position-components
Jul 15, 2026
Merged

[diffs/edit] Sanitize malformed position components in normalizePosition#986
fat merged 1 commit into
beta-1.3from
fat/sanitize-position-components

Conversation

@fat

@fat fat commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

What

Fixes the worst bug found by the editor-behavior test audit: an edit whose range carries a NaN (or fractional) line/character component silently replaced the entire document with the inserted text.

Repro before this fix:

const d = new TextDocument('inmemory://x', 'hello\nworld', 'plain');
d.applyEdits([{ range: { start: { line: 0, character: NaN }, end: { line: 0, character: NaN } }, newText: 'X' }]);
d.getText(); // "X"  — everything else gone

Why

normalizePosition clamped with bare Math.max(0, Math.min(...)), which passes NaN straight through, and fractional indexes survived the clamps only to break the integer-keyed line-offset lookup. Either way the resolved offset became NaN, and a NaN-ranged edit degenerates into a whole-document replace.

Fix

normalizePosition sanitizes components before clamping, consistent with its existing silent-clamp policy for out-of-range values: NaN and -Infinity act as 0, fractions floor, +Infinity clamps to the max. Every position-taking API funnels through it (offsetAt, #resolveEdit), so all edit paths are covered in one place. +Infinity behavior is byte-identical to before (already clamped correctly by accident).

Tests

  • The two test.failing pins for this bug flip to permanent regression tests with exact expected outputs (insertion lands at the sanitized position, no content lost).
  • The Infinity divergence pin is unchanged and still passes.
  • Full diffs suite: 1278 pass / 0 fail; typecheck and oxlint clean.
  • test/README.md known-bug inventory: 32 → 30.

First PR of the failing-test burn-down; next up is the history-equivalence cluster (7 pins).

🤖 Generated with Claude Code

Math.min/max pass NaN straight through and fractional indexes break the
integer-keyed line-offset lookup, so a NaN (or fractional) line/character
resolved to a NaN offset and the degenerate edit range swallowed the
entire document — an insert with one malformed component silently erased
everything else. normalizePosition now sanitizes before clamping: NaN
and -Infinity act as 0, fractions floor, +Infinity clamps to the max.
Every position-taking API funnels through it (offsetAt, resolveEdit),
so all edit paths are covered. Flips the two malformed-position pins to
permanent regression tests with exact expectations; pinned known-bug
count drops to 30.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pierre-docs-diffs Ready Ready Preview Jul 14, 2026 11:04pm
pierre-docs-diffshub Ready Ready Preview Jul 14, 2026 11:04pm
pierre-docs-trees Ready Ready Preview Jul 14, 2026 11:04pm
pierrejs-diff-demo Ready Ready Preview Jul 14, 2026 11:04pm

Request Review

@amadeus amadeus left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. could there be a valid reason to throw in this situation? or is there something about the API where a user could provide bad data? But probably not a big deal overall

@fat

fat commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

mm don't think so

@fat
fat merged commit 4af6b0b into beta-1.3 Jul 15, 2026
8 checks passed
@fat
fat deleted the fat/sanitize-position-components branch July 15, 2026 01:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants