eslint-plugin-unicorn icon indicating copy to clipboard operation
eslint-plugin-unicorn copied to clipboard

`no-array-callback-reference` rule is not compatible with TypeScript type guards

Open moltar opened this issue 4 years ago ā€¢ 20 comments

function isDefined<T>(argument: T | undefined): argument is T {
  return typeof argument !== 'undefined'
}

const items = [1, 2, undefined]

items.filter(isDefined).map((number) => {
  // number is now type guarded and undefined is eliminated
  console.log(number)
})

items.filter((num) => isDefined(num)).map((number) => {
  // however, this way it does not work, and "number" can still be undefined
  console.log(number)
})

I am not sure why TypeScript is not inferring the results in the second way.

moltar avatar Jun 23 '20 03:06 moltar

Console shows the same result on typescript playground

fisker avatar Jun 23 '20 05:06 fisker

That's not true:


number is not undefined:

Screenshot:

image

number is: number | undefined type.

Screenshot:

image

I think my example was poor, here is a better one:

https://www.typescriptlang.org/play/index.html?target=99#code/GYVwdgxgLglg9mABDAzgEQKbBmDATAHgBUA+ACgEMAnAcxAFsMwoAuRIxAH0XDyx3wBKNtTqNmyFO0QBvAFCJEVDFBBUkUAJ4AHDHGCJRDJlEQBCALwXEAcl79ceG3IC+cuRAQpTMKBnpS1gDaAIwANIgATBH22I4Auu6+-igAdNgANn5UZKiYcUKp9BTaZGRgDABGGFSCiBYksgqIAPQtiBX01VSSHXAA7ohauoh01Hx4hmCTsQKTqIgYGTD0OBR+eM3KquodVTWugkl+AekwWTVlnXUNkvlz5QyCgkUlV-u19Y3yim2IABYDDAANxqESg-wW-QommQpjwcAwUjAcFM-TgVAA1hEKNNEAAiTrdfGICC4xDec4ZRDVHjTBz4LYqNRIIk1RAAakQIUOQA

moltar avatar Jun 23 '20 05:06 moltar

Sorry, I mean complied js code result.

fisker avatar Jun 23 '20 05:06 fisker

But the code being validated is the TypeScript one. So Iā€™m not sure how the result is relevant?

moltar avatar Jun 23 '20 06:06 moltar

Expect

items.filter(((num) => isDefined(num)) as typeof isDefined).map((number) => {
  // however, this way it does not work, and "number" can still be undefined
  return number + 1
})

?

fisker avatar Jun 23 '20 07:06 fisker

I am not sure what is going on there. I am getting confused.

Both of the code snippets I posted are valid.

My only concern is that the rule is being triggered in a case where there is no other option. The only option is to do filter(isDefined), as that is how TypeScript works/infers.

moltar avatar Jun 23 '20 07:06 moltar

It's not only about .filter, all function references lost types.

fisker avatar Jun 23 '20 07:06 fisker

Hi, the issue is described here: https://github.com/microsoft/TypeScript/issues/10734 and here: https://github.com/microsoft/TypeScript/issues/16069. Would it be sensible to add an exception for functions with type guards (either by default or through an option)?

oss6 avatar Jun 28 '20 14:06 oss6

@oss6 thanks for finding the proper references!

moltar avatar Jun 29 '20 01:06 moltar

I think the rule is renamed to unicorn/no-array-callback-reference now as I understand.

moltar avatar Jan 18 '21 07:01 moltar

I agree that this particular scenario is one where array callbacks is actually nice to use. I'd be interested if the unicorn/no-array-callback-reference rule was configurable to allow it specifically for filter calls, since wrapping a function in TypeScript loses the type guard, making this non-ergonomic

osdiab avatar Feb 09 '21 08:02 osdiab

I could try my hand at this. My initial plan is to add an option with a name like ignoreTypeGuardFilters to the rule. If this is set to true, the linter will ignore any function considered a type guard.

This should be trivial for cases where the function exists in the same file, such as this: https://astexplorer.net/#/gist/6cb8b6a6683042ce444d2033901e43c5/95af3ecc1cb391cc9b94a37fe455880ab94c0bc9 I'm not sure about what to do when the function is imported, though.

Does anyone know of any existing rules that rely on the Typescript return type of something that's imported? (in unicorn or anywhere else). I've tried thinking about this for 15 minutes without coming up with someone ā€“ but I'm relatively new to writing/reading eslint rules.

Vages avatar Feb 20 '21 16:02 Vages

Does anyone know of any existing rules that rely on the Typescript return type of something that's imported?

I don't know about this, but you can try check https://github.com/typescript-eslint/typescript-eslint, maybe they do this.

fisker avatar Feb 20 '21 18:02 fisker

I met this problem too. I'd like to keep using the rule but I can see no convenient workaround. Would a more practical solution be to let the user configure the list of allowed function names, either for filter callbacks or any callback? Introducing type sensitive lints seems pretty drastic for such an isolated issue.

robeady avatar May 26 '21 23:05 robeady

The typeguard info is lost because the wrapper function is not a typeguard. If you simply make your wrapper function a typeguard then problem fixed.

e.g.

items.filter((num): num is number => isDefined(num)).map((number) => {
  console.log(number + 1);
});

playground link

Though this being said, typeguards probably should be ignored by this rule (probably via an option like @Vages suggests (I would suggest the option be named ignoreTypeGuards or allowTypeGuards)) as typeguards are highly unlikely to change their parameter signature.

This should be trivial for cases where the function exists in the same file I'm not sure about what to do when the function is imported, though.

It shouldn't make any difference if the function is defined in the same file or not; all the information needed is in the function's type information.

If people want this, I can make a PR (I'm the main contributor for eslint-plugin-functional so I'm pretty experienced with working with eslint rules).

RebeccaStevens avatar Sep 10 '21 03:09 RebeccaStevens

With an experienced eslint-rulemaker like you, @RebeccaStevens, making the extension, I'm quite certain that the lint-rule should just ignore type-guards by default (in hindsight, I think I only suggested it due to a lack of confidence in my lint-rule abilities šŸ˜…). Having an option for it seems bloated, and if this actually affects someone negatively (which I really doubt), we could add the option in later.

I'm only an ordinary contributor myself, but if you make a PR, I could test it and give some feedback. Just tag me when you add it. PS: I use eslint-plugin-functional; good work!

Vages avatar Sep 11 '21 09:09 Vages

I haven't looked too closely at the rule but I believe currently the rule doesn't use type information. It might be best to use an option to ignore typeguards so that the rule still works out of the box without needing type information; thus also ensuring JS users can still use the rule. I'm not familiar with the internals of this project however so I don't know whether that would be a concern.

RebeccaStevens avatar Sep 11 '21 09:09 RebeccaStevens

I'm not familiar with the internals of this project however so I don't know whether that would be a concern.

I'm not either. Do you have an opinion, @fisker?

Vages avatar Sep 11 '21 09:09 Vages

Was there ever any update on this? Currently can work around with disable comment, but would be nice to be able to remove them.

tetarchus avatar Sep 18 '22 16:09 tetarchus

+1, I'd love to see a fix to this rule so I can stop disabling it on all inline typeguards.

briavicenti avatar Mar 09 '23 21:03 briavicenti