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
- clamp once, matching
String.prototype.slice, or
- 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.
Version
main@ 4079d57What happens
remove,update,resetandsliceall normalise negative indices withThat is a loop, not a single adjustment, so an index below
-lengthwraps 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.-100wraps eight times:-100 → -87 → -74 → -61 → -48 → -35 → -22 → -9 → 4.Same for the others:
The
slicecase is the clearest:String.prototype.sliceclamps a too-negative start at0,MagicString#sliceresolves it to4.Expected
An index below
-lengthis out of range. Resolving it to an arbitrary in-range position is the one outcome that cannot be right. EitherString.prototype.slice, orCharacter 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 indicestest (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.