+
Skip to content
This repository was archived by the owner on Aug 31, 2023. It is now read-only.

feat(rome_analyze): allow rules to dynamically emit local suppressions #2909

Merged
merged 2 commits into from
Jul 22, 2022

Conversation

leops
Copy link
Contributor

@leops leops commented Jul 20, 2022

Summary

This PR adds a new suppressed_nodes method to the Rule trait. This hook allows lint rules to emit dynamic node suppressions for their own query, for instance to prevent the rule from matching again on a node that would be removed by a previously-emitted code action.

Test Plan

I've modified the UseTemplate rule to use this new method instead of recursively checking for nested binary expressions, this rule has tests checking the rule does not trigger again on recursive string concatenation expressions

@leops leops requested a review from xunilrj as a code owner July 20, 2022 16:20
@leops leops requested a review from a team July 20, 2022 16:20
@leops leops temporarily deployed to aws July 20, 2022 16:20 Inactive
@leops
Copy link
Contributor Author

leops commented Jul 20, 2022

!bench_analyzer

@github-actions
Copy link

github-actions bot commented Jul 20, 2022

@github-actions
Copy link

Analyzer Benchmark Results

group                     main                                   pr
-----                     ----                                   --
analyzer/css.js           1.00      3.4±0.08ms     3.5 MB/sec    1.04      3.5±0.03ms     3.3 MB/sec
analyzer/index.js         1.00      6.9±0.16ms     4.7 MB/sec    1.03      7.1±0.15ms     4.5 MB/sec
analyzer/lint.ts          1.00      4.3±0.08ms     9.8 MB/sec    1.02      4.3±0.14ms     9.6 MB/sec
analyzer/parser.ts        1.00     11.0±0.23ms     4.4 MB/sec    1.01     11.1±0.06ms     4.3 MB/sec
analyzer/router.ts        1.01      6.8±0.07ms     8.7 MB/sec    1.00      6.8±0.11ms     8.8 MB/sec
analyzer/statement.ts     1.00      8.9±0.18ms     4.0 MB/sec    1.00      8.9±0.12ms     4.0 MB/sec
analyzer/typescript.ts    1.05     13.3±0.30ms     4.1 MB/sec    1.00     12.7±0.27ms     4.3 MB/sec

/// Internal state for a given rule
#[derive(Clone, Default)]
struct RuleState<L: Language> {
suppressions: Rc<RefCell<RuleSuppressions<L>>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be an alternative to using an Rc + RefCell to split node.rules into

  • node.rules: Storing the rule + an index into the state array
  • states: A Vec storing the states of all Rules.

I don't understand the code well enough to tell if this isn't leading to any other borrow checker issues but may be an alternative to express that the state is shared and that node.rules is just a data structure to get faster rule lookup.

/// Internal state for a given rule
#[derive(Clone, Default)]
struct RuleState<L: Language> {
suppressions: Rc<RefCell<RuleSuppressions<L>>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain me what Rc and RefCell are used for here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to improve query matching performance, a given Rule may actually be represented as multiple RegistryRule withing a registry instance: for instance the NoEmptyPattern will have an entry matching JsArrayBindingPattern and another one matching JsObjectBindingPattern nodes.
Now although these are two distinct entries into the registry, they should share the same underlying RuleState since they belong to the same rule, and Rc allows that by implementing a reference-counted smart pointer managing a single instance of RuleSuppressions shared between multiple entries.
However since the state is now shared and the reference count is a dynamic runtime property, Rust cannot statically check how many outstanding references to the state exists and will not allow it to be mutated, so RefCell gets around that limitation by pushing this verification to the runtime, keeping a counter of outstanding mutable and immutable references and panicking if the code tries to do both at the same time.
Now as @MichaReiser mentioned here it would also be possible to manually manage how the state is shared by storing a vector of RuleState in PhaseRules, and having each RegistryRule simply store an index into that vector instead of an explicit reference-counted pointer. This would allow the Rust compiler to statically check the mutable access to the state and may actually be easier to understand so I'll try implementing this approach instead

/// matching the `Query`. This is useful for rules that implement a code
/// action that recursively modifies multiple nodes at once, this hook
/// allows these rules to avoid matching on those nodes again.
fn suppressed_nodes(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add an example - code snippet - of how to use this new function into the documentation?

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Jul 21, 2022

Deploying with  Cloudflare Pages  Cloudflare Pages

Latest commit: 124084b
Status: ✅  Deploy successful!
Preview URL: https://4fb64414.tools-8rn.pages.dev
Branch Preview URL: https://feature-rule-suppression.tools-8rn.pages.dev

View logs

@leops leops temporarily deployed to aws July 21, 2022 08:45 Inactive
@xunilrj
Copy link
Contributor

xunilrj commented Jul 21, 2022

I am wondering what would be the performance impact of not putting this in the "rule framework", but making it easy for rules to use RuleSuppressions directly.

We would be avoiding 99% of contains(...) on an empty set.

@leops
Copy link
Contributor Author

leops commented Jul 21, 2022

We would be avoiding 99% of contains(...) on an empty set.

I expected this cost to be quite low since the implementation of HashSet::contains has a "fast path" and aborts immediately if the underlying hash table is empty, but it's incredibly easy to make incorrect assumptions about performance so I could be wrong about that ...

@leops leops merged commit 2a988fb into main Jul 22, 2022
@leops leops deleted the feature/rule-suppression branch July 22, 2022 09:32
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载