Skip to content

Edge Cases and Errors

m4bwav edited this page Sep 30, 2026 · 1 revision

2.0.0 checks every argument before it slices, and throws instead of returning damaged text. There are two error classes: a TypeError when an argument has the wrong type and a RangeError when the position is a number but not a usable one. Each message names the argument and describes what it got without calling anything on it (no toString, no getters).

The examples on this page use a small helper that prints the result as JSON, or the error's class and message:

import replaceStringAtPosition from 'replace-string-at-position';

function attempt(...args) {
	try {
		return JSON.stringify(replaceStringAtPosition(...args));
	} catch (error) {
		return `${error.name}: ${error.message}`;
	}
}

console.log(attempt('abc', 'b', 'X', 1));

prints:

"aXc"

Text arguments that are not strings

originalString, sourceString and newString must each be a string or a String object. Numbers, null, undefined, booleans, BigInts, symbols, functions, arrays and array-likes are refused:

console.log(attempt(null, 'a', 'X', 0));
console.log(attempt(undefined, 'a', 'X', 0));
console.log(attempt(12345, '3', 'X', 2));
console.log(attempt(['a', 'b', 'c'], 'b', 'X', 1));
console.log(attempt('abcdef', {length: 3}, 'X', 1));
console.log(attempt('abcdef', ['b'], 'X', 1));
console.log(attempt('abcdef', 'b', 5, 1));
console.log(attempt('abcdef', 'b', null, 1));
console.log(attempt('abcdef', 'b', true, 1));
console.log(attempt('abcdef', 'b', 1n, 1));
console.log(attempt('abcdef', 'b', Symbol('x'), 1));
console.log(attempt('abcdef', 'b', () => 'x', 1));

prints:

TypeError: Expected `originalString` to be a string, got null
TypeError: Expected `originalString` to be a string, got undefined
TypeError: Expected `originalString` to be a string, got 12345
TypeError: Expected `originalString` to be a string, got an array
TypeError: Expected `sourceString` to be a string, got an object
TypeError: Expected `sourceString` to be a string, got an array
TypeError: Expected `newString` to be a string, got 5
TypeError: Expected `newString` to be a string, got null
TypeError: Expected `newString` to be a string, got a boolean
TypeError: Expected `newString` to be a string, got a bigint
TypeError: Expected `newString` to be a string, got a symbol
TypeError: Expected `newString` to be a string, got a function

A number as newString is the refusal most likely to surprise: 1.0.4 turned 5 into '5'. Pass String(value).

What counts as a string

A string primitive, or a String object from any realm, read by its internal value. A Proxy wrapping a String object is refused, because reading it would run the proxy's code; Object('X') makes a real String object and is accepted:

console.log(attempt('abc', new Proxy(new String('b'), {}), 'X', 1));
console.log(attempt('abc', 'b', Object('X'), 1));

prints:

TypeError: Expected `sourceString` to be a string, got an object
"aXc"

A position that is not a number

position must be of type number. A missing position, a numeric string, a boolean, an array, a BigInt and a Number object are all refused. A string is quoted in the message, cut to its first 20 code points:

console.log(attempt('abcdef', 'b', 'X'));
console.log(attempt('abcdef', 'b', 'X', null));
console.log(attempt('abcdef', 'b', 'X', '1'));
console.log(attempt('abcdef', 'b', 'X', 'a very long position string indeed'));
console.log(attempt('abcdef', 'b', 'X', true));
console.log(attempt('abcdef', 'b', 'X', [2]));
console.log(attempt('abcdef', 'b', 'X', 1n));
console.log(attempt('abcdef', 'b', 'X', new Number(2)));

prints:

TypeError: Expected `position` to be a number, got undefined
TypeError: Expected `position` to be a number, got null
TypeError: Expected `position` to be a number, got the string "1"
TypeError: Expected `position` to be a number, got the string "a very long position..."
TypeError: Expected `position` to be a number, got a boolean
TypeError: Expected `position` to be a number, got an array
TypeError: Expected `position` to be a number, got a bigint
TypeError: Expected `position` to be a number, got an object

A position read from user input or a URL is a string: convert it with Number() and let the RangeError below catch what does not convert.

A position out of range

A number must be a safe integer from 0 to the text's length, inclusive. The message gives the allowed range for this text:

console.log(attempt('abc', 'b', 'X', -1));
console.log(attempt('abc', 'b', 'X', 1.5));
console.log(attempt('abc', 'b', 'X', NaN));
console.log(attempt('abc', 'b', 'X', 4));
console.log(attempt('abc', 'b', 'X', Infinity));
console.log(attempt('abc', 'b', 'X', 2 ** 53));
console.log(attempt('', 'b', 'X', 1));

prints:

RangeError: Expected `position` to be an integer from 0 to 3, got -1
RangeError: Expected `position` to be an integer from 0 to 3, got 1.5
RangeError: Expected `position` to be an integer from 0 to 3, got NaN
RangeError: Expected `position` to be an integer from 0 to 3, got 4
RangeError: Expected `position` to be an integer from 0 to 3, got Infinity
RangeError: Expected `position` to be an integer from 0 to 3, got 9007199254740992
RangeError: Expected `position` to be an integer from 0 to 0, got 1

The commonest way to get here is indexOf returning -1 because the text was not found:

import replaceStringAtPosition from 'replace-string-at-position';

const message = 'hi @bot please help';
const at = message.indexOf('@nobody');
console.log(at);
try {
	replaceStringAtPosition(message, '@nobody', '', at);
} catch (error) {
	console.log(error instanceof RangeError, error.message);
}

prints:

-1
true Expected `position` to be an integer from 0 to 19, got -1

1.0.4 did not throw here: it put newString at the start and cut sourceString.length - 1 code units from the front of the text, so ('abcdef', 'bcd', 'X', -1) gave 'Xcdef' (Versions and upgrading). Check for -1 before calling (Recipes).

The order of the checks

originalString, sourceString and newString are checked in that order, then the type of position, then its range. Only the first problem is reported:

console.log(attempt(null, 'b', 'X', 'x'));
console.log(attempt('abc', null, 5, 1));
console.log(attempt('abc', 'b', 5, -1));

prints:

TypeError: Expected `originalString` to be a string, got null
TypeError: Expected `sourceString` to be a string, got null
TypeError: Expected `newString` to be a string, got 5

How a value is described

Value Described as
a string the string "...", JSON-quoted, cut to 20 code points and ... when longer (never inside a surrogate pair)
a number its String() form: -1, 1.5, NaN, Infinity, 9007199254740992
null, undefined null, undefined
a BigInt a bigint (never its digits, however large)
a boolean, symbol or function a boolean, a symbol, a function
an array an array
any other object, a Number object, a Proxy, a revoked Proxy an object

The rows come from the published dist/index.cjs and the outputs above; the revoked Proxy row is covered by the repository's unit tests, not by a case on this page.

The error objects

They are ordinary TypeError and RangeError instances: instanceof works, and they have no code or other own properties besides message and stack. Match on the class, not on the message text, if your code handles them.

Clone this wiki locally