From d08fd8de355ae91846e27a3504380690ca703ebc Mon Sep 17 00:00:00 2001 From: Jason Weinzierl Date: Wed, 13 Nov 2024 16:03:52 -0600 Subject: [PATCH 1/5] feat(throw-error)!: stop linting throw statements --- README.md | 2 +- docs/rules/throw-error.md | 30 +------ src/rules/throw-error.ts | 35 ++++---- tests/rules/throw-error.test.ts | 138 +++++++++++++++----------------- 4 files changed, 91 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index 7b819887..28373293 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,6 @@ The package includes the following rules. | [no-unsafe-takeuntil](docs/rules/no-unsafe-takeuntil.md) | Disallow applying operators after `takeUntil`. | ✅ | | | 💭 | | | [prefer-observer](docs/rules/prefer-observer.md) | Disallow passing separate handlers to `subscribe` and `tap`. | | 🔧 | 💡 | 💭 | | | [suffix-subjects](docs/rules/suffix-subjects.md) | Enforce the use of a suffix in subject identifiers. | | | | 💭 | | -| [throw-error](docs/rules/throw-error.md) | Enforce passing only `Error` values to error notifications. | | | | 💭 | | +| [throw-error](docs/rules/throw-error.md) | Enforce passing only `Error` values to `throwError`. | | | | 💭 | | diff --git a/docs/rules/throw-error.md b/docs/rules/throw-error.md index 2764f87c..749358fe 100644 --- a/docs/rules/throw-error.md +++ b/docs/rules/throw-error.md @@ -1,49 +1,27 @@ -# Enforce passing only `Error` values to error notifications (`rxjs-x/throw-error`) +# Enforce passing only `Error` values to `throwError` (`rxjs-x/throw-error`) 💭 This rule requires [type information](https://typescript-eslint.io/linting/typed-linting). -This rule forbids throwing values that are neither `Error` nor `DOMException` instances. +This rule forbids passing values that are not `Error` objects to `throwError`. +It's similar to the typescript-eslint [`only-throw-error`](https://typescript-eslint.io/rules/only-throw-error/) rule, +but is for the `throwError` Observable creation function - not `throw` statements. ## Rule details Examples of **incorrect** code for this rule: -```ts -throw "Kaboom!"; -``` - ```ts import { throwError } from "rxjs"; -throwError("Kaboom!"); -``` -```ts -import { throwError } from "rxjs"; throwError(() => "Kaboom!"); ``` Examples of **correct** code for this rule: -```ts -throw new Error("Kaboom!"); -``` - -```ts -throw new RangeError("Kaboom!"); -``` - -```ts -throw new DOMException("Kaboom!"); -``` - ```ts import { throwError } from "rxjs"; -throwError(new Error("Kaboom!")); -``` -```ts -import { throwError } from "rxjs"; throwError(() => new Error("Kaboom!")); ``` diff --git a/src/rules/throw-error.ts b/src/rules/throw-error.ts index beb6cfae..f2467034 100644 --- a/src/rules/throw-error.ts +++ b/src/rules/throw-error.ts @@ -9,11 +9,11 @@ export const throwErrorRule = ruleCreator({ meta: { docs: { description: - 'Enforce passing only `Error` values to error notifications.', + 'Enforce passing only `Error` values to `throwError`.', requiresTypeChecking: true, }, messages: { - forbidden: 'Passing non-Error values are forbidden.', + forbidden: 'Passing non-Error values is forbidden.', }, schema: [], type: 'problem', @@ -23,33 +23,40 @@ export const throwErrorRule = ruleCreator({ const { esTreeNodeToTSNodeMap, program, getTypeAtLocation } = ESLintUtils.getParserServices(context); const { couldBeObservable } = getTypeServices(context); - function checkNode(node: es.Node) { + function checkThrowArgument(node: es.Node) { let type = getTypeAtLocation(node); + if (couldBeFunction(type)) { const tsNode = esTreeNodeToTSNodeMap.get(node); const annotation = (tsNode as ts.ArrowFunction).type; const body = (tsNode as ts.ArrowFunction).body; type = program.getTypeChecker().getTypeAtLocation(annotation ?? body); } - if ( - !tsutils.isIntrinsicAnyType(type) - && !tsutils.isIntrinsicUnknownType(type) - && !couldBeType(type, /^(Error|DOMException)$/) - ) { - context.report({ - messageId: 'forbidden', - node, - }); + + if (tsutils.isIntrinsicAnyType(type)) { + return; + } + + if (tsutils.isIntrinsicUnknownType(type)) { + return; } + + if (couldBeType(type, /^Error$/)) { + return; + } + + context.report({ + messageId: 'forbidden', + node, + }); } return { - 'ThrowStatement > *': checkNode, 'CallExpression[callee.name=\'throwError\']': (node: es.CallExpression) => { if (couldBeObservable(node)) { const [arg] = node.arguments; if (arg) { - checkNode(arg); + checkThrowArgument(arg); } } }, diff --git a/tests/rules/throw-error.test.ts b/tests/rules/throw-error.test.ts index 590821cd..dd45c755 100644 --- a/tests/rules/throw-error.test.ts +++ b/tests/rules/throw-error.test.ts @@ -6,45 +6,32 @@ import { ruleTester } from '../rule-tester'; ruleTester({ types: true }).run('throw-error', throwErrorRule, { valid: [ stripIndent` - // throw Error - const a = () => { throw new Error("error"); }; - `, - stripIndent` - // throw DOMException - const a = () => { throw new DOMException("error"); }; - `, - stripIndent` - // throw any - const a = () => { throw "error" as any }; - `, - stripIndent` - // throw returned any - const a = () => { throw errorMessage(); }; + // Error + import { throwError } from "rxjs"; - function errorMessage(): any { - return "error"; - } + const ob1 = throwError(new Error("Boom!")); `, stripIndent` - // throwError Error + // RangeError import { throwError } from "rxjs"; - const ob1 = throwError(new Error("Boom!")); + const ob1 = throwError(new RangeError("Boom!")); `, stripIndent` - // throwError DOMException + // DOMException + /// import { throwError } from "rxjs"; const ob1 = throwError(new DOMException("Boom!")); `, stripIndent` - // throwError any + // any import { throwError } from "rxjs"; const ob1 = throwError("Boom!" as any); `, stripIndent` - // throwError returned any + // returned any import { throwError } from "rxjs"; const ob1 = throwError(errorMessage()); @@ -54,13 +41,13 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { } `, stripIndent` - // throwError unknown + // unknown import { throwError } from "rxjs"; const ob1 = throwError("Boom!" as unknown); `, stripIndent` - // throwError returned unknown + // returned unknown import { throwError } from "rxjs"; const ob1 = throwError(errorMessage()); @@ -70,25 +57,26 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { } `, stripIndent` - // throwError Error with factory + // Error with factory import { throwError } from "rxjs"; const ob1 = throwError(() => new Error("Boom!")); `, stripIndent` - // throwError DOMException with factory + // DOMException with factory + /// import { throwError } from "rxjs"; const ob1 = throwError(() => new DOMException("Boom!")); `, stripIndent` - // throwError any with factory + // any with factory import { throwError } from "rxjs"; const ob1 = throwError(() => "Boom!" as any); `, stripIndent` - // throwError returned any with factory + // returned any with factory import { throwError } from "rxjs"; const ob1 = throwError(() => errorMessage()); @@ -97,6 +85,16 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { return "error"; } `, + stripIndent` + // subtype with Object.assign + // https://github.com/cartant/rxjs-tslint-rules/issues/86 + import { throwError } from "rxjs"; + + throwError(() => Object.assign( + new Error("Not Found"), + { code: "NOT_FOUND" } + )); + `, stripIndent` // no signature // There will be no signature for callback and @@ -105,6 +103,26 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { callback(); `, stripIndent` + // unrelated throw statements (use @typescript-eslint/only-throw-error instead). + const a = () => { throw "error"; }; + const b = () => { throw new Error("error"); }; + + const errorMessage = "Boom!"; + const c = () => { throw errorMessage; }; + + const d = () => { throw errorMessage(); }; + function errorMessage() { + return "error"; + } + + const e = () => { throw new DOMException("error"); }; + const f = () => { throw "error" as any }; + + const g = () => { throw errorMessageAny(); }; + function errorMessageAny(): any { + return "error"; + } + https://github.com/cartant/rxjs-tslint-rules/issues/85 try { throw new Error("error"); @@ -116,34 +134,7 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { invalid: [ fromFixture( stripIndent` - // throw string - const a = () => { throw "error"; }; - ~~~~~~~ [forbidden] - `, - ), - fromFixture( - stripIndent` - // throw returned string - const a = () => { throw errorMessage(); }; - ~~~~~~~~~~~~~~ [forbidden] - - function errorMessage() { - return "error"; - } - `, - ), - fromFixture( - stripIndent` - // throw string variable - const errorMessage = "Boom!"; - - const a = () => { throw errorMessage; }; - ~~~~~~~~~~~~ [forbidden] - `, - ), - fromFixture( - stripIndent` - // throwError string + // string import { throwError } from "rxjs"; const ob1 = throwError("Boom!"); @@ -152,7 +143,7 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { ), fromFixture( stripIndent` - // throwError returned string + // returned string import { throwError } from "rxjs"; const ob1 = throwError(errorMessage()); @@ -165,21 +156,7 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { ), fromFixture( stripIndent` - https://github.com/cartant/rxjs-tslint-rules/issues/86 - const a = () => { throw "error"; }; - ~~~~~~~ [forbidden] - const b = () => { throw new Error("error"); }; - const c = () => { - throw Object.assign( - new Error("Not Found"), - { code: "NOT_FOUND" } - ); - }; - `, - ), - fromFixture( - stripIndent` - // throwError string with factory + // string with factory import { throwError } from "rxjs"; const ob1 = throwError(() => "Boom!"); @@ -188,7 +165,7 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { ), fromFixture( stripIndent` - // throwError returned string with factory + // returned string with factory import { throwError } from "rxjs"; const ob1 = throwError(() => errorMessage()); @@ -199,5 +176,20 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { } `, ), + fromFixture( + stripIndent` + // falsy + import { throwError } from "rxjs"; + + const ob1 = throwError(() => 0); + ~~~~~~~ [forbidden] + const ob2 = throwError(() => false); + ~~~~~~~~~~~ [forbidden] + const ob3 = throwError(() => null); + ~~~~~~~~~~ [forbidden] + const ob4 = throwError(() => undefined); + ~~~~~~~~~~~~~~~ [forbidden] + `, + ), ], }); From 0e974743cfd0a213b41d0513174779fcecfefbb4 Mon Sep 17 00:00:00 2001 From: Jason Weinzierl Date: Wed, 13 Nov 2024 16:35:55 -0600 Subject: [PATCH 2/5] feat(throw-error): option to disallow any and unknown in throw-error Also reworked tests to prioritize testing factory because rxjs has deprecated non-factory. --- docs/rules/throw-error.md | 11 +++++ src/rules/throw-error.ts | 23 ++++++++-- tests/rules/throw-error.test.ts | 80 ++++++++++++++++++++++----------- 3 files changed, 84 insertions(+), 30 deletions(-) diff --git a/docs/rules/throw-error.md b/docs/rules/throw-error.md index 749358fe..924b74f3 100644 --- a/docs/rules/throw-error.md +++ b/docs/rules/throw-error.md @@ -25,3 +25,14 @@ import { throwError } from "rxjs"; throwError(() => new Error("Kaboom!")); ``` + +## Options + + + +| Name | Description | Type | Default | +| :--------------------- | :---------------------------------------------------------- | :------ | :------ | +| `allowThrowingAny` | Whether to always allow throwing values typed as `any`. | Boolean | `true` | +| `allowThrowingUnknown` | Whether to always allow throwing values typed as `unknown`. | Boolean | `true` | + + diff --git a/src/rules/throw-error.ts b/src/rules/throw-error.ts index f2467034..2693ea67 100644 --- a/src/rules/throw-error.ts +++ b/src/rules/throw-error.ts @@ -4,8 +4,13 @@ import ts from 'typescript'; import { couldBeFunction, couldBeType, getTypeServices } from '../etc'; import { ruleCreator } from '../utils'; +const defaultOptions: readonly { + allowThrowingAny?: boolean; + allowThrowingUnknown?: boolean; +}[] = []; + export const throwErrorRule = ruleCreator({ - defaultOptions: [], + defaultOptions, meta: { docs: { description: @@ -15,13 +20,23 @@ export const throwErrorRule = ruleCreator({ messages: { forbidden: 'Passing non-Error values is forbidden.', }, - schema: [], + schema: [ + { + properties: { + allowThrowingAny: { type: 'boolean', default: true, description: 'Whether to always allow throwing values typed as `any`.' }, + allowThrowingUnknown: { type: 'boolean', default: true, description: 'Whether to always allow throwing values typed as `unknown`.' }, + }, + type: 'object', + }, + ], type: 'problem', }, name: 'throw-error', create: (context) => { const { esTreeNodeToTSNodeMap, program, getTypeAtLocation } = ESLintUtils.getParserServices(context); const { couldBeObservable } = getTypeServices(context); + const [config = {}] = context.options; + const { allowThrowingAny = true, allowThrowingUnknown = true } = config; function checkThrowArgument(node: es.Node) { let type = getTypeAtLocation(node); @@ -33,11 +48,11 @@ export const throwErrorRule = ruleCreator({ type = program.getTypeChecker().getTypeAtLocation(annotation ?? body); } - if (tsutils.isIntrinsicAnyType(type)) { + if (allowThrowingAny && tsutils.isIntrinsicAnyType(type)) { return; } - if (tsutils.isIntrinsicUnknownType(type)) { + if (allowThrowingUnknown && tsutils.isIntrinsicUnknownType(type)) { return; } diff --git a/tests/rules/throw-error.test.ts b/tests/rules/throw-error.test.ts index dd45c755..1e5c70c0 100644 --- a/tests/rules/throw-error.test.ts +++ b/tests/rules/throw-error.test.ts @@ -9,32 +9,40 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { // Error import { throwError } from "rxjs"; - const ob1 = throwError(new Error("Boom!")); + const ob1 = throwError(() => new Error("Boom!")); `, stripIndent` // RangeError import { throwError } from "rxjs"; - const ob1 = throwError(new RangeError("Boom!")); + const ob1 = throwError(() => new RangeError("Boom!")); `, stripIndent` // DOMException /// import { throwError } from "rxjs"; - const ob1 = throwError(new DOMException("Boom!")); + const ob1 = throwError(() => new DOMException("Boom!")); + `, + stripIndent` + // custom Error + import { throwError } from "rxjs"; + + class MyFailure extends Error {} + + const ob1 = throwError(() => new MyFailure("Boom!")); `, stripIndent` // any import { throwError } from "rxjs"; - const ob1 = throwError("Boom!" as any); + const ob1 = throwError(() => "Boom!" as any); `, stripIndent` // returned any import { throwError } from "rxjs"; - const ob1 = throwError(errorMessage()); + const ob1 = throwError(() => errorMessage()); function errorMessage(): any { return "error"; @@ -44,49 +52,49 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { // unknown import { throwError } from "rxjs"; - const ob1 = throwError("Boom!" as unknown); + const ob1 = throwError(() => "Boom!" as unknown); `, stripIndent` // returned unknown import { throwError } from "rxjs"; - const ob1 = throwError(errorMessage()); + const ob1 = throwError(() => errorMessage()); function errorMessage(): unknown { return "error"; } `, stripIndent` - // Error with factory + // Error without factory (deprecated) import { throwError } from "rxjs"; - const ob1 = throwError(() => new Error("Boom!")); + const ob1 = throwError(new Error("Boom!")); `, stripIndent` - // DOMException with factory + // DOMException without factory (deprecated) /// import { throwError } from "rxjs"; - const ob1 = throwError(() => new DOMException("Boom!")); + const ob1 = throwError(new DOMException("Boom!")); `, stripIndent` - // any with factory + // any without factory (deprecated) import { throwError } from "rxjs"; - const ob1 = throwError(() => "Boom!" as any); + const ob1 = throwError("Boom!" as any); `, stripIndent` - // returned any with factory + // returned any without factory (deprecated) import { throwError } from "rxjs"; - const ob1 = throwError(() => errorMessage()); + const ob1 = throwError(errorMessage()); function errorMessage(): any { return "error"; } `, stripIndent` - // subtype with Object.assign + // Object.assign // https://github.com/cartant/rxjs-tslint-rules/issues/86 import { throwError } from "rxjs"; @@ -137,8 +145,8 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { // string import { throwError } from "rxjs"; - const ob1 = throwError("Boom!"); - ~~~~~~~ [forbidden] + const ob1 = throwError(() => "Boom!"); + ~~~~~~~~~~~~~ [forbidden] `, ), fromFixture( @@ -146,8 +154,8 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { // returned string import { throwError } from "rxjs"; - const ob1 = throwError(errorMessage()); - ~~~~~~~~~~~~~~ [forbidden] + const ob1 = throwError(() => errorMessage()); + ~~~~~~~~~~~~~~~~~~~~ [forbidden] function errorMessage() { return "Boom!"; @@ -156,26 +164,46 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { ), fromFixture( stripIndent` - // string with factory + // string without factory (deprecated) import { throwError } from "rxjs"; - const ob1 = throwError(() => "Boom!"); - ~~~~~~~~~~~~~ [forbidden] + const ob1 = throwError("Boom!"); + ~~~~~~~ [forbidden] `, ), fromFixture( stripIndent` - // returned string with factory + // returned string without factory (deprecated) import { throwError } from "rxjs"; - const ob1 = throwError(() => errorMessage()); - ~~~~~~~~~~~~~~~~~~~~ [forbidden] + const ob1 = throwError(errorMessage()); + ~~~~~~~~~~~~~~ [forbidden] function errorMessage() { return "Boom!"; } `, ), + fromFixture( + stripIndent` + // any not allowed + import { throwError } from "rxjs"; + + throwError(() => "Boom!" as any); + ~~~~~~~~~~~~~~~~~~~~ [forbidden] + `, + { options: [{ allowThrowingAny: false }] }, + ), + fromFixture( + stripIndent` + // unknown not allowed + import { throwError } from "rxjs"; + + throwError(() => "Boom!" as unknown); + ~~~~~~~~~~~~~~~~~~~~~~~~ [forbidden] + `, + { options: [{ allowThrowingUnknown: false }] }, + ), fromFixture( stripIndent` // falsy From dbee8c6793d526be95453555695362f1e6cd96a6 Mon Sep 17 00:00:00 2001 From: Jason Weinzierl Date: Wed, 13 Nov 2024 16:40:40 -0600 Subject: [PATCH 3/5] fix(throw-error): report return type of arrow function --- src/rules/throw-error.ts | 5 ++++- tests/rules/throw-error.test.ts | 16 ++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/rules/throw-error.ts b/src/rules/throw-error.ts index 2693ea67..77ef4d15 100644 --- a/src/rules/throw-error.ts +++ b/src/rules/throw-error.ts @@ -40,8 +40,11 @@ export const throwErrorRule = ruleCreator({ function checkThrowArgument(node: es.Node) { let type = getTypeAtLocation(node); + let reportNode = node; if (couldBeFunction(type)) { + reportNode = (node as es.ArrowFunctionExpression).body ?? node; + const tsNode = esTreeNodeToTSNodeMap.get(node); const annotation = (tsNode as ts.ArrowFunction).type; const body = (tsNode as ts.ArrowFunction).body; @@ -62,7 +65,7 @@ export const throwErrorRule = ruleCreator({ context.report({ messageId: 'forbidden', - node, + node: reportNode, }); } diff --git a/tests/rules/throw-error.test.ts b/tests/rules/throw-error.test.ts index 1e5c70c0..42cdad75 100644 --- a/tests/rules/throw-error.test.ts +++ b/tests/rules/throw-error.test.ts @@ -146,7 +146,7 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { import { throwError } from "rxjs"; const ob1 = throwError(() => "Boom!"); - ~~~~~~~~~~~~~ [forbidden] + ~~~~~~~ [forbidden] `, ), fromFixture( @@ -155,7 +155,7 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { import { throwError } from "rxjs"; const ob1 = throwError(() => errorMessage()); - ~~~~~~~~~~~~~~~~~~~~ [forbidden] + ~~~~~~~~~~~~~~ [forbidden] function errorMessage() { return "Boom!"; @@ -190,7 +190,7 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { import { throwError } from "rxjs"; throwError(() => "Boom!" as any); - ~~~~~~~~~~~~~~~~~~~~ [forbidden] + ~~~~~~~~~~~~~~ [forbidden] `, { options: [{ allowThrowingAny: false }] }, ), @@ -200,7 +200,7 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { import { throwError } from "rxjs"; throwError(() => "Boom!" as unknown); - ~~~~~~~~~~~~~~~~~~~~~~~~ [forbidden] + ~~~~~~~~~~~~~~~~~~ [forbidden] `, { options: [{ allowThrowingUnknown: false }] }, ), @@ -210,13 +210,13 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { import { throwError } from "rxjs"; const ob1 = throwError(() => 0); - ~~~~~~~ [forbidden] + ~ [forbidden] const ob2 = throwError(() => false); - ~~~~~~~~~~~ [forbidden] + ~~~~~ [forbidden] const ob3 = throwError(() => null); - ~~~~~~~~~~ [forbidden] + ~~~~ [forbidden] const ob4 = throwError(() => undefined); - ~~~~~~~~~~~~~~~ [forbidden] + ~~~~~~~~~ [forbidden] `, ), ], From 25e95348a1f5ab1f0a837a37ea8fae6662ff5667 Mon Sep 17 00:00:00 2001 From: Jason Weinzierl Date: Wed, 13 Nov 2024 16:49:49 -0600 Subject: [PATCH 4/5] test(throw-error): evaluate different function bodies --- tests/rules/throw-error.test.ts | 36 +++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/rules/throw-error.test.ts b/tests/rules/throw-error.test.ts index 42cdad75..e1e4c2c1 100644 --- a/tests/rules/throw-error.test.ts +++ b/tests/rules/throw-error.test.ts @@ -32,6 +32,22 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { const ob1 = throwError(() => new MyFailure("Boom!")); `, + stripIndent` + // arrow function return + import { throwError } from "rxjs"; + + throwError(() => { + return new Error("Boom!"); + }); + `, + stripIndent` + // function return + import { throwError } from "rxjs"; + + throwError(function () { + return new Error("Boom!"); + }); + `, stripIndent` // any import { throwError } from "rxjs"; @@ -103,6 +119,17 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { { code: "NOT_FOUND" } )); `, + stripIndent` + // Object.assign arrow function return + import { throwError } from "rxjs"; + + throwError(() => { + return Object.assign( + new Error("Not Found"), + { code: "NOT_FOUND" } + ); + }); + `, stripIndent` // no signature // There will be no signature for callback and @@ -219,5 +246,14 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { ~~~~~~~~~ [forbidden] `, ), + fromFixture( + stripIndent` + // Object.assign with non-Error + import { throwError } from "rxjs"; + + throwError(() => Object.assign({ message: "Not Found" }, { code: "NOT_FOUND" })); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [forbidden] + `, + ), ], }); From a6a51e0738c39a162f4b98dc44a22d82f9917ea1 Mon Sep 17 00:00:00 2001 From: Jason Weinzierl Date: Fri, 15 Nov 2024 22:41:02 -0600 Subject: [PATCH 5/5] fix: also handle namespace import of throwError --- src/rules/throw-error.ts | 19 +++++++++++++------ tests/rules/throw-error.test.ts | 9 +++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/rules/throw-error.ts b/src/rules/throw-error.ts index 77ef4d15..75c63b6d 100644 --- a/src/rules/throw-error.ts +++ b/src/rules/throw-error.ts @@ -69,14 +69,21 @@ export const throwErrorRule = ruleCreator({ }); } + function checkNode(node: es.CallExpression) { + if (couldBeObservable(node)) { + const [arg] = node.arguments; + if (arg) { + checkThrowArgument(arg); + } + } + } + return { 'CallExpression[callee.name=\'throwError\']': (node: es.CallExpression) => { - if (couldBeObservable(node)) { - const [arg] = node.arguments; - if (arg) { - checkThrowArgument(arg); - } - } + checkNode(node); + }, + 'CallExpression[callee.property.name=\'throwError\']': (node: es.CallExpression) => { + checkNode(node); }, }; }, diff --git a/tests/rules/throw-error.test.ts b/tests/rules/throw-error.test.ts index e1e4c2c1..6c51c954 100644 --- a/tests/rules/throw-error.test.ts +++ b/tests/rules/throw-error.test.ts @@ -255,5 +255,14 @@ ruleTester({ types: true }).run('throw-error', throwErrorRule, { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [forbidden] `, ), + fromFixture( + stripIndent` + // namespace import + import * as Rx from "rxjs"; + + Rx.throwError(() => "Boom!"); + ~~~~~~~ [forbidden] + `, + ), ], });