-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Base rule extension: no-var configuration for declarations #7941
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
Honestly I think we're just fine not doing anything here - declaration files are a fraction of a fraction of a percent of almost every codebase - so building and maintaining new lint rules with exceptions is really not worth the effort or burden, IMO. Better to document the small edge-case as good usecase for a disable comment and move on. The big reason I think a disable is better is because it serves as clear documentation for future readers. Not everyone will know that a var must be used in that location - so commenting like below serves as an educational tombstone to prevent someone accidentally breaking it in future. // eslint-diaable-next-line no-var -- we must use a var here so that the global declaration works as expected. Using let/const would cause incorrect behaviour. |
The fraction is low, but their existence is inevitable, though. Untyped dependencies, front end globals injected by scripts (like the Google API and FB API)... |
Huh. It's really strange that this didn't show up when I searched. Thanks for letting me know.
I'm genuinely not sure if those failure cases are correct. Using
Do you mean eslint documentation will say this, or eslint users will write this documentation it in their projects?
Sorry, do you mean, "better than implementing this issue," "better than disabling the rule," "better than leaving it as a warning/error," or something else entirely? |
I mean a disable comment, as I illustrated in the code block in my response.
Unlikely to get such a comment into ESLint core's docs - but likely fits as an FAQ article or even just these issues serve as documentation as they contain the relevant context. @Josh-Cena for sure - they exist, though the vast majority of So we're talking case that impacts a fraction of a fraction of the codebase (just Most TS devs will never run into this and those that do will likely only touch such a file once and never again. For sure it's a completely valid concern and something that we probably should handle! But given we're already bandwidth constrained - it seems like something much better suited to "document and move on" rather than "implement an entire rule to handle" |
Thanks, @bradzacher I agree about the small number of eslint users this will affect. I'd like to document where this comes up for me:
I empathize and that works fine for me. It's others I worry about. They have to learn what I've learned before they can "document and move on," and it took me a while to get to the point where I understood my symptoms (EDIT: I documented this journey here). But yeah, I get it: there are much better bang for your buck issues out there. :) PS: This is besides the point, but this proposed rule applies to all typescript files, not just declaration files. You can put |
@bradzacher @Josh-Cena did you two come to any conclusions here? Personally I'd lean towards having two separate rules: a |
This was my personal opinion
And I have the same opinion for #2594 - it's 3 years old and has zero engagement (no reactions or comments). It's not an issue the community cares for so we should close it. |
I think the rule is simple enough and has good educational impacts, so I'm not against implementing it. People would turn it on indirectly through their preset anyway—they don't have to be aware of its existence. |
It wouldn't have any educational impact though - it would just stop erroring in this specific case. It wouldn't teach anyone anything! |
The existence of this rule teaches people that they can and should use |
We already have the base rule turned on in our So people don't know they have the rule turned on in the first place because it's turned on in a black-box extended config. And 99.999% of the time they don't get reports from the rule because people don't use So how are we improving education here? It seems to me like nobody would ever see a report or know the rule exists so nothing would change. |
My point about "education" is not an error or a lack of error, which is why I agree a rule that enforces
|
I'd like to provide some context regarding how other toolchains are addressing this issue. Biome implemented this feature earlier this year, while Deno lint, which supports TypeScript as a first-class citizen, doesn't seem to have implemented it yet. I can't estimate the actual cost of adding and maintaining a new rule, but seeing by the implementation of the no-var rule, it doesn't seem like it would be overly long or complex 10-20 lines even with this feature. From an educational perspective, it would be beneficial to have a rule like prefer-declare-global-with-var as well. Many users inadvertently define global variables by using the interface Window {} merging. At least eslint's no-var rule, which implies that using var is incorrect, steers developers in the wrong way. In fact, there's an instance where I recommended using var in a review, only for it to be changed to interface Window {} not sure if this rule's error triggered it or not. |
I'm strong +1 on requiring var in top level declarations. The base rule report logic is 100% trivial (report on every All options for implementing this are easy IMO. Would take no more than a few hours. If we wanted to create a separate rule for top-level declarations, we'd just have If we wanted to create our own fork/extension of no-var, just query all |
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
Our students were also running problems after we enabled the We discourage disabling lint rules as they are learning. I proposed in the issue above that a blessed syntax be documented, but maybe it's better to instead change the |
Coming over from #9865, I think I've landed in the camp of making a
#7941 (comment) in particular too. |
My 2C: |
It's not a bad practice developers are trying to lint out. |
To be clear - I didn't say it wasn't. Personally I'd sooner see us remove I think that we also need to consider our own biases here. We're all biased because we are all power users. We have setup many codebases and maintained infrastructure. We have run into this and so it's something we're aware of. By-and-large the average TS user doesn't touch global declarations and the like. They don't run into this issue because they don't encounter the problem. Put it another way. Is it worth burning our very limited bandwidth building and maintaining an extension rule when it only impacts "users that are writing It's a very specific usecase. AIUI the immutable global case doesn't matter cos you can use a We all have -1'd more common usecases than that. I don't think that we should be continuing with this just because we're personally invested. |
Is this true? I would have thought that the global mutable var pattern would not be so niche - it's quite useful for simple global singletons, not sure I would classify this as a code smell or an antipattern. |
With the advent of ESM -- yeah it is definitely niche and smelly! Why would you create a global singleton via a global var when you can neatly export the singleton from a module and import it where required?
Both of these are non-issues with ESM: class MySingleton { ... }
export { type MySingleton };
const instance = new MySingleton();
export default instance;
// or perhaps you want a lazy init pattern
let instance: MySingleton | null = null;
export default function getOrInit(): MySingleton {
if (instance == null) {
instance = new MySingleton();
}
return instance;
}
|
Hmm, you're giving me something to consider 🤔 - maybe using an IIFE closure would be fine for keeping a single database connection across hot reloading (at least for the first case that I'm thinking of now... not to say that I have thought of every possible use case of global mutable state): Before (with
|
At the very least if you're going to use a global mutable variable for your DB client you should use a non-enumerable property with a module-private |
I guess drifting a bit off topic here (feel free to minimize this comment), but by "someone" I'm assuming you mean some code running in the Node.js global execution context? I guess if you have untrusted code running there you have bigger problems anyway and a symbol won't protect much. |
Symbol properties do not grant you extra safety, because of |
Wait what? Who specced those functions out to enumerate non-enumerable keys?!? But yes we are way off topic now! |
So we looked into the IIFE + closure option above, and it looks like we still need
So it's my conclusion from all of this that Others also confirm this approach:
This is of course only one use case for |
I dunno but to me it sounds like the global variable is easier, but is also hiding a bug in your dev env that could lead to unsafety or undefined behaviour. You're spawning a db connection using one set of code. After a hot reload you have a new set of code that could be expecting to use a db connection that's setup with a different config. So the only way to fix this state would be to hard reload your dev env to forcefully drop the connection and then recreate the entire connection using the new code. |
This bug would only occur with changes to the database configuration:
So, I would argue, more of an edge case. If this is a common pain point for developers (which I would doubt), then a comment at the database configuration code "Restart your dev server on any changes" would resolve it. Or if it's a common pain point and you want to allow developers to ignore comments: a wrapper script for the dev server to restart it on changes to the database config. The main point here for this issue is that IMO this is a valid usage of mutable |
To be clear: I never said it wasn't valid to use
Which I still hold to be true. In modern code generally it's a code smell to rely on mutable global variables and the vast, vast, vast majority of usecases, and in general people are instead going to rely solely on ESM to encapsulate things and declare dependencies explicitly. That doesn't mean there aren't valid usecases for mutable variables in |
Exactly, global declarations is not everyday things, but there are 2.5k |
When I've worked with and taught other developers, there are sometimes points where they need to use a global variable. When they find I'd prefer the linter to default to supporting |
@NotWoods I think you bring up a separate issue. This one is about using |
When declaring a global variable in TypeScript, using `var` vs `let`/`const` yields different behaviour. A declaration using `var` yields the expected result. See [TypeScript playground](https://www.typescriptlang.org/play/?#code/PQgEB4CcFMDNpgOwMbVAGwJYCMC8AiAEwHsBbfUYAPgFgAoew6ZdAQxlAHN1jtX1QAb3qhRGaABdQAGUkAuUAGcJkTIk4ixAN3agAauwXLV6+ptFqJCWK1SgA6mpIB3IebGjHiIyrUa6HgC+9MEMdGDcvPygtqiKivSyEvQGkPReZuHAoM5OxK6ckvS5iC4AdEnFec5lqVWl+WUZYWAlLkpFdG2NSaC4oADkA-XlqX2Dw13VTWrjQ5kRPHzoACoAFpiKXJ2Ry+ubFTtL-PuKtez0uycbZ830i1GrNx3JdFdPB73982-HH2djb6Td6nGaIOaTe7ZRTQdCwbavGFww6I2Gwc5pOhI9F3LIdOEvejYlEQolojGkrHkryU+jQAAeAAdiJApIJAkA) Refs eslint#19173 Refs microsoft/vscode#248075 Refs typescript-eslint/typescript-eslint#2594 Refs typescript-eslint/typescript-eslint#7941
I think this issue can be closed in favor of eslint/eslint#19714. |
eslint/eslint#19714 is a PR, not an issue. Hopefully it is a good solution to this issue however. |
Uh oh!
There was an error while loading. Please reload this page.
Before You File a Proposal Please Confirm You Have Done The Following...
My proposal is suitable for this project
Link to the base rule
https://eslint.org/docs/latest/rules/no-var
Description
Typescript global declarations are more subtle than I ever expected. For a better understanding, I recommend these SO answers:
Here are some key takeaways. If you want to add properties to
globalThis
:import
orexport
keyword (i.e., it's a script, not a module), it is done like so:declare var age: number
.import
orexport
, you write this instead:If
const
orlet
is used instead ofvar
,globalThis.age = 18
would error in both scenarios. So if the intention is to declareglobalThis.age
, it is necessary to use thevar
keyword: usingconst
orlet
would be a mistake. When enabled, the base no-var rule indiscriminately marks allvar
usage as wrong, but in this case, it is required.Fail
Pass
Additional Info
No response
The text was updated successfully, but these errors were encountered: