From 76a4f1a0ac4cfa63244d19c1f8c18a2defbc920b Mon Sep 17 00:00:00 2001 From: "Arend van Beelen jr." Date: Thu, 19 Sep 2024 22:37:24 +0200 Subject: [PATCH] feat(grit): implement GritQL log() function (#4003) --- .../biome_grit_patterns/src/grit_binding.rs | 11 ++++++- crates/biome_grit_patterns/src/grit_query.rs | 6 ++-- .../src/grit_target_node.rs | 12 +++++-- .../src/pattern_compiler.rs | 1 + .../src/pattern_compiler/call_compiler.rs | 9 ++++- .../src/pattern_compiler/log_compiler.rs | 33 +++++++++++++++++++ .../predicate_call_compiler.rs | 11 +++++-- .../pattern_compiler/predicate_compiler.rs | 4 +-- .../src/pattern_compiler/snippet_compiler.rs | 32 ------------------ .../biome_grit_patterns/tests/quick_test.rs | 25 ++++++++++---- .../biome_grit_patterns/tests/spec_tests.rs | 19 +++++++++-- .../tests/specs/ts/log.grit | 3 ++ .../tests/specs/ts/log.snap | 17 ++++++++++ .../biome_grit_patterns/tests/specs/ts/log.ts | 1 + crates/biome_service/src/file_handlers/mod.rs | 2 +- 15 files changed, 131 insertions(+), 55 deletions(-) create mode 100644 crates/biome_grit_patterns/src/pattern_compiler/log_compiler.rs create mode 100644 crates/biome_grit_patterns/tests/specs/ts/log.grit create mode 100644 crates/biome_grit_patterns/tests/specs/ts/log.snap create mode 100644 crates/biome_grit_patterns/tests/specs/ts/log.ts diff --git a/crates/biome_grit_patterns/src/grit_binding.rs b/crates/biome_grit_patterns/src/grit_binding.rs index cb2fec18385a..d45dd4a3e989 100644 --- a/crates/biome_grit_patterns/src/grit_binding.rs +++ b/crates/biome_grit_patterns/src/grit_binding.rs @@ -71,7 +71,16 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { } fn get_sexp(&self) -> Option { - None + Some(match self { + Self::File(path) => format!("({})", path.display()), + Self::Node(grit_target_node) => format!("({grit_target_node:?})"), + Self::Range(text_range, source) => format!( + "({})", + &source[text_range.start().into()..text_range.end().into()] + ), + Self::Empty(_, _) => "(empty)".to_owned(), + Self::Constant(constant) => format!("({constant})"), + }) } fn position(&self, _language: &GritTargetLanguage) -> Option { diff --git a/crates/biome_grit_patterns/src/grit_query.rs b/crates/biome_grit_patterns/src/grit_query.rs index 63e33cb33db3..6e5d068a6c94 100644 --- a/crates/biome_grit_patterns/src/grit_query.rs +++ b/crates/biome_grit_patterns/src/grit_query.rs @@ -23,7 +23,7 @@ use grit_pattern_matcher::file_owners::{FileOwner, FileOwners}; use grit_pattern_matcher::pattern::{ FilePtr, FileRegistry, Matcher, Pattern, ResolvedPattern, State, VariableSourceLocations, }; -use grit_util::{Ast, ByteRange, InputRanges, Range, VariableMatch}; +use grit_util::{AnalysisLogs, Ast, ByteRange, InputRanges, Range, VariableMatch}; use im::Vector; use std::collections::{BTreeMap, BTreeSet}; use std::ffi::OsStr; @@ -64,7 +64,7 @@ pub struct GritQuery { } impl GritQuery { - pub fn execute(&self, file: GritTargetFile) -> Result> { + pub fn execute(&self, file: GritTargetFile) -> Result<(Vec, AnalysisLogs)> { let file_owners = FileOwners::new(); let files = vec![file]; let file_ptr = FilePtr::new(0, 0); @@ -100,7 +100,7 @@ impl GritQuery { } } - Ok(results) + Ok((results, logs)) } pub fn from_node( diff --git a/crates/biome_grit_patterns/src/grit_target_node.rs b/crates/biome_grit_patterns/src/grit_target_node.rs index 93b8f247bde0..70c051860633 100644 --- a/crates/biome_grit_patterns/src/grit_target_node.rs +++ b/crates/biome_grit_patterns/src/grit_target_node.rs @@ -3,7 +3,7 @@ use crate::util::TextRangeGritExt; use biome_js_syntax::{JsSyntaxKind, JsSyntaxNode, JsSyntaxToken}; use biome_rowan::{NodeOrToken, SyntaxKind, SyntaxSlot, TextRange}; use grit_util::{AstCursor, AstNode as GritAstNode, ByteRange, CodeRange}; -use std::{borrow::Cow, ops::Deref, str::Utf8Error}; +use std::{borrow::Cow, fmt::Debug, ops::Deref, str::Utf8Error}; use NodeOrToken::*; @@ -187,7 +187,7 @@ generate_target_node! { [JsLanguage, JsSyntaxNode, JsSyntaxToken, JsSyntaxKind] } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, PartialEq)] pub struct GritTargetNode<'a> { node: GritTargetLanguageNode, tree: &'a GritTargetTree, @@ -263,6 +263,14 @@ impl<'a> GritTargetNode<'a> { } } +impl<'a> Debug for GritTargetNode<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GritTargetNode") + .field("node", &self.node) + .finish() + } +} + impl<'a> Deref for GritTargetNode<'a> { type Target = GritTargetLanguageNode; diff --git a/crates/biome_grit_patterns/src/pattern_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler.rs index 0c36aae3a2b0..9846e0b26c6c 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler.rs @@ -45,6 +45,7 @@ mod limit_compiler; mod list_compiler; mod list_index_compiler; mod literal_compiler; +mod log_compiler; mod map_accessor_compiler; mod map_compiler; mod match_compiler; diff --git a/crates/biome_grit_patterns/src/pattern_compiler/call_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/call_compiler.rs index 79a6b2d52fe2..4e44c08a87ed 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/call_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/call_compiler.rs @@ -1,4 +1,6 @@ -use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use super::{ + compilation_context::NodeCompilationContext, log_compiler::LogCompiler, PatternCompiler, +}; use crate::{grit_context::GritQueryContext, CompileError, NodeLikeArgumentError}; use biome_grit_syntax::{ AnyGritMaybeNamedArg, AnyGritPattern, GritNamedArgList, GritNodeLike, GritSyntaxKind, @@ -18,6 +20,11 @@ pub(super) fn call_pattern_from_node_with_name( context: &mut NodeCompilationContext, is_rhs: bool, ) -> Result, CompileError> { + if name == "log" { + return LogCompiler::from_named_args(node.named_args(), context) + .map(|log| Pattern::Log(Box::new(log))); + } + let named_args = named_args_from_node(node, &name, context)?; let mut args = named_args_to_map(named_args, context)?; let named_args_count = node.named_args().into_iter().count(); diff --git a/crates/biome_grit_patterns/src/pattern_compiler/log_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/log_compiler.rs new file mode 100644 index 000000000000..d17acb5e3d47 --- /dev/null +++ b/crates/biome_grit_patterns/src/pattern_compiler/log_compiler.rs @@ -0,0 +1,33 @@ +use super::call_compiler::*; +use super::compilation_context::NodeCompilationContext; +use crate::{grit_context::GritQueryContext, CompileError}; +use biome_grit_syntax::GritNamedArgList; +use grit_pattern_matcher::pattern::{Log, Pattern, VariableInfo}; + +pub(crate) struct LogCompiler; + +impl LogCompiler { + pub(crate) fn from_named_args( + named_args: GritNamedArgList, + context: &mut NodeCompilationContext, + ) -> Result, CompileError> { + let named_args = node_to_args_pairs( + "log", + named_args, + &context.compilation.lang, + &Some(vec!["message".to_owned(), "variable".to_owned()]), + )?; + let mut args = named_args_to_map(named_args, context)?; + let message = args.remove("$message"); + let variable = args.remove("$variable"); + let variable = variable.and_then(|pattern| match pattern { + Pattern::Variable(variable) => { + let source_location = &context.vars_array[variable.scope][variable.index]; + Some(VariableInfo::new(source_location.name.clone(), variable)) + } + _ => None, + }); + + Ok(Log::new(variable, message)) + } +} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/predicate_call_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/predicate_call_compiler.rs index 1bbb042be466..00504dae03b3 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/predicate_call_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/predicate_call_compiler.rs @@ -1,10 +1,11 @@ use super::call_compiler::*; use super::compilation_context::NodeCompilationContext; +use super::log_compiler::LogCompiler; use crate::NodeLikeArgumentError; use crate::{grit_context::GritQueryContext, CompileError}; use biome_grit_syntax::GritPredicateCall; use biome_rowan::AstNode; -use grit_pattern_matcher::pattern::PrCall; +use grit_pattern_matcher::pattern::{PrCall, Predicate}; pub(crate) struct PrCallCompiler; @@ -12,10 +13,14 @@ impl PrCallCompiler { pub(crate) fn from_node( node: &GritPredicateCall, context: &mut NodeCompilationContext, - ) -> Result, CompileError> { + ) -> Result, CompileError> { let name = node.name()?; let name = name.text(); + if name == "log" { + return LogCompiler::from_named_args(node.named_args(), context).map(Predicate::Log); + } + let info = if let Some(info) = context.compilation.predicate_definition_info.get(&name) { info } else if let Some(info) = context.compilation.function_definition_info.get(&name) { @@ -37,6 +42,6 @@ impl PrCallCompiler { } let args = match_args_to_params(&name, args, ¶ms, &context.compilation.lang)?; - Ok(PrCall::new(info.index, args)) + Ok(Predicate::Call(Box::new(PrCall::new(info.index, args)))) } } diff --git a/crates/biome_grit_patterns/src/pattern_compiler/predicate_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/predicate_compiler.rs index 51e2022c7a0f..eb55b5287b7f 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/predicate_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/predicate_compiler.rs @@ -39,9 +39,7 @@ impl PredicateCompiler { AnyGritPredicate::GritPredicateAssignment(node) => Ok(Predicate::Assignment(Box::new( PrAssignmentCompiler::from_node(node, context)?, ))), - AnyGritPredicate::GritPredicateCall(node) => Ok(Predicate::Call(Box::new( - PrCallCompiler::from_node(node, context)?, - ))), + AnyGritPredicate::GritPredicateCall(node) => PrCallCompiler::from_node(node, context), AnyGritPredicate::GritPredicateEqual(node) => Ok(Predicate::Equal(Box::new( PrEqualCompiler::from_node(node, context)?, ))), diff --git a/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs index 820c19fe56e8..6057ee8e755b 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs @@ -523,38 +523,6 @@ mod tests { , ), ), - tree: GritTargetTree { - root: JsLanguage( - Node( - 0: JS_MODULE@0..20 - 0: (empty) - 1: (empty) - 2: JS_DIRECTIVE_LIST@0..0 - 3: JS_MODULE_ITEM_LIST@0..20 - 0: JS_EXPRESSION_STATEMENT@0..20 - 0: JS_CALL_EXPRESSION@0..20 - 0: JS_STATIC_MEMBER_EXPRESSION@0..11 - 0: JS_IDENTIFIER_EXPRESSION@0..7 - 0: JS_REFERENCE_IDENTIFIER@0..7 - 0: IDENT@0..7 "console" [] [] - 1: DOT@7..8 "." [] [] - 2: JS_NAME@8..11 - 0: IDENT@8..11 "log" [] [] - 1: (empty) - 2: (empty) - 3: JS_CALL_ARGUMENTS@11..20 - 0: L_PAREN@11..12 "(" [] [] - 1: JS_CALL_ARGUMENT_LIST@12..19 - 0: JS_STRING_LITERAL_EXPRESSION@12..19 - 0: JS_STRING_LITERAL@12..19 "'hello'" [] [] - 2: R_PAREN@19..20 ")" [] [] - 1: (empty) - 4: EOF@20..20 "" [] [] - , - ), - ), - source: "console.log('hello')", - }, } "###); } diff --git a/crates/biome_grit_patterns/tests/quick_test.rs b/crates/biome_grit_patterns/tests/quick_test.rs index 0c2980137d92..d442b6e2d125 100644 --- a/crates/biome_grit_patterns/tests/quick_test.rs +++ b/crates/biome_grit_patterns/tests/quick_test.rs @@ -8,8 +8,8 @@ use biome_js_syntax::JsFileSource; #[test] fn test_query() { let parse_grit_result = parse_grit( - "`console.log($args)` where { - $args <: contains `world` + "`console.log($arg)` => . where { + log(message=\"This is a debug log\", variable=$arg), } ", ); @@ -28,16 +28,27 @@ fn test_query() { println!("Diagnostics from compiling query:\n{:?}", query.diagnostics); } - let body = r#"console.log("hello, world"); -console.log("hello", world); -console.log(`hello ${world}`); -"#; + let body = r#"console.log("grape");"#; let file = GritTargetFile { path: "test.js".into(), parse: parse(body, JsFileSource::tsx(), JsParserOptions::default()).into(), }; - let results = query.execute(file).expect("could not execute query"); + let (results, logs) = query.execute(file).expect("could not execute query"); println!("Results: {results:#?}"); + + if !logs.is_empty() { + println!( + "\n## Logs\n\n{}", + logs.iter() + .map(|log| format!( + "Message: {}Syntax: {}", + log.message, + log.syntax_tree.as_deref().unwrap_or_default() + )) + .collect::>() + .join("\n") + ); + } } diff --git a/crates/biome_grit_patterns/tests/spec_tests.rs b/crates/biome_grit_patterns/tests/spec_tests.rs index 9e3f5006ba39..5a52cd7fcf01 100644 --- a/crates/biome_grit_patterns/tests/spec_tests.rs +++ b/crates/biome_grit_patterns/tests/spec_tests.rs @@ -67,12 +67,27 @@ fn run_test(input: &'static str, _: &str, _: &str, _: &str) { } }; - let results = query + let (results, logs) = query .execute(target_file) .unwrap_or_else(|err| panic!("cannot execute query from {query_path:?}: {err:?}")); let snapshot_result = SnapshotResult::from_query_results(results); - let snapshot = format!("{snapshot_result:#?}"); + let snapshot = if logs.is_empty() { + format!("{snapshot_result:#?}") + } else { + let logs = logs + .iter() + .map(|log| { + format!( + "Message: {}Syntax: {}", + log.message, + log.syntax_tree.as_deref().unwrap_or_default() + ) + }) + .collect::>() + .join("\n"); + format!("{snapshot_result:#?}\n\n## Logs\n\n{logs}") + }; insta::with_settings!({ prepend_module_to_snapshot => false, diff --git a/crates/biome_grit_patterns/tests/specs/ts/log.grit b/crates/biome_grit_patterns/tests/specs/ts/log.grit new file mode 100644 index 000000000000..366bb40de413 --- /dev/null +++ b/crates/biome_grit_patterns/tests/specs/ts/log.grit @@ -0,0 +1,3 @@ +`console.log($arg)` => . where { + log(message="This is a debug log", variable=$arg), +} diff --git a/crates/biome_grit_patterns/tests/specs/ts/log.snap b/crates/biome_grit_patterns/tests/specs/ts/log.snap new file mode 100644 index 000000000000..438da694bf63 --- /dev/null +++ b/crates/biome_grit_patterns/tests/specs/ts/log.snap @@ -0,0 +1,17 @@ +--- +source: crates/biome_grit_patterns/tests/spec_tests.rs +expression: log +--- +SnapshotResult { + messages: [], + matched_ranges: [ + "1:1-1:21", + ], + rewritten_files: [], + created_files: [], +} + +## Logs + +Message: This is a debug log +Syntax: (GritTargetNode { node: JsLanguage(Node(JS_CALL_ARGUMENT_LIST@12..19)) }) diff --git a/crates/biome_grit_patterns/tests/specs/ts/log.ts b/crates/biome_grit_patterns/tests/specs/ts/log.ts new file mode 100644 index 000000000000..e973c8a98e55 --- /dev/null +++ b/crates/biome_grit_patterns/tests/specs/ts/log.ts @@ -0,0 +1 @@ +console.log("grape"); diff --git a/crates/biome_service/src/file_handlers/mod.rs b/crates/biome_service/src/file_handlers/mod.rs index 7b607a877f3a..469793f8b2e6 100644 --- a/crates/biome_service/src/file_handlers/mod.rs +++ b/crates/biome_service/src/file_handlers/mod.rs @@ -647,7 +647,7 @@ pub(crate) fn search( query: &GritQuery, _settings: WorkspaceSettingsHandle, ) -> Result, WorkspaceError> { - let query_result = query + let (query_result, _logs) = query .execute(GritTargetFile { path: path.to_path_buf(), parse,