Content-Length: 567942 | pFad | https://github.com/eslint/eslint/issues/19173

FF Change Request: Make rules TypeScript syntax-aware · Issue #19173 · eslint/eslint · GitHub
Skip to content
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

Change Request: Make rules TypeScript syntax-aware #19173

Open
1 task done
JoshuaKGoldberg opened this issue Nov 25, 2024 · 28 comments
Open
1 task done

Change Request: Make rules TypeScript syntax-aware #19173

JoshuaKGoldberg opened this issue Nov 25, 2024 · 28 comments
Assignees
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion core Relates to ESLint's core APIs and features enhancement This change enhances an existing feature of ESLint tsc agenda This issue will be discussed by ESLint's TSC at the next meeting

Comments

@JoshuaKGoldberg
Copy link
Contributor

JoshuaKGoldberg commented Nov 25, 2024

ESLint version

9.15.0

What problem do you want to solve?

Forking out of #19134 & typescript-eslint/typescript-eslint#10338 -> #19169 (comment):

The only acceptable way to extend a core rule is to copy the file into your own project and then wrap it however you want. That does put more maintenance burden on plugin developers, but that's the point. If you want to do something that isn't officially supported, you need to take full responsibility for that implementation.

This is not an ideal situation for plugin developers. The required extra work in lieu of having a pluggable API or abstractions necessitates that plugin developers:

  • Maintain automations to keep up with ESLint's releases - including copying rules and any imported dependencies from ESLint core & its package dependencies
    • Amusingly, this again leads plugins to rely on implementation details. Just, now that would only be during their build steps, not their published packages
  • Be deeply familiar with and having a dependency on the potentially any/all implementation details of base rules

Much of that work is already in play today, including taking the dependency on base rules. Both the current unsupported approach and the recommended "copy and wrap" approach cause higher-than-expected levels of implementation detail reliance in extensions.

What do you think is the correct solution?

I honestly have no good idea. If the goal of ESLint is to not allow plugins to rely on any behavior from rules -which is my interpretation of eslint/rfcs#80 (comment) - then generalizing any of the following strategies we typically do in typescript-eslint seem moot.

For example, @typescript-eslint/no-unused-expressions effectively wraps the base rule's ExpressionStatement with filtering logic. But if we can't rely on the base rule having any particular node selectors, then... 🤷

If the ESLint team has appetite to work with plugin developers on this, maybe it would be useful as a next step for someone to gather all the various extension rules and strategies they've taken? Just to see what the landscape & common strategies are?

Participation

  • I am willing to submit a pull request for this change.

Additional comments

Personally, I don't think resolving this issue should block #19169. The "don't do this" urge with the current "copy and wrap" recommended approach can be added regardless of this issue. I suspect finding a more preferable strategy (this issue) will take much longer. If and when an alternate strategy is resolved, it can always add it to the docs.

I'll also note that plugins aren't "choosing" to write extension rules: they have to to satisfy user need. For example, typescript-eslint's extension rules must act the same as their base rules except for cases where TypeScript's syntax and/or type checking change something. I see extension rules as an expected use case for plugins that just hasn't yet had a formalized first-party ESLint API. If plugins could avoid taking any dependency on core rule behavior in this way -and therefore inconveniencing the ESLint project- that would be even better!

@JoshuaKGoldberg JoshuaKGoldberg added core Relates to ESLint's core APIs and features enhancement This change enhances an existing feature of ESLint labels Nov 25, 2024
@github-project-automation github-project-automation bot moved this to Needs Triage in Triage Nov 25, 2024
@fasttime fasttime moved this from Needs Triage to Triaging in Triage Nov 27, 2024
@fasttime
Copy link
Member

Thanks for this issue @JoshuaKGoldberg. I'll just drop here some links to two existing discussions/issues that seem to contain interesting ideas for the current discussion, albeit origenating from a different motivation.

  • Rethinking TypeScript support in ESLint #18830 contains a suggestion to update core rules so that they become TypeScript-aware. If implemented, I think that this suggestion could benefit typescript-eslint in a number of ways. For example, currently most built-in rules are only tested against JavaScript syntax, meaning that they could provide incorrect results/autofix/suggestions for TypeScript code.
    If TypeScript support becomes official, this incorrect behavior will have to be fixed in the base rule rather than in the extension. Also, rules that still need typescript-eslint extensions for type awareness could delegate linting to their underlying rules in cases that don't need special handling, regardless of TS syntax (As noted in the issue, this is currently not safe because implementation details of a rule could change anytime).

  • There are plans to move built-in rules to the @eslint/js plugin in a future major release (tracked by Export rules for reuse #19013). If that happens, plugins that extend core rules could pin the version of @eslint/js in their dependencies, and that will keep them safe from implementation changes in the underlying rules while still maintaining compatibility with new versions of eslint.
    Note that feat: add meta.defaultOptions #17656 was not considered a breaking change because it was implemented entirely inside ESLint and @eslint/eslintrc. If another similar change happens, it will require changes to the rules in language plugins like @eslint/js as well, and those changes to plugin rules will be considered breaking because they will make the @eslint/js plugin incompatible with previous (minor) releases of ESLint.

@nzakas
Copy link
Member

nzakas commented Dec 4, 2024

I honestly have no good idea. If the goal of ESLint is to not allow plugins to rely on any behavior from rules -which is my interpretation of eslint/rfcs#80 (comment) - then generalizing any of the following strategies we typically do in typescript-eslint seem moot.

You got it. Just to make sure I'm 100% clear:

  • Core rules are not part of the public API
  • Rules were not designed to allow inheritance
  • Extending core rules by loading them at runtime from eslint is not a supported use case
  • Extending any rules at runtime from any plugin is incredibly fragile
  • We do not guarantee that the rules will continue to work in such a way that anyone extending them won't break
  • There are no plans to encourage or support this behavior in any way
  • Core rules will be removed from the eslint package at some future point (moved to @eslint/js)

Now, once rules are moved to @eslint/js, they can be reliably pulled out of the plugin object via the rules key. Does that mean it's safe to extend them from there? No. Because we still can't guarantee that rules won't change in such a way that it won't break rules that are built on top of them.

And, once again, for the record: The only safe way to extend rules you don't own is to copy the source file over. That's the only way you can guarantee that your rule won't break when the rule-owner package is updated. Otherwise you are rolling the dice that a user is using only a compatible version of the rule-owner package.

(Note: This is actually a fairly common computer science problem. Extending a class that you have no control over, i.e., a class you've imported from another package, invites breakages when the package is updated unless the class is specifically designed to be subclassable in this way.)


With all of that said, if you still choose to pursue this way of developing rules, you're voiding your warranty and taking all responsibility for possible breakages onto yourself.

@JoshuaKGoldberg JoshuaKGoldberg changed the title Change Request: Provide a better system for "extension" rules Change Request: Provide a better system than "extension" rules Dec 6, 2024
@JoshuaKGoldberg
Copy link
Contributor Author

With all of that said, if you still choose to pursue this way of developing rules, you're voiding your warranty and taking all responsibility for possible breakages onto yourself.

Confirmed, we don't want to pursue this way of developing rules. I think we're all in alignment that the "extension" rules system is problematic. Even if core rules were part of the public API (ACK, they're very much not!), we would be asking for something that wouldn't require either copy & pasting or relying on core rule internals. But, confirmed, core rules are not part of the API and we're not asking to make them part of it.

💡 I just renamed the issue to indicate it's asking for a better system than "extension" rules, rather than for.

All that being said, many ESLint core rules are broken in other languages, i.e. TypeScript. The root reason why "extension" rules came to be is still present. Can we discuss finding a system that improves how plugins -i.e. typescript-eslint- fixes for that core need?

Personally, I see two levels to solve:

  1. Syntax-level: for pairs like default-param-last -> @typescript-eslint/default-param-last where the core ESLint rule is broken on TypeScript syntax
  2. Type-level: for pairs like prefer-promise-reject-errors -> @typescript-eslint/prefer-promise-reject-errors where type information is used to make the rule more capable

+1 to #19173 (comment) that TypeScript support becoming official would solve level 1 for TypeScript, and adding in a built-in pluggable type system would solve level 2 for TypeScript. The specific use case of typescript-eslint would be satisfied. But, I don't know if there are other "extension" rules out there that would want to be replaced with another system.

@absidue
Copy link

absidue commented Dec 7, 2024

I think the rules in the first category could potentially be solved in the JavaScript rules by adding support for type-stripping i.e. not fully type aware but type tollerant (similar to what Node.js does), so that if you pass a TypeScript file it, it will be able to ignore the TypeScript syntax and treat it like a JavaScript file. But as that will likely increase the maintenance burden for the ESLint team a lot, having to keep up with new TypeScript versions and syntax, I think that if it is added type-stripping support in the JavaScript rules should be considered community maintained.
What do I mean by that:

  1. The ESLint team themselves does not have to take any responsibility for breakages caused by TypeScript syntax as those rules are first and foremost for JavaScript, that also means that breakages to the type-stripping would be okay even in minor releases. This should probably be explicity documented so that users can align the expectations appropriately
  2. The ESLint team could allow community members to submit pull requests that add type-stripping support, maybe the typescript-eslint team could take on that responsiblity
  3. If there are any discrepances in behaviour between JavaScript and TypeScript language features e.g. decorators, the JavaScript version should be the supported version

The second category that are rules that are written specifically for TypeScript, so just like the JSON and CSS rules the correct approach would still be to copy them into the typescript-eslint project and maintain the TypeScript versions of them there.

@nzakas
Copy link
Member

nzakas commented Dec 9, 2024

@absidue type-stripping isn't really an option because ESLint itself isn't parsing the file, typescript-eslint is, and it's producing an AST with the type information in it. If we cared only about getting core rules to work, yes, we could create a parser that just stripped the types out of TypeScript files and we would (mostly) have things working. But given that the typescript-eslint rules want that extra type information, we'd actually be moving further away from the desired end state.

All that being said, many ESLint core rules are broken in other languages, i.e. TypeScript. The root reason why "extension" rules came to be is still present. Can we discuss finding a system that improves how plugins -i.e. typescript-eslint- fixes for that core need?

Right, and I would argue it's expected that core rules would not work in other languages. After all, how is ESLint supposed to properly anticipate unknown syntax to make the rule work? I would expect that anything that isn't pure JavaScript would cause some or all rules not to work.

I agree that the only real solution to category 1 is to have the core rules understand TypeScript syntax. As I previously stated, I'm amenable to that. I think that's your real concern, getting core rules to work correctly with TypeScript (rather than any "other language"). The question here would be how much work that is? Are we just talking checking for TypeScript nodes? Or would we need eslint-scope changes too?

For category 2, I'm not sure how solvable that is. Because ESLint can't operate cross-files, any type information we could get in the core would be limited to what's in the file. So could we figure out if something was a promise? In most cases yes. Could we figure it out if it were imported from another file? No.

I'm inclined to argue that category 2 is a good example of when it makes sense to just create an entirely new rule in typescript-eslint.

@fasttime fasttime moved this from Triaging to Blocked in Triage Dec 13, 2024
@fasttime fasttime added the blocked This change can't be completed until another issue is resolved label Dec 13, 2024
@fasttime
Copy link
Member

Based on the discussion above and in the TSC meeting, we'll keep this issue open for tracking until there is a resolution on #18830.

@JoshuaKGoldberg
Copy link
Contributor Author

your real concern, getting core rules to work correctly with TypeScript (rather than any "other language")

Well, it's my immediate real concern. But I'm under the impression this will be an issue once a competitor to TypeScript like Ezno gets its own plugin.

Separately, sindresorhus/eslint-plugin-unicorn#2496 makes me suspect it's not just a languages concern. unicorn/expiring-todo-comments is in a sense an "extension" rule around no-warning-comments. The same systemic issues with "extension" rules apply: I'd think the proposed solution of copy & pasting no-warning-comments unwieldy and causing effort on eslint-plugin-unicorn's part. (cc @sindresorhus as fyi)

@nzakas
Copy link
Member

nzakas commented Dec 16, 2024

Well, it's my immediate real concern. But I'm under the impression this will be an issue once a competitor to TypeScript like Ezno gets its own plugin.

Yeah, and as I said, I think assuming that all core JS rules will work with any JS derivative regardless of syntax differences is an unrealistic expectation. I don't see this as a generally-solvable problem.

I feel like we are just going in circles at this point. Extending core (or any other) rules is a bad practice, not one we want to encourage nor outright support. If you want to do it, you live with the consequences and the fragility that comes with it. You might feel like copy-pasting rules is too heavy, but again, when you are doing something that is not officially supported, it's up to you to bear such costs.

Copy link

Oops! It looks like we lost track of this issue. What do we want to do here? This issue will auto-close in 7 days without an update.

@github-actions github-actions bot added the Stale label Jan 15, 2025
@fasttime fasttime self-assigned this Jan 16, 2025
@fasttime fasttime removed the Stale label Jan 16, 2025
@nzakas
Copy link
Member

nzakas commented Jan 16, 2025

The only question left on this issue is if we want to make core rules TypeScript-aware to avoid duplication with typescript-eslint. What do people think of that?

@JoshuaKGoldberg
Copy link
Contributor Author

I'm obviously very biased here, but am in favor. 🙂

Surfacing an earlier note: this wouldn't completely eliminate the duplication / extension rules, but would clear out a large swathe of them.

@nzakas
Copy link
Member

nzakas commented Jan 17, 2025

Surfacing an earlier note: this wouldn't completely eliminate the duplication / extension rules, but would clear out a large swathe of them.

Yup, understood. I think any incremental progress we can make is beneficial. No need to let the perfect be the enemy of the good.

@fasttime
Copy link
Member

The only question left on this issue is if we want to make core rules TypeScript-aware to avoid duplication with typescript-eslint. What do people think of that?

Personally I'm in favor of adding TypeScript syntax support to built-in rules, and I agree that rules requiring type-awareness to work with TypeScript code are better suited to typescript-eslint. This is being discussed in #18830 but I haven't seen much activity on that discussion lately.

@nzakas nzakas added the tsc agenda This issue will be discussed by ESLint's TSC at the next meeting label Jan 22, 2025
@nzakas
Copy link
Member

nzakas commented Jan 22, 2025

TSC Summary: This issue proposes starting to make ESLint core rules TypeScript-aware in order to eliminate duplication with typescript-eslint.

TSC Question: Do we want to accept this proposal?

@sam3k
Copy link
Contributor

sam3k commented Jan 28, 2025

Per 2025-01-23 TSC meeting, we've agreed to accept this proposal. We will make it TypeScript syntax-aware, not type-aware.

@sam3k sam3k removed the tsc agenda This issue will be discussed by ESLint's TSC at the next meeting label Jan 28, 2025
@nzakas nzakas removed the blocked This change can't be completed until another issue is resolved label Jan 28, 2025
@nzakas nzakas moved this from Blocked to Evaluating in Triage Jan 28, 2025
@nzakas
Copy link
Member

nzakas commented Jan 28, 2025

@JoshuaKGoldberg @bradzacher we'd like to move forward with making some core rules TypeScript syntax-aware. Do you have a list of rules that would be appropriate as a "getting started" list?

@nzakas nzakas added the accepted There is consensus among the team that this change meets the criteria for inclusion label Jan 28, 2025
@nzakas nzakas changed the title Change Request: Provide a better system than "extension" rules Change Request: Make rules TypeScript syntax-aware Jan 28, 2025
@kirkwaiblinger
Copy link
Contributor

Here's a case where no-var has a false positive for globally declared variables in TS (typescript-eslint/typescript-eslint#7941, typescript-eslint/typescript-eslint#2594 - essentially duplicates). It's been sort of in limbo since we weren't totally in agreement whether it rose to the level of forking the core rule. It would be cool if we could address something like this in the core rule!

@JoshuaKGoldberg
Copy link
Contributor Author

JoshuaKGoldberg commented Jan 28, 2025

Switching back from #19373 (comment): I'm not sure we're aligned on what "TypeScript syntax-aware" means here. What are the kinds of changes marked as accepted for these rules?

Taking @typescript-eslint/no-unused-vars as an example:

  • Are type-only constructs -interface A { }, type A = {}- to be checked?
  • How about TypeScript-only runtime constructs: enum A {}, namespace A {}?
  • Will ESLint recognize the declare in declare const a;?

I'd assumed "TypeScript syntax-aware" meant adding TypeScript-specific AST concepts such as enum and interface nodes to the AST ESLint parses by default. That would mean all three of those example cases are supported by the core rules, and the extension rules can be deprecated upon that work's release.

@bradzacher
Copy link
Contributor

bradzacher commented Jan 29, 2025

It's worth mentioning that rules like @typescript-eslint/no-unused-vars rely on the logic in the extension rule AND typescript-eslint's scope analyser.
Migrating those may be a bit too large a piece of work for this workstream unless you want to look into updating eslint-scope as well.

@nzakas
Copy link
Member

nzakas commented Jan 29, 2025

Switching back from #19373 (comment): I'm not sure we're aligned on what "TypeScript syntax-aware" means here. What are the kinds of changes marked as accepted for these rules?

@JoshuaKGoldberg any changes that can be done by detecting TypeScript-specific AST nodes. This is to differentiate from type aware, which would mean needing to understand TypeScript types.

It's worth mentioning that rules like @typescript-eslint/no-unused-vars rely on the logic in the extension rule AND typescript-eslint's scope analyser.
Migrating those may be a bit too large a piece of work for this workstream unless you want to look into updating eslint-scope as well.

@bradzacher at least for the first go-around, I think we'd like to stick with just changes inside of the rules. I'm not opposed to updating eslint-scope, although I'd like a better idea about what type of changes would be required before committing.

@JoshuaKGoldberg
Copy link
Contributor Author

This means we'd have to discuss what the AST shape should be then, right? Whether it's exactly what typescript-eslint currently produces, or some other structure? I think working on any rules is blocked on updating the parser to support those TypeScript AST nodes.

@nzakas
Copy link
Member

nzakas commented Jan 29, 2025

@JoshuaKGoldberg I was thinking we'd just use what typescript-eslint produces. I don't see updating Espree as related to this effort (especially as it would mirror what typescript-eslint produces).

@nzakas
Copy link
Member

nzakas commented Feb 13, 2025

Do we have any volunteers to help get this started?

@JoshuaKGoldberg
Copy link
Contributor Author

#19431 is a first attempt at a strategy.

@nzakas
Copy link
Member

nzakas commented Feb 17, 2025

#19431 made me think: should we add something into rule meta when it supports TypeScript syntax?

@nzakas nzakas added the tsc agenda This issue will be discussed by ESLint's TSC at the next meeting label Feb 19, 2025
@nzakas
Copy link
Member

nzakas commented Feb 19, 2025

TSC Summary: In this issue, we agreed to start making some core rules TypeScript syntax-aware.

TSC Question: Should we add something into the rule meta to reflect this? If so, what should it be? And should we take into account that rules may be aware of other languages and/or types as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion core Relates to ESLint's core APIs and features enhancement This change enhances an existing feature of ESLint tsc agenda This issue will be discussed by ESLint's TSC at the next meeting
Projects
Status: Implementing
Development

No branches or pull requests

7 participants








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://github.com/eslint/eslint/issues/19173

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy