biome_analyze/
registry.rs

1use crate::{
2    context::RuleContext,
3    matcher::{GroupKey, MatchQueryParams},
4    query::{QueryKey, Queryable},
5    signals::RuleSignal,
6    AddVisitor, AnalysisFilter, GroupCategory, QueryMatcher, Rule, RuleGroup, RuleKey,
7    RuleMetadata, ServiceBag, SignalEntry, Visitor,
8};
9use biome_diagnostics::Error;
10use biome_rowan::{AstNode, Language, RawSyntaxKind, SyntaxKind, SyntaxNode};
11use rustc_hash::{FxHashMap, FxHashSet};
12use std::{
13    any::TypeId,
14    borrow,
15    collections::{BTreeMap, BTreeSet},
16};
17
18/// Defines all the phases that the [RuleRegistry] supports.
19#[repr(usize)]
20#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub enum Phases {
22    Syntax = 0,
23    Semantic = 1,
24}
25
26/// Defines which phase a rule will run. This will be defined
27/// by the set of services a rule demands.
28pub trait Phase {
29    fn phase() -> Phases;
30}
31
32/// If a rule do not need any service it can run on the syntax phase.
33impl Phase for () {
34    fn phase() -> Phases {
35        Phases::Syntax
36    }
37}
38
39pub trait RegistryVisitor<L: Language> {
40    /// Record the category `C` to this visitor
41    fn record_category<C: GroupCategory<Language = L>>(&mut self) {
42        C::record_groups(self);
43    }
44
45    /// Record the group `G` to this visitor
46    fn record_group<G: RuleGroup<Language = L>>(&mut self) {
47        G::record_rules(self);
48    }
49
50    /// Record the rule `R` to this visitor
51    fn record_rule<R>(&mut self)
52    where
53        R: Rule + 'static,
54        R::Query: Queryable<Language = L>,
55        <R::Query as Queryable>::Output: Clone;
56}
57
58/// Stores metadata information for all the rules in the registry, sorted
59/// alphabetically
60#[derive(Debug, Default)]
61pub struct MetadataRegistry {
62    inner: BTreeSet<MetadataKey>,
63}
64
65impl MetadataRegistry {
66    /// Return a unique identifier for a rule group if it's known by this registry
67    pub fn find_group(&self, group: &str) -> Option<GroupKey> {
68        let key = self.inner.get(group)?;
69        Some(key.into_group_key())
70    }
71
72    /// Return a unique identifier for a rule if it's known by this registry
73    pub fn find_rule(&self, group: &str, rule: &str) -> Option<RuleKey> {
74        let key = self.inner.get(&(group, rule))?;
75        Some(key.into_rule_key())
76    }
77
78    pub(crate) fn insert_rule(&mut self, group: &'static str, rule: &'static str) {
79        self.inner.insert(MetadataKey {
80            inner: (group, rule),
81        });
82    }
83}
84
85impl<L: Language> RegistryVisitor<L> for MetadataRegistry {
86    fn record_rule<R>(&mut self)
87    where
88        R: Rule + 'static,
89        R::Query: Queryable<Language = L>,
90        <R::Query as Queryable>::Output: Clone,
91    {
92        self.insert_rule(<R::Group as RuleGroup>::NAME, R::METADATA.name);
93    }
94}
95
96/// The rule registry holds type-erased instances of all active analysis rules
97/// for each phase.
98/// What defines a phase is the set of services that a phase offers. Currently
99/// we have:
100/// - Syntax Phase: No services are offered, thus its rules can be run immediately;
101/// - Semantic Phase: Offers the semantic model, thus these rules can only run
102/// after the "SemanticModel" is ready, which demands a whole transverse of the parsed tree.
103pub struct RuleRegistry<L: Language> {
104    /// Holds a collection of rules for each phase.
105    phase_rules: [PhaseRules<L>; 2],
106}
107
108impl<L: Language + Default> RuleRegistry<L> {
109    pub fn builder<'a>(
110        filter: &'a AnalysisFilter<'a>,
111        root: &'a L::Root,
112    ) -> RuleRegistryBuilder<'a, L> {
113        RuleRegistryBuilder {
114            filter,
115            root,
116            registry: RuleRegistry {
117                phase_rules: Default::default(),
118            },
119            visitors: BTreeMap::default(),
120            services: ServiceBag::default(),
121            diagnostics: Vec::new(),
122        }
123    }
124}
125
126/// Holds a collection of rules for each phase.
127#[derive(Default)]
128struct PhaseRules<L: Language> {
129    /// Maps the [TypeId] of known query matches types to the corresponding list of rules
130    type_rules: FxHashMap<TypeId, TypeRules<L>>,
131    /// Holds a list of states for all the rules in this phase
132    rule_states: Vec<RuleState<L>>,
133}
134
135enum TypeRules<L: Language> {
136    SyntaxRules { rules: Vec<SyntaxKindRules<L>> },
137    TypeRules { rules: Vec<RegistryRule<L>> },
138}
139
140pub struct RuleRegistryBuilder<'a, L: Language> {
141    filter: &'a AnalysisFilter<'a>,
142    root: &'a L::Root,
143    // Rule Registry
144    registry: RuleRegistry<L>,
145    // Analyzer Visitors
146    visitors: BTreeMap<(Phases, TypeId), Box<dyn Visitor<Language = L>>>,
147    // Service Bag
148    services: ServiceBag,
149    diagnostics: Vec<Error>,
150}
151
152impl<L: Language + Default + 'static> RegistryVisitor<L> for RuleRegistryBuilder<'_, L> {
153    fn record_category<C: GroupCategory<Language = L>>(&mut self) {
154        if self.filter.match_category::<C>() {
155            C::record_groups(self);
156        }
157    }
158
159    fn record_group<G: RuleGroup<Language = L>>(&mut self) {
160        if self.filter.match_group::<G>() {
161            G::record_rules(self);
162        }
163    }
164
165    /// Add the rule `R` to the list of rules stores in this registry instance
166    fn record_rule<R>(&mut self)
167    where
168        R: Rule + 'static,
169        <R as Rule>::Options: Default,
170        R::Query: Queryable<Language = L>,
171        <R::Query as Queryable>::Output: Clone,
172    {
173        if !self.filter.match_rule::<R>() {
174            return;
175        }
176
177        let phase = R::phase() as usize;
178        let phase = &mut self.registry.phase_rules[phase];
179
180        let rule = RegistryRule::new::<R>(phase.rule_states.len());
181
182        match <R::Query as Queryable>::key() {
183            QueryKey::Syntax(key) => {
184                let TypeRules::SyntaxRules { rules } = phase
185                    .type_rules
186                    .entry(TypeId::of::<SyntaxNode<L>>())
187                    .or_insert_with(|| TypeRules::SyntaxRules { rules: Vec::new() })
188                else {
189                    unreachable!("the SyntaxNode type has already been registered as a TypeRules instead of a SyntaxRules, this is generally caused by an implementation of `Queryable::key` returning a `QueryKey::TypeId` with the type ID of `SyntaxNode`")
190                };
191
192                // Iterate on all the SyntaxKind variants this node can match
193                for kind in key.iter() {
194                    // Convert the numerical value of `kind` to an index in the
195                    // `nodes` vector
196                    let RawSyntaxKind(index) = kind.to_raw();
197                    let index = usize::from(index);
198
199                    // Ensure the vector has enough capacity by inserting empty
200                    // `SyntaxKindRules` as required
201                    if rules.len() <= index {
202                        rules.resize_with(index + 1, SyntaxKindRules::new);
203                    }
204
205                    // Insert a handle to the rule `R` into the `SyntaxKindRules` entry
206                    // corresponding to the SyntaxKind index
207                    let node = &mut rules[index];
208                    node.rules.push(rule);
209                }
210            }
211            QueryKey::TypeId(key) => {
212                let TypeRules::TypeRules { rules } = phase
213                    .type_rules
214                    .entry(key)
215                    .or_insert_with(|| TypeRules::TypeRules { rules: Vec::new() })
216                else {
217                    unreachable!("the query type has already been registered as a SyntaxRules instead of a TypeRules, this is generally ca used by an implementation of `Queryable::key` returning a `QueryKey::TypeId` with the type ID of `SyntaxNode`")
218                };
219
220                rules.push(rule);
221            }
222        }
223
224        phase.rule_states.push(RuleState::default());
225
226        <R::Query as Queryable>::build_visitor(&mut self.visitors, self.root);
227    }
228}
229
230impl<L: Language> AddVisitor<L> for BTreeMap<(Phases, TypeId), Box<dyn Visitor<Language = L>>> {
231    fn add_visitor<F, V>(&mut self, phase: Phases, visitor: F)
232    where
233        F: FnOnce() -> V,
234        V: Visitor<Language = L> + 'static,
235    {
236        self.entry((phase, TypeId::of::<V>()))
237            .or_insert_with(move || Box::new((visitor)()));
238    }
239}
240
241type BuilderResult<L> = (
242    RuleRegistry<L>,
243    ServiceBag,
244    Vec<Error>,
245    BTreeMap<(Phases, TypeId), Box<dyn Visitor<Language = L>>>,
246);
247
248impl<L: Language> RuleRegistryBuilder<'_, L> {
249    pub fn build(self) -> BuilderResult<L> {
250        (
251            self.registry,
252            self.services,
253            self.diagnostics,
254            self.visitors,
255        )
256    }
257}
258
259impl<L: Language + 'static> QueryMatcher<L> for RuleRegistry<L> {
260    fn match_query(&mut self, mut params: MatchQueryParams<L>) {
261        let phase = &mut self.phase_rules[params.phase as usize];
262
263        let query_type = params.query.type_id();
264        let Some(rules) = phase.type_rules.get(&query_type) else {
265            return;
266        };
267
268        let rules = match rules {
269            TypeRules::SyntaxRules { rules } => {
270                let node = params.query.downcast_ref::<SyntaxNode<L>>().unwrap();
271
272                // Convert the numerical value of the SyntaxKind to an index in the
273                // `syntax` vector
274                let RawSyntaxKind(kind) = node.kind().to_raw();
275                let kind = usize::from(kind);
276
277                // Lookup the syntax entry corresponding to the SyntaxKind index
278                match rules.get(kind) {
279                    Some(entry) => &entry.rules,
280                    None => return,
281                }
282            }
283            TypeRules::TypeRules { rules } => rules,
284        };
285
286        // Run all the rules registered to this QueryMatch
287        for rule in rules {
288            let state = &mut phase.rule_states[rule.state_index];
289            // TODO: #3394 track error in the signal queue
290            let _ = (rule.run)(&mut params, state);
291        }
292    }
293}
294
295/// [SyntaxKindRules] holds a collection of [Rule]s that match a specific [SyntaxKind] value
296struct SyntaxKindRules<L: Language> {
297    rules: Vec<RegistryRule<L>>,
298}
299
300impl<L: Language> SyntaxKindRules<L> {
301    fn new() -> Self {
302        Self { rules: Vec::new() }
303    }
304}
305
306pub(crate) type RuleLanguage<R> = QueryLanguage<<R as Rule>::Query>;
307pub(crate) type QueryLanguage<N> = <N as Queryable>::Language;
308pub(crate) type NodeLanguage<N> = <N as AstNode>::Language;
309
310pub(crate) type RuleRoot<R> = LanguageRoot<RuleLanguage<R>>;
311pub type LanguageRoot<L> = <L as Language>::Root;
312
313/// Key struct for a rule in the metadata map, sorted alphabetically
314#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
315pub struct MetadataKey {
316    inner: (&'static str, &'static str),
317}
318
319impl MetadataKey {
320    fn into_group_key(self) -> GroupKey {
321        let (group, _) = self.inner;
322        GroupKey::new(group)
323    }
324
325    fn into_rule_key(self) -> RuleKey {
326        let (group, rule) = self.inner;
327        RuleKey::new(group, rule)
328    }
329}
330
331impl<'a> borrow::Borrow<(&'a str, &'a str)> for MetadataKey {
332    fn borrow(&self) -> &(&'a str, &'a str) {
333        &self.inner
334    }
335}
336
337impl borrow::Borrow<str> for MetadataKey {
338    fn borrow(&self) -> &str {
339        self.inner.0
340    }
341}
342
343/// Metadata entry for a rule and its group in the registry
344pub struct RegistryRuleMetadata {
345    pub group: &'static str,
346    pub rule: RuleMetadata,
347}
348
349impl RegistryRuleMetadata {
350    pub fn to_rule_key(&self) -> RuleKey {
351        RuleKey::new(self.group, self.rule.name)
352    }
353}
354
355/// Internal representation of a single rule in the registry
356#[derive(Copy, Clone)]
357pub struct RegistryRule<L: Language> {
358    run: RuleExecutor<L>,
359    state_index: usize,
360}
361
362/// Internal state for a given rule
363#[derive(Default)]
364struct RuleState<L: Language> {
365    suppressions: RuleSuppressions<L>,
366}
367
368/// Set of nodes this rule has suppressed from matching its query
369#[derive(Default)]
370pub struct RuleSuppressions<L: Language> {
371    inner: FxHashSet<SyntaxNode<L>>,
372}
373
374impl<L: Language> RuleSuppressions<L> {
375    /// Suppress query matching for the given node
376    pub fn suppress_node(&mut self, node: SyntaxNode<L>) {
377        self.inner.insert(node);
378    }
379}
380
381/// Executor for rule as a generic function pointer
382type RuleExecutor<L> = fn(&mut MatchQueryParams<L>, &mut RuleState<L>) -> Result<(), Error>;
383
384impl<L: Language + Default> RegistryRule<L> {
385    fn new<R>(state_index: usize) -> Self
386    where
387        R: Rule + 'static,
388        <R as Rule>::Options: Default,
389        R::Query: Queryable<Language = L> + 'static,
390        <R::Query as Queryable>::Output: Clone,
391    {
392        /// Generic implementation of RuleExecutor for any rule type R
393        fn run<R>(
394            params: &mut MatchQueryParams<RuleLanguage<R>>,
395            state: &mut RuleState<RuleLanguage<R>>,
396        ) -> Result<(), Error>
397        where
398            R: Rule + 'static,
399            R::Query: 'static,
400            <R::Query as Queryable>::Output: Clone,
401            <R as Rule>::Options: Default,
402        {
403            if let Some(node) = params.query.downcast_ref::<SyntaxNode<RuleLanguage<R>>>() {
404                if state.suppressions.inner.contains(node) {
405                    return Ok(());
406                }
407            }
408
409            // SAFETY: The rule should never get executed in the first place
410            // if the query doesn't match
411            let query_result = params.query.downcast_ref().unwrap();
412            let query_result = <R::Query as Queryable>::unwrap_match(params.services, query_result);
413            let globals = params.options.globals();
414            let preferred_quote = params.options.preferred_quote();
415            let options = params.options.rule_options::<R>().unwrap_or_default();
416            let ctx = match RuleContext::new(
417                &query_result,
418                params.root,
419                params.services,
420                &globals,
421                &params.options.file_path,
422                &options,
423                preferred_quote,
424            ) {
425                Ok(ctx) => ctx,
426                Err(error) => return Err(error),
427            };
428
429            for result in R::run(&ctx) {
430                let text_range =
431                    R::text_range(&ctx, &result).unwrap_or_else(|| params.query.text_range());
432
433                R::suppressed_nodes(&ctx, &result, &mut state.suppressions);
434
435                let signal = Box::new(RuleSignal::<R>::new(
436                    params.root,
437                    query_result.clone(),
438                    result,
439                    params.services,
440                    params.apply_suppression_comment,
441                    params.options,
442                ));
443
444                params.signal_queue.push(SignalEntry {
445                    signal,
446                    rule: RuleKey::rule::<R>(),
447                    text_range,
448                });
449            }
450
451            Ok(())
452        }
453
454        Self {
455            run: run::<R>,
456            state_index,
457        }
458    }
459}