diff --git a/.size-limit.json b/.size-limit.json
index c3acaeaa..693b8897 100644
--- a/.size-limit.json
+++ b/.size-limit.json
@@ -37,7 +37,7 @@
{
"name": "es6-zip",
"path": "lib/dist/es6/mod/ts-utils.js",
- "limit": "10.5 Kb",
+ "limit": "10.75 Kb",
"gzip": true,
"running": false
},
diff --git a/README.md b/README.md
index c6953ca8..6226e4ed 100644
--- a/README.md
+++ b/README.md
@@ -123,7 +123,7 @@ Below is a categorized list of all available utilities with direct links to thei
| Number | [getIntValue](https://nevware21.github.io/ts-utils/typedoc/functions/getIntValue.html)(); [isInteger](https://nevware21.github.io/ts-utils/typedoc/functions/isInteger.html)(); [isFiniteNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isFiniteNumber.html)(); [isNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isNumber.html)();
| Math | [mathAbs](https://nevware21.github.io/ts-utils/typedoc/functions/mathAbs.html)(); [mathAcos](https://nevware21.github.io/ts-utils/typedoc/functions/mathAcos.html)(); [mathAsin](https://nevware21.github.io/ts-utils/typedoc/functions/mathAsin.html)(); [mathAtan](https://nevware21.github.io/ts-utils/typedoc/functions/mathAtan.html)(); [mathAtan2](https://nevware21.github.io/ts-utils/typedoc/functions/mathAtan2.html)(); [mathCeil](https://nevware21.github.io/ts-utils/typedoc/functions/mathCeil.html)(); [mathCos](https://nevware21.github.io/ts-utils/typedoc/functions/mathCos.html)(); [mathExp](https://nevware21.github.io/ts-utils/typedoc/functions/mathExp.html)(); [mathFloor](https://nevware21.github.io/ts-utils/typedoc/functions/mathFloor.html)(); [mathLog](https://nevware21.github.io/ts-utils/typedoc/functions/mathLog.html)(); [mathMax](https://nevware21.github.io/ts-utils/typedoc/functions/mathMax.html)(); [mathMin](https://nevware21.github.io/ts-utils/typedoc/functions/mathMin.html)(); [mathPow](https://nevware21.github.io/ts-utils/typedoc/functions/mathPow.html)(); [mathRandom](https://nevware21.github.io/ts-utils/typedoc/functions/mathRandom.html)(); [mathRound](https://nevware21.github.io/ts-utils/typedoc/functions/mathRound.html)(); [mathSin](https://nevware21.github.io/ts-utils/typedoc/functions/mathSin.html)(); [mathSqrt](https://nevware21.github.io/ts-utils/typedoc/functions/mathSqrt.html)(); [mathTan](https://nevware21.github.io/ts-utils/typedoc/functions/mathTan.html)(); [mathToInt](https://nevware21.github.io/ts-utils/typedoc/functions/mathToInt.html)(); [mathTrunc](https://nevware21.github.io/ts-utils/typedoc/functions/mathTrunc.html)();
| Object | [deepExtend](https://nevware21.github.io/ts-utils/typedoc/functions/deepExtend.html)(); [isObject](https://nevware21.github.io/ts-utils/typedoc/functions/isObject.html)(); [objAssign](https://nevware21.github.io/ts-utils/typedoc/functions/objAssign.html)(); [objCopyProps](https://nevware21.github.io/ts-utils/typedoc/functions/objCopyProps.html)(); [objCreate](https://nevware21.github.io/ts-utils/typedoc/functions/objCreate.html)(); [objDeepCopy](https://nevware21.github.io/ts-utils/typedoc/functions/objDeepCopy.html)(); [objDeepFreeze](https://nevware21.github.io/ts-utils/typedoc/functions/objDeepFreeze.html)(); [objDefine](https://nevware21.github.io/ts-utils/typedoc/functions/objDefine.html)(); [objDefineAccessors](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineAccessors.html)(); [objDefineGet](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineGet.html)(); [objDefineProp](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineProp.html)(); [objDefineProps](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineProps.html)(); [objDefineProperties](https://nevware21.github.io/ts-utils/typedoc/functions/objDefineProperties.html)(); [objEntries](https://nevware21.github.io/ts-utils/typedoc/functions/objEntries.html)(); [objExtend](https://nevware21.github.io/ts-utils/typedoc/functions/objExtend.html)(); [objForEachKey](https://nevware21.github.io/ts-utils/typedoc/functions/objForEachKey.html)(); [objFreeze](https://nevware21.github.io/ts-utils/typedoc/functions/objFreeze.html)(); [objFromEntries](https://nevware21.github.io/ts-utils/typedoc/functions/objFromEntries.html)(); [objGetOwnPropertyDescriptor](https://nevware21.github.io/ts-utils/typedoc/functions/objGetOwnPropertyDescriptor.html)(); [objGetOwnPropertyDescriptors](https://nevware21.github.io/ts-utils/typedoc/functions/objGetOwnPropertyDescriptors.html)(); [objGetOwnPropertyNames](https://nevware21.github.io/ts-utils/typedoc/functions/objGetOwnPropertyNames.html)(); [objGetOwnPropertySymbols](https://nevware21.github.io/ts-utils/typedoc/functions/objGetOwnPropertySymbols.html)(); [objHasOwn](https://nevware21.github.io/ts-utils/typedoc/functions/objHasOwn.html)(); [objHasOwnProperty](https://nevware21.github.io/ts-utils/typedoc/functions/objHasOwnProperty.html)(); [objIs](https://nevware21.github.io/ts-utils/typedoc/functions/objIs.html)(); [objIsExtensible](https://nevware21.github.io/ts-utils/typedoc/functions/objIsExtensible.html)(); [objIsFrozen](https://nevware21.github.io/ts-utils/typedoc/functions/objIsFrozen.html)(); [objIsSealed](https://nevware21.github.io/ts-utils/typedoc/functions/objIsSealed.html)(); [objKeys](https://nevware21.github.io/ts-utils/typedoc/functions/objKeys.html)(); [objPreventExtensions](https://nevware21.github.io/ts-utils/typedoc/functions/objPreventExtensions.html)(); [objPropertyIsEnumerable](https://nevware21.github.io/ts-utils/typedoc/functions/objPropertyIsEnumerable.html)(); [objSeal](https://nevware21.github.io/ts-utils/typedoc/functions/objSeal.html)(); [objGetPrototypeOf](https://nevware21.github.io/ts-utils/typedoc/functions/objGetPrototypeOf.html)(); [objSetPrototypeOf](https://nevware21.github.io/ts-utils/typedoc/functions/objSetPrototypeOf.html)(); [objToString](https://nevware21.github.io/ts-utils/typedoc/functions/objToString.html)(); [objValues](https://nevware21.github.io/ts-utils/typedoc/functions/objValues.html)();
[polyObjEntries](https://nevware21.github.io/ts-utils/typedoc/functions/polyObjEntries.html)(); [polyObjIs](https://nevware21.github.io/ts-utils/typedoc/functions/polyObjIs.html)(); [polyObjKeys](https://nevware21.github.io/ts-utils/typedoc/functions/polyObjKeys.html)();
-| String | [asString](https://nevware21.github.io/ts-utils/typedoc/functions/asString.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [isString](https://nevware21.github.io/ts-utils/typedoc/functions/isString.html)(); [strEndsWith](https://nevware21.github.io/ts-utils/typedoc/functions/strEndsWith.html)(); [strIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/strIndexOf.html)(); [strIsNullOrEmpty](https://nevware21.github.io/ts-utils/typedoc/functions/strIsNullOrEmpty.html)(); [strIsNullOrWhiteSpace](https://nevware21.github.io/ts-utils/typedoc/functions/strIsNullOrWhiteSpace.html)(); [strLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/strLastIndexOf.html)(); [strLeft](https://nevware21.github.io/ts-utils/typedoc/functions/strLeft.html)(); [strPadEnd](https://nevware21.github.io/ts-utils/typedoc/functions/strPadEnd.html)(); [strPadStart](https://nevware21.github.io/ts-utils/typedoc/functions/strPadStart.html)(); [strRepeat](https://nevware21.github.io/ts-utils/typedoc/functions/strRepeat.html)(); [strRight](https://nevware21.github.io/ts-utils/typedoc/functions/strRight.html)(); [strSlice](https://nevware21.github.io/ts-utils/typedoc/functions/strSlice.html)(); [strSplit](https://nevware21.github.io/ts-utils/typedoc/functions/strSplit.html)(); [strStartsWith](https://nevware21.github.io/ts-utils/typedoc/functions/strStartsWith.html)(); [strSubstr](https://nevware21.github.io/ts-utils/typedoc/functions/strSubstr.html)(); [strSubstring](https://nevware21.github.io/ts-utils/typedoc/functions/strSubstring.html)(); [strSymSplit](https://nevware21.github.io/ts-utils/typedoc/functions/strSymSplit.html)(); [strTrim](https://nevware21.github.io/ts-utils/typedoc/functions/strTrim.html)(); [strTrimEnd](https://nevware21.github.io/ts-utils/typedoc/functions/strTrimEnd.html)(); [strTrimLeft](https://nevware21.github.io/ts-utils/typedoc/functions/strTrimLeft.html)(); [strTrimRight](https://nevware21.github.io/ts-utils/typedoc/functions/strTrimRight.html)(); [strTrimStart](https://nevware21.github.io/ts-utils/typedoc/functions/strTrimStart.html)(); [strLetterCase](https://nevware21.github.io/ts-utils/typedoc/functions/strLetterCase.html)(); [strCamelCase](https://nevware21.github.io/ts-utils/typedoc/functions/strCamelCase.html)(); [strKebabCase](https://nevware21.github.io/ts-utils/typedoc/functions/strKebabCase.html)(); [strSnakeCase](https://nevware21.github.io/ts-utils/typedoc/functions/strSnakeCase.html)(); [strUpper](https://nevware21.github.io/ts-utils/typedoc/functions/strUpper.html)(); [strLower](https://nevware21.github.io/ts-utils/typedoc/functions/strLower.html)(); [strContains](https://nevware21.github.io/ts-utils/typedoc/functions/strContains.html)(); [strIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/strIncludes.html)();
[polyStrSubstr](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrSubstr.html)(); [polyStrTrim](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrTrim.html)(); [polyStrTrimEnd](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrTrimEnd.html)(); [polyStrTrimStart](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrTrimStart.html)(); [polyStrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrIncludes.html)();
+| String | [asString](https://nevware21.github.io/ts-utils/typedoc/functions/asString.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [isString](https://nevware21.github.io/ts-utils/typedoc/functions/isString.html)(); [strEndsWith](https://nevware21.github.io/ts-utils/typedoc/functions/strEndsWith.html)(); [strIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/strIndexOf.html)(); [strIsNullOrEmpty](https://nevware21.github.io/ts-utils/typedoc/functions/strIsNullOrEmpty.html)(); [strIsNullOrWhiteSpace](https://nevware21.github.io/ts-utils/typedoc/functions/strIsNullOrWhiteSpace.html)(); [strLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/strLastIndexOf.html)(); [strLeft](https://nevware21.github.io/ts-utils/typedoc/functions/strLeft.html)(); [strPadEnd](https://nevware21.github.io/ts-utils/typedoc/functions/strPadEnd.html)(); [strPadStart](https://nevware21.github.io/ts-utils/typedoc/functions/strPadStart.html)(); [strRepeat](https://nevware21.github.io/ts-utils/typedoc/functions/strRepeat.html)(); [strReplace](https://nevware21.github.io/ts-utils/typedoc/functions/strReplace.html)(); [strReplaceAll](https://nevware21.github.io/ts-utils/typedoc/functions/strReplaceAll.html)(); [strRight](https://nevware21.github.io/ts-utils/typedoc/functions/strRight.html)(); [strSlice](https://nevware21.github.io/ts-utils/typedoc/functions/strSlice.html)(); [strSplit](https://nevware21.github.io/ts-utils/typedoc/functions/strSplit.html)(); [strStartsWith](https://nevware21.github.io/ts-utils/typedoc/functions/strStartsWith.html)(); [strSubstr](https://nevware21.github.io/ts-utils/typedoc/functions/strSubstr.html)(); [strSubstring](https://nevware21.github.io/ts-utils/typedoc/functions/strSubstring.html)(); [strSymSplit](https://nevware21.github.io/ts-utils/typedoc/functions/strSymSplit.html)(); [strTrim](https://nevware21.github.io/ts-utils/typedoc/functions/strTrim.html)(); [strTrimEnd](https://nevware21.github.io/ts-utils/typedoc/functions/strTrimEnd.html)(); [strTrimLeft](https://nevware21.github.io/ts-utils/typedoc/functions/strTrimLeft.html)(); [strTrimRight](https://nevware21.github.io/ts-utils/typedoc/functions/strTrimRight.html)(); [strTrimStart](https://nevware21.github.io/ts-utils/typedoc/functions/strTrimStart.html)(); [strLetterCase](https://nevware21.github.io/ts-utils/typedoc/functions/strLetterCase.html)(); [strCamelCase](https://nevware21.github.io/ts-utils/typedoc/functions/strCamelCase.html)(); [strKebabCase](https://nevware21.github.io/ts-utils/typedoc/functions/strKebabCase.html)(); [strSnakeCase](https://nevware21.github.io/ts-utils/typedoc/functions/strSnakeCase.html)(); [strUpper](https://nevware21.github.io/ts-utils/typedoc/functions/strUpper.html)(); [strLower](https://nevware21.github.io/ts-utils/typedoc/functions/strLower.html)(); [strContains](https://nevware21.github.io/ts-utils/typedoc/functions/strContains.html)(); [strIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/strIncludes.html)();
[polyStrSubstr](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrSubstr.html)(); [polyStrTrim](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrTrim.html)(); [polyStrTrimEnd](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrTrimEnd.html)(); [polyStrTrimStart](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrTrimStart.html)(); [polyStrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/polyStrIncludes.html)();
| Symbol | [WellKnownSymbols](https://nevware21.github.io/ts-utils/typedoc/enums/WellKnownSymbols.html) (const enum);
[getKnownSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/getKnownSymbol.html)(); [getSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/getSymbol.html)(); [hasSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/hasSymbol.html)(); [isSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/isSymbol.html)(); [newSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/newSymbol.html)(); [symbolFor](https://nevware21.github.io/ts-utils/typedoc/functions/symbolFor.html)(); [symbolKeyFor](https://nevware21.github.io/ts-utils/typedoc/functions/symbolKeyFor.html)();
[polyGetKnownSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/polyGetKnownSymbol.html)(); [polyNewSymbol](https://nevware21.github.io/ts-utils/typedoc/functions/polyNewSymbol.html)(); [polySymbolFor](https://nevware21.github.io/ts-utils/typedoc/functions/polySymbolFor.html)(); [polySymbolKeyFor](https://nevware21.github.io/ts-utils/typedoc/functions/polySymbolKeyFor.html)();
Polyfills are used to automatically backfill runtimes that do not support `Symbol`, not all of the Symbol functionality is provided.
| Timer | [createTimeout](https://nevware21.github.io/ts-utils/typedoc/functions/createTimeout.html)(); [createTimeoutWith](https://nevware21.github.io/ts-utils/typedoc/functions/createTimeoutWith.html)(); [elapsedTime](https://nevware21.github.io/ts-utils/typedoc/functions/elapsedTime.html)(); [perfNow](https://nevware21.github.io/ts-utils/typedoc/functions/perfNow.html)(); [setGlobalTimeoutOverrides](https://nevware21.github.io/ts-utils/typedoc/functions/setGlobalTimeoutOverrides.html)(); [setTimeoutOverrides](https://nevware21.github.io/ts-utils/typedoc/functions/setTimeoutOverrides.html)(); [utcNow](https://nevware21.github.io/ts-utils/typedoc/functions/utcNow.html)(); [scheduleIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleIdleCallback.html)(); [scheduleInterval](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleInterval.html)(); [scheduleTimeout](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleTimeout.html)(); [scheduleTimeoutWith](https://nevware21.github.io/ts-utils/typedoc/functions/scheduleTimeoutWith.html)(); [hasIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/hasIdleCallback.html)();
For runtimes that don't support `requestIdleCallback` normal setTimeout() is used with the values from [`setDefaultIdleTimeout`](https://nevware21.github.io/ts-utils/typedoc/functions/setDefaultIdleTimeout.html)() and [`setDefaultMaxExecutionTime`](https://nevware21.github.io/ts-utils/typedoc/functions/setDefaultMaxExecutionTime.html)();
[polyUtcNow](https://nevware21.github.io/ts-utils/typedoc/functions/polyUtcNow.html)();
| Conversion & Encoding | [encodeAsJson](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsJson.html)(); [encodeAsHtml](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsHtml.html)(); [encodeAsBase64](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsBase64.html)(); [decodeBase64](https://nevware21.github.io/ts-utils/typedoc/functions/decodeBase64.html)(); [encodeAsBase64Url](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsBase64Url.html)(); [decodeBase64Url](https://nevware21.github.io/ts-utils/typedoc/functions/decodeBase64Url.html)(); [encodeAsHex](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsHex.html)(); [decodeHex](https://nevware21.github.io/ts-utils/typedoc/functions/decodeHex.html)(); [encodeAsUri](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsUri.html)(); [decodeUri](https://nevware21.github.io/ts-utils/typedoc/functions/decodeUri.html)(); [asString](https://nevware21.github.io/ts-utils/typedoc/functions/asString.html)(); [getIntValue](https://nevware21.github.io/ts-utils/typedoc/functions/getIntValue.html)(); [normalizeJsName](https://nevware21.github.io/ts-utils/typedoc/functions/normalizeJsName.html)(); [strLetterCase](https://nevware21.github.io/ts-utils/typedoc/functions/strLetterCase.html)(); [strCamelCase](https://nevware21.github.io/ts-utils/typedoc/functions/strCamelCase.html)(); [strKebabCase](https://nevware21.github.io/ts-utils/typedoc/functions/strKebabCase.html)(); [strSnakeCase](https://nevware21.github.io/ts-utils/typedoc/functions/strSnakeCase.html)(); [strUpper](https://nevware21.github.io/ts-utils/typedoc/functions/strUpper.html)(); [strLower](https://nevware21.github.io/ts-utils/typedoc/functions/strLower.html)();
@@ -225,6 +225,7 @@ import {
arrFindLast, arrFindLastIndex, // ES2023
objGetOwnPropertyDescriptors, // ES2017
strPadStart, strPadEnd, // ES2017
+ strReplaceAll, // ES2021
isBigInt, // ES2020
isWeakMap, isWeakSet // ES2015+
} from "@nevware21/ts-utils";
@@ -241,6 +242,9 @@ const descriptors = objGetOwnPropertyDescriptors(myObject);
const paddedString = strPadStart("123", 5, "0"); // "00123"
const paddedEnd = strPadEnd("hello", 10, "."); // "hello....."
+// String replaceAll (ES2021)
+const replaced = strReplaceAll("a-b-a", "a", "x"); // "x-b-x"
+
// Safe type checks for modern types
if (isBigInt(someValue)) {
// Handle BigInt (ES2020) safely
diff --git a/docs/feature-backlog.md b/docs/feature-backlog.md
new file mode 100644
index 00000000..63671d0b
--- /dev/null
+++ b/docs/feature-backlog.md
@@ -0,0 +1,89 @@
+# Feature Backlog and Request Tracking
+
+This document tracks suggested additions for `@nevware21/ts-utils`.
+
+## Request RQ-2026-03-17-ADDITIONS
+
+- Request: Identify what additions could be added to ts-utils
+- Requested by: Maintainer discussion
+- Priority: High
+- Scope: Array, object, string, iterator, typing, and documentation improvements
+
+### Objective
+
+Identify practical, minification-friendly, cross-environment additions that fit the library design principles:
+
+- zero dependencies
+- ES5 compatibility for v0.x / v1.x
+- polyfill-backed behavior where needed
+- strong tree-shaking and small bundles
+
+## Suggested Additions (Proposed Only)
+
+### Language-Native Suggestions (with ECMAScript Version)
+
+The following suggestions map directly to JavaScript language / standard library features:
+
+- `strReplaceAll` wrapper/polyfill path
+ - JavaScript feature: `String.prototype.replaceAll()`
+ - Added in: **ECMAScript 2021 (ES12)**
+
+| Suggestion | JavaScript Feature | ECMAScript Version |
+| --- | --- | --- |
+| `strReplaceAll` wrapper/polyfill path | `String.prototype.replaceAll()` | ECMAScript 2021 (ES12) |
+
+Notes:
+
+- Other suggestions below are library-level utilities (not direct language features).
+- Iterator helpers are intentionally listed as utility suggestions here rather than standard-language mappings.
+
+### A. Typing Improvements (High Value)
+
+- `ReadonlyRecord` helper type alias for API ergonomics
+- `DeepPartial` utility type
+- `DeepReadonly` utility type
+- `Mutable` utility type for controlled writable transformations
+
+### B. Object Utilities (Medium Value)
+
+- `objPick` / `objOmit`
+- `objMapValues`
+- `objMergeIf`
+- `objDiff`
+
+Notes:
+
+- maintain plain-object safety patterns
+- avoid behavior changes to existing deep copy helpers
+
+### C. String Utilities (Medium Value)
+
+- `strTruncate` (with optional suffix)
+- `strCount` (substring occurrences)
+- `strReplaceAll` wrapper/polyfill path **(ECMAScript 2021 / ES12 language feature wrapper)**
+- `strCapitalizeWords`
+
+### D. Iterator and Collection Helpers (Medium Value)
+
+- `iterMap`, `iterFilter`, `iterTake`
+- `arrToMap` helpers with stable key selection
+- lightweight set operations for iterables
+
+### E. Reliability and Tooling (High Value)
+
+- keep bundle-size thresholds justified with measured report
+- require test parity for polyfill vs native behavior
+- ensure newly exported functions are reflected in README utility matrix
+
+## Acceptance Criteria for this Request
+
+- [x] Identify additions by category
+- [x] Prioritize additions by value and fit
+- [ ] Create follow-up issue list for proposed items
+- [ ] Add ownership and target milestone per item
+
+## Next Actions
+
+1. Open GitHub issues for sections A-D candidates.
+2. Add milestone tags for upcoming releases.
+3. Keep this document focused on proposed additions only.
diff --git a/lib/src/array/with.ts b/lib/src/array/with.ts
index 7e93a709..2765af64 100644
--- a/lib/src/array/with.ts
+++ b/lib/src/array/with.ts
@@ -11,6 +11,7 @@ import { _unwrapFunctionWithPoly } from "../internal/unwrapFunction";
import { isArrayLike } from "../helpers/base";
import { getLength } from "../helpers/length";
import { arrSlice } from "./slice";
+import { throwRangeError } from "../helpers/throw";
/**
* The arrWith() method is the copying version of using the bracket notation to change the value
@@ -60,7 +61,7 @@ export function polyArrWith(theArray: ArrayLike, index: number, value: T):
let result: T[];
if (!isArrayLike(theArray)) {
- throw new RangeError("Invalid array");
+ throwRangeError("Invalid array");
}
const len = getLength(theArray);
@@ -73,7 +74,7 @@ export function polyArrWith(theArray: ArrayLike, index: number, value: T):
// Check bounds
if (idx < 0 || idx >= len) {
- throw new RangeError("Index out of bounds");
+ throwRangeError("Index out of bounds");
}
// Create a copy and set value
diff --git a/lib/src/helpers/encode.ts b/lib/src/helpers/encode.ts
index 5b309336..9173c5f7 100644
--- a/lib/src/helpers/encode.ts
+++ b/lib/src/helpers/encode.ts
@@ -10,6 +10,7 @@ import { EMPTY, NULL_VALUE, TO_STRING, UNDEF_VALUE } from "../internal/constants
import { asString } from "../string/as_string";
import { strCamelCase } from "../string/conversion";
import { strPadStart } from "../string/pad";
+import { strReplace } from "../string/replace";
import { strRepeat } from "../string/repeat";
import { strSubstr } from "../string/substring";
import { strUpper } from "../string/upper_lower";
@@ -79,7 +80,7 @@ let _base64Cache: { [key: string]: number };
*/
/*#__NO_SIDE_EFFECTS__*/
export function normalizeJsName(jsName: string, camelCase?: boolean): string {
- let result = asString(jsName).replace(INVALID_JS_NAME, "_");
+ let result = strReplace(asString(jsName), INVALID_JS_NAME, "_");
return !isUndefined(camelCase) ? strCamelCase(result, !camelCase) : result;
}
@@ -129,7 +130,7 @@ export function encodeAsJson(value: T, format?: boolean | number): string {
if (isString(value)) {
// encode if a character is not an alpha, numeric, space or some special characters
- result = DBL_QUOTE + value.replace(/[^\w .,\-!@#$%\^&*\(\)_+={}\[\]:;|<>?]/g, (match) => {
+ result = DBL_QUOTE + strReplace(value, /[^\w .,\-!@#$%\^&*\(\)_+={}\[\]:;|<>?]/g, (match) => {
if(match === DBL_QUOTE || match === "\\") {
return "\\" + match;
}
@@ -184,7 +185,7 @@ export function encodeAsHtml(value: string) {
"'": "#39"
});
- return asString(value).replace(/[&<>"']/g, match => "&" + _htmlEntityCache[match] + ";");
+ return strReplace(asString(value), /[&<>"']/g, match => "&" + _htmlEntityCache[match] + ";");
}
/**
@@ -273,7 +274,7 @@ export function encodeAsBase64Url(value: string): string {
let encoded = encodeAsBase64(value);
if (encoded) {
- encoded = encoded.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
+ encoded = strReplace(strReplace(strReplace(encoded, /\+/g, "-"), /\//g, "_"), /=/g, "");
}
return encoded || EMPTY;
@@ -304,7 +305,7 @@ export function decodeBase64Url(value: string): string {
result = result + strRepeat("=", pad);
}
- result = decodeBase64(result.replace(/-/g, "+").replace(/_/g, "/")) || EMPTY;
+ result = decodeBase64(strReplace(strReplace(result, /-/g, "+"), /_/g, "/")) || EMPTY;
}
return result || value || EMPTY;
diff --git a/lib/src/helpers/regexp.ts b/lib/src/helpers/regexp.ts
index 1db2aca8..e68ba8bc 100644
--- a/lib/src/helpers/regexp.ts
+++ b/lib/src/helpers/regexp.ts
@@ -8,6 +8,7 @@
import { EMPTY } from "../internal/constants";
import { asString } from "../string/as_string";
+import { strReplace } from "../string/replace";
const MATCH_ANY = "(.*)";
const MATCH_SINGLE = "(.)";
@@ -27,7 +28,7 @@ const MATCH_SINGLE = "(.)";
function _createRegExp(value: string, escapeRgx: RegExp, replaceFn: (value: string) => string, ignoreCase: boolean, fullMatch?: boolean) {
// eslint-disable-next-line security/detect-non-literal-regexp
return new RegExp(
- (fullMatch ? "^" : EMPTY) + replaceFn(value.replace(escapeRgx, "\\$1")) + (fullMatch ? "$" : EMPTY),
+ (fullMatch ? "^" : EMPTY) + replaceFn(strReplace(value, escapeRgx, "\\$1")) + (fullMatch ? "$" : EMPTY),
ignoreCase ? "i" : "");
}
@@ -70,7 +71,7 @@ function _createRegExp(value: string, escapeRgx: RegExp, replaceFn: (value: stri
/*#__NO_SIDE_EFFECTS__*/
export function createWildcardRegex(value: string, ignoreCase?: boolean, fullMatch?: boolean) {
return _createRegExp(asString(value), /([-+|^$#\.\?{}()\[\]\\\/\"\'])/g, (value: string) => {
- return value.replace(/\*/g, MATCH_ANY);
+ return strReplace(value, /\*/g, MATCH_ANY);
}, !!ignoreCase, fullMatch);
}
@@ -120,7 +121,7 @@ export function createWildcardRegex(value: string, ignoreCase?: boolean, fullMat
/*#__NO_SIDE_EFFECTS__*/
export function createFilenameRegex(value: string, ignoreCase?: boolean, fullMatch?: boolean) {
return _createRegExp(asString(value), /([-+|^$#\.{}()\\\/\[\]\"\'])/g, (value: string) => {
- return value.replace(/(\\\\|\\\/|\*|\?)/g, function (_all, g1) {
+ return strReplace(value, /(\\\\|\\\/|\*|\?)/g, function (_all, g1) {
if (g1 == "\\/" || g1 == "\\\\") {
return "[\\\\\\/]{1}";
}
@@ -195,7 +196,7 @@ export function createFilenameRegex(value: string, ignoreCase?: boolean, fullMat
export function makeGlobRegex(value: string, ignoreCase?: boolean, fullMatch?: boolean) {
return _createRegExp(asString(value), /([-+|^$#\.{}()\\\/\[\]\"\'])/g, (value: string) => {
//"**\/*\.txt"
- return value.replace(/(\*\*\\[\\\/]|\\\\|\\\/|\*\*|\*|\?)/g, function (_all, g1) {
+ return strReplace(value, /(\*\*\\[\\\/]|\\\\|\\\/|\*\*|\*|\?)/g, function (_all, g1) {
if (g1 == "**\\/" || g1 == "**\\\\") {
return "(.*[\\\\\\/])*";
}
diff --git a/lib/src/index.ts b/lib/src/index.ts
index 59149f54..98f80c87 100644
--- a/lib/src/index.ts
+++ b/lib/src/index.ts
@@ -128,6 +128,8 @@ export { strContains, strIncludes, polyStrIncludes } from "./string/includes";
export { strIndexOf, strLastIndexOf } from "./string/index_of";
export { strIsNullOrWhiteSpace, strIsNullOrEmpty } from "./string/is_null_or";
export { strPadEnd, strPadStart } from "./string/pad";
+export { strReplace } from "./string/replace";
+export { strReplaceAll } from "./string/replace_all";
export { strRepeat } from "./string/repeat";
export { strSlice } from "./string/slice";
export { strStartsWith } from "./string/starts_with";
diff --git a/lib/src/polyfills.ts b/lib/src/polyfills.ts
index 512fbeab..a65483a1 100644
--- a/lib/src/polyfills.ts
+++ b/lib/src/polyfills.ts
@@ -18,6 +18,7 @@ import { polyStrPadEnd, polyStrPadStart } from "./string/pad";
import { makePolyFn } from "./internal/poly_helpers";
import { polyStrSubstr } from "./string/substring";
import { polyStrIncludes } from "./string/includes";
+import { polyStrReplaceAll } from "./string/replace_all";
import { polyObjFromEntries } from "./polyfills/object/objFromEntries";
import { polyObjGetOwnPropertyDescriptors, _polyObjGetOwnPropertySymbols, _polyObjGetOwnPropertyNames } from "./polyfills/object/objGetOwnProperty";
import { polyObjPreventExtensions } from "./polyfills/object/objPreventExtensions";
@@ -58,7 +59,8 @@ import { polyArrWith } from "./array/with";
"trimEnd": polyStrTrimEnd,
"trimRight": polyStrTrimEnd,
"substr": polyStrSubstr,
- "includes": polyStrIncludes
+ "includes": polyStrIncludes,
+ "replaceAll": polyStrReplaceAll
};
const arrayClsPolyfills = {
@@ -104,4 +106,4 @@ import { polyArrWith } from "./array/with";
});
})();
-export { polyArrAt, polyArrFill, polyArrWith };
+export { polyArrAt, polyArrFill, polyArrWith, polyStrReplaceAll };
diff --git a/lib/src/polyfills/trim.ts b/lib/src/polyfills/trim.ts
index 8bb9e31e..2420ee7c 100644
--- a/lib/src/polyfills/trim.ts
+++ b/lib/src/polyfills/trim.ts
@@ -8,14 +8,17 @@
import { EMPTY } from "../internal/constants";
import { _throwIfNullOrUndefined } from "../internal/throwIf";
+import { asString } from "../string/as_string";
+import { strReplace } from "../string/replace";
/*#__NO_SIDE_EFFECTS__*/
function _createTrimFn(exp: RegExp): (value: string) => string {
return function _doTrim(value: string): string {
_throwIfNullOrUndefined(value);
+ value = asString(value);
- if (value && value.replace) {
- value = value.replace(exp, EMPTY);
+ if (value) {
+ value = strReplace(value, exp, EMPTY);
}
return value;
diff --git a/lib/src/string/conversion.ts b/lib/src/string/conversion.ts
index d9f695e6..3f444332 100644
--- a/lib/src/string/conversion.ts
+++ b/lib/src/string/conversion.ts
@@ -8,6 +8,7 @@
import { EMPTY } from "../internal/constants";
import { asString } from "./as_string";
+import { strReplace } from "./replace";
import { strTrim } from "./trim";
import { strLower, strUpper } from "./upper_lower";
@@ -22,7 +23,7 @@ import { strLower, strUpper } from "./upper_lower";
*/
/*#__NO_SIDE_EFFECTS__*/
function _convertCase(value: T, newPrefix: string, upperWord?: boolean): string {
- return strTrim(asString(value)).replace(/((_|\W)+(\w){0,1}|([a-z])([A-Z]))/g,
+ return strReplace(strTrim(asString(value)), /((_|\W)+(\w){0,1}|([a-z])([A-Z]))/g,
(_match, _g1, _g2, wordStart, upperPrefix, upperLetter) => {
let convertMatch = wordStart || upperLetter|| EMPTY;
if (upperWord) {
@@ -53,7 +54,7 @@ function _convertCase(value: T, newPrefix: string, upperWord?: boolean): stri
*/
/*#__NO_SIDE_EFFECTS__*/
export function strLetterCase(value: T): string {
- return asString(value).replace(/(_|\b)\w/g, strUpper);
+ return strReplace(asString(value), /(_|\b)\w/g, strUpper);
}
/**
@@ -92,7 +93,7 @@ export function strLetterCase(value: T): string {
export function strCamelCase(value: T, upperFirst?: boolean): string {
let result = _convertCase(value, "", true);
- return result.replace(/^\w/, upperFirst ? strUpper : strLower);
+ return strReplace(result, /^\w/, upperFirst ? strUpper : strLower);
}
/**
diff --git a/lib/src/string/is_null_or.ts b/lib/src/string/is_null_or.ts
index 227f3999..6471d52f 100644
--- a/lib/src/string/is_null_or.ts
+++ b/lib/src/string/is_null_or.ts
@@ -8,6 +8,7 @@
import { isNullOrUndefined, isString } from "../helpers/base";
import { EMPTY } from "../internal/constants";
+import { strReplace } from "./replace";
/**
* This method checks if the string `value` is null, undefined, an empty string or only contains
@@ -19,7 +20,7 @@ import { EMPTY } from "../internal/constants";
/*#__NO_SIDE_EFFECTS__*/
export function strIsNullOrWhiteSpace(value: string): boolean {
if (isString(value)) {
- return value.replace(/[\s\t\r\n\f]+/g, EMPTY) === EMPTY;
+ return strReplace(value, /[\s\t\r\n\f]+/g, EMPTY) === EMPTY;
}
return isNullOrUndefined(value)
diff --git a/lib/src/string/replace.ts b/lib/src/string/replace.ts
new file mode 100644
index 00000000..d0f197ec
--- /dev/null
+++ b/lib/src/string/replace.ts
@@ -0,0 +1,29 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import { StrProto } from "../internal/constants";
+import { _unwrapFunction } from "../internal/unwrapFunction";
+
+/**
+ * The strReplace() method returns a new string with one, some, or all matches of a pattern replaced
+ * by a replacement.
+ * @function
+ * @since 0.14.0
+ * @group String
+ * @param value - The string value to search and replace within.
+ * @param searchValue - The value to search for. Can be a string or regular expression.
+ * @param replaceValue - The replacement string or replacer function.
+ * @returns A new string with one, some, or all matches replaced.
+ * @example
+ * ```ts
+ * strReplace("a-b-a", "a", "x"); // "x-b-a"
+ * strReplace("a1b2", /\d/g, "#"); // "a#b#"
+ * strReplace("hello", /[aeiou]/, "*"); // "h*llo"
+ * ```
+ */
+export const strReplace: (value: string, searchValue: string | RegExp, replaceValue: string | ((substring: string, ...args: any[]) => string)) => string = (/*#__PURE__*/_unwrapFunction("replace", StrProto as any));
diff --git a/lib/src/string/replace_all.ts b/lib/src/string/replace_all.ts
new file mode 100644
index 00000000..373c31a1
--- /dev/null
+++ b/lib/src/string/replace_all.ts
@@ -0,0 +1,85 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import { isFunction, isRegExp, isStrictNullOrUndefined, isString } from "../helpers/base";
+import { throwTypeError } from "../helpers/throw";
+import { StrProto } from "../internal/constants";
+import { _throwIfNullOrUndefined } from "../internal/throwIf";
+import { _unwrapFunctionWithPoly } from "../internal/unwrapFunction";
+import { getKnownSymbol } from "../symbol/symbol";
+import { WellKnownSymbols } from "../symbol/well_known";
+import { asString } from "./as_string";
+import { strReplace } from "./replace";
+
+/**
+ * The strReplaceAll() method returns a new string with all matches of a pattern replaced by a replacement.
+ * @function
+ * @since 0.14.0
+ * @group String
+ * @param value - The string value to search and replace within.
+ * @param searchValue - The value to search for. Can be a string or a global regular expression.
+ * @param replaceValue - The replacement string or replacer function.
+ * @returns A new string with every occurrence of searchValue replaced.
+ * @throws TypeError if searchValue is a regular expression without the global flag.
+ * @example
+ * ```ts
+ * strReplaceAll("a-b-a", "a", "x"); // "x-b-x"
+ * strReplaceAll("abc123abc", /abc/g, "X"); // "X123X"
+ * strReplaceAll("abca", "a", (value, index) => value.toUpperCase() + index);
+ * // "A0bcA3"
+ * ```
+ */
+export const strReplaceAll: (value: string, searchValue: string | RegExp, replaceValue: string | ((substring: string, ...args: any[]) => string)) => string = (/*#__PURE__*/_unwrapFunctionWithPoly("replaceAll", StrProto as any, polyStrReplaceAll));
+
+/**
+ * Polyfill implementation of String.prototype.replaceAll().
+ * @since 0.14.0
+ * @group String
+ * @group Polyfill
+ * @param value - The string value to search and replace within.
+ * @param searchValue - The value to search for. Can be a string or a global regular expression.
+ * @param replaceValue - The replacement string or replacer function.
+ * @returns A new string with every occurrence of searchValue replaced.
+ * @throws TypeError if searchValue is a regular expression without the global flag.
+ */
+/*#__NO_SIDE_EFFECTS__*/
+export function polyStrReplaceAll(value: string, searchValue: string | RegExp, replaceValue: string | ((substring: string, ...args: any[]) => string)): string {
+ _throwIfNullOrUndefined(value);
+
+ let matchSymbol = getKnownSymbol(WellKnownSymbols.match);
+ let replaceSymbol = getKnownSymbol(WellKnownSymbols.replace);
+ let replaceFn: (value: string, replaceValue: string | ((substring: string, ...args: any[]) => string)) => string;
+ let matcher: string | RegExp;
+ let isRegex = isRegExp(searchValue);
+ let theValue = isString(value) ? value : asString(value);
+ let isSearchNotNull = searchValue || !isStrictNullOrUndefined(searchValue);
+
+ if (isRegex && isSearchNotNull) {
+ isRegex = (searchValue as any)[matchSymbol] !== false;
+ }
+
+ if (isRegex) {
+ if (!(searchValue as RegExp).global) {
+ throwTypeError("searchValue must be a global regular expression");
+ }
+
+ matcher = searchValue;
+ } else {
+ replaceFn = isSearchNotNull ? (searchValue as any)[replaceSymbol] : null;
+ if (isFunction(replaceFn)) {
+ return replaceFn.call(searchValue, theValue, replaceValue);
+ }
+
+ let search = isString(searchValue) ? searchValue : asString(searchValue);
+
+ // eslint-disable-next-line security/detect-non-literal-regexp
+ matcher = new RegExp(strReplace(search, /[.*+?^${}()|[\]\\]/g, "\\$&") || "(?:)", "g");
+ }
+
+ return strReplace(theValue, matcher, replaceValue as any);
+}
diff --git a/lib/test/bundle-size-check.js b/lib/test/bundle-size-check.js
index 6d5dd6c6..56709fe7 100644
--- a/lib/test/bundle-size-check.js
+++ b/lib/test/bundle-size-check.js
@@ -7,7 +7,7 @@ const configs = [
{
name: "es5-min-full",
path: "../bundle/es5/umd/ts-utils.min.js",
- limit: 30.5 * 1024, // 30.5 kb in bytes
+ limit: 30.75 * 1024, // 30.75 kb in bytes
compress: false
},
{
@@ -31,7 +31,7 @@ const configs = [
{
name: "es5-min-poly",
path: "../bundle/es5/ts-polyfills-utils.min.js",
- limit: 9 * 1024, // 9 kb in bytes
+ limit: 9.5 * 1024, // 9.5 kb in bytes
compress: false
}
];
diff --git a/lib/test/src/common/array/with.test.ts b/lib/test/src/common/array/with.test.ts
index ef5f7eb7..ce58006d 100644
--- a/lib/test/src/common/array/with.test.ts
+++ b/lib/test/src/common/array/with.test.ts
@@ -65,8 +65,8 @@ describe("arrWith", () => {
it("should throw for null and undefined array", () => {
// Native .with() throws TypeError for null/undefined
- assert.throws(() => arrWith(null, 0, 99));
- assert.throws(() => arrWith(undefined, 0, 99));
+ assert.throws(() => arrWith(null as any, 0, 99));
+ assert.throws(() => arrWith(undefined as any, 0, 99));
});
it("should work with array-like objects", () => {
@@ -84,6 +84,13 @@ describe("arrWith", () => {
});
describe("polyArrWith", () => {
+ it("should throw RangeError for invalid array-like input", () => {
+ assert.throws(() => polyArrWith(null as any, 0, 99), RangeError);
+ assert.throws(() => polyArrWith(undefined as any, 0, 99), RangeError);
+ assert.throws(() => polyArrWith(123 as any, 0, 99), RangeError);
+ assert.throws(() => polyArrWith({} as any, 0, 99), RangeError);
+ });
+
it("should match native Array.prototype.with for valid indices", () => {
const arr: any = [1, 2, 3, 4, 5];
diff --git a/lib/test/src/common/string/replace.test.ts b/lib/test/src/common/string/replace.test.ts
new file mode 100644
index 00000000..0f4056bc
--- /dev/null
+++ b/lib/test/src/common/string/replace.test.ts
@@ -0,0 +1,79 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import { assert } from "@nevware21/tripwire-chai";
+import { dumpObj } from "../../../../src/helpers/diagnostics";
+import { strReplace } from "../../../../src/string/replace";
+
+describe("strReplace", () => {
+ it("basic replacement", () => {
+ assert.equal(strReplace("a-b-a", "a", "x"), "x-b-a");
+ });
+
+ it("supports regular expressions", () => {
+ assert.equal(strReplace("abc123def", /\d+/g, "#"), "abc#def");
+ });
+
+ it("supports replacement function", () => {
+ assert.equal(strReplace("a1b2", /\d/g, (_match, index) => "(" + index + ")"), "a(1)b(3)");
+ });
+
+ it("throws for null and undefined values", () => {
+ assert.throws(() => strReplace(null as any, "a", "b"));
+ assert.throws(() => strReplace(undefined as any, "a", "b"));
+ });
+
+ it("matches native replace semantics", () => {
+ let replacer = (match: string, offset: number) => {
+ return "(" + match + ":" + offset + ")";
+ };
+
+ let testCases: { value: any; searchValue: any; replaceValue: any }[] = [
+ { value: "banana", searchValue: "a", replaceValue: "o" },
+ { value: "abc123abc", searchValue: /abc/g, replaceValue: "X" },
+ { value: "abc123abc", searchValue: /\d+/, replaceValue: replacer },
+ { value: "a.b.a.b", searchValue: ".", replaceValue: "!" },
+ { value: "aaa", searchValue: "a", replaceValue: "$$" }
+ ];
+
+ for (let lp = 0; lp < testCases.length; lp++) {
+ let testCase = testCases[lp];
+ _checkNativeParity(testCase.value, testCase.searchValue, testCase.replaceValue);
+ }
+ });
+
+ function _checkNativeParity(value: any, searchValue: any, replaceValue: any) {
+ let testResult: any;
+ let nativeResult: any;
+ let testThrew: any;
+ let nativeThrew: any;
+
+ try {
+ testResult = strReplace(value, searchValue, replaceValue);
+ } catch (e) {
+ testThrew = e;
+ }
+
+ try {
+ nativeResult = String.prototype.replace.call(value, searchValue, replaceValue);
+ } catch (e) {
+ nativeThrew = e;
+ }
+
+ if (testThrew) {
+ assert.equal(true, !!nativeThrew,
+ "Checking whether Native and strReplace both threw [" + dumpObj(testThrew) + "] - [" + dumpObj(nativeThrew || nativeResult) + "] for [" + dumpObj(value) + "] [" + dumpObj(searchValue) + "]");
+ } else if (nativeThrew) {
+ assert.ok(false,
+ "Native threw but strReplace did not [" + dumpObj(testResult) + "] - [" + dumpObj(nativeThrew) + "] for [" + dumpObj(value) + "] [" + dumpObj(searchValue) + "]");
+ } else {
+ assert.equal(testResult, nativeResult,
+ "Checking whether Native and strReplace returned the same [" + dumpObj(testResult) + "] - [" + dumpObj(nativeResult) + "] for [" + dumpObj(value) + "] [" + dumpObj(searchValue) + "]");
+ }
+ }
+});
diff --git a/lib/test/src/common/string/replace_all.test.ts b/lib/test/src/common/string/replace_all.test.ts
new file mode 100644
index 00000000..1714aa98
--- /dev/null
+++ b/lib/test/src/common/string/replace_all.test.ts
@@ -0,0 +1,195 @@
+/*
+ * @nevware21/ts-utils
+ * https://github.com/nevware21/ts-utils
+ *
+ * Copyright (c) 2026 NevWare21 Solutions LLC
+ * Licensed under the MIT license.
+ */
+
+import { assert } from "@nevware21/tripwire-chai";
+import { dumpObj } from "../../../../src/helpers/diagnostics";
+import { polyStrReplaceAll, strReplaceAll } from "../../../../src/string/replace_all";
+import { getKnownSymbol } from "../../../../src/symbol/symbol";
+import { WellKnownSymbols } from "../../../../src/symbol/well_known";
+import { isNode } from "../../../../src/helpers/environment";
+
+describe("strReplaceAll helper", () => {
+ it("basic string replacement", () => {
+ assert.equal(strReplaceAll("a-b-a-b", "a", "x"), "x-b-x-b");
+ assert.equal(polyStrReplaceAll("a-b-a-b", "a", "x"), "x-b-x-b");
+ });
+
+ it("supports empty search string", () => {
+ assert.equal(strReplaceAll("abc", "", "-"), "-a-b-c-");
+ assert.equal(polyStrReplaceAll("abc", "", "-"), "-a-b-c-");
+ });
+
+ it("supports replacement patterns", () => {
+ assert.equal(strReplaceAll("abc", "b", "[$&]"), "a[b]c");
+ assert.equal(polyStrReplaceAll("abc", "b", "[$&]"), "a[b]c");
+ });
+
+ it("supports function replacements", () => {
+ assert.equal(strReplaceAll("abca", "a", (value, index) => value.toUpperCase() + index), "A0bcA3");
+ assert.equal(polyStrReplaceAll("abca", "a", (value, index) => value.toUpperCase() + index), "A0bcA3");
+ });
+
+ it("supports global regular expressions", () => {
+ assert.equal(strReplaceAll("abc123def456", /\d+/g, "#"), "abc#def#");
+ assert.equal(polyStrReplaceAll("abc123def456", /\d+/g, "#"), "abc#def#");
+ });
+
+ it("delegates to @@replace for non-RegExp search objects", () => {
+ const replaceSym = getKnownSymbol(WellKnownSymbols.replace);
+ const searchValue = {
+ [replaceSym]: (value: string, replaceValue: string | ((substring: string, ...args: any[]) => string)) => {
+ return "custom:" + value + ":" + dumpObj(replaceValue);
+ }
+ };
+
+ const expected = "custom:abcabc:" + dumpObj("X");
+ assert.equal(polyStrReplaceAll("abcabc", searchValue as any, "X"), expected);
+ assert.equal(strReplaceAll("abcabc", searchValue as any, "X"), expected);
+ });
+
+ it("treats RegExp with @@match false as non-RegExp for global check", () => {
+ const matchSym = getKnownSymbol(WellKnownSymbols.match);
+ const searchValue = /a/;
+ (searchValue as any)[matchSym] = false;
+
+ assert.equal(strReplaceAll("a_a", searchValue as any, "x"), "x_a");
+ assert.equal(polyStrReplaceAll("a_a", searchValue as any, "x"), "x_a");
+ });
+
+ if (isNode()) {
+ // Browser environment is not "letting" us override built-in prototypes to test delegation for falsey values,
+ // but currently we can test in Node, this may fail if Node adds a native implementation, but we can address at that time if needed.
+ it("delegates to @@replace for falsey non-null search values", () => {
+ const replaceSym = getKnownSymbol(WellKnownSymbols.replace);
+ const numberReplace = (Number.prototype as any)[replaceSym];
+ const booleanReplace = (Boolean.prototype as any)[replaceSym];
+ const stringReplace = (String.prototype as any)[replaceSym];
+
+ (Number.prototype as any)[replaceSym] = function(value: string, replaceValue: any) {
+ return "number:" + value + ":" + dumpObj(replaceValue);
+ };
+ (Boolean.prototype as any)[replaceSym] = function(value: string, replaceValue: any) {
+ return "boolean:" + value + ":" + dumpObj(replaceValue);
+ };
+ (String.prototype as any)[replaceSym] = function(value: string, replaceValue: any) {
+ return "string:" + value + ":" + dumpObj(replaceValue);
+ };
+
+ try {
+ const expectedReplace = dumpObj("X");
+
+ assert.equal(strReplaceAll("abc", 0 as any, "X"), "number:abc:" + expectedReplace);
+ assert.equal(polyStrReplaceAll("abc", 0 as any, "X"), "number:abc:" + expectedReplace);
+
+ assert.equal(strReplaceAll("abc", false as any, "X"), "boolean:abc:" + expectedReplace);
+ assert.equal(polyStrReplaceAll("abc", false as any, "X"), "boolean:abc:" + expectedReplace);
+
+ assert.equal(strReplaceAll("abc", "" as any, "X"), "string:abc:" + expectedReplace);
+ assert.equal(polyStrReplaceAll("abc", "" as any, "X"), "string:abc:" + expectedReplace);
+ } finally {
+ (Number.prototype as any)[replaceSym] = numberReplace;
+ (Boolean.prototype as any)[replaceSym] = booleanReplace;
+ (String.prototype as any)[replaceSym] = stringReplace;
+ }
+ });
+ }
+
+ it("throws for non-global regular expressions", () => {
+ _expectThrow(() => {
+ strReplaceAll("abc123", /\d+/, "#");
+ });
+
+ _expectThrow(() => {
+ polyStrReplaceAll("abc123", /\d+/, "#");
+ });
+ });
+
+ it("throws for null and undefined values", () => {
+ _expectThrow(() => {
+ strReplaceAll(null as any, "a", "b");
+ });
+
+ _expectThrow(() => {
+ polyStrReplaceAll(undefined as any, "a", "b");
+ });
+ });
+
+ it("matches native replaceAll semantics", () => {
+ let replacer = (match: string, index: number) => {
+ return "(" + match + ":" + index + ")";
+ };
+
+ let testCases: { value: any; searchValue: any; replaceValue: any }[] = [
+ { value: "banana", searchValue: "a", replaceValue: "o" },
+ { value: "banana", searchValue: "", replaceValue: "-" },
+ { value: "abc123abc", searchValue: "abc", replaceValue: "X" },
+ { value: "abc123abc", searchValue: /abc/g, replaceValue: "X" },
+ { value: "abc123abc", searchValue: /\d+/g, replaceValue: replacer },
+ { value: "a*b*a", searchValue: "*", replaceValue: "-" },
+ { value: "a[b]c[d]", searchValue: "[", replaceValue: "(" },
+ { value: "a.b.a.b", searchValue: ".", replaceValue: "!" },
+ { value: "null value", searchValue: null, replaceValue: "nil" },
+ { value: "undefined value", searchValue: undefined, replaceValue: "undef" },
+ { value: 12341234, searchValue: "34", replaceValue: "x" },
+ { value: "aaa", searchValue: "a", replaceValue: "$$" }
+ ];
+
+ for (let lp = 0; lp < testCases.length; lp++) {
+ let testCase = testCases[lp];
+ _checkNativeParity("strReplaceAll", (value, searchValue, replaceValue) => {
+ return strReplaceAll(value, searchValue, replaceValue);
+ }, testCase.value, testCase.searchValue, testCase.replaceValue);
+
+ _checkNativeParity("polyStrReplaceAll", (value, searchValue, replaceValue) => {
+ return polyStrReplaceAll(value, searchValue, replaceValue);
+ }, testCase.value, testCase.searchValue, testCase.replaceValue);
+ }
+ });
+
+ function _expectThrow(cb: () => void): Error {
+ try {
+ cb();
+ } catch (e) {
+ assert.ok(true, "Expected an exception to be thrown");
+ return e as Error;
+ }
+
+ assert.ok(false, "Expected an exception to be thrown");
+ return null as any;
+ }
+
+ function _checkNativeParity(testName: string, testFn: (value: any, searchValue: any, replaceValue: any) => string, value: any, searchValue: any, replaceValue: any) {
+ let testResult: any;
+ let nativeResult: any;
+ let testThrew: any;
+ let nativeThrew: any;
+
+ try {
+ testResult = testFn(value, searchValue, replaceValue);
+ } catch (e) {
+ testThrew = e;
+ }
+
+ try {
+ nativeResult = (String.prototype as any).replaceAll.call(value, searchValue, replaceValue);
+ } catch (e) {
+ nativeThrew = e;
+ }
+
+ if (testThrew) {
+ assert.equal(true, !!nativeThrew,
+ "Checking whether Native and " + testName + " both threw [" + dumpObj(testThrew) + "] - [" + dumpObj(nativeThrew || nativeResult) + "] for [" + dumpObj(value) + "] [" + dumpObj(searchValue) + "]");
+ } else if (nativeThrew) {
+ assert.ok(false,
+ "Native threw but " + testName + " did not [" + dumpObj(testResult) + "] - [" + dumpObj(nativeThrew) + "] for [" + dumpObj(value) + "] [" + dumpObj(searchValue) + "]");
+ } else {
+ assert.equal(testResult, nativeResult,
+ "Checking whether Native and " + testName + " returned the same [" + dumpObj(testResult) + "] - [" + dumpObj(nativeResult) + "] for [" + dumpObj(value) + "] [" + dumpObj(searchValue) + "]");
+ }
+ }
+});
diff --git a/lib/test/src/common/string/trim.test.ts b/lib/test/src/common/string/trim.test.ts
index fae2ac55..4fb4b1b2 100644
--- a/lib/test/src/common/string/trim.test.ts
+++ b/lib/test/src/common/string/trim.test.ts
@@ -43,6 +43,36 @@ describe("string helpers", () => {
_checkPolyTrimEnd(undefined);
});
+ describe("trim with non-nullish falsy values", () => {
+ _checkTrim(0);
+ _checkTrim(false);
+ });
+
+ describe("polyTrim with non-nullish falsy values", () => {
+ _checkPolyTrim(0);
+ _checkPolyTrim(false);
+ });
+
+ describe("trimStart with non-nullish falsy values", () => {
+ _checkTrimStart(0);
+ _checkTrimStart(false);
+ });
+
+ describe("polyTrimStart with non-nullish falsy values", () => {
+ _checkPolyTrimStart(0);
+ _checkPolyTrimStart(false);
+ });
+
+ describe("trimEnd with non-nullish falsy values", () => {
+ _checkTrimEnd(0);
+ _checkTrimEnd(false);
+ });
+
+ describe("polyTrimEnd with non-nullish falsy values", () => {
+ _checkPolyTrimEnd(0);
+ _checkPolyTrimEnd(false);
+ });
+
describe("trim With values", () => {
_checkTrim("null");
_checkTrim("undefined");
@@ -160,7 +190,7 @@ describe("string helpers", () => {
_checkTrimEnd(" abba ");
_checkTrimEnd("zyxyvutsrqponmlkjihgfedcba");
_checkTrimEnd(" zyxyvutsrqponmlkjihgfedcba");
- _checkTrimStart(" abba ");
+ _checkTrimEnd(" abba ");
});
describe("polyTrimEnd With values", () => {