UNPKG

@spacingbat3/lss

Version:

LSS: Literal String Sanitizer – sanitizes string based on specific inputs and tries to guess the accurate type (in TypeScript/Typed JS).

108 lines 5.61 kB
"use strict"; /*------------------------------------------------------* * Copyright (c) 2023 Dawid Papiewski "SpacingBat3". * * * * All rights reserved. Licensed under the ISC license. * *------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.sanitizeLiteral = exports.parseableRange = void 0; /** * Sets of chars that are correctly understood by current engine and can be * transformed to valid {@linkcode charset}. * * @since v1.0.0 */ exports.parseableRange = Object.freeze(["a-z", "A-Z", "0-9", "---"]); /** * A type-safe string sanitizer supporting any set of chars while being capable * of calculating the expected result as a static type if literal is provided as * a name. Predicts the accurate output based on input types or errors (`never`) * if function is guaranteed to fail in the runtime. * * @remarks * * It is designed to be rather performant at runtime (it uses [`RegExp`] * under-the-hood), however be aware of long compilation times as TypeScript * will have to do heavy calculations for function result in case of complex * strings and character sets (a lot of operations are done in char-by-char * manner, using `infer` and type recursion — there's a real chance that for * very long literal strings TypeScript will just give up at calculations and * end compilation with an error!). * * [`RegExp`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp "RegExp – JavaScript | MDN" * * @privateRemarks * * This function began, for me and anyone digging into the source code, as a * resource for learning advanced TypeScript manipulations on string literals. * I will also use it for my own personal projects. * * @param value - Value to sanitize. Should be a *non-nullish* `string`. * @param charset - A string that represents a set of characters. For ranges, only values from {@linkcode parseableRange} are valid. * @param replacement - A `char` (i.e. `string` with `length === 0`) which should replace invalid characters inside the string. * @param trimMode – Definies how string should be trimmed. Defaults to `left` (compatibility reasons). * * @returns - Original {@link value} for nullish values, sanitized string for anything else. * @throws - [`TypeError`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypeError "TypeError – JavaScript | MDN") for unresolveable {@link charset} or invalid {@link trimMode}, [`RangeError`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RangeError "RangeError – JavaScript | MDN") for non-char values in {@link replacement} and [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error "Error – JavaScript | MDN") for {@link value} which cannot be sanitized to the expected {@link charset}. * * @example * * // (const) regular: "fooBar3" * const regular = "fooBar3" as const; * // (const) mod1: "FOOBAR3" * const mod1 = sanitizeLiteral(regular,"A-Z0-9"); * // (const) mod2: "foobarz" * const mod2 = sanitizeLiteral(regular,"a-z","z"); * // (const) mod3: "oo_ar3" * const mod3 = sanitizeLiteral(regular,"acdeghijklmnopqrstuvwxyz0-9","_"); * * @since v1.0.0 */ function sanitizeLiteral(value, charset = "a-z0-9", replacement = "-", trimMode = "left") { if (value === null || value === undefined) return value; if ((charset.match(/([^])-([^])/gm) ?? []).find(element => !["a-z", "A-Z", "0-9", "---"].includes(element)) !== undefined) throw new TypeError(`Unrecognized charset: "${charset}"!`); if (replacement.length !== 1) throw new RangeError("Parameter 'replacement' should be a valid character"); charset = charset.replaceAll(/([\]^\\])/g, "\\$1"); let valueString; const regexp = { valid: new RegExp(`[${charset}]`), invalid: new RegExp(`[^${charset}${replacement.replaceAll("]", "\\]")}]`, "g") }; if (typeof value !== "string") valueString = String(value); else valueString = value; if (regexp.invalid.test(valueString) || valueString.startsWith(replacement)) { // Try to convert string to uppercase or lowercase based on charset. valueString = !/[A-Z]/.test(charset) ? valueString.toLowerCase() : !/[a-z]/.test(charset) ? valueString.toUpperCase() : valueString; // Trim string based on the trimMode switch (trimMode) { case "both": //@ts-expect-error – fallthrough intended case "left": valueString = valueString.slice(valueString.search(regexp.valid)); if (trimMode === "left") break; //@ts-expect-error – fallthrough intended case "right": valueString = valueString.slice(0, valueString.length - Array.from(valueString).reverse().findIndex(c => regexp.valid.test(c))); case null: break; default: throw new TypeError(`Invalid trim mode: "${trimMode}"`); } // Replace the rest with the replacement character. valueString = valueString.replaceAll(regexp.invalid, replacement); } // Do not accept the empty strings if (valueString.length === 0) throw new Error("Parameter 'name' is not sanitizable!"); return valueString; } exports.sanitizeLiteral = sanitizeLiteral; exports.default = sanitizeLiteral; //# sourceMappingURL=lib.js.map