Skip to content

Negative indices below -length wrap repeatedly and resolve to an unrelated position #319

Description

@wahidrizka

Version

main @ 4079d57

What happens

remove, update, reset and slice all normalise negative indices with

while (start < 0) start += this.original.length

That is a loop, not a single adjustment, so an index below -length wraps around as many times as it takes to become positive and lands on an unrelated but perfectly valid-looking position. Nothing is thrown and the wrong characters are edited.

import MagicString from 'magic-string'

const s = new MagicString('problems = 99') // length 13

s.remove(-100, 5)
s.toString() // "probems = 99"  <- identical to s.remove(4, 5)

-100 wraps eight times: -100 → -87 → -74 → -61 → -48 → -35 → -22 → -9 → 4.

Same for the others:

new MagicString('problems = 99').update(-100, 5, 'X').toString() // "probXems = 99"
new MagicString('problems = 99').slice(-100, 5)                  // "l"
'problems = 99'.slice(-100, 5)                                   // "probl"

The slice case is the clearest: String.prototype.slice clamps a too-negative start at 0, MagicString#slice resolves it to 4.

Expected

An index below -length is out of range. Resolving it to an arbitrary in-range position is the one outcome that cannot be right. Either

  1. clamp once, matching String.prototype.slice, or
  2. throw Character is out of bounds, matching what these methods already do for a negative index on an empty string.

Both are single-step resolutions, so the existing should accept negative indices test (remove(-2, -1) on 'abcde''abce') keeps passing either way. I verified that.

Note

This is a behaviour change, so I deliberately left it out of #318, which only makes the existing range errors report their resolved indices. Happy to send a PR once you have a preference between clamping and throwing.

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