diff --git a/README.md b/README.md index 06ae4eb..36c9f11 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,10 @@ deno add jsr:@bytebury/toolkit ## Sample Usage ```ts +import { average, isNoneOrWhitespace, title } from "@bytebury/toolkit"; + function sayHelloTo(name: string): void { - if ((isWhitespace(name)) { + if (isNoneOrWhitespace(name)) { console.log("Hello, Guest!"); } else { console.log(`Hello, ${title(name)}!`); @@ -47,3 +49,10 @@ function getAverageAge(): number { return average(people.map(({ age }) => age)); // 5 } ``` + +## Naming Notes + +Helpers such as `isWhitespace` and `isEmpty` are already `null` and `undefined` +safe. The more explicit `isNoneOrWhitespace`, `isNoneOrEmpty`, +`isNotNoneOrWhitespace`, and `isNotNoneOrEmpty` aliases are available when that +behavior should be visible at the call site. diff --git a/deno.json b/deno.json index f34c9e7..10d2603 100644 --- a/deno.json +++ b/deno.json @@ -1,25 +1,25 @@ { - "name": "@bytebury/toolkit", - "version": "2.0.0", - "exports": { - ".": "./mod.ts", - "./core": "./src/core.ts", - "./dates": "./src/dates.ts", - "./duration": "./src/duration.ts", - "./numbers": "./src/numbers.ts", - "./objects": "./src/objects.ts", - "./strings": "./src/strings.ts", - "./utility-types": "./src/utility_types.ts" - }, - "tasks": { - "dev": "deno test --watch" - }, - "license": "MIT", - "imports": { - "@std/assert": "jsr:@std/assert@1" - }, - "compilerOptions": { - "lib": ["dom", "dom.iterable", "deno.ns"] - }, - "exclude": ["deno.lock", "scripts/**", "npm/**"] + "name": "@bytebury/toolkit", + "version": "2.1.0", + "exports": { + ".": "./mod.ts", + "./core": "./src/core.ts", + "./dates": "./src/dates.ts", + "./duration": "./src/duration.ts", + "./numbers": "./src/numbers.ts", + "./objects": "./src/objects.ts", + "./strings": "./src/strings.ts", + "./utility-types": "./src/utility_types.ts" + }, + "tasks": { + "dev": "deno test --watch" + }, + "license": "MIT", + "imports": { + "@std/assert": "jsr:@std/assert@1" + }, + "compilerOptions": { + "lib": ["dom", "dom.iterable", "deno.ns"] + }, + "exclude": ["deno.lock", "scripts/**", "npm/**"] } diff --git a/src/core.ts b/src/core.ts index 6e20bee..7b773ca 100644 --- a/src/core.ts +++ b/src/core.ts @@ -189,7 +189,7 @@ export function reverse( ): string | Set | T[] { if (typeof thing === "string") return thing.split("").reverse().join(""); if (thing instanceof Set) return new Set([...thing].reverse()); - return thing.reverse(); + return [...thing].reverse(); } /** @@ -220,6 +220,13 @@ export function isEmpty(thing: unknown): boolean { return false; } +/** + * Alias for `isEmpty`. + */ +export function isNoneOrEmpty(thing: unknown): boolean { + return isEmpty(thing); +} + /** * Determines if the given thing is not empty. * @@ -240,6 +247,13 @@ export function isNotEmpty(thing: unknown): boolean { return !isEmpty(thing); } +/** + * Alias for `isNotEmpty`. + */ +export function isNotNoneOrEmpty(thing: unknown): boolean { + return isNotEmpty(thing); +} + /** * Returns the distinct values from a list. * @@ -328,7 +342,7 @@ export function falsy(thing: unknown): boolean { * isSome({}); // true * ``` */ -export function isSome(thing: unknown): boolean { +export function isSome(thing: T | null | undefined): thing is T { return !isNone(thing); } @@ -343,7 +357,7 @@ export function isSome(thing: unknown): boolean { * isNone(0); // false * ``` */ -export function isNone(thing: unknown): boolean { +export function isNone(thing: unknown): thing is null | undefined { return thing === null || thing === undefined; } diff --git a/src/core_test.ts b/src/core_test.ts index b6de662..15f442b 100644 --- a/src/core_test.ts +++ b/src/core_test.ts @@ -24,9 +24,11 @@ import { isEqual, isEqualIgnoreCase, isNone, + isNoneOrEmpty, isNotEmpty, isNotEqual, isNotEqualIgnoreCase, + isNotNoneOrEmpty, isSome, last, move, @@ -169,7 +171,10 @@ Deno.test("reverse strings", () => { }); Deno.test("reverse arrays", () => { - assertEquals(reverse([1, 2, 3]), [3, 2, 1]); + const list = [1, 2, 3]; + + assertEquals(reverse(list), [3, 2, 1]); + assertEquals(list, [1, 2, 3]); }); Deno.test("reverse sets", () => { @@ -205,6 +210,19 @@ Deno.test("isEmpty Set and Map", () => { assertFalse(isEmpty(new Map([["key", "value"]]))); }); +Deno.test("isNoneOrEmpty behavior", () => { + assert(isNoneOrEmpty(null)); + assert(isNoneOrEmpty(undefined)); + assert(isNoneOrEmpty("")); + assert(isNoneOrEmpty([])); + assert(isNoneOrEmpty({})); + assert(isNoneOrEmpty(new Set())); + assert(isNoneOrEmpty(new Map())); + assertFalse(isNoneOrEmpty(" ")); + assertFalse(isNoneOrEmpty([1])); + assertFalse(isNoneOrEmpty({ foo: "bar" })); +}); + Deno.test("isNotEmpty behavior", () => { assertFalse(isNotEmpty(null)); assertFalse(isNotEmpty("")); @@ -217,6 +235,19 @@ Deno.test("isNotEmpty behavior", () => { assertFalse(isNotEmpty(new Set())); }); +Deno.test("isNotNoneOrEmpty behavior", () => { + assertFalse(isNotNoneOrEmpty(null)); + assertFalse(isNotNoneOrEmpty(undefined)); + assertFalse(isNotNoneOrEmpty("")); + assertFalse(isNotNoneOrEmpty([])); + assertFalse(isNotNoneOrEmpty({})); + assertFalse(isNotNoneOrEmpty(new Set())); + assertFalse(isNotNoneOrEmpty(new Map())); + assert(isNotNoneOrEmpty(" ")); + assert(isNotNoneOrEmpty([1])); + assert(isNotNoneOrEmpty({ foo: "bar" })); +}); + Deno.test("unique produces distinct values", () => { assertEquals(unique([1, 2, 2, 3]), [1, 2, 3]); assertEquals(unique(["a", "b", "a"]), ["a", "b"]); @@ -305,6 +336,15 @@ Deno.test("isSome", () => { assert(isSome([])); }); +Deno.test("isSome narrows the type", () => { + const value: string | null | undefined = "hello"; + + if (isSome(value)) { + const text: string = value; + assertStrictEquals(text.toUpperCase(), "HELLO"); + } +}); + Deno.test("isNone", () => { assert(isNone(null)); assert(isNone(undefined)); @@ -316,6 +356,15 @@ Deno.test("isNone", () => { assertFalse(isNone([])); }); +Deno.test("isNone narrows the type", () => { + const value: string | null | undefined = null; + + if (isNone(value)) { + const none: null | undefined = value; + assertStrictEquals(none, null); + } +}); + Deno.test("noop behavior", () => { noop(); }); diff --git a/src/strings.ts b/src/strings.ts index d3a1076..3eee507 100644 --- a/src/strings.ts +++ b/src/strings.ts @@ -18,6 +18,13 @@ export function isWhitespace(text: string): boolean { return trim(text).length === 0; } +/** + * Alias for `isWhitespace`. + */ +export function isNoneOrWhitespace(text: string): boolean { + return isWhitespace(text); +} + /** * Determines if the given text contains any non-whitespace characters. * @@ -35,6 +42,13 @@ export function isNotWhitespace(text: string): boolean { return !isWhitespace(text); } +/** + * Alias for `isNotWhitespace`. + */ +export function isNotNoneOrWhitespace(text: string): boolean { + return isNotWhitespace(text); +} + /** * Trims the whitespace from the beginning and the end. This is an * alias for `.trim()`. Useful for when you're mapping over lists. diff --git a/src/strings_test.ts b/src/strings_test.ts index a247c82..539e252 100644 --- a/src/strings_test.ts +++ b/src/strings_test.ts @@ -1,6 +1,8 @@ import { assert } from "@std/assert"; import { camel, + isNoneOrWhitespace, + isNotNoneOrWhitespace, isNotWhitespace, isWhitespace, kebab, @@ -27,6 +29,16 @@ Deno.test("isWhitespace", () => { assert(!isWhitespace("Hello")); }); +Deno.test("isNoneOrWhitespace", () => { + assert(isNoneOrWhitespace(" ")); + assert(isNoneOrWhitespace(null as unknown as string)); + assert(isNoneOrWhitespace(undefined as unknown as string)); + assert(isNoneOrWhitespace("")); + assert(isNoneOrWhitespace("\t")); + assert(isNoneOrWhitespace("\n")); + assert(!isNoneOrWhitespace("Hello")); +}); + Deno.test("isNotWhitespace", () => { assert(!isNotWhitespace(" ")); assert(!isNotWhitespace("\t")); @@ -34,6 +46,16 @@ Deno.test("isNotWhitespace", () => { assert(isNotWhitespace("Hello")); }); +Deno.test("isNotNoneOrWhitespace", () => { + assert(!isNotNoneOrWhitespace(" ")); + assert(!isNotNoneOrWhitespace(null as unknown as string)); + assert(!isNotNoneOrWhitespace(undefined as unknown as string)); + assert(!isNotNoneOrWhitespace("")); + assert(!isNotNoneOrWhitespace("\t")); + assert(!isNotNoneOrWhitespace("\n")); + assert(isNotNoneOrWhitespace("Hello")); +}); + Deno.test("trim", () => { assert(trim(" Hello, World! ") === "Hello, World!"); assert(trim("\nHey\n\t") === "Hey");