Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(eslint-plugin): [ban-types] rework default options#848

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
bradzacher merged 2 commits intov3fromban-types-object
May 9, 2020

Conversation

bradzacher
Copy link
Member

@bradzacherbradzacher commentedAug 13, 2019
edited
Loading

BREAKING CHANGE:

Updated the config based on feedback:

  • Removed fixer fromobject andObject
    • Updated the messages to be much clearer.
  • Added{}
  • AddedFunction
{// ... config for lower case primitives ...Function:{message:['The `Function` type accepts any function-like value.','It provides no type-safety when calling the function, which can be a common source of bugs.','It also accepts things like class declarations, which will throw at runtime as they will not be called with `new`.','If you are expecting the function to accept certain arguments, you should explicitly define function shape.',].join('\n'),},Object:{message:['The `Object` type actually means "any non-nullish value", so it is marginally better than `unknown`.','- If you want a type meaning "any object", you probably want `Record<string, unknown>` instead.','- If you want a type meaning "any value", you probably want `unknown` instead.',].join('\n'),},'{}':{message:['`{}` actually means "any non-nullish value".','- If you want a type meaning "any object", you probably want `Record<string, unknown>` instead.','- If you want a type meaning "any value", you probably want `unknown` instead.',].join('\n'),},object:{message:['The `object` type is currently hard to use (see https://github.com/microsoft/TypeScript/issues/21732).','Consider using `Record<string, unknown>` instead, as it allows you to more easily inspect and use the keys.',].join('\n'),},}

Fixes#842

sindresorhus, ux-engineer, danielnixon, and fabiospampinato reacted with thumbs up emojibrainkim reacted with thumbs down emojibrainkim reacted with eyes emoji
@bradzacherbradzacher added the enhancementNew feature or request labelAug 13, 2019
@typescript-eslint
Copy link
Contributor

Thanks for the PR,@bradzacher!

typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community.

The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately.

Thanks again!


🙏Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently onhttps://opencollective.com/typescript-eslint

@glen-84
Copy link
Contributor

I'm not sure if it makes sense to includeobject here. This rule has always been about object wrappers (of the pascal-case variety). A developer can always addobject to the configuration if they really want it.

Also, as mentioned in#842, I'm not sure if fixing theObject case makes sense.

@bradzacher
Copy link
MemberAuthor

bradzacher commentedAug 13, 2019
edited
Loading

updated code and OP description. LMK your thoughts on this.


This rule has always been about object wrappers

This is not a correct statement.
The default options were just a set of sensible bans that could be applied to any and every codebase. The defaults existed to apply consistency to a codebase's types.

The fact that they were all "use camelCase instead of PascalCase" was purely coincidental.

@glen-84
Copy link
Contributor

updated code and OP description. LMK your thoughts on this.

It's definitely an improvement, although I'm still not 100% sure aboutobject. It would be good to hear some more opinions. 🙂

This is not a correct statement.

I meant that thedefaults have always been about object wrappers, that's not to say that other objects cannot be used, just that it would be a (breaking, BTW) change to how things have been for quite a while now, and as I said, developers can always add this type to their configurations if they have no use for it in their codebases.

@JamesHenryJamesHenry added this to the3.0.0 milestoneAug 19, 2019
@JamesHenryJamesHenry added the breaking changeThis change will require a new major version to be released labelAug 19, 2019
@JamesHenry
Copy link
Member

I'm inclined to agree that this is a breaking change, so I've tagged it for v3 (which won't be miles away, we won't leave as big of a gap as we did between 1 and 2)

@eole1712
Copy link

@JamesHenry Maybe until then we can give a way to disable the default behaviour for Object type ? Should I do a PR for this?

@glen-84
Copy link
Contributor

@bradzacher,

Have you considered addingFunction and{} to the default configuration? (I know that the latter has only been possible in recent times.)

Function -> Prefer a specific function type, like() => void.
{} -> Every type exceptnull andundefined is assignable to{}.

(no fixers)

@threehams
Copy link
Contributor

Function has uses in conditional types and generic constraints, even if though it'sincredibly confusing in other usages.

@bradzacher
Copy link
MemberAuthor

bradzacher commentedDec 28, 2019
edited
Loading

Yeah I haven't revisited this in a while, but both of those seem reasonable.

For context of the types here: I really want the default config to reflect things you shouldn't use 99% of the time, because the 1% is easily covered by disable comments, with a supporting comment explaining why it's a reasonable usage.

I would definitely argue thatFunction should be banned by default as well, as the vast,vast majority of the time you shouldn't be using it.

This is whyObject andobject are banned.

ForObject/object, there should never be a time that you should use them. There is always a better type, and IMO it's really confusing to people thatnumber is assignable toObject. Similarly, it can be pretty confusing to people that any function is assignable toObject/object.
I would say that the majority of the time that people useobject/Object, they are saying "please provide an actual object", not "please provide an object-like value" (see examples below).

The problem is that I added a fixer with the original defaults, and I think there shouldn't be one for these types. "Basic" users probably want the fixer, as they meant an object type, but "advanced" users know what the type means, so they use it "correctly", so the fixer breaks their code. A very clear message explaining exactly what the types do is the best approach for them (same for{}).

functiontest_Record(obj:Record<string,unknown>){obj.foo.toString();// ✅ Expected Error - obj.foo is of type unknownif('foo'inobj){obj.foo.toString();// ✅ Expected Error - obj.foo is of type unknown}if(typeofobj.foo==='object'&&obj.foo){obj.foo.toString();// ✅ No Error}if(objinstanceofDate){obj.getDate();// ✅ No Error}}functiontest_object(obj:object){obj.foo.toString();// ✅ Expected Error - Property 'foo' does not exist on type 'object'.if('foo'inobj){obj.foo.toString();// ❌ Weird error - Property 'foo' does not exist on type 'object'.}if('foo'inobj&&typeofobj.foo==='object'&&obj.foo){// ❌ Unexpected Error - Property 'foo' does not exist on type 'object'.obj.foo.toString();// ❌ Unexpected error - same}}functiontest_Object(obj:Object){obj.foo.toString();// ✅ Expected Errorif('foo'inobj){obj.foo.toString();// ❌ Weird error - Property 'foo' does not exist on type 'object'.}if('foo'inobj&&typeofobj.foo==='object'&&obj.foo){// ❌ Unexpected Error - Property 'foo' does not exist on type 'object'.obj.foo.toString();// ❌ Unexpected error - same}}test_Record({});// ✅ Expected No Errortest_object({});// ✅ Expected No Errortest_Object({});// ✅ Expected No Errortest_Record(1);// ✅ Expected Errortest_object(1);// ✅ Expected Errortest_Object(1);// ❌ Unexpected No Errortest_Record(true);// ✅ Expected Errortest_object(true);// ✅ Expected Errortest_Object(true);// ❌ Unexpected No Errortest_Record(null);// ✅ Expected Errortest_object(null);// ✅ Expected Errortest_Object(null);// ✅ Expected Errortest_Record(()=>{});// ✅ Expected Errortest_object(()=>{});// ❌ Unexpected No Errortest_Object(()=>{});// ❌ Unexpected No Error

repl

@glen-84
Copy link
Contributor

and IMO it's really confusing to people thatnumber is assignable toobject.

Did you meanObject?


I'm still a bit unsure aboutobject. When you say:

The only way to actually interrogate a variable typed with object is via an explicit type assertion

... I assume that you mean:

(objasDate).getDate();

... but you can also useinstanceof to narrow the type:

functiontest(obj:object){if(objinstanceofDate){obj.getDate();}elseif(objinstanceofLocation){obj.hash;}}

@bradzacher
Copy link
MemberAuthor

Sorry, I just realised I messed up my example because TS only reported one error per expression. I've updated it so it's correct.

But yes,Object is essentially "any non-null value", andobject is essentially "any non-null, object-like value".

Object is almost always better represented asunknown, as IMO this is much clearer as to your intent, and forces you to use strict type assertions from the get-go.
object is almost always better represented asRecord<string, unknown>, as it ismuch easier to work with (works with property checks (in etc), works withinstance of), whilst still requiring strict type assertions on the properties.

I'm still a bit unsure about object. When you say ...

Instance of is definitely one way to refine it, but that only works when you have a well defined class structure, etc. But then if you're expecting aDate object, why wouldn't you just union in theDate type on your argument?

What I'm talking about expressly is the "I accept any object; anything that can use the string indexer and works correctly withObject.keys". If you ever want to refine the object down to have certain keys in it ('key' in obj,obj.key != null), it will not work - youhave to explicitly cast it (see my example in the above comment).

@glen-84
Copy link
Contributor

'foo' in obj doesn't work forobject because ofmicrosoft/TypeScript#21732.

I think the main difference between the two is that withRecord<string, unknown>, you have access to any property with typeunknown, which you can use to check for its existence and type.

It would be interesting to see actual use cases of both types.

But then if you're expecting aDate object, why wouldn't you just union in theDate type on your argument?

It was just a contrived example to show the usage ofinstanceof.

@Retsam
Copy link
Contributor

Retsam commentedDec 29, 2019
edited
Loading

Overall, personally, I wouldn't banobject.


object is a fairly niche type - there's relatively few cases where I actually think it's the right signature for a function (though itextends object is sometimes useful as a generic constraint) - but as far as I've seen in most cases where beginners useobject orObject, the "correct" fix is either a more specific type that specifies the intended contract for the object, or a case where they should be using generics. Stuff like:

functionnoviceCode(x:object){return(xasany).a+(xasany).b;}

AndRecord<string, unknown> isn't any better for these cases, it's just as "niche" asobject, I just don't think "arbitrary, non-primitive value, without any restrictions on its keys" is very a useful type (outside of generics). So a linter rule suggesting to replaceobject withRecord<string, unknown> isn't particularly helpful in these cases. Here it might be more helpful to say "consider using a more specific type or a generic constraint", and not have a fixer.


The narrowing thing is a shame, butunknown doesn't narrow very well, either. (I thinkTypeScript#25720 is the most relevant issue).

In fact, it's specifically becauseobject doesn't narrow well thatunknown doesn't narrow well, as the first step in narrowingunknown to a specific type like{foo: string} is to narrow it toobject. (e.g.typeof x === "object" && x)

I imagine this issue will get fixed eventually, (which would mean fixingobject so it can narrow).

And usingRecord<string, unknown> instead is only a partial workaround for the narrowing issue; it works for flat objects, but doesn't help if you're trying to narrow to something like{foo: {bar: string}}, (as you'll run into the same issue when you try to narrowunknown to{bar: string}.


And in general, banningobject seems to diverge from the best practices of the Typescript team - it's a fundamental part of the language: astypeof x === "object" narrows toobject | null, and some of the built-in typings use it (Object.keys being the most prominent example). And I've never seen it recommended to be avoided like the other types banned by default.

The new (unreleased)handbook section onobject, doesn't recommend avoiding it, and in fact specifically recommends it overObject or{}.

object is notObject. Always useobject!

As I mentioned above, it's rare that I thinkobject is the right type to use; I think banningobject can be a useful linter rule, but IMO, that's a fairly opinionated decision, while I think banning the things the handbook specifically recommends avoiding -Object,String,Number,Boolean, (Symbol andFunction aren't specifically mentioned in the handbook, but I think it's largely the same situation) - is unopinionated.

glen-84, brainkim, and mattacosta reacted with thumbs up emojibrainkim reacted with eyes emoji

@bradzacher
Copy link
MemberAuthor

object is a fairly niche type there's relatively few cases where I actually think it's the right signature for a function

Which is the point I was making. 99.9% of the time, you don't want to use it, and the vast majority of codebases won't want to use it. IMO that makes it a great candidate for banning by default.
In the 0.1% of the cases, you can use a disable comment and clearly justify why you're using the type, or you can override the default config (should have a better config solution for the 3.0 release).

I agree though, that a fixer was the wrong approach. For bothObject andobject, I will change it to have a clear message as to why they're discouraged.

sindresorhus reacted with thumbs up emoji

@bradzacher
Copy link
MemberAuthor

Note: I have updated the PR based on feedback.
LMK your thoughts on the new error messages.

glen-84 reacted with thumbs up emoji

@codecov
Copy link

codecovbot commentedJan 13, 2020
edited
Loading

Codecov Report

Merging#848 intov3 willdecrease coverage by0.03%.
The diff coverage is84.61%.

@@            Coverage Diff             @@##               v3     #848      +/-   ##==========================================- Coverage   93.91%   93.87%   -0.04%==========================================  Files         171      172       +1       Lines        7789     7804      +15       Branches     2240     2239       -1     ==========================================+ Hits         7315     7326      +11- Misses        217      221       +4  Partials      257      257
FlagCoverage Δ
#unittest93.87% <84.61%> (-0.04%)⬇️
Impacted FilesCoverage Δ
...xperimental-utils/src/eslint-utils/applyDefault.ts100.00% <ø> (ø)
.../experimental-utils/src/ts-eslint-scope/analyze.ts100.00% <ø> (ø)
packages/eslint-plugin/src/util/objectIterators.ts66.66% <66.66%> (ø)
packages/eslint-plugin/src/rules/ban-types.ts100.00% <100.00%> (ø)
...ackages/eslint-plugin/src/rules/member-ordering.ts98.46% <100.00%> (ø)

@bradzacherbradzacher changed the titlefeat(eslint-plugin): [ban-types] tighten defaultsfeat(eslint-plugin): [ban-types] rework default optionsMay 9, 2020
@bradzacherbradzacher merged commit11265b6 intov3May 9, 2020
@bradzacherbradzacher deleted the ban-types-object branchMay 9, 2020 23:16
@github-actionsgithub-actionsbot locked asresolvedand limited conversation to collaboratorsJun 9, 2020
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.
Reviewers

@sindresorhussindresorhussindresorhus left review comments

@armano2armano2armano2 left review comments

@glen-84glen-84glen-84 requested changes

Assignees
No one assigned
Labels
breaking changeThis change will require a new major version to be releasedenhancementNew feature or request
Projects
None yet
Milestone
3.0.0
Development

Successfully merging this pull request may close these issues.

[ban-types] Conflict with no-explicit-any
8 participants
@bradzacher@glen-84@JamesHenry@eole1712@threehams@Retsam@sindresorhus@armano2

[8]ページ先頭

©2009-2025 Movatter.jp