Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fa18350c0 | ||
|
|
4c2ba0a1b4 | ||
|
|
c48030aea0 | ||
|
|
19b619b768 | ||
|
|
fe64105602 | ||
|
|
67c131e41c | ||
|
|
2c3ef2892e | ||
|
|
3c0237a413 | ||
|
|
6a0cbbcbdf | ||
|
|
71bde0eea9 | ||
|
|
994e8a7346 | ||
|
|
dbd475c319 |
@@ -16,20 +16,30 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
- name: Run linting
|
||||
run: npm run lint
|
||||
|
||||
publish-npm:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm publish --provenance --access public
|
||||
- name: Install Node.js dependencies
|
||||
run: npm ci
|
||||
- name: Compile TypeScript files
|
||||
run: npm run build
|
||||
- name: Publish to NPM
|
||||
run: npm publish --provenance --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
|
||||
@@ -24,3 +24,5 @@ jobs:
|
||||
run: npm run build
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
- name: Run linting
|
||||
run: npm run lint
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
.github
|
||||
.prettierrc.yml
|
||||
*.tsbuildinfo
|
||||
.versionrc.json
|
||||
.vscode
|
||||
CODEOWNERS
|
||||
src
|
||||
test
|
||||
tsconfig.json
|
||||
@@ -2,6 +2,33 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
||||
|
||||
## [0.2.2](https://github.com/Optolith/helpers/compare/v0.2.1...v0.2.2) (2024-12-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Result type ([19b619b](https://github.com/Optolith/helpers/commit/19b619b768855c70cb1dc4044e58cba971974585))
|
||||
|
||||
## [0.2.1](https://github.com/Optolith/helpers/compare/v0.2.0...v0.2.1) (2024-10-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **typeSafety:** add option to customize error message ([3c0237a](https://github.com/Optolith/helpers/commit/3c0237a413fc7e4da041e2cfaa76a530d357cce7))
|
||||
|
||||
## [0.2.0](https://github.com/Optolith/helpers/compare/v0.1.3...v0.2.0) (2024-10-04)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* add exports field to control imports
|
||||
|
||||
### build
|
||||
|
||||
* add exports field to control imports ([71bde0e](https://github.com/Optolith/helpers/commit/71bde0eea9d9e68bfc80751eff0caba08e685307))
|
||||
|
||||
## [0.1.3](https://github.com/Optolith/helpers/compare/v0.1.2...v0.1.3) (2024-10-04)
|
||||
|
||||
## [0.1.2](https://github.com/Optolith/helpers/compare/v0.1.1...v0.1.2) (2024-10-04)
|
||||
|
||||
## [0.1.1](https://github.com/Optolith/helpers/compare/v0.1.0...v0.1.1) (2024-10-04)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// @ts-check
|
||||
|
||||
import eslint from '@eslint/js'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.strictTypeChecked,
|
||||
tseslint.configs.stylisticTypeChecked,
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"test/**.ts",
|
||||
],
|
||||
rules: {
|
||||
"@typescript-eslint/no-floating-promises": "off",
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: [
|
||||
"lib",
|
||||
"eslint.config.js",
|
||||
],
|
||||
}
|
||||
)
|
||||
Generated
+1321
-25
File diff suppressed because it is too large
Load Diff
+18
-7
@@ -1,13 +1,21 @@
|
||||
{
|
||||
"name": "@optolith/helpers",
|
||||
"version": "0.1.2",
|
||||
"version": "0.2.2",
|
||||
"description": "A set of JavaScript helper functions for multiple Optolith packages.",
|
||||
"main": "lib/index.js",
|
||||
"files": [
|
||||
"lib",
|
||||
"AUTHORS",
|
||||
"CHANGELOG.md"
|
||||
],
|
||||
"exports": {
|
||||
"./*": "./lib/*.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -b",
|
||||
"watch": "tsc -b -w",
|
||||
"release": "commit-and-tag-version",
|
||||
"test": "glob -c \"node --import tsx --test\" \"./test/**/*.ts\""
|
||||
"test": "glob -c \"node --import tsx --test\" \"./test/**/*.ts\"",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -20,11 +28,14 @@
|
||||
},
|
||||
"homepage": "https://github.com/Optolith/helpers#readme",
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.7.4",
|
||||
"commit-and-tag-version": "^12.4.4",
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/node": "^22.10.2",
|
||||
"commit-and-tag-version": "^12.5.0",
|
||||
"eslint": "^9.17.0",
|
||||
"glob": "^11.0.0",
|
||||
"tsx": "^4.19.1",
|
||||
"typescript": "^5.6.2"
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.18.0"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
+7
-7
@@ -99,12 +99,12 @@ export const count = <T>(
|
||||
export const countBy = <T, K extends string | number | symbol>(
|
||||
arr: T[],
|
||||
fn: (value: T, index: number) => K
|
||||
): { [key in K]: number } =>
|
||||
arr.reduce((acc, value, index) => {
|
||||
): Partial<Record<K, number>> =>
|
||||
arr.reduce<Partial<Record<K, number>>>((acc, value, index) => {
|
||||
const key = fn(value, index)
|
||||
acc[key] = (acc[key] ?? 0) + 1
|
||||
return acc
|
||||
}, {} as { [key in K]: number })
|
||||
}, {})
|
||||
|
||||
/**
|
||||
* Counts the number of elements the function returns the same values for and
|
||||
@@ -113,14 +113,14 @@ export const countBy = <T, K extends string | number | symbol>(
|
||||
export const countByMany = <T, K extends string | number | symbol>(
|
||||
arr: T[],
|
||||
fn: (value: T, index: number) => K[]
|
||||
): { [key in K]: number } =>
|
||||
arr.reduce((acc, value, index) => {
|
||||
): Partial<Record<K, number>> =>
|
||||
arr.reduce<Partial<Record<K, number>>>((acc, value, index) => {
|
||||
const keys = fn(value, index)
|
||||
unique(keys).forEach((key) => {
|
||||
acc[key] = (acc[key] ?? 0) + 1
|
||||
})
|
||||
return acc
|
||||
}, {} as { [key in K]: number })
|
||||
}, {})
|
||||
|
||||
/**
|
||||
* Partitions an array into two arrays based on a predicate.
|
||||
@@ -178,7 +178,7 @@ export const reduceWhile = <T, U>(
|
||||
let acc = initial
|
||||
let index = 0
|
||||
while (index < arr.length && !pred(acc)) {
|
||||
acc = fn(acc, arr[index]!, index)
|
||||
acc = fn(acc, arr[index] as T, index)
|
||||
index++
|
||||
}
|
||||
return acc
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ export const classList = (
|
||||
...cls: (string | null | undefined | Record<string, boolean | undefined>)[]
|
||||
): string =>
|
||||
cls
|
||||
.flatMap(cl => {
|
||||
.flatMap((cl) => {
|
||||
if (cl === null || cl === undefined) {
|
||||
return []
|
||||
} else if (typeof cl === "string") {
|
||||
|
||||
+9
-5
@@ -15,18 +15,22 @@ export const constant =
|
||||
export const andEvery =
|
||||
<T>(...predicates: ((value: T) => boolean)[]) =>
|
||||
(value: T): boolean =>
|
||||
predicates.every(predicate => predicate(value))
|
||||
predicates.every((predicate) => predicate(value))
|
||||
|
||||
export function orSome<T, U extends T, V extends T>(
|
||||
f: (value: T) => value is U,
|
||||
g: (value: T) => value is V,
|
||||
g: (value: T) => value is V
|
||||
): (value: T) => value is U | V
|
||||
export function orSome<T>(...fns: ((value: T) => boolean)[]): (value: T) => boolean
|
||||
export function orSome<T>(
|
||||
...fns: ((value: T) => boolean)[]
|
||||
): (value: T) => boolean
|
||||
/**
|
||||
* Returns a function that combines predicate functions disjunctionally.
|
||||
*/
|
||||
export function orSome<T>(...predicates: ((value: T) => boolean)[]): (value: T) => boolean {
|
||||
return value => predicates.some(predicate => predicate(value))
|
||||
export function orSome<T>(
|
||||
...predicates: ((value: T) => boolean)[]
|
||||
): (value: T) => boolean {
|
||||
return (value) => predicates.some((predicate) => predicate(value))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+10
-2
@@ -14,13 +14,21 @@ export const plusMinus = "\xB1"
|
||||
* Forces signing on the given number, returning `undefined` on zero.
|
||||
*/
|
||||
export const signIgnoreZero = (x: number): string | undefined =>
|
||||
x > 0 ? `+${x}` : x < 0 ? `${minus}\u2060${Math.abs(x)}` : undefined
|
||||
x > 0
|
||||
? `+${x.toString()}`
|
||||
: x < 0
|
||||
? `${minus}\u2060${Math.abs(x).toString()}`
|
||||
: undefined
|
||||
|
||||
/**
|
||||
* Forces signing on the given number.
|
||||
*/
|
||||
export const sign = (x: number): string =>
|
||||
x > 0 ? `+${x}` : x < 0 ? `${minus}\u2060${Math.abs(x)}` : "0"
|
||||
x > 0
|
||||
? `+${x.toString()}`
|
||||
: x < 0
|
||||
? `${minus}\u2060${Math.abs(x).toString()}`
|
||||
: "0"
|
||||
|
||||
/**
|
||||
* Returns the sign of the given number. Returns `undefined` if the number is
|
||||
|
||||
+19
-9
@@ -6,34 +6,41 @@ export type Maybe<T> = Just<T> | Nothing
|
||||
/**
|
||||
* A maybe that contains a value.
|
||||
*/
|
||||
export type Just<T> = { readonly tag: "Just"; readonly value: T }
|
||||
export interface Just<T> {
|
||||
readonly tag: "Just"
|
||||
readonly value: T
|
||||
}
|
||||
|
||||
/**
|
||||
* A maybe that contains nothing.
|
||||
*/
|
||||
export type Nothing = { readonly tag: "Nothing" }
|
||||
export interface Nothing {
|
||||
readonly tag: "Nothing"
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a maybe that contains a value.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
|
||||
export const Just = <T>(value: T): Maybe<T> => ({ tag: "Just", value })
|
||||
|
||||
/**
|
||||
* Checks if a maybe contains a value.
|
||||
*/
|
||||
export const isJust = <T>(result: Maybe<T>): result is Just<T> => result.tag === "Just"
|
||||
export const isJust = <T>(result: Maybe<T>): result is Just<T> =>
|
||||
result.tag === "Just"
|
||||
|
||||
/**
|
||||
* Creates a maybe that contains nothing.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
|
||||
export const Nothing: Maybe<never> = { tag: "Nothing" }
|
||||
|
||||
/**
|
||||
* Checks if a maybe contains nothing.
|
||||
*/
|
||||
export const isNothing = <T>(result: Maybe<T>): result is Nothing => result.tag === "Nothing"
|
||||
export const isNothing = <T>(result: Maybe<T>): result is Nothing =>
|
||||
result.tag === "Nothing"
|
||||
|
||||
/**
|
||||
* Creates a maybe from a nullable value.
|
||||
@@ -61,13 +68,16 @@ export const map = <T, U>(maybe: Maybe<T>, f: (value: T) => U): Maybe<U> =>
|
||||
export const combine = <T1, T2, TR>(
|
||||
maybe1: Maybe<T1>,
|
||||
maybe2: Maybe<T2>,
|
||||
f: (value1: T1, value2: T2) => TR,
|
||||
): Maybe<TR> => (isJust(maybe1) && isJust(maybe2) ? Just(f(maybe1.value, maybe2.value)) : Nothing)
|
||||
f: (value1: T1, value2: T2) => TR
|
||||
): Maybe<TR> =>
|
||||
isJust(maybe1) && isJust(maybe2)
|
||||
? Just(f(maybe1.value, maybe2.value))
|
||||
: Nothing
|
||||
|
||||
/**
|
||||
* A namespace for maybe functions.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
|
||||
export const Maybe = Object.freeze({
|
||||
Just,
|
||||
isJust,
|
||||
|
||||
+19
-8
@@ -1,7 +1,9 @@
|
||||
/**
|
||||
* Extracts `null` and `undefined` from a type.
|
||||
*/
|
||||
export type Nullish<T = null | undefined> = T extends null | undefined ? T : never
|
||||
export type Nullish<T = null | undefined> = T extends null | undefined
|
||||
? T
|
||||
: never
|
||||
|
||||
/**
|
||||
* Checks if a value is `null` or `undefined`.
|
||||
@@ -12,13 +14,16 @@ export const isNullish = <T>(value: T): value is Exclude<T, NonNullable<T>> =>
|
||||
/**
|
||||
* Checks if a value is not `null` or `undefined`.
|
||||
*/
|
||||
export const isNotNullish = <T>(value: T): value is NonNullable<T> => !isNullish(value)
|
||||
export const isNotNullish = <T>(value: T): value is NonNullable<T> =>
|
||||
!isNullish(value)
|
||||
|
||||
/**
|
||||
* Maps a value to another value if it is not `null` or `undefined`.
|
||||
*/
|
||||
export const mapNullable = <T, U>(value: T, map: (value: NonNullable<T>) => U): U | Nullish<T> =>
|
||||
isNotNullish(value) ? map(value) : (value as Nullish<T>)
|
||||
export const mapNullable = <T, U>(
|
||||
value: T,
|
||||
map: (value: NonNullable<T>) => U
|
||||
): U | Nullish<T> => (isNotNullish(value) ? map(value) : (value as Nullish<T>))
|
||||
|
||||
/**
|
||||
* Maps a value to another value if it is not `null` or `undefined`, otherwise
|
||||
@@ -27,7 +32,7 @@ export const mapNullable = <T, U>(value: T, map: (value: NonNullable<T>) => U):
|
||||
export const mapNullableDefault = <T, U>(
|
||||
value: T,
|
||||
map: (value: NonNullable<T>) => U,
|
||||
defaultValue: U,
|
||||
defaultValue: U
|
||||
): U => (isNotNullish(value) ? map(value) : defaultValue)
|
||||
|
||||
/**
|
||||
@@ -51,15 +56,21 @@ export const nullableToArray = <T>(value: T): NonNullable<T>[] =>
|
||||
*/
|
||||
export function ensure<T, T1 extends T>(
|
||||
value: T,
|
||||
predicate: (value: T) => value is T1,
|
||||
predicate: (value: T) => value is T1
|
||||
): T1 | undefined
|
||||
/**
|
||||
* Returns the value if it matches the given predicate, otherwise `undefined`.
|
||||
*/
|
||||
export function ensure<T>(value: T, predicate: (value: T) => boolean): T | undefined
|
||||
export function ensure<T>(
|
||||
value: T,
|
||||
predicate: (value: T) => boolean
|
||||
): T | undefined
|
||||
/**
|
||||
* Returns the value if it matches the given predicate, otherwise `undefined`.
|
||||
*/
|
||||
export function ensure<T>(value: T, predicate: (value: T) => boolean): T | undefined {
|
||||
export function ensure<T>(
|
||||
value: T,
|
||||
predicate: (value: T) => boolean
|
||||
): T | undefined {
|
||||
return predicate(value) ? value : undefined
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
export const mapObject = <T extends object, U>(
|
||||
object: T,
|
||||
map: (value: T[keyof T], key: keyof T) => U | undefined,
|
||||
map: (value: T[keyof T], key: keyof T) => U | undefined
|
||||
): { [key in keyof T]: Exclude<U, undefined> } => {
|
||||
const result: { [key in keyof T]: Exclude<U, undefined> } = {} as never
|
||||
|
||||
|
||||
+4
-2
@@ -12,7 +12,9 @@ export const range = (bounds: RangeBounds): number[] => {
|
||||
const [start, end] = bounds
|
||||
|
||||
if (start > end) {
|
||||
throw new RangeError("The upper bound must be greater than or equal to the lower bound.")
|
||||
throw new RangeError(
|
||||
"The upper bound must be greater than or equal to the lower bound."
|
||||
)
|
||||
}
|
||||
|
||||
return Array.from({ length: end - start + 1 }, (_, i) => i + start)
|
||||
@@ -32,7 +34,7 @@ export const isInRange = (bounds: RangeBounds, value: number): boolean =>
|
||||
export const indexInRange = (bounds: RangeBounds, value: number) => {
|
||||
if (!isInRange(bounds, value)) {
|
||||
throw new RangeError(
|
||||
`indexInRange: index for ${value} is out of range (${bounds[0]}...${bounds[1]})`,
|
||||
`indexInRange: index for ${value.toString()} is out of range (${bounds[0].toString()}...${bounds[1].toString()})`
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -2,7 +2,8 @@
|
||||
* Checks if the provided string is a string representation of a natural number.
|
||||
* @param test The string to test.
|
||||
*/
|
||||
export const isNaturalNumber = (test: string) => /^(?:0|[1-9][0-9]*)$/u.test(test)
|
||||
export const isNaturalNumber = (test: string) =>
|
||||
/^(?:0|[1-9][0-9]*)$/u.test(test)
|
||||
|
||||
/**
|
||||
* Checks if the provided string is a string representation of an integer.
|
||||
@@ -15,7 +16,8 @@ export const isInteger = (test: string) => /^(?:0|-?[1-9][0-9]*)$/u.test(test)
|
||||
* number. Both `.` and `,` are accepted as decimal separators.
|
||||
* @param test The string to test.
|
||||
*/
|
||||
export const isFloat = (test: string) => /^(?:(?:0|-?[1-9][0-9]*)(?:[.,][0-9]+)?)$/u.test(test)
|
||||
export const isFloat = (test: string) =>
|
||||
/^(?:(?:0|-?[1-9][0-9]*)(?:[.,][0-9]+)?)$/u.test(test)
|
||||
|
||||
/**
|
||||
* Checks if the provided string either is an empty string or passes the given
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* A result is either a value or an error.
|
||||
*/
|
||||
export type Result<T, E> = Ok<T> | Error<E>
|
||||
|
||||
/**
|
||||
* A result that contains a value.
|
||||
*/
|
||||
export interface Ok<T> {
|
||||
readonly tag: "Ok"
|
||||
readonly value: T
|
||||
}
|
||||
|
||||
/**
|
||||
* A result that contains an error.
|
||||
*/
|
||||
export interface Error<E> {
|
||||
readonly tag: "Error"
|
||||
readonly error: E
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a result that contains a value.
|
||||
*/
|
||||
export const ok = <T>(value: T): Result<T, never> => ({ tag: "Ok", value })
|
||||
|
||||
/**
|
||||
* Checks if a result contains a value.
|
||||
*/
|
||||
export const isOk = <T, E>(result: Result<T, E>): result is Ok<T> =>
|
||||
result.tag === "Ok"
|
||||
|
||||
/**
|
||||
* Creates a result that contains an error.
|
||||
*/
|
||||
export const error = <E>(error: E): Result<never, E> => ({
|
||||
tag: "Error",
|
||||
error,
|
||||
})
|
||||
|
||||
/**
|
||||
* Checks if a result contains an error.
|
||||
*/
|
||||
export const isError = <T, E>(result: Result<T, E>): result is Error<E> =>
|
||||
result.tag === "Error"
|
||||
|
||||
/**
|
||||
* Reduces a result to a value of a common type.
|
||||
*/
|
||||
export const reduce = <T, E, R>(
|
||||
result: Result<T, E>,
|
||||
fok: (value: T) => R,
|
||||
ferror: (error: E) => R
|
||||
): R => (isOk(result) ? fok(result.value) : ferror(result.error))
|
||||
|
||||
/**
|
||||
* Maps the value of a result to a new value.
|
||||
*/
|
||||
export const map = <T, U, E>(
|
||||
result: Result<T, E>,
|
||||
f: (value: T) => U
|
||||
): Result<U, E> => (isOk(result) ? ok(f(result.value)) : result)
|
||||
|
||||
/**
|
||||
* Maps an error to a new error.
|
||||
*/
|
||||
export const mapError = <T, E, F>(
|
||||
result: Result<T, E>,
|
||||
f: (value: E) => F
|
||||
): Result<T, F> => (isError(result) ? error(f(result.error)) : result)
|
||||
|
||||
export const combine = <T1, T2, TR, E1, E2, ER>(
|
||||
result1: Result<T1, E1>,
|
||||
result2: Result<T2, E2>,
|
||||
fok: (value1: T1, value2: T2) => TR,
|
||||
ferror: (error1: E1, error2: E2) => ER
|
||||
): Result<TR, E1 | E2 | ER> =>
|
||||
isOk(result1)
|
||||
? isOk(result2)
|
||||
? ok(fok(result1.value, result2.value))
|
||||
: result2
|
||||
: isOk(result2)
|
||||
? result1
|
||||
: error(ferror(result1.error, result2.error))
|
||||
|
||||
export const Result = Object.freeze({
|
||||
ok,
|
||||
isOk,
|
||||
error,
|
||||
isError,
|
||||
reduce,
|
||||
map,
|
||||
mapError,
|
||||
combine,
|
||||
})
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* Checks if a value is a non-empty string.
|
||||
*/
|
||||
export const isNonEmptyString = (value: string | null | undefined): value is string =>
|
||||
typeof value === "string" && value.length > 0
|
||||
export const isNonEmptyString = (
|
||||
value: string | null | undefined
|
||||
): value is string => typeof value === "string" && value.length > 0
|
||||
|
||||
+8
-2
@@ -1,7 +1,10 @@
|
||||
/**
|
||||
* This function is used to make sure that the `switch` is exhaustive. Place it
|
||||
* in the `default` case of the `switch`.
|
||||
*
|
||||
* Optionally, you can pass a custom error message.
|
||||
* @param _x - The value that is used in the `switch`.
|
||||
* @param [msg] - A custom error message.
|
||||
* @example
|
||||
* const aorb = (x: "a" | "b") => {
|
||||
* switch (x) {
|
||||
@@ -11,6 +14,9 @@
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export function assertExhaustive(_x: never): never {
|
||||
throw new Error("The switch is not exhaustive.")
|
||||
export function assertExhaustive(
|
||||
_x: never,
|
||||
msg = "The switch is not exhaustive."
|
||||
): never {
|
||||
throw new Error(msg)
|
||||
}
|
||||
|
||||
+1
-1
@@ -203,7 +203,7 @@ describe("reduceWhile", () => {
|
||||
it("should return the initial value for an empty array", () => {
|
||||
const result = reduceWhile(
|
||||
[],
|
||||
(acc, value) => acc + value,
|
||||
(acc, value) => acc + (value as number),
|
||||
() => false,
|
||||
10
|
||||
)
|
||||
|
||||
+5
-1
@@ -100,7 +100,11 @@ describe(combine.name, () => {
|
||||
it("returns a maybe that contains nothing if either input maybe contains nothing", () => {
|
||||
const maybe1 = Just("hello")
|
||||
const maybe2 = Nothing
|
||||
const result = combine(maybe1, maybe2, (value1, value2) => value1 + value2)
|
||||
const result = combine(
|
||||
maybe1,
|
||||
maybe2,
|
||||
(value1, value2) => value1 + (value2 as string)
|
||||
)
|
||||
assert.deepEqual(result, { tag: "Nothing" })
|
||||
})
|
||||
|
||||
|
||||
+2
-2
@@ -5,14 +5,14 @@ import { mapObject } from "../src/object.js"
|
||||
describe("mapObject", () => {
|
||||
it("maps all own properties of an object to a new object", () => {
|
||||
const object = { a: 1, b: 2, c: 3 }
|
||||
const result = mapObject(object, (value, key) => value + key)
|
||||
const result = mapObject(object, (value, key) => value.toString() + key)
|
||||
assert.deepEqual(result, { a: "1a", b: "2b", c: "3c" })
|
||||
})
|
||||
|
||||
it("omits properties for which the mapping function returns undefined", () => {
|
||||
const object = { a: 1, b: 2, c: 3 }
|
||||
const result = mapObject(object, (value, key) =>
|
||||
key === "b" ? undefined : value + key
|
||||
key === "b" ? undefined : value.toString() + key
|
||||
)
|
||||
assert.deepEqual(result, { a: "1a", c: "3c" })
|
||||
})
|
||||
|
||||
+23
-6
@@ -3,10 +3,27 @@ import { describe, it } from "node:test"
|
||||
import { romanize } from "../src/roman.js"
|
||||
|
||||
describe("romanize", () => {
|
||||
it("returns 0 on 0", () => assert.equal(romanize(0), "0"))
|
||||
it("returns I on 1", () => assert.equal(romanize(1), "I"))
|
||||
it("returns V on 5", () => assert.equal(romanize(5), "V"))
|
||||
it("returns IX on 9", () => assert.equal(romanize(9), "IX"))
|
||||
it("returns XVIII on 18", () => assert.equal(romanize(18), "XVIII"))
|
||||
it("returns −IX on -9", () => assert.equal(romanize(-9), "−IX"))
|
||||
it("returns 0 on 0", () => {
|
||||
assert.equal(romanize(0), "0")
|
||||
})
|
||||
|
||||
it("returns I on 1", () => {
|
||||
assert.equal(romanize(1), "I")
|
||||
})
|
||||
|
||||
it("returns V on 5", () => {
|
||||
assert.equal(romanize(5), "V")
|
||||
})
|
||||
|
||||
it("returns IX on 9", () => {
|
||||
assert.equal(romanize(9), "IX")
|
||||
})
|
||||
|
||||
it("returns XVIII on 18", () => {
|
||||
assert.equal(romanize(18), "XVIII")
|
||||
})
|
||||
|
||||
it("returns −IX on -9", () => {
|
||||
assert.equal(romanize(-9), "−IX")
|
||||
})
|
||||
})
|
||||
|
||||
+12
-5
@@ -3,11 +3,18 @@ import { describe, it } from "node:test"
|
||||
import { isNonEmptyString } from "../src/string.js"
|
||||
|
||||
describe("isNonEmptyString", () => {
|
||||
it("returns false on undefined", () =>
|
||||
assert.equal(isNonEmptyString(undefined), false))
|
||||
it("returns false on null", () => assert.equal(isNonEmptyString(null), false))
|
||||
it("returns false on an empty string", () =>
|
||||
assert.equal(isNonEmptyString(""), false))
|
||||
it("returns false on undefined", () => {
|
||||
assert.equal(isNonEmptyString(undefined), false)
|
||||
})
|
||||
|
||||
it("returns false on null", () => {
|
||||
assert.equal(isNonEmptyString(null), false)
|
||||
})
|
||||
|
||||
it("returns false on an empty string", () => {
|
||||
assert.equal(isNonEmptyString(""), false)
|
||||
})
|
||||
|
||||
it("returns true on a non-empty string", () => {
|
||||
assert.equal(isNonEmptyString("a"), true)
|
||||
assert.equal(isNonEmptyString("ab"), true)
|
||||
|
||||
@@ -11,4 +11,12 @@ describe("assertExhaustive", () => {
|
||||
err instanceof Error && err.message === "The switch is not exhaustive."
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error with a custom message", () => {
|
||||
assert.throws(
|
||||
// @ts-expect-error The function should never receive a value.
|
||||
() => assertExhaustive("", "Custom error message"),
|
||||
(err) => err instanceof Error && err.message === "Custom error message"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user