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
Rule proposal: Disallow calling Number.isNaN on non-number types #9035
Comments
Interesting! Code Advice (whether this gets accepted or you'd like to try implementing it as a simple custom rule)... Will wait to see what others have to say, though, on whether this ought to be part of typescript-eslint or not. FYI this is probably something that you could solve without a lint rule by modifying the global types, but that has its own downsides. You might be interested to peruse the conversation for use-unknown-in-catch-callback-variable, where this was discussed in some detail #7526 (comment) 🙂 |
Seems like this is similar to #5360 I personally don't think this should be a lint rule and instead if your codebase wants different behaviour:
So I'm a -1 on this. |
The well-defined and expected behavior for calling I can see an argument for allowing calling this on a union type that includes But for all other cases, where Like, I don't think there's anyone who uses eslint who would actively not want this to be flagged as an error at compile time by something, whether by the compiler or an eslint rule:
Patching lib types or using something like typescript-eslint is much more widely adopted compared to something like better-typescript-lib, so I think adding a lint rule for this is the best way to help people avoid this problem. A rule that says "don't call Number.isNaN on things that are known at compile time to definitely not be a number" would help many people to catch bugs, and doesn't seem to have any downsides. I'll amend my proposal a bit: Configuration option
|
Any predicate has this pitfall of "accepting unrelated types". For example, your own There are other ways you can shoot yourself in the foot while doing type narrowing, by the way: const a = 1;
if (typeof a === "string") {
// Not even caught by no-unnecessary-condition!
} |
Any usage of a predicate where the result can be known at compile time is pretty suspicious, but as you point out, there's currently no way in TypeScript to detect that systematically for all predicates. I do think Anecdotally, I have seen this class of bug several times in my career, and at at one point at my most recent job, 2/12 uses of I have never seen people have similar problems with incorrect usage of other predicates, so it seems like it'd be a waste of time for anyone to write lint rules for them, but with If I did the work and made a PR for this, is anyone strongly enough opposed to this that they'd be opposed to merging it? Or is it more at the level of "I don't see much point in this but it doesn't seem harmful if someone else wants to do the work"? |
That doesn't sound unreasonable to me, but remember that contributions also imply future work by the team to maintain them, so I'll wait for more feedback from Brad. |
yes - to be clear it's not that we don't think there's value in such a check. It's a question of maintenance cost against user value. We are volunteer maintainers. We have barely enough bandwidth to keep everything maintained as it is. So we are very careful about adding new rules. Once a contributor builds a rule and it merges - in general the contributor never contributes again. Which means the maintenance team is on-the-hook for fixing bugs and ensuring future TS version compatibility. I.e. every new rule or option carries with it a maintenance burden - a cost to the maintenance team. When we change the AST - we need to update every rule. When we support new TS versions we need to ensure every rule doesn't break. When new ES versions are released we need to ensure existing rules support new semantics/syntax. When new ESLint versions (particularly majors) are released we need to ensure existing rules work with the changes. If we want to refactor our codebase we need to refactor every rule. Etc. Even the simplest of rules have a maintenance burden. When we are evaluating a new rule what we are looking at is the cost-to-value ratio. Is the cost of bringing in a new rule higher or lower than the value it gives to our users? Value is a nebulous thing because it is subjective - to each person it is different. For you the perceived value of this rule is high - this bug has impacted you directly so preventing it in future is high value to you. So if value is subjective - how do we as a maintenance team evaluate it? Well we apply our own experiences - each of us have had our own careers in various spaces and we've seen code at many different companies solving many different problems. We also have our experiences from talking to users as part of this project - it's 2nd hand, anecdotal experience but it helps guide us all the same. We also evaluate it against the history of the project. The plugin itself is ~8y old (longer if you consider its If we're ever unsure - we default to the wisdom of the collective and leave an issue open for reactions / comments. 3-6m later we come back and see if anyone else has come and reacted. If few people do then it's a good signal that people either aren't running into the issue or they aren't looking to lint against it. Phew. So with all that being said - is does this rule pass the cost:value bar? I think it has some limited value, for sure - it catches a potential bug vector. But it is - as you've researched - a reasonably rare one. I do think there are other (better) ways around this. For me right now this doesn't seem like something that the general userbase would really see value in turning on - it would likely need to be included in a recommended set for anyone to use it. I'm still sitting as a -1 - but I'm happy to leave this to evaluate community engagement. |
Worth noting that the TS type for |
I'm so confused lol.. "This thing is specced to coerce everything to a number - so it must only accept numbers" and "This thing is specced to always return false for non-numbers - so it must accept anything". Seems like such contradictory thinking?!? |
It is a longstanding confusion of TS users. See microsoft/TypeScript#37449 and the many issues linked. In essence, TS wants us to only use a function in the domain where no type coercion happens. |
Before You File a Proposal Please Confirm You Have Done The Following...
My proposal is suitable for this project
Description
Several years ago, the type signature of Number.isNaN in TypeScript was changed to accept
unknown
: microsoft/TypeScript#24436I think this was a mistake, because it breaks type safety on this function and doesn't provide a benefit for most use cases, but the decision seems unlikely to be reversed at this point.
In any codebase I've worked in, there's no use case for calling Number.isNaN on any type aside from
number
, and any time Number.isNaN is called on a non-number, it's guaranteed to be a bug. It's a pretty easy mistake to make, and currently there is no automated way to catch it. It would be valuable to have an eslint rule to detect this.E.g.:
Anecdotally: In the current codebase that I work in, after finding one bug that was caused by calling this function on a string instead of number, I searched the codebase for other uses of isNaN on a string and found one additional bug that way. I suspect this is a common mistake.
Fail Cases
Pass Cases
Additional Info
If someone can point me to an example PR where someone added a similar rule before, I'm happy to take a crack at implementing this myself.
The text was updated successfully, but these errors were encountered: