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

templates-no-negated-async not reporting some failures #615

Closed
rafaelss95 opened this issue May 6, 2018 · 12 comments
Closed

templates-no-negated-async not reporting some failures #615

rafaelss95 opened this issue May 6, 2018 · 12 comments
Labels

Comments

@rafaelss95
Copy link
Collaborator

rafaelss95 commented May 6, 2018

Reproduction:

{
  "rules": {
    "templates-no-negated-async": true
  }
}
@Component({
  selector: 'app-test',
  template: `
    <div *ngFor="let a of collection; trackBy: trackByFn"></div>
    {{ (foo | async) == false }} // not reporting failure here
    {{ !(bar | async) }} // not reporting failure here
  `,
})
export class TestComponent {}

@Component({
  selector: 'app-test',
  template: `
    <div *ngIf="!(a | async)"></div> // idk if it's intentional, but it's not reporting failure
    {{ (foo | async) == false }} // not reporting failure here
    {{ !(bar | async) }} // not reporting failure here
    {{ !(isHandset | async) ? 'dialog' : 'navigation' }} // not reporting failure here
  `,
})
export class TestComponent {}
@wKoza wKoza added the bug label May 16, 2018
@rafaelss95
Copy link
Collaborator Author

rafaelss95 commented Jul 7, 2018

I've started to looking at this issue and I found what's the problem:

const operator = this.codeWithMap.code.slice(endLeftSpan, startRightSpan);

For the first template above the results are:

endLeftSpan = 28
startRightSpan = 33
this.codeWithMap.code.slice(endLeftSpan, startRightSpan); returns "t a o" (from *ngFor line).

It seems that there is a real problem with multiline templates. It's only getting the code from the first line, I really don't know why.


Question:

Why do we always assume that the expression is being compared to false? What if the user put ! trying to represent a nil check (null/undefined)?

@Component({
  selector: 'app-test',
  template: '<div *ngIf="!(data$ | async)">Some content</div>'
})
class TestComponent implements OnInit {
  data$: Observable<User | undefined>;

  ngOnInit() {
    this.data$ = someServiceThatCanReturnUserOrUndefined;
  }
}

In this case, an user is trying to use ! to check if Observable emits a undefined or even a null value, so the expression could be changed to:

<div *ngIf="(data$ | async) == null">Some content</div>

<!-- or -->

<div *ngIf="(data$ | async) === undefined">Some content</div>

... instead, we replace the expression with === false, what brings an unexpected behavior, a completely unsafe replace.


Also, there are real failures related to the current replacement:

<div *ngIf="!(foo | async)">Something</div>

... After running --fix:

<div *ngIf="(foo | async) === false >Something</div>
<!-- final quote removed and extra whitespace added -->

IMHO, we should remove the "fix" option for this rule, since we can't assume what the "expression" is trying to do... or maybe leave only the replace for strict equality (== false -> === false).

@wKoza @mgechev let me know your thoughts.

@mgechev
Copy link
Owner

mgechev commented Jul 12, 2018

IMHO, we should remove the "fix" option for this rule, since we can't assume what the "expression" is trying to do... or maybe leave only the replace for strict equality (== false -> === false).

Yes, we should remove the fix because it's not safe.

@rafaelss95
Copy link
Collaborator Author

rafaelss95 commented Jul 12, 2018

@mgechev I'm still trying to find a valid use case for this rule, do you have one?

Here...

this.addFailureFromStartToEnd(spanStart, spanEnd, 'Async pipes can not be negated, use (observable | async) === false instead', [

... we say "Async pipes can not be negated". Why not?

Take a look at this sample:

@Component({
  selector: 'app-root',
  template: `
    <div *ngIf="!(obs$ | async)">
      Loading... <!-- this will be displayed until the observable emits a value -->
    </div>
  `
})
export class AppComponent  {
  obs$ = observableOf([1, 2, 3]).pipe(
    delay(2000) // simulates an http request
  );
}

What would be the benefits of an explicit check like this <div *ngIf="(obs$ | async) === null" instead of what I did above?

Source sample

@rafaelss95
Copy link
Collaborator Author

@mgechev just to complement: unless there's a valid case that this rule could be useful, I'm voting in favor of deprecate this rule.

@mgechev
Copy link
Owner

mgechev commented Jul 14, 2018

I'll look at the story behind it over the weekend.

@rafaelss95
Copy link
Collaborator Author

@mgechev Friendly bump, have you looked?

@mgechev
Copy link
Owner

mgechev commented Jul 16, 2018

Yes, here's the motivation. I'm not sure if the value it brings justifies the corner cases you pointed. I'd vote to drop the rule. Let's keep the issue open for a few more days and collect opinions.

@rafaelss95
Copy link
Collaborator Author

@mgechev better close this and create a new issue about deprecate this rule or keep the discussion here?

@mgechev
Copy link
Owner

mgechev commented Jul 29, 2018

Sounds good!

@mgechev
Copy link
Owner

mgechev commented Feb 17, 2019

@rafaelss95 since we had this conversation, we reevaluated the pros and cons of this rule and introduced it in Google. Let's keep it as part of codelyzer.

@mgechev mgechev closed this as completed Feb 17, 2019
@rafaelss95
Copy link
Collaborator Author

@mgechev Okay, can we just update the following failure message?

static readonly FAILURE_STRING_NEGATED_PIPE = 'Async pipes can not be negated, use (observable | async) === false instead';

@mgechev
Copy link
Owner

mgechev commented Feb 18, 2019

Sure. How do you suggest to update it?

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

No branches or pull requests

3 participants