Skip to content
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

no-unsafe-takeuntil: Accept object properties as operators #90

Merged
merged 4 commits into from
Dec 31, 2021

Conversation

alwonder
Copy link
Contributor

Motivation

I wanted to add the rule for the custom takeUntilDestroy operator in our working project. The operator is the method of an angular component, which fires when ngOnDestroy is called. But I stumbled upon the issue that the rule ignores my method:

// .eslintrc.json

"rxjs/no-unsafe-takeuntil": [
  "error",
  {
    "alias": ["takeUntilDestroy"]
  }
]
// My method

of(null)
  .pipe(
    this.takeUntilDestroy(), // It doesn't say anything
    tap(() => {}),
  )
  .subscribe(() => {});

Solution

I added the new option acceptObjectProperties (false by default) which allows adding not only functions but object properties and class members. So with this config:

// .eslintrc.json

"rxjs/no-unsafe-takeuntil": [
  "error",
  {
    "alias": ["takeUntilDestroy"],
    "acceptObjectProperties": true
  }
]

eslint would highlight the problem method:

// My method

of(null)
  .pipe(
    this.takeUntilDestroy(), // Applying operators after takeUntil is forbidden.
    tap(() => {}),
  )
  .subscribe(() => {});

@cartant
Copy link
Owner

cartant commented Dec 30, 2021

I think we could make this change without introducing the acceptObjectProperties option. That is, checking for a call via a member expression would be sensible default behaviour - as calls made via namespaced imports are represented as member expressions.

ATM - i.e. without your change - this wouldn't behave the way it should:

import * as rxjs from "rxjs";
rxjs.of(null)
  .pipe(
    rxjs.takeUntil(rxjs.NEVER),
    rxjs.tap(() => {}),
  )
  .subscribe(() => {});

So your change is more of a general fix, I think.

@alwonder
Copy link
Contributor Author

Yes, I think you're right. In that case, I'll delete the option.

Copy link
Owner

@cartant cartant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

@@ -83,7 +84,7 @@ const rule = ruleCreator({
const { couldBeObservable } = getTypeServices(context);

return {
[`CallExpression[callee.property.name='pipe'] > CallExpression[callee.name=${checkedOperatorsRegExp}]`]:
[`CallExpression[callee.property.name='pipe'] > CallExpression[callee.name=${checkedOperatorsRegExp}], CallExpression[callee.property.name=${checkedOperatorsRegExp}]`]:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will match calls via member expressions that are outside of pipe, as the , separates the member-expression selector from the pipe selector.

If you're curious, you can verify that this is how the selectors behave using http://estools.github.io/esquery/ with:

of(null).pipe(foo())
x.bar()

as the source and

CallExpression[callee.property.name='pipe'] > CallExpression[callee.name='foo'], CallExpression[callee.property.name='bar']

as the selector.

I'm going to merge this and fix it with a subsequent commit. My preference, in these situations, is to have two separate selectors/properties calling shared function.

@cartant cartant merged commit 4fe7423 into cartant:main Dec 31, 2021
@cartant
Copy link
Owner

cartant commented Dec 31, 2021

Your change should be in 4.0.4 - which has just been published.

@alwonder
Copy link
Contributor Author

Great, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants