use crate::{
context::RuleContext,
matcher::{GroupKey, MatchQueryParams},
query::{QueryKey, Queryable},
signals::RuleSignal,
AddVisitor, AnalysisFilter, GroupCategory, QueryMatcher, Rule, RuleGroup, RuleKey,
RuleMetadata, ServiceBag, SignalEntry, Visitor,
};
use biome_diagnostics::Error;
use biome_rowan::{AstNode, Language, RawSyntaxKind, SyntaxKind, SyntaxNode};
use rustc_hash::{FxHashMap, FxHashSet};
use std::{
any::TypeId,
borrow,
collections::{BTreeMap, BTreeSet},
};
#[repr(usize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Phases {
Syntax = 0,
Semantic = 1,
}
pub trait Phase {
fn phase() -> Phases;
}
impl Phase for () {
fn phase() -> Phases {
Phases::Syntax
}
}
pub trait RegistryVisitor<L: Language> {
fn record_category<C: GroupCategory<Language = L>>(&mut self) {
C::record_groups(self);
}
fn record_group<G: RuleGroup<Language = L>>(&mut self) {
G::record_rules(self);
}
fn record_rule<R>(&mut self)
where
R: Rule + 'static,
R::Query: Queryable<Language = L>,
<R::Query as Queryable>::Output: Clone;
}
#[derive(Debug, Default)]
pub struct MetadataRegistry {
inner: BTreeSet<MetadataKey>,
}
impl MetadataRegistry {
pub fn find_group(&self, group: &str) -> Option<GroupKey> {
let key = self.inner.get(group)?;
Some(key.into_group_key())
}
pub fn find_rule(&self, group: &str, rule: &str) -> Option<RuleKey> {
let key = self.inner.get(&(group, rule))?;
Some(key.into_rule_key())
}
pub(crate) fn insert_rule(&mut self, group: &'static str, rule: &'static str) {
self.inner.insert(MetadataKey {
inner: (group, rule),
});
}
}
impl<L: Language> RegistryVisitor<L> for MetadataRegistry {
fn record_rule<R>(&mut self)
where
R: Rule + 'static,
R::Query: Queryable<Language = L>,
<R::Query as Queryable>::Output: Clone,
{
self.insert_rule(<R::Group as RuleGroup>::NAME, R::METADATA.name);
}
}
pub struct RuleRegistry<L: Language> {
phase_rules: [PhaseRules<L>; 2],
}
impl<L: Language + Default> RuleRegistry<L> {
pub fn builder<'a>(
filter: &'a AnalysisFilter<'a>,
root: &'a L::Root,
) -> RuleRegistryBuilder<'a, L> {
RuleRegistryBuilder {
filter,
root,
registry: RuleRegistry {
phase_rules: Default::default(),
},
visitors: BTreeMap::default(),
services: ServiceBag::default(),
diagnostics: Vec::new(),
}
}
}
#[derive(Default)]
struct PhaseRules<L: Language> {
type_rules: FxHashMap<TypeId, TypeRules<L>>,
rule_states: Vec<RuleState<L>>,
}
enum TypeRules<L: Language> {
SyntaxRules { rules: Vec<SyntaxKindRules<L>> },
TypeRules { rules: Vec<RegistryRule<L>> },
}
pub struct RuleRegistryBuilder<'a, L: Language> {
filter: &'a AnalysisFilter<'a>,
root: &'a L::Root,
registry: RuleRegistry<L>,
visitors: BTreeMap<(Phases, TypeId), Box<dyn Visitor<Language = L>>>,
services: ServiceBag,
diagnostics: Vec<Error>,
}
impl<L: Language + Default + 'static> RegistryVisitor<L> for RuleRegistryBuilder<'_, L> {
fn record_category<C: GroupCategory<Language = L>>(&mut self) {
if self.filter.match_category::<C>() {
C::record_groups(self);
}
}
fn record_group<G: RuleGroup<Language = L>>(&mut self) {
if self.filter.match_group::<G>() {
G::record_rules(self);
}
}
fn record_rule<R>(&mut self)
where
R: Rule + 'static,
<R as Rule>::Options: Default,
R::Query: Queryable<Language = L>,
<R::Query as Queryable>::Output: Clone,
{
if !self.filter.match_rule::<R>() {
return;
}
let phase = R::phase() as usize;
let phase = &mut self.registry.phase_rules[phase];
let rule = RegistryRule::new::<R>(phase.rule_states.len());
match <R::Query as Queryable>::key() {
QueryKey::Syntax(key) => {
let TypeRules::SyntaxRules { rules } = phase
.type_rules
.entry(TypeId::of::<SyntaxNode<L>>())
.or_insert_with(|| TypeRules::SyntaxRules { rules: Vec::new() })
else {
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`")
};
for kind in key.iter() {
let RawSyntaxKind(index) = kind.to_raw();
let index = usize::from(index);
if rules.len() <= index {
rules.resize_with(index + 1, SyntaxKindRules::new);
}
let node = &mut rules[index];
node.rules.push(rule);
}
}
QueryKey::TypeId(key) => {
let TypeRules::TypeRules { rules } = phase
.type_rules
.entry(key)
.or_insert_with(|| TypeRules::TypeRules { rules: Vec::new() })
else {
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`")
};
rules.push(rule);
}
}
phase.rule_states.push(RuleState::default());
<R::Query as Queryable>::build_visitor(&mut self.visitors, self.root);
}
}
impl<L: Language> AddVisitor<L> for BTreeMap<(Phases, TypeId), Box<dyn Visitor<Language = L>>> {
fn add_visitor<F, V>(&mut self, phase: Phases, visitor: F)
where
F: FnOnce() -> V,
V: Visitor<Language = L> + 'static,
{
self.entry((phase, TypeId::of::<V>()))
.or_insert_with(move || Box::new((visitor)()));
}
}
type BuilderResult<L> = (
RuleRegistry<L>,
ServiceBag,
Vec<Error>,
BTreeMap<(Phases, TypeId), Box<dyn Visitor<Language = L>>>,
);
impl<L: Language> RuleRegistryBuilder<'_, L> {
pub fn build(self) -> BuilderResult<L> {
(
self.registry,
self.services,
self.diagnostics,
self.visitors,
)
}
}
impl<L: Language + 'static> QueryMatcher<L> for RuleRegistry<L> {
fn match_query(&mut self, mut params: MatchQueryParams<L>) {
let phase = &mut self.phase_rules[params.phase as usize];
let query_type = params.query.type_id();
let Some(rules) = phase.type_rules.get(&query_type) else {
return;
};
let rules = match rules {
TypeRules::SyntaxRules { rules } => {
let node = params.query.downcast_ref::<SyntaxNode<L>>().unwrap();
let RawSyntaxKind(kind) = node.kind().to_raw();
let kind = usize::from(kind);
match rules.get(kind) {
Some(entry) => &entry.rules,
None => return,
}
}
TypeRules::TypeRules { rules } => rules,
};
for rule in rules {
let state = &mut phase.rule_states[rule.state_index];
let _ = (rule.run)(&mut params, state);
}
}
}
struct SyntaxKindRules<L: Language> {
rules: Vec<RegistryRule<L>>,
}
impl<L: Language> SyntaxKindRules<L> {
fn new() -> Self {
Self { rules: Vec::new() }
}
}
pub(crate) type RuleLanguage<R> = QueryLanguage<<R as Rule>::Query>;
pub(crate) type QueryLanguage<N> = <N as Queryable>::Language;
pub(crate) type NodeLanguage<N> = <N as AstNode>::Language;
pub(crate) type RuleRoot<R> = LanguageRoot<RuleLanguage<R>>;
pub type LanguageRoot<L> = <L as Language>::Root;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct MetadataKey {
inner: (&'static str, &'static str),
}
impl MetadataKey {
fn into_group_key(self) -> GroupKey {
let (group, _) = self.inner;
GroupKey::new(group)
}
fn into_rule_key(self) -> RuleKey {
let (group, rule) = self.inner;
RuleKey::new(group, rule)
}
}
impl<'a> borrow::Borrow<(&'a str, &'a str)> for MetadataKey {
fn borrow(&self) -> &(&'a str, &'a str) {
&self.inner
}
}
impl borrow::Borrow<str> for MetadataKey {
fn borrow(&self) -> &str {
self.inner.0
}
}
pub struct RegistryRuleMetadata {
pub group: &'static str,
pub rule: RuleMetadata,
}
impl RegistryRuleMetadata {
pub fn to_rule_key(&self) -> RuleKey {
RuleKey::new(self.group, self.rule.name)
}
}
#[derive(Copy, Clone)]
pub struct RegistryRule<L: Language> {
run: RuleExecutor<L>,
state_index: usize,
}
#[derive(Default)]
struct RuleState<L: Language> {
suppressions: RuleSuppressions<L>,
}
#[derive(Default)]
pub struct RuleSuppressions<L: Language> {
inner: FxHashSet<SyntaxNode<L>>,
}
impl<L: Language> RuleSuppressions<L> {
pub fn suppress_node(&mut self, node: SyntaxNode<L>) {
self.inner.insert(node);
}
}
type RuleExecutor<L> = fn(&mut MatchQueryParams<L>, &mut RuleState<L>) -> Result<(), Error>;
impl<L: Language + Default> RegistryRule<L> {
fn new<R>(state_index: usize) -> Self
where
R: Rule + 'static,
<R as Rule>::Options: Default,
R::Query: Queryable<Language = L> + 'static,
<R::Query as Queryable>::Output: Clone,
{
fn run<R>(
params: &mut MatchQueryParams<RuleLanguage<R>>,
state: &mut RuleState<RuleLanguage<R>>,
) -> Result<(), Error>
where
R: Rule + 'static,
R::Query: 'static,
<R::Query as Queryable>::Output: Clone,
<R as Rule>::Options: Default,
{
if let Some(node) = params.query.downcast_ref::<SyntaxNode<RuleLanguage<R>>>() {
if state.suppressions.inner.contains(node) {
return Ok(());
}
}
let query_result = params.query.downcast_ref().unwrap();
let query_result = <R::Query as Queryable>::unwrap_match(params.services, query_result);
let globals = params.options.globals();
let options = params.options.rule_options::<R>().unwrap_or_default();
let ctx = match RuleContext::new(
&query_result,
params.root,
params.services,
&globals,
¶ms.options.file_path,
&options,
) {
Ok(ctx) => ctx,
Err(error) => return Err(error),
};
for result in R::run(&ctx) {
let text_range =
R::text_range(&ctx, &result).unwrap_or_else(|| params.query.text_range());
R::suppressed_nodes(&ctx, &result, &mut state.suppressions);
let signal = Box::new(RuleSignal::<R>::new(
params.root,
query_result.clone(),
result,
params.services,
params.apply_suppression_comment,
params.options,
));
params.signal_queue.push(SignalEntry {
signal,
rule: RuleKey::rule::<R>(),
text_range,
});
}
Ok(())
}
Self {
run: run::<R>,
state_index,
}
}
}