@spacingbat3/lss
Version:
LSS: Literal String Sanitizer – sanitizes string based on specific inputs and tries to guess the accurate type (in TypeScript/Typed JS).
142 lines (141 loc) • 8.86 kB
TypeScript
/**
* Sets of chars that are correctly understood by current engine and can be
* transformed to valid {@linkcode charset}.
*
* @since v1.0.0
*/
export declare const parseableRange: readonly ["a-z", "A-Z", "0-9", "---"];
/**
* Like `unknown`, except it includes some primitive type just so generics can
* pick any commonly-used primitive type.
*/
export type unknownLiteral = null | undefined | object | string | number | bigint;
/**
* A valid mode for trimming the string. `null` skips trimming entirely.
*
* @since v1.2.0
*/
export type trimMode = null | "left" | "right" | "both";
type stringify<T> = T extends number ? `${T}` : T extends string ? T : string;
/**
* Splits `T` to the union of chars. If `T` is an empty string, it resolves to
* `never`.
*/
export type charset<T extends string> = (T extends `${infer C extends string}${infer R extends string}` ? (C | charset<R>) : T extends "" ? never : T);
/**
* A union of letters from English alphabet (lowercase).
*/
type char = charset<"abcdefghijklmnopqrstuvwxyz">;
/**
* A union of arabic numbers (i.e. decimal number system).
*/
type digit = charset<"0123456789">;
type parseableRange = (typeof parseableRange)[number];
/**
* Maps given {@link parseableRange} to {@link charset}.
*/
type extendRange<T extends parseableRange> = (T extends "a-z" ? char : T extends "A-Z" ? Uppercase<char> : (T extends "0-9" ? digit : T extends "---" ? "-" : never));
type range2charset<T extends string> = T extends parseableRange ? extendRange<T> : (T extends `${infer F extends "a-z"}${string}` ? (T extends `${F}${infer R extends string}` ? extendRange<F> | range2charset<R> : never) : T extends `${infer F extends "A-Z"}${string}` ? (T extends `${F}${infer R extends string}` ? extendRange<F> | range2charset<R> : never) : T extends `${infer F extends "0-9"}${string}` ? (T extends `${F}${infer R extends string}` ? extendRange<F> | range2charset<R> : never) : T extends `${infer F extends string}${infer R extends string}` ? (charset<F> | range2charset<R>) : never);
type _case<T extends string, C extends string> = (C extends `${string}${char}${string}` ? (C extends `${string}${Uppercase<char>}${string}` ? T : Lowercase<T>) : Uppercase<T>);
/**
* A generic type which modifies literal string to replace `T` string with `R`
* replacement character based on `C` charset. It should work exactly the same
* as runtime function does when combined with {@link _trimMode}.
*/
type _replace<T extends string, C extends string, R extends string> = (T extends `${infer F extends string}${infer S extends string}` ? (F extends range2charset<C> ? (`${F}${_replace<S, C, R>}`) : `${R}${_replace<S, C, R>}`) : T);
/**
* A helper generic type to trim string to remove leading chars outside of
* range.
*/
type _trimLeft<T extends string, C extends string> = (T extends `${infer F extends string}${infer R extends string}` ? (F extends range2charset<C> ? T : _trimLeft<R, C>) : T);
/**
* A helper generic type to trim string to remove trailing chars outside of
* range. Does not respect the left side of the string and assumes the first
* char found would be part of invalid chars on right side (which leaves empty
* string in such scenarios).
*/
type _trimRightPhase2<T extends string, C extends string> = (T extends `${infer F extends string}${infer R extends string}` ? (F extends range2charset<C> ? `${F}${_trimRightPhase2<R, C>}` : "") : T);
/**
* A helper generic type to trim string to remove trailing chars outside of
* range. Unlike to {@link _trimRightPhase2} it leaves leading chars untouched.
*/
type _trimRight<T extends string, C extends string> = (T extends `${infer F extends string}${infer R extends string}` ? (F extends range2charset<C> ? `${F}${_trimRightPhase2<R, C>}` : `${F}${_trimRight<R, C>}`) : T);
/**
* Trims a string based on {@link trimMode} (`M`) parameter.
* It should provide the same logic as in runtime function.
*
* @template T - A value to be trimmed.
* @template C - A charset used during sanitization.
* @template M - A way of trimming the string.
*/
type _trimMode<T extends string, C extends string, M extends trimMode> = (M extends "right" ? _trimRight<T, C> : M extends "both" ? (_trimRightPhase2<_trimLeft<T, C>, C>) : M extends "left" ? _trimLeft<T, C> : M extends null ? T : never);
/**
* Ensures given string is a *char* (i.e. has `length === 1`). It will resolve
* to `never` both for strings with multiple characters and empty strings.
*
* @template T - any `string`.
*/
type ensureChar<T extends string> = (T extends `${infer P extends string}${string}` ? (T extends P ? T : never) : never);
/** Ensures given string is non-empty. Resolves to `never` otherwise. */
type ensureNonEmpty<T extends string> = T extends "" ? never : T;
/** Resolves to last character from the given string. */
type lastChar<T extends string> = (T extends `${string}${infer R extends string}` ? (R extends "" ? T : lastChar<R>) : T);
/** Infers a set of chars from given string as a union type. */
type charGroups<T extends string> = (T extends `${infer S extends string}-${infer E extends string}${infer R extends string}` ? (S extends "" ? charGroups<R> : `${lastChar<S>}-${E}` | charGroups<R>) : never);
/**
* Transforms {@link sanitizeLiteral} function parameters to provide an expected
* result type for a given set of literals.
*
* @template V - Value to be sanitized.
* @template C - A charset to be used for sanitization.
* @template R - A replacement character to be used for sanitization.
* @template M - A range of value trimming.
*/
export type sanitizeResult<V, C extends string, R extends string, M extends trimMode> = (V extends null | undefined ? V : R extends ensureChar<R> ? (charGroups<C> extends parseableRange ? V extends string | number ? (ensureNonEmpty<_replace<_trimMode<_case<stringify<V>, C>, C, M>, C, R>>) : string : never) : never);
/**
* 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
*/
export declare function sanitizeLiteral<V extends unknownLiteral, C extends string = "a-z0-9", R extends string = "-", M extends trimMode = "left">(value: V, charset?: C, replacement?: R, trimMode?: M): sanitizeResult<V, C, R, M>;
export default sanitizeLiteral;