Skip to content

Commit

Permalink
feat(grit): add regex support in Grit queries (#3814)
Browse files Browse the repository at this point in the history
  • Loading branch information
arendjr authored Sep 6, 2024
1 parent b9fb304 commit 6e5cf86
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 1 deletion.
6 changes: 6 additions & 0 deletions crates/biome_grit_patterns/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ pub enum CompileError {
/// A metavariable was expected at the given range.
InvalidMetavariableRange(ByteRange),

/// Regular expressions are not allowed on the right-hand side of a rule.
InvalidRegexPosition,

/// Incorrect reference to a metavariable.
MetavariableNotFound(String),

Expand Down Expand Up @@ -89,6 +92,9 @@ impl Diagnostic for CompileError {
CompileError::InvalidMetavariableRange(_) => {
fmt.write_markup(markup! { "Invalid range for metavariable" })
}
CompileError::InvalidRegexPosition => fmt.write_markup(
markup! { "Regular expressions are not allowed on the right-hand side of a rule" },
),
CompileError::MetavariableNotFound(var) => {
fmt.write_markup(markup! { "Metavariable not found: "{{var}} })
}
Expand Down
6 changes: 5 additions & 1 deletion crates/biome_grit_patterns/src/pattern_compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ mod or_compiler;
mod predicate_call_compiler;
mod predicate_compiler;
mod predicate_return_compiler;
mod regex_compiler;
mod rewrite_compiler;
mod sequential_compiler;
mod snippet_compiler;
Expand Down Expand Up @@ -88,6 +89,7 @@ use biome_grit_syntax::{AnyGritMaybeCurlyPattern, AnyGritPattern, GritSyntaxKind
use biome_rowan::AstNode as _;
use grit_pattern_matcher::pattern::{DynamicPattern, DynamicSnippet, DynamicSnippetPart, Pattern};
use node_like_compiler::NodeLikeCompiler;
use regex_compiler::RegexCompiler;

pub(crate) use self::auto_wrap::auto_wrap_pattern;

Expand Down Expand Up @@ -211,7 +213,9 @@ impl PatternCompiler {
AnyGritPattern::GritPatternWhere(node) => Ok(Pattern::Where(Box::new(
WhereCompiler::from_node(node, context)?,
))),
AnyGritPattern::GritRegexPattern(_) => todo!(),
AnyGritPattern::GritRegexPattern(node) => Ok(Pattern::Regex(Box::new(
RegexCompiler::from_node(node, context, is_rhs)?,
))),
AnyGritPattern::GritRewrite(node) => Ok(Pattern::Rewrite(Box::new(
RewriteCompiler::from_node(node, context)?,
))),
Expand Down
69 changes: 69 additions & 0 deletions crates/biome_grit_patterns/src/pattern_compiler/regex_compiler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use super::{compilation_context::NodeCompilationContext, variable_compiler::VariableCompiler};
use crate::{
diagnostics::CompilerDiagnostic, grit_context::GritQueryContext,
pattern_compiler::snippet_compiler::parse_snippet_content, util::TextRangeGritExt,
CompileError,
};
use biome_grit_syntax::{AnyGritRegex, GritRegexPattern};
use grit_pattern_matcher::pattern::{RegexLike, RegexPattern};
use grit_util::Language;

pub(crate) struct RegexCompiler;

impl RegexCompiler {
pub(crate) fn from_node(
node: &GritRegexPattern,
context: &mut NodeCompilationContext,
is_rhs: bool,
) -> Result<RegexPattern<GritQueryContext>, CompileError> {
if is_rhs {
return Err(CompileError::InvalidRegexPosition);
}

let regex = match node.regex()? {
AnyGritRegex::GritRegexLiteral(regex_node) => {
let token = regex_node.value_token()?;
let regex = token.text_trimmed();
debug_assert!(regex.starts_with("r\"") && regex.ends_with('"'));
RegexLike::Regex(regex[2..regex.len() - 1].to_string())
}
AnyGritRegex::GritSnippetRegexLiteral(regex_node) => {
let token = regex_node.value_token()?;
let regex = token.text_trimmed();
let range = token.text_trimmed_range().to_byte_range();
debug_assert!(regex.starts_with("r`") && regex.ends_with('`'));

if !context
.compilation
.lang
.metavariable_regex()
.is_match(regex)
{
let alternative = format!("r\"{}\"", &regex[2..regex.len() - 1]);
context.log(CompilerDiagnostic::new_warning(
format!("Unnecessary use of metavariable snippet syntax without metavariables. Replace {regex} with {alternative}"),
token.text_trimmed_range(),
));
}

let pattern =
parse_snippet_content(&regex[2..regex.len() - 1], range, context, is_rhs)?;
RegexLike::Pattern(Box::new(pattern))
}
};

let variables: Vec<_> = node
.variables()
.and_then(|variables| variables.args().ok())
.map(|args| {
args.grit_variable_list()
.into_iter()
.filter_map(Result::ok)
.map(|variable| VariableCompiler::from_node(&variable, context))
.collect()
})
.unwrap_or_default();

Ok(RegexPattern::new(regex, variables))
}
}
4 changes: 4 additions & 0 deletions crates/biome_grit_patterns/tests/specs/ts/regex.grit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
`console.log($message)` where {
$name = "Lucy",
$message <: r`"([a-zA-Z]*), Lucy"`($greeting) => `$name, $greeting`
}
12 changes: 12 additions & 0 deletions crates/biome_grit_patterns/tests/specs/ts/regex.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
source: crates/biome_grit_patterns/tests/spec_tests.rs
expression: regex
---
SnapshotResult {
messages: [],
matched_ranges: [
"2:1-2:27",
],
rewritten_files: [],
created_files: [],
}
2 changes: 2 additions & 0 deletions crates/biome_grit_patterns/tests/specs/ts/regex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
console.log("Hello, Bert");
console.log("Hello, Lucy");

0 comments on commit 6e5cf86

Please sign in to comment.