Skip to content

[no-redeclare] False positive for types and values with the same name #2585

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

Closed
bogdanb opened this issue Sep 21, 2020 · 12 comments
Closed

[no-redeclare] False positive for types and values with the same name #2585

bogdanb opened this issue Sep 21, 2020 · 12 comments
Labels
package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin working as intended Issues that are closed as they are working as intended

Comments

@bogdanb
Copy link
Contributor

bogdanb commented Sep 21, 2020

  • [×] I have tried restarting my IDE and the issue persists.
  • [×] I have updated to the latest version of the packages.
  • [×] I have read the FAQ and my problem is not listed.

Repro

{
  "rules": {
    "@typescript-eslint/no-redeclare": ["error"],
  }
}
const Type = { test: "example" };
type Type = typeof Type;

Expected Result

I would expect the code to be allowed without complaint.

Actual Result

$ npx eslint test.ts

C:\workspace\project\test.ts
  2:6  error  'Type' is already defined  @typescript-eslint/no-redeclare

Additional Info

No exception was thrown. (Not clear from the issue template if you want the --debug output regardless, I assume it’s useful only for crashes.)

The rule is enabled by some preset, not sure which. I use these: "plugin:react/recommended", "plugin:react-hooks/recommended", "standard-with-typescript", "prettier", "prettier/@typescript-eslint", "prettier/babel", "prettier/react", "prettier/standard"

The behavior changed sometimes in the last three months, it didn’t use to complain about these at the start of summer.

For the record, in my actual use case I’m using io-ts, where this is a common pattern.

Versions

package version
@typescript-eslint/eslint-plugin 4.1.1
@typescript-eslint/parser 4.1.1
TypeScript 4.0.3
ESLint 7.9.0
node 14.7.0
@bogdanb bogdanb added package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin triage Waiting for team members to take a look labels Sep 21, 2020
@bogdanb
Copy link
Contributor Author

bogdanb commented Sep 21, 2020

Might want to look at similar-sounding eslint/typescript-eslint-parser#443

@bogdanb
Copy link
Contributor Author

bogdanb commented Sep 21, 2020

I reproduced it with 4.0.0, when the rule seems to have been added. I think previously there was only an eslint rule with the same name, and that one did not report these cases.

@bradzacher bradzacher added working as intended Issues that are closed as they are working as intended and removed triage Waiting for team members to take a look labels Sep 21, 2020
@bradzacher
Copy link
Member

This is mentioned in the rule docs:

https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-redeclare.md#ignoredeclarationmerge

Note: Even with this option set to true, this rule will report if you name a type and a variable the same name. This is intentional. Declaring a variable and a type and a variable the same is usually an accident, and it can lead to hard-to-understand code. If you have a rare case where you're intentionally naming a type the same name as a variable, use a disable comment. For example:

@bogdanb
Copy link
Contributor Author

bogdanb commented Sep 21, 2020

Huh, didn't notice that part after reading it three times... Sorry!

I'd like to ask for an option to change this behavior (e.g. allowSameNameTypeAndValue).

Stuff like IO-TS (mentioned above) use this pattern a lot.

