diff --git a/README.md b/README.md index dd30b1d7..4a68a477 100644 --- a/README.md +++ b/README.md @@ -64,48 +64,49 @@ The package includes the following rules. πŸ’­ Requires [type information](https://typescript-eslint.io/linting/typed-linting).\ ❌ Deprecated. -| NameΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  | Description | πŸ’Ό | πŸ”§ | πŸ’‘ | πŸ’­ | ❌ | -| :--------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------- | :- | :- | :- | :- | :- | -| [ban-observables](docs/rules/ban-observables.md) | Disallow banned observable creators. | | | | | | -| [ban-operators](docs/rules/ban-operators.md) | Disallow banned operators. | | | | πŸ’­ | | -| [finnish](docs/rules/finnish.md) | Enforce Finnish notation. | | | | πŸ’­ | | -| [just](docs/rules/just.md) | Require the use of `just` instead of `of`. | | πŸ”§ | | | | -| [macro](docs/rules/macro.md) | Require the use of the RxJS Tools Babel macro. | | πŸ”§ | | | ❌ | -| [no-async-subscribe](docs/rules/no-async-subscribe.md) | Disallow passing `async` functions to `subscribe`. | βœ… | | | πŸ’­ | | -| [no-compat](docs/rules/no-compat.md) | Disallow the `rxjs-compat` package. | | | | | | -| [no-connectable](docs/rules/no-connectable.md) | Disallow operators that return connectable observables. | | | | πŸ’­ | | -| [no-create](docs/rules/no-create.md) | Disallow the static `Observable.create` function. | βœ… | | | πŸ’­ | | -| [no-cyclic-action](docs/rules/no-cyclic-action.md) | Disallow cyclic actions in effects and epics. | | | | πŸ’­ | | -| [no-explicit-generics](docs/rules/no-explicit-generics.md) | Disallow unnecessary explicit generic type arguments. | | | | | | -| [no-exposed-subjects](docs/rules/no-exposed-subjects.md) | Disallow public and protected subjects. | | | | πŸ’­ | | -| [no-finnish](docs/rules/no-finnish.md) | Disallow Finnish notation. | | | | πŸ’­ | | -| [no-ignored-error](docs/rules/no-ignored-error.md) | Disallow calling `subscribe` without specifying an error handler. | | | | πŸ’­ | | -| [no-ignored-notifier](docs/rules/no-ignored-notifier.md) | Disallow observables not composed from the `repeatWhen` or `retryWhen` notifier. | βœ… | | | πŸ’­ | | -| [no-ignored-observable](docs/rules/no-ignored-observable.md) | Disallow ignoring observables returned by functions. | | | | πŸ’­ | | -| [no-ignored-replay-buffer](docs/rules/no-ignored-replay-buffer.md) | Disallow using `ReplaySubject`, `publishReplay` or `shareReplay` without specifying the buffer size. | βœ… | | | | | -| [no-ignored-subscribe](docs/rules/no-ignored-subscribe.md) | Disallow calling `subscribe` without specifying arguments. | | | | πŸ’­ | | -| [no-ignored-subscription](docs/rules/no-ignored-subscription.md) | Disallow ignoring the subscription returned by `subscribe`. | | | | πŸ’­ | | -| [no-ignored-takewhile-value](docs/rules/no-ignored-takewhile-value.md) | Disallow ignoring the value within `takeWhile`. | βœ… | | | | | -| [no-implicit-any-catch](docs/rules/no-implicit-any-catch.md) | Disallow implicit `any` error parameters in `catchError` operators. | βœ… | πŸ”§ | πŸ’‘ | πŸ’­ | | -| [no-index](docs/rules/no-index.md) | Disallow importing index modules. | βœ… | | | | | -| [no-internal](docs/rules/no-internal.md) | Disallow importing internal modules. | βœ… | πŸ”§ | πŸ’‘ | | | -| [no-nested-subscribe](docs/rules/no-nested-subscribe.md) | Disallow calling `subscribe` within a `subscribe` callback. | βœ… | | | πŸ’­ | | -| [no-redundant-notify](docs/rules/no-redundant-notify.md) | Disallow sending redundant notifications from completed or errored observables. | βœ… | | | πŸ’­ | | -| [no-sharereplay](docs/rules/no-sharereplay.md) | Disallow unsafe `shareReplay` usage. | βœ… | | | | | -| [no-subclass](docs/rules/no-subclass.md) | Disallow subclassing RxJS classes. | | | | πŸ’­ | | -| [no-subject-unsubscribe](docs/rules/no-subject-unsubscribe.md) | Disallow calling the `unsubscribe` method of subjects. | βœ… | | | πŸ’­ | | -| [no-subject-value](docs/rules/no-subject-value.md) | Disallow accessing the `value` property of a `BehaviorSubject` instance. | | | | πŸ’­ | | -| [no-subscribe-handlers](docs/rules/no-subscribe-handlers.md) | Disallow passing handlers to `subscribe`. | | | | πŸ’­ | | -| [no-tap](docs/rules/no-tap.md) | Disallow the `tap` operator. | | | | | ❌ | -| [no-topromise](docs/rules/no-topromise.md) | Disallow use of the `toPromise` method. | | | | πŸ’­ | | -| [no-unbound-methods](docs/rules/no-unbound-methods.md) | Disallow passing unbound methods. | βœ… | | | πŸ’­ | | -| [no-unsafe-catch](docs/rules/no-unsafe-catch.md) | Disallow unsafe `catchError` usage in effects and epics. | | | | πŸ’­ | | -| [no-unsafe-first](docs/rules/no-unsafe-first.md) | Disallow unsafe `first`/`take` usage in effects and epics. | | | | πŸ’­ | | -| [no-unsafe-subject-next](docs/rules/no-unsafe-subject-next.md) | Disallow unsafe optional `next` calls. | βœ… | | | πŸ’­ | | -| [no-unsafe-switchmap](docs/rules/no-unsafe-switchmap.md) | Disallow unsafe `switchMap` usage in effects and epics. | | | | πŸ’­ | | -| [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 `throwError`. | | | | πŸ’­ | | +| NameΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β  | Description | πŸ’Ό | πŸ”§ | πŸ’‘ | πŸ’­ | ❌ | +| :--------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------- | :- | :- | :- | :- | :- | +| [ban-observables](docs/rules/ban-observables.md) | Disallow banned observable creators. | | | | | | +| [ban-operators](docs/rules/ban-operators.md) | Disallow banned operators. | | | | πŸ’­ | | +| [finnish](docs/rules/finnish.md) | Enforce Finnish notation. | | | | πŸ’­ | | +| [just](docs/rules/just.md) | Require the use of `just` instead of `of`. | | πŸ”§ | | | | +| [macro](docs/rules/macro.md) | Require the use of the RxJS Tools Babel macro. | | πŸ”§ | | | ❌ | +| [no-async-subscribe](docs/rules/no-async-subscribe.md) | Disallow passing `async` functions to `subscribe`. | βœ… | | | πŸ’­ | | +| [no-compat](docs/rules/no-compat.md) | Disallow the `rxjs-compat` package. | | | | | | +| [no-connectable](docs/rules/no-connectable.md) | Disallow operators that return connectable observables. | | | | πŸ’­ | | +| [no-create](docs/rules/no-create.md) | Disallow the static `Observable.create` function. | βœ… | | | πŸ’­ | | +| [no-cyclic-action](docs/rules/no-cyclic-action.md) | Disallow cyclic actions in effects and epics. | | | | πŸ’­ | | +| [no-explicit-generics](docs/rules/no-explicit-generics.md) | Disallow unnecessary explicit generic type arguments. | | | | | | +| [no-exposed-subjects](docs/rules/no-exposed-subjects.md) | Disallow public and protected subjects. | | | | πŸ’­ | | +| [no-finnish](docs/rules/no-finnish.md) | Disallow Finnish notation. | | | | πŸ’­ | | +| [no-ignored-default-value](docs/rules/no-ignored-default-value.md) | Disallow using `firstValueFrom`, `lastValueFrom`, `first`, and `last` without specifying a default value. | | | | πŸ’­ | | +| [no-ignored-error](docs/rules/no-ignored-error.md) | Disallow calling `subscribe` without specifying an error handler. | | | | πŸ’­ | | +| [no-ignored-notifier](docs/rules/no-ignored-notifier.md) | Disallow observables not composed from the `repeatWhen` or `retryWhen` notifier. | βœ… | | | πŸ’­ | | +| [no-ignored-observable](docs/rules/no-ignored-observable.md) | Disallow ignoring observables returned by functions. | | | | πŸ’­ | | +| [no-ignored-replay-buffer](docs/rules/no-ignored-replay-buffer.md) | Disallow using `ReplaySubject`, `publishReplay` or `shareReplay` without specifying the buffer size. | βœ… | | | | | +| [no-ignored-subscribe](docs/rules/no-ignored-subscribe.md) | Disallow calling `subscribe` without specifying arguments. | | | | πŸ’­ | | +| [no-ignored-subscription](docs/rules/no-ignored-subscription.md) | Disallow ignoring the subscription returned by `subscribe`. | | | | πŸ’­ | | +| [no-ignored-takewhile-value](docs/rules/no-ignored-takewhile-value.md) | Disallow ignoring the value within `takeWhile`. | βœ… | | | | | +| [no-implicit-any-catch](docs/rules/no-implicit-any-catch.md) | Disallow implicit `any` error parameters in `catchError` operators. | βœ… | πŸ”§ | πŸ’‘ | πŸ’­ | | +| [no-index](docs/rules/no-index.md) | Disallow importing index modules. | βœ… | | | | | +| [no-internal](docs/rules/no-internal.md) | Disallow importing internal modules. | βœ… | πŸ”§ | πŸ’‘ | | | +| [no-nested-subscribe](docs/rules/no-nested-subscribe.md) | Disallow calling `subscribe` within a `subscribe` callback. | βœ… | | | πŸ’­ | | +| [no-redundant-notify](docs/rules/no-redundant-notify.md) | Disallow sending redundant notifications from completed or errored observables. | βœ… | | | πŸ’­ | | +| [no-sharereplay](docs/rules/no-sharereplay.md) | Disallow unsafe `shareReplay` usage. | βœ… | | | | | +| [no-subclass](docs/rules/no-subclass.md) | Disallow subclassing RxJS classes. | | | | πŸ’­ | | +| [no-subject-unsubscribe](docs/rules/no-subject-unsubscribe.md) | Disallow calling the `unsubscribe` method of subjects. | βœ… | | | πŸ’­ | | +| [no-subject-value](docs/rules/no-subject-value.md) | Disallow accessing the `value` property of a `BehaviorSubject` instance. | | | | πŸ’­ | | +| [no-subscribe-handlers](docs/rules/no-subscribe-handlers.md) | Disallow passing handlers to `subscribe`. | | | | πŸ’­ | | +| [no-tap](docs/rules/no-tap.md) | Disallow the `tap` operator. | | | | | ❌ | +| [no-topromise](docs/rules/no-topromise.md) | Disallow use of the `toPromise` method. | | | | πŸ’­ | | +| [no-unbound-methods](docs/rules/no-unbound-methods.md) | Disallow passing unbound methods. | βœ… | | | πŸ’­ | | +| [no-unsafe-catch](docs/rules/no-unsafe-catch.md) | Disallow unsafe `catchError` usage in effects and epics. | | | | πŸ’­ | | +| [no-unsafe-first](docs/rules/no-unsafe-first.md) | Disallow unsafe `first`/`take` usage in effects and epics. | | | | πŸ’­ | | +| [no-unsafe-subject-next](docs/rules/no-unsafe-subject-next.md) | Disallow unsafe optional `next` calls. | βœ… | | | πŸ’­ | | +| [no-unsafe-switchmap](docs/rules/no-unsafe-switchmap.md) | Disallow unsafe `switchMap` usage in effects and epics. | | | | πŸ’­ | | +| [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 `throwError`. | | | | πŸ’­ | | diff --git a/docs/rules/no-ignored-default-value.md b/docs/rules/no-ignored-default-value.md new file mode 100644 index 00000000..ca399502 --- /dev/null +++ b/docs/rules/no-ignored-default-value.md @@ -0,0 +1,29 @@ +# Disallow using `firstValueFrom`, `lastValueFrom`, `first`, and `last` without specifying a default value (`rxjs-x/no-ignored-default-value`) + +πŸ’­ This rule requires [type information](https://typescript-eslint.io/linting/typed-linting). + + + +This rule prevents `EmptyError` rejections if there were no emissions from `firstValueFrom`, `lastValueFrom`, `first`, or `last` by requiring `defaultValue`. + +## Rule details + +Examples of **incorrect** code for this rule: + +```ts +import { Subject, firstValueFrom } from "rxjs"; + +const sub = new Subject(); +const result = firstValueFrom(sub); +sub.complete(); +``` + +Examples of **correct** code for this rule: + +```ts +import { Subject, firstValueFrom } from "rxjs"; + +const sub = new Subject(); +const result = firstValueFrom(sub, { defaultValue: null }); +sub.complete(); +``` diff --git a/src/index.ts b/src/index.ts index 49e7492e..fce1f0b6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,7 @@ import { noCyclicActionRule } from './rules/no-cyclic-action'; import { noExplicitGenericsRule } from './rules/no-explicit-generics'; import { noExposedSubjectsRule } from './rules/no-exposed-subjects'; import { noFinnishRule } from './rules/no-finnish'; +import { noIgnoredDefaultValueRule } from './rules/no-ignored-default-value'; import { noIgnoredErrorRule } from './rules/no-ignored-error'; import { noIgnoredNotifierRule } from './rules/no-ignored-notifier'; import { noIgnoredObservableRule } from './rules/no-ignored-observable'; @@ -60,6 +61,7 @@ const plugin = { 'no-explicit-generics': noExplicitGenericsRule, 'no-exposed-subjects': noExposedSubjectsRule, 'no-finnish': noFinnishRule, + 'no-ignored-default-value': noIgnoredDefaultValueRule, 'no-ignored-error': noIgnoredErrorRule, 'no-ignored-notifier': noIgnoredNotifierRule, 'no-ignored-observable': noIgnoredObservableRule, diff --git a/src/rules/no-ignored-default-value.ts b/src/rules/no-ignored-default-value.ts new file mode 100644 index 00000000..81173a73 --- /dev/null +++ b/src/rules/no-ignored-default-value.ts @@ -0,0 +1,112 @@ +import { TSESTree as es, ESLintUtils } from '@typescript-eslint/utils'; +import { getTypeServices, isIdentifier, isMemberExpression, isObjectExpression, isProperty } from '../etc'; +import { ruleCreator } from '../utils'; + +export const noIgnoredDefaultValueRule = ruleCreator({ + defaultOptions: [], + meta: { + docs: { + description: 'Disallow using `firstValueFrom`, `lastValueFrom`, `first`, and `last` without specifying a default value.', + requiresTypeChecking: true, + }, + messages: { + forbidden: 'Not specifying a default value is forbidden.', + }, + schema: [], + type: 'problem', + }, + name: 'no-ignored-default-value', + create: (context) => { + const { getTypeAtLocation } = ESLintUtils.getParserServices(context); + const { couldBeObservable, couldBeType } = getTypeServices(context); + + function checkConfigObj(configArg: es.ObjectExpression) { + if (!configArg.properties.some(p => isProperty(p) && isIdentifier(p.key) && p.key.name === 'defaultValue')) { + context.report({ + messageId: 'forbidden', + node: configArg, + }); + } + } + + function checkConfigType(configArg: es.Node) { + const configArgType = getTypeAtLocation(configArg); + if (!configArgType?.getProperties().some(p => p.name === 'defaultValue')) { + context.report({ + messageId: 'forbidden', + node: configArg, + }); + } + } + + function checkArg(arg: es.Node) { + if (isIdentifier(arg)) { + checkConfigType(arg); + return; + } else if (isMemberExpression(arg) && isIdentifier(arg.property)) { + checkConfigType(arg.property); + return; + } + if (!isObjectExpression(arg)) { + return; + } + checkConfigObj(arg); + } + + function checkFunctionArgs(node: es.Node, args: es.CallExpressionArgument[]) { + if (!couldBeType(node, 'firstValueFrom', { name: /[/\\]rxjs[/\\]/ }) + && !couldBeType(node, 'lastValueFrom', { name: /[/\\]rxjs[/\\]/ })) { + return; + } + if (!args || args.length <= 0) { + return; + } + const [observableArg, configArg] = args; + if (!couldBeObservable(observableArg)) { + return; + } + if (!configArg) { + context.report({ + messageId: 'forbidden', + node, + }); + return; + } + checkArg(configArg); + } + + function checkOperatorArgs(node: es.Node, args: es.CallExpressionArgument[]) { + if (!couldBeType(node, 'first', { name: /[/\\]rxjs[/\\]/ }) + && !couldBeType(node, 'last', { name: /[/\\]rxjs[/\\]/ })) { + return; + } + + if (!args || args.length <= 0) { + context.report({ + messageId: 'forbidden', + node, + }); + return; + } + const [arg] = args; + checkArg(arg); + } + + return { + 'CallExpression[callee.name=/^(firstValueFrom|lastValueFrom)$/]': (node: es.CallExpression) => { + checkFunctionArgs(node.callee, node.arguments); + }, + 'CallExpression[callee.property.name=/^(firstValueFrom|lastValueFrom)$/]': (node: es.CallExpression) => { + const memberExpression = node.callee as es.MemberExpression; + checkFunctionArgs(memberExpression.property, node.arguments); + }, + 'CallExpression[callee.property.name=\'pipe\'] > CallExpression[callee.name=/^(first|last)$/]': (node: es.CallExpression) => { + checkOperatorArgs(node.callee, node.arguments); + }, + 'CallExpression[callee.property.name=\'pipe\'] > CallExpression[callee.property.name=/^(first|last)$/]': (node: es.CallExpression) => { + const memberExpression = node.callee as es.MemberExpression; + checkOperatorArgs(memberExpression.property, node.arguments); + }, + }; + }, +}); diff --git a/src/rules/no-ignored-replay-buffer.ts b/src/rules/no-ignored-replay-buffer.ts index 2ee4cc31..c2f10219 100644 --- a/src/rules/no-ignored-replay-buffer.ts +++ b/src/rules/no-ignored-replay-buffer.ts @@ -1,4 +1,5 @@ -import { AST_NODE_TYPES, TSESTree as es } from '@typescript-eslint/utils'; +import { TSESTree as es } from '@typescript-eslint/utils'; +import { isIdentifier, isObjectExpression, isProperty } from '../etc'; import { ruleCreator } from '../utils'; export const noIgnoredReplayBufferRule = ruleCreator({ @@ -21,7 +22,7 @@ export const noIgnoredReplayBufferRule = ruleCreator({ node: es.Identifier, shareReplayConfigArg: es.ObjectExpression, ) { - if (!shareReplayConfigArg.properties.some(p => p.type === AST_NODE_TYPES.Property && p.key.type === AST_NODE_TYPES.Identifier && p.key.name === 'bufferSize')) { + if (!shareReplayConfigArg.properties.some(p => isProperty(p) && isIdentifier(p.key) && p.key.name === 'bufferSize')) { context.report({ messageId: 'forbidden', node, @@ -41,8 +42,8 @@ export const noIgnoredReplayBufferRule = ruleCreator({ } if (node.name === 'shareReplay' && args?.length === 1) { - const arg = args[0]; - if (arg.type === AST_NODE_TYPES.ObjectExpression) { + const [arg] = args; + if (isObjectExpression(arg)) { checkShareReplayConfig(node, arg); } } diff --git a/tests/rules/no-ignored-default-value.test.ts b/tests/rules/no-ignored-default-value.test.ts new file mode 100644 index 00000000..0f10483e --- /dev/null +++ b/tests/rules/no-ignored-default-value.test.ts @@ -0,0 +1,166 @@ +import { stripIndent } from 'common-tags'; +import { noIgnoredDefaultValueRule } from '../../src/rules/no-ignored-default-value'; +import { fromFixture } from '../etc'; +import { ruleTester } from '../rule-tester'; + +ruleTester({ types: true }).run('no-ignored-default-value', noIgnoredDefaultValueRule, { + valid: [ + stripIndent` + // firstValueFrom with default value + import { firstValueFrom, of } from "rxjs"; + + firstValueFrom(of(42), { defaultValue: 0 }); + firstValueFrom(of(42), { defaultValue: null }); + firstValueFrom(of(42), { defaultValue: undefined }); + function getValue(obs) { + return firstValueFrom(obs, { defaultValue: "hello" }); + } + class Foo { + getValue(obs) { + return firstValueFrom(obs, { defaultValue: "world" }); + } + } + `, + stripIndent` + // lastValueFrom with default value + import { lastValueFrom, of } from "rxjs"; + + lastValueFrom(of(42), { defaultValue: 0 }); + lastValueFrom(of(42), { defaultValue: null }); + lastValueFrom(of(42), { defaultValue: undefined }); + `, + stripIndent` + // first with default value + import { first, of } from "rxjs"; + + of(42).pipe(first(x => x, { defaultValue: 0 })); + of(42).pipe(first(x => x, { defaultValue: null })); + of(42).pipe(first(x => x, { defaultValue: undefined })); + `, + stripIndent` + // last with default value + import { last, of } from "rxjs"; + + of(42).pipe(last(x => x, { defaultValue: 0 })); + of(42).pipe(last(x => x, { defaultValue: null })); + of(42).pipe(last(x => x, { defaultValue: undefined })); + `, + stripIndent` + // other operators + import { of, map, filter, refCount } from "rxjs"; + + of(42).pipe(map(x => x), filter(x => x > 0), shareReplay({ bufferSize: 1, refCount: true })); + `, + stripIndent` + // non-RxJS firstValueFrom + import { of } from "rxjs"; + + function firstValueFrom(obs) {} + firstValueFrom(of(42)); + + class Foo { + firstValueFrom(obs) {} + } + const myFoo = new Foo(); + myFoo.firstValueFrom(of(42)); + `, + stripIndent` + // non-RxJS lastValueFrom + import { of } from "rxjs"; + + function lastValueFrom(obs) {} + lastValueFrom(of(42)); + `, + stripIndent` + // non-RxJS first + import { of } from "rxjs"; + + function first() {} + of(42).pipe(first()); + `, + stripIndent` + // non-RxJS last + import { of } from "rxjs"; + + function last() {} + of(42).pipe(last()); + `, + ], + invalid: [ + fromFixture( + stripIndent` + // firstValueFrom ignored + import { firstValueFrom, of } from "rxjs"; + + firstValueFrom(of(42)); + ~~~~~~~~~~~~~~ [forbidden] + firstValueFrom(of(42), {}); + ~~ [forbidden] + const config = {}; + firstValueFrom(of(42), config); + ~~~~~~ [forbidden] + const config2 = { config: {} }; + firstValueFrom(of(42), config2.config); + ~~~~~~ [forbidden] + `, + ), + fromFixture( + stripIndent` + // lastValueFrom ignored + import { lastValueFrom, of } from "rxjs"; + + lastValueFrom(of(42)); + ~~~~~~~~~~~~~ [forbidden] + `, + ), + fromFixture( + stripIndent` + // first ignored + import { first, of } from "rxjs"; + + of(42).pipe(first()); + ~~~~~ [forbidden] + of(42).pipe(first({})); + ~~ [forbidden] + const config = {}; + of(42).pipe(first(config)); + ~~~~~~ [forbidden] + const config2 = { config: {} }; + of(42).pipe(first(config2.config)); + ~~~~~~ [forbidden] + `, + ), + fromFixture( + stripIndent` + // last ignored + import { last, of } from "rxjs"; + + of(42).pipe(last()); + ~~~~ [forbidden] + `, + ), + fromFixture( + stripIndent` + // namespace import + import * as Rx from "rxjs"; + + Rx.firstValueFrom(Rx.of(42)); + ~~~~~~~~~~~~~~ [forbidden] + Rx.of(42).pipe(Rx.first()); + ~~~~~ [forbidden] + `, + ), + fromFixture( + stripIndent` + // operators import (deprecated) + import { of } from "rxjs"; + import { first, last } from "rxjs/operators"; + + of(42).pipe(first()); + ~~~~~ [forbidden] + of(42).pipe(last()); + ~~~~ [forbidden] + `, + ), + ], +});