style: add eslint and fix warnings and formatting
This commit is contained in:
@@ -18,6 +18,7 @@ jobs:
|
||||
node-version: 22
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
- run: npm lint
|
||||
|
||||
publish-npm:
|
||||
needs: test
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
+1297
-1
File diff suppressed because it is too large
Load Diff
+6
-2
@@ -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
@@ -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
|
||||
|
||||
+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
|
||||
|
||||
+1
-1
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user