Browse Source

feat: utility types NonEmptyString and LooseUnion

Sv443 1 year ago
parent
commit
49bc85e3c0
5 changed files with 122 additions and 25 deletions
  1. 5 0
      .changeset/funny-fans-develop.md
  2. 24 16
      .eslintrc.cjs
  3. 61 1
      README.md
  4. 8 8
      lib/array.ts
  5. 24 0
      lib/misc.ts

+ 5 - 0
.changeset/funny-fans-develop.md

@@ -0,0 +1,5 @@
+---
+"@sv443-network/userutils": minor
+---
+
+Added utility types `NonEmptyString` and `LooseUnion`

+ 24 - 16
.eslintrc.cjs

@@ -4,14 +4,8 @@ module.exports = {
     browser: true,
     browser: true,
     es6: true,
     es6: true,
   },
   },
-  ignorePatterns: [
-    "*.map",
-    "dist/**",
-  ],
-  extends: [
-    "eslint:recommended",
-    "plugin:@typescript-eslint/recommended",
-  ],
+  ignorePatterns: ["*.map", "dist/**"],
+  extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
   globals: {
   globals: {
     Atomics: "readonly",
     Atomics: "readonly",
     SharedArrayBuffer: "readonly",
     SharedArrayBuffer: "readonly",
@@ -22,19 +16,33 @@ module.exports = {
   parserOptions: {
   parserOptions: {
     ecmaVersion: "latest",
     ecmaVersion: "latest",
   },
   },
-  plugins: [
-    "@typescript-eslint",
-  ],
+  plugins: ["@typescript-eslint"],
   rules: {
   rules: {
     "no-unreachable": "off",
     "no-unreachable": "off",
-    "quotes": [ "error", "double" ],
-    "semi": [ "error", "always" ],
-    "eol-last": [ "error", "always" ],
+    quotes: ["error", "double"],
+    semi: ["error", "always"],
+    "eol-last": ["error", "always"],
     "no-async-promise-executor": "off",
     "no-async-promise-executor": "off",
-    "indent": ["error", 2, { "ignoredNodes": ["VariableDeclaration[declarations.length=0]"] }],
+    indent: [
+      "error",
+      2,
+      { ignoredNodes: ["VariableDeclaration[declarations.length=0]"] },
+    ],
     "@typescript-eslint/no-non-null-assertion": "off",
     "@typescript-eslint/no-non-null-assertion": "off",
-    "@typescript-eslint/no-unused-vars": ["warn", { "ignoreRestSiblings": true, "argsIgnorePattern": "^_" }],
+    "@typescript-eslint/no-unused-vars": [
+      "warn",
+      { ignoreRestSiblings: true, argsIgnorePattern: "^_" },
+    ],
     "@typescript-eslint/ban-ts-comment": "off",
     "@typescript-eslint/ban-ts-comment": "off",
+    "@typescript-eslint/ban-types": [
+      "error",
+      {
+        types: {
+          "{}": false,
+        },
+        extendDefaults: true,
+      },
+    ],
     "comma-dangle": ["error", "only-multiline"],
     "comma-dangle": ["error", "only-multiline"],
     "no-misleading-character-class": "off",
     "no-misleading-character-class": "off",
   },
   },

+ 61 - 1
README.md

@@ -56,7 +56,9 @@ View the documentation of previous major releases: [2.0.1](https://github.com/Sv
     - [tr.getLanguage()](#trgetlanguage) - returns the currently active language
     - [tr.getLanguage()](#trgetlanguage) - returns the currently active language
   - [**Utility types for TypeScript:**](#utility-types)
   - [**Utility types for TypeScript:**](#utility-types)
     - [Stringifiable](#stringifiable) - any value that is a string or can be converted to one (implicitly or explicitly)
     - [Stringifiable](#stringifiable) - any value that is a string or can be converted to one (implicitly or explicitly)
-    - [NonEmptyArray](https://github.com/Sv443-Network/UserUtils#nonemptyarray) - any array that should have at least one item
+    - [NonEmptyArray](#nonemptyarray) - any array that should have at least one item
+    - [NonEmptyString](#nonemptystring) - any string that should have at least one character
+    - [LooseUnion](#looseunion) - a union that gives autocomplete in the IDE but also allows any other value of the same type
 
 
 <br><br>
 <br><br>
 
 
@@ -1442,6 +1444,11 @@ logSomething(barObject); // Type Error
 <br>
 <br>
 
 
 ## NonEmptyArray
 ## NonEmptyArray
+Usage:
+```ts
+NonEmptyArray<TItem = unknown>
+```
+  
 This type describes an array that has at least one item.  
 This type describes an array that has at least one item.  
 Use the generic parameter to specify the type of the items in the array.  
 Use the generic parameter to specify the type of the items in the array.  
   
   
@@ -1465,6 +1472,59 @@ logFirstItem(["04abc", "69"]); // 4
 
 
 </details>
 </details>
 
 
+<br>
+
+## NonEmptyString
+Usage:
+```ts
+NonEmptyString<TString extends string>
+```
+  
+This type describes a string that has at least one character.  
+  
+<details><summary><b>Example - click to view</b></summary>
+
+```ts
+import type { NonEmptyString } from "@sv443-network/userutils";
+
+function convertToNumber<T extends string>(str: NonEmptyString<T>) {
+  console.log(parseInt(str));
+}
+
+convertToNumber("04abc"); // "4"
+convertToNumber("");      // type error: Argument of type 'string' is not assignable to parameter of type 'never'
+```
+
+</details>
+
+<br>
+
+## LooseUnion
+Usage:
+```ts
+LooseUnion<TUnion extends string | number | object>
+```
+  
+A type that offers autocomplete in the IDE for the passed union but also allows any value of the same type to be passed.  
+Supports unions of strings, numbers and objects.  
+  
+<details><summary><b>Example - click to view</b></summary>
+
+```ts
+function foo(bar: LooseUnion<"a" | "b" | "c">) {
+  console.log(bar);
+}
+
+// when typing the following, autocomplete suggests "a", "b" and "c"
+// foo("
+
+foo("a"); // included in autocomplete, no type error
+foo("");  // *not* included in autocomplete, still no type error
+foo(1);   // type error: Argument of type '1' is not assignable to parameter of type 'LooseUnion<"a" | "b" | "c">'
+```
+
+</details>
+
 <br><br><br><br>
 <br><br><br><br>
 
 
 <!-- #MARKER Footer -->
 <!-- #MARKER Footer -->

+ 8 - 8
lib/array.ts

@@ -1,18 +1,18 @@
 import { randRange } from "./math";
 import { randRange } from "./math";
 
 
 /** Describes an array with at least one item */
 /** Describes an array with at least one item */
-export type NonEmptyArray<T = unknown> = [T, ...T[]];
+export type NonEmptyArray<TArray = unknown> = [TArray, ...TArray[]];
 
 
 /** Returns a random item from the passed array */
 /** Returns a random item from the passed array */
-export function randomItem<T = unknown>(array: T[]) {
-  return randomItemIndex<T>(array)[0];
+export function randomItem<TItem = unknown>(array: TItem[]) {
+  return randomItemIndex<TItem>(array)[0];
 }
 }
 
 
 /**
 /**
  * Returns a tuple of a random item and its index from the passed array  
  * Returns a tuple of a random item and its index from the passed array  
  * Returns `[undefined, undefined]` if the passed array is empty
  * Returns `[undefined, undefined]` if the passed array is empty
  */
  */
-export function randomItemIndex<T = unknown>(array: T[]): [item?: T, index?: number] {
+export function randomItemIndex<TItem = unknown>(array: TItem[]): [item?: TItem, index?: number] {
   if(array.length === 0)
   if(array.length === 0)
     return [undefined, undefined];
     return [undefined, undefined];
 
 
@@ -22,18 +22,18 @@ export function randomItemIndex<T = unknown>(array: T[]): [item?: T, index?: num
 }
 }
 
 
 /** Returns a random item from the passed array and mutates the array to remove the item */
 /** Returns a random item from the passed array and mutates the array to remove the item */
-export function takeRandomItem<T = unknown>(arr: T[]) {
-  const [itm, idx] = randomItemIndex<T>(arr);
+export function takeRandomItem<TItem = unknown>(arr: TItem[]) {
+  const [itm, idx] = randomItemIndex<TItem>(arr);
 
 
   if(idx === undefined)
   if(idx === undefined)
     return undefined;
     return undefined;
 
 
   arr.splice(idx, 1);
   arr.splice(idx, 1);
-  return itm as T;
+  return itm as TItem;
 }
 }
 
 
 /** Returns a copy of the array with its items in a random order */
 /** Returns a copy of the array with its items in a random order */
-export function randomizeArray<T = unknown>(array: T[]) {
+export function randomizeArray<TItem = unknown>(array: TItem[]) {
   const retArray = [...array]; // so array and retArray don't point to the same memory address
   const retArray = [...array]; // so array and retArray don't point to the same memory address
 
 
   if(array.length === 0)
   if(array.length === 0)

+ 24 - 0
lib/misc.ts

@@ -1,6 +1,30 @@
 /** Represents any value that is either a string itself or can be converted to one (implicitly or explicitly) because it has a toString() method */
 /** Represents any value that is either a string itself or can be converted to one (implicitly or explicitly) because it has a toString() method */
 export type Stringifiable = string | { toString(): string };
 export type Stringifiable = string | { toString(): string };
 
 
+/**
+ * A type that offers autocomplete for the passed union but also allows any arbitrary value of the same type to be passed.  
+ * Supports unions of strings, numbers and objects.
+ */
+export type LooseUnion<TUnion extends string | number | object> =
+  (TUnion) | (
+    TUnion extends string
+      ? (string & {})
+      : (
+        TUnion extends object
+          ? (object & {})
+          : (number & {})
+      )
+  );
+
+/**
+ * A type that allows all strings except for empty ones
+ * @example
+ * function foo<T extends string>(bar: NonEmptyString<T>) {
+ *   console.log(bar);
+ * }
+ */
+export type NonEmptyString<TString extends string> = TString extends "" ? never : TString;
+
 /**
 /**
  * Automatically appends an `s` to the passed {@linkcode word}, if {@linkcode num} is not equal to 1
  * Automatically appends an `s` to the passed {@linkcode word}, if {@linkcode num} is not equal to 1
  * @param word A word in singular form, to auto-convert to plural
  * @param word A word in singular form, to auto-convert to plural