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

refactor(rome_analyze): add the visitor and queryable trait #2778

Merged
merged 5 commits into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions crates/rome_analyze/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
use crate::{registry::RuleRoot, Rule};

pub struct RuleContext<R>
pub struct RuleContext<'a, R>
where
R: ?Sized + Rule,
{
query_result: <R as Rule>::Query,
root: RuleRoot<R>,
query_result: &'a <R as Rule>::Query,
root: &'a RuleRoot<R>,
}

impl<R> RuleContext<R>
impl<'a, R> RuleContext<'a, R>
where
R: Rule,
{
pub fn new(query_result: <R as Rule>::Query, root: RuleRoot<R>) -> Self {
pub fn new(query_result: &'a <R as Rule>::Query, root: &'a RuleRoot<R>) -> Self {
Self { query_result, root }
}

pub fn query(&self) -> &<R as Rule>::Query {
&self.query_result
self.query_result
}

pub fn root(&self) -> RuleRoot<R> {
Expand Down
107 changes: 46 additions & 61 deletions crates/rome_analyze/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,59 @@
use std::ops;

mod categories;
pub mod context;
mod query;
mod registry;
mod rule;
mod signals;
mod syntax;
mod visitor;

pub use crate::categories::{ActionCategory, RuleCategories, RuleCategory};
pub use crate::query::{Ast, QueryKey, QueryMatch, Queryable};
pub use crate::registry::{LanguageRoot, RuleRegistry};
pub use crate::rule::{Rule, RuleAction, RuleDiagnostic, RuleMeta};
pub use crate::signals::{AnalyzerAction, AnalyzerSignal};
use rome_diagnostics::file::FileId;
use rome_rowan::{AstNode, Language, SyntaxNode, TextRange, WalkEvent};
use std::ops;
pub use crate::syntax::SyntaxVisitor;
pub use crate::visitor::{NodeVisitor, Visitor, VisitorContext};
use rome_rowan::{AstNode, Language, TextRange};

/// The analyzer is the main entry point into the `rome_analyze` infrastructure.
/// Its role is to run a collection of [Visitor]s over a syntax tree, with each
/// visitor implementing various analysis over this syntax tree to generate
/// auxiliary data structures as well as emit "query match" events to be
/// processed by lint rules and in turn emit "analyzer signals" in the form of
/// diagnostics, code actions or both
pub struct Analyzer<L, B> {
visitors: Vec<Box<dyn Visitor<B, Language = L>>>,
}

impl<L: Language, B> Analyzer<L, B> {
pub fn empty() -> Self {
Self {
visitors: Vec::new(),
}
}

pub fn add_visitor<V>(&mut self, visitor: V)
where
V: Visitor<B, Language = L> + 'static,
{
self.visitors.push(Box::new(visitor));
}

pub fn run(mut self, ctx: &mut VisitorContext<L, B>) -> Option<B> {
for event in ctx.root.syntax().preorder() {
for visitor in &mut self.visitors {
if let ControlFlow::Break(br) = visitor.visit(&event, ctx) {
return Some(br);
}
}
}

None
}
}

/// Allows filtering the list of rules that will be executed in a run of the analyzer,
/// and at what source code range signals (diagnostics or actions) may be raised
Expand Down Expand Up @@ -59,61 +102,3 @@ pub enum Never {}
/// (`Option<Never>` has a size of 0 and can be elided, while `Option<()>` has
/// a size of 1 as it still need to store a discriminant)
pub type ControlFlow<B = Never> = ops::ControlFlow<B>;

/// The [Analyzer] is intended is an executor for a set of rules contained in a
/// [RuleRegistry]: these rules can query the syntax tree and emit [AnalyzerSignal]
/// objects in response to a query match, with a signal being a generic object
/// containing a diagnostic, a code action or both.
pub struct Analyzer<L: Language> {
registry: RuleRegistry<L>,
has_suppressions: fn(&SyntaxNode<L>) -> bool,
}

impl<L: Language> Analyzer<L> {
/// Create a new instance of the analyzer from a registry instance, and a
/// language-specific suppression parser
pub fn new(registry: RuleRegistry<L>, has_suppressions: fn(&SyntaxNode<L>) -> bool) -> Self {
Self {
registry,
has_suppressions,
}
}

/// Run the analyzer on the provided `root`: this process will use the given `filter`
/// to selectively restrict analysis to specific rules / a specific source range,
/// then call the `callback` when an analysis rule emits a diagnostic or action
pub fn analyze<B>(
self,
file_id: FileId,
root: &LanguageRoot<L>,
range: Option<TextRange>,
mut callback: impl FnMut(&dyn AnalyzerSignal<L>) -> ControlFlow<B>,
) -> Option<B> {
let mut iter = root.syntax().preorder();
while let Some(event) = iter.next() {
let node = match event {
WalkEvent::Enter(node) => node,
WalkEvent::Leave(_) => continue,
};

if let Some(range) = range {
if node.text_range().ordering(range).is_ne() {
iter.skip_subtree();
continue;
}
}

if (self.has_suppressions)(&node) {
iter.skip_subtree();
continue;
}

if let ControlFlow::Break(b) = self.registry.analyze(file_id, root, node, &mut callback)
{
return Some(b);
}
}

None
}
}
52 changes: 52 additions & 0 deletions crates/rome_analyze/src/query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use rome_rowan::{AstNode, Language, SyntaxKindSet, SyntaxNode};

use crate::registry::NodeLanguage;

/// Trait implemented for all types, for example lint rules can query them to emit diagnostics or code actions.
pub trait Queryable: Sized {
type Language: Language;

/// Statically declares which [QueryMatch] variant is matched by this
/// [Queryable] type. For instance the [Ast] queryable matches on
/// [QueryMatch::Syntax], so its key is defined as [QueryKey::Syntax]
const KEY: QueryKey<Self::Language>;
leops marked this conversation as resolved.
Show resolved Hide resolved

/// Unwrap an instance of `Self` from a [QueryMatch].
///
/// ## Panics
///
/// If the [QueryMatch] variant of `query` doesn't match `Self::KEY`
fn unwrap_match(query: &QueryMatch<Self::Language>) -> Self;
}

/// Enumerate all the types of [Queryable] analyzer visitors may emit
pub enum QueryMatch<L: Language> {
Syntax(SyntaxNode<L>),
}

/// Mirrors the variants of [QueryMatch] to statically compute which queries a
/// given [Queryable] type can match
pub enum QueryKey<L: Language> {
Syntax(SyntaxKindSet<L>),
}

/// Query type usable by lint rules to match on specific [AstNode] types
#[derive(Clone)]
pub struct Ast<N>(pub N);
Copy link
Contributor

Choose a reason for hiding this comment

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

doc?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is basically just a "newtype" struct that wraps an AstNode type N to implement Queryable on it, this is a common pattern in Rust to get around the orphan rule


impl<N> Queryable for Ast<N>
where
N: AstNode + 'static,
{
type Language = NodeLanguage<N>;

/// Match on [QueryMatch::Syntax] if the kind of the syntax node matches
/// the kind set of `N`
const KEY: QueryKey<Self::Language> = QueryKey::Syntax(N::KIND_SET);
leops marked this conversation as resolved.
Show resolved Hide resolved

fn unwrap_match(query: &QueryMatch<Self::Language>) -> Self {
match query {
QueryMatch::Syntax(node) => Self(N::unwrap_cast(node.clone())),
}
}
}
Loading