Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[no-unnecessary-type-assertion] This rule is different from the tslint rule 'no-unnecessary-type-assertion' #1310

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

Closed
ooyyloo opened this issue Dec 6, 2019 · 6 comments
Labels
package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin working as intended Issues that are closed as they are working as intended

Comments

@ooyyloo
Copy link

ooyyloo commented Dec 6, 2019

The tslint rule 'no-unnecessary-type-assertion' reports an error only when the type assertions does not change the type of a variable. But this rule seems to report errors when the types of source variable and the receiver are different. These two rules are different, but the ROADMAP.md tells me these two rules are the same.

Example

  let x: string | undefined;
  let y: string | undefined = x!;

Consider about the code above. There is no error when using tslint rule 'no-unnecessary-type-assertion', but there are errors when using '@typescript-eslint/no-unnecessary-type-assertion'.

Versions

package version
@typescript-eslint/eslint-plugin 2.10.0
@typescript-eslint/parser 2.10.0
TypeScript 3.7.2
ESLint 6.7.2
node 12.13.0
npm 6.12.0
@ooyyloo ooyyloo added package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin triage Waiting for team members to take a look labels Dec 6, 2019
@vilicvane
Copy link
Contributor

I think the original tslint rule does it right. An non-null assertion is added just because the expression, at certain position, is believed to be non-null. And this has nothing to do with the assignment / return type.

@bradzacher
Copy link
Member

Have you got an example of where they differ?

It's really hard to evaluate an issue like this with just the vague description.
A code sample is worth 1000 words.

@bradzacher bradzacher added awaiting response Issues waiting for a reply from the OP or another party and removed triage Waiting for team members to take a look labels Dec 6, 2019
@ooyyloo
Copy link
Author

ooyyloo commented Dec 6, 2019

Have you got an example of where they differ?

It's really hard to evaluate an issue like this with just the vague description.
A code sample is worth 1000 words.

I add an example in the description above.

@bradzacher bradzacher added working as intended Issues that are closed as they are working as intended and removed awaiting response Issues waiting for a reply from the OP or another party labels Dec 6, 2019
@bradzacher
Copy link
Member

bradzacher commented Dec 6, 2019

Thanks.

Looking at your example - I don't understand what's wrong with it.
The ! is unnecessary, as the original type (string | undefined) is exactly assignable to the receiving type (string | undefined).

If you change the example slightly to:

let x: string | undefined | null;
let y: string | undefined = x!;

Then the rule will not report, because string | null | undefined is not assignable to string | undefined, due to the null.


The point of the rule is to help you flag and remove code that doesn't do anything. In your example, the non-null assertion does literally nothing.

I don't see the problem with using the extra information of the contextual receiving type to determine if the assertion is unnecessary.

By using the receiving type, it also opens up the entire world of checking function arguments, react props, etc.

declare const x: string | undefined;
declare function MyComponent(props: { prop: string | undefined }): ReactNode;

MyComponent({ prop: x! }); // non-null assertion is unnecessary
<MyComponent prop={x!} />; // non-null assertion is unnecessary

Yes it is not exactly 1:1 with the tslint rule, but that's intentional. The TSLint rule is more relaxed, and thus misses the cases outlined above.

I don't think the rule strays far enough away from the tslint rule to warrant being marked differently in the docs. It's only marginally more strict.


Note that we do not attempt to have exact 1:1 compatibility with tslint rules for many reasons such as:

  • they have options that are unused/deprecated.
  • they have options that are too complex due to organic growth.
  • they have functionality that is too loose, and was never made stricter because either:
    • it required a breaking change version bump
    • nobody thought to implement it

@vilicvane
Copy link
Contributor

@bradzacher Can we reopen this issue for further discussion?

This is not about compatibility with TSLint rules, but about being semantically correct.

declare const x: string | undefined;
declare function MyComponent(props: { prop: string | undefined }): ReactNode;

makeXNonNull();

// And here we know x will be non-null, but the type system don't know about that,
// so we added a non-null assertion to hint the correct type of this expression.
MyComponent({ prop: x! });

In the modified example above, it is unnecessary to do non-null assertion to pass type checking, but it is semantically correct to do non-null assertion as we know x after makeXNonNull() is non-null.

The non-null assertion also provides a hint for other developers to quickly recognize the type of x asserted is narrowed due to a certain process, even though not recognized by the type system.

And the essential point behind is that, type assertion is the assertion to the type of the value being asserted, and it has nothing to do with the contextual receiving type. The idea "if the absence of type assertion won't make the compiler complain, then the type assertion is unnecessary" is simply not semantically correct.

@bradzacher
Copy link
Member

bradzacher commented Dec 7, 2019

The name of the rule is pretty self-explanatory as to why it flag this usage.
no unnecessary type assertion - the rule shall enforce that there are no type assertions that are unnecessary (i.e are not required by the receiver, or do not change the type).

declare const x: string | undefined;
declare function MyComponent(props: { prop: string | undefined }): ReactNode;

makeXNonNull();

MyComponent({ prop: x! }); // no type error
MyComponent({ prop: x }); // no type error

Considering there is no type error with or without the !, that implies that it is unnecessary.
So it is correct for the rule to flag it.

An important note is that this rule will do a similar check in the future for explicit casts (angle-bracket/as). It's just that those casts are a lot harder to handle, as they can use arbitrarily complex types, so I haven't gotten around to implementing it yet.

// TODO - add contextually unnecessary check for this

provides a hint for other developers to quickly recognise the type of x asserted is narrowed due to a certain process

IMO it doesn't convey that meaning to the developer. The meaning it conveys when used like this: myFunc(x!) is not "FYI, x is non-nullish for this function call", it is "myFunc requires a non-nullish argument".

Everywhere I see people use the non-null operator, it's not in a place where there's been an explicit check. Why not? If there was an explicit check, you wouldn't need the assertion operator. Instead it's only used in places where the developer can say "it's reasonably safe to assume that the nullish type here shouldn't happen, and I don't think it's worthwhile to check for it".

There's a really simple workaround for this as well, which the rule allows:

declare const x: string | undefined;
declare function MyComponent(props: { prop: string | undefined }): ReactNode;

makeXNonNull();
// makeXNonNull will enforce that x is non-null, so we can assume the type is non-null for the rest of the block
const nonNullX = x!;

MyComponent({ prop: nonNullX });

Another approach, if you do not think the assertion is unnecessary, is to use an eslint disable comment.
This is a great way to handle it as well, as you can use an explicit comment as well to say why you think the assertion is necessary.

declare const x: string | undefined;
declare function MyComponent(props: { prop: string | undefined }): ReactNode;

makeXNonNull();

// This assertion is necessary to show that x has been previously asserted to be non-null, but the types were not updated as it was done in a different scope.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
MyComponent({ prop: x! });

Also - just a nitpick - you shouldn't be using functions that imply such side-effects anyway. They are not typesafe for obvious reasons, and are a good way to introduce subtle bugs and type holes in your codebase.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 20, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin working as intended Issues that are closed as they are working as intended
Projects
None yet
Development

No branches or pull requests

3 participants