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

Commit

Permalink
allow visitors to be tested individually
Browse files Browse the repository at this point in the history
  • Loading branch information
leops committed Jun 28, 2022
1 parent 5969118 commit 5bb829a
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 71 deletions.
69 changes: 3 additions & 66 deletions crates/rome_analyze/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ 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};
pub use crate::syntax::SyntaxVisitor;
pub use crate::visitor::{NodeVisitor, Visitor, VisitorContext};
use rome_rowan::{AstNode, Language, SyntaxNode, TextRange, WalkEvent};
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
Expand Down Expand Up @@ -100,68 +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 [SyntaxVisitor] is the simplest form of visitor implemented for the
/// analyzer, it simply broadcast each [WalkEvent::Enter] as a query match
/// event for the [SyntaxNode] being entered
pub struct SyntaxVisitor<L: Language, F> {
has_suppressions: F,
skip_subtree: Option<SyntaxNode<L>>,
}

impl<L: Language, F> SyntaxVisitor<L, F>
where
F: Fn(&SyntaxNode<L>) -> bool,
{
pub fn new(has_suppressions: F) -> Self {
Self {
has_suppressions,
skip_subtree: None,
}
}
}

impl<L: Language, F, B> Visitor<B> for SyntaxVisitor<L, F>
where
F: Fn(&SyntaxNode<L>) -> bool,
{
type Language = L;

fn visit(
&mut self,
event: &WalkEvent<SyntaxNode<Self::Language>>,
ctx: &mut VisitorContext<L, B>,
) -> ControlFlow<B> {
let node = match event {
WalkEvent::Enter(node) => node,
WalkEvent::Leave(node) => {
if let Some(skip_subtree) = &self.skip_subtree {
if skip_subtree == node {
self.skip_subtree.take();
}
}

return ControlFlow::Continue(());
}
};

if self.skip_subtree.is_some() {
return ControlFlow::Continue(());
}

if let Some(range) = ctx.range {
if node.text_range().ordering(range).is_ne() {
self.skip_subtree = Some(node.clone());
return ControlFlow::Continue(());
}
}

if (self.has_suppressions)(node) {
self.skip_subtree = Some(node.clone());
return ControlFlow::Continue(());
}

let query = QueryMatch::Syntax(node.clone());
ctx.match_query(&query)
}
}
133 changes: 133 additions & 0 deletions crates/rome_analyze/src/syntax.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use rome_rowan::{Language, SyntaxNode, WalkEvent};

use crate::{ControlFlow, QueryMatch, Visitor, VisitorContext};

/// The [SyntaxVisitor] is the simplest form of visitor implemented for the
/// analyzer, it simply broadcast each [WalkEvent::Enter] as a query match
/// event for the [SyntaxNode] being entered
pub struct SyntaxVisitor<L: Language, F> {
has_suppressions: F,
skip_subtree: Option<SyntaxNode<L>>,
}

impl<L: Language, F> SyntaxVisitor<L, F>
where
F: Fn(&SyntaxNode<L>) -> bool,
{
pub fn new(has_suppressions: F) -> Self {
Self {
has_suppressions,
skip_subtree: None,
}
}
}

impl<L: Language, F, B> Visitor<B> for SyntaxVisitor<L, F>
where
F: Fn(&SyntaxNode<L>) -> bool,
{
type Language = L;

fn visit(
&mut self,
event: &WalkEvent<SyntaxNode<Self::Language>>,
ctx: &mut VisitorContext<L, B>,
) -> ControlFlow<B> {
let node = match event {
WalkEvent::Enter(node) => node,
WalkEvent::Leave(node) => {
if let Some(skip_subtree) = &self.skip_subtree {
if skip_subtree == node {
self.skip_subtree.take();
}
}

return ControlFlow::Continue(());
}
};

if self.skip_subtree.is_some() {
return ControlFlow::Continue(());
}

if let Some(range) = ctx.range {
if node.text_range().ordering(range).is_ne() {
self.skip_subtree = Some(node.clone());
return ControlFlow::Continue(());
}
}

if (self.has_suppressions)(node) {
self.skip_subtree = Some(node.clone());
return ControlFlow::Continue(());
}

let query = QueryMatch::Syntax(node.clone());
ctx.match_query(&query)
}
}

