From 071345778396f6849611b8b49381e7e23a6e0345 Mon Sep 17 00:00:00 2001 From: "Arend van Beelen jr." Date: Tue, 25 Jun 2024 20:34:57 +0200 Subject: [PATCH 1/5] Add auto-wrap with Step + integrate with search command --- crates/biome_cli/src/commands/mod.rs | 2 +- crates/biome_cli/src/execute/traverse.rs | 6 +- .../biome_grit_patterns/src/grit_binding.rs | 16 +- .../biome_grit_patterns/src/grit_context.rs | 140 ++++++++++++-- crates/biome_grit_patterns/src/grit_query.rs | 72 ++++--- .../src/grit_resolved_pattern.rs | 180 +++++++++++------- crates/biome_grit_patterns/src/lib.rs | 5 +- .../src/pattern_compiler.rs | 4 +- .../pattern_compiler/accumulate_compiler.rs | 2 +- .../src/pattern_compiler/as_compiler.rs | 7 +- .../src/pattern_compiler/auto_wrap.rs | 6 +- .../src/pattern_compiler/bubble_compiler.rs | 4 +- .../pattern_compiler/compilation_context.rs | 10 +- .../pattern_compiler/container_compiler.rs | 2 +- .../src/pattern_compiler/equal_compiler.rs | 2 +- .../pattern_compiler/map_accessor_compiler.rs | 2 +- .../src/pattern_compiler/rewrite_compiler.rs | 2 +- .../src/pattern_compiler/snippet_compiler.rs | 14 +- .../src/pattern_compiler/variable_compiler.rs | 32 ++-- crates/biome_grit_patterns/src/variables.rs | 1 + .../biome_grit_patterns/tests/quick_test.rs | 7 +- crates/biome_service/src/diagnostics.rs | 15 +- crates/biome_service/src/workspace.rs | 4 +- crates/biome_service/src/workspace/server.rs | 68 ++++--- 24 files changed, 387 insertions(+), 216 deletions(-) diff --git a/crates/biome_cli/src/commands/mod.rs b/crates/biome_cli/src/commands/mod.rs index 0b49de90d539..f460dfc1a81b 100644 --- a/crates/biome_cli/src/commands/mod.rs +++ b/crates/biome_cli/src/commands/mod.rs @@ -386,7 +386,7 @@ pub enum BiomeCommand { /// The GritQL pattern to search for. /// /// Note that the search command (currently) does not support rewrites. - #[bpaf(positional("PATH"))] + #[bpaf(positional("PATTERN"))] pattern: String, /// Single file, single path or list of paths. diff --git a/crates/biome_cli/src/execute/traverse.rs b/crates/biome_cli/src/execute/traverse.rs index efb0dcb9fc1d..a6ca646fcc45 100644 --- a/crates/biome_cli/src/execute/traverse.rs +++ b/crates/biome_cli/src/execute/traverse.rs @@ -42,7 +42,8 @@ pub(crate) fn traverse( TraversalMode::Check { .. } | TraversalMode::Lint { .. } | TraversalMode::Format { .. } - | TraversalMode::CI { .. } => { + | TraversalMode::CI { .. } + | TraversalMode::Search { .. } => { // If `--staged` or `--changed` is specified, it's acceptable for them to be empty, so ignore it. if !execution.is_vcs_targeted() { match current_dir() { @@ -583,7 +584,7 @@ impl<'ctx, 'app> TraversalContext for TraversalOptions<'ctx, 'app> { TraversalMode::Lint { .. } => file_features.supports_lint(), // Imagine if Biome can't handle its own configuration file... TraversalMode::Migrate { .. } => true, - TraversalMode::Search { .. } => false, + TraversalMode::Search { .. } => true, } } @@ -613,6 +614,7 @@ fn handle_file(ctx: &TraversalOptions, path: &Path) { } Ok(Ok(FileStatus::Ignored)) => {} Ok(Err(err)) => { + println!("Oh no! {err:?}"); ctx.increment_unchanged(); ctx.skipped.fetch_add(1, Ordering::Relaxed); ctx.push_message(err); diff --git a/crates/biome_grit_patterns/src/grit_binding.rs b/crates/biome_grit_patterns/src/grit_binding.rs index ed7ede97f588..5ce7ced6875f 100644 --- a/crates/biome_grit_patterns/src/grit_binding.rs +++ b/crates/biome_grit_patterns/src/grit_binding.rs @@ -106,7 +106,7 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { } fn is_suppressed(&self, _language: &GritTargetLanguage, _current_name: Option<&str>) -> bool { - todo!() + false // TODO: Implement suppression } fn get_insertion_padding( @@ -188,7 +188,19 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { } fn is_truthy(&self) -> bool { - todo!() + match self { + Self::File(_) => true, + Self::Node(node) => { + if node.is_list() { + node.has_children() + } else { + true + } + } + Self::Range(..) => true, + Self::Empty(..) => false, + Self::Constant(c) => c.is_truthy(), + } } fn log_empty_field_rewrite_error( diff --git a/crates/biome_grit_patterns/src/grit_context.rs b/crates/biome_grit_patterns/src/grit_context.rs index 9213a3e4cf62..32c896be7d0a 100644 --- a/crates/biome_grit_patterns/src/grit_context.rs +++ b/crates/biome_grit_patterns/src/grit_context.rs @@ -6,13 +6,16 @@ use crate::grit_resolved_pattern::GritResolvedPattern; use crate::grit_target_language::GritTargetLanguage; use crate::grit_target_node::GritTargetNode; use crate::grit_tree::GritTargetTree; -use anyhow::Result; +use anyhow::{anyhow, bail, Result}; +use grit_pattern_matcher::constants::{GLOBAL_VARS_SCOPE_INDEX, NEW_FILES_INDEX}; use grit_pattern_matcher::context::{ExecContext, QueryContext}; use grit_pattern_matcher::file_owners::{FileOwner, FileOwners}; use grit_pattern_matcher::pattern::{ - CallBuiltIn, GritFunctionDefinition, Pattern, PatternDefinition, PredicateDefinition, State, + CallBuiltIn, File, FilePtr, GritFunctionDefinition, Matcher, Pattern, PatternDefinition, + PredicateDefinition, ResolvedPattern, State, }; -use grit_util::{AnalysisLogs, FileOrigin}; +use grit_util::{AnalysisLogs, FileOrigin, InputRanges, MatchRanges}; +use im::vector; use path_absolutize::Absolutize; use std::path::PathBuf; @@ -33,7 +36,12 @@ impl QueryContext for GritQueryContext { } pub struct GritExecContext<'a> { + /// The language to which the snippet should apply. lang: GritTargetLanguage, + + /// The name of the snippet being executed. + name: Option<&'a str>, + loadable_files: &'a [GritTargetFile], files: &'a FileOwners, functions: Vec>, @@ -44,11 +52,13 @@ pub struct GritExecContext<'a> { impl<'a> GritExecContext<'a> { pub fn new( lang: GritTargetLanguage, + name: Option<&'a str>, loadable_files: &'a [GritTargetFile], files: &'a FileOwners, ) -> Self { Self { lang, + name, loadable_files, files, functions: Vec::new(), @@ -95,16 +105,110 @@ impl<'a> ExecContext<'a, GritQueryContext> for GritExecContext<'a> { fn exec_step( &'a self, - _step: &'a Pattern, - _binding: &GritResolvedPattern, - _state: &mut State<'a, GritQueryContext>, - _logs: &mut AnalysisLogs, + step: &'a Pattern, + binding: &GritResolvedPattern, + state: &mut State<'a, GritQueryContext>, + logs: &mut AnalysisLogs, ) -> Result { - todo!() + let mut files = if let Some(files) = binding.get_file_pointers() { + files + .iter() + .map(|f| state.files.latest_revision(f)) + .collect::>() + } else { + return Ok(false); + }; + + let binding = if files.len() == 1 { + ResolvedPattern::from_file_pointer(*files.last().unwrap()) + } else { + // Load all files into memory and collect successful file pointers + files.retain(|file_ptr| { + self.load_file(&GritFile::Ptr(*file_ptr), state, logs) + .unwrap_or(false) + }); + ResolvedPattern::from_files(ResolvedPattern::from_list_parts( + files.iter().map(|f| ResolvedPattern::from_file_pointer(*f)), + )) + }; + if !step.execute(&binding, state, self, logs)? { + return Ok(false); + } + + // todo, for multifile we need to split up the matches by file. + let (variables, ranges, suppressed) = + state.bindings_history_to_ranges(&self.lang, self.name()); + + let input_ranges = InputRanges { + ranges, + variables, + suppressed, + }; + for file_ptr in files { + let file = state.files.get_file_owner(file_ptr); + let mut match_log = file.matches.borrow_mut(); + + if match_log.input_matches.is_none() { + match_log.input_matches = Some(input_ranges.clone()); + } + + // TODO: Implement effect application + } + + let new_files_binding = + &mut state.bindings[GLOBAL_VARS_SCOPE_INDEX].back_mut().unwrap()[NEW_FILES_INDEX]; + if new_files_binding.value.is_none() { + new_files_binding.value = Some(GritResolvedPattern::from_list_parts([].into_iter())); + } + + let Some(new_files) = new_files_binding + .value + .as_ref() + .and_then(ResolvedPattern::get_list_items) + else { + bail!("Expected a list of files") + }; + + for f in new_files { + let Some(file) = f.get_file() else { + bail!("Expected a list of files") + }; + + let name: PathBuf = file + .name(&state.files) + .text(&state.files, &self.lang) + .unwrap() + .as_ref() + .into(); + let body = file + .body(&state.files) + .text(&state.files, &self.lang) + .unwrap(); + let owned_file = file_owner_from_matches( + name.clone(), + &body, + None, + FileOrigin::New, + &self.lang, + logs, + )? + .ok_or_else(|| { + anyhow!( + "failed to construct new file for file {}", + name.to_string_lossy() + ) + })?; + self.files().push(owned_file); + let _ = state.files.push_new_file(self.files().last().unwrap()); + } + + state.effects = vector![]; + new_files_binding.value = Some(ResolvedPattern::from_list_parts([].into_iter())); + Ok(true) } fn name(&self) -> Option<&str> { - todo!() + self.name } fn load_file( @@ -127,7 +231,14 @@ impl<'a> ExecContext<'a, GritQueryContext> for GritExecContext<'a> { // TODO: Verify the workspace's maximum file size. - let file = file_owner_from_matches(&file.path, &file.content, &self.lang, logs)?; + let file = file_owner_from_matches( + &file.path, + &file.content, + None, + FileOrigin::Fresh, + &self.lang, + logs, + )?; if let Some(file) = file { self.files.push(file); state.files.load_file(ptr, self.files.last().unwrap()); @@ -141,14 +252,17 @@ impl<'a> ExecContext<'a, GritQueryContext> for GritExecContext<'a> { fn file_owner_from_matches( name: impl Into, source: &str, + matches: Option, + old_tree: FileOrigin<'_, GritTargetTree>, language: &GritTargetLanguage, logs: &mut AnalysisLogs, ) -> Result>> { let name = name.into(); + let new = !old_tree.is_fresh(); let Some(tree) = language .get_parser() - .parse_file(source, Some(&name), logs, FileOrigin::Fresh) + .parse_file(source, Some(&name), logs, old_tree) else { return Ok(None); }; @@ -158,8 +272,8 @@ fn file_owner_from_matches( name, absolute_path, tree, - matches: Default::default(), - new: false, + matches: matches.unwrap_or_default().into(), + new, })) } diff --git a/crates/biome_grit_patterns/src/grit_query.rs b/crates/biome_grit_patterns/src/grit_query.rs index d7c3a936aeb4..3972b6077f39 100644 --- a/crates/biome_grit_patterns/src/grit_query.rs +++ b/crates/biome_grit_patterns/src/grit_query.rs @@ -3,7 +3,7 @@ use crate::grit_context::{GritExecContext, GritQueryContext, GritTargetFile}; use crate::grit_resolved_pattern::GritResolvedPattern; use crate::grit_target_language::GritTargetLanguage; use crate::grit_tree::GritTargetTree; -use crate::pattern_compiler::PatternCompiler; +use crate::pattern_compiler::{auto_wrap_pattern, PatternCompiler}; use crate::pattern_compiler::{ compilation_context::CompilationContext, compilation_context::NodeCompilationContext, }; @@ -12,7 +12,6 @@ use crate::CompileError; use anyhow::bail; use anyhow::Result; use biome_grit_syntax::{GritRoot, GritRootExt}; -use grit_pattern_matcher::binding::Binding; use grit_pattern_matcher::constants::{ ABSOLUTE_PATH_INDEX, FILENAME_INDEX, NEW_FILES_INDEX, PROGRAM_INDEX, }; @@ -23,6 +22,7 @@ use grit_pattern_matcher::pattern::{ use grit_util::{Ast, ByteRange, InputRanges, Range, VariableMatch}; use im::Vector; use std::collections::{BTreeMap, BTreeSet}; +use std::ffi::OsStr; use std::path::{Path, PathBuf}; // These need to remain ordered by index. @@ -42,6 +42,9 @@ pub struct GritQuery { /// Diagnostics discovered during compilation of the query. pub diagnostics: Vec, + /// The name of the snippet being executed. + pub name: Option, + /// Target language for the query. language: GritTargetLanguage, @@ -54,7 +57,12 @@ impl GritQuery { let file_owners = FileOwners::new(); let files = vec![file]; let file_ptr = FilePtr::new(0, 0); - let context = GritExecContext::new(self.language.clone(), &files, &file_owners); + let context = GritExecContext::new( + self.language.clone(), + self.name.as_deref(), + &files, + &file_owners, + ); let var_registry = VarRegistry::from_locations(&self.variable_locations); @@ -75,11 +83,6 @@ impl GritQuery { results.push(result) } } - results.extend(results_from_bindings_history( - &files[0].path, - &state, - &self.language, - )); } Ok(results) @@ -87,16 +90,18 @@ impl GritQuery { pub fn from_node( root: GritRoot, - source_path: &Path, + path: Option<&Path>, lang: GritTargetLanguage, ) -> Result { - let context = CompilationContext::new(source_path, lang); + let context = CompilationContext::new(path, lang); let mut vars_array = vec![GLOBAL_VARS .iter() .map(|global_var| VariableSourceLocations { name: global_var.0.to_string(), - file: source_path.to_string_lossy().into_owned(), + file: path + .map(Path::to_string_lossy) + .map_or_else(|| "unnamed".to_owned(), |p| p.to_string()), locations: BTreeSet::new(), }) .collect::>()]; @@ -106,6 +111,8 @@ impl GritQuery { .collect(); let mut diagnostics = Vec::new(); + // Make sure + // We're not in a local scope yet, so this map is kinda useless. // It's just there because all node compilers expect one. let mut vars = BTreeMap::new(); @@ -123,11 +130,26 @@ impl GritQuery { &mut node_context, )?; + let mut pattern_definitions = Vec::new(); + let pattern = auto_wrap_pattern( + pattern, + &mut pattern_definitions, + true, + None, + &mut node_context, + None, + )?; + + let name = path + .and_then(Path::file_stem) + .map(OsStr::to_string_lossy) + .map(|stem| stem.to_string()); let language = context.lang; let variable_locations = VariableLocations::new(vars_array); Ok(Self { pattern, + name, language, diagnostics, variable_locations, @@ -313,31 +335,3 @@ pub struct Message { pub range: Vec, pub variable_runtime_id: String, } - -fn results_from_bindings_history( - path: &Path, - state: &State, - language: &GritTargetLanguage, -) -> Vec { - println!("{state:#?}"); - let mut results = Vec::new(); - for scope in state.bindings.iter() { - for content in scope.last().unwrap().iter() { - for value in content.value_history.iter() { - if let Some(bindings) = value.get_bindings() { - for binding in bindings { - if let Some(range) = binding.position(language) { - results.push(GritQueryResult::Match(Match { - messages: Vec::new(), - source_file: path.to_path_buf(), - ranges: vec![range], - variables: Vec::new(), - })); - } - } - } - } - } - } - results -} diff --git a/crates/biome_grit_patterns/src/grit_resolved_pattern.rs b/crates/biome_grit_patterns/src/grit_resolved_pattern.rs index b2bb67d6dad6..3e716f552deb 100644 --- a/crates/biome_grit_patterns/src/grit_resolved_pattern.rs +++ b/crates/biome_grit_patterns/src/grit_resolved_pattern.rs @@ -5,15 +5,15 @@ use crate::grit_target_node::GritTargetNode; use crate::grit_tree::GritTargetTree; use crate::GritTargetLanguage; use crate::{grit_binding::GritBinding, grit_context::GritQueryContext}; -use anyhow::{anyhow, Error, Result}; +use anyhow::{anyhow, bail, Error, Result}; use grit_pattern_matcher::binding::Binding; use grit_pattern_matcher::constant::Constant; use grit_pattern_matcher::context::{ExecContext, QueryContext}; use grit_pattern_matcher::effects::Effect; use grit_pattern_matcher::pattern::{ - Accessor, DynamicPattern, DynamicSnippet, DynamicSnippetPart, File, FilePtr, FileRegistry, - GritCall, ListIndex, Pattern, PatternName, PatternOrResolved, ResolvedFile, ResolvedPattern, - ResolvedSnippet, State, + to_unsigned, Accessor, DynamicPattern, DynamicSnippet, DynamicSnippetPart, File, FilePtr, + FileRegistry, GritCall, ListIndex, Pattern, PatternName, PatternOrResolved, ResolvedFile, + ResolvedPattern, ResolvedSnippet, State, }; use grit_util::{AnalysisLogs, Ast, CodeRange, Range}; use im::{vector, Vector}; @@ -389,11 +389,23 @@ impl<'a> ResolvedPattern<'a, GritQueryContext> for GritResolvedPattern<'a> { } fn get_file_pointers(&self) -> Option> { - todo!() + match self { + Self::Binding(_) => None, + Self::Snippets(_) => None, + Self::List(_) => handle_files(self), + Self::Map(_) => None, + Self::File(file) => extract_file_pointer(file).map(|f| vec![f]), + Self::Files(files) => handle_files(files), + Self::Constant(_) => None, + } } fn get_files(&self) -> Option<&Self> { - todo!() + if let Self::Files(files) = self { + Some(files) + } else { + None + } } fn get_last_binding(&self) -> Option<&GritBinding<'a>> { @@ -404,12 +416,20 @@ impl<'a> ResolvedPattern<'a, GritQueryContext> for GritResolvedPattern<'a> { } } - fn get_list_item_at(&self, _index: isize) -> Option<&Self> { - todo!() + fn get_list_item_at(&self, index: isize) -> Option<&Self> { + if let Self::List(items) = self { + to_unsigned(index, items.len()).and_then(|index| items.get(index)) + } else { + None + } } - fn get_list_item_at_mut(&mut self, _index: isize) -> Option<&mut Self> { - todo!() + fn get_list_item_at_mut(&mut self, index: isize) -> Option<&mut Self> { + if let Self::List(items) = self { + to_unsigned(index, items.len()).and_then(|index| items.get_mut(index)) + } else { + None + } } fn get_list_items(&self) -> Option> { @@ -460,10 +480,25 @@ impl<'a> ResolvedPattern<'a, GritQueryContext> for GritResolvedPattern<'a> { fn is_truthy( &self, - _state: &mut State<'a, GritQueryContext>, - _language: &::Language<'a>, + state: &mut State<'a, GritQueryContext>, + language: &GritTargetLanguage, ) -> Result { - todo!() + let truthiness = match self { + Self::Binding(bindings) => bindings.last().map_or(false, Binding::is_truthy), + Self::List(elements) => !elements.is_empty(), + Self::Map(map) => !map.is_empty(), + Self::Constant(c) => c.is_truthy(), + Self::Snippets(s) => { + if let Some(s) = s.last() { + s.is_truthy(state, language)? + } else { + false + } + } + Self::File(..) => true, + Self::Files(..) => true, + }; + Ok(truthiness) } fn linearized_text( @@ -479,35 +514,65 @@ impl<'a> ResolvedPattern<'a, GritQueryContext> for GritResolvedPattern<'a> { } fn matches_undefined(&self) -> bool { - todo!() + match self { + Self::Binding(b) => b + .last() + .and_then(Binding::as_constant) + .map_or(false, Constant::is_undefined), + Self::Constant(Constant::Undefined) => true, + Self::Constant(_) + | Self::Snippets(_) + | Self::List(_) + | Self::Map(_) + | Self::File(_) + | Self::Files(_) => false, + } } fn matches_false_or_undefined(&self) -> bool { - todo!() + // should this match a binding to the constant `false` as well? + matches!(self, Self::Constant(Constant::Boolean(false))) || self.matches_undefined() } fn normalize_insert( &mut self, _binding: &GritBinding, _is_first: bool, - _language: &::Language<'a>, + _language: &GritTargetLanguage, ) -> Result<()> { todo!() } - fn position( - &self, - _language: &::Language<'a>, - ) -> Option { - todo!() + fn position(&self, language: &GritTargetLanguage) -> Option { + if let Self::Binding(binding) = self { + if let Some(binding) = binding.last() { + return binding.position(language); + } + } + + None } - fn push_binding(&mut self, _binding: GritBinding) -> Result<()> { - todo!() + fn push_binding(&mut self, binding: GritBinding<'a>) -> Result<()> { + let Self::Binding(bindings) = self else { + bail!("can only push to bindings"); + }; + + bindings.push_back(binding); + Ok(()) } - fn set_list_item_at_mut(&mut self, _index: isize, _value: Self) -> anyhow::Result { - todo!() + fn set_list_item_at_mut(&mut self, index: isize, value: Self) -> Result { + let Self::List(items) = self else { + bail!("can only set items on a list"); + }; + + let Some(index) = to_unsigned(index, items.len()) else { + return Ok(false); + }; + + items.insert(index, value); + Ok(true) } fn text( @@ -543,53 +608,26 @@ impl<'a> ResolvedPattern<'a, GritQueryContext> for GritResolvedPattern<'a> { } } -#[derive(Clone)] -struct TodoBindingIterator<'a> { - _pattern: &'a GritResolvedPattern<'a>, -} - -impl<'a> Iterator for TodoBindingIterator<'a> { - type Item = GritBinding<'a>; - - fn next(&mut self) -> Option { - todo!() +fn extract_file_pointer(file: &GritFile) -> Option { + match file { + GritFile::Resolved(_) => None, + GritFile::Ptr(ptr) => Some(*ptr), } } -#[derive(Clone)] -struct TodoSelfIterator<'a> { - _pattern: &'a GritResolvedPattern<'a>, -} - -impl<'a> Iterator for TodoSelfIterator<'a> { - type Item = GritResolvedPattern<'a>; - - fn next(&mut self) -> Option { - todo!() - } -} - -struct TodoSelfRefIterator<'a> { - _pattern: &'a GritResolvedPattern<'a>, -} - -impl<'a> Iterator for TodoSelfRefIterator<'a> { - type Item = &'a GritResolvedPattern<'a>; - - fn next(&mut self) -> Option { - todo!() - } -} - -#[derive(Clone)] -struct TodoSnippetIterator<'a> { - _pattern: &'a GritResolvedPattern<'a>, -} - -impl<'a> Iterator for TodoSnippetIterator<'a> { - type Item = ResolvedSnippet<'a, GritQueryContext>; - - fn next(&mut self) -> Option { - todo!() - } +fn handle_files(files_list: &GritResolvedPattern) -> Option> { + let GritResolvedPattern::List(files) = files_list else { + return None; + }; + + files + .iter() + .map(|resolved| { + if let GritResolvedPattern::File(GritFile::Ptr(ptr)) = resolved { + Some(*ptr) + } else { + None + } + }) + .collect() } diff --git a/crates/biome_grit_patterns/src/lib.rs b/crates/biome_grit_patterns/src/lib.rs index f51073f8d5d1..ff99bb60add7 100644 --- a/crates/biome_grit_patterns/src/lib.rs +++ b/crates/biome_grit_patterns/src/lib.rs @@ -1,4 +1,3 @@ -#![allow(dead_code)] // FIXME: Remove when more stuff is ready mod diagnostics; mod errors; mod grit_analysis_ext; @@ -21,7 +20,7 @@ mod variables; pub use errors::*; pub use grit_context::GritTargetFile; -pub use grit_query::GritQuery; +pub use grit_query::{GritQuery, GritQueryResult}; pub use grit_target_language::{GritTargetLanguage, JsTargetLanguage}; use biome_grit_parser::parse_grit; @@ -30,7 +29,7 @@ use std::path::Path; /// Compiles a Grit pattern from the given source string. pub fn compile_pattern( source: &str, - path: &Path, + path: Option<&Path>, language: GritTargetLanguage, ) -> Result { let parsed = parse_grit(source); diff --git a/crates/biome_grit_patterns/src/pattern_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler.rs index 9fc121d2b302..f377789578c4 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler.rs @@ -88,6 +88,8 @@ use biome_rowan::AstNode as _; use grit_pattern_matcher::pattern::{DynamicPattern, DynamicSnippet, DynamicSnippetPart, Pattern}; use node_like_compiler::NodeLikeCompiler; +pub(crate) use self::auto_wrap::auto_wrap_pattern; + pub(crate) struct PatternCompiler; impl PatternCompiler { @@ -221,7 +223,7 @@ impl PatternCompiler { ))), AnyGritPattern::GritUnderscore(_) => Ok(Pattern::Underscore), AnyGritPattern::GritVariable(node) => Ok(Pattern::Variable( - VariableCompiler::from_node(node, context)?, + VariableCompiler::from_node(node, context), )), AnyGritPattern::GritWithin(node) => Ok(Pattern::Within(Box::new( WithinCompiler::from_node(node, context)?, diff --git a/crates/biome_grit_patterns/src/pattern_compiler/accumulate_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/accumulate_compiler.rs index a4e18bb5b461..dcd9385a3bf5 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/accumulate_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/accumulate_compiler.rs @@ -36,7 +36,7 @@ impl PrAccumulateCompiler { node: &GritPredicateAccumulate, context: &mut NodeCompilationContext, ) -> Result, CompileError> { - let left = Pattern::Variable(VariableCompiler::from_node(&node.left()?, context)?); + let left = Pattern::Variable(VariableCompiler::from_node(&node.left()?, context)); let right = PatternCompiler::from_node_with_rhs(&node.right()?, context, true)?; let dynamic_right = match &right { Pattern::Dynamic(r) => Some(r.clone()), diff --git a/crates/biome_grit_patterns/src/pattern_compiler/as_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/as_compiler.rs index 697df4d24a2e..dfc613d6f08d 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/as_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/as_compiler.rs @@ -11,8 +11,11 @@ use biome_rowan::AstNode as _; use grit_pattern_matcher::pattern::{Container, Match, Pattern, Predicate, Where}; use grit_util::{traverse, AstNode, Language, Order}; +// TODO: `as` keyword +#[allow(dead_code)] pub(crate) struct AsCompiler; +#[allow(dead_code)] impl AsCompiler { pub(crate) fn from_node( node: &GritPatternAs, @@ -35,7 +38,7 @@ impl AsCompiler { } let pattern = PatternCompiler::from_node(&pattern, context)?; - let variable = VariableCompiler::from_node(&variable, context)?; + let variable = VariableCompiler::from_node(&variable, context); Ok(Where::new( Pattern::Variable(variable), Predicate::Match(Box::new(Match::new( @@ -46,6 +49,7 @@ impl AsCompiler { } } +#[allow(dead_code)] fn pattern_repeated_variable( pattern: &AnyGritPattern, name: &str, @@ -71,6 +75,7 @@ fn pattern_repeated_variable( .any(|b| b)) } +#[allow(dead_code)] fn is_variables_in_snippet(name: &str, snippet: &str, lang: &impl Language) -> bool { let variables = split_snippet(snippet, lang); variables.iter().any(|v| v.1 == name) diff --git a/crates/biome_grit_patterns/src/pattern_compiler/auto_wrap.rs b/crates/biome_grit_patterns/src/pattern_compiler/auto_wrap.rs index 0bca7097c2c2..3a1420b2f154 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/auto_wrap.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/auto_wrap.rs @@ -10,7 +10,7 @@ use grit_pattern_matcher::pattern::{ use grit_util::FileRange; use std::collections::BTreeMap; -pub(super) fn auto_wrap_pattern( +pub fn auto_wrap_pattern( pattern: Pattern, pattern_definitions: &mut [PatternDefinition], is_not_multifile: bool, @@ -362,7 +362,7 @@ fn wrap_pattern_in_range( ranges: Vec, context: &mut NodeCompilationContext, ) -> Result, CompileError> { - let var = context.variable_from_name(var_name)?; + let var = context.variable_from_name(var_name); let mut predicates = Vec::new(); for file_range in ranges { let range = file_range.range.clone(); @@ -403,7 +403,7 @@ fn wrap_pattern_in_contains( pattern: Pattern, context: &mut NodeCompilationContext, ) -> Result, CompileError> { - let var = context.variable_from_name(var_name)?; + let var = context.variable_from_name(var_name); let pattern = Pattern::Where(Box::new(Where::new( Pattern::Variable(var), Predicate::Match(Box::new(Match::new( diff --git a/crates/biome_grit_patterns/src/pattern_compiler/bubble_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/bubble_compiler.rs index 5809c0ef9e66..ce99c62ec1af 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/bubble_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/bubble_compiler.rs @@ -39,13 +39,13 @@ impl BubbleCompiler { return Err(CompileError::DuplicateParameters); } - let params = local_context.get_variables(¶meters)?; + let params = local_context.get_variables(¶meters); let body = PatternCompiler::from_maybe_curly_node(&node.pattern()?, &mut local_context)?; let args = parameters .into_iter() - .map(|(name, range)| Ok(Pattern::Variable(context.register_variable(name, range)?))) + .map(|(name, range)| Ok(Pattern::Variable(context.register_variable(name, range)))) .collect::, CompileError>>()?; let pattern_def = PatternDefinition::new( diff --git a/crates/biome_grit_patterns/src/pattern_compiler/compilation_context.rs b/crates/biome_grit_patterns/src/pattern_compiler/compilation_context.rs index 323637b2dfe4..f9fe1e8fa16b 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/compilation_context.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/compilation_context.rs @@ -17,15 +17,9 @@ pub(crate) struct CompilationContext<'a> { } impl<'a> CompilationContext<'a> { - pub(crate) fn new(source_path: &'a Path, lang: GritTargetLanguage) -> Self { - let mut this = Self::new_anonymous(lang); - this.source_path = Some(source_path); - this - } - - pub(crate) fn new_anonymous(lang: GritTargetLanguage) -> Self { + pub(crate) fn new(source_path: Option<&'a Path>, lang: GritTargetLanguage) -> Self { Self { - source_path: None, + source_path, lang, pattern_definition_info: Default::default(), predicate_definition_info: Default::default(), diff --git a/crates/biome_grit_patterns/src/pattern_compiler/container_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/container_compiler.rs index 7f6693ebfb0f..9501abae91d8 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/container_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/container_compiler.rs @@ -21,7 +21,7 @@ impl ContainerCompiler { MapAccessorCompiler::from_node(accessor, context)?, ))), AnyGritContainer::GritVariable(variable) => Ok(Container::Variable( - VariableCompiler::from_node(variable, context)?, + VariableCompiler::from_node(variable, context), )), AnyGritContainer::GritBogusContainer(_) => Err(CompileError::UnexpectedKind( GritSyntaxKind::GRIT_BOGUS_CONTAINER.into(), diff --git a/crates/biome_grit_patterns/src/pattern_compiler/equal_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/equal_compiler.rs index cdcfa844f7fd..d8ea0b12a7bb 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/equal_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/equal_compiler.rs @@ -13,7 +13,7 @@ impl PrEqualCompiler { node: &GritPredicateEqual, context: &mut NodeCompilationContext, ) -> Result, CompileError> { - let variable = VariableCompiler::from_node(&node.left()?, context)?; + let variable = VariableCompiler::from_node(&node.left()?, context); let pattern = PatternCompiler::from_node_with_rhs(&node.right()?, context, true)?; Ok(Equal::new(variable, pattern)) diff --git a/crates/biome_grit_patterns/src/pattern_compiler/map_accessor_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/map_accessor_compiler.rs index acda036254d9..a7186d43b7c4 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/map_accessor_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/map_accessor_compiler.rs @@ -28,7 +28,7 @@ impl MapAccessorCompiler { AccessorKey::String(name.syntax().text_trimmed().to_string()) } AnyGritMapKey::GritVariable(variable) => { - AccessorKey::Variable(VariableCompiler::from_node(&variable, context)?) + AccessorKey::Variable(VariableCompiler::from_node(&variable, context)) } }; diff --git a/crates/biome_grit_patterns/src/pattern_compiler/rewrite_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/rewrite_compiler.rs index 63516f3c94ab..d912b6949f2f 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/rewrite_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/rewrite_compiler.rs @@ -50,7 +50,7 @@ impl PrRewriteCompiler { node: &GritPredicateRewrite, context: &mut NodeCompilationContext, ) -> Result, CompileError> { - let left = Pattern::Variable(VariableCompiler::from_node(&node.left()?, context)?); + let left = Pattern::Variable(VariableCompiler::from_node(&node.left()?, context)); let right = node.right()?; let right = to_dynamic_pattern( 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 a37e3b7fba8f..60ae5cc8e156 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs @@ -54,7 +54,7 @@ pub(crate) fn parse_snippet_content( return match source.trim() { "$_" | "^_" => Ok(Pattern::Underscore), name => { - let var = context.register_variable(name.to_owned(), range)?; + let var = context.register_variable(name.to_owned(), range); Ok(Pattern::Variable(var)) } }; @@ -121,7 +121,7 @@ pub(crate) fn dynamic_snippet_from_source( *var, ))); } else if var.starts_with("$GLOBAL_") { - let variable = context.register_variable(var.to_string(), range)?; + let variable = context.register_variable(var.to_string(), range); parts.push(DynamicSnippetPart::Variable(variable)); } else { return Err(CompileError::UnknownVariable(var.to_string())); @@ -469,7 +469,7 @@ fn text_to_var( let range = *range_map .get(&range) .ok_or_else(|| CompileError::InvalidMetavariableRange(range))?; - let var = context.register_variable(name, range + context_range.start)?; + let var = context.register_variable(name, range + context_range.start); Ok(SnippetValues::Variable(var)) } } @@ -496,8 +496,6 @@ fn unescape(raw_string: &str) -> String { #[cfg(test)] mod tests { - use std::path::Path; - use super::*; use crate::{ grit_js_parser::GritJsParser, pattern_compiler::compilation_context::CompilationContext, @@ -571,10 +569,8 @@ mod tests { #[test] fn test_pattern_from_node() { - let compilation_context = CompilationContext::new( - Path::new("test.js"), - GritTargetLanguage::JsTargetLanguage(JsTargetLanguage), - ); + let compilation_context = + CompilationContext::new(None, GritTargetLanguage::JsTargetLanguage(JsTargetLanguage)); let mut vars = BTreeMap::new(); let mut vars_array = Vec::new(); let mut global_vars = BTreeMap::new(); diff --git a/crates/biome_grit_patterns/src/pattern_compiler/variable_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/variable_compiler.rs index 1ba5a08509c3..a353addcc6f1 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/variable_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/variable_compiler.rs @@ -1,6 +1,5 @@ use super::compilation_context::NodeCompilationContext; use crate::util::TextRangeGritExt; -use crate::CompileError; use biome_grit_syntax::GritVariable; use biome_rowan::AstNode; use grit_pattern_matcher::constants::GLOBAL_VARS_SCOPE_INDEX; @@ -12,10 +11,7 @@ use std::path::Path; pub(crate) struct VariableCompiler; impl VariableCompiler { - pub(crate) fn from_node( - node: &GritVariable, - context: &mut NodeCompilationContext, - ) -> Result { + pub(crate) fn from_node(node: &GritVariable, context: &mut NodeCompilationContext) -> Variable { let name = node.syntax().text_trimmed().to_string(); let range = node.range().to_byte_range(); context.register_variable(name, range) @@ -23,28 +19,24 @@ impl VariableCompiler { } impl<'a> NodeCompilationContext<'a> { - pub(super) fn variable_from_name(&mut self, name: String) -> Result { + pub(super) fn variable_from_name(&mut self, name: String) -> Variable { self.register_variable_with_optional_range(name, None) } pub(super) fn get_variables( &mut self, params: &[(String, ByteRange)], - ) -> Result, CompileError> { + ) -> Vec<(String, Variable)> { params .iter() .map(|(name, range)| { - let index = self.register_variable(name.clone(), *range)?; - Ok((name.to_owned(), index)) + let index = self.register_variable(name.clone(), *range); + (name.to_owned(), index) }) .collect() } - pub(super) fn register_variable( - &mut self, - name: String, - range: ByteRange, - ) -> Result { + pub fn register_variable(&mut self, name: String, range: ByteRange) -> Variable { self.register_variable_with_optional_range( name, Some(FileLocation { @@ -54,11 +46,11 @@ impl<'a> NodeCompilationContext<'a> { ) } - fn register_variable_with_optional_range( + pub fn register_variable_with_optional_range( &mut self, name: String, location: Option, - ) -> Result { + ) -> Variable { let Self { vars, vars_array, @@ -71,7 +63,7 @@ impl<'a> NodeCompilationContext<'a> { if let Some(FileLocation { range, .. }) = location { vars_array[*scope_index][*i].locations.insert(range); } - return Ok(Variable::new(*scope_index, *i)); + return Variable::new(*scope_index, *i); } if let Some(i) = global_vars.get(&name) { @@ -82,7 +74,7 @@ impl<'a> NodeCompilationContext<'a> { .insert(range); } } - return Ok(Variable::new(GLOBAL_VARS_SCOPE_INDEX, *i)); + return Variable::new(GLOBAL_VARS_SCOPE_INDEX, *i); } let (name_map, scope_index) = if name.starts_with("$GLOBAL_") { (global_vars, GLOBAL_VARS_SCOPE_INDEX) @@ -108,11 +100,11 @@ impl<'a> NodeCompilationContext<'a> { .unwrap_or_default(), locations, }); - Ok(Variable::new(scope_index, index)) + Variable::new(scope_index, index) } } -struct FileLocation<'a> { +pub struct FileLocation<'a> { path: Option<&'a Path>, range: ByteRange, } diff --git a/crates/biome_grit_patterns/src/variables.rs b/crates/biome_grit_patterns/src/variables.rs index d6d2f3abf204..311ba13ece15 100644 --- a/crates/biome_grit_patterns/src/variables.rs +++ b/crates/biome_grit_patterns/src/variables.rs @@ -17,6 +17,7 @@ impl VariableLocations { Self(locations) } + #[allow(dead_code)] pub(crate) fn compiled_vars(&self) -> Vec { let mut variables = Vec::new(); for (i, scope) in self.0.iter().enumerate() { diff --git a/crates/biome_grit_patterns/tests/quick_test.rs b/crates/biome_grit_patterns/tests/quick_test.rs index e0ee6a4a5025..c96fdc242967 100644 --- a/crates/biome_grit_patterns/tests/quick_test.rs +++ b/crates/biome_grit_patterns/tests/quick_test.rs @@ -1,5 +1,3 @@ -use std::path::Path; - use biome_grit_parser::parse_grit; use biome_grit_patterns::{GritQuery, GritTargetFile, GritTargetLanguage, JsTargetLanguage}; @@ -7,8 +5,7 @@ use biome_grit_patterns::{GritQuery, GritTargetFile, GritTargetLanguage, JsTarge #[ignore] #[test] fn test_query() { - // TODO: Still need to implement autowrapping. - let parse_grit_result = parse_grit("file(body = contains bubble `\"hello\"`)"); + let parse_grit_result = parse_grit("`\"hello\"`"); if !parse_grit_result.diagnostics().is_empty() { println!( "Diagnostics from parsing query:\n{:?}", @@ -18,7 +15,7 @@ fn test_query() { let query = GritQuery::from_node( parse_grit_result.tree(), - Path::new("quick_test.js"), + None, GritTargetLanguage::JsTargetLanguage(JsTargetLanguage), ) .expect("could not construct query"); diff --git a/crates/biome_service/src/diagnostics.rs b/crates/biome_service/src/diagnostics.rs index 7247cf9ecdbe..082d3d192bcf 100644 --- a/crates/biome_service/src/diagnostics.rs +++ b/crates/biome_service/src/diagnostics.rs @@ -329,6 +329,8 @@ pub enum SearchError { PatternCompilationError(CompileError), /// No pattern with the given ID InvalidPattern(InvalidPattern), + /// Error while executing the search query. + QueryError(QueryError), } #[derive(Debug, Serialize, Deserialize, Diagnostic)] @@ -341,6 +343,16 @@ pub enum SearchError { )] pub struct InvalidPattern; +#[derive(Debug, Serialize, Deserialize, Diagnostic)] +#[diagnostic( + category = "search", + message( + message("Error executing the Grit query."), + description = "For reference, please consult: https://docs.grit.io/language/syntax" + ) +)] +pub struct QueryError(pub String); + pub fn extension_error(path: &BiomePath) -> WorkspaceError { let file_source = DocumentFileSource::from_path(path); WorkspaceError::source_file_not_supported( @@ -427,7 +439,8 @@ impl From for WorkspaceError { impl From for WorkspaceError { fn from(value: CompileError) -> Self { - Self::SearchError(SearchError::PatternCompilationError(value)) + // FIXME: This really needs proper diagnostics + Self::SearchError(SearchError::QueryError(QueryError(format!("{value:?}")))) } } diff --git a/crates/biome_service/src/workspace.rs b/crates/biome_service/src/workspace.rs index 493a78f6622c..282cda255340 100644 --- a/crates/biome_service/src/workspace.rs +++ b/crates/biome_service/src/workspace.rs @@ -124,7 +124,9 @@ impl FileFeaturesResult { (FeatureName::Lint, SupportKind::FileNotSupported), (FeatureName::Format, SupportKind::FileNotSupported), (FeatureName::OrganizeImports, SupportKind::FileNotSupported), - (FeatureName::Search, SupportKind::FileNotSupported), + // FIXME: I remember I was not supposed to do this, but I forgot why. + // It works though, so please remind me what the right way to do this is :) + (FeatureName::Search, SupportKind::Supported), ]; pub fn new() -> Self { diff --git a/crates/biome_service/src/workspace/server.rs b/crates/biome_service/src/workspace/server.rs index 6abd9168a3fb..b9ed79027faa 100644 --- a/crates/biome_service/src/workspace/server.rs +++ b/crates/biome_service/src/workspace/server.rs @@ -7,6 +7,7 @@ use super::{ RenameResult, SearchPatternParams, SearchResults, SupportsFeatureParams, UnregisterProjectFolderParams, UpdateProjectParams, UpdateSettingsParams, }; +use crate::diagnostics::{InvalidPattern, QueryError, SearchError}; use crate::file_handlers::{ Capabilities, CodeActionsParams, DocumentFileSource, FixAllParams, LintParams, ParseResult, }; @@ -24,17 +25,18 @@ use biome_diagnostics::{ }; use biome_formatter::Printed; use biome_fs::{BiomePath, ConfigName}; -use biome_grit_patterns::GritQuery; +use biome_grit_patterns::{GritQuery, GritQueryResult, GritTargetFile}; use biome_json_parser::{parse_json_with_cache, JsonParserOptions}; use biome_json_syntax::JsonFileSource; use biome_parser::AnyParse; use biome_project::NodeJsProject; -use biome_rowan::{NodeCache, TextLen, TextRange, TextSize}; +use biome_rowan::{NodeCache, TextRange}; use dashmap::{mapref::entry::Entry, DashMap}; use indexmap::IndexSet; use std::ffi::OsStr; use std::fs; use std::path::Path; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::{panic::RefUnwindSafe, sync::RwLock}; use tracing::{debug, info, info_span}; @@ -75,24 +77,6 @@ pub(crate) struct Document { node_cache: NodeCache, } -// TODO: remove once an actual implementation for the matches is present -struct DummySearchMatchesProvider; - -impl DummySearchMatchesProvider { - // a match that goes from the first to the last character of the first line, if present - fn get_range(input: &str) -> Vec { - let mut lines = input.lines(); - let first_line = lines.next(); - - let max_size = match first_line { - Some(v) => v.text_len(), - None => return vec![], - }; - - vec![TextRange::new(TextSize::from(0), max_size)] - } -} - impl WorkspaceServer { /// Create a new [Workspace] /// @@ -793,30 +777,50 @@ impl Workspace for WorkspaceServer { ) -> Result { let pattern = biome_grit_patterns::compile_pattern( ¶ms.pattern, - Path::new("filename"), // TODO: Pass the real filename. + None, biome_grit_patterns::JsTargetLanguage.into(), )?; - let pattern_id = PatternId::from("1234"); // TODO: Generate a real ID. + + let pattern_id = PatternId::from(make_search_pattern_id()); self.patterns.insert(pattern_id.clone(), pattern); Ok(ParsePatternResult { pattern_id }) } fn search_pattern(&self, params: SearchPatternParams) -> Result { - let SearchPatternParams { path, .. } = params; + let SearchPatternParams { path, pattern } = params; + + let Some(query) = self.patterns.get(&pattern) else { + return Err(WorkspaceError::SearchError(SearchError::InvalidPattern( + InvalidPattern, + ))); + }; - // TODO: Let's implement some real matching here... let document = self .documents - .get_mut(&path) + .get(&path) .ok_or_else(WorkspaceError::not_found)?; - let content = document.content.as_str(); - - let match_ranges = DummySearchMatchesProvider::get_range(content); + let query_result = query + .execute(GritTargetFile { + path: path.to_path_buf(), + content: document.content.clone(), + }) + .map_err(|err| { + WorkspaceError::SearchError(SearchError::QueryError(QueryError(err.to_string()))) + })?; + + let matches = query_result + .into_iter() + .flat_map(|result| match result { + GritQueryResult::Match(m) => m.ranges, + _ => Vec::new(), + }) + .map(|range| TextRange::new(range.start_byte.into(), range.end_byte.into())) + .collect(); Ok(SearchResults { file: path, - matches: match_ranges, + matches, }) } @@ -851,3 +855,9 @@ impl Workspace for WorkspaceServer { fn is_dir(path: &Path) -> bool { path.is_dir() || (path.is_symlink() && fs::read_link(path).is_ok_and(|path| path.is_dir())) } + +fn make_search_pattern_id() -> String { + static COUNTER: AtomicUsize = AtomicUsize::new(1); + let counter = COUNTER.fetch_add(1, Ordering::AcqRel); + format!("p{counter}") +} From 2bdb9fd13fe6a221cf47b8caac42d883a2d1473d Mon Sep 17 00:00:00 2001 From: "Arend van Beelen jr." Date: Tue, 2 Jul 2024 21:38:16 +0200 Subject: [PATCH 2/5] Register search capabilities per language --- crates/biome_cli/src/execute/traverse.rs | 3 +- crates/biome_css_formatter/tests/language.rs | 13 ++--- crates/biome_css_parser/src/lib.rs | 14 ++++- crates/biome_graphql_parser/src/lib.rs | 14 ++++- crates/biome_grit_patterns/src/diagnostics.rs | 2 +- .../biome_grit_patterns/src/grit_context.rs | 55 +++++++++++++------ .../biome_grit_patterns/src/grit_js_parser.rs | 21 ++++++- crates/biome_grit_patterns/src/grit_query.rs | 3 +- .../src/grit_target_language.rs | 16 +++++- crates/biome_grit_patterns/src/variables.rs | 2 +- .../biome_grit_patterns/tests/quick_test.rs | 13 +++-- crates/biome_js_formatter/tests/language.rs | 8 +-- crates/biome_js_parser/src/parse.rs | 14 ++++- crates/biome_json_formatter/tests/language.rs | 4 +- crates/biome_json_parser/src/lib.rs | 14 ++++- crates/biome_parser/src/lib.rs | 2 +- crates/biome_rowan/src/syntax/node.rs | 2 +- .../biome_service/src/file_handlers/astro.rs | 12 ++-- crates/biome_service/src/file_handlers/css.rs | 11 +--- .../src/file_handlers/graphql.rs | 13 ++--- .../src/file_handlers/javascript.rs | 14 ++--- .../biome_service/src/file_handlers/json.rs | 13 ++--- crates/biome_service/src/file_handlers/mod.rs | 45 +++++++++++++++ .../biome_service/src/file_handlers/svelte.rs | 12 ++-- crates/biome_service/src/file_handlers/vue.rs | 12 ++-- crates/biome_service/src/workspace.rs | 12 +++- crates/biome_service/src/workspace/server.rs | 48 +++++++--------- 27 files changed, 251 insertions(+), 141 deletions(-) diff --git a/crates/biome_cli/src/execute/traverse.rs b/crates/biome_cli/src/execute/traverse.rs index a6ca646fcc45..7d12a6330884 100644 --- a/crates/biome_cli/src/execute/traverse.rs +++ b/crates/biome_cli/src/execute/traverse.rs @@ -584,7 +584,7 @@ impl<'ctx, 'app> TraversalContext for TraversalOptions<'ctx, 'app> { TraversalMode::Lint { .. } => file_features.supports_lint(), // Imagine if Biome can't handle its own configuration file... TraversalMode::Migrate { .. } => true, - TraversalMode::Search { .. } => true, + TraversalMode::Search { .. } => file_features.supports_search(), } } @@ -614,7 +614,6 @@ fn handle_file(ctx: &TraversalOptions, path: &Path) { } Ok(Ok(FileStatus::Ignored)) => {} Ok(Err(err)) => { - println!("Oh no! {err:?}"); ctx.increment_unchanged(); ctx.skipped.fetch_add(1, Ordering::Relaxed); ctx.push_message(err); diff --git a/crates/biome_css_formatter/tests/language.rs b/crates/biome_css_formatter/tests/language.rs index 0dfa81319a6b..c378aab61c39 100644 --- a/crates/biome_css_formatter/tests/language.rs +++ b/crates/biome_css_formatter/tests/language.rs @@ -19,14 +19,11 @@ impl TestFormatLanguage for CssTestFormatLanguage { type FormatLanguage = CssFormatLanguage; fn parse(&self, text: &str) -> AnyParse { - let parse = parse_css( - text, - CssParserOptions::default() - .allow_wrong_line_comments() - .allow_css_modules(), - ); - - AnyParse::new(parse.syntax().as_send().unwrap(), parse.into_diagnostics()) + let options = CssParserOptions::default() + .allow_wrong_line_comments() + .allow_css_modules(); + + parse_css(text, options).into() } fn to_language_settings<'a>( diff --git a/crates/biome_css_parser/src/lib.rs b/crates/biome_css_parser/src/lib.rs index 937d5164936f..255747acdafa 100644 --- a/crates/biome_css_parser/src/lib.rs +++ b/crates/biome_css_parser/src/lib.rs @@ -6,7 +6,7 @@ use crate::syntax::parse_root; use biome_css_factory::CssSyntaxFactory; use biome_css_syntax::{CssLanguage, CssRoot, CssSyntaxNode}; pub use biome_parser::prelude::*; -use biome_parser::tree_sink::LosslessTreeSink; +use biome_parser::{tree_sink::LosslessTreeSink, AnyParse}; use biome_rowan::{AstNode, NodeCache}; pub use parser::CssParserOptions; @@ -107,6 +107,18 @@ impl CssParse { } } +impl From for AnyParse { + fn from(parse: CssParse) -> Self { + let root = parse.syntax(); + let diagnostics = parse.into_diagnostics(); + Self::new( + // SAFETY: the parser should always return a root node + root.as_send().unwrap(), + diagnostics, + ) + } +} + #[cfg(test)] mod tests { use crate::{parse_css, CssParserOptions}; diff --git a/crates/biome_graphql_parser/src/lib.rs b/crates/biome_graphql_parser/src/lib.rs index 6409c3931d27..b459c78fd2cb 100644 --- a/crates/biome_graphql_parser/src/lib.rs +++ b/crates/biome_graphql_parser/src/lib.rs @@ -3,7 +3,7 @@ use biome_graphql_factory::GraphqlSyntaxFactory; use biome_graphql_syntax::{GraphqlLanguage, GraphqlRoot, GraphqlSyntaxNode}; pub use biome_parser::prelude::*; -use biome_parser::tree_sink::LosslessTreeSink; +use biome_parser::{tree_sink::LosslessTreeSink, AnyParse}; use biome_rowan::{AstNode, NodeCache}; use parser::{parse_root, GraphqlParser}; @@ -96,6 +96,18 @@ impl GraphqlParse { } } +impl From for AnyParse { + fn from(parse: GraphqlParse) -> Self { + let root = parse.syntax(); + let diagnostics = parse.into_diagnostics(); + Self::new( + // SAFETY: the parser should always return a root node + root.as_send().unwrap(), + diagnostics, + ) + } +} + #[cfg(test)] mod tests { use crate::parse_graphql; diff --git a/crates/biome_grit_patterns/src/diagnostics.rs b/crates/biome_grit_patterns/src/diagnostics.rs index cd496022316c..4f24bb38763b 100644 --- a/crates/biome_grit_patterns/src/diagnostics.rs +++ b/crates/biome_grit_patterns/src/diagnostics.rs @@ -1,7 +1,7 @@ use biome_diagnostics::Diagnostic; use biome_rowan::TextRange; -#[derive(Debug, Diagnostic)] +#[derive(Clone, Debug, Diagnostic)] #[diagnostic(severity = Warning)] pub struct CompilerDiagnostic { #[message] diff --git a/crates/biome_grit_patterns/src/grit_context.rs b/crates/biome_grit_patterns/src/grit_context.rs index 32c896be7d0a..073aeab13369 100644 --- a/crates/biome_grit_patterns/src/grit_context.rs +++ b/crates/biome_grit_patterns/src/grit_context.rs @@ -7,6 +7,7 @@ use crate::grit_target_language::GritTargetLanguage; use crate::grit_target_node::GritTargetNode; use crate::grit_tree::GritTargetTree; use anyhow::{anyhow, bail, Result}; +use biome_parser::AnyParse; use grit_pattern_matcher::constants::{GLOBAL_VARS_SCOPE_INDEX, NEW_FILES_INDEX}; use grit_pattern_matcher::context::{ExecContext, QueryContext}; use grit_pattern_matcher::file_owners::{FileOwner, FileOwners}; @@ -184,20 +185,13 @@ impl<'a> ExecContext<'a, GritQueryContext> for GritExecContext<'a> { .body(&state.files) .text(&state.files, &self.lang) .unwrap(); - let owned_file = file_owner_from_matches( - name.clone(), - &body, - None, - FileOrigin::New, - &self.lang, - logs, - )? - .ok_or_else(|| { - anyhow!( - "failed to construct new file for file {}", - name.to_string_lossy() - ) - })?; + let owned_file = + new_file_owner(name.clone(), &body, &self.lang, logs)?.ok_or_else(|| { + anyhow!( + "failed to construct new file for file {}", + name.to_string_lossy() + ) + })?; self.files().push(owned_file); let _ = state.files.push_new_file(self.files().last().unwrap()); } @@ -233,7 +227,7 @@ impl<'a> ExecContext<'a, GritQueryContext> for GritExecContext<'a> { let file = file_owner_from_matches( &file.path, - &file.content, + &file.parse, None, FileOrigin::Fresh, &self.lang, @@ -251,7 +245,7 @@ impl<'a> ExecContext<'a, GritQueryContext> for GritExecContext<'a> { fn file_owner_from_matches( name: impl Into, - source: &str, + parse: &AnyParse, matches: Option, old_tree: FileOrigin<'_, GritTargetTree>, language: &GritTargetLanguage, @@ -262,7 +256,7 @@ fn file_owner_from_matches( let Some(tree) = language .get_parser() - .parse_file(source, Some(&name), logs, old_tree) + .from_cached_parse_result(parse, Some(&name), logs) else { return Ok(None); }; @@ -277,6 +271,31 @@ fn file_owner_from_matches( })) } +fn new_file_owner( + name: impl Into, + source: &str, + language: &GritTargetLanguage, + logs: &mut AnalysisLogs, +) -> Result>> { + let name = name.into(); + + let Some(tree) = language + .get_parser() + .parse_file(source, Some(&name), logs, FileOrigin::New) + else { + return Ok(None); + }; + + let absolute_path = name.absolutize()?.to_path_buf(); + Ok(Some(FileOwner { + name, + absolute_path, + tree, + matches: Default::default(), + new: true, + })) +} + /// Simple wrapper for target files so that we can avoid doing file I/O inside /// the Grit engine. /// @@ -286,5 +305,5 @@ fn file_owner_from_matches( #[derive(Clone, Debug)] pub struct GritTargetFile { pub path: PathBuf, - pub content: String, + pub parse: AnyParse, } diff --git a/crates/biome_grit_patterns/src/grit_js_parser.rs b/crates/biome_grit_patterns/src/grit_js_parser.rs index 92fbfc5fcb5d..87b85004650e 100644 --- a/crates/biome_grit_patterns/src/grit_js_parser.rs +++ b/crates/biome_grit_patterns/src/grit_js_parser.rs @@ -1,11 +1,30 @@ -use crate::{grit_analysis_ext::GritAnalysisExt, grit_tree::GritTargetTree}; +use crate::{ + grit_analysis_ext::GritAnalysisExt, grit_target_language::GritTargetParser, + grit_tree::GritTargetTree, +}; use biome_js_parser::{parse, JsParserOptions}; use biome_js_syntax::JsFileSource; +use biome_parser::AnyParse; use grit_util::{AnalysisLogs, FileOrigin, Parser, SnippetTree}; use std::path::Path; pub struct GritJsParser; +impl GritTargetParser for GritJsParser { + fn from_cached_parse_result( + &self, + parse: &AnyParse, + path: Option<&Path>, + logs: &mut AnalysisLogs, + ) -> Option { + for diagnostic in parse.diagnostics() { + logs.push(diagnostic.to_log(path)); + } + + Some(GritTargetTree::new(parse.syntax().into())) + } +} + impl Parser for GritJsParser { type Tree = GritTargetTree; diff --git a/crates/biome_grit_patterns/src/grit_query.rs b/crates/biome_grit_patterns/src/grit_query.rs index 3972b6077f39..118e60a311fe 100644 --- a/crates/biome_grit_patterns/src/grit_query.rs +++ b/crates/biome_grit_patterns/src/grit_query.rs @@ -36,6 +36,7 @@ const GLOBAL_VARS: [(&str, usize); 4] = [ /// Represents a top-level Grit query. /// /// Grit queries provide the +#[derive(Clone, Debug)] pub struct GritQuery { pub pattern: Pattern, @@ -111,8 +112,6 @@ impl GritQuery { .collect(); let mut diagnostics = Vec::new(); - // Make sure - // We're not in a local scope yet, so this map is kinda useless. // It's just there because all node compilers expect one. let mut vars = BTreeMap::new(); diff --git a/crates/biome_grit_patterns/src/grit_target_language.rs b/crates/biome_grit_patterns/src/grit_target_language.rs index 3aff9b527ab4..d333c1cc3014 100644 --- a/crates/biome_grit_patterns/src/grit_target_language.rs +++ b/crates/biome_grit_patterns/src/grit_target_language.rs @@ -1,13 +1,15 @@ mod js_target_language; +use biome_parser::AnyParse; pub use js_target_language::JsTargetLanguage; use crate::grit_js_parser::GritJsParser; use crate::grit_target_node::{GritTargetNode, GritTargetSyntaxKind}; use crate::grit_tree::GritTargetTree; use biome_rowan::SyntaxKind; -use grit_util::{Ast, CodeRange, EffectRange, Language, Parser, SnippetTree}; +use grit_util::{AnalysisLogs, Ast, CodeRange, EffectRange, Language, Parser, SnippetTree}; use std::borrow::Cow; +use std::path::Path; /// Generates the `GritTargetLanguage` enum. /// @@ -35,7 +37,7 @@ macro_rules! generate_target_language { } } - pub fn get_parser(&self) -> Box> { + pub fn get_parser(&self) -> Box { match self { $(Self::$language(_) => Box::new($parser)),+ } @@ -243,3 +245,13 @@ trait GritTargetLanguageImpl { false } } + +pub trait GritTargetParser: Parser { + #[allow(clippy::wrong_self_convention)] + fn from_cached_parse_result( + &self, + parse: &AnyParse, + path: Option<&Path>, + logs: &mut AnalysisLogs, + ) -> Option; +} diff --git a/crates/biome_grit_patterns/src/variables.rs b/crates/biome_grit_patterns/src/variables.rs index 311ba13ece15..aaf49ffa05d4 100644 --- a/crates/biome_grit_patterns/src/variables.rs +++ b/crates/biome_grit_patterns/src/variables.rs @@ -9,7 +9,7 @@ use im::{vector, Vector}; /// to separate scopes, while the inner vector contains the variables. For each /// variable, we track the separate locations (plural) where that variable /// occurs. -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct VariableLocations(Vec>); impl VariableLocations { diff --git a/crates/biome_grit_patterns/tests/quick_test.rs b/crates/biome_grit_patterns/tests/quick_test.rs index c96fdc242967..7c85bf2263a2 100644 --- a/crates/biome_grit_patterns/tests/quick_test.rs +++ b/crates/biome_grit_patterns/tests/quick_test.rs @@ -1,5 +1,7 @@ use biome_grit_parser::parse_grit; use biome_grit_patterns::{GritQuery, GritTargetFile, GritTargetLanguage, JsTargetLanguage}; +use biome_js_parser::{parse, JsParserOptions}; +use biome_js_syntax::JsFileSource; // Use this test to quickly execute a Grit query against a source snippet. #[ignore] @@ -24,15 +26,16 @@ fn test_query() { println!("Diagnostics from compiling query:\n{:?}", query.diagnostics); } - let file = GritTargetFile { - path: "test.js".into(), - content: r#" + let body = r#" function hello() { console .log("hello"); } -"# - .to_string(), +"#; + + 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"); diff --git a/crates/biome_js_formatter/tests/language.rs b/crates/biome_js_formatter/tests/language.rs index 4ad692e77939..4346cd8660e7 100644 --- a/crates/biome_js_formatter/tests/language.rs +++ b/crates/biome_js_formatter/tests/language.rs @@ -25,13 +25,9 @@ impl TestFormatLanguage for JsTestFormatLanguage { type FormatLanguage = JsFormatLanguage; fn parse(&self, text: &str) -> AnyParse { - let parse = parse( - text, - self.source_type, - JsParserOptions::default().with_parse_class_parameter_decorators(), - ); + let options = JsParserOptions::default().with_parse_class_parameter_decorators(); - AnyParse::new(parse.syntax().as_send().unwrap(), parse.into_diagnostics()) + parse(text, self.source_type, options).into() } fn to_language_settings<'a>( diff --git a/crates/biome_js_parser/src/parse.rs b/crates/biome_js_parser/src/parse.rs index 41dd404ae397..f03a143abcde 100644 --- a/crates/biome_js_parser/src/parse.rs +++ b/crates/biome_js_parser/src/parse.rs @@ -4,8 +4,8 @@ use crate::*; use biome_js_syntax::{ AnyJsRoot, JsFileSource, JsLanguage, JsModule, JsScript, JsSyntaxNode, ModuleKind, }; -use biome_parser::event::Event; use biome_parser::token_source::Trivia; +use biome_parser::{event::Event, AnyParse}; use biome_rowan::{AstNode, NodeCache}; use std::marker::PhantomData; @@ -113,6 +113,18 @@ impl> Parse { } } +impl From> for AnyParse { + fn from(parse: Parse) -> Self { + let root = parse.syntax(); + let diagnostics = parse.into_diagnostics(); + Self::new( + // SAFETY: the parser should always return a root node + root.as_send().unwrap(), + diagnostics, + ) + } +} + fn parse_common( text: &str, source_type: JsFileSource, diff --git a/crates/biome_json_formatter/tests/language.rs b/crates/biome_json_formatter/tests/language.rs index 8c911e119c69..498f167379e1 100644 --- a/crates/biome_json_formatter/tests/language.rs +++ b/crates/biome_json_formatter/tests/language.rs @@ -22,9 +22,7 @@ impl TestFormatLanguage for JsonTestFormatLanguage { type FormatLanguage = JsonFormatLanguage; fn parse(&self, text: &str) -> AnyParse { - let parse = parse_json(text, JsonParserOptions::default().with_allow_comments()); - - AnyParse::new(parse.syntax().as_send().unwrap(), parse.into_diagnostics()) + parse_json(text, JsonParserOptions::default().with_allow_comments()).into() } fn to_language_settings<'a>( diff --git a/crates/biome_json_parser/src/lib.rs b/crates/biome_json_parser/src/lib.rs index 8633a92bf964..c1df88182aa0 100644 --- a/crates/biome_json_parser/src/lib.rs +++ b/crates/biome_json_parser/src/lib.rs @@ -5,7 +5,7 @@ use crate::syntax::parse_root; use biome_json_factory::JsonSyntaxFactory; use biome_json_syntax::{JsonLanguage, JsonRoot, JsonSyntaxNode}; pub use biome_parser::prelude::*; -use biome_parser::tree_sink::LosslessTreeSink; +use biome_parser::{tree_sink::LosslessTreeSink, AnyParse}; use biome_rowan::{AstNode, NodeCache}; pub use parser::JsonParserOptions; @@ -105,3 +105,15 @@ impl JsonParse { JsonRoot::unwrap_cast(self.syntax()) } } + +impl From for AnyParse { + fn from(parse: JsonParse) -> Self { + let root = parse.syntax(); + let diagnostics = parse.into_diagnostics(); + Self::new( + // SAFETY: the parser should always return a root node + root.as_send().unwrap(), + diagnostics, + ) + } +} diff --git a/crates/biome_parser/src/lib.rs b/crates/biome_parser/src/lib.rs index 88a06e254e22..5c708d83c225 100644 --- a/crates/biome_parser/src/lib.rs +++ b/crates/biome_parser/src/lib.rs @@ -651,7 +651,7 @@ pub trait SyntaxFeature: Sized { /// /// It can be dynamically downcast into a concrete [SyntaxNode] or [AstNode] of /// the corresponding language, generally through a language-specific capability -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct AnyParse { pub(crate) root: SendNode, pub(crate) diagnostics: Vec, diff --git a/crates/biome_rowan/src/syntax/node.rs b/crates/biome_rowan/src/syntax/node.rs index d5238ebd479a..fc7ba81c45a0 100644 --- a/crates/biome_rowan/src/syntax/node.rs +++ b/crates/biome_rowan/src/syntax/node.rs @@ -809,7 +809,7 @@ impl From for SyntaxNode { /// Language-agnostic representation of the root node of a syntax tree, can be /// sent or shared between threads -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct SendNode { language: TypeId, green: GreenNode, diff --git a/crates/biome_service/src/file_handlers/astro.rs b/crates/biome_service/src/file_handlers/astro.rs index e921c83d69e4..b777e76f84cc 100644 --- a/crates/biome_service/src/file_handlers/astro.rs +++ b/crates/biome_service/src/file_handlers/astro.rs @@ -17,6 +17,8 @@ use biome_rowan::NodeCache; use lazy_static::lazy_static; use regex::{Matches, Regex, RegexBuilder}; +use super::SearchCapabilities; + #[derive(Debug, Default, PartialEq, Eq)] pub struct AstroFileHandler; @@ -86,6 +88,8 @@ impl ExtensionHandler for AstroFileHandler { format_range: Some(format_range), format_on_type: Some(format_on_type), }, + // TODO: We should be able to search JS portions already + search: SearchCapabilities { search_file: None }, } } } @@ -106,15 +110,9 @@ fn parse( JsParserOptions::default(), cache, ); - let root = parse.syntax(); - let diagnostics = parse.into_diagnostics(); ParseResult { - any_parse: AnyParse::new( - // SAFETY: the parser should always return a root node - root.as_send().unwrap(), - diagnostics, - ), + any_parse: parse.into(), language: Some(JsFileSource::astro().into()), } } diff --git a/crates/biome_service/src/file_handlers/css.rs b/crates/biome_service/src/file_handlers/css.rs index be33cf500130..7eb9f24b566c 100644 --- a/crates/biome_service/src/file_handlers/css.rs +++ b/crates/biome_service/src/file_handlers/css.rs @@ -1,6 +1,6 @@ use super::{ is_diagnostic_error, CodeActionsParams, ExtensionHandler, FixAllParams, LintParams, - LintResults, ParseResult, + LintResults, ParseResult, SearchCapabilities, }; use crate::configuration::to_analyzer_rules; use crate::file_handlers::DebugCapabilities; @@ -204,6 +204,7 @@ impl ExtensionHandler for CssFileHandler { format_range: Some(format_range), format_on_type: Some(format_on_type), }, + search: SearchCapabilities { search_file: None }, } } } @@ -229,14 +230,8 @@ fn parse( .to_override_css_parser_options(biome_path, options); } let parse = biome_css_parser::parse_css_with_cache(text, cache, options); - let root = parse.syntax(); - let diagnostics = parse.into_diagnostics(); ParseResult { - any_parse: AnyParse::new( - // SAFETY: the parser should always return a root node - root.as_send().unwrap(), - diagnostics, - ), + any_parse: parse.into(), language: None, } } diff --git a/crates/biome_service/src/file_handlers/graphql.rs b/crates/biome_service/src/file_handlers/graphql.rs index c4af8f46ef39..352c43ffdd9b 100644 --- a/crates/biome_service/src/file_handlers/graphql.rs +++ b/crates/biome_service/src/file_handlers/graphql.rs @@ -1,4 +1,6 @@ -use super::{DocumentFileSource, ExtensionHandler, LintParams, LintResults, ParseResult}; +use super::{ + DocumentFileSource, ExtensionHandler, LintParams, LintResults, ParseResult, SearchCapabilities, +}; use crate::file_handlers::DebugCapabilities; use crate::file_handlers::{ AnalyzerCapabilities, Capabilities, FormatterCapabilities, ParserCapabilities, @@ -77,6 +79,7 @@ impl ExtensionHandler for GraphqlFileHandler { format_range: None, format_on_type: None, }, + search: SearchCapabilities { search_file: None }, } } } @@ -89,15 +92,9 @@ fn parse( cache: &mut NodeCache, ) -> ParseResult { let parse = parse_graphql_with_cache(text, cache); - let root = parse.syntax(); - let diagnostics = parse.into_diagnostics(); ParseResult { - any_parse: AnyParse::new( - // SAFETY: the parser should always return a root node - root.as_send().unwrap(), - diagnostics, - ), + any_parse: parse.into(), language: Some(file_source), } } diff --git a/crates/biome_service/src/file_handlers/javascript.rs b/crates/biome_service/src/file_handlers/javascript.rs index febda3f262f6..78827edc54ae 100644 --- a/crates/biome_service/src/file_handlers/javascript.rs +++ b/crates/biome_service/src/file_handlers/javascript.rs @@ -1,6 +1,7 @@ use super::{ - AnalyzerCapabilities, CodeActionsParams, DebugCapabilities, ExtensionHandler, + search_file, AnalyzerCapabilities, CodeActionsParams, DebugCapabilities, ExtensionHandler, FormatterCapabilities, LintParams, LintResults, ParseResult, ParserCapabilities, + SearchCapabilities, }; use crate::configuration::to_analyzer_rules; use crate::diagnostics::extension_error; @@ -289,6 +290,9 @@ impl ExtensionHandler for JsFileHandler { format_range: Some(format_range), format_on_type: Some(format_on_type), }, + search: SearchCapabilities { + search_file: Some(search_file), + }, } } } @@ -319,14 +323,8 @@ fn parse( let file_source = file_source.to_js_file_source().unwrap_or_default(); let parse = biome_js_parser::parse_js_with_cache(text, file_source, options, cache); - let root = parse.syntax(); - let diagnostics = parse.into_diagnostics(); ParseResult { - any_parse: AnyParse::new( - // SAFETY: the parser should always return a root node - root.as_send().unwrap(), - diagnostics, - ), + any_parse: parse.into(), language: None, } } diff --git a/crates/biome_service/src/file_handlers/json.rs b/crates/biome_service/src/file_handlers/json.rs index ef2d5568de45..0d6e8466ca51 100644 --- a/crates/biome_service/src/file_handlers/json.rs +++ b/crates/biome_service/src/file_handlers/json.rs @@ -1,6 +1,8 @@ use std::ffi::OsStr; -use super::{CodeActionsParams, DocumentFileSource, ExtensionHandler, ParseResult}; +use super::{ + CodeActionsParams, DocumentFileSource, ExtensionHandler, ParseResult, SearchCapabilities, +}; use crate::configuration::to_analyzer_rules; use crate::file_handlers::DebugCapabilities; use crate::file_handlers::{ @@ -164,6 +166,7 @@ impl ExtensionHandler for JsonFileHandler { format_range: Some(format_range), format_on_type: Some(format_on_type), }, + search: SearchCapabilities { search_file: None }, } } } @@ -194,15 +197,9 @@ fn parse( options }; let parse = biome_json_parser::parse_json_with_cache(text, cache, options); - let root = parse.syntax(); - let diagnostics = parse.into_diagnostics(); ParseResult { - any_parse: AnyParse::new( - // SAFETY: the parser should always return a root node - root.as_send().unwrap(), - diagnostics, - ), + any_parse: parse.into(), language: Some(file_source), } } diff --git a/crates/biome_service/src/file_handlers/mod.rs b/crates/biome_service/src/file_handlers/mod.rs index 6452a7ca2160..ee91fb4f4680 100644 --- a/crates/biome_service/src/file_handlers/mod.rs +++ b/crates/biome_service/src/file_handlers/mod.rs @@ -2,6 +2,7 @@ use self::{ css::CssFileHandler, javascript::JsFileHandler, json::JsonFileHandler, unknown::UnknownFileHandler, }; +use crate::diagnostics::{QueryError, SearchError}; pub use crate::file_handlers::astro::{AstroFileHandler, ASTRO_FENCE}; use crate::file_handlers::graphql::GraphqlFileHandler; pub use crate::file_handlers::svelte::{SvelteFileHandler, SVELTE_FENCE}; @@ -23,6 +24,7 @@ use biome_diagnostics::{Diagnostic, Severity}; use biome_formatter::Printed; use biome_fs::BiomePath; use biome_graphql_syntax::GraphqlFileSource; +use biome_grit_patterns::{GritQuery, GritQueryResult, GritTargetFile}; use biome_js_parser::{parse, JsParserOptions}; use biome_js_syntax::{EmbeddingKind, JsFileSource, Language, TextRange, TextSize}; use biome_json_syntax::JsonFileSource; @@ -354,6 +356,7 @@ pub struct Capabilities { pub(crate) debug: DebugCapabilities, pub(crate) analyzer: AnalyzerCapabilities, pub(crate) formatter: FormatterCapabilities, + pub(crate) search: SearchCapabilities, } #[derive(Clone)] @@ -469,6 +472,20 @@ pub(crate) struct FormatterCapabilities { pub(crate) format_on_type: Option, } +type Search = fn( + &BiomePath, + &DocumentFileSource, + AnyParse, + &GritQuery, + WorkspaceSettingsHandle, +) -> Result, WorkspaceError>; + +#[derive(Default)] +pub(crate) struct SearchCapabilities { + /// It searches through a file + pub(crate) search_file: Option, +} + /// Main trait to use to add a new language to Biome pub(crate) trait ExtensionHandler { /// Capabilities that can applied to a file @@ -584,6 +601,34 @@ pub(crate) fn parse_lang_from_script_opening_tag(script_opening_tag: &str) -> La .map_or(Language::JavaScript, |lang| lang) } +pub(crate) fn search_file( + path: &BiomePath, + _file_source: &DocumentFileSource, + parse: AnyParse, + query: &GritQuery, + _settings: WorkspaceSettingsHandle, +) -> Result, WorkspaceError> { + let query_result = query + .execute(GritTargetFile { + path: path.to_path_buf(), + parse, + }) + .map_err(|err| { + WorkspaceError::SearchError(SearchError::QueryError(QueryError(err.to_string()))) + })?; + + let matches = query_result + .into_iter() + .flat_map(|result| match result { + GritQueryResult::Match(m) => m.ranges, + _ => Vec::new(), + }) + .map(|range| TextRange::new(range.start_byte.into(), range.end_byte.into())) + .collect(); + + Ok(matches) +} + #[test] fn test_svelte_script_lang() { const SVELTE_JS_SCRIPT_OPENING_TAG: &str = r#"