Using a comment to disable the rule is not quite the same, because I would like to be notified if I do have two values or two types with the same name (I did have that issue more than once, and it's hard to notice).

Also, I'd have to add a couple hundred annotations, I don't feel like having that many exceptions would be good practice.

@bradzacher
Copy link
Member

TBH I question why you would even bother using the rule - typescript itself does the same check much better without the need for any configuration.


Why do you need to export a type that is just an alias of typeof? Wouldn't it be simpler clearer to just use typeof T at the usage site instead of shadowing the variable name into the type scope?

@bogdanb
Copy link
Contributor Author

bogdanb commented Sep 21, 2020

For your first question, I’m not actually using the rule myself, it comes from one of the standard presets. (Not sure which, something like prettier. I can check tomorrow if it’s important.) I assumed that since someone actually added this rule in typescript-eslint (it didn’t exist before 4.0.0) it actually does something, but I don’t really know the reasoning. (I did disable it for my project, but I’ve no idea what else it would have caught that I’ll be missing from now on.)

For your second question: the code above is just a minimal example to reproduce the issue. In actual code using IO-TS the second line would be something like:

   type Test = t.TypeOf<typeof Test>

That is much more annoying to type a few hundred times, not to mention hard to read. (The first line would also be something more complex that declares the type’s structure as an object accessible at runtime.)

Also, both the variable and the type are logically representations of the same conceptual “type” (one for compile-time checks, one for run-time checks), so having the same name makes sense in the context of IO-TS: you are actually referring to the same thing, just from different perspectives. (The library tries hard to allow almost identical syntax in both contexts to help this intuition.) Having Type in some places and t.TypeOf<typeof Type> (or even typeof Type, were it possible) would actually be more confusing.

@bradzacher
Copy link
Member

I assumed that since someone actually added this rule in typescript-eslint it actually does something

I added it because I know people use it. Some people don't actually use TS to build their code so they like to rely on lint rules for validation. I don't know why... it defeats the purpose of using TS - but people have weird setups 🤷‍♂️.

If I didn't add it, then I know we would have gotten reports about the base no-redeclare rule not working for declaration merging and for function overload cases.

Prior to 4.0, this was handled implicitly by our super dodgy hacked together scope analyser (it was hacked so it didn't emit multiple declarations). Post 4.0, the full scope analyser emits these as multiple declarations on purpose, so an extension rule is required to teach the rule about what these mean.

but I’ve no idea what else it would have caught that I’ll be missing from now on

Nothing at all. no-redeclare only catches cases where you declare the same name within the same block scope. TS catches and errors on all of these cases itself except for:

var x = 1;
var x = 2;

I doubt you use var, so it's a non-issue.


The full io-ts example makes much more sense, and it is definitely valid to do the shadowing there.

@bogdanb
Copy link
Contributor Author

bogdanb commented Sep 21, 2020

OK, so if I got that right, since my current project usese TS to compile stuff, simply disabling the no-redeclare rule seems to be enough. So as far as I’m concerned, there’s no urgency.

But you agree that in theory the usage is valid (also, overwriting standard presets is not ideal so it would be nice if the preset authors had a better option). So, should this be reopened? Should I open a separate feature request for the option? I don’t think I could do a decent job trying to add this, given how little I understand of the ts-eslint’s internals, so there’s little chance of a pull request from me anytime soon, but maybe you want to have it tracked?

@newswim
Copy link

newswim commented Oct 21, 2020

I've noticed the rule throwing an error for function overloads in typescript.

@ejose19
Copy link

ejose19 commented Oct 31, 2020

@bradzacher I saw your comment regarding an option to allow this, and here's an use case that currently it's the best way to deal with extending enums, maybe you can reconsider that option so devs don't need to use eslint-disable-next-line on each extended enum and similar use cases?

@brainkim
Copy link
Contributor

brainkim commented Nov 12, 2020

I’m running into this issue when trying to upgrade. Here’s an example of the code which errors:

export const Fragment = "";
export type Fragment = typeof Fragment;

Maybe we can have an exception if the right hand side of the type assignment has a typeof operator?

@yss14
Copy link

yss14 commented Nov 23, 2020

Introducing this intentional behaviour leads to a really bad developers experiences when upgrading typescript-eslint because we have to if we want to move on to typescript@4.x.
We would have to add the ignore comment to over 400+ occurrences.

Now the open question for our team is to completely disable the no-redeclare rule or not to move on to typescript@4.x, where completely disabling the no-redeclare is unfortunately the preferred "solution".

For such tools like eslint, which is used in so many different projects, there should always be an option to control this behaviour. Otherwise many teams develop an aversion to linters imho.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Dec 24, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin working as intended Issues that are closed as they are working as intended
Projects
None yet
Development

No branches or pull requests

6 participants