-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
[no-explicit-any] Allow safe usages of any
#1071
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
-declare function withBar<T extends Component<any>>(
+declare function withBar<T extends Component<never>>( you should pretty much never have to use
Another way to think of it: |
@bradzacher how would you rewrite this definition so it doesn't use import * as React from 'react';
interface Props<T> {
elements: T[]
}
declare class MyComponent<T> extends React.Component<Props<T>> {};
declare function debug<T extends React.ComponentType<any>>(Component: T): T;
debug(MyComponent); |
Hmmm.. Digging into it, from what I can see is the answer is that you can't. There are some rather complex interactions in the react types and react type resolution logic, so it's not entirely surprising. If you're working with components with generics on the props, then you will have to use declare function debug<TProps>(Component: React.ComponentType<TProps>): typeof Component;
const Res1 = debug(MyComponent); // typeof === React.ComponentType<Props<unknown>> 😭 An alternate option is to defer typing to the caller of your HOC, but this is pretty ugly and not a good solution: declare function debug<TProps>(comp: React.ComponentType<TProps>): never;
const Res1: typeof MyComponent = debug(MyComponent);
const Res2 = debug(MyComponent);
let x = <Res1 elements={['']} />
// works fine
let y = <Res2 elements={['']} />
// JSX element type 'Res2' does not have any construct or call signatures. Another alternate option is to drop the type restraint, but declare function debug<T>(comp: T): T;
const Res1: typeof MyComponent = debug(MyComponent); I would note that back when I was doing react typescript on the regular, JSX + generics was very much an unsupported use case. Since then support has changed a lot (you can explicitly use generics in JSX now, for example), so my knowledge is limited in this regard. |
The problem is that there isn't really any "safe" I suspect that a rule that detected these edge cases where it's "acceptable" to use For example, in a different usecase, the function debug<T extends Array<any>>(comp: T): T {
comp.push('');
comp.push(1);
comp.push(false);
return comp;
}
interface Props<T> { element: T[] }
const array = new Array<Props<string>>();
const out = debug(array);
// typeof out === Array<Props<String>>
// but we can see that it clearly no longer matches that signature do to the presence of the values added What are the differences? between the two. If someone wants to take the time to research, and write such a rule, happy to accept a PR! |
How about this?
|
This function would be made both simpler and type-safe if we defined it like this: function debug<T>(comp: T[]): T[] {
comp.push('');
comp.push(1);
comp.push(false);
return comp;
} This wouldn't allow any of the This signature is saying "give me an array of anything and I will produce an array of anything in return", which is precisely what the code in your example does: function debug<T extends Array<any>>(comp: T): T { whereas this one is saying "give me an array of something and I will give you an array of the same something in return", which is a stronger promise. function debug<T>(comp: T[]): T[] {
This signature won't work when props are a union. import * as React from 'react';
interface LinkProps {
href: string;
}
interface ButtonProps {
onClick: React.UIEventHandler<HTMLButto
8000
nElement>;
}
type Props = LinkProps | ButtonProps;
declare class Button extends React.Component<Props> {}
declare function debug<T>(Component: React.ComponentType<T>): typeof Component;
debug(Button); // Compile-time error |
Use cases for anyType guardsWhen validating user input, interface ErrorLike {
message: string;
}
function isErrorLike(candidate: unknown): candidate is ErrorLike {
return (
typeof candidate === 'object' &&
object !== null &&
// 'message' in candidate; // Can't do it
// typeof candidate.message === 'string'; // Can't do it
(typeof (candidate as ErrorLike).message === 'string') // Could have just used `any`
)
} User-defined type guards are unsafe by design — runtime type checking is one of TypeScript's no-goals. The programmer is required to take full responsibility for whatever is happening inside a user-defined type guard. Because we can't rely on the type system to help us there, it's fair to say a more relaxed position towards When it doesn't improve anythingConsider a function that wraps a method from the standard library. const isEqual = (left: any, right: any): boolean =>
Object.is(left, right); Because Conditional typesWhen using the type ValueOf<T> =
T extends Record<any, infer U>
? U
: never This is a way of saying "for as long as it's a These are a few examples I leave in case anyone really wants to add some heuristics to this rule. I totally agree it's better to use something other than |
This case also doesn't work for the nested generics problem, as mentioned in my comment.
It would indeed, I was writing an intentionally bad function signature so that I could highlight the point that it's really, really hard to detect a "safe" usage. You could also add I found a nice solution for the interface ErrorLike {
message: string;
}
function isObjectLike(candidate: unknown): candidate is Record<string, unknown> {
return (
typeof candidate === 'object' &&
candidate !== null
);
}
function isErrorLike(candidate: unknown): candidate is ErrorLike {
return (
isObjectLike(candidate) &&
typeof candidate.message === 'string'
);
} The const isEqual1 = (left: unknown, right: unknown): boolean =>
Object.is(left, right);
isEqual1({ a: 1 }, { b: 2 });
const isEqual2 = <TL, TR>(left: TL, right: TR): boolean =>
Object.is(left, right);
isEqual2({ a: 1 }, { b: 2 }); Just because the underlying API uses this particular case you can solve by using the correct union: type ValueOf<T> =
T extends Record<string | number | symbol, infer U>
? U
: never;
type Foo1 = ValueOf<Record<string, number>>; // typeof === number
type Foo2 = ValueOf<Record<number, number>>; // typeof === number
type Foo3 = ValueOf<Record<symbol, number>>; // typeof === number
type Foo4 = ValueOf<{ a: 1, b: 2 }>; // typeof === 1 | 2
type Foo5 = ValueOf<{ a: string, b: number }>; // typeof === string | number
type Foo6 = ValueOf<{ a: 1, b: 2, c: boolean, d: object }>; // typeof === boolean | object | 1 | 2 Though this is definitely one of the cases, where In the
Looking at the call site of the type, you wouldn't be able to tell that providing This is why I say that it's a very complex problem to solve. As an aside, I think that in future, this rule would be okay to relax more, when this rule is introduced: #791 |
That's very nice! Thank you for sharing.
In this specific example, we don't even need conditional types, we can achieve the same result by defining
And I agree! I don't believe it makes sense to try and "solve" it statically. It's better if a programmer uses their best judgment. |
What about usage with Angular? |
Thinking about this more - it's really hard to define what a "safe" Using declare class Foo<T> {
prop: T;
}
function foo<T extends Foo<any>>(arg: T) {
arg.prop.a.b.c.d; // prop is type any
}
foo(new Foo<string>()) // will error at runtime The only place that I can think of where an
Even then, I'm not entirely sure that it's 100% safe to do that. I think that it would take a lot of TS experience (or rather, specific knowledge of this issue) to understand why Why is this a problem? If an engineer cannot understand why the rule doesn't flag a specific case, then it causes two things:
As such, I think that this proposal should ultimately be rejected. |
/*
Argument of type 'Component<Props>' is not assignable to parameter of type 'Component<unknown>'.
Type 'unknown' is not assignable to type 'Props'.
*/
const NewComponent = withBar(MyComponent); I was wondering why the error message is |
is it possible to add a parameter ignoreArrays to allow any[] only ? |
even I don't knoz what to pass instead of the any inside the decorators, in the proxy, ... These cases are blocking me to adopt this rule, and les developers use any everywhere... |
This is the situation I found my self in: export function makeVariableContent<T extends { new(...args: any[]): BaseNode }>(constructor: T): T {
return class extends constructor implements VariableContentNode {
/******/
}
} Maybe we can allow |
Nope. If you have specific requirements for your codebase, it would be pretty trivial to fork the rule temporarily to handle your migration use case, and then delete the fork once your migration is complete. |
I'm going to close this issue. This is not possible to action, as there's no way to detect a "safe" any See my previous comment. |
Repro
Expected Result
No error
Actual Result
Error
Additional Info
You might think we should change
any
tounknown
, to fix this error:… however, if we do that, the function call
withBar
will fail understrictFunctionTypes
:For this reason, I believe this is a valid and safe place to use
any
.The rule
no-explicit-any
does exactly what it says on the tin—it prevents all explicit usages ofany
. Perhaps what I am asking for is a separate rule—one that only disallows unsafe usages ofany
—although I would question why you would want to disallow all explicit usages including safe ones.Versions
@typescript-eslint/eslint-plugin
2.2.0
@typescript-eslint/parser
2.2.0
TypeScript
3.6.3
ESLint
5.16.0
node
12.8.1
npm
6.10.2
The text was updated successfully, but these errors were encountered: