From 2cfa5fce28a85f155cfef101968816b8e2aedd68 Mon Sep 17 00:00:00 2001 From: rawpixel-vincent Date: Tue, 5 Mar 2024 10:22:12 +0700 Subject: [PATCH] add withTrim, withReplace and update the types of enum / value() for convenience --- README.md | 10 ++++++++++ StringLiteralList.d.ts | 13 +++++++++++-- StringLiteralList.js | 44 ++++++++++++++++++++++++++++++++++++------ stringList.test.js | 34 ++++++++++++++++++++++++++++++++ types.d.ts | 13 +++++++++++++ 5 files changed, 106 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 56055d9..437c0fa 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,10 @@ The StringList class extends the Array interface types to work with string liter - `withSuffix($)`: add suffix to all the words. - `withDerivatedPrefix($)` and `withDerivatedSuffix($)`: Generate words variants with or without the given suffix/prefix depending on their presence. - `value($)`: similar to enum but throws an error if the value doesn't exists. + - `enum` Object is exposed as readonly. + - `withTrim()`: trim all the words. + - `withReplace(search, replacement)`: call the String.prototype.replace on all the words. + - `withReplaceAll(search, replacement)`: call the String.prototype.replaceAll on all the words. ## Installation @@ -68,6 +72,10 @@ v.value('not') => throws; v.withDerivatedSuffix('s') => SL<"foo" | "foos" | "bars" "bar">; v.withDerivatedPrefix('#') => SL<"foo" | "#foo" | "bar" | "#bar">; + +v.withTrim() => SL<"foo" | "bar">; +v.withReplace('a', 'e') => SL<"foo" | "ber">; +v.withReplaceAll('o', 'e') => SL<"fee" | "bar">; ``` ```js @@ -113,6 +121,8 @@ foods.withDerivatedSuffix('s'); => SL<"food" | "bars" | "pasta" | "meatballs" | const tags = stringList('spring', '#boot', '#typescript', 'fundamentals'); tags.withDerivatedPrefix('#'); => SL<"#spring" | "#boot" | "#typescript" | "#fundamentals" | "spring" | "boot" | "typescript" | "fundamentals"> + +const scored = stringList('if has ', 'spaces', ' between ', ' o r', 'into the words').withTrim().withReplaceAll(' ', '_') => SL<"if_has" | "spaces" | "between" | "o_r" |"into_the_words"> ``` #### list.concat(...(string|StringList)[]) diff --git a/StringLiteralList.d.ts b/StringLiteralList.d.ts index dd720ea..20ba884 100644 --- a/StringLiteralList.d.ts +++ b/StringLiteralList.d.ts @@ -22,7 +22,16 @@ export interface IStringList withDerivatedPrefix( chars: S ): IStringList, T>, S>, sl.utils.StringConcat>>; - value(val: V): V extends T ? V : never; + withReplace< + S extends string | RegExp, + D extends string + >(searchValue: S, replaceValue: D): IStringList>; + withReplaceAll< + S extends string | RegExp, + D extends string + >(searchValue: S, replaceValue: D): IStringList>; + withTrim(): IStringList>; + value(val): T; mutable(): T & string[]; sort(compareFn?: (a: P1, b: P2) => number): this; reverse(): this; @@ -36,7 +45,7 @@ export interface IStringList // Readonly overrides readonly length: number; readonly [n: number]: T | undefined; - readonly enum: { [P in T & string]: P }; + readonly enum: { [P in T & string]: P } & Omit<{ [P in number | string | symbol]: P extends number | symbol ? never : T | undefined | null }, T>; // Supported Methods at(n: number): T | undefined; diff --git a/StringLiteralList.js b/StringLiteralList.js index a3320ab..4446c8b 100644 --- a/StringLiteralList.js +++ b/StringLiteralList.js @@ -6,9 +6,22 @@ import 'core-js/actual/array/with.js'; export class SL extends Array { literal = undefined; enum; + hasEmpty = false; constructor(...args) { super(...args); - this.enum = Object.freeze(Object.fromEntries(this.map((v) => [v, v]))); + this.enum = Object.fromEntries( + this.map((v) => { + if (v === '') { + this.hasEmpty = true; + } + return [v, v]; + }), + ); + + if (this.hasEmpty) { + this.enum[''] = ''; + } + Object.freeze(this.enum); } concat(...args) { @@ -27,15 +40,19 @@ export class SL extends Array { return Object.freeze(new SL(...super.slice.apply(this, arguments))); } - withPrefix(prefix) { + withTrim() { + return Object.freeze(new SL(...super.map((e) => e.trim()))); + } + + withPrefix(prefix = '') { return Object.freeze(new SL(...super.map((e) => `${prefix}${e}`))); } - withSuffix(suffix) { + withSuffix(suffix = '') { return Object.freeze(new SL(...super.map((e) => `${e}${suffix}`))); } - withDerivatedSuffix(chars) { + withDerivatedSuffix(chars = '') { return Object.freeze( new SL( ...super.flatMap((t) => [ @@ -48,7 +65,7 @@ export class SL extends Array { ); } - withDerivatedPrefix(chars) { + withDerivatedPrefix(chars = '') { return Object.freeze( new SL( ...super.flatMap((t) => [ @@ -61,8 +78,23 @@ export class SL extends Array { ); } + withReplace(string, replacement = '') { + return Object.freeze( + new SL(...super.map((e) => e.replace(string, replacement))), + ); + } + + withReplaceAll(string, replacement = '') { + return Object.freeze( + new SL(...super.map((e) => e.replaceAll(string, replacement))), + ); + } + value(value) { - if (this.enum[value]) { + if ( + typeof value === 'string' && + (this.enum[value] || (this.hasEmpty && value === '')) + ) { return value; } throw new Error(`Invalid value ${value}`); diff --git a/stringList.test.js b/stringList.test.js index 9fe4211..9369cfc 100644 --- a/stringList.test.js +++ b/stringList.test.js @@ -246,6 +246,40 @@ t.test("withSuffix('.suffix')", (t) => { t.end(); }); +t.test('withReplace("1")', (t) => { + const list = stringList('f1oo', 'b1ar').withReplace('1', ''); + testExpectedArrayValues(t, list, 'foo', 'bar'); + testEscapingFromStringList(t, list, 'foo', 'bar'); + t.end(); +}); + +t.test('withReplaceAll("z")', (t) => { + const list = stringList('foo', 'azzztiv', 'zzz', 'z1').withReplaceAll( + 'z', + '', + ); + testExpectedArrayValues(t, list, 'foo', 'ativ', '', '1'); + testEscapingFromStringList(t, list, 'foo', 'ativ', '', '1'); + t.end(); +}); + +t.test('withTrim()', (t) => { + const list = stringList(' foo ', ' bar ').withTrim(); + testExpectedArrayValues(t, list, 'foo', 'bar'); + testEscapingFromStringList(t, list, 'foo', 'bar'); + t.end(); +}); + +t.test('withTrim().withReplaceAll("_")', (t) => { + const list = stringList('has spaces ', ' has more_spaces') + .withTrim() + .withReplaceAll(' ', '_'); + testExpectedArrayValues(t, list, 'has_spaces', 'has_more_spaces'); + testEscapingFromStringList(t, list, 'has_spaces', 'has_more_spaces'); + + t.end(); +}); + t.test("concat('zing', 'boom')", (t) => { const list = stringList('foo', 'bar').concat('zing', 'boom'); testExpectedArrayValues(t, list, 'foo', 'bar', 'zing', 'boom'); diff --git a/types.d.ts b/types.d.ts index 8c4e23e..d6424bd 100644 --- a/types.d.ts +++ b/types.d.ts @@ -2,6 +2,8 @@ import { ArrayInPlaceMutation } from './StringLiteralList.js'; export namespace sl { export namespace utils { + type IsStringLiteral = [T] extends [string] ? [string] extends [T] ? false : Uppercase extends Uppercase> ? Lowercase extends Lowercase> ? true : false : false : false; + export type StringConcat< T1 extends string | number | bigint | boolean, T2 extends string | number | bigint | boolean, @@ -24,6 +26,17 @@ export namespace sl { : sentence extends `${prefix}${infer rest}` ? rest : sentence; + + + type TrimStart = IsStringLiteral extends true ? T extends ` ${infer rest}` ? TrimStart : T : string; + + type TrimEnd = IsStringLiteral extends true ? T extends `${infer rest} ` ? TrimEnd : T : string; + + type Trim = TrimEnd>; + + type Replace = lookup extends string ? IsStringLiteral extends true ? sentence extends `${infer rest}${lookup}${infer rest2}` ? `${rest}${replacement}${rest2}` : sentence : string : string; + + type ReplaceAll = lookup extends string ? IsStringLiteral extends true ? sentence extends `${infer rest}${lookup}${infer rest2}` ? `${rest}${replacement}${ReplaceAll}` : sentence : string : string; } export namespace specs {