-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Enhancement: [no-explicit-any] Option to disable rule for generic constraints (revisited) #7751
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
Comments
No, it was locked by a bot. The comments after closing were all people suggesting example of why you 100% had to use any, followed by us countering with ways to explicitly avoid the For example 1 there exists a much, much simpler option - patch the package to export the type. Modern package managers support patching out of the box - so it's trivial to locally create a patch for your version and then create a PR for the package from that patch. This avoids the need for any shenanigans or type gymnastics. For example two - it depends what your goal is. If your goal is to have correct code - then strategy 2 is the correct option. You have also skipped over an even better solution. You have clearly defined a type where the generics are optional - hence you're happy to pass So if the generics are optional define them as such. interface Generics<
A extends unknown[] = unknown[],
B extends unknown[] = unknown[],
C extends unknown[] = unknown[]
> {
a: A,
b: B,
c: C,
}
class Builder<Values extends Generics = Generics> {...}
declare function useBuilder<B extends Builder>(builder: B): void; Now you no longer need to define any generics and can pass everything through concisely whilst still having the option to pass generics if required. |
You have to be really careful with In your specific example of "a builder type that only uses the generic as a mapped type" - sure there's a lot of implicit safety built in specifically in the way that things are structured. However if you enable such an option you're opening the door to simpler usecases in your codebase and you can introduce unsafe code very easily. For example your first example is COMPLETELY UNSAFE. useAny({
generics: 1,
// ts does not error here even though 1 clearly does not satisfy the generic object constraint.
}); It is not safe to just enable the use of |
thanks @bradzacher That final playground is a super example of why even in generic constraints we shouldn't be using |
Relevant for the exported types point: #7670 |
Before You File a Proposal Please Confirm You Have Done The Following...
My proposal is suitable for this project
Link to the rule's documentation
https://typescript-eslint.io/rules/no-explicit-any/
Description
I'd like to revisit this issue for adding an option to the
no-explicit-any
rule that allows the use ofany
as generic constraint. That issue was seemingly closed by a bot, so I'm not even sure if that issue reached a real consensus. Also, I've written a few code examples that I think add some value to the discussion, which is why I'm opening this issue even if there's technically another existing (closed) issue.Example 1: Non-exported types in 3rd party packages
When integrating with 3rd party typescript packages, it's unfortunately common for packages to not expose all the types the consumer may need. While this is a fault in those packages, I think we have to be pragmatic and acknowledge that this is a common enough problem to justify a workaround.
Let's imagine the package
example-package
that doesn't export a generic constraint type:This is what it would look like to consume this package:
Strategy 1: Using
never
as generic constraintUsing
never
makes the constraint too specific and the function now only acceptsFoo<never>
Strategy 2: Using
unknown
as generic constraintUsing
unknown
requires us to reconstruct the type used for the generic constraint while usingunknown
everywhere.This actually works. Neither Typescript or typescript-eslint yields errors, but this is unnecessarily verbose when all I want is to tell typescript that
useUnknown
should accept any form ofFoo
. It's also brittle, since I have to redefine implementation details of the 3rd party package. If the package refactors their internal type definitions, I will have to refactor my code.Strategy 3: Using
any
as generic constraintFinally let's try using
any
. It gives us the best developer experience. It applies the generic constraint we want, is terse and not brittle, and without yielding any typescript errors. However, unfortunately, typescript-eslint complains if you haveno-explicit-any
enabled.Example 2: The same problem can still occur even if you own all the type definitions
Let's say we have this builder pattern:
Now we want to define some functions that accepts generic variants of this Builder type. Observe that if we try the same strategies as in Example 1, we'll experience the exact same problems:
Strategy 1: Using
never
as generic constraintStrategy 2: Using
unknown
as generic constraintOnce again, this works, but is as verbose and brittle as mentioned in Example 1.
Strategy 3: Using
any
as generic constraintOnce again,
any
as generic constraint is the winner:eslint test cases
Fail
Pass
The text was updated successfully, but these errors were encountered: