diff --git a/.github/workflows/bump-workspace.yml b/.github/workflows/bump-workspace.yml index 3f4e3f6..4f726d4 100644 --- a/.github/workflows/bump-workspace.yml +++ b/.github/workflows/bump-workspace.yml @@ -23,8 +23,3 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GIT_USER_NAME: adjsky GIT_USER_EMAIL: igorlfmartins@mail.ru - - - run: deno fmt - - uses: stefanzweifel/git-auto-commit-action@v5 - with: - commit_message: format changes diff --git a/.vscode/settings.json b/.vscode/settings.json index 404d1c7..16e8af3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { - "deno.enable": true, - "editor.defaultFormatter": "denoland.vscode-deno", - "[ignore]": { - "editor.defaultFormatter": "foxundermoon.shell-format" - } + "deno.enable": true, + "editor.defaultFormatter": "denoland.vscode-deno", + "[ignore]": { + "editor.defaultFormatter": "foxundermoon.shell-format" + } } diff --git a/core/build_npm.ts b/core/build_npm.ts index 66537f1..6e6db1d 100644 --- a/core/build_npm.ts +++ b/core/build_npm.ts @@ -4,28 +4,28 @@ import denoJson from "./deno.json" with { type: "json" }; await emptyDir("./npm"); await build({ - entryPoints: ["./mod.ts"], - outDir: "./npm", - shims: {}, - test: false, - package: { - name: "resulto", - version: denoJson.version, - description: - "TypeScript implementation of the Result type from Rust with async support", - author: "adjsky", - repository: { - "type": "git", - "url": "git+https://github.com/adjsky/resulto.git", - }, - keywords: [ - "result", - "rust", - ], - license: "MIT", - }, - postBuild() { - Deno.copyFileSync("../LICENSE", "npm/LICENSE"); - Deno.copyFileSync("README.md", "npm/README.md"); - }, + entryPoints: ["./mod.ts"], + outDir: "./npm", + shims: {}, + test: false, + package: { + name: "resulto", + version: denoJson.version, + description: + "TypeScript implementation of the Result type from Rust with async support", + author: "adjsky", + repository: { + "type": "git", + "url": "git+https://github.com/adjsky/resulto.git", + }, + keywords: [ + "result", + "rust", + ], + license: "MIT", + }, + postBuild() { + Deno.copyFileSync("../LICENSE", "npm/LICENSE"); + Deno.copyFileSync("README.md", "npm/README.md"); + }, }); diff --git a/core/chain.js b/core/chain.js index 1627fa7..9f4bd58 100644 --- a/core/chain.js +++ b/core/chain.js @@ -3,38 +3,38 @@ * @returns {*} */ export function chain(chainHead) { - let currentPromise = Promise.resolve(chainHead); - let currentThis; + let currentPromise = Promise.resolve(chainHead); + let currentThis; - const proxy = new Proxy(function () {}, { - get(_, property) { - switch (property) { - case "then": - case "catch": - case "finally": - return (...args) => currentPromise[property](...args); - default: - currentPromise = currentPromise.then((resolvedObject) => { - currentThis = resolvedObject; + const proxy = new Proxy(function () {}, { + get(_, property) { + switch (property) { + case "then": + case "catch": + case "finally": + return (...args) => currentPromise[property](...args); + default: + currentPromise = currentPromise.then((resolvedObject) => { + currentThis = resolvedObject; - return resolvedObject?.[property]; - }); + return resolvedObject?.[property]; + }); - return proxy; - } - }, - apply(_, __, args) { - currentPromise = currentPromise.then((resolvedFunction) => { - if (typeof resolvedFunction != "function") { - throw new Error(`Trying to call ${resolvedFunction}`); - } + return proxy; + } + }, + apply(_, __, args) { + currentPromise = currentPromise.then((resolvedFunction) => { + if (typeof resolvedFunction != "function") { + throw new Error(`Trying to call ${resolvedFunction}`); + } - return Reflect.apply(resolvedFunction, currentThis, args); - }); + return Reflect.apply(resolvedFunction, currentThis, args); + }); - return proxy; - }, - }); + return proxy; + }, + }); - return proxy; + return proxy; } diff --git a/core/deno.json b/core/deno.json index b3bd236..16bb9e6 100644 --- a/core/deno.json +++ b/core/deno.json @@ -1,7 +1,7 @@ { - "name": "@resulto/core", - "version": "1.4.0", - "exports": { - ".": "./mod.ts" - } + "name": "@resulto/core", + "version": "1.4.0", + "exports": { + ".": "./mod.ts" + } } diff --git a/core/mod.ts b/core/mod.ts index bd54e15..5ec4390 100644 --- a/core/mod.ts +++ b/core/mod.ts @@ -1,23 +1,23 @@ export { - type AsyncResult, - combine, - combineAsync, - Err, - err, - errAsync, - type ErrFn, - type ErrPredicate, - type Fn, - fromPromise, - fromSafePromise, - fromThrowable, - Ok, - ok, - okAsync, - type Predicate, - type Result, - type ResultDeclarations, - ResultError, - type UnwrapErrs, - type UnwrapOks, + type AsyncResult, + combine, + combineAsync, + Err, + err, + errAsync, + type ErrFn, + type ErrPredicate, + type Fn, + fromPromise, + fromSafePromise, + fromThrowable, + Ok, + ok, + okAsync, + type Predicate, + type Result, + type ResultDeclarations, + ResultError, + type UnwrapErrs, + type UnwrapOks, } from "./result.ts"; diff --git a/core/result.ts b/core/result.ts index 9a1a927..2550c24 100644 --- a/core/result.ts +++ b/core/result.ts @@ -7,277 +7,277 @@ export type Fn = (value: T) => U; export type ErrFn = (error: E) => F; export type UnwrapOks< - T extends readonly ( - | Result - | AsyncResult - )[], + T extends readonly ( + | Result + | AsyncResult + )[], > = { - [i in keyof T]: T[i] extends Result ? U - : T[i] extends AsyncResult ? U - : never; + [i in keyof T]: T[i] extends Result ? U + : T[i] extends AsyncResult ? U + : never; }; export type UnwrapErrs< - T extends readonly ( - | Result - | AsyncResult - )[], + T extends readonly ( + | Result + | AsyncResult + )[], > = { - [i in keyof T]: T[i] extends Result ? U - : T[i] extends AsyncResult ? U - : never; + [i in keyof T]: T[i] extends Result ? U + : T[i] extends AsyncResult ? U + : never; }; export type Mutable = { - -readonly [P in keyof T]: T[P]; + -readonly [P in keyof T]: T[P]; }; export class ResultError extends Error { - data: unknown; + data: unknown; - constructor(message: string, data: unknown) { - super(message); + constructor(message: string, data: unknown) { + super(message); - this.data = data; - } + this.data = data; + } } export interface ResultDeclarations { - /** - * Checks if `Result` is `Ok`. - */ - isOk(): this is Ok; - - /** - * Checks if `Result` is `Ok` and the value inside of it matches a predicate. - */ - isOkAnd(f: Predicate): boolean; - - /** - * Checks if `Result` is `Err`. - */ - isErr(): this is Err; - - /** - * Checks if `Result` is `Err` and the value inside of it matches a predicate. - */ - isErrAnd(f: ErrPredicate): boolean; - - /** - * Maps a `Result` to `Result` by applying a function `f` to a - * contained `Ok` value, leaving an `Err` value untouched. - * - * This function can be used to compose the results of two functions. - */ - map(f: Fn): Result; - - /** - * Maps an `AsyncResult` to `AsyncResult` by applying a function - * `f` to a contained `Ok` value, leaving an `Err` value untouched. - * - * This function can be used to compose the results of two functions. - * - * Use this method instead of {@link ResultDeclarations.map} when the provided - * `f` returns promise. - */ - asyncMap(f: Fn>): AsyncResult; - - /** - * Returns the provided `value` (if `Err`), or applies a function to the - * contained value (if `Ok`). - */ - mapOr(value: U, f: Fn): U; - - /** - * Returns the provided `value` (if `Err`), or applies a function to the - * contained value (if `Ok`). - * - * Use this method instead of {@link ResultDeclarations.mapOr} when the - * provided `f` returns promise. - */ - asyncMapOr(value: U, f: Fn>): Promise; - - /** - * Maps a `Result` to `U` by applying function `fallbackFn` to a - * contained `Err` value, or function `f` to a contained `Ok` value. - * - * This function can be used to unpack a successful result while handling an - * error. - */ - mapOrElse(fallbackFn: ErrFn, f: Fn): U; - - /** - * Maps an `AsyncResult` to `Promise` by applying function - * `fallbackFn` to a contained `Err` value, or function `f` to a contained - * `Ok` value. - * - * This function can be used to unpack a successful result while handling an - * error. - * - * Use this method instead of {@link ResultDeclarations.mapOrElse} when the - * provided `fallbackFn` or `f` return promise. - */ - asyncMapOrElse( - fallbackFn: ErrFn>, - f: Fn>, - ): Promise; - - /** - * Maps a `Result` to `Result` by applying a function to a - * contained `Err` value, leaving an `Ok` value untouched. - * - * This function can be used to pass through a successful result while - * handling an error. - */ - mapErr(f: ErrFn): Result; - - /** - * Maps an `AsyncResult` to `AsyncResult` by applying a function - * to a contained `Err` value, leaving an `Ok` value untouched. - * - * This function can be used to pass through a successful result while - * handling an error. - * - * Use this method instead of {@link ResultDeclarations.mapErr} when the - * provided `f` returns promise. - */ - asyncMapErr(f: ErrFn>): AsyncResult; - - /** - * Calls the provided function `f` with the contained value (if `Ok`). - */ - inspect(f: Fn): Result; - - /** - * Calls the provided function `f` with the contained error (if `Err`). - */ - inspectErr(f: ErrFn): Result; - - /** - * Performs a side effect on the contained value (if `Ok`). - * - * NOTE: `f` is awaited. - */ - tap(f: Fn>): AsyncResult; - - /** - * Performs a side effect on the contained error (if `Err`). - * - * NOTE: `f` is awaited. - */ - tapErr(f: Fn>): AsyncResult; - - /** - * Returns the contained `Ok` value. - * - * This function may throw `UnwrapError` (if `Err`). - */ - expect(msg: string): T; - - /** - * Returns the contained `Ok` value. - * - * This function may throw `UnwrapError` (if `Err`). - */ - unwrap(): T; - - /** - * Returns the contained `Err` value. - * - * This function may throw `UnwrapError` (if `Ok`). - */ - expectErr(msg: string): E; - - /** - * Returns the contained `Err` value. - * - * This function may throw `UnwrapError` (if `Ok`). - */ - unwrapErr(): E; - - /** - * Returns `res` if the result is `Ok`, otherwise returns the `Err` value. - */ - and(res: Result): Result; - - /** - * Calls `f` if the result is `Ok`, otherwise returns the `Err` value. - * - * This function can be used for control flow based on `Result` values. - */ - andThen(f: Fn>): Result; - - /** - * Calls `f` if the result is `Ok`, otherwise returns the `Err` value. - * - * This function can be used for control flow based on `AsyncResult` values. - * - * Use this method instead of {@link ResultDeclarations.andThen} when the - * provided `f` returns promise. - */ - asyncAndThen(f: Fn>>): AsyncResult; - - /** - * Returns `res` if the result is `Err`, otherwise returns the `Ok` value. - */ - or(res: Result): Result; - - /** - * Calls `f` if the result is `Err`, otherwise returns the `Ok` value. - * - * This function can be used for control flow based on `Result` values. - */ - orElse(f: ErrFn>): Result; - - /** - * Calls `f` if the result is `Err`, otherwise returns the `Ok` value. - * - * This function can be used for control flow based on `AsyncResult` values. - * - * Use this method instead of {@link ResultDeclarations.orElse} when the - * provided `f` returns promise. - */ - asyncOrElse( - f: ErrFn>>, - ): AsyncResult; - - /** - * Returns the contained `Ok` value or a provided `value`. - */ - unwrapOr(value: U): U | T; - - /** - * Returns the contained `Ok `value or computes it from a `f`. - */ - unwrapOrElse(f: ErrFn): U | T; - - /** - * Returns the contained `Ok `value or computes it from a `f`. - * - * Use this method instead of {@link ResultDeclarations.unwrapOrElse} when the - * provided `f` returns promise. - */ - asyncUnwrapOrElse(f: ErrFn>): Promise; - - /** - * Calls `okFn` if the result is `Ok`, otherwise calls `errFn`. - * - * Both `okFn` and `errFn` must have the same return type. - */ - match(okFn: Fn, errFn: ErrFn): U; - - /** - * Calls `okFn` if the result is `Ok`, otherwise calls `errFn`. - * - * Both `okFn` and `errFn` must have the same return type. - * - * Use this method instead of {@link ResultDeclarations.match} when the - * provided `okFn` or `errFn` return promise. - */ - asyncMatch( - okFn: Fn>, - errFn: ErrFn>, - ): Promise; + /** + * Checks if `Result` is `Ok`. + */ + isOk(): this is Ok; + + /** + * Checks if `Result` is `Ok` and the value inside of it matches a predicate. + */ + isOkAnd(f: Predicate): boolean; + + /** + * Checks if `Result` is `Err`. + */ + isErr(): this is Err; + + /** + * Checks if `Result` is `Err` and the value inside of it matches a predicate. + */ + isErrAnd(f: ErrPredicate): boolean; + + /** + * Maps a `Result` to `Result` by applying a function `f` to a + * contained `Ok` value, leaving an `Err` value untouched. + * + * This function can be used to compose the results of two functions. + */ + map(f: Fn): Result; + + /** + * Maps an `AsyncResult` to `AsyncResult` by applying a function + * `f` to a contained `Ok` value, leaving an `Err` value untouched. + * + * This function can be used to compose the results of two functions. + * + * Use this method instead of {@link ResultDeclarations.map} when the provided + * `f` returns promise. + */ + asyncMap(f: Fn>): AsyncResult; + + /** + * Returns the provided `value` (if `Err`), or applies a function to the + * contained value (if `Ok`). + */ + mapOr(value: U, f: Fn): U; + + /** + * Returns the provided `value` (if `Err`), or applies a function to the + * contained value (if `Ok`). + * + * Use this method instead of {@link ResultDeclarations.mapOr} when the + * provided `f` returns promise. + */ + asyncMapOr(value: U, f: Fn>): Promise; + + /** + * Maps a `Result` to `U` by applying function `fallbackFn` to a + * contained `Err` value, or function `f` to a contained `Ok` value. + * + * This function can be used to unpack a successful result while handling an + * error. + */ + mapOrElse(fallbackFn: ErrFn, f: Fn): U; + + /** + * Maps an `AsyncResult` to `Promise` by applying function + * `fallbackFn` to a contained `Err` value, or function `f` to a contained + * `Ok` value. + * + * This function can be used to unpack a successful result while handling an + * error. + * + * Use this method instead of {@link ResultDeclarations.mapOrElse} when the + * provided `fallbackFn` or `f` return promise. + */ + asyncMapOrElse( + fallbackFn: ErrFn>, + f: Fn>, + ): Promise; + + /** + * Maps a `Result` to `Result` by applying a function to a + * contained `Err` value, leaving an `Ok` value untouched. + * + * This function can be used to pass through a successful result while + * handling an error. + */ + mapErr(f: ErrFn): Result; + + /** + * Maps an `AsyncResult` to `AsyncResult` by applying a function + * to a contained `Err` value, leaving an `Ok` value untouched. + * + * This function can be used to pass through a successful result while + * handling an error. + * + * Use this method instead of {@link ResultDeclarations.mapErr} when the + * provided `f` returns promise. + */ + asyncMapErr(f: ErrFn>): AsyncResult; + + /** + * Calls the provided function `f` with the contained value (if `Ok`). + */ + inspect(f: Fn): Result; + + /** + * Calls the provided function `f` with the contained error (if `Err`). + */ + inspectErr(f: ErrFn): Result; + + /** + * Performs a side effect on the contained value (if `Ok`). + * + * NOTE: `f` is awaited. + */ + tap(f: Fn>): AsyncResult; + + /** + * Performs a side effect on the contained error (if `Err`). + * + * NOTE: `f` is awaited. + */ + tapErr(f: Fn>): AsyncResult; + + /** + * Returns the contained `Ok` value. + * + * This function may throw `UnwrapError` (if `Err`). + */ + expect(msg: string): T; + + /** + * Returns the contained `Ok` value. + * + * This function may throw `UnwrapError` (if `Err`). + */ + unwrap(): T; + + /** + * Returns the contained `Err` value. + * + * This function may throw `UnwrapError` (if `Ok`). + */ + expectErr(msg: string): E; + + /** + * Returns the contained `Err` value. + * + * This function may throw `UnwrapError` (if `Ok`). + */ + unwrapErr(): E; + + /** + * Returns `res` if the result is `Ok`, otherwise returns the `Err` value. + */ + and(res: Result): Result; + + /** + * Calls `f` if the result is `Ok`, otherwise returns the `Err` value. + * + * This function can be used for control flow based on `Result` values. + */ + andThen(f: Fn>): Result; + + /** + * Calls `f` if the result is `Ok`, otherwise returns the `Err` value. + * + * This function can be used for control flow based on `AsyncResult` values. + * + * Use this method instead of {@link ResultDeclarations.andThen} when the + * provided `f` returns promise. + */ + asyncAndThen(f: Fn>>): AsyncResult; + + /** + * Returns `res` if the result is `Err`, otherwise returns the `Ok` value. + */ + or(res: Result): Result; + + /** + * Calls `f` if the result is `Err`, otherwise returns the `Ok` value. + * + * This function can be used for control flow based on `Result` values. + */ + orElse(f: ErrFn>): Result; + + /** + * Calls `f` if the result is `Err`, otherwise returns the `Ok` value. + * + * This function can be used for control flow based on `AsyncResult` values. + * + * Use this method instead of {@link ResultDeclarations.orElse} when the + * provided `f` returns promise. + */ + asyncOrElse( + f: ErrFn>>, + ): AsyncResult; + + /** + * Returns the contained `Ok` value or a provided `value`. + */ + unwrapOr(value: U): U | T; + + /** + * Returns the contained `Ok `value or computes it from a `f`. + */ + unwrapOrElse(f: ErrFn): U | T; + + /** + * Returns the contained `Ok `value or computes it from a `f`. + * + * Use this method instead of {@link ResultDeclarations.unwrapOrElse} when the + * provided `f` returns promise. + */ + asyncUnwrapOrElse(f: ErrFn>): Promise; + + /** + * Calls `okFn` if the result is `Ok`, otherwise calls `errFn`. + * + * Both `okFn` and `errFn` must have the same return type. + */ + match(okFn: Fn, errFn: ErrFn): U; + + /** + * Calls `okFn` if the result is `Ok`, otherwise calls `errFn`. + * + * Both `okFn` and `errFn` must have the same return type. + * + * Use this method instead of {@link ResultDeclarations.match} when the + * provided `okFn` or `errFn` return promise. + */ + asyncMatch( + okFn: Fn>, + errFn: ErrFn>, + ): Promise; } /** @@ -298,533 +298,533 @@ export type Result = Ok | Err; * @augments ResultDeclarations */ export type AsyncResult = { - // We have to duplicate declarations due to the TypeScript limitations. - // The only thing we do here is using `AsyncResult` instead of `Result` and - // wrapping other types in `Promise` to allow chaining and make autocompletion - // happy. - // Reference: https://github.com/sindresorhus/type-fest/issues/178 - - /** - * Maps an `AsyncResult` to `AsyncResult` by applying a function - * `f` to a contained `Ok` value, leaving an `Err` value untouched. - * - * This function can be used to compose the results of two functions. - */ - map(f: Fn): AsyncResult; - - /** - * Maps an `AsyncResult` to `AsyncResult` by applying a function - * `f` to a contained `Ok` value, leaving an `Err` value untouched. - * - * This function can be used to compose the results of two functions. - * - * Use this method instead of {@link AsyncResult.map} when the provided `f` - * returns promise. - */ - asyncMap(f: Fn>): AsyncResult; - - /** - * Maps an `AsyncResult` to `AsyncResult` by applying a function - * to a contained `Err` value, leaving an `Ok` value untouched. - * - * This function can be used to pass through a successful result while - * handling an error. - */ - mapErr(f: ErrFn): AsyncResult; - - /** - * Maps an `AsyncResult` to `AsyncResult` by applying a function - * to a contained `Err` value, leaving an `Ok` value untouched. - * - * This function can be used to pass through a successful result while - * handling an error. - * - * Use this method instead of {@link AsyncResult.mapErr} when the provided `f` - * returns promise. - */ - asyncMapErr(f: ErrFn>): AsyncResult; - - /** - * Returns the provided `value` (if `Err`), or applies a function to the - * contained value (if `Ok`). - */ - mapOr(value: U, f: Fn): Promise; - - /** - * Returns the provided `value` (if `Err`), or applies a function to the - * contained value (if `Ok`). - * - * Use this method instead of {@link AsyncResult.mapOr} when the provided `f` - * returns promise. - */ - asyncMapOr(value: U, f: Fn>): Promise; - - /** - * Maps an `AsyncResult` to `Promise` by applying function - * `fallbackFn` to a contained `Err` value, or function `f` to a contained - * `Ok` value. - * - * This function can be used to unpack a successful result while handling an - * error. - */ - mapOrElse(fallbackFn: ErrFn, f: Fn): Promise; - - /** - * Maps an `AsyncResult` to `Promise` by applying function - * `fallbackFn` to a contained `Err` value, or function `f` to a contained - * `Ok` value. - * - * This function can be used to unpack a successful result while handling an - * error. - * - * Use this method instead of {@link AsyncResult.mapOrElse} when the provided - * `fallbackFn` or `f` return promise. - */ - asyncMapOrElse( - fallbackFn: ErrFn>, - f: Fn>, - ): Promise; - - /** - * Calls the provided function `f` with the contained value (if `Ok`). - */ - inspect(f: Fn): AsyncResult; - - /** - * Calls the provided function `f` with the contained error (if `Err`). - */ - inspectErr(f: ErrFn): AsyncResult; - - /** - * Performs a side effect on the contained value (if `Ok`). - * - * NOTE: `f` is awaited. - */ - tap(f: Fn>): AsyncResult; - - /** - * Performs a side effect on the contained error (if `Err`). - * - * NOTE: `f` is awaited. - */ - tapErr(f: Fn>): AsyncResult; - - /** - * Returns the contained `Ok` value. - * - * This function may throw `UnwrapError` (if `Err`). - */ - expect(msg: string): Promise; - - /** - * Returns the contained `Ok` value. - * - * This function may throw `UnwrapError` (if `Err`). - */ - unwrap(): Promise; - - /** - * Returns the contained `Err` value. - * - * This function may throw `UnwrapError` (if `Ok`). - */ - expectErr(msg: string): Promise; - - /** - * Returns the contained `Err` value. - * - * This function may throw `UnwrapError` (if `Ok`). - */ - unwrapErr(): Promise; - - /** - * Returns `res` if the result is `Ok`, otherwise returns the `Err` value. - */ - and(res: Result): AsyncResult; - - /** - * Calls `f` if the result is `Ok`, otherwise returns the `Err` value. - * - * This function can be used for control flow based on `AsyncResult` values. - */ - andThen(f: Fn>): AsyncResult; - - /** - * Calls `f` if the result is `Ok`, otherwise returns the `Err` value. - * - * This function can be used for control flow based on `AsyncResult` values. - * - * Use this method instead of {@link AsyncResult.andThen} when the provided - * `f` returns promise. - */ - asyncAndThen(f: Fn>>): AsyncResult; - - /** - * Returns `res` if the result is `Err`, otherwise returns the `Ok` value. - */ - or(res: Result): AsyncResult; - - /** - * Calls `f` if the result is `Err`, otherwise returns the `Ok` value. - * - * This function can be used for control flow based on `AsyncResult` values. - */ - orElse(f: ErrFn>): AsyncResult; - - /** - * Calls `f` if the result is `Err`, otherwise returns the `Ok` value. - * - * This function can be used for control flow based on `AsyncResult` values. - * - * Use this method instead of {@link AsyncResult.orElse} when the provided `f` - * returns promise. - */ - asyncOrElse( - f: ErrFn>>, - ): AsyncResult; - - /** - * Returns the contained `Ok` value or a provided `value`. - */ - unwrapOr(value: U): Promise; - - /** - * Calls `f` if the result is `Err`, otherwise returns the `Ok` value. - * - * This function can be used for control flow based on `AsyncResult` values. - * - * Use this method instead of {@link AsyncResult.orElse} when the provided `f` - * returns promise. - */ - unwrapOrElse(f: ErrFn): Promise; - - /** - * Returns the contained `Ok `value or computes it from a `f`. - * - * Use this method instead of {@link AsyncResult.unwrapOrElse} when the - * provided `f` returns promise. - */ - asyncUnwrapOrElse(f: ErrFn>): Promise; - - /** - * Calls `okFn` if the result is `Ok`, otherwise calls `errFn`. - * - * Both `okFn` and `errFn` must have the same return type. - */ - match(okFn: Fn, errFn: ErrFn): Promise; - - /** - * Calls `okFn` if the result is `Ok`, otherwise calls `errFn`. - * - * Both `okFn` and `errFn` must have the same return type. - * - * Use this method instead of {@link AsyncResult.match} when the provided - * `okFn` or `errFn` return promise. - */ - asyncMatch( - okFn: Fn>, - errFn: ErrFn>, - ): Promise; + // We have to duplicate declarations due to the TypeScript limitations. + // The only thing we do here is using `AsyncResult` instead of `Result` and + // wrapping other types in `Promise` to allow chaining and make autocompletion + // happy. + // Reference: https://github.com/sindresorhus/type-fest/issues/178 + + /** + * Maps an `AsyncResult` to `AsyncResult` by applying a function + * `f` to a contained `Ok` value, leaving an `Err` value untouched. + * + * This function can be used to compose the results of two functions. + */ + map(f: Fn): AsyncResult; + + /** + * Maps an `AsyncResult` to `AsyncResult` by applying a function + * `f` to a contained `Ok` value, leaving an `Err` value untouched. + * + * This function can be used to compose the results of two functions. + * + * Use this method instead of {@link AsyncResult.map} when the provided `f` + * returns promise. + */ + asyncMap(f: Fn>): AsyncResult; + + /** + * Maps an `AsyncResult` to `AsyncResult` by applying a function + * to a contained `Err` value, leaving an `Ok` value untouched. + * + * This function can be used to pass through a successful result while + * handling an error. + */ + mapErr(f: ErrFn): AsyncResult; + + /** + * Maps an `AsyncResult` to `AsyncResult` by applying a function + * to a contained `Err` value, leaving an `Ok` value untouched. + * + * This function can be used to pass through a successful result while + * handling an error. + * + * Use this method instead of {@link AsyncResult.mapErr} when the provided `f` + * returns promise. + */ + asyncMapErr(f: ErrFn>): AsyncResult; + + /** + * Returns the provided `value` (if `Err`), or applies a function to the + * contained value (if `Ok`). + */ + mapOr(value: U, f: Fn): Promise; + + /** + * Returns the provided `value` (if `Err`), or applies a function to the + * contained value (if `Ok`). + * + * Use this method instead of {@link AsyncResult.mapOr} when the provided `f` + * returns promise. + */ + asyncMapOr(value: U, f: Fn>): Promise; + + /** + * Maps an `AsyncResult` to `Promise` by applying function + * `fallbackFn` to a contained `Err` value, or function `f` to a contained + * `Ok` value. + * + * This function can be used to unpack a successful result while handling an + * error. + */ + mapOrElse(fallbackFn: ErrFn, f: Fn): Promise; + + /** + * Maps an `AsyncResult` to `Promise` by applying function + * `fallbackFn` to a contained `Err` value, or function `f` to a contained + * `Ok` value. + * + * This function can be used to unpack a successful result while handling an + * error. + * + * Use this method instead of {@link AsyncResult.mapOrElse} when the provided + * `fallbackFn` or `f` return promise. + */ + asyncMapOrElse( + fallbackFn: ErrFn>, + f: Fn>, + ): Promise; + + /** + * Calls the provided function `f` with the contained value (if `Ok`). + */ + inspect(f: Fn): AsyncResult; + + /** + * Calls the provided function `f` with the contained error (if `Err`). + */ + inspectErr(f: ErrFn): AsyncResult; + + /** + * Performs a side effect on the contained value (if `Ok`). + * + * NOTE: `f` is awaited. + */ + tap(f: Fn>): AsyncResult; + + /** + * Performs a side effect on the contained error (if `Err`). + * + * NOTE: `f` is awaited. + */ + tapErr(f: Fn>): AsyncResult; + + /** + * Returns the contained `Ok` value. + * + * This function may throw `UnwrapError` (if `Err`). + */ + expect(msg: string): Promise; + + /** + * Returns the contained `Ok` value. + * + * This function may throw `UnwrapError` (if `Err`). + */ + unwrap(): Promise; + + /** + * Returns the contained `Err` value. + * + * This function may throw `UnwrapError` (if `Ok`). + */ + expectErr(msg: string): Promise; + + /** + * Returns the contained `Err` value. + * + * This function may throw `UnwrapError` (if `Ok`). + */ + unwrapErr(): Promise; + + /** + * Returns `res` if the result is `Ok`, otherwise returns the `Err` value. + */ + and(res: Result): AsyncResult; + + /** + * Calls `f` if the result is `Ok`, otherwise returns the `Err` value. + * + * This function can be used for control flow based on `AsyncResult` values. + */ + andThen(f: Fn>): AsyncResult; + + /** + * Calls `f` if the result is `Ok`, otherwise returns the `Err` value. + * + * This function can be used for control flow based on `AsyncResult` values. + * + * Use this method instead of {@link AsyncResult.andThen} when the provided + * `f` returns promise. + */ + asyncAndThen(f: Fn>>): AsyncResult; + + /** + * Returns `res` if the result is `Err`, otherwise returns the `Ok` value. + */ + or(res: Result): AsyncResult; + + /** + * Calls `f` if the result is `Err`, otherwise returns the `Ok` value. + * + * This function can be used for control flow based on `AsyncResult` values. + */ + orElse(f: ErrFn>): AsyncResult; + + /** + * Calls `f` if the result is `Err`, otherwise returns the `Ok` value. + * + * This function can be used for control flow based on `AsyncResult` values. + * + * Use this method instead of {@link AsyncResult.orElse} when the provided `f` + * returns promise. + */ + asyncOrElse( + f: ErrFn>>, + ): AsyncResult; + + /** + * Returns the contained `Ok` value or a provided `value`. + */ + unwrapOr(value: U): Promise; + + /** + * Calls `f` if the result is `Err`, otherwise returns the `Ok` value. + * + * This function can be used for control flow based on `AsyncResult` values. + * + * Use this method instead of {@link AsyncResult.orElse} when the provided `f` + * returns promise. + */ + unwrapOrElse(f: ErrFn): Promise; + + /** + * Returns the contained `Ok `value or computes it from a `f`. + * + * Use this method instead of {@link AsyncResult.unwrapOrElse} when the + * provided `f` returns promise. + */ + asyncUnwrapOrElse(f: ErrFn>): Promise; + + /** + * Calls `okFn` if the result is `Ok`, otherwise calls `errFn`. + * + * Both `okFn` and `errFn` must have the same return type. + */ + match(okFn: Fn, errFn: ErrFn): Promise; + + /** + * Calls `okFn` if the result is `Ok`, otherwise calls `errFn`. + * + * Both `okFn` and `errFn` must have the same return type. + * + * Use this method instead of {@link AsyncResult.match} when the provided + * `okFn` or `errFn` return promise. + */ + asyncMatch( + okFn: Fn>, + errFn: ErrFn>, + ): Promise; } & Promise>; export class Ok implements ResultDeclarations { - constructor(readonly value: T) {} + constructor(readonly value: T) {} - isOk(): this is Ok { - return true; - } + isOk(): this is Ok { + return true; + } - isOkAnd(f: Predicate): boolean { - return f(this.value); - } + isOkAnd(f: Predicate): boolean { + return f(this.value); + } - isErr(): this is Err { - return false; - } + isErr(): this is Err { + return false; + } - isErrAnd(): boolean { - return false; - } + isErrAnd(): boolean { + return false; + } - map(f: Fn): Result { - return ok(f(this.value)); - } + map(f: Fn): Result { + return ok(f(this.value)); + } - asyncMap(f: Fn>): AsyncResult { - return chain(f(this.value).then(ok)); - } + asyncMap(f: Fn>): AsyncResult { + return chain(f(this.value).then(ok)); + } - mapOr(_: U, f: Fn): U { - return f(this.value); - } + mapOr(_: U, f: Fn): U { + return f(this.value); + } - asyncMapOr(_: U, f: Fn>): Promise { - return f(this.value); - } + asyncMapOr(_: U, f: Fn>): Promise { + return f(this.value); + } - mapOrElse(_: ErrFn, f: Fn): U { - return f(this.value); - } + mapOrElse(_: ErrFn, f: Fn): U { + return f(this.value); + } - asyncMapOrElse( - _: ErrFn>, - f: Fn>, - ): Promise { - return Promise.resolve(f(this.value)); - } + asyncMapOrElse( + _: ErrFn>, + f: Fn>, + ): Promise { + return Promise.resolve(f(this.value)); + } - mapErr(): Result { - return ok(this.value); - } + mapErr(): Result { + return ok(this.value); + } - asyncMapErr(): AsyncResult { - return chain(ok(this.value)); - } + asyncMapErr(): AsyncResult { + return chain(ok(this.value)); + } - inspect(f: Fn): Result { - f(this.value); + inspect(f: Fn): Result { + f(this.value); - return ok(this.value); - } + return ok(this.value); + } - inspectErr(): Result { - return ok(this.value); - } + inspectErr(): Result { + return ok(this.value); + } - tap(f: Fn>): AsyncResult { - return chain(f(this.value).then(() => ok(this.value))); - } + tap(f: Fn>): AsyncResult { + return chain(f(this.value).then(() => ok(this.value))); + } - tapErr(): AsyncResult { - return okAsync(this.value); - } + tapErr(): AsyncResult { + return okAsync(this.value); + } - expect(): T { - return this.value; - } + expect(): T { + return this.value; + } - unwrap(): T { - return this.value; - } + unwrap(): T { + return this.value; + } - expectErr(msg: string): never { - throw new ResultError(msg, this.value); - } + expectErr(msg: string): never { + throw new ResultError(msg, this.value); + } - unwrapErr(): never { - throw new ResultError("Called `unwrapErr` on `Ok`", this.value); - } + unwrapErr(): never { + throw new ResultError("Called `unwrapErr` on `Ok`", this.value); + } - and(res: Result): Result { - return res; - } + and(res: Result): Result { + return res; + } - andThen(f: Fn>): Result { - return f(this.value); - } + andThen(f: Fn>): Result { + return f(this.value); + } - asyncAndThen(f: Fn>>): AsyncResult { - return chain(f(this.value)); - } + asyncAndThen(f: Fn>>): AsyncResult { + return chain(f(this.value)); + } - or(): Result { - return ok(this.value); - } - - orElse(): Result { - return ok(this.value); - } - - asyncOrElse(): AsyncResult { - return chain(ok(this.value)); - } - - unwrapOr(): U | T { - return this.value; - } - - unwrapOrElse(): U | T { - return this.value; - } - - asyncUnwrapOrElse(): Promise { - return Promise.resolve(this.value); - } - - match(okFn: Fn): U { - return okFn(this.value); - } - - asyncMatch(okFn: Fn>): Promise { - return Promise.resolve(okFn(this.value)); - } + or(): Result { + return ok(this.value); + } + + orElse(): Result { + return ok(this.value); + } + + asyncOrElse(): AsyncResult { + return chain(ok(this.value)); + } + + unwrapOr(): U | T { + return this.value; + } + + unwrapOrElse(): U | T { + return this.value; + } + + asyncUnwrapOrElse(): Promise { + return Promise.resolve(this.value); + } + + match(okFn: Fn): U { + return okFn(this.value); + } + + asyncMatch(okFn: Fn>): Promise { + return Promise.resolve(okFn(this.value)); + } } export class Err implements ResultDeclarations { - constructor(readonly error: E) {} + constructor(readonly error: E) {} - isOk(): this is Ok { - return false; - } + isOk(): this is Ok { + return false; + } - isOkAnd(): boolean { - return false; - } + isOkAnd(): boolean { + return false; + } - isErr(): this is Err { - return true; - } + isErr(): this is Err { + return true; + } - isErrAnd(f: Predicate): boolean { - return f(this.error); - } + isErrAnd(f: Predicate): boolean { + return f(this.error); + } - map(): Result { - return err(this.error); - } + map(): Result { + return err(this.error); + } - asyncMap(): AsyncResult { - return chain(err(this.error)); - } + asyncMap(): AsyncResult { + return chain(err(this.error)); + } - mapOr(value: U): U { - return value; - } + mapOr(value: U): U { + return value; + } - asyncMapOr(value: U): Promise { - return Promise.resolve(value); - } - - mapOrElse(fallback: ErrFn): U { - return fallback(this.error); - } - - asyncMapOrElse(fallbackFn: ErrFn>): Promise { - return Promise.resolve(fallbackFn(this.error)); - } - - mapErr(f: ErrFn): Result { - return err(f(this.error)); - } - - asyncMapErr(f: ErrFn>): AsyncResult { - return chain(f(this.error).then(err)); - } - - inspect(): Result { - return err(this.error); - } - - inspectErr(f: ErrFn): Result { - f(this.error); - - return err(this.error); - } - - tap(): AsyncResult { - return chain(err(this.error)); - } - - tapErr(f: Fn>): AsyncResult { - return chain(f(this.error).then(() => errAsync(this.error))); - } - - expect(msg: string): never { - throw new ResultError(msg, this.error); - } - - unwrap(): never { - throw new ResultError("Called `unwrap` on `Err`", this.error); - } - - expectErr(): E { - return this.error; - } - - unwrapErr(): E { - return this.error; - } - - and(): Result { - return err(this.error); - } - - andThen(): Result { - return err(this.error); - } - - asyncAndThen(): AsyncResult { - return chain(err(this.error)); - } - - or(res: Result): Result { - return res; - } - - orElse(f: ErrFn>): Result { - return f(this.error); - } - - asyncOrElse( - f: ErrFn>>, - ): AsyncResult { - return chain(f(this.error)); - } + asyncMapOr(value: U): Promise { + return Promise.resolve(value); + } + + mapOrElse(fallback: ErrFn): U { + return fallback(this.error); + } + + asyncMapOrElse(fallbackFn: ErrFn>): Promise { + return Promise.resolve(fallbackFn(this.error)); + } + + mapErr(f: ErrFn): Result { + return err(f(this.error)); + } + + asyncMapErr(f: ErrFn>): AsyncResult { + return chain(f(this.error).then(err)); + } + + inspect(): Result { + return err(this.error); + } + + inspectErr(f: ErrFn): Result { + f(this.error); + + return err(this.error); + } + + tap(): AsyncResult { + return chain(err(this.error)); + } + + tapErr(f: Fn>): AsyncResult { + return chain(f(this.error).then(() => errAsync(this.error))); + } + + expect(msg: string): never { + throw new ResultError(msg, this.error); + } + + unwrap(): never { + throw new ResultError("Called `unwrap` on `Err`", this.error); + } + + expectErr(): E { + return this.error; + } + + unwrapErr(): E { + return this.error; + } + + and(): Result { + return err(this.error); + } + + andThen(): Result { + return err(this.error); + } + + asyncAndThen(): AsyncResult { + return chain(err(this.error)); + } + + or(res: Result): Result { + return res; + } + + orElse(f: ErrFn>): Result { + return f(this.error); + } + + asyncOrElse( + f: ErrFn>>, + ): AsyncResult { + return chain(f(this.error)); + } - unwrapOr(value: U): U | T { - return value; - } + unwrapOr(value: U): U | T { + return value; + } - unwrapOrElse(f: ErrFn): U | T { - return f(this.error); - } + unwrapOrElse(f: ErrFn): U | T { + return f(this.error); + } - asyncUnwrapOrElse(f: ErrFn>): Promise { - return f(this.error); - } + asyncUnwrapOrElse(f: ErrFn>): Promise { + return f(this.error); + } - match(_: Fn, errFn: ErrFn): U { - return errFn(this.error); - } + match(_: Fn, errFn: ErrFn): U { + return errFn(this.error); + } - asyncMatch( - _: Fn>, - errFn: ErrFn>, - ): Promise { - return Promise.resolve(errFn(this.error)); - } + asyncMatch( + _: Fn>, + errFn: ErrFn>, + ): Promise { + return Promise.resolve(errFn(this.error)); + } } /** * Creates an `Ok` variant of `Result`. */ export function ok(value: T): Result { - return new Ok(value); + return new Ok(value); } /** * Creates an `Ok` variant of `AsyncResult`. */ export function okAsync( - value: T | Promise, + value: T | Promise, ): AsyncResult { - return chain(Promise.resolve(value).then(ok)); + return chain(Promise.resolve(value).then(ok)); } /** * Creates an `Err` variant of `Result`. */ export function err(error: E): Result { - return new Err(error); + return new Err(error); } /** * Creates an `Err` variant of `AsyncResult`. */ export function errAsync( - error: E | Promise, + error: E | Promise, ): AsyncResult { - return chain(Promise.resolve(error).then(err)); + return chain(Promise.resolve(error).then(err)); } /** @@ -835,10 +835,10 @@ export function errAsync( * error from `unknown` to `E`. */ export function fromPromise( - promise: Promise, - errorFn?: ErrFn, + promise: Promise, + errorFn?: ErrFn, ): AsyncResult { - return chain(promise.then(ok).catch((e) => err(errorFn ? errorFn(e) : e))); + return chain(promise.then(ok).catch((e) => err(errorFn ? errorFn(e) : e))); } /** @@ -849,7 +849,7 @@ export function fromPromise( * promise will cause `AsyncResult` to reject.** */ export function fromSafePromise(promise: Promise): AsyncResult { - return chain(promise.then(ok)); + return chain(promise.then(ok)); } /** @@ -860,14 +860,14 @@ export function fromSafePromise(promise: Promise): AsyncResult { * error from `unknown` to `E`. */ export function fromThrowable( - f: Fn, - errorFn?: ErrFn, + f: Fn, + errorFn?: ErrFn, ): Result { - try { - return ok(f()); - } catch (e) { - return err(errorFn ? errorFn(e) : (e as E)); - } + try { + return ok(f()); + } catch (e) { + return err(errorFn ? errorFn(e) : (e as E)); + } } /** @@ -878,19 +878,19 @@ export function fromThrowable( * contain the first `Err` error. */ export function combine[]>( - results: [...T], + results: [...T], ): Result, UnwrapErrs[number]> { - const unwrapped = [] as Mutable>; + const unwrapped = [] as Mutable>; - for (const result of results) { - if (result.isErr()) { - return err(result.error as UnwrapErrs[number]); - } + for (const result of results) { + if (result.isErr()) { + return err(result.error as UnwrapErrs[number]); + } - unwrapped.push(result.value); - } + unwrapped.push(result.value); + } - return ok(unwrapped); + return ok(unwrapped); } /** @@ -901,10 +901,10 @@ export function combine[]>( * returned `AsyncResult` will contain the first `Err` error. */ export function combineAsync< - T extends readonly ( - | Result - | AsyncResult - )[], + T extends readonly ( + | Result + | AsyncResult + )[], >(results: [...T]): AsyncResult, UnwrapErrs[number]> { - return chain(Promise.all(results).then(combine)); + return chain(Promise.all(results).then(combine)); } diff --git a/core/result_test.ts b/core/result_test.ts index d1ba64e..b4ea2c1 100644 --- a/core/result_test.ts +++ b/core/result_test.ts @@ -2,522 +2,522 @@ import { assert, assertEquals, assertRejects, assertThrows } from "@std/assert"; import { assertSpyCall, assertSpyCalls, spy } from "@std/testing/mock"; import { assertType, type IsExact } from "@std/testing/types"; import { - combine, - combineAsync, - err, - errAsync, - fromPromise, - fromSafePromise, - fromThrowable, - ok, - okAsync, - ResultError, + combine, + combineAsync, + err, + errAsync, + fromPromise, + fromSafePromise, + fromThrowable, + ok, + okAsync, + ResultError, } from "./result.ts"; Deno.test("ok", () => { - const result = ok(4); - assert(result.isOk()); - assert(!result.isErr()); + const result = ok(4); + assert(result.isOk()); + assert(!result.isErr()); - assertType>(true); + assertType>(true); - assert(result.isOkAnd((v) => v > 0)); + assert(result.isOkAnd((v) => v > 0)); }); Deno.test("err", () => { - const result = err(4); - assert(!result.isOk()); - assert(result.isErr()); + const result = err(4); + assert(!result.isOk()); + assert(result.isErr()); - assertType>(true); + assertType>(true); - assert(result.isErrAnd((v) => v > 0)); + assert(result.isErrAnd((v) => v > 0)); }); Deno.test("fromThrowable", () => { - assertEquals(fromThrowable(() => 4).unwrap(), 4); - assertEquals( - fromThrowable(() => { - throw "error"; - }).unwrapErr(), - "error", - ); - assertEquals( - fromThrowable( - () => { - throw "some error"; - }, - () => "mapped error", - ).unwrapErr(), - "mapped error", - ); + assertEquals(fromThrowable(() => 4).unwrap(), 4); + assertEquals( + fromThrowable(() => { + throw "error"; + }).unwrapErr(), + "error", + ); + assertEquals( + fromThrowable( + () => { + throw "some error"; + }, + () => "mapped error", + ).unwrapErr(), + "mapped error", + ); }); Deno.test("combine", () => { - assertEquals(combine([ok(4)]).unwrap(), [4]); - assertEquals(combine([ok(4), ok("ook")]).unwrap(), [4, "ook"]); - assertEquals(combine([err("err")]).unwrapErr(), "err"); - assertEquals(combine([err("1"), err("2")]).unwrapErr(), "1"); - assertEquals(combine([ok(1), err(2)]).unwrapErr(), 2); + assertEquals(combine([ok(4)]).unwrap(), [4]); + assertEquals(combine([ok(4), ok("ook")]).unwrap(), [4, "ook"]); + assertEquals(combine([err("err")]).unwrapErr(), "err"); + assertEquals(combine([err("1"), err("2")]).unwrapErr(), "1"); + assertEquals(combine([ok(1), err(2)]).unwrapErr(), 2); - { - const v = combine([ok(4), ok("")]).unwrap(); - assertType>(true); - } + { + const v = combine([ok(4), ok("")]).unwrap(); + assertType>(true); + } - { - const v = combine([ok([4]), ok("")]).unwrap(); - assertType>(true); - } + { + const v = combine([ok([4]), ok("")]).unwrap(); + assertType>(true); + } }); Deno.test("okAsync", async () => { - const result = await okAsync(4); - assert(result.isOk()); - assert(!result.isErr()); + const result = await okAsync(4); + assert(result.isOk()); + assert(!result.isErr()); }); Deno.test("errAsync", async () => { - const result = await errAsync(4); - assert(!result.isOk()); - assert(result.isErr()); + const result = await errAsync(4); + assert(!result.isOk()); + assert(result.isErr()); }); Deno.test("fromPromise", async () => { - assertEquals(await fromPromise(Promise.resolve(1)).unwrap(), 1); + assertEquals(await fromPromise(Promise.resolve(1)).unwrap(), 1); - assertEquals( - await fromPromise(Promise.reject("rejected error")).unwrapErr(), - "rejected error", - ); + assertEquals( + await fromPromise(Promise.reject("rejected error")).unwrapErr(), + "rejected error", + ); - assertEquals( - await fromPromise( - Promise.reject("rejected error"), - () => "mapped error", - ).unwrapErr(), - "mapped error", - ); + assertEquals( + await fromPromise( + Promise.reject("rejected error"), + () => "mapped error", + ).unwrapErr(), + "mapped error", + ); }); Deno.test("fromSafePromise", async () => { - assertEquals(await fromSafePromise(Promise.resolve(1)).unwrap(), 1); + assertEquals(await fromSafePromise(Promise.resolve(1)).unwrap(), 1); - await assertRejects( - async () => - await fromSafePromise(Promise.reject(new Error("rejected error"))), - Error, - "rejected error", - ); + await assertRejects( + async () => + await fromSafePromise(Promise.reject(new Error("rejected error"))), + Error, + "rejected error", + ); }); Deno.test("combineAsync", async () => { - assertEquals(await combineAsync([ok(4)]).unwrap(), [4]); - assertEquals(await combineAsync([ok(4), okAsync("ook")]).unwrap(), [ - 4, - "ook", - ]); - assertEquals(await combineAsync([err("err")]).unwrapErr(), "err"); - assertEquals( - await combineAsync([err("1"), errAsync("2")]).unwrapErr(), - "1", - ); - assertEquals(await combineAsync([okAsync(1), err(2)]).unwrapErr(), 2); - - { - const v = await combineAsync([ok([4]), ok("")]).unwrap(); - assertType>(true); - } + assertEquals(await combineAsync([ok(4)]).unwrap(), [4]); + assertEquals(await combineAsync([ok(4), okAsync("ook")]).unwrap(), [ + 4, + "ook", + ]); + assertEquals(await combineAsync([err("err")]).unwrapErr(), "err"); + assertEquals( + await combineAsync([err("1"), errAsync("2")]).unwrapErr(), + "1", + ); + assertEquals(await combineAsync([okAsync(1), err(2)]).unwrapErr(), 2); + + { + const v = await combineAsync([ok([4]), ok("")]).unwrap(); + assertType>(true); + } }); Deno.test("Result.map", () => { - assertEquals( - ok(4) - .map((v) => v + 1) - .unwrap(), - 5, - ); - assertEquals( - err(4) - .map((v) => v + 1) - .unwrapErr(), - 4, - ); + assertEquals( + ok(4) + .map((v) => v + 1) + .unwrap(), + 5, + ); + assertEquals( + err(4) + .map((v) => v + 1) + .unwrapErr(), + 4, + ); }); Deno.test("Result.mapOr", () => { - assertEquals( - ok(4).mapOr(0, (v) => v + 1), - 5, - ); - assertEquals( - err(4).mapOr(0, (v) => v + 1), - 0, - ); + assertEquals( + ok(4).mapOr(0, (v) => v + 1), + 5, + ); + assertEquals( + err(4).mapOr(0, (v) => v + 1), + 0, + ); }); Deno.test("Result.mapOrElse", () => { - assertEquals( - ok(4).mapOrElse( - () => 0, - (v) => v + 1, - ), - 5, - ); - assertEquals( - err(4).mapOrElse( - (errValue) => errValue - 1, - (v) => v + 1, - ), - 3, - ); + assertEquals( + ok(4).mapOrElse( + () => 0, + (v) => v + 1, + ), + 5, + ); + assertEquals( + err(4).mapOrElse( + (errValue) => errValue - 1, + (v) => v + 1, + ), + 3, + ); }); Deno.test("Result.mapErr", () => { - assertEquals( - ok(4) - .mapErr((v) => v + 1) - .unwrap(), - 4, - ); - assertEquals( - err(4) - .mapErr((v) => v + 1) - .unwrapErr(), - 5, - ); + assertEquals( + ok(4) + .mapErr((v) => v + 1) + .unwrap(), + 4, + ); + assertEquals( + err(4) + .mapErr((v) => v + 1) + .unwrapErr(), + 5, + ); }); Deno.test("Result.inspect", () => { - const fn = spy(); + const fn = spy(); - ok(4).inspect(fn); - assertSpyCalls(fn, 1); - err(4).inspect(fn); - assertSpyCalls(fn, 1); + ok(4).inspect(fn); + assertSpyCalls(fn, 1); + err(4).inspect(fn); + assertSpyCalls(fn, 1); }); Deno.test("Result.inspectErr", () => { - const fn = spy(); + const fn = spy(); - ok(4).inspectErr(fn); - assertSpyCalls(fn, 0); - err(4).inspectErr(fn); - assertSpyCalls(fn, 1); + ok(4).inspectErr(fn); + assertSpyCalls(fn, 0); + err(4).inspectErr(fn); + assertSpyCalls(fn, 1); }); Deno.test("Result.tap", async () => { - let modified = false; + let modified = false; - const fn = spy(async () => { - await new Promise((resolve) => setTimeout(resolve, 5)); + const fn = spy(async () => { + await new Promise((resolve) => setTimeout(resolve, 5)); - modified = true; - }); + modified = true; + }); - { - const tapped = err("err").tap(fn); - assertSpyCalls(fn, 0); - assert(!modified); - assertEquals(await tapped.unwrapErr(), "err"); - } + { + const tapped = err("err").tap(fn); + assertSpyCalls(fn, 0); + assert(!modified); + assertEquals(await tapped.unwrapErr(), "err"); + } - { - const tapped = await ok(4).tap(fn); + { + const tapped = await ok(4).tap(fn); - assertSpyCall(fn, 0, { args: [4] }); - assert(modified); - assertEquals(tapped.unwrap(), 4); - } + assertSpyCall(fn, 0, { args: [4] }); + assert(modified); + assertEquals(tapped.unwrap(), 4); + } }); Deno.test("Result.tapErr", async () => { - let modified = false; + let modified = false; - const fn = spy(async () => { - await new Promise((resolve) => setTimeout(resolve, 5)); + const fn = spy(async () => { + await new Promise((resolve) => setTimeout(resolve, 5)); - modified = true; - }); + modified = true; + }); - { - const tapped = ok(4).tapErr(fn); + { + const tapped = ok(4).tapErr(fn); - assertSpyCalls(fn, 0); - assert(!modified); - assertEquals(await tapped.unwrap(), 4); - } + assertSpyCalls(fn, 0); + assert(!modified); + assertEquals(await tapped.unwrap(), 4); + } - { - const tapped = await err("err").tapErr(fn); + { + const tapped = await err("err").tapErr(fn); - assertSpyCall(fn, 0, { args: ["err"] }); - assert(modified); - assertEquals(tapped.unwrapErr(), "err"); - } + assertSpyCall(fn, 0, { args: ["err"] }); + assert(modified); + assertEquals(tapped.unwrapErr(), "err"); + } }); Deno.test("Result.expect", () => { - assertEquals(ok(4).expect("error"), 4); - assertThrows(() => err(4).expect("error"), ResultError, "error"); + assertEquals(ok(4).expect("error"), 4); + assertThrows(() => err(4).expect("error"), ResultError, "error"); }); Deno.test("Result.unwrap", () => { - assertEquals(ok(4).unwrap(), 4); - assertThrows( - () => err(4).unwrap(), - ResultError, - "Called `unwrap` on `Err`", - ); + assertEquals(ok(4).unwrap(), 4); + assertThrows( + () => err(4).unwrap(), + ResultError, + "Called `unwrap` on `Err`", + ); }); Deno.test("Result.expectErr", () => { - assertThrows(() => ok(4).expectErr("error"), ResultError, "error"); - assertEquals(err(4).expectErr("error"), 4); + assertThrows(() => ok(4).expectErr("error"), ResultError, "error"); + assertEquals(err(4).expectErr("error"), 4); }); Deno.test("Result.unwrapErr", () => { - assertThrows( - () => ok(4).unwrapErr(), - ResultError, - "Called `unwrapErr` on `Ok`", - ); - assertEquals(err(4).unwrapErr(), 4); + assertThrows( + () => ok(4).unwrapErr(), + ResultError, + "Called `unwrapErr` on `Ok`", + ); + assertEquals(err(4).unwrapErr(), 4); }); Deno.test("Result.and", () => { - assertEquals(ok(1).and(ok(2)).unwrap(), 2); - assertEquals(ok(1).and(err(2)).unwrapErr(), 2); - assertEquals(err(1).and(ok(2)).unwrapErr(), 1); - assertEquals(err(1).and(err(2)).unwrapErr(), 1); + assertEquals(ok(1).and(ok(2)).unwrap(), 2); + assertEquals(ok(1).and(err(2)).unwrapErr(), 2); + assertEquals(err(1).and(ok(2)).unwrapErr(), 1); + assertEquals(err(1).and(err(2)).unwrapErr(), 1); }); Deno.test("Result.andThen", () => { - assertEquals( - ok(4) - .andThen((v) => ok(v + 1)) - .unwrap(), - 5, - ); - assertEquals( - ok(4) - .andThen((v) => err(v - 1)) - .unwrapErr(), - 3, - ); - assertEquals( - ok(4) - .andThen(() => err("err")) - .unwrapErr(), - "err", - ); - assertEquals( - err(4) - .andThen(() => ok(0)) - .unwrapErr(), - 4, - ); - assertEquals( - err(4) - .andThen(() => err("qwe")) - .unwrapErr(), - 4, - ); + assertEquals( + ok(4) + .andThen((v) => ok(v + 1)) + .unwrap(), + 5, + ); + assertEquals( + ok(4) + .andThen((v) => err(v - 1)) + .unwrapErr(), + 3, + ); + assertEquals( + ok(4) + .andThen(() => err("err")) + .unwrapErr(), + "err", + ); + assertEquals( + err(4) + .andThen(() => ok(0)) + .unwrapErr(), + 4, + ); + assertEquals( + err(4) + .andThen(() => err("qwe")) + .unwrapErr(), + 4, + ); }); Deno.test("Result.or", () => { - assertEquals(ok(1).or(ok(2)).unwrap(), 1); - assertEquals(ok(1).or(err(2)).unwrap(), 1); - assertEquals(err(1).or(ok(2)).unwrap(), 2); - assertEquals(err(1).or(err(2)).unwrapErr(), 2); + assertEquals(ok(1).or(ok(2)).unwrap(), 1); + assertEquals(ok(1).or(err(2)).unwrap(), 1); + assertEquals(err(1).or(ok(2)).unwrap(), 2); + assertEquals(err(1).or(err(2)).unwrapErr(), 2); }); Deno.test("Result.orElse", () => { - const fn = spy((v: number) => ok(v / 2)); + const fn = spy((v: number) => ok(v / 2)); - ok(4).orElse(fn); + ok(4).orElse(fn); - assertSpyCalls(fn, 0); + assertSpyCalls(fn, 0); - assertEquals( - err(4) - .orElse((v) => err(v + 1)) - .unwrapErr(), - 5, - ); - assertEquals( - err(4) - .orElse((v) => ok(v + 1)) - .unwrap(), - 5, - ); + assertEquals( + err(4) + .orElse((v) => err(v + 1)) + .unwrapErr(), + 5, + ); + assertEquals( + err(4) + .orElse((v) => ok(v + 1)) + .unwrap(), + 5, + ); }); Deno.test("Result.unwrapOr", () => { - assertEquals(ok(4).unwrapOr(0), 4); - assertEquals(err(4).unwrapOr(0), 0); - assertEquals(ok(4).unwrapOr("str"), 4); - assertEquals(err(4).unwrapOr("str"), "str"); + assertEquals(ok(4).unwrapOr(0), 4); + assertEquals(err(4).unwrapOr(0), 0); + assertEquals(ok(4).unwrapOr("str"), 4); + assertEquals(err(4).unwrapOr("str"), "str"); }); Deno.test("Result.unwrapOrElse", () => { - assertEquals( - ok(4).unwrapOrElse(() => 0), - 4, - ); - assertEquals( - err(4).unwrapOrElse((v) => v - 1), - 3, - ); - assertEquals( - ok(4).unwrapOrElse(() => "str"), - 4, - ); - assertEquals( - err(4).unwrapOrElse(() => "str"), - "str", - ); + assertEquals( + ok(4).unwrapOrElse(() => 0), + 4, + ); + assertEquals( + err(4).unwrapOrElse((v) => v - 1), + 3, + ); + assertEquals( + ok(4).unwrapOrElse(() => "str"), + 4, + ); + assertEquals( + err(4).unwrapOrElse(() => "str"), + "str", + ); }); Deno.test("Result.match", () => { - const okFn = spy(); - const errFn = spy(); + const okFn = spy(); + const errFn = spy(); - ok(4).match(okFn, errFn); + ok(4).match(okFn, errFn); - assertSpyCall(okFn, 0, { args: [4] }); - assertSpyCalls(errFn, 0); + assertSpyCall(okFn, 0, { args: [4] }); + assertSpyCalls(errFn, 0); - err(4).match(okFn, errFn); + err(4).match(okFn, errFn); - assertSpyCalls(okFn, 1); - assertSpyCall(errFn, 0, { args: [4] }); + assertSpyCalls(okFn, 1); + assertSpyCall(errFn, 0, { args: [4] }); }); Deno.test("Result.asyncMap", async () => { - assertEquals( - await ok(2) - .asyncMap((v) => Promise.resolve(v + 1)) - .unwrap(), - 3, - ); - assertEquals( - await err(2) - .asyncMap((v) => Promise.resolve(v + 1)) - .unwrapErr(), - 2, - ); + assertEquals( + await ok(2) + .asyncMap((v) => Promise.resolve(v + 1)) + .unwrap(), + 3, + ); + assertEquals( + await err(2) + .asyncMap((v) => Promise.resolve(v + 1)) + .unwrapErr(), + 2, + ); }); Deno.test("Result.asyncMapOr", async () => { - assertEquals(await ok(2).asyncMapOr(0, (v) => Promise.resolve(v + 1)), 3); - assertEquals(await err(2).asyncMapOr(0, (v) => Promise.resolve(v + 1)), 0); + assertEquals(await ok(2).asyncMapOr(0, (v) => Promise.resolve(v + 1)), 3); + assertEquals(await err(2).asyncMapOr(0, (v) => Promise.resolve(v + 1)), 0); }); Deno.test("Result.asyncMapOrElse", async () => { - assertEquals( - await ok(2).asyncMapOrElse( - (err) => err - 1, - (v) => v + 1, - ), - 3, - ); - assertEquals( - await err(2).asyncMapOrElse( - (err) => err - 1, - (v) => v + 1, - ), - 1, - ); + assertEquals( + await ok(2).asyncMapOrElse( + (err) => err - 1, + (v) => v + 1, + ), + 3, + ); + assertEquals( + await err(2).asyncMapOrElse( + (err) => err - 1, + (v) => v + 1, + ), + 1, + ); }); Deno.test("Result.asyncMapErr", async () => { - assertEquals( - await ok(2) - .asyncMapErr((v) => Promise.resolve(v + 1)) - .unwrap(), - 2, - ); - assertEquals( - await err(2) - .asyncMapErr((v) => Promise.resolve(v + 1)) - .unwrapErr(), - 3, - ); + assertEquals( + await ok(2) + .asyncMapErr((v) => Promise.resolve(v + 1)) + .unwrap(), + 2, + ); + assertEquals( + await err(2) + .asyncMapErr((v) => Promise.resolve(v + 1)) + .unwrapErr(), + 3, + ); }); Deno.test("Result.asyncAndThen", async () => { - assertEquals( - await ok(4) - .asyncAndThen((v) => Promise.resolve(ok(v + 1))) - .unwrap(), - 5, - ); - assertEquals( - await ok(4) - .asyncAndThen((v) => Promise.resolve(err(v - 1))) - .unwrapErr(), - 3, - ); - assertEquals( - await err(4) - .asyncAndThen(() => Promise.resolve(ok(0))) - .unwrapErr(), - 4, - ); + assertEquals( + await ok(4) + .asyncAndThen((v) => Promise.resolve(ok(v + 1))) + .unwrap(), + 5, + ); + assertEquals( + await ok(4) + .asyncAndThen((v) => Promise.resolve(err(v - 1))) + .unwrapErr(), + 3, + ); + assertEquals( + await err(4) + .asyncAndThen(() => Promise.resolve(ok(0))) + .unwrapErr(), + 4, + ); }); Deno.test("Result.asyncOrElse", async () => { - const f = spy(() => Promise.resolve(ok(5))); + const f = spy(() => Promise.resolve(ok(5))); - await ok(4).asyncOrElse(f); - assertSpyCalls(f, 0); + await ok(4).asyncOrElse(f); + assertSpyCalls(f, 0); - assertEquals( - await err(4) - .asyncOrElse((v) => Promise.resolve(err(v + 1))) - .unwrapErr(), - 5, - ); + assertEquals( + await err(4) + .asyncOrElse((v) => Promise.resolve(err(v + 1))) + .unwrapErr(), + 5, + ); - assertEquals( - await err(4) - .asyncOrElse((v) => Promise.resolve(ok(v - 1))) - .unwrap(), - 3, - ); + assertEquals( + await err(4) + .asyncOrElse((v) => Promise.resolve(ok(v - 1))) + .unwrap(), + 3, + ); }); Deno.test("Result.asyncUnwrapOrElse", async () => { - assertEquals(await ok(4).asyncUnwrapOrElse(() => Promise.resolve(0)), 4); - assertEquals( - await err(4).asyncUnwrapOrElse((v) => Promise.resolve(v - 1)), - 3, - ); + assertEquals(await ok(4).asyncUnwrapOrElse(() => Promise.resolve(0)), 4); + assertEquals( + await err(4).asyncUnwrapOrElse((v) => Promise.resolve(v - 1)), + 3, + ); }); Deno.test("Result.asyncMatch", async () => { - const okFn = spy(); - const errFn = spy(); + const okFn = spy(); + const errFn = spy(); - await ok(4).asyncMatch(okFn, errFn); + await ok(4).asyncMatch(okFn, errFn); - assertSpyCall(okFn, 0, { args: [4] }); - assertSpyCalls(errFn, 0); + assertSpyCall(okFn, 0, { args: [4] }); + assertSpyCalls(errFn, 0); - await err(-1).asyncMatch(okFn, errFn); + await err(-1).asyncMatch(okFn, errFn); - assertSpyCalls(okFn, 1); - assertSpyCall(errFn, 0, { args: [-1] }); + assertSpyCalls(okFn, 1); + assertSpyCall(errFn, 0, { args: [-1] }); }); Deno.test("chaining", async () => { - assertEquals( - await okAsync(4) - .asyncMap((v) => Promise.resolve(v + 1)) - .andThen((v) => ok(v + 1)) - .map((v) => v + 1) - .unwrap(), - 7, - ); + assertEquals( + await okAsync(4) + .asyncMap((v) => Promise.resolve(v + 1)) + .andThen((v) => ok(v + 1)) + .map((v) => v + 1) + .unwrap(), + 7, + ); }); diff --git a/deno.json b/deno.json index 97d4bbc..a9fa52e 100644 --- a/deno.json +++ b/deno.json @@ -1,17 +1,14 @@ { - "tasks": { - "test": "deno test --allow-read --allow-env --allow-sys" - }, - "imports": { - "@david/dax": "jsr:@david/dax@^0.42.0", - "@deno/dnt": "jsr:@deno/dnt@^0.41.3", - "@std/assert": "jsr:@std/assert@^1.0.6", - "@std/testing": "jsr:@std/testing@^1.0.3" - }, - "fmt": { - "useTabs": true - }, - "workspace": ["./eslint-plugin", "./core"], - "lock": false, - "nodeModulesDir": "auto" + "tasks": { + "test": "deno test --allow-read --allow-env --allow-sys" + }, + "imports": { + "@david/dax": "jsr:@david/dax@^0.42.0", + "@deno/dnt": "jsr:@deno/dnt@^0.41.3", + "@std/assert": "jsr:@std/assert@^1.0.6", + "@std/testing": "jsr:@std/testing@^1.0.3" + }, + "workspace": ["./eslint-plugin", "./core"], + "lock": false, + "nodeModulesDir": "auto" } diff --git a/eslint-plugin/README.md b/eslint-plugin/README.md index a6f27c2..49df260 100644 --- a/eslint-plugin/README.md +++ b/eslint-plugin/README.md @@ -17,9 +17,9 @@ npm install --save-dev eslint @eslint/js typescript typescript-eslint eslint-plu ```json { - "compilerOptions": { - "strict": true - } + "compilerOptions": { + "strict": true + } } ``` @@ -31,9 +31,9 @@ import resulto from "eslint-plugin-resulto"; import ts from "typescript-eslint"; export default ts.config( - js.configs.recommended, - ...ts.configs.recommended, - resulto.configs.recommended, + js.configs.recommended, + ...ts.configs.recommended, + resulto.configs.recommended, ); ``` @@ -51,14 +51,14 @@ To make this work in TypeScript without making ESLint or tsc angry you need to: ```js export default ts.config({ - rules: { - "@typescript-eslint/no-unused-vars": [ - "warn", - { - varsIgnorePattern: "^_", - }, - ], - }, + rules: { + "@typescript-eslint/no-unused-vars": [ + "warn", + { + varsIgnorePattern: "^_", + }, + ], + }, }); ``` diff --git a/eslint-plugin/build_npm.ts b/eslint-plugin/build_npm.ts index e72c6ec..3fd71e3 100644 --- a/eslint-plugin/build_npm.ts +++ b/eslint-plugin/build_npm.ts @@ -4,35 +4,35 @@ import denoJson from "./deno.json" with { type: "json" }; await emptyDir("./npm"); await build({ - entryPoints: ["./mod.ts"], - outDir: "./npm", - shims: {}, - test: false, - package: { - name: "eslint-plugin-resulto", - version: denoJson.version, - description: "ESLint plugin for resulto", - author: "adjsky", - repository: { - "type": "git", - "url": "git+https://github.com/adjsky/resulto.git", - }, - keywords: [ - "result", - "rust", - "eslint", - "plugin", - ], - license: "MIT", - peerDependencies: { - "@typescript-eslint/parser": "8.x", - }, - dependencies: { - "@typescript-eslint/utils": "^8.5.0", - }, - }, - postBuild() { - Deno.copyFileSync("../LICENSE", "npm/LICENSE"); - Deno.copyFileSync("README.md", "npm/README.md"); - }, + entryPoints: ["./mod.ts"], + outDir: "./npm", + shims: {}, + test: false, + package: { + name: "eslint-plugin-resulto", + version: denoJson.version, + description: "ESLint plugin for resulto", + author: "adjsky", + repository: { + "type": "git", + "url": "git+https://github.com/adjsky/resulto.git", + }, + keywords: [ + "result", + "rust", + "eslint", + "plugin", + ], + license: "MIT", + peerDependencies: { + "@typescript-eslint/parser": "8.x", + }, + dependencies: { + "@typescript-eslint/utils": "^8.5.0", + }, + }, + postBuild() { + Deno.copyFileSync("../LICENSE", "npm/LICENSE"); + Deno.copyFileSync("README.md", "npm/README.md"); + }, }); diff --git a/eslint-plugin/deno.json b/eslint-plugin/deno.json index 2b0fd88..5964a21 100644 --- a/eslint-plugin/deno.json +++ b/eslint-plugin/deno.json @@ -1,13 +1,13 @@ { - "name": "@resulto/eslint-plugin", - "version": "2.1.0", - "exports": { - ".": "./mod.ts" - }, - "imports": { - "@typescript-eslint/parser": "npm:@typescript-eslint/parser@^8.12.2", - "@typescript-eslint/rule-tester": "npm:@typescript-eslint/rule-tester@^8.12.2", - "@typescript-eslint/utils": "npm:@typescript-eslint/utils@^8.12.2", - "typescript": "npm:typescript@^5.6.3" - } + "name": "@resulto/eslint-plugin", + "version": "2.1.0", + "exports": { + ".": "./mod.ts" + }, + "imports": { + "@typescript-eslint/parser": "npm:@typescript-eslint/parser@^8.12.2", + "@typescript-eslint/rule-tester": "npm:@typescript-eslint/rule-tester@^8.12.2", + "@typescript-eslint/utils": "npm:@typescript-eslint/utils@^8.12.2", + "typescript": "npm:typescript@^5.6.3" + } } diff --git a/eslint-plugin/mod.ts b/eslint-plugin/mod.ts index 171ffd8..50b08c5 100644 --- a/eslint-plugin/mod.ts +++ b/eslint-plugin/mod.ts @@ -4,28 +4,28 @@ import denoJson from "./deno.json" with { type: "json" }; import mustUseResult from "./rules/must_use_result.ts"; const plugin: FlatConfig.Plugin = { - configs: { - get recommended() { - return recommended; - }, - }, - meta: { - name: "eslint-plugin-resulto", - version: denoJson.version, - }, - rules: { - "must-use-result": mustUseResult, - }, + configs: { + get recommended() { + return recommended; + }, + }, + meta: { + name: "eslint-plugin-resulto", + version: denoJson.version, + }, + rules: { + "must-use-result": mustUseResult, + }, }; const recommended: FlatConfig.Config = { - name: "name/recommended", - plugins: { - resulto: plugin, - }, - rules: { - "resulto/must-use-result": "error", - }, + name: "name/recommended", + plugins: { + resulto: plugin, + }, + rules: { + "resulto/must-use-result": "error", + }, }; export default plugin; diff --git a/eslint-plugin/rules/must_use_result.ts b/eslint-plugin/rules/must_use_result.ts index 66e1cbe..6ba20c1 100644 --- a/eslint-plugin/rules/must_use_result.ts +++ b/eslint-plugin/rules/must_use_result.ts @@ -1,154 +1,154 @@ import { ESLintUtils } from "@typescript-eslint/utils"; import type { - ParserServicesWithTypeInformation, - TSESTree, + ParserServicesWithTypeInformation, + TSESTree, } from "@typescript-eslint/utils"; import type { TypeChecker, TypeReference } from "typescript"; const ruleCreator = ESLintUtils.RuleCreator( - (name) => - `https://github.com/adjsky/resulto/tree/master/docs/eslint/${name}.md`, + (name) => + `https://github.com/adjsky/resulto/tree/master/docs/eslint/${name}.md`, ); const rule = ruleCreator({ - name: "must-use-result", - meta: { - docs: { - description: "Result must be used to make sure errors are handled.", - }, - type: "problem", - messages: { - mustUse: "`Result` may be an `Err` variant, which should be handled.", - }, - schema: [], - }, - defaultOptions: [], - create(context) { - const parserServices = ESLintUtils.getParserServices(context); - const typeChecker = parserServices.program.getTypeChecker(); - - return { - "CallExpression,NewExpression"( - node: TSESTree.CallExpression | TSESTree.NewExpression, - ) { - if (!isResult(node, parserServices, typeChecker)) { - return; - } - - if (isReturnedOrAssigned(node)) { - return; - } - - if (isMatched(node)) { - return; - } - - context.report({ - messageId: "mustUse", - node, - }); - }, - }; - }, + name: "must-use-result", + meta: { + docs: { + description: "Result must be used to make sure errors are handled.", + }, + type: "problem", + messages: { + mustUse: "`Result` may be an `Err` variant, which should be handled.", + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const parserServices = ESLintUtils.getParserServices(context); + const typeChecker = parserServices.program.getTypeChecker(); + + return { + "CallExpression,NewExpression"( + node: TSESTree.CallExpression | TSESTree.NewExpression, + ) { + if (!isResult(node, parserServices, typeChecker)) { + return; + } + + if (isReturnedOrAssigned(node)) { + return; + } + + if (isMatched(node)) { + return; + } + + context.report({ + messageId: "mustUse", + node, + }); + }, + }; + }, }); function isResult( - node: TSESTree.Node, - parserServices: ParserServicesWithTypeInformation, - typeChecker: TypeChecker, + node: TSESTree.Node, + parserServices: ParserServicesWithTypeInformation, + typeChecker: TypeChecker, ) { - const tsNodeMap = parserServices.esTreeNodeToTSNodeMap.get(node); - const type = typeChecker.getTypeAtLocation(tsNodeMap); + const tsNodeMap = parserServices.esTreeNodeToTSNodeMap.get(node); + const type = typeChecker.getTypeAtLocation(tsNodeMap); - const symbol = type.getSymbol() ?? type.aliasSymbol; + const symbol = type.getSymbol() ?? type.aliasSymbol; - if (!symbol) { - return false; - } + if (!symbol) { + return false; + } - let symbolToCheck = typeChecker.symbolToString(symbol); + let symbolToCheck = typeChecker.symbolToString(symbol); - if (symbolToCheck == "Promise") { - const typeArgument = (type as TypeReference).typeArguments?.[0]; + if (symbolToCheck == "Promise") { + const typeArgument = (type as TypeReference).typeArguments?.[0]; - if (!typeArgument) { - return false; - } + if (!typeArgument) { + return false; + } - const resolvedSymbol = typeArgument.getSymbol() ?? - typeArgument.aliasSymbol; + const resolvedSymbol = typeArgument.getSymbol() ?? + typeArgument.aliasSymbol; - if (!resolvedSymbol) { - return false; - } + if (!resolvedSymbol) { + return false; + } - symbolToCheck = typeChecker.symbolToString(resolvedSymbol); - } + symbolToCheck = typeChecker.symbolToString(resolvedSymbol); + } - return ( - symbolToCheck == "Result" || - symbolToCheck == "AsyncResult" || - symbolToCheck == "Ok" || - symbolToCheck == "Err" - ); + return ( + symbolToCheck == "Result" || + symbolToCheck == "AsyncResult" || + symbolToCheck == "Ok" || + symbolToCheck == "Err" + ); } function isReturnedOrAssigned(node: TSESTree.Node) { - const { parent } = node; - - if (parent?.type == "AwaitExpression") { - return isReturnedOrAssigned(parent); - } - - if ( - parent?.type == "MemberExpression" || - (node.type == "MemberExpression" && parent) - ) { - return isReturnedOrAssigned(parent); - } - - if ( - parent?.type == "LogicalExpression" || - parent?.type == "ConditionalExpression" - ) { - return isReturnedOrAssigned(parent); - } - - return ( - (parent?.type == "VariableDeclarator" && parent.init == node) || - (parent?.type == "AssignmentExpression" && parent.right == node) || - (parent?.type == "ReturnStatement" && parent.argument == node) || - (parent?.type == "ArrowFunctionExpression" && parent.body == node) || - isArgument(node) - ); + const { parent } = node; + + if (parent?.type == "AwaitExpression") { + return isReturnedOrAssigned(parent); + } + + if ( + parent?.type == "MemberExpression" || + (node.type == "MemberExpression" && parent) + ) { + return isReturnedOrAssigned(parent); + } + + if ( + parent?.type == "LogicalExpression" || + parent?.type == "ConditionalExpression" + ) { + return isReturnedOrAssigned(parent); + } + + return ( + (parent?.type == "VariableDeclarator" && parent.init == node) || + (parent?.type == "AssignmentExpression" && parent.right == node) || + (parent?.type == "ReturnStatement" && parent.argument == node) || + (parent?.type == "ArrowFunctionExpression" && parent.body == node) || + isArgument(node) + ); } function isArgument(node: TSESTree.Node) { - const { parent } = node; + const { parent } = node; - return ( - parent?.type == "CallExpression" || - parent?.type == "ArrayExpression" || - (parent?.type == "Property" && parent.parent.type == "ObjectExpression") - ); + return ( + parent?.type == "CallExpression" || + parent?.type == "ArrayExpression" || + (parent?.type == "Property" && parent.parent.type == "ObjectExpression") + ); } function isMatched({ parent }: TSESTree.Node) { - if ( - parent?.type == "MemberExpression" && - parent.property.type == "Identifier" && - (parent.property.name == "match" || - parent.property.name == "asyncMatch") - ) { - return true; - } - - if (!parent) { - return false; - } - - return isMatched(parent); + if ( + parent?.type == "MemberExpression" && + parent.property.type == "Identifier" && + (parent.property.name == "match" || + parent.property.name == "asyncMatch") + ) { + return true; + } + + if (!parent) { + return false; + } + + return isMatched(parent); } export default rule; diff --git a/eslint-plugin/rules/must_use_result_test.ts b/eslint-plugin/rules/must_use_result_test.ts index 718653d..82774cb 100644 --- a/eslint-plugin/rules/must_use_result_test.ts +++ b/eslint-plugin/rules/must_use_result_test.ts @@ -2,7 +2,7 @@ import rule from "../rules/must_use_result.ts"; import { tester, trimLeadingIndent } from "../test/helpers.ts"; function injectCode(code: string) { - return ` + return ` interface Result { chain(): Result @@ -62,10 +62,10 @@ ${trimLeadingIndent(code)} } tester.run("must-use-result", rule, { - valid: [ - { - name: "assign a sync result and match it", - code: injectCode(` + valid: [ + { + name: "assign a sync result and match it", + code: injectCode(` const okResult = ok(5) const errResult = err(5) @@ -75,18 +75,18 @@ tester.run("must-use-result", rule, { err(5).match(noop, noop) err(5).asyncMatch(noop, noop) `), - }, - { - name: "create a sync result using class constructor and assign it", - code: injectCode(` + }, + { + name: "create a sync result using class constructor and assign it", + code: injectCode(` const okResult = new Ok(5) const errResult = new Err(5) `), - }, + }, - { - name: "assign an async result and match it", - code: injectCode(` + { + name: "assign an async result and match it", + code: injectCode(` const okAsyncResult = okAsync(5) const errAsyncResult = errAsync(5) @@ -96,11 +96,11 @@ tester.run("must-use-result", rule, { errAsync(5).match(noop, noop) errAsync(5).asyncMatch(noop, noop) `), - }, + }, - { - name: "assign an async result and match it, using await", - code: injectCode(` + { + name: "assign an async result and match it, using await", + code: injectCode(` const okAsyncResult = await okAsync(5) const errAsyncResult = await errAsync(5) @@ -110,139 +110,139 @@ tester.run("must-use-result", rule, { (await errAsync(5)).match(noop, noop) (await errAsync(5)).asyncMatch(noop, noop) `), - }, + }, - { - name: "return a sync result in a sync function and assign it", - code: injectCode(` + { + name: "return a sync result in a sync function and assign it", + code: injectCode(` const okResult = getOk() const errResult = getErr() `), - }, + }, - { - name: "return an async result in a sync function and assign it", - code: injectCode(` + { + name: "return an async result in a sync function and assign it", + code: injectCode(` const okAsyncResult = getOkAsync() const errAsyncResult = getErrAsync() `), - }, + }, - { - name: "return a sync result in an async function and assign it", - code: injectCode(` + { + name: "return a sync result in an async function and assign it", + code: injectCode(` const okResultPromise = asyncGetOk() const errResultPromise = asyncGetErr() `), - }, + }, - { - name: "return an async result in an async function and assign it", - code: injectCode(` + { + name: "return an async result in an async function and assign it", + code: injectCode(` const okAsyncResultPromise = asyncGetAsyncOk() const errAsyncResultPromise = asyncGetAsyncOk() `), - }, + }, - { - name: "return a sync result in an async function, await and assign it", - code: injectCode(` + { + name: "return a sync result in an async function, await and assign it", + code: injectCode(` const okResult = await asyncGetOk() const errResult = await asyncGetErr() `), - }, + }, - { - name: "return an async result in an async function, await and assign it", - code: injectCode(` + { + name: "return an async result in an async function, await and assign it", + code: injectCode(` const okAsyncResult = await asyncGetAsyncOk() const errAsyncResult = await asyncGetAsyncErr() `), - }, + }, - { - name: "create a sync result and immediately pass it as an argument", - code: injectCode(` + { + name: "create a sync result and immediately pass it as an argument", + code: injectCode(` function doSomething(res: Result) { // } doSomething(ok(4)) doSomething(err(5)) `), - }, + }, - { - name: "create results in an array expression, assigned to a variable", - code: injectCode(` + { + name: "create results in an array expression, assigned to a variable", + code: injectCode(` const _ = [ok(1), err(2), okAsync(3), errAsync(4)] `), - }, - { - name: "create results in an array expression", - code: injectCode(` + }, + { + name: "create results in an array expression", + code: injectCode(` [ok(3), err(5), okAsync(2), errAsync(0)] `), - }, + }, - { - name: - "create a sync result in an object expression, assigned to a variable", - code: injectCode(` + { + name: + "create a sync result in an object expression, assigned to a variable", + code: injectCode(` const _ = { res: ok(2) } `), - }, + }, - { - name: "create a sync result and return it from an arrow function", - code: injectCode(` + { + name: "create a sync result and return it from an arrow function", + code: injectCode(` () => ok(1) `), - }, + }, - { - name: "create a sync result and return it from a regular function", - code: injectCode(` + { + name: "create a sync result and return it from a regular function", + code: injectCode(` function fn() { return ok(1) } `), - }, + }, - { - name: "create a sync result and assign it to a declared variable", - code: injectCode(` + { + name: "create a sync result and assign it to a declared variable", + code: injectCode(` let res res = ok(1) `), - }, + }, - { - name: "do stuff with results in logical expressions and assign them", - code: injectCode(` + { + name: "do stuff with results in logical expressions and assign them", + code: injectCode(` let someBool = false const _ = someBool ? ok(4) : err(2) const _ = someBool && ok(4) const _ = someBool || err(4) const _ = someBool ?? ok(2) `), - }, - { - name: - "do stuff with results in logical expressions and assign them to a declared variable", - code: injectCode(` + }, + { + name: + "do stuff with results in logical expressions and assign them to a declared variable", + code: injectCode(` let res res = false ? ok(4) : err(2) res = true && ok(4) res = false || err(4) res = true ?? ok(2) `), - }, + }, - { - name: "pass results as arguments using logical expressions", - code: injectCode(` + { + name: "pass results as arguments using logical expressions", + code: injectCode(` function fn(res: Result) { // } @@ -250,122 +250,122 @@ tester.run("must-use-result", rule, { fn(false && ok(4)) fn(false ? ok(4) : err(5)) `), - }, + }, - { - name: - "return a result from a sync function after calling some method on it returning another result", - code: injectCode(` + { + name: + "return a result from a sync function after calling some method on it returning another result", + code: injectCode(` function test() { return ok(4).chain() } `), - }, - { - name: - "return a result from an async function after calling some method on it returning another result", - code: injectCode(` + }, + { + name: + "return a result from an async function after calling some method on it returning another result", + code: injectCode(` async function test() { return await okAsync(4).chain() } `), - }, - ], - invalid: [ - { - name: "create a sync result but don't assign it", - code: injectCode(` + }, + ], + invalid: [ + { + name: "create a sync result but don't assign it", + code: injectCode(` ok(5) err(5) `), - errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], - }, + errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], + }, - { - name: "create a sync result using class constructor but don't assign it", - code: injectCode(` + { + name: "create a sync result using class constructor but don't assign it", + code: injectCode(` new Ok(5) new Err(5) `), - errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], - }, + errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], + }, - { - name: "create an async result but don't assign it", - code: injectCode(` + { + name: "create an async result but don't assign it", + code: injectCode(` okAsync(5) errAsync(5) `), - errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], - }, + errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], + }, - { - name: "create an async result, await it, but don't assign it", - code: injectCode(` + { + name: "create an async result, await it, but don't assign it", + code: injectCode(` await okAsync(5) await errAsync(5) `), - errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], - }, + errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], + }, - { - name: - "call a sync function, returning a sync result, but don't assign it", - code: injectCode(` + { + name: + "call a sync function, returning a sync result, but don't assign it", + code: injectCode(` getOk() getErr() `), - errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], - }, + errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], + }, - { - name: - "call a sync function, returning an async result, but don't assign it", - code: injectCode(` + { + name: + "call a sync function, returning an async result, but don't assign it", + code: injectCode(` getAsyncOk() getAsyncErr() `), - errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], - }, + errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], + }, - { - name: - "call an async function, returning a sync result, but don't assign it", - code: injectCode(` + { + name: + "call an async function, returning a sync result, but don't assign it", + code: injectCode(` asyncGetOk() asyncGetErr() `), - errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], - }, + errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], + }, - { - name: - "call an async function, returning an async result, but don't assign it", - code: injectCode(` + { + name: + "call an async function, returning an async result, but don't assign it", + code: injectCode(` asyncGetAsyncOk() asyncGetAsyncOk() `), - errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], - }, + errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], + }, - { - name: - "call an async function, returning a sync result, await it, but don't assign it", - code: injectCode(` + { + name: + "call an async function, returning a sync result, await it, but don't assign it", + code: injectCode(` await asyncGetOk() await asyncGetErr() `), - errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], - }, + errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], + }, - { - name: - "call an async function, returning an async result, await it, but don't assign it", - code: injectCode(` + { + name: + "call an async function, returning an async result, await it, but don't assign it", + code: injectCode(` await asyncGetAsyncOk() await asyncGetAsyncErr() `), - errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], - }, - ], + errors: [{ messageId: "mustUse" }, { messageId: "mustUse" }], + }, + ], }); diff --git a/eslint-plugin/test/fixture/tsconfig.json b/eslint-plugin/test/fixture/tsconfig.json index 67563bb..2ce472f 100644 --- a/eslint-plugin/test/fixture/tsconfig.json +++ b/eslint-plugin/test/fixture/tsconfig.json @@ -1,6 +1,6 @@ { - "compilerOptions": { - "strict": true - }, - "include": ["file.ts"] + "compilerOptions": { + "strict": true + }, + "include": ["file.ts"] } diff --git a/eslint-plugin/test/helpers.ts b/eslint-plugin/test/helpers.ts index 04e75d4..08a5947 100644 --- a/eslint-plugin/test/helpers.ts +++ b/eslint-plugin/test/helpers.ts @@ -7,20 +7,20 @@ RuleTester.it = it; RuleTester.itOnly = it.only; export const tester = new RuleTester({ - languageOptions: { - parserOptions: { - tsconfigRootDir: `${import.meta.dirname}/fixture`, - project: "./tsconfig.json", - }, - }, + languageOptions: { + parserOptions: { + tsconfigRootDir: `${import.meta.dirname}/fixture`, + project: "./tsconfig.json", + }, + }, }); export function trimLeadingIndent(str: string) { - const matched = str.match(/^[\r\n]?(\s+)/); + const matched = str.match(/^[\r\n]?(\s+)/); - if (!matched) { - return str; - } + if (!matched) { + return str; + } - return str.replace(new RegExp("^" + matched[1], "gm"), "").trim(); + return str.replace(new RegExp("^" + matched[1], "gm"), "").trim(); } diff --git a/publish_npm.ts b/publish_npm.ts index d826382..0eaebb3 100644 --- a/publish_npm.ts +++ b/publish_npm.ts @@ -2,6 +2,6 @@ import denoJson from "./deno.json" with { type: "json" }; import { $ } from "@david/dax"; for (const cwd of denoJson.workspace) { - await $`cd ${cwd} && deno run -A ./build_npm.ts`; - await $`cd ${cwd}/npm && npm publish`; + await $`cd ${cwd} && deno run -A ./build_npm.ts`; + await $`cd ${cwd}/npm && npm publish`; }