diff --git a/crates/biome_cli/src/commands/mod.rs b/crates/biome_cli/src/commands/mod.rs index 5323782b6bbd..2f3103ac7468 100644 --- a/crates/biome_cli/src/commands/mod.rs +++ b/crates/biome_cli/src/commands/mod.rs @@ -394,7 +394,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 25274f4923a8..f64cf82c4294 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 { .. } => file_features.supports_search(), } } 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_diagnostics/src/location.rs b/crates/biome_diagnostics/src/location.rs index b4e4c95efe88..52cb73d8eb51 100644 --- a/crates/biome_diagnostics/src/location.rs +++ b/crates/biome_diagnostics/src/location.rs @@ -5,7 +5,7 @@ use std::ops::Range; use std::{borrow::Borrow, ops::Deref}; /// Represents the location of a diagnostic in a resource. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Default, Clone, Copy)] pub struct Location<'a> { /// The resource this diagnostic is associated with. pub resource: Option>, 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/errors.rs b/crates/biome_grit_patterns/src/errors.rs index 7658138f4efb..1b9d47485fef 100644 --- a/crates/biome_grit_patterns/src/errors.rs +++ b/crates/biome_grit_patterns/src/errors.rs @@ -1,31 +1,34 @@ -use biome_diagnostics::serde::Diagnostic as SerializableDiagnostic; -use biome_diagnostics::Diagnostic; +use std::fmt::Debug; + +use biome_console::{fmt::Formatter, markup}; +use biome_diagnostics::Location; +use biome_diagnostics::{category, Category, Diagnostic, LogCategory, Severity}; +use biome_parser::diagnostic::ParseDiagnostic; use biome_rowan::SyntaxError; use grit_util::ByteRange; -use serde::{Deserialize, Serialize}; -#[derive(Debug, Deserialize, Diagnostic, Serialize)] +#[derive(Debug, Diagnostic)] #[diagnostic( category = "parse", severity = Error, message = "Error(s) parsing pattern", )] pub struct ParsePatternError { - diagnostics: Vec, + pub diagnostics: Vec, } -#[derive(Debug, Deserialize, Diagnostic, Serialize)] +#[derive(Debug, Diagnostic)] #[diagnostic( category = "parse", severity = Error, message = "Error(s) parsing pattern snippet", )] pub struct ParseSnippetError { - diagnostics: Vec, + diagnostics: Vec, } // TODO: We definitely need to improve diagnostics. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug)] pub enum CompileError { /// Indicates the (top-level) pattern could not be parsed. ParsePatternError(ParsePatternError), @@ -77,7 +80,116 @@ pub enum CompileError { UnknownVariable(String), } -impl Diagnostic for CompileError {} +impl Diagnostic for CompileError { + fn category(&self) -> Option<&'static Category> { + Some(category!("parse")) + } + + fn message(&self, fmt: &mut Formatter<'_>) -> std::io::Result<()> { + match self { + CompileError::ParsePatternError(error) => { + fmt.write_markup(markup! { "Error parsing pattern" })?; + match error.diagnostics.first() { + Some(diag) => { + fmt.write_str(": ")?; + diag.message(fmt) + } + None => Ok(()), + } + } + CompileError::ParseSnippetError(error) => { + fmt.write_markup(markup! { "Error parsing snippet" })?; + match error.diagnostics.first() { + Some(diag) => { + fmt.write_str(": ")?; + diag.message(fmt) + } + None => Ok(()), + } + } + CompileError::MissingSyntaxNode => { + fmt.write_markup(markup! { "A syntax node was missing" }) + } + CompileError::DuplicateParameters => { + fmt.write_markup(markup! { "Duplicate parameters" }) + } + CompileError::InvalidMetavariableRange(_) => { + fmt.write_markup(markup! { "Invalid range for metavariable" }) + } + CompileError::MetavariableNotFound(var) => { + fmt.write_markup(markup! { "Metavariable not found: "{{var}} }) + } + CompileError::ReservedMetavariable(var) => { + fmt.write_markup(markup! { "Reserved metavariable: "{{var}} }) + } + CompileError::UnsupportedKind(kind) => { + fmt.write_markup(markup! { "Unsupported syntax kind ("{{kind}}")" }) + } + CompileError::UnexpectedKind(kind) => { + fmt.write_markup(markup! { "Unexpected syntax kind ("{{kind}}")" }) + } + CompileError::UnknownFunctionOrPattern(name) => { + fmt.write_markup(markup! { "Unknown function or pattern: "{{name}} }) + } + CompileError::LiteralOutOfRange(value) => { + fmt.write_markup(markup! { "Literal value out of range: "{{value}} }) + } + CompileError::MissingPattern => fmt.write_markup(markup! { "Missing pattern" }), + CompileError::InvalidBracketedMetavariable => { + fmt.write_markup(markup! { "Invalid bracketed metavariable" }) + } + CompileError::FunctionArgument(_) => { + fmt.write_markup(markup! { "Invalid function argument" }) + } + CompileError::UnknownFunctionOrPredicate(name) => { + fmt.write_markup(markup! { "Unknown function or predicate: "{{name}} }) + } + CompileError::UnknownVariable(var) => { + fmt.write_markup(markup! { "Unknown variable: "{{var}} }) + } + } + } + + fn location(&self) -> Location<'_> { + match self { + CompileError::ParsePatternError(error) => error + .diagnostics + .first() + .map(Diagnostic::location) + .unwrap_or_default(), + _ => Location::default(), + } + } + + fn description(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CompileError::ParsePatternError(error) => match error.diagnostics.first() { + Some(diag) => diag.description(fmt), + None => Ok(()), + }, + CompileError::ParseSnippetError(error) => match error.diagnostics.first() { + Some(diag) => diag.description(fmt), + None => Ok(()), + }, + CompileError::FunctionArgument(error) => error.fmt(fmt), + _ => Ok(()), + } + } + + fn advices(&self, visitor: &mut dyn biome_diagnostics::Visit) -> std::io::Result<()> { + match self { + CompileError::ReservedMetavariable(_) => visitor.record_log( + LogCategory::Info, + &markup! { "Try using a different variable name" }.to_owned(), + ), + _ => Ok(()), + } + } + + fn severity(&self) -> Severity { + Severity::Error + } +} impl From for CompileError { fn from(error: SyntaxError) -> Self { @@ -93,7 +205,7 @@ impl From for CompileError { } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug)] pub enum NodeLikeArgumentError { /// Duplicate arguments in invocation. DuplicateArguments { name: String }, 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..846593fefcc1 100644 --- a/crates/biome_grit_patterns/src/grit_context.rs +++ b/crates/biome_grit_patterns/src/grit_context.rs @@ -6,13 +6,17 @@ 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 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}; 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 +37,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 +53,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 +106,100 @@ 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)? + .as_ref() + .into(); + let body = file.body(&state.files).text(&state.files, &self.lang)?; + 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); + // SAFETY: We just pushed to the list of files, so there must be one. + 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 +222,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.parse, + None, + FileOrigin::Fresh, + &self.lang, + logs, + )?; if let Some(file) = file { self.files.push(file); state.files.load_file(ptr, self.files.last().unwrap()); @@ -139,6 +241,34 @@ impl<'a> ExecContext<'a, GritQueryContext> for GritExecContext<'a> { } fn file_owner_from_matches( + name: impl Into, + parse: &AnyParse, + 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() + .from_cached_parse_result(parse, Some(&name), logs) + else { + return Ok(None); + }; + + let absolute_path = name.absolutize()?.to_path_buf(); + Ok(Some(FileOwner { + name, + absolute_path, + tree, + matches: matches.unwrap_or_default().into(), + new, + })) +} + +fn new_file_owner( name: impl Into, source: &str, language: &GritTargetLanguage, @@ -148,7 +278,7 @@ fn file_owner_from_matches( let Some(tree) = language .get_parser() - .parse_file(source, Some(&name), logs, FileOrigin::Fresh) + .parse_file(source, Some(&name), logs, FileOrigin::New) else { return Ok(None); }; @@ -159,7 +289,7 @@ fn file_owner_from_matches( absolute_path, tree, matches: Default::default(), - new: false, + new: true, })) } @@ -172,5 +302,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 d7c3a936aeb4..118e60a311fe 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. @@ -36,12 +36,16 @@ 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, /// 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 +58,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 +84,6 @@ impl GritQuery { results.push(result) } } - results.extend(results_from_bindings_history( - &files[0].path, - &state, - &self.language, - )); } Ok(results) @@ -87,16 +91,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::>()]; @@ -123,11 +129,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 +334,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 cf382c861aa1..8aea54ffc3ef 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/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/lib.rs b/crates/biome_grit_patterns/src/lib.rs index f51073f8d5d1..f18dbb3a79d3 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,9 +29,15 @@ 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); + if parsed.has_errors() { + return Err(CompileError::ParsePatternError(ParsePatternError { + diagnostics: parsed.into_diagnostics(), + })); + } + GritQuery::from_node(parsed.tree(), path, language) } 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..6eec30001ed8 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())); @@ -468,8 +468,8 @@ fn text_to_var( GritMetaValue::Variable(name) => { let range = *range_map .get(&range) - .ok_or_else(|| CompileError::InvalidMetavariableRange(range))?; - let var = context.register_variable(name, range + context_range.start)?; + .ok_or(CompileError::InvalidMetavariableRange(range))?; + 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..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 { @@ -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..8fb094213186 100644 --- a/crates/biome_grit_patterns/tests/quick_test.rs +++ b/crates/biome_grit_patterns/tests/quick_test.rs @@ -1,14 +1,13 @@ -use std::path::Path; - 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] #[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 +17,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"); @@ -27,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 84be32545503..62a4eb170251 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/diagnostics.rs b/crates/biome_service/src/diagnostics.rs index 86d4df5d2aff..4f67d8bf09e9 100644 --- a/crates/biome_service/src/diagnostics.rs +++ b/crates/biome_service/src/diagnostics.rs @@ -4,12 +4,13 @@ use biome_configuration::diagnostics::{ConfigurationDiagnostic, EditorConfigDiag use biome_configuration::{BiomeDiagnostic, CantLoadExtendFile}; use biome_console::fmt::Bytes; use biome_console::markup; +use biome_diagnostics::serde::Diagnostic as SerdeDiagnostic; use biome_diagnostics::{ category, Advices, Category, Diagnostic, DiagnosticTags, Location, LogCategory, Severity, Visit, }; use biome_formatter::{FormatError, PrintError}; use biome_fs::{BiomePath, FileSystemDiagnostic}; -use biome_grit_patterns::CompileError; +use biome_grit_patterns::{CompileError, ParsePatternError}; use biome_js_analyze::utils::rename::RenameError; use serde::{Deserialize, Serialize}; use std::error::Error; @@ -323,12 +324,19 @@ impl Diagnostic for SourceFileNotSupported { } } -#[derive(Debug, Serialize, Deserialize, Diagnostic)] +#[derive(Debug, Deserialize, Diagnostic, Serialize)] pub enum SearchError { /// An invalid pattern was given - PatternCompilationError(CompileError), + PatternCompilationError(PatternCompilationError), /// No pattern with the given ID InvalidPattern(InvalidPattern), + /// Error while executing the search query. + QueryError(QueryDiagnostic), +} + +#[derive(Debug, Deserialize, Diagnostic, Serialize)] +pub struct PatternCompilationError { + pub diagnostics: Vec, } #[derive(Debug, Serialize, Deserialize, Diagnostic)] @@ -341,6 +349,34 @@ pub enum SearchError { )] pub struct InvalidPattern; +#[derive(Debug, Deserialize, Serialize)] +pub struct QueryDiagnostic(pub String); + +impl Diagnostic for QueryDiagnostic { + fn category(&self) -> Option<&'static Category> { + Some(category!("search")) + } + + fn severity(&self) -> Severity { + Severity::Error + } + + fn message(&self, fmt: &mut biome_console::fmt::Formatter<'_>) -> std::io::Result<()> { + fmt.write_str("Error executing the Grit query") + } + + fn description(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt.write_str(&self.0) + } + + fn verbose_advices(&self, visitor: &mut dyn Visit) -> std::io::Result<()> { + visitor.record_log( + LogCategory::Info, + &markup! { "Please consult ""the official grit syntax page""." } + ) + } +} + pub fn extension_error(path: &BiomePath) -> WorkspaceError { let file_source = DocumentFileSource::from_path(path); WorkspaceError::source_file_not_supported( @@ -427,7 +463,23 @@ impl From for WorkspaceError { impl From for WorkspaceError { fn from(value: CompileError) -> Self { - Self::SearchError(SearchError::PatternCompilationError(value)) + match &value { + CompileError::ParsePatternError(ParsePatternError { diagnostics }) => { + Self::SearchError(SearchError::PatternCompilationError( + PatternCompilationError { + diagnostics: diagnostics + .iter() + .cloned() + .map(SerdeDiagnostic::new) + .collect(), + }, + )) + } + // FIXME: This really needs proper diagnostics + _ => Self::SearchError(SearchError::QueryError(QueryDiagnostic(format!( + "{value:?}" + )))), + } } } diff --git a/crates/biome_service/src/file_handlers/astro.rs b/crates/biome_service/src/file_handlers/astro.rs index e921c83d69e4..bab20b8e54dd 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: 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 8c97c0bb76f4..2812513133a3 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; @@ -202,6 +202,7 @@ impl ExtensionHandler for CssFileHandler { format_range: Some(format_range), format_on_type: Some(format_on_type), }, + search: SearchCapabilities { search: None }, } } } @@ -227,14 +228,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 fd774df29e86..358403fff43b 100644 --- a/crates/biome_service/src/file_handlers/graphql.rs +++ b/crates/biome_service/src/file_handlers/graphql.rs @@ -1,6 +1,6 @@ use super::{ is_diagnostic_error, CodeActionsParams, DocumentFileSource, ExtensionHandler, FixAllParams, - LintParams, LintResults, ParseResult, + LintParams, LintResults, ParseResult, SearchCapabilities, }; use crate::file_handlers::DebugCapabilities; use crate::file_handlers::{ @@ -173,6 +173,7 @@ impl ExtensionHandler for GraphqlFileHandler { format_range: Some(format_range), format_on_type: Some(format_on_type), }, + search: SearchCapabilities { search: None }, } } } @@ -185,15 +186,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 b420e4cd8934..fbf78334023e 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, AnalyzerCapabilities, CodeActionsParams, DebugCapabilities, ExtensionHandler, FormatterCapabilities, LintParams, LintResults, ParseResult, ParserCapabilities, + SearchCapabilities, }; use crate::configuration::to_analyzer_rules; use crate::diagnostics::extension_error; @@ -293,6 +294,9 @@ impl ExtensionHandler for JsFileHandler { format_range: Some(format_range), format_on_type: Some(format_on_type), }, + search: SearchCapabilities { + search: Some(search), + }, } } } @@ -323,14 +327,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 4def0ca460d6..a128476e92da 100644 --- a/crates/biome_service/src/file_handlers/json.rs +++ b/crates/biome_service/src/file_handlers/json.rs @@ -1,7 +1,9 @@ use std::borrow::Cow; 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::{ @@ -167,6 +169,7 @@ impl ExtensionHandler for JsonFileHandler { format_range: Some(format_range), format_on_type: Some(format_on_type), }, + search: SearchCapabilities { search: None }, } } } @@ -197,15 +200,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 96fb667905f4..720fd701aee4 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::{QueryDiagnostic, 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; @@ -349,6 +351,7 @@ pub struct Capabilities { pub(crate) debug: DebugCapabilities, pub(crate) analyzer: AnalyzerCapabilities, pub(crate) formatter: FormatterCapabilities, + pub(crate) search: SearchCapabilities, } #[derive(Clone)] @@ -464,6 +467,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: Option, +} + /// Main trait to use to add a new language to Biome pub(crate) trait ExtensionHandler { /// Capabilities that can applied to a file @@ -579,6 +596,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( + 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(QueryDiagnostic(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#"