diff --git a/README.md b/README.md index 6103011..f449b93 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ In addition to immutable rules this project also contains a few rules for enforc * [no-loop-statement](#no-loop-statement) * [no-throw](#no-throw) * [no-try](#no-try) + * [no-reject](#no-reject) * [Recommended built-in rules](#recommended-built-in-rules) ## Immutability rules @@ -451,7 +452,25 @@ function divide(x: number, y: number): number | Error { } ``` -And likewise for async functions: +Or in the case of an async function, a rejected promise should be returned. + +```typescript +async function divide(x: Promise, y: Promise): Promise { + const [xv, yv] = await Promise.all([x, y]); + + return yv === 0 + ? Promise.reject(new Error("Cannot divide by zero.")) + : xv / yv; +} +``` + +### no-try + +Try statements are not part of functional programming. See [no-throw](#no-throw) for more information. + +### no-reject + +You can view a `Promise` as a result object with built-in error (something like `{ value: number } | { error: Error }`) in which case a rejected `Promise` can be viewed a returned result and thus fits with functional programming. You can also view a rejected promise as something similar to an exception and as such something that does not fit with functional programming. If your view is the latter you can use the `no-reject` rule to disallow rejected promises. ```typescript async function divide( @@ -460,14 +479,16 @@ async function divide( ): Promise { const [xv, yv] = await Promise.all([x, y]); + // Rejecting the promise is not allowed so resolve to an Error instead + + // return yv === 0 + // ? Promise.reject(new Error("Cannot divide by zero.")) + // : xv / yv; + return yv === 0 ? new Error("Cannot divide by zero.") : xv / yv; } ``` -### no-try - -Try statements are not part of functional programming. See [no-throw](#no-throw) for more information. - ## Options ### Using the `ignore-local` option diff --git a/src/noRejectRule.ts b/src/noRejectRule.ts new file mode 100644 index 0000000..dc7d63b --- /dev/null +++ b/src/noRejectRule.ts @@ -0,0 +1,30 @@ +import * as ts from "typescript"; +import * as Lint from "tslint"; +import { + createInvalidNode, + CheckNodeResult, + createCheckNodeRule +} from "./shared/check-node"; + +type Options = {}; + +// tslint:disable-next-line:variable-name +export const Rule = createCheckNodeRule( + checkNode, + "Unexpected reject, return an error instead." +); + +function checkNode( + node: ts.Node, + _ctx: Lint.WalkContext +): CheckNodeResult { + if ( + ts.isPropertyAccessExpression(node) && + ts.isIdentifier(node.expression) && + node.expression.text === "Promise" && + node.name.text === "reject" + ) { + return { invalidNodes: [createInvalidNode(node, [])] }; + } + return { invalidNodes: [] }; +} diff --git a/src/noThrowRule.ts b/src/noThrowRule.ts index ef7a98a..dfb3f49 100644 --- a/src/noThrowRule.ts +++ b/src/noThrowRule.ts @@ -19,16 +19,7 @@ function checkNode( node: ts.Node, _ctx: Lint.WalkContext ): CheckNodeResult { - if (utils.isThrowStatement(node)) { - return { invalidNodes: [createInvalidNode(node, [])] }; - } - if ( - ts.isPropertyAccessExpression(node) && - ts.isIdentifier(node.expression) && - node.expression.text === "Promise" && - node.name.text === "reject" - ) { - return { invalidNodes: [createInvalidNode(node, [])] }; - } - return { invalidNodes: [] }; + return utils.isThrowStatement(node) + ? { invalidNodes: [createInvalidNode(node, [])] } + : { invalidNodes: [] }; } diff --git a/test/rules/no-reject/default/test.ts.lint b/test/rules/no-reject/default/test.ts.lint new file mode 100644 index 0000000..38e0d01 --- /dev/null +++ b/test/rules/no-reject/default/test.ts.lint @@ -0,0 +1,18 @@ +const error = new Error(); + +function foo(): Promise { + if (Math.random() > 0.5) { + return Promise.reject(new Error("bar")) + ~~~~~~~~~~~~~~ [failure] + } + return Promise.resolve(10) +} + +function bar(): Promise { + if (Math.random() > 0.5) { + return Promise.resolve(new Error("foo")) + } + return Promise.resolve(10) +} + +[failure]: Unexpected reject, return an error instead. diff --git a/test/rules/no-reject/default/tslint.json b/test/rules/no-reject/default/tslint.json new file mode 100644 index 0000000..bb2ca4e --- /dev/null +++ b/test/rules/no-reject/default/tslint.json @@ -0,0 +1,6 @@ +{ + "rulesDirectory": ["../../../../rules"], + "rules": { + "no-reject": true + } +} diff --git a/test/rules/no-throw/default/test.ts.lint b/test/rules/no-throw/default/test.ts.lint index b854774..7fc70e7 100644 --- a/test/rules/no-throw/default/test.ts.lint +++ b/test/rules/no-throw/default/test.ts.lint @@ -9,19 +9,4 @@ throw error; throw new Error(); ~~~~~~~~~~~~~~~~~~ [failure] -function foo(): Promise { - if (Math.random() > 0.5) { - return Promise.reject(new Error("bar")) - ~~~~~~~~~~~~~~ [failure] - } - return Promise.resolve(10) -} - -function bar(): Promise { - if (Math.random() > 0.5) { - return Promise.resolve(new Error("foo")) - } - return Promise.resolve(10) -} - [failure]: Unexpected throw, throwing exceptions is not functional.