#[cfg(test)]
mod tests {
use rome_rowan::{
raw_language::{RawLanguage, RawLanguageKind, RawLanguageRoot, RawSyntaxTreeBuilder},
AstNode,
};

use crate::{Analyzer, ControlFlow, Never, QueryMatch, SyntaxVisitor, VisitorContext};

/// Checks the syntax visitor emits a [QueryMatch] for each node in the syntax tree
#[test]
fn syntax_visitor() {
let root = {
let mut builder = RawSyntaxTreeBuilder::new();

builder.start_node(RawLanguageKind::ROOT);
builder.start_node(RawLanguageKind::EXPRESSION_LIST);

builder.start_node(RawLanguageKind::LITERAL_EXPRESSION);
builder.token(RawLanguageKind::NUMBER_TOKEN, "1");
builder.finish_node();

builder.start_node(RawLanguageKind::LITERAL_EXPRESSION);
builder.token(RawLanguageKind::NUMBER_TOKEN, "2");
builder.finish_node();

builder.finish_node();
builder.finish_node();

RawLanguageRoot::unwrap_cast(builder.finish())
};

let mut analyzer = Analyzer::empty();
analyzer.add_visitor(SyntaxVisitor::new(|_| false));

let mut nodes = Vec::new();
let mut ctx: VisitorContext<RawLanguage, Never> = VisitorContext {
file_id: 0,
root,
range: None,
match_query: Box::new(|_, _, query_match| match query_match {
QueryMatch::Syntax(node) => {
nodes.push(node.kind());
ControlFlow::Continue(())
}
}),
};

let result = analyzer.run(&mut ctx);
assert!(result.is_none());

drop(ctx);

assert_eq!(
nodes.as_slice(),
&[
RawLanguageKind::ROOT,
RawLanguageKind::EXPRESSION_LIST,
RawLanguageKind::LITERAL_EXPRESSION,
RawLanguageKind::LITERAL_EXPRESSION
]
);
}
}
10 changes: 6 additions & 4 deletions crates/rome_analyze/src/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@ use std::ops::ControlFlow;
use rome_diagnostics::file::FileId;
use rome_rowan::{AstNode, Language, SyntaxNode, TextRange, WalkEvent};

use crate::{registry::NodeLanguage, LanguageRoot, QueryMatch, RuleRegistry};
use crate::{registry::NodeLanguage, LanguageRoot, QueryMatch};

/// Mutable context objects shared by all visitors
pub struct VisitorContext<'a, L: Language, B> {
pub file_id: FileId,
pub root: LanguageRoot<L>,
pub range: Option<TextRange>,
pub registry: RuleRegistry<'a, L, B>,
pub match_query: MatchQuery<'a, L, B>,
}

type MatchQuery<'a, L, B> =
Box<dyn FnMut(FileId, &LanguageRoot<L>, &QueryMatch<L>) -> ControlFlow<B> + 'a>;

impl<'a, L: Language, B> VisitorContext<'a, L, B> {
pub fn match_query(&mut self, query_match: &QueryMatch<L>) -> ControlFlow<B> {
self.registry
.match_query(self.file_id, &self.root, query_match)
(self.match_query)(self.file_id, &self.root, query_match)
}
}

Expand Down
5 changes: 4 additions & 1 deletion crates/rome_js_analyze/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,14 @@ where
has_suppressions_category(SuppressionCategory::Lint, node)
}));

let mut registry = build_registry(&filter, callback);
let mut ctx = VisitorContext {
file_id,
root: root.clone(),
range: filter.range,
registry: build_registry(&filter, callback),
match_query: Box::new(move |file_id, root, query_match| {
registry.match_query(file_id, root, query_match)
}),
};

analyzer.run(&mut ctx)
Expand Down

0 comments on commit 5bb829a

Please sign in to comment.