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

Avoid multiple iterations over nodes and symbols in linter #5204

Open
overlookmotel opened this issue Aug 25, 2024 · 10 comments
Open

Avoid multiple iterations over nodes and symbols in linter #5204

overlookmotel opened this issue Aug 25, 2024 · 10 comments
Assignees
Labels
A-linter Area - Linter C-performance Category - Solution not expected to change functional behavior, only performance good first issue Experience Level - Good for newcomers

Comments

@overlookmotel
Copy link
Collaborator

Various linter rules are implemented as a Rule::run_once method which iterates over nodes or symbols.

Usually this is because they work in 2 passes. The first pass collects info from the AST/symbols and then the 2nd processes that info.

This results in nodes or symbols being unnecessarily iterated over multiple times, when run and run_on_symbol methods are already provided for doing this.

I imagine it'd be faster to:

  • Add a Rule::run_once_at_end method which executes after run and run_on_symbol have executed on all nodes/symbols.
  • Make Rule::run and Rule::run_on_symbol take a &mut self instead of &self.
  • Reimplement these rules:
    • To store their intermediate data in self.
    • Do the main loop over nodes or symbols as a run / run_on_symbol method.
    • Do the 2nd pass in run_once_at_end.

It's iterating over nodes repeatedly which would likely have the largest perf impact. But there are so many rules which iterate over symbols in run_once that it might also be having a noticeable impact in aggregate.

Which rules?

Rules which use run_once in this way are:

Iterating over nodes

  • eslint/func_names
  • eslint/no_this_before_super
  • import/no_named_as_default_member
  • jest/no_large_snapshots
  • jest/prefer_hooks_in_order
  • jsdoc/require_returns
  • tree_shaking/no_side_effects_in_initialization (which does a partial tree traversal)

Iterating over symbols

  • eslint/no_shadow_restricted_name
  • jest/consistent_test_it
  • jest/expect_expect
  • jest/max_expects
  • jest/max_nested_describes
  • jest/no_alias_methods
  • jest/no_conditional_expect
  • jest/no_confusing_set_timeout
  • jest/no_disabled_tests
  • jest/no_done_callback
  • Loads more jest rules + vitest rules via collect_possible_jest_call_node

@DonIsaac What do you think?

@overlookmotel overlookmotel added A-linter Area - Linter C-performance Category - Solution not expected to change functional behavior, only performance labels Aug 25, 2024
@DonIsaac
Copy link
Collaborator

I like where this is going. I need to think on this for a bit before I give an answer.

@Boshen
Copy link
Member

Boshen commented Aug 26, 2024

This is not a priority, none of the rules are enabled by default.

@overlookmotel
Copy link
Collaborator Author

#5201 was an example of fixing a rule which was unnecessarily iterating over symbols.

@Boshen
Copy link
Member

Boshen commented Sep 7, 2024

cc @mysteryven if you're intrested.

@Boshen Boshen added the good first issue Experience Level - Good for newcomers label Sep 7, 2024
@shulaoda
Copy link
Contributor

Is there any progress? I think this is a good idea. 🤔

@Boshen
Copy link
Member

Boshen commented Sep 12, 2024

Is there any progress? I think this is a good idea. 🤔

I marked this as good first issue since no one is working on this 😅

@shulaoda
Copy link
Contributor

Let me implement it. 👀

@overlookmotel
Copy link
Collaborator Author

overlookmotel commented Sep 12, 2024

Thank you @shulaoda! That'd be great.

The ones which iterate over nodes should be first priority, as they'll be the most expensive.

Please keep PRs small so we can review them more easily. 1 PR for the changes to Rule (run_once_at_end etc). And then please update each rule in a separate PR.

Updating Rule methods to use a mutable &mut self may be more complicated than it sounds, because need a nice API for setting up internal state. Feel free to ask for advice if you run into any snags.

@Boshen
Copy link
Member

Boshen commented Sep 13, 2024

Make Rule::run and Rule::run_on_symbol take a &mut self instead of &self.

This is not going to work. The rules are singletons, immutable by design and cannot contain state. They are shared across threads for all files run in parallel.

If we really need state, we need to add the states to context, something like FxHashMap<Rule, Box<dyn RuleState>>

@overlookmotel
Copy link
Collaborator Author

Ah I didn't realize that rules were shared across threads.

As far as I understand, each rule gets its own LinterCtx object. Are they also shared across threads?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-linter Area - Linter C-performance Category - Solution not expected to change functional behavior, only performance good first issue Experience Level - Good for newcomers
Projects
None yet
Development

No branches or pull requests

4 participants