Skip to content

Adding excludedObservableTypes option to prefer-takeuntil #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/rules/prefer-takeuntil.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ The `checkDecorators` property is an array containing the names of the decorator

The `alias` property is an array of names of operators that should be treated similarly to `takeUntil`.

The `excludedObservableTypes` property is an array containing the names of types that should be excluded from the check.

```json
{
"rxjs-angular/prefer-takeuntil": [
Expand All @@ -66,7 +68,8 @@ The `alias` property is an array of names of operators that should be treated si
"alias": ["untilDestroyed"],
"checkComplete": true,
"checkDecorators": ["Component"],
"checkDestroy": true
"checkDestroy": true,
"excludedObservableTypes": ["AsyncSubject"]
}
]
}
Expand Down
18 changes: 16 additions & 2 deletions source/rules/prefer-takeuntil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const defaultOptions: readonly {
checkComplete?: boolean;
checkDecorators?: string[];
checkDestroy?: boolean;
excludedObservableTypes?: string[];
}[] = [];

const rule = ruleCreator({
Expand All @@ -51,22 +52,24 @@ const rule = ruleCreator({
checkComplete: { type: "boolean" },
checkDecorators: { type: "array", items: { type: "string" } },
checkDestroy: { type: "boolean" },
excludedObservableTypes: { type: "array", items: { type: "string" } },
},
type: "object",
description: stripIndent`
An optional object with optional \`alias\`, \`checkComplete\`, \`checkDecorators\` and \`checkDestroy\` properties.
An optional object with optional \`alias\`, \`checkComplete\`, \`checkDecorators\`, \`checkDestroy\` and \`excludedObservableTypes\` properties.
The \`alias\` property is an array containing the names of operators that aliases for \`takeUntil\`.
The \`checkComplete\` property is a boolean that determines whether or not \`complete\` must be called after \`next\`.
The \`checkDecorators\` property is an array containing the names of the decorators that determine whether or not a class is checked.
The \`checkDestroy\` property is a boolean that determines whether or not a \`Subject\`-based \`ngOnDestroy\` must be implemented.
The \`excludedObservableTypes\` property is an array containing the names of types that should be excluded from the check.
`,
},
],
type: "problem",
},
name: "prefer-takeuntil",
create: (context, unused: typeof defaultOptions) => {
const { couldBeObservable } = getTypeServices(context);
const { couldBeObservable, couldBeType } = getTypeServices(context);

// If an alias is specified, check for the subject-based destroy only if
// it's explicitly configured. It's extremely unlikely a subject-based
Expand All @@ -78,6 +81,7 @@ const rule = ruleCreator({
checkComplete = false,
checkDecorators = ["Component"],
checkDestroy = alias.length === 0,
excludedObservableTypes = [],
} = config;

type Entry = {
Expand All @@ -103,6 +107,9 @@ const rule = ruleCreator({
if (!couldBeObservable(object)) {
return;
}
if (excludedObservableTypes.some((e) => couldBeType(object, e))) {
return;
}
checkSubscribe(callExpression, entry);
});

Expand Down Expand Up @@ -265,6 +272,13 @@ const rule = ruleCreator({
isIdentifier(object.callee.property) &&
object.callee.property.name === "pipe"
) {
if (
excludedObservableTypes.some((e) =>
couldBeType((object.callee as any).object, e)
)
) {
return;
}
const operators = object.arguments;
operators.forEach((operator) => {
if (isCallExpression(operator)) {
Expand Down
50 changes: 50 additions & 0 deletions tests/rules/prefer-takeuntil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,56 @@ ruleTester({ types: true }).run("prefer-takeuntil", rule, {
},
],
},
{
code: stripIndent`
import { Component } from "@angular/core";
import { AsyncSubject } from "rxjs";
import { switchMap } from "rxjs/operators";

const o = new AsyncSubject<string>();

@Component({
selector: "component-with-excluded-type-1"
})
class CorrectComponent implements OnDestroy {
someMethod() {
o.subscribe();
}
}
`,
options: [
{
excludedObservableTypes: ["AsyncSubject"],
checkDestroy: false,
},
],
},
{
code: stripIndent`
import { Component } from "@angular/core";
import { AsyncSubject } from "rxjs";
import { switchMap } from "rxjs/operators";

const o = new AsyncSubject<string>();

@Component({
selector: "component-with-excluded-type-2"
})
class CorrectComponent implements OnDestroy {
someMethod() {
o.pipe(
switchMap(_ => o),
).subscribe();
}
}
`,
options: [
{
excludedObservableTypes: ["AsyncSubject"],
checkDestroy: false,
},
],
},
],
invalid: [
fromFixture(
Expand Down