Skip to content

Commit

Permalink
feat: utility types NonEmptyString and LooseUnion
Browse files Browse the repository at this point in the history
  • Loading branch information
Sv443 committed Nov 17, 2023
1 parent 2ae665d commit 49bc85e
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .changeset/funny-fans-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sv443-network/userutils": minor
---

Added utility types `NonEmptyString` and `LooseUnion`
40 changes: 24 additions & 16 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
},
Expand Down
62 changes: 61 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -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.
Expand All @@ -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 -->
Expand Down
16 changes: 8 additions & 8 deletions lib/array.ts
Original file line number Diff line number Diff line change
@@ -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];

Expand All @@ -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)
Expand Down
24 changes: 24 additions & 0 deletions lib/misc.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit 49bc85e

Please sign in to comment.