diff --git a/@coven/compare/compareIterables.ts b/@coven/compare/compareIterables.ts index 8caf42c..c3ea620 100644 --- a/@coven/compare/compareIterables.ts +++ b/@coven/compare/compareIterables.ts @@ -41,6 +41,7 @@ export const compareIterables = const leftIterator = getIterator(left); const rightIterator = getIterator(right); + // deno-lint-ignore coven/no-for for ( let index = 0, { done: leftDone = false, value: leftValue } = diff --git a/@coven/compare/deno.json b/@coven/compare/deno.json index 7dbb9c3..c7eb4a4 100644 --- a/@coven/compare/deno.json +++ b/@coven/compare/deno.json @@ -2,5 +2,5 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "exports": "./mod.ts", "name": "@coven/compare", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@coven/compare/tests/compare.test.ts b/@coven/compare/tests/compare.test.ts index b683ae4..1ee31e8 100644 --- a/@coven/compare/tests/compare.test.ts +++ b/@coven/compare/tests/compare.test.ts @@ -1,5 +1,10 @@ import { EMPTY_ARRAY, EMPTY_OBJECT } from "@coven/constants"; -import { EMPTY_ITERABLE_ITERATOR } from "@coven/iterables"; +import { + EMPTY_ITERABLE_ITERATOR, + filter, + iterableToArray, + map, +} from "@coven/iterables"; import { createObject } from "@coven/utils"; import { assertEquals } from "@std/assert"; import { compare } from "../compare.ts"; @@ -195,16 +200,20 @@ Deno.test( constructor: Array, length: 1, }); + const keyFilter = filter( + (key: string | symbol) => !Reflect.ownKeys(fakeArray).includes(key), + ); + const keyMap = map((key: string | symbol) => ({ + kind: DELETE_KIND, + left: Array.prototype[key as keyof typeof Array.prototype], + path: [key], + })); return assertEquals( flat(compare(["coven"])(fakeArray)), - Reflect.ownKeys(Array.prototype) - .filter((key) => !Reflect.ownKeys(fakeArray).includes(key)) - .map((key) => ({ - kind: DELETE_KIND, - left: Array.prototype[key as keyof typeof Array.prototype], - path: [key], - })) as ReturnType, + iterableToArray( + keyMap(keyFilter(Reflect.ownKeys(Array.prototype))), + ) as ReturnType, ); }, ); diff --git a/@coven/constants/deno.json b/@coven/constants/deno.json index 200b7f4..7c955f2 100644 --- a/@coven/constants/deno.json +++ b/@coven/constants/deno.json @@ -2,5 +2,5 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "exports": "./mod.ts", "name": "@coven/constants", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@coven/cron/compareField.ts b/@coven/cron/compareField.ts index d0ebda4..394bb75 100644 --- a/@coven/cron/compareField.ts +++ b/@coven/cron/compareField.ts @@ -1,3 +1,4 @@ +import { some } from "@coven/iterables"; import { memoFunction } from "@coven/memo"; import type { Filter, Unary } from "@coven/types"; import { always } from "@coven/utils"; @@ -34,7 +35,7 @@ export const compareField: Unary< isAllToken(field) ? alwaysTrue : ( memoFunction( isListField(field) ? - (value) => field.some(compareRangeOrValue(value)) + (value) => some(compareRangeOrValue(value))(field) : (value) => compareRangeOrValue(value)(field), ) ), diff --git a/@coven/cron/deno.json b/@coven/cron/deno.json index 779e768..2da8305 100644 --- a/@coven/cron/deno.json +++ b/@coven/cron/deno.json @@ -2,5 +2,5 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "exports": "./mod.ts", "name": "@coven/cron", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@coven/cron/isRangeString.ts b/@coven/cron/isRangeString.ts index d92621b..15e451f 100644 --- a/@coven/cron/isRangeString.ts +++ b/@coven/cron/isRangeString.ts @@ -1,3 +1,4 @@ +import { iterableToArray, map } from "@coven/iterables"; import { memoFunction } from "@coven/memo"; import { parseDecimal } from "@coven/parsers"; import type { Predicate } from "@coven/types"; @@ -5,6 +6,8 @@ import type { RangeString } from "./RangeString.ts"; import { rangeStringTest } from "./rangeStringTest.ts"; import { RANGE_EXPRESSION_SEPARATOR_TOKEN } from "./tokens.ts"; +const mapParseDecimal = map(parseDecimal); + /** * Predicate checking if given value is a cron string range * ({@linkcode RangeString}). @@ -15,9 +18,9 @@ import { RANGE_EXPRESSION_SEPARATOR_TOKEN } from "./tokens.ts"; export const isRangeString: Predicate = memoFunction( (value): value is RangeString => { if (rangeStringTest(value)) { - const [from, to] = value - .split(RANGE_EXPRESSION_SEPARATOR_TOKEN) - .map(parseDecimal) as [from: number, to: number]; + const [from, to] = iterableToArray( + mapParseDecimal(value.split(RANGE_EXPRESSION_SEPARATOR_TOKEN)), + ) as [from: number, to: number]; return from <= to; } else { diff --git a/@coven/cron/isValidExpression.ts b/@coven/cron/isValidExpression.ts index 7ad52e7..15237f9 100644 --- a/@coven/cron/isValidExpression.ts +++ b/@coven/cron/isValidExpression.ts @@ -7,7 +7,7 @@ import { cronRegExp } from "./cronRegExp.ts"; /** * Validates if a string is a cron expression. * - * @see {CronString} + * @see {@linkcode CronString} */ export const isValidExpression = test(build("iu")(cronRegExp)) as Predicate< string, diff --git a/@coven/cron/minutes.ts b/@coven/cron/minutes.ts index 33036be..e7dcd22 100644 --- a/@coven/cron/minutes.ts +++ b/@coven/cron/minutes.ts @@ -1,4 +1,4 @@ -import { iteratorFunctionToIterableIterator } from "@coven/iterables"; +import { forever, iteratorFunctionToIterableIterator } from "@coven/iterables"; const minute = Temporal.Duration.from("PT1M"); @@ -17,12 +17,8 @@ const minute = Temporal.Duration.from("PT1M"); export const minutes = ( plainDateTime: Temporal.PlainDateTime, ): IterableIterator => - iteratorFunctionToIterableIterator( - function* (): Generator { - let currentDateTime = plainDateTime; + iteratorFunctionToIterableIterator(() => { + let currentDateTime = plainDateTime; - for (;;) { - yield (currentDateTime = currentDateTime.add(minute)); - } - }, - ); + return forever(() => (currentDateTime = currentDateTime.add(minute))); + }); diff --git a/@coven/cron/nextISODate.ts b/@coven/cron/nextISODate.ts index ce2d927..db79669 100644 --- a/@coven/cron/nextISODate.ts +++ b/@coven/cron/nextISODate.ts @@ -8,7 +8,7 @@ import { nextISODates } from "./nextISODates.ts"; /** * Get next ISO date string for the given date and the given cron expression. * - * @example Getting the next ISO Date string corresponding to ghe given cron expression + * @example Getting the next ISO Date string corresponding to the given cron expression * ```typescript * nextISODate("1989-10-13T10:15:00.000Z")("* * * * *"); // "1989-10-13T10:16:00.000Z" * ``` diff --git a/@coven/cron/parse.ts b/@coven/cron/parse.ts index e0cf045..eab5f3b 100644 --- a/@coven/cron/parse.ts +++ b/@coven/cron/parse.ts @@ -41,7 +41,7 @@ export const parse: Unary< ); return ( - length(entries) === 0 ? undefined : ( + length(entries) === 0n ? undefined : ( memo(entriesToObject(entries)) )) as Maybe; }); diff --git a/@coven/cron/stringify.ts b/@coven/cron/stringify.ts index 10ab089..d735bb2 100644 --- a/@coven/cron/stringify.ts +++ b/@coven/cron/stringify.ts @@ -1,6 +1,6 @@ -import { EMPTY_OBJECT } from "@coven/constants"; +import { join, map } from "@coven/iterables"; import { memoFunction } from "@coven/memo"; -import type { Maybe, Unary } from "@coven/types"; +import type { Maybe, Unary, ValueOf } from "@coven/types"; import type { CronObject } from "./CronObject.ts"; import type { CronString } from "./CronString.ts"; import { fieldNamesTuple } from "./fieldNamesTuple.ts"; @@ -8,6 +8,13 @@ import { isValidExpression } from "./isValidExpression.ts"; import { stringifyField } from "./stringifyField.ts"; import { ALL_TOKEN } from "./tokens.ts"; +const spaceJoin = join(" "); + +const mapStringifyField = (cron?: Partial) => + map((name: ValueOf) => + stringifyField(cron?.[name] ?? ALL_TOKEN), + ); + /** * Takes a cron object and returns a sting expression. * @@ -32,10 +39,8 @@ import { ALL_TOKEN } from "./tokens.ts"; export const stringify: Unary< [cron: Maybe>], Maybe -> = memoFunction((cron = EMPTY_OBJECT) => { - const expression = fieldNamesTuple - .map((name) => stringifyField(cron[name] ?? ALL_TOKEN)) - .join(" "); +> = memoFunction((cron) => { + const expression = spaceJoin(mapStringifyField(cron)(fieldNamesTuple)); return isValidExpression(expression) ? expression : undefined; }); diff --git a/@coven/cron/stringifyList.ts b/@coven/cron/stringifyList.ts index 5d16642..cf17308 100644 --- a/@coven/cron/stringifyList.ts +++ b/@coven/cron/stringifyList.ts @@ -1,11 +1,24 @@ +import { join, map } from "@coven/iterables"; import { memoFunction } from "@coven/memo"; -import type { Maybe, Unary } from "@coven/types"; +import type { Maybe, Stringable, Unary } from "@coven/types"; import type { Field } from "./Field.ts"; import type { ListString } from "./ListString.ts"; +import type { ValueOrRangeField } from "./ValueOrRangeField.ts"; import { isListField } from "./isListField.ts"; import { stringifyRange } from "./stringifyRange.ts"; import { LIST_EXPRESSION_SEPARATOR_TOKEN } from "./tokens.ts"; +const stringifyRangeMap = map( + (item: ValueOrRangeField) => + stringifyRange(item) ?? `${item as number}`, +); + +const joinList = join(LIST_EXPRESSION_SEPARATOR_TOKEN) as < + Item extends Stringable, +>( + iterable: Iterable, +) => ListString; + /** * Turns cron list into a string. * @@ -23,9 +36,5 @@ export const stringifyList: Unary< [field: Readonly>], Maybe > = memoFunction((field) => - isListField(field) ? - (field - .map((item) => stringifyRange(item) ?? `${item as number}`) - .join(LIST_EXPRESSION_SEPARATOR_TOKEN) as ListString) - : undefined, + isListField(field) ? joinList(stringifyRangeMap(field)) : undefined, ); diff --git a/@coven/cron/tests/parse.test.ts b/@coven/cron/tests/parse.test.ts index 066ca2f..ebe8db0 100644 --- a/@coven/cron/tests/parse.test.ts +++ b/@coven/cron/tests/parse.test.ts @@ -1,6 +1,8 @@ // This file is huge, but is because we have to cover quite a lot // deno-lint-ignore coven/max-lines +import { forEach } from "@coven/iterables"; import { memo } from "@coven/memo"; +import type { ValueOf } from "@coven/types"; import { assertStrictEquals } from "@std/assert"; import type { CronString } from "../CronString.ts"; import { parse } from "../parse.ts"; @@ -300,16 +302,16 @@ Deno.test( ), ); -februaryBadDaysOfMonth.forEach((februaryBadDayOfMonth) => +forEach((februaryBadDayOfMonth: ValueOf) => Deno.test(`* * ${februaryBadDayOfMonth} 2 * returns undefined`, () => assertStrictEquals( parse(`* * ${februaryBadDayOfMonth} 2 *`), undefined, ), ), -); +)(februaryBadDaysOfMonth); -februaryBadDaysOfMonth.forEach((februaryBadDayOfMonth) => +forEach((februaryBadDayOfMonth: ValueOf) => Deno.test( `* * ${februaryBadDayOfMonth} 2,3 * returns valid date because 3 is included`, () => @@ -328,9 +330,9 @@ februaryBadDaysOfMonth.forEach((februaryBadDayOfMonth) => } as const), ), ), -); +)(februaryBadDaysOfMonth); -februaryBadDaysOfMonth.forEach((februaryBadDayOfMonth) => +forEach((februaryBadDayOfMonth: ValueOf) => Deno.test( `* * ${februaryBadDayOfMonth} 2-4 * returns valid date because 3 is included`, () => @@ -348,4 +350,4 @@ februaryBadDaysOfMonth.forEach((februaryBadDayOfMonth) => } as const), ), ), -); +)(februaryBadDaysOfMonth); diff --git a/@coven/expression/deno.json b/@coven/expression/deno.json index 0c100e2..4e31664 100644 --- a/@coven/expression/deno.json +++ b/@coven/expression/deno.json @@ -2,5 +2,5 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "exports": "./mod.ts", "name": "@coven/expression", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@coven/iterables/async/count.ts b/@coven/iterables/async/count.ts index b31ef04..7503b85 100644 --- a/@coven/iterables/async/count.ts +++ b/@coven/iterables/async/count.ts @@ -9,15 +9,15 @@ import { length } from "./length.ts"; * @example * ```typescript * const countOdds = count((number: number) => number % 2 !== 1); - * countOdds([1, 2, 3, 4, 5]); // 3 - * countOdds([0, 2, 4, 6, 8]); // 0 + * countOdds([1, 2, 3, 4, 5]); // 3n + * countOdds([0, 2, 4, 6, 8]); // 0n * ``` * @param predicate Predicate function for items to be counted. * @returns Curried function with `predicate` in context. */ export const count = ( predicate: Filter<[item: Item]>, -): AsyncUnary<[iterable: AwaitableIterable], number> => { +): AsyncUnary<[iterable: AwaitableIterable], bigint> => { const predicateFilter = filter(predicate); return (iterable) => length(predicateFilter(iterable)); diff --git a/@coven/iterables/async/drop.ts b/@coven/iterables/async/drop.ts index e3faa15..20d2c30 100644 --- a/@coven/iterables/async/drop.ts +++ b/@coven/iterables/async/drop.ts @@ -1,4 +1,4 @@ -import type { AwaitableIterable } from "@coven/types"; +import type { AwaitableIterable, Numeric } from "@coven/types"; import { filter } from "./filter.ts"; import { iteratorFunctionToAsyncIterableIterator } from "./iteratorFunctionToAsyncIterableIterator.ts"; @@ -8,21 +8,21 @@ import { iteratorFunctionToAsyncIterableIterator } from "./iteratorFunctionToAsy * * @example * ```typescript - * const drop2 = drop(2); + * const drop2 = drop(2n); * drop2([1, 2, 3, 4, 5]); // [3, 4, 5] * ``` * @param amount Amount of items to drop. * @returns Curried function with `amount` in context. */ export const drop = ( - amount: number, + amount: Numeric, ): (( iterable: AwaitableIterable, ) => Readonly>>) => { const amountFilter = (iterable: AwaitableIterable) => { - let count = -1; + let count = -1n; - return filter(() => (count += 1) >= amount)( + return filter(() => (count += 1n) >= amount)( iterable, ) as AwaitableIterable; }; diff --git a/@coven/iterables/async/entriesToObject.ts b/@coven/iterables/async/entriesToObject.ts index 427b237..8b29d32 100644 --- a/@coven/iterables/async/entriesToObject.ts +++ b/@coven/iterables/async/entriesToObject.ts @@ -21,6 +21,7 @@ import { reduce } from "./reduce.ts"; * ["number", 1] * ]); // { foo: "bar", number: 1 } * ``` + * @param iterable `AsyncIterable` of entries to turn into an object. * @returns Object constructed from entries. */ export const entriesToObject = reduce( diff --git a/@coven/iterables/async/every.ts b/@coven/iterables/async/every.ts index 1d73990..2e3df79 100644 --- a/@coven/iterables/async/every.ts +++ b/@coven/iterables/async/every.ts @@ -33,6 +33,7 @@ export const every: { async (iterable) => { for await (const item of iterable) { if (!predicate(item)) { + // deno-lint-ignore coven/no-early-return return false; } } diff --git a/@coven/iterables/async/find.ts b/@coven/iterables/async/find.ts index 3d5ec96..0799f41 100644 --- a/@coven/iterables/async/find.ts +++ b/@coven/iterables/async/find.ts @@ -33,7 +33,8 @@ export const find: { async (iterable): Promise> => { for await (const item of iterable) { if (predicate(item)) { - return item as Maybe; + // deno-lint-ignore coven/no-early-return + return item; } } diff --git a/@coven/iterables/async/initial.ts b/@coven/iterables/async/initial.ts index 6900ed2..a4314dd 100644 --- a/@coven/iterables/async/initial.ts +++ b/@coven/iterables/async/initial.ts @@ -24,6 +24,7 @@ export const initial = ( const iterator = getIterator(iterable); let item = await iterator.next(); + // deno-lint-ignore coven/no-for for (; !item.done; ) { const value = item.value; diff --git a/@coven/iterables/async/length.ts b/@coven/iterables/async/length.ts index 07acb59..7a67376 100644 --- a/@coven/iterables/async/length.ts +++ b/@coven/iterables/async/length.ts @@ -3,15 +3,18 @@ import { always } from "@coven/utils"; import { reduce } from "./reduce.ts"; /** - * Get the length of an iterable or asynchronous iterable. + * Get the length of an iterable or asynchronous iterable (using `bigint` for + * really big iterables). * * @example * ```typescript - * length([1, 2, 3]); // 3 + * length([1, 2, 3]); // 3n * ``` * @param iterable Iterable or asynchronous iterable to get the length from. * @returns Promise with the length of the iterable. */ export const length: ( iterable: Iterable, -) => Promise = reduce(always((total) => total + 1))(0); +) => Promise = reduce(always((total) => total + 1n))( + 0n, +); diff --git a/@coven/iterables/async/some.ts b/@coven/iterables/async/some.ts index ed15a41..33c3140 100644 --- a/@coven/iterables/async/some.ts +++ b/@coven/iterables/async/some.ts @@ -33,6 +33,7 @@ export const some: { async (iterable) => { for await (const item of iterable) { if (predicate(item)) { + // deno-lint-ignore coven/no-early-return return true; } } diff --git a/@coven/iterables/async/take.ts b/@coven/iterables/async/take.ts index 41e2bbd..67d11c3 100644 --- a/@coven/iterables/async/take.ts +++ b/@coven/iterables/async/take.ts @@ -1,4 +1,4 @@ -import type { AwaitableIterable } from "@coven/types"; +import type { AwaitableIterable, Numeric } from "@coven/types"; import { iteratorFunctionToAsyncIterableIterator } from "./iteratorFunctionToAsyncIterableIterator.ts"; /** @@ -14,7 +14,7 @@ import { iteratorFunctionToAsyncIterableIterator } from "./iteratorFunctionToAsy */ export const take = ( - amount: number, + amount: Numeric, ): (( iterable: AwaitableIterable, ) => Readonly>) => @@ -23,7 +23,7 @@ export const take = async function* (): AsyncGenerator { let count = 0n; - if (amount > 0) { + if (amount > 0n) { for await (const item of iterable) { if (count < amount) { yield item; diff --git a/@coven/iterables/async/tests/count.test.ts b/@coven/iterables/async/tests/count.test.ts index 41b018a..5f5ac3d 100644 --- a/@coven/iterables/async/tests/count.test.ts +++ b/@coven/iterables/async/tests/count.test.ts @@ -7,18 +7,18 @@ const countAll = count((_) => true); Deno.test( "Array of mixed numbers and an even counter returns amount of even numbers in the array", - async () => assertStrictEquals(await countEvens([0, 1, 2, 3, 4]), 3), + async () => assertStrictEquals(await countEvens([0, 1, 2, 3, 4]), 3n), ); Deno.test("Empty array and an even counter returns 0", async () => - assertStrictEquals(await countEvens(EMPTY_ARRAY), 0), + assertStrictEquals(await countEvens(EMPTY_ARRAY), 0n), ); Deno.test("Array of odd numbers and an even counter returns 0", async () => - assertStrictEquals(await countEvens([1, 3, 5, 7]), 0), + assertStrictEquals(await countEvens([1, 3, 5, 7]), 0n), ); Deno.test( "Array of mixed numbers and a counter with no filter returns full length", - async () => assertStrictEquals(await countAll([0, 1, 2, 3, 4]), 5), + async () => assertStrictEquals(await countAll([0, 1, 2, 3, 4]), 5n), ); diff --git a/@coven/iterables/async/tests/length.test.ts b/@coven/iterables/async/tests/length.test.ts index cd01769..859f79a 100644 --- a/@coven/iterables/async/tests/length.test.ts +++ b/@coven/iterables/async/tests/length.test.ts @@ -5,17 +5,17 @@ import { length } from "../length.ts"; import { toIterable } from "../toIterable.ts"; Deno.test("Array returns length", async () => - assertStrictEquals(await length([0, 1, 2]), 3), + assertStrictEquals(await length([0, 1, 2]), 3n), ); Deno.test("Iterable returns length", async () => - assertStrictEquals(await length(range(1)(0)(2)), 3), + assertStrictEquals(await length(range(1)(0)(2)), 3n), ); Deno.test("Empty array returns 0", async () => - assertStrictEquals(await length(EMPTY_ARRAY), 0), + assertStrictEquals(await length(EMPTY_ARRAY), 0n), ); Deno.test("Empty iterable returns 0", async () => - assertStrictEquals(await length(toIterable(EMPTY_ARRAY)), 0), + assertStrictEquals(await length(toIterable(EMPTY_ARRAY)), 0n), ); diff --git a/@coven/iterables/async/tests/take.test.ts b/@coven/iterables/async/tests/take.test.ts index 46dd674..505c5b7 100644 --- a/@coven/iterables/async/tests/take.test.ts +++ b/@coven/iterables/async/tests/take.test.ts @@ -4,8 +4,8 @@ import { repeat } from "../../repeat.ts"; import { iterableToArray } from "../iterableToArray.ts"; import { take } from "../take.ts"; -const take2 = take(2); -const takeNone = take(0); +const take2 = take(2n); +const takeNone = take(0n); const takeAll = take(Infinity); const repeatZeroForever = repeat(Infinity)(0); diff --git a/@coven/iterables/async/tests/zipIndex.test.ts b/@coven/iterables/async/tests/zipIndex.test.ts index 541a0fd..db38b59 100644 --- a/@coven/iterables/async/tests/zipIndex.test.ts +++ b/@coven/iterables/async/tests/zipIndex.test.ts @@ -8,8 +8,8 @@ Deno.test( "Array with two strings returns Iterable of tuples with indexes and strings", async () => assertEquals(await iterableToArray(zipIndex(["foo", "bar"])), [ - [0, "foo"], - [1, "bar"], + [0n, "foo"], + [1n, "bar"], ]), ); @@ -21,7 +21,7 @@ Deno.test( "Iterable of strings returns Iterable of tuples with indexes and strings", async () => assertEquals(await iterableToArray(zipIndex(repeat(2)("foo"))), [ - [0, "foo"], - [1, "foo"], + [0n, "foo"], + [1n, "foo"], ]), ); diff --git a/@coven/iterables/async/zipIndex.ts b/@coven/iterables/async/zipIndex.ts index 052f101..0cfba09 100644 --- a/@coven/iterables/async/zipIndex.ts +++ b/@coven/iterables/async/zipIndex.ts @@ -14,6 +14,6 @@ import { zip } from "./zip.ts"; */ export const zipIndex: ( iterableSecond: AwaitableIterable, -) => AsyncIterableIterator> = zip( - range(1)(0)(Infinity), +) => AsyncIterableIterator> = zip( + range(1n)(0n)(Infinity), ); diff --git a/@coven/iterables/count.ts b/@coven/iterables/count.ts index a023472..34a5e53 100644 --- a/@coven/iterables/count.ts +++ b/@coven/iterables/count.ts @@ -8,15 +8,15 @@ import { length } from "./length.ts"; * @example * ```typescript * const countOdds = count((number: number) => number % 2 !== 1); - * countOdds([1, 2, 3, 4, 5]); // 3 - * countOdds([0, 2, 4, 6, 8]); // 0 + * countOdds([1, 2, 3, 4, 5]); // 3n + * countOdds([0, 2, 4, 6, 8]); // 0n * ``` * @param predicate Predicate function for items to be counted. * @returns Curried function with `predicate` in context. */ export const count = ( predicate: Filter<[item: Item]>, -): Unary<[iterable: Iterable], number> => { +): Unary<[iterable: Iterable], bigint> => { const predicateFilter = filter(predicate); return (iterable: Iterable) => length(predicateFilter(iterable)); diff --git a/@coven/iterables/deno.json b/@coven/iterables/deno.json index f2a8956..2f40eeb 100644 --- a/@coven/iterables/deno.json +++ b/@coven/iterables/deno.json @@ -5,5 +5,5 @@ "./async": "./async/mod.ts" }, "name": "@coven/iterables", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@coven/iterables/drop.ts b/@coven/iterables/drop.ts index a2589bb..10e42ab 100644 --- a/@coven/iterables/drop.ts +++ b/@coven/iterables/drop.ts @@ -1,4 +1,5 @@ -import { getIterator } from "./getIterator.ts"; +import type { Numeric } from "@coven/types"; +import { filter } from "./filter.ts"; import { iteratorFunctionToIterableIterator } from "./iteratorFunctionToIterableIterator.ts"; /** @@ -6,17 +7,25 @@ import { iteratorFunctionToIterableIterator } from "./iteratorFunctionToIterable * * @example * ```typescript - * const drop2 = drop(2); + * const drop2 = drop(2n); * drop2([1, 2, 3, 4, 5]); // [3, 4, 5] * ``` * @param amount Amount of items to drop. * @returns Curried function with `amount` in context. */ -export const drop = - ( - amount: number, - ): ((iterable: Iterable) => IterableIterator) => - (iterable: Iterable) => - iteratorFunctionToIterableIterator(() => - getIterator(iterable).drop(amount), - ); +export const drop = ( + amount: Numeric, +): ((iterable: Iterable) => IterableIterator) => { + const amountFilter = (iterable: Iterable) => { + let count = -1n; + + return filter(() => (count += 1n) >= amount)( + iterable, + ) as Iterable; + }; + + return (iterable: Iterable) => + iteratorFunctionToIterableIterator(function* (): Generator { + yield* amount > 0 ? amountFilter(iterable) : iterable; + }); +}; diff --git a/@coven/iterables/entriesToObject.ts b/@coven/iterables/entriesToObject.ts index 6e4e946..7b0bcf5 100644 --- a/@coven/iterables/entriesToObject.ts +++ b/@coven/iterables/entriesToObject.ts @@ -14,6 +14,7 @@ import { reduce } from "./reduce.ts"; * ["number", 1] * ]); // { foo: "bar", number: 1 } * ``` + * @param iterable `Iterable` of entries to turn into an object. * @returns Object constructed from entries. */ export const entriesToObject = reduce( diff --git a/@coven/iterables/forever.ts b/@coven/iterables/forever.ts new file mode 100644 index 0000000..a682203 --- /dev/null +++ b/@coven/iterables/forever.ts @@ -0,0 +1,28 @@ +import type { Nullary } from "@coven/types"; +import { iteratorFunctionToIterableIterator } from "./iteratorFunctionToIterableIterator.ts"; + +/** + * Generator that runs forever yielding the return type of the `yielder` + * function every iteration. + * + * @example + * ```typescript + * import { take, iterableToArray } from "@coven/iterables"; + * + * const countLoops = () => { + * let loop = 0n; + * return forever(() => (loop = loop + 1n)); + * }; + * + * take(10n)(countLoops()); // [1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n, 9n, 10n] + * ``` + * @param yielder Function to be called forever. + * @returns `IterableIterator` with return value of given yielder. + */ +export const forever = (yielder: Nullary): IterableIterator => + iteratorFunctionToIterableIterator(function* (): Generator { + // deno-lint-ignore coven/no-for + for (;;) { + yield yielder(); + } + }); diff --git a/@coven/iterables/initial.ts b/@coven/iterables/initial.ts index 40d52b1..c52fba2 100644 --- a/@coven/iterables/initial.ts +++ b/@coven/iterables/initial.ts @@ -24,6 +24,7 @@ export const initial = >( iteratorFunctionToIterableIterator(function* (): Generator { const iterator = getIterator(iterable); + // deno-lint-ignore coven/no-for for (let item = iterator.next(); !item.done; ) { const value = item.value; diff --git a/@coven/iterables/intersperse.ts b/@coven/iterables/intersperse.ts index 4ba6071..7085767 100644 --- a/@coven/iterables/intersperse.ts +++ b/@coven/iterables/intersperse.ts @@ -3,6 +3,8 @@ import { initial } from "./initial.ts"; import { repeat } from "./repeat.ts"; import { zip } from "./zip.ts"; +const repeatInfinitely = repeat(Infinity); + /** * Add the given `separator` between each element of the given iterable. * @@ -17,12 +19,8 @@ import { zip } from "./zip.ts"; export const intersperse = ( separator: Separator, ): ((iterable: Iterable) => IterableIterator) => { - const repeatSeparator = repeat(Infinity)(separator); + const repeatSeparator = repeatInfinitely(separator); - return (iterable: Iterable) => { - const zipped = zip(iterable)(repeatSeparator); - const flattened = flat(zipped); - const initialVal = initial(flattened); - return initialVal; - }; + return (iterable: Iterable) => + initial(flat(zip(iterable)(repeatSeparator))); }; diff --git a/@coven/iterables/length.ts b/@coven/iterables/length.ts index 734fa32..8582c94 100644 --- a/@coven/iterables/length.ts +++ b/@coven/iterables/length.ts @@ -2,15 +2,15 @@ import { always } from "@coven/utils"; import { reduce } from "./reduce.ts"; /** - * Get the length of an iterable. + * Get the length of an iterable (using `bigint` for really big iterables). * * @example * ```typescript - * length([1, 2, 3]); // 3 + * length([1, 2, 3]); // 3n * ``` * @param iterable Iterable to get the length from. - * @returns Promise with the length of the iterable. + * @returns The length of the iterable. */ -export const length: (iterable: Iterable) => number = reduce( - always((total: number) => total + 1), -)(0); +export const length: (iterable: Iterable) => bigint = reduce( + always((total: bigint) => total + 1n), +)(0n); diff --git a/@coven/iterables/mod.ts b/@coven/iterables/mod.ts index 070adb7..2098293 100644 --- a/@coven/iterables/mod.ts +++ b/@coven/iterables/mod.ts @@ -8,6 +8,7 @@ export { filter } from "./filter.ts"; export { find } from "./find.ts"; export { flat } from "./flat.ts"; export { forEach } from "./forEach.ts"; +export { forever } from "./forever.ts"; export { getIterator } from "./getIterator.ts"; export { groupBy } from "./groupBy.ts"; export { head } from "./head.ts"; diff --git a/@coven/iterables/random.ts b/@coven/iterables/random.ts index 5c7562f..7661e7d 100644 --- a/@coven/iterables/random.ts +++ b/@coven/iterables/random.ts @@ -1,5 +1,6 @@ import type { Stringable, Unary } from "@coven/types"; import { seededRandom } from "@coven/utils"; +import { forever } from "./forever.ts"; import { iteratorFunctionToIterableIterator } from "./iteratorFunctionToIterableIterator.ts"; /** @@ -28,13 +29,13 @@ export const random = > => (from) => (to) => - iteratorFunctionToIterableIterator(function* (): Generator { - let state: string | number = `${seed}`; + iteratorFunctionToIterableIterator(() => { + let state: Stringable = seed; const min = from < to ? from : to; const max = from > to ? from : to; - for (;;) { - yield Math.min( + return forever(() => + Math.min( Math.max( (state = seededRandom(`${state}(${min}-${max})`) @@ -43,6 +44,6 @@ export const random = min, ), max, - ); - } + ), + ); }); diff --git a/@coven/iterables/range.ts b/@coven/iterables/range.ts index 6a69c87..7bfac84 100644 --- a/@coven/iterables/range.ts +++ b/@coven/iterables/range.ts @@ -1,4 +1,5 @@ -import type { Unary } from "@coven/types"; +import { isBigInt } from "@coven/predicates"; +import type { Numeric, Unary } from "@coven/types"; import { iteratorFunctionToIterableIterator } from "./iteratorFunctionToIterableIterator.ts"; /** @@ -13,19 +14,33 @@ import { iteratorFunctionToIterableIterator } from "./iteratorFunctionToIterable * @returns Curried function with `step` set in context. */ export const range = - ( - step: number, - ): Unary<[from: number], Unary<[to: number], IterableIterator>> => + ( + step: Step, + ): Unary< + [from: Numeric], + Unary< + [to: Numeric], + IterableIterator + > + > => (from) => (to) => - iteratorFunctionToIterableIterator(function* (): Generator { + iteratorFunctionToIterableIterator(function* (): Generator< + Step extends bigint ? bigint : number + > { + // deno-lint-ignore prefer-const + let current = ( + isBigInt(step) ? + BigInt(from) + : Number(from)) as Step extends bigint ? bigint : number; if (from === to) { - yield from; + yield current; } else { + // deno-lint-ignore coven/no-for for ( - let current = from; + ; from < to ? current <= to : current >= to; - current += from < to ? step : -step + (current as bigint) += (from < to ? step : -step) as bigint ) { yield current; } diff --git a/@coven/iterables/repeat.ts b/@coven/iterables/repeat.ts index 16e6660..7c92b13 100644 --- a/@coven/iterables/repeat.ts +++ b/@coven/iterables/repeat.ts @@ -1,5 +1,7 @@ import type { Numeric } from "@coven/types"; -import { iteratorFunctionToIterableIterator } from "./iteratorFunctionToIterableIterator.ts"; +import { always } from "@coven/utils"; +import { forever } from "./forever.ts"; +import { take } from "./take.ts"; /** * Repeat given item the specified amount of times (can be `BigInt` or @@ -16,14 +18,4 @@ import { iteratorFunctionToIterableIterator } from "./iteratorFunctionToIterable export const repeat = (times: Numeric): ((item: Item) => IterableIterator) => (item: Item) => - iteratorFunctionToIterableIterator(function* (): Generator { - if (times === Infinity) { - for (;;) { - yield item; - } - } else { - for (let count = 0n; count < times; count += 1n) { - yield item; - } - } - }); + take(times)(forever(always(item))); diff --git a/@coven/iterables/some.ts b/@coven/iterables/some.ts index 3127604..922a245 100644 --- a/@coven/iterables/some.ts +++ b/@coven/iterables/some.ts @@ -1,4 +1,4 @@ -import type { AsyncFilter, Filter, Predicate, Single } from "@coven/types"; +import type { Filter, Predicate, Single } from "@coven/types"; import { getIterator } from "./getIterator.ts"; /** @@ -24,7 +24,5 @@ export const some = (( ( predicate: Predicate, ): Predicate, Iterable>; - ( - predicate: Filter<[item: Item]>, - ): AsyncFilter<[iterable: Iterable]>; + (predicate: Filter<[item: Item]>): Filter<[iterable: Iterable]>; }; diff --git a/@coven/iterables/take.ts b/@coven/iterables/take.ts index de7536d..086e325 100644 --- a/@coven/iterables/take.ts +++ b/@coven/iterables/take.ts @@ -1,4 +1,4 @@ -import { getIterator } from "./getIterator.ts"; +import type { Numeric } from "@coven/types"; import { iteratorFunctionToIterableIterator } from "./iteratorFunctionToIterableIterator.ts"; /** @@ -14,9 +14,20 @@ import { iteratorFunctionToIterableIterator } from "./iteratorFunctionToIterable */ export const take = ( - amount: number, + amount: Numeric, ): ((iterable: Iterable) => IterableIterator) => (iterable: Iterable) => - iteratorFunctionToIterableIterator(() => - getIterator(iterable).take(amount), - ); + iteratorFunctionToIterableIterator(function* (): Generator { + let count = 0n; + + if (amount > 0n) { + for (const item of iterable) { + if (count < amount) { + yield item; + count += 1n; + } else { + return; + } + } + } + }); diff --git a/@coven/iterables/tests/count.test.ts b/@coven/iterables/tests/count.test.ts index 6c26757..2b39725 100644 --- a/@coven/iterables/tests/count.test.ts +++ b/@coven/iterables/tests/count.test.ts @@ -7,18 +7,18 @@ const countAll = count((_) => true); Deno.test( "Array of mixed numbers and an even counter returns amount of even numbers in the array", - () => assertStrictEquals(countEvens([0, 1, 2, 3, 4]), 3), + () => assertStrictEquals(countEvens([0, 1, 2, 3, 4]), 3n), ); Deno.test("Empty array and an even counter returns 0", () => - assertStrictEquals(countEvens(EMPTY_ARRAY), 0), + assertStrictEquals(countEvens(EMPTY_ARRAY), 0n), ); Deno.test("Array of odd numbers and an even counter returns 0", () => - assertStrictEquals(countEvens([1, 3, 5, 7]), 0), + assertStrictEquals(countEvens([1, 3, 5, 7]), 0n), ); Deno.test( "Array of mixed numbers and a counter with no filter returns full length", - () => assertStrictEquals(countAll([0, 1, 2, 3, 4]), 5), + () => assertStrictEquals(countAll([0, 1, 2, 3, 4]), 5n), ); diff --git a/@coven/iterables/tests/length.test.ts b/@coven/iterables/tests/length.test.ts index b83cafe..16de7fc 100644 --- a/@coven/iterables/tests/length.test.ts +++ b/@coven/iterables/tests/length.test.ts @@ -5,16 +5,16 @@ import { range } from "../range.ts"; const array = [0, 1, 2]; -Deno.test("Array returns length", () => assertStrictEquals(length(array), 3)); +Deno.test("Array returns length", () => assertStrictEquals(length(array), 3n)); Deno.test("Iterable returns length", () => - assertStrictEquals(length(range(1)(0)(2)), 3), + assertStrictEquals(length(range(1)(0)(2)), 3n), ); Deno.test("Empty array returns 0", () => - assertStrictEquals(length(EMPTY_ARRAY), 0), + assertStrictEquals(length(EMPTY_ARRAY), 0n), ); Deno.test("Empty iterable returns 0", () => - assertStrictEquals(length(Iterator.from(EMPTY_ARRAY)), 0), + assertStrictEquals(length(Iterator.from(EMPTY_ARRAY)), 0n), ); diff --git a/@coven/iterables/tests/take.test.ts b/@coven/iterables/tests/take.test.ts index f91cad4..846acf2 100644 --- a/@coven/iterables/tests/take.test.ts +++ b/@coven/iterables/tests/take.test.ts @@ -1,15 +1,12 @@ import { EMPTY_ARRAY } from "@coven/constants"; +import { repeat } from "@coven/iterables"; import { assertEquals } from "@std/assert"; import { iterableToArray } from "../iterableToArray.ts"; import { take } from "../take.ts"; -const infiniteIterable = function* (item: Item): Iterable { - for (;;) { - yield item; - } -}; -const take2 = take(2); -const takeNone = take(0); +const infiniteIterable = (item: Item) => repeat(Infinity)(item); +const take2 = take(2n); +const takeNone = take(0n); const takeAll = take(Infinity); Deno.test( diff --git a/@coven/math/README.md b/@coven/math/README.md index 393ba77..37d5338 100644 --- a/@coven/math/README.md +++ b/@coven/math/README.md @@ -47,6 +47,18 @@ import { calculate } from "@coven/math"; `${calculate(103_993).over(33_102)}`; // "3.14159265301190260407226149477372968400700863996133164159265301190260407226149477372968400700863996133164159265301190260407226149477372968400700863996133164159265301190260407226149477372968400700863996133164159265301190260407226149477372968400700863996134" 🤯 ``` +Each `calculate` operation returns a new frozen object. This methods don't use +`this` so they can be safely extracted: + +```typescript +import { calculate } from "@coven/math"; + +const { plus } = calculate(0.1); ++plus(0.2); // 0.3 ++plus(0); // 0.1 ++plus(1e20).minus(1e20); // 0.1 +``` + ## Other links - [Coverage](https://app.codecov.io/github/covenengineering/libraries). diff --git a/@coven/math/calculate.ts b/@coven/math/calculate.ts index 68f78b0..a852991 100644 --- a/@coven/math/calculate.ts +++ b/@coven/math/calculate.ts @@ -1,10 +1,11 @@ -import type { Numeric } from "@coven/types"; +import { memoFunction } from "@coven/memo"; +import type { Numeric, Unary } from "@coven/types"; import { createObject } from "@coven/utils"; import type { Calculation } from "./Calculation.ts"; import { numericToPrecise } from "./numericToPrecise.ts"; +import type { Precise } from "./precise.ts"; import { preciseAdd } from "./preciseAdd.ts"; import { preciseDivide } from "./preciseDivide.ts"; -import type { PreciseFunction } from "./PreciseFunction.ts"; import { preciseMultiply } from "./preciseMultiply.ts"; import { preciseSubtract } from "./preciseSubtract.ts"; import { preciseToNumber } from "./preciseToNumber.ts"; @@ -23,33 +24,36 @@ import { preciseToString } from "./preciseToString.ts"; * @see {@linkcode preciseMultiply} * @see {@linkcode preciseSubtract} * @param value Value to run operations on. - * @returns An object with `divideBy`, `minus`, `plus` and `times` methods and a `value` property. + * @returns An object with chainable methods to update the current value. */ -export const calculate = (value: Numeric): Calculation => { - /** @internal Current value (always holds a {@linkcode Precise}). */ - let left = numericToPrecise(value); +export const calculate: Unary<[value: Numeric], Calculation> = memoFunction( + (value) => { + const createCalculation = memoFunction((raw: Precise) => + Object.freeze( + createObject({ + minus: (right: Numeric) => + createCalculation( + preciseSubtract(numericToPrecise(right))(raw), + ), + over: (right: Numeric) => + createCalculation( + preciseDivide(numericToPrecise(right))(raw), + ), + plus: (right: Numeric) => + createCalculation( + preciseAdd(numericToPrecise(right))(raw), + ), + raw, + times: (right: Numeric) => + createCalculation( + preciseMultiply(numericToPrecise(right))(raw), + ), + toString: () => preciseToString(raw), + valueOf: () => preciseToNumber(raw), + }), + ), + ); - /** Curried function to generate the methods for each precise function */ - const method = (preciseFunction: PreciseFunction) => - ({ - get: - () => - (right: Numeric): Calculation => ( - (left = preciseFunction(numericToPrecise(right))(left)), - calculation - ), - }) as const satisfies PropertyDescriptor; - - /** Output object (read only and as lazy as possible) */ - const calculation = Object.defineProperties(createObject(), { - minus: method(preciseSubtract), - over: method(preciseDivide), - plus: method(preciseAdd), - raw: { get: () => left }, - times: method(preciseMultiply), - toString: { value: () => preciseToString(left) }, - valueOf: { value: () => preciseToNumber(left) }, - }) as Calculation; - - return calculation; -}; + return createCalculation(numericToPrecise(value)); + }, +); diff --git a/@coven/math/deno.json b/@coven/math/deno.json index ffd04f0..975c71f 100644 --- a/@coven/math/deno.json +++ b/@coven/math/deno.json @@ -2,5 +2,5 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "exports": "./mod.ts", "name": "@coven/math", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@coven/math/preciseDivide.ts b/@coven/math/preciseDivide.ts index 5c9e8bc..84dda48 100644 --- a/@coven/math/preciseDivide.ts +++ b/@coven/math/preciseDivide.ts @@ -50,6 +50,7 @@ export const preciseDivide: PreciseFunction = memoFunction((divisor) => { dividendCoefficient / divisorCoefficient; let scaledDividend = dividendCoefficient; + // deno-lint-ignore coven/no-for for ( ; -exponent > EXPONENT_MIN diff --git a/@coven/math/preciseRound.ts b/@coven/math/preciseRound.ts index 4bddea4..2ffffbd 100644 --- a/@coven/math/preciseRound.ts +++ b/@coven/math/preciseRound.ts @@ -31,6 +31,7 @@ export const preciseRound: Unary<[precise: Precise], Precise> = memoFunction( } else { let coefficient = getPreciseCoefficient(value); + // deno-lint-ignore coven/no-for for (; exponent < 0n; ) { const rem = coefficient % 10n; coefficient /= 10n; diff --git a/@coven/math/preciseTruncate.ts b/@coven/math/preciseTruncate.ts index 9538fc7..3acd00f 100644 --- a/@coven/math/preciseTruncate.ts +++ b/@coven/math/preciseTruncate.ts @@ -31,6 +31,7 @@ export const preciseTruncate: Unary<[precise: Precise], Precise> = memoFunction( } else { let coefficient = getPreciseCoefficient(value); + // deno-lint-ignore coven/no-for for (; exponent < 0n; ) { coefficient /= 10n; exponent += 1n; diff --git a/@coven/math/tests/getPreciseCoefficient.test.ts b/@coven/math/tests/getPreciseCoefficient.test.ts index e69b190..e287435 100644 --- a/@coven/math/tests/getPreciseCoefficient.test.ts +++ b/@coven/math/tests/getPreciseCoefficient.test.ts @@ -1,3 +1,4 @@ +import { forEach, range } from "@coven/iterables"; import { assertStrictEquals } from "@std/assert"; import { getPreciseCoefficient } from "../getPreciseCoefficient.ts"; import { precise } from "../precise.ts"; @@ -14,9 +15,11 @@ Deno.test("Can get coefficient bondaries from a Precise", () => { checkGetPreciseCoefficient(-1n); }); -Deno.test("Can get coefficient powers of 2 from a Precise", () => { - for (let index = 0n; index < 55n; index++) { - checkGetPreciseCoefficient(2n ** index); - checkGetPreciseCoefficient(-(2n ** index)); - } -}); +Deno.test("Can get coefficient powers of 2 from a Precise", () => + forEach( + (index: bigint) => ( + checkGetPreciseCoefficient(2n ** index), + checkGetPreciseCoefficient(-(2n ** index)) + ), + )(range(1n)(0n)(54n)), +); diff --git a/@coven/math/tests/getPreciseExponent.test.ts b/@coven/math/tests/getPreciseExponent.test.ts index f2f9df7..a16fc98 100644 --- a/@coven/math/tests/getPreciseExponent.test.ts +++ b/@coven/math/tests/getPreciseExponent.test.ts @@ -1,11 +1,12 @@ +import { forEach, range } from "@coven/iterables"; import { assertStrictEquals } from "@std/assert"; import { EXPONENT_MAX } from "../EXPONENT_MAX.ts"; import { EXPONENT_MIN } from "../EXPONENT_MIN.ts"; import { getPreciseExponent } from "../getPreciseExponent.ts"; import { precise } from "../precise.ts"; -Deno.test("Can get all exponents from a Precise", () => { - for (let exponent = EXPONENT_MIN; exponent < EXPONENT_MAX; exponent += 1n) { - assertStrictEquals(getPreciseExponent(precise(1n, exponent)), exponent); - } -}); +Deno.test("Can get all exponents from a Precise", () => + forEach((exponent: bigint) => + assertStrictEquals(getPreciseExponent(precise(1n, exponent)), exponent), + )(range(1n)(EXPONENT_MIN)(EXPONENT_MAX)), +); diff --git a/@coven/math/tests/isPreciseZero.test.ts b/@coven/math/tests/isPreciseZero.test.ts index 50c70af..dcb0203 100644 --- a/@coven/math/tests/isPreciseZero.test.ts +++ b/@coven/math/tests/isPreciseZero.test.ts @@ -1,15 +1,12 @@ +import { forEach, range } from "@coven/iterables"; import { assert } from "@std/assert"; import { EXPONENT_MAX } from "../EXPONENT_MAX.ts"; import { EXPONENT_MIN } from "../EXPONENT_MIN.ts"; import { isPreciseZero } from "../isPreciseZero.ts"; import { precise } from "../precise.ts"; -Deno.test("All 255 possible values of zero are considered zero", () => { - for ( - let exponent = EXPONENT_MIN; - exponent <= EXPONENT_MAX; - exponent += 1n - ) { - assert(isPreciseZero(precise(0n, exponent))); - } -}); +Deno.test("All 255 possible values of zero are considered zero", () => + forEach((exponent: bigint) => assert(isPreciseZero(precise(0n, exponent))))( + range(1n)(EXPONENT_MIN)(EXPONENT_MAX), + ), +); diff --git a/@coven/memo/MemoizableTuple.ts b/@coven/memo/MemoizableTuple.ts index 0ed14e1..d2a680a 100644 --- a/@coven/memo/MemoizableTuple.ts +++ b/@coven/memo/MemoizableTuple.ts @@ -5,4 +5,5 @@ import type { Memoizable } from "./Memoizable.ts"; * * @see {@linkcode Memoizable} */ +// deno-lint-ignore coven/no-array-type export type MemoizableTuple = readonly Memoizable[]; diff --git a/@coven/memo/deno.json b/@coven/memo/deno.json index 4ad26e9..51bb6b3 100644 --- a/@coven/memo/deno.json +++ b/@coven/memo/deno.json @@ -2,5 +2,5 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "exports": "./mod.ts", "name": "@coven/memo", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@coven/memo/tests/memoFunction.test.ts b/@coven/memo/tests/memoFunction.test.ts index f430c9b..adeff4b 100644 --- a/@coven/memo/tests/memoFunction.test.ts +++ b/@coven/memo/tests/memoFunction.test.ts @@ -9,9 +9,7 @@ Deno.test( "Cached double function and several operations duplicated values runs once per value", () => assertStrictEquals( - ([2, 2, 2, 3, 3, 3, 2, 2, 2].map((number) => - memoizedDouble(number), - ), + ([2, 2, 2, 3, 3, 3, 2, 2, 2].map((item) => memoizedDouble(item)), times), 2, ), diff --git a/@coven/pair/deno.json b/@coven/pair/deno.json index 3b57e93..9566796 100644 --- a/@coven/pair/deno.json +++ b/@coven/pair/deno.json @@ -13,5 +13,5 @@ "react-dom": "npm:react-dom@^19.2.6" }, "name": "@coven/pair", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@coven/parsers/deno.json b/@coven/parsers/deno.json index 77dff09..b157e6a 100644 --- a/@coven/parsers/deno.json +++ b/@coven/parsers/deno.json @@ -2,5 +2,5 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "exports": "./mod.ts", "name": "@coven/parsers", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@coven/predicates/between.ts b/@coven/predicates/between.ts index defd122..88fcc9c 100644 --- a/@coven/predicates/between.ts +++ b/@coven/predicates/between.ts @@ -1,4 +1,4 @@ -import type { Numeric } from "@coven/types"; +import type { Filter, Numeric, Unary } from "@coven/types"; /** * Checks if a `value` is between `start` and `end` (inclusive). @@ -18,15 +18,20 @@ import type { Numeric } from "@coven/types"; export const between = ( start: NumericOrString, - ): (( - end: NumericOrString extends string ? string - : NumericOrString extends number ? number - : bigint, - ) => ( - value: NumericOrString extends string ? string - : NumericOrString extends number ? number - : bigint, - ) => boolean) => + ): Unary< + [ + end: NumericOrString extends string ? string + : NumericOrString extends number ? number + : bigint, + ], + Filter< + [ + value: NumericOrString extends string ? string + : NumericOrString extends number ? number + : bigint, + ] + > + > => (end) => (value) => ((start as number) === (end as number) diff --git a/@coven/predicates/deno.json b/@coven/predicates/deno.json index cf09684..4171509 100644 --- a/@coven/predicates/deno.json +++ b/@coven/predicates/deno.json @@ -2,5 +2,5 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "exports": "./mod.ts", "name": "@coven/predicates", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@coven/predicates/isPropertyOf.ts b/@coven/predicates/isPropertyOf.ts index 1423dc1..d4da01e 100644 --- a/@coven/predicates/isPropertyOf.ts +++ b/@coven/predicates/isPropertyOf.ts @@ -1,4 +1,4 @@ -import type { ReadonlyRecord } from "@coven/types"; +import type { Filter, ReadonlyRecord } from "@coven/types"; /** * Check if the given key is present in the given object (not inherited). @@ -15,6 +15,6 @@ import type { ReadonlyRecord } from "@coven/types"; export const isPropertyOf = ( object: ReadonlyRecord, - ): ((key: Key) => boolean) => + ): Filter<[key: Key]> => (key) => Object.hasOwn(object, key); diff --git a/@coven/rules/deno.json b/@coven/rules/deno.json index e35eb41..a084e5d 100644 --- a/@coven/rules/deno.json +++ b/@coven/rules/deno.json @@ -2,5 +2,5 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "exports": "./mod.ts", "name": "@coven/rules", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@coven/rules/mod.ts b/@coven/rules/mod.ts index 5cce040..21a0169 100644 --- a/@coven/rules/mod.ts +++ b/@coven/rules/mod.ts @@ -1,11 +1,13 @@ import { maxLines } from "./max-lines.ts"; +import { noArrayType } from "./no-array-type.ts"; import { noBreak } from "./no-break.ts"; import { noClass } from "./no-class.ts"; import { noContinue } from "./no-continue.ts"; import { noDefaultExport } from "./no-default-export.ts"; import { noDoWhile } from "./no-do-while.ts"; +import { noEarlyReturn } from "./no-early-return.ts"; import { noEnum } from "./no-enum.ts"; -import { noForIn } from "./no-for-in.ts"; +import { noFor } from "./no-for.ts"; import { noFunction } from "./no-function.ts"; import { noNull } from "./no-null.ts"; import { noSwitch } from "./no-switch.ts"; @@ -20,13 +22,15 @@ import { noWhile } from "./no-while.ts"; * * Full list of rules: * - `max-lines`: Allow a max of `300` lines per file. + * - `no-array-type`: Disallow `Type[]` syntax. * - `no-break`: Disallow `break` statements. * - `no-class`: Disallow classes. * - `no-continue`: Disallow `continue` statements. * - `no-default-export`: Disallow `export default` (use named instead). * - `no-do-while`: Disallow `do..while` loops. + * - `no-early-return`: Disallow early `return`s. * - `no-enum`: Disallow `enum`. - * - `no-for-in`: Disallow `for..in` loops. + * - `no-for`: Disallow `for` loops. * - `no-function`: Disallow `function` (use `const` instead). * - `no-null`: Disallow `null` (use `undefined` instead). * - `no-switch`: Disallow `switch`. @@ -47,13 +51,15 @@ export default { name: "coven", rules: { "max-lines": maxLines, + "no-array-type": noArrayType, "no-break": noBreak, "no-class": noClass, "no-continue": noContinue, "no-default-export": noDefaultExport, "no-do-while": noDoWhile, + "no-early-return": noEarlyReturn, "no-enum": noEnum, - "no-for-in": noForIn, + "no-for": noFor, "no-function": noFunction, "no-null": noNull, "no-switch": noSwitch, diff --git a/@coven/rules/no-array-type.ts b/@coven/rules/no-array-type.ts new file mode 100644 index 0000000..ac3448f --- /dev/null +++ b/@coven/rules/no-array-type.ts @@ -0,0 +1,9 @@ +import { no } from "./no.ts"; + +/** + * Rule to avoid the `Type[]` array type syntax. + */ +export const noArrayType: Deno.lint.Rule = no( + "TSArrayType", + "Avoid using `Type[]` array type syntax. Use `ReadonlyArray` instead.", +); diff --git a/@coven/rules/no-class.ts b/@coven/rules/no-class.ts index 0ab2c66..6509668 100644 --- a/@coven/rules/no-class.ts +++ b/@coven/rules/no-class.ts @@ -4,6 +4,6 @@ import { no } from "./no.ts"; * Rule to avoid classes. */ export const noClass: Deno.lint.Rule = no( - "ClassExpression", + ["ClassExpression", "ClassDeclaration"], "Avoid using `class`. Use a function instead.", ); diff --git a/@coven/rules/no-early-return.ts b/@coven/rules/no-early-return.ts new file mode 100644 index 0000000..81fffa6 --- /dev/null +++ b/@coven/rules/no-early-return.ts @@ -0,0 +1,47 @@ +/** + * Gets all the `return`s in the given `block`. + */ +const getAllBlockReturns = ( + block: Deno.lint.BlockStatement, +): ReadonlyArray => + block.body + ?.flatMap((statement) => + "body" in statement && Array.isArray(statement.body) ? + getAllBlockReturns(statement as Deno.lint.BlockStatement) + : statement, + ) + .filter((statement) => statement.type === "ReturnStatement"); + +/** + * Gets all the `return`s in the given `node`. + */ +const getAllFunctionReturns = ( + node: Extract>, +): ReadonlyArray => + "parent" in node && node.parent.type !== "TSParameterProperty" ? + ( + (node.parent.type === "ArrowFunctionExpression" + || node.parent.type === "FunctionDeclaration" + || node.parent.type === "FunctionExpression") + && node.parent.body?.type === "BlockStatement" + ) ? + getAllBlockReturns(node.parent.body) + : "parent" in node.parent ? getAllFunctionReturns(node.parent) + : [] + : []; + +/** + * Rule to avoid early `return` statements. + */ +export const noEarlyReturn = { + create: (context): Deno.lint.LintVisitor => ({ + ReturnStatement: (node): void => + (getAllFunctionReturns(node).at(-1) ?? node) === node ? + undefined + : context.report({ + message: + "Avoid early `return`. Use a single `return` instead.", + node, + }), + }), +} as const satisfies Deno.lint.Rule; diff --git a/@coven/rules/no-for-in.ts b/@coven/rules/no-for-in.ts deleted file mode 100644 index 9cdfda3..0000000 --- a/@coven/rules/no-for-in.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { no } from "./no.ts"; - -/** - * Rule to avoid `for..in` structures. - */ -export const noForIn: Deno.lint.Rule = no( - "ForInStatement", - "Avoid using `for..in`. Use utils from `@coven/iterables` or any modern looping method.", -); diff --git a/@coven/rules/no-for.ts b/@coven/rules/no-for.ts new file mode 100644 index 0000000..c481760 --- /dev/null +++ b/@coven/rules/no-for.ts @@ -0,0 +1,9 @@ +import { no } from "./no.ts"; + +/** + * Rule to avoid `for` loops. + */ +export const noFor: Deno.lint.Rule = no( + "ForStatement", + "Avoid using `for`. Use utils from `@coven/iterables` or any modern looping method.", +); diff --git a/@coven/rules/no.ts b/@coven/rules/no.ts index 37783db..339d36a 100644 --- a/@coven/rules/no.ts +++ b/@coven/rules/no.ts @@ -4,18 +4,22 @@ import type { DenoLintContext } from "./DenoLintContext.ts"; * Shorthand for common "Avoid this, do that." lintinrg rules. */ export const no = ( - visitor: Visitor, + visitors: Visitor | ReadonlyArray, message: string, condition?: (data: { context: Deno.lint.RuleContext; node: DenoLintContext; }) => boolean, ): Deno.lint.Rule => ({ - create: (context): Deno.lint.LintVisitor => ({ - [visitor]: (node: DenoLintContext): void => { - (condition?.({ context, node }) ?? true) ? - context.report({ message, node }) - : undefined; - }, - }), + create: (context): Deno.lint.LintVisitor => + Object.fromEntries( + (Array.isArray(visitors) ? visitors : [visitors]).map((visitor) => [ + visitor, + (node: DenoLintContext): void => { + (condition?.({ context, node }) ?? true) ? + context.report({ message, node }) + : undefined; + }, + ]), + ), }); diff --git a/@coven/template/deno.json b/@coven/template/deno.json index 344c7b9..338b925 100644 --- a/@coven/template/deno.json +++ b/@coven/template/deno.json @@ -2,5 +2,5 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "exports": "./mod.ts", "name": "@coven/template", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@coven/terminal/deno.json b/@coven/terminal/deno.json index 409f9fb..e3bc61c 100644 --- a/@coven/terminal/deno.json +++ b/@coven/terminal/deno.json @@ -2,5 +2,5 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "exports": "./mod.ts", "name": "@coven/terminal", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@coven/terminal/mix.ts b/@coven/terminal/mix.ts index db82f51..2f8ed22 100644 --- a/@coven/terminal/mix.ts +++ b/@coven/terminal/mix.ts @@ -1,5 +1,5 @@ +import { reduce } from "@coven/iterables"; import type { Formatter } from "./Formatter.ts"; - /** * Formatter composer wrapper. * @@ -31,7 +31,7 @@ export const mix = * @returns Formatted string. */ (input, ...expressions) => - formatters.reduce( - (output, formatter) => formatter(output, ...expressions), - input, - ) as string; + reduce( + (formatter: Formatter) => (output: typeof input) => + formatter(output, ...expressions) as typeof input, + )(input)(formatters) as string; diff --git a/@coven/terminal/normalizeString.ts b/@coven/terminal/normalizeString.ts index 421bc8f..131a6c4 100644 --- a/@coven/terminal/normalizeString.ts +++ b/@coven/terminal/normalizeString.ts @@ -1,3 +1,4 @@ +import { append, flat, join, zip } from "@coven/iterables"; import { memoFunction } from "@coven/memo"; import type { Fallback, @@ -5,6 +6,9 @@ import type { Stringable, } from "@coven/types"; +const appendEmpty = append([""]); +const joinEmpty = join(""); + /** * Takes a string or a template string and returns a plain string. * @@ -29,10 +33,6 @@ export const normalizeString: < ...expressions: ReadonlyArray ): Fallback => (typeof input === "string" ? input : ( - input.reduce( - (output, string, index) => - `${output}${string}${expressions[index] ?? ""}`, - "", - ) + joinEmpty(flat(zip(input)(appendEmpty(expressions)))) )) as Fallback, ); diff --git a/@coven/types/deno.json b/@coven/types/deno.json index 6d4a787..b2d50ad 100644 --- a/@coven/types/deno.json +++ b/@coven/types/deno.json @@ -2,5 +2,5 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "exports": "./mod.ts", "name": "@coven/types", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@coven/utils/deno.json b/@coven/utils/deno.json index c1faeb9..214c19c 100644 --- a/@coven/utils/deno.json +++ b/@coven/utils/deno.json @@ -2,5 +2,5 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "exports": "./mod.ts", "name": "@coven/utils", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@simulcast/core/deno.json b/@simulcast/core/deno.json index f9c845e..dc754ef 100644 --- a/@simulcast/core/deno.json +++ b/@simulcast/core/deno.json @@ -2,5 +2,5 @@ "$schema": "https://raw.githubusercontent.com/denoland/deno/main/cli/schemas/config-file.v1.json", "exports": "./mod.ts", "name": "@simulcast/core", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@simulcast/preact/deno.json b/@simulcast/preact/deno.json index 14e78a7..9f60646 100644 --- a/@simulcast/preact/deno.json +++ b/@simulcast/preact/deno.json @@ -11,5 +11,5 @@ "preact": "npm:preact@^10.29.1" }, "name": "@simulcast/preact", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@simulcast/react/deno.json b/@simulcast/react/deno.json index dfb4da7..543b57f 100644 --- a/@simulcast/react/deno.json +++ b/@simulcast/react/deno.json @@ -12,5 +12,5 @@ "react": "npm:react@^19.2.6" }, "name": "@simulcast/react", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/@simulcast/vue/deno.json b/@simulcast/vue/deno.json index 98d76d4..c69a6c1 100644 --- a/@simulcast/vue/deno.json +++ b/@simulcast/vue/deno.json @@ -6,5 +6,5 @@ "vue": "npm:vue@^3.5.34" }, "name": "@simulcast/vue", - "version": "0.9.7" + "version": "0.9.8" } diff --git a/deno.json b/deno.json index 8382163..f4fd5b4 100644 --- a/deno.json +++ b/deno.json @@ -95,6 +95,10 @@ "test-coverage": { "command": "deno test --ignore-env --coverage --doc --import=./setupTests.ts --parallel --quiet", "description": "Run tests generating coverage (even for documentation)" + }, + "version-bump": { + "command": "deno --allow-read=. --allow-write=. ./scripts/version-bump.ts", + "description": "Bump version of all packages" } }, "workspace": [ diff --git a/scripts/version-bump.ts b/scripts/version-bump.ts new file mode 100644 index 0000000..f2f3c55 --- /dev/null +++ b/scripts/version-bump.ts @@ -0,0 +1,39 @@ +const getDenoJSON = function* (path = "."): Generator { + for (const dirEntry of Deno.readDirSync(path)) { + const fullPath = `${path}/${dirEntry.name}`; + if (dirEntry.isDirectory) { + yield* getDenoJSON(fullPath); + } else { + if (dirEntry.isFile && dirEntry.name === "deno.json") { + yield fullPath; + } + } + } +}; + +const textDecoder = new TextDecoder("utf-8"); +const textEncoder = new TextEncoder(); +let newVersion: string | undefined; + +for (const file of getDenoJSON()) { + const content = textDecoder.decode(Deno.readFileSync(file)); + + if (content.includes('"version": "')) { + const [, version = "UNKNOWN"] = + /"version": "(?(?:\d+\.){2}\d+)"/.exec(content) ?? []; + + Deno.writeFileSync( + file, + textEncoder.encode( + content.replace( + `"${version}"`, + `"${ + newVersion + ?? (newVersion = + prompt("New version?", version) ?? version) + }"`, + ), + ), + ); + } +}