Explorar o código

feat: utility types NonEmptyString and LooseUnion

Sv443 hai 1 ano
pai
achega
49bc85e3c0
Modificáronse 5 ficheiros con 122 adicións e 25 borrados
  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,
     es6: true,
   },
-  ignorePatterns: [
-    "*.map",
-    "dist/**",
-  ],
-  extends: [
-    "eslint:recommended",
-    "plugin:@typescript-eslint/recommended",
-  ],
+  ignorePatterns: ["*.map", "dist/**"],
+  extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
   globals: {
     Atomics: "readonly",
     SharedArrayBuffer: "readonly",
@@ -22,19 +16,33 @@ module.exports = {
   parserOptions: {
     ecmaVersion: "latest",
   },
-  plugins: [
-    "@typescript-eslint",
-  ],
+  plugins: ["@typescript-eslint"],
   rules: {
     "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",
-    "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-unused-vars": ["warn", { "ignoreRestSiblings": true, "argsIgnorePattern": "^_" }],
+    "@typescript-eslint/no-unused-vars": [
+      "warn",
+      { ignoreRestSiblings: true, argsIgnorePattern: "^_" },
+    ],
     "@typescript-eslint/ban-ts-comment": "off",
+    "@typescript-eslint/ban-types": [
+      "error",
+      {
+        types: {
+          "{}": false,
+        },
+        extendDefaults: true,
+      },
+    ],
     "comma-dangle": ["error", "only-multiline"],
     "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
   - [**Utility types for TypeScript:**](#utility-types)
     - [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>
 
@@ -1442,6 +1444,11 @@ logSomething(barObject); // Type Error
 <br>
 
 ## NonEmptyArray
+Usage:
+```ts
+NonEmptyArray<TItem = unknown>
+```
+  
 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.  
   
@@ -1465,6 +1472,59 @@ logFirstItem(["04abc", "69"]); // 4
 
 </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>
 
 <!-- #MARKER Footer -->

+ 8 - 8
lib/array.ts

@@ -1,18 +1,18 @@
 import { randRange } from "./math";
 
 /** 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 */
-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 `[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)
     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 */
-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)
     return undefined;
 
   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 */
-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
 
   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 */
 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
  * @param word A word in singular form, to auto-convert to plural