style: add eslint and fix warnings and formatting

This commit is contained in:
Lukas Obermann
2024-12-16 13:38:05 +01:00
parent 67c131e41c
commit fe64105602
21 changed files with 1459 additions and 58 deletions
+1
View File
@@ -18,6 +18,7 @@ jobs:
node-version: 22
- run: npm ci
- run: npm test
- run: npm lint
publish-npm:
needs: test
+2
View File
@@ -22,5 +22,7 @@ jobs:
run: npm ci
- name: Compile TypeScript files
run: npm run build
- name: Run lining
run: npm lint
- name: Run tests
run: npm test
+32
View File
@@ -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",
],
}
)
+1297 -1
View File
File diff suppressed because it is too large Load Diff
+6 -2
View File
@@ -10,7 +10,8 @@
"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",
@@ -23,11 +24,14 @@
},
"homepage": "https://github.com/Optolith/helpers#readme",
"devDependencies": {
"@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.2",
"typescript": "^5.7.2"
"typescript": "^5.7.2",
"typescript-eslint": "^8.18.0"
},
"type": "module"
}
+7 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+3 -2
View File
@@ -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
+1 -1
View File
@@ -16,7 +16,7 @@
*/
export function assertExhaustive(
_x: never,
msg: string = "The switch is not exhaustive."
msg = "The switch is not exhaustive."
): never {
throw new Error(msg)
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)