From 6e5cf86112080c36f3a5fd2bd2fb75a437869fb1 Mon Sep 17 00:00:00 2001 From: "Arend van Beelen jr." Date: Fri, 6 Sep 2024 21:42:28 +0200 Subject: [PATCH] feat(grit): add regex support in Grit queries (#3814) --- crates/biome_grit_patterns/src/errors.rs | 6 ++ .../src/pattern_compiler.rs | 6 +- .../src/pattern_compiler/regex_compiler.rs | 69 +++++++++++++++++++ .../tests/specs/ts/regex.grit | 4 ++ .../tests/specs/ts/regex.snap | 12 ++++ .../tests/specs/ts/regex.ts | 2 + 6 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/regex_compiler.rs create mode 100644 crates/biome_grit_patterns/tests/specs/ts/regex.grit create mode 100644 crates/biome_grit_patterns/tests/specs/ts/regex.snap create mode 100644 crates/biome_grit_patterns/tests/specs/ts/regex.ts diff --git a/crates/biome_grit_patterns/src/errors.rs b/crates/biome_grit_patterns/src/errors.rs index 4f223444014a..5bfac8723d30 100644 --- a/crates/biome_grit_patterns/src/errors.rs +++ b/crates/biome_grit_patterns/src/errors.rs @@ -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), @@ -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}} }) } diff --git a/crates/biome_grit_patterns/src/pattern_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler.rs index bb36dcf367c1..a3453bce61d6 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler.rs @@ -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; @@ -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; @@ -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)?, ))), diff --git a/crates/biome_grit_patterns/src/pattern_compiler/regex_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/regex_compiler.rs new file mode 100644 index 000000000000..02fde0525cc6 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/regex_compiler.rs @@ -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, 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\"{}\"", ®ex[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(®ex[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)) + } +} diff --git a/crates/biome_grit_patterns/tests/specs/ts/regex.grit b/crates/biome_grit_patterns/tests/specs/ts/regex.grit new file mode 100644 index 000000000000..7a774cacf3df --- /dev/null +++ b/crates/biome_grit_patterns/tests/specs/ts/regex.grit @@ -0,0 +1,4 @@ +`console.log($message)` where { + $name = "Lucy", + $message <: r`"([a-zA-Z]*), Lucy"`($greeting) => `$name, $greeting` +} diff --git a/crates/biome_grit_patterns/tests/specs/ts/regex.snap b/crates/biome_grit_patterns/tests/specs/ts/regex.snap new file mode 100644 index 000000000000..ca2afac0acce --- /dev/null +++ b/crates/biome_grit_patterns/tests/specs/ts/regex.snap @@ -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: [], +} diff --git a/crates/biome_grit_patterns/tests/specs/ts/regex.ts b/crates/biome_grit_patterns/tests/specs/ts/regex.ts new file mode 100644 index 000000000000..5a6a46f25bd9 --- /dev/null +++ b/crates/biome_grit_patterns/tests/specs/ts/regex.ts @@ -0,0 +1,2 @@ +console.log("Hello, Bert"); +console.log("Hello, Lucy");