diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css b/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css index 49cf369ab780..0a590f6c82e8 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css +++ b/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css @@ -1,15 +1,15 @@ .foo { - color: μcolor; + color: µcolor; } .foo { - μbar + µbar } .foo { - @media μbaz {} + @media µbaz {} } -μqux {} +µqux {} -μ... {} \ No newline at end of file +µ... {} \ No newline at end of file diff --git a/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css.snap b/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css.snap index 3a5b59789418..e27b6b403854 100644 --- a/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css.snap +++ b/crates/biome_css_parser/tests/css_test_suite/ok/grit_metavariable/metavar.css.snap @@ -6,20 +6,20 @@ expression: snapshot ```css .foo { - color: μcolor; + color: µcolor; } .foo { - μbar + µbar } .foo { - @media μbaz {} + @media µbaz {} } -μqux {} +µqux {} -μ... {} +µ... {} ``` @@ -56,7 +56,7 @@ CssRoot { colon_token: COLON@16..18 ":" [] [Whitespace(" ")], value: CssGenericComponentValueList [ CssMetavariable { - value_token: GRIT_METAVARIABLE@18..25 "μcolor" [] [], + value_token: GRIT_METAVARIABLE@18..25 "µcolor" [] [], }, ], }, @@ -87,7 +87,7 @@ CssRoot { l_curly_token: L_CURLY@35..36 "{" [] [], items: CssDeclarationOrRuleList [ CssMetavariable { - value_token: GRIT_METAVARIABLE@36..46 "μbar" [Newline("\n"), Whitespace(" ")] [], + value_token: GRIT_METAVARIABLE@36..46 "µbar" [Newline("\n"), Whitespace(" ")] [], }, ], r_curly_token: R_CURLY@46..48 "}" [Newline("\n")] [], @@ -117,7 +117,7 @@ CssRoot { media_token: MEDIA_KW@62..68 "media" [] [Whitespace(" ")], queries: CssMediaQueryList [ CssMetavariable { - value_token: GRIT_METAVARIABLE@68..74 "μbaz" [] [Whitespace(" ")], + value_token: GRIT_METAVARIABLE@68..74 "µbaz" [] [Whitespace(" ")], }, ], block: CssDeclarationOrRuleBlock { @@ -134,7 +134,7 @@ CssRoot { CssQualifiedRule { prelude: CssSelectorList [ CssMetavariable { - value_token: GRIT_METAVARIABLE@78..86 "μqux" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + value_token: GRIT_METAVARIABLE@78..86 "µqux" [Newline("\n"), Newline("\n")] [Whitespace(" ")], }, ], block: CssDeclarationOrRuleBlock { @@ -146,7 +146,7 @@ CssRoot { CssQualifiedRule { prelude: CssSelectorList [ CssMetavariable { - value_token: GRIT_METAVARIABLE@88..96 "μ..." [Newline("\n"), Newline("\n")] [Whitespace(" ")], + value_token: GRIT_METAVARIABLE@88..96 "µ..." [Newline("\n"), Newline("\n")] [Whitespace(" ")], }, ], block: CssDeclarationOrRuleBlock { @@ -187,7 +187,7 @@ CssRoot { 1: COLON@16..18 ":" [] [Whitespace(" ")] 2: CSS_GENERIC_COMPONENT_VALUE_LIST@18..25 0: CSS_METAVARIABLE@18..25 - 0: GRIT_METAVARIABLE@18..25 "μcolor" [] [] + 0: GRIT_METAVARIABLE@18..25 "µcolor" [] [] 1: (empty) 1: SEMICOLON@25..26 ";" [] [] 2: R_CURLY@26..28 "}" [Newline("\n")] [] @@ -205,7 +205,7 @@ CssRoot { 0: L_CURLY@35..36 "{" [] [] 1: CSS_DECLARATION_OR_RULE_LIST@36..46 0: CSS_METAVARIABLE@36..46 - 0: GRIT_METAVARIABLE@36..46 "μbar" [Newline("\n"), Whitespace(" ")] [] + 0: GRIT_METAVARIABLE@36..46 "µbar" [Newline("\n"), Whitespace(" ")] [] 2: R_CURLY@46..48 "}" [Newline("\n")] [] 2: CSS_QUALIFIED_RULE@48..78 0: CSS_SELECTOR_LIST@48..55 @@ -226,7 +226,7 @@ CssRoot { 0: MEDIA_KW@62..68 "media" [] [Whitespace(" ")] 1: CSS_MEDIA_QUERY_LIST@68..74 0: CSS_METAVARIABLE@68..74 - 0: GRIT_METAVARIABLE@68..74 "μbaz" [] [Whitespace(" ")] + 0: GRIT_METAVARIABLE@68..74 "µbaz" [] [Whitespace(" ")] 2: CSS_DECLARATION_OR_RULE_BLOCK@74..76 0: L_CURLY@74..75 "{" [] [] 1: CSS_DECLARATION_OR_RULE_LIST@75..75 @@ -235,7 +235,7 @@ CssRoot { 3: CSS_QUALIFIED_RULE@78..88 0: CSS_SELECTOR_LIST@78..86 0: CSS_METAVARIABLE@78..86 - 0: GRIT_METAVARIABLE@78..86 "μqux" [Newline("\n"), Newline("\n")] [Whitespace(" ")] + 0: GRIT_METAVARIABLE@78..86 "µqux" [Newline("\n"), Newline("\n")] [Whitespace(" ")] 1: CSS_DECLARATION_OR_RULE_BLOCK@86..88 0: L_CURLY@86..87 "{" [] [] 1: CSS_DECLARATION_OR_RULE_LIST@87..87 @@ -243,7 +243,7 @@ CssRoot { 4: CSS_QUALIFIED_RULE@88..98 0: CSS_SELECTOR_LIST@88..96 0: CSS_METAVARIABLE@88..96 - 0: GRIT_METAVARIABLE@88..96 "μ..." [Newline("\n"), Newline("\n")] [Whitespace(" ")] + 0: GRIT_METAVARIABLE@88..96 "µ..." [Newline("\n"), Newline("\n")] [Whitespace(" ")] 1: CSS_DECLARATION_OR_RULE_BLOCK@96..98 0: L_CURLY@96..97 "{" [] [] 1: CSS_DECLARATION_OR_RULE_LIST@97..97 diff --git a/crates/biome_css_parser/tests/spec_test.rs b/crates/biome_css_parser/tests/spec_test.rs index e2029c8a41de..0492518ef16c 100644 --- a/crates/biome_css_parser/tests/spec_test.rs +++ b/crates/biome_css_parser/tests/spec_test.rs @@ -174,7 +174,7 @@ pub fn run(test_case: &str, _snapshot_name: &str, test_directory: &str, outcome_ #[test] pub fn quick_test() { let code = r#" -μ... {} +µ... {} "#; let root = parse_css( diff --git a/crates/biome_grit_patterns/src/grit_binding.rs b/crates/biome_grit_patterns/src/grit_binding.rs index f00021e04a01..109fa202a99e 100644 --- a/crates/biome_grit_patterns/src/grit_binding.rs +++ b/crates/biome_grit_patterns/src/grit_binding.rs @@ -2,12 +2,13 @@ use crate::{ grit_context::GritQueryContext, grit_target_language::GritTargetLanguage, grit_target_node::GritTargetNode, source_location_ext::SourceFileExt, util::TextRangeGritExt, }; +use anyhow::bail; use biome_diagnostics::{display::SourceFile, SourceCode}; use biome_rowan::TextRange; use grit_pattern_matcher::{ binding::Binding, constant::Constant, effects::Effect, pattern::FileRegistry, }; -use grit_util::{AnalysisLogs, AstNode, ByteRange, CodeRange, Range}; +use grit_util::{AnalysisLogBuilder, AnalysisLogs, AstNode, ByteRange, CodeRange, Range}; use std::{borrow::Cow, collections::HashMap, path::Path}; #[derive(Clone, Debug, PartialEq)] @@ -50,8 +51,6 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { /// Returns the only node bound by this binding. /// - /// This includes list bindings that only match a single child. - /// /// Returns `None` if the binding has no associated node, or if there is /// more than one associated node. fn singleton(&self) -> Option> { @@ -101,8 +100,25 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { } } - fn is_equivalent_to(&self, _other: &Self, _language: &GritTargetLanguage) -> bool { - todo!() + fn is_equivalent_to(&self, other: &Self, language: &GritTargetLanguage) -> bool { + match self { + Self::Node(node1) => match other { + Self::Node(node2) => are_equivalent(node1, node2), + Self::Range(range, source) => self + .text(language) + .is_ok_and(|t| t == source[range.start().into()..range.end().into()]), + Self::File(_) | Self::Empty(..) | Self::Constant(_) => false, + }, + Self::Empty(node1, sort1) => match other { + Self::Empty(node2, sort2) => node1.kind() == node2.kind() && sort1 == sort2, + Self::Range(..) | Self::File(_) | Self::Node(..) | Self::Constant(_) => false, + }, + Self::Constant(c1) => other.as_constant().map_or(false, |c2| *c1 == c2), + Self::Range(range, source) => other + .text(language) + .is_ok_and(|t| t == source[range.start().into()..range.end().into()]), + Self::File(path1) => other.as_filename().map_or(false, |path2| *path1 == path2), + } } fn is_suppressed(&self, _language: &GritTargetLanguage, _current_name: Option<&str>) -> bool { @@ -115,7 +131,7 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { _is_first: bool, _language: &GritTargetLanguage, ) -> Option { - todo!() + None // TODO: Implement insertion padding } fn linearized_text( @@ -127,7 +143,7 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { _distributed_indent: Option, _logs: &mut AnalysisLogs, ) -> anyhow::Result> { - todo!() + bail!("Not implemented") // TODO: Implement rewriting } fn text(&self, _language: &GritTargetLanguage) -> anyhow::Result> { @@ -184,7 +200,11 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { } fn parent_node(&self) -> Option> { - todo!() + match self { + GritBinding::Node(node) => node.parent(), + GritBinding::Empty(node, _) => Some(node.clone()), + GritBinding::File(_) | GritBinding::Range(_, _) | GritBinding::Constant(_) => None, + } } fn is_truthy(&self) -> bool { @@ -206,8 +226,91 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { fn log_empty_field_rewrite_error( &self, _language: &GritTargetLanguage, - _logs: &mut grit_util::AnalysisLogs, + logs: &mut grit_util::AnalysisLogs, ) -> anyhow::Result<()> { - todo!() + if let Self::Empty(node, slot) = self { + let range = Range::from_byte_range(node.source(), node.byte_range()); + let log = AnalysisLogBuilder::default() + .level(441_u16) + .source(node.source()) + .position(range.start) + .range(range) + .message(format!( + "Error: failed to rewrite binding, cannot derive range of empty slot {slot} of node with kind {:?}", + node.kind() + )) + .build()?; + logs.push(log); + } + + Ok(()) + } +} + +/// Checks whether two nodes are equivalent. +/// +/// We define two nodes to be equivalent if they have the same sort (kind) and +/// equivalent named fields. +/// +/// TODO: Improve performance. Equivalence checks happen often so we want them to +/// be fast. The current implementation requires a traversal of the tree on all +/// named fields, which can be slow for large nodes. It also creates a cursor +/// at each traversal step. +/// +/// Potential improvements: +/// 1. Use cursors that are passed as arguments -- not clear if this would be faster. +/// 2. Precompute hashes on all nodes, which define the equivalence relation. The check then becomes O(1). +pub fn are_equivalent(node1: &GritTargetNode, node2: &GritTargetNode) -> bool { + // If the source is identical, we consider the nodes equivalent. + // This covers most cases of constant nodes. + // We may want a more precise check here eventually, but this is a good start. + if node1.text() == node2.text() { + return true; } + + // If the node kinds are different, then the nodes are not equivalent. + // But if one of them is a list with a single node, we may still find a + // match against that node. + if node1.kind() != node2.kind() { + return if node1.is_list() { + let mut children1 = node1.named_children(); + match (children1.next(), children1.next()) { + (Some(only_child), None) => are_equivalent(&only_child, node2), + _ => false, + } + } else if node2.is_list() { + let mut children2 = node2.named_children(); + match (children2.next(), children2.next()) { + (Some(only_child), None) => are_equivalent(node1, &only_child), + _ => false, + } + } else { + false + }; + } + + // If the node kinds are the same, then we need to check the named fields. + let named_fields1 = node1.named_children(); + let mut named_fields2 = node2.named_children(); + + // If there are no children, this is effectively a leaf node. If two leaf + // nodes have different sources (see above), then they are not equivalent. + // If they do not have the same sources, we consider them different. + let mut is_empty = true; + + // Recurse through the named fields to find the first mismatch. + for child1 in named_fields1 { + is_empty = false; + + match named_fields2.next() { + Some(child2) => { + if !are_equivalent(&child1, &child2) { + return false; + } + } + None => return false, + } + } + + named_fields2.next().is_none() && !is_empty } diff --git a/crates/biome_grit_patterns/src/grit_target_language.rs b/crates/biome_grit_patterns/src/grit_target_language.rs index 81f995890ca9..0a7c4e570948 100644 --- a/crates/biome_grit_patterns/src/grit_target_language.rs +++ b/crates/biome_grit_patterns/src/grit_target_language.rs @@ -1,12 +1,12 @@ 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 crate::CompileError; +use biome_parser::AnyParse; use biome_rowan::SyntaxKind; use grit_util::{AnalysisLogs, Ast, CodeRange, EffectRange, Language, Parser, SnippetTree}; use std::borrow::Cow; 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 dcbb40ab03cc..08dd938bfea3 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs @@ -306,9 +306,9 @@ fn implicit_metavariable_regex( let name = m.as_str(); let variable = text_to_var(name, range, context_range, range_map, context).ok()?; match variable { - SnippetValues::Dots => return None, - SnippetValues::Underscore => regex_string.push_str(uncapture_string), - SnippetValues::Variable(var) => { + SnippetValue::Dots => return None, + SnippetValue::Underscore => regex_string.push_str(uncapture_string), + SnippetValue::Variable(var) => { regex_string.push_str(capture_string); variables.push(var); } @@ -318,8 +318,7 @@ fn implicit_metavariable_regex( if last < source.len() { regex_string.push_str(®ex::escape(&source[last..])); } - let regex = regex_string.to_string(); - let regex = RegexLike::Regex(regex); + let regex = RegexLike::Regex(regex_string); Some(RegexPattern::new(regex, variables)) } @@ -425,18 +424,19 @@ pub fn split_snippet<'a>(snippet: &'a str, lang: &impl Language) -> Vec<(ByteRan ranges_and_metavars } -enum SnippetValues { +#[derive(Debug)] +enum SnippetValue { Dots, Underscore, Variable(Variable), } -impl From for Pattern { - fn from(value: SnippetValues) -> Self { +impl From for Pattern { + fn from(value: SnippetValue) -> Self { match value { - SnippetValues::Dots => Pattern::Dots, - SnippetValues::Underscore => Pattern::Underscore, - SnippetValues::Variable(v) => Pattern::Variable(v), + SnippetValue::Dots => Pattern::Dots, + SnippetValue::Underscore => Pattern::Underscore, + SnippetValue::Variable(v) => Pattern::Variable(v), } } } @@ -447,21 +447,21 @@ fn text_to_var( context_range: ByteRange, range_map: &BTreeMap, context: &mut NodeCompilationContext, -) -> Result { - let name = context +) -> Result { + let meta_value = context .compilation .lang .snippet_metavariable_to_grit_metavariable(name) .ok_or_else(|| CompileError::MetavariableNotFound(name.to_string()))?; - match name { - GritMetaValue::Dots => Ok(SnippetValues::Dots), - GritMetaValue::Underscore => Ok(SnippetValues::Underscore), + match meta_value { + GritMetaValue::Dots => Ok(SnippetValue::Dots), + GritMetaValue::Underscore => Ok(SnippetValue::Underscore), GritMetaValue::Variable(name) => { let range = *range_map .get(&range) .ok_or(CompileError::InvalidMetavariableRange(range))?; let var = context.register_variable(name, range + context_range.start); - Ok(SnippetValues::Variable(var)) + Ok(SnippetValue::Variable(var)) } } } @@ -795,4 +795,541 @@ mod tests { ) "###); } + + #[test] + fn test_pattern_with_metavariables_from_node() { + let compilation_context = + CompilationContext::new(None, GritTargetLanguage::JsTargetLanguage(JsTargetLanguage)); + let mut vars = BTreeMap::new(); + let mut vars_array = vec![Vec::new()]; + let mut global_vars = BTreeMap::new(); + let mut diagnostics = Vec::new(); + let mut context = NodeCompilationContext::new( + &compilation_context, + &mut vars, + &mut vars_array, + &mut global_vars, + &mut diagnostics, + ); + + let snippet_source = "µfn && µfn()"; + let range = ByteRange::new(0, snippet_source.len()); + let pattern = parse_snippet_content(snippet_source, range, &mut context, false) + .expect("cannot parse snippet"); + let formatted = format!("{pattern:#?}"); + let snapshot = Regex::new("normalizer: 0x[0-9a-f]{16}") + .unwrap() + .replace_all(&formatted, "normalizer: [address redacted]"); + + insta::assert_snapshot!(&snapshot, @r###" + CodeSnippet( + GritCodeSnippet { + patterns: [ + ( + JsSyntaxKind( + JS_PROPERTY_OBJECT_MEMBER, + ), + AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_PROPERTY_OBJECT_MEMBER, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: Variable( + Variable { + scope: 0, + index: 0, + }, + ), + }, + GritNodePatternArg { + slot_index: 1, + pattern: Dynamic( + Snippet( + DynamicSnippet { + parts: [ + String( + "", + ), + ], + }, + ), + ), + }, + GritNodePatternArg { + slot_index: 2, + pattern: AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_LOGICAL_EXPRESSION, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: Dynamic( + Snippet( + DynamicSnippet { + parts: [ + String( + "", + ), + ], + }, + ), + ), + }, + GritNodePatternArg { + slot_index: 1, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + AMP2, + ), + equivalence_class: None, + text: "&&", + }, + ), + }, + GritNodePatternArg { + slot_index: 2, + pattern: AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_CALL_EXPRESSION, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: Variable( + Variable { + scope: 0, + index: 0, + }, + ), + }, + GritNodePatternArg { + slot_index: 1, + pattern: Dynamic( + Snippet( + DynamicSnippet { + parts: [ + String( + "", + ), + ], + }, + ), + ), + }, + GritNodePatternArg { + slot_index: 2, + pattern: Dynamic( + Snippet( + DynamicSnippet { + parts: [ + String( + "", + ), + ], + }, + ), + ), + }, + GritNodePatternArg { + slot_index: 3, + pattern: AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_CALL_ARGUMENTS, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + L_PAREN, + ), + equivalence_class: None, + text: "(", + }, + ), + }, + GritNodePatternArg { + slot_index: 1, + pattern: List( + List { + patterns: [], + }, + ), + }, + GritNodePatternArg { + slot_index: 2, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + R_PAREN, + ), + equivalence_class: None, + text: ")", + }, + ), + }, + ], + }, + ), + }, + ], + }, + ), + }, + ], + }, + ), + }, + ], + }, + ), + ), + ( + JsSyntaxKind( + JS_LOGICAL_EXPRESSION, + ), + AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_LOGICAL_EXPRESSION, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: Variable( + Variable { + scope: 0, + index: 0, + }, + ), + }, + GritNodePatternArg { + slot_index: 1, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + AMP2, + ), + equivalence_class: None, + text: "&&", + }, + ), + }, + GritNodePatternArg { + slot_index: 2, + pattern: AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_CALL_EXPRESSION, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: Variable( + Variable { + scope: 0, + index: 0, + }, + ), + }, + GritNodePatternArg { + slot_index: 1, + pattern: Dynamic( + Snippet( + DynamicSnippet { + parts: [ + String( + "", + ), + ], + }, + ), + ), + }, + GritNodePatternArg { + slot_index: 2, + pattern: Dynamic( + Snippet( + DynamicSnippet { + parts: [ + String( + "", + ), + ], + }, + ), + ), + }, + GritNodePatternArg { + slot_index: 3, + pattern: AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_CALL_ARGUMENTS, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + L_PAREN, + ), + equivalence_class: None, + text: "(", + }, + ), + }, + GritNodePatternArg { + slot_index: 1, + pattern: List( + List { + patterns: [], + }, + ), + }, + GritNodePatternArg { + slot_index: 2, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + R_PAREN, + ), + equivalence_class: None, + text: ")", + }, + ), + }, + ], + }, + ), + }, + ], + }, + ), + }, + ], + }, + ), + ), + ( + JsSyntaxKind( + JSX_TEXT, + ), + AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JSX_TEXT, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + JSX_TEXT_LITERAL, + ), + equivalence_class: None, + text: "µfn && µfn()", + }, + ), + }, + ], + }, + ), + ), + ( + JsSyntaxKind( + JS_PROPERTY_OBJECT_MEMBER, + ), + AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_PROPERTY_OBJECT_MEMBER, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: Variable( + Variable { + scope: 0, + index: 0, + }, + ), + }, + GritNodePatternArg { + slot_index: 1, + pattern: Dynamic( + Snippet( + DynamicSnippet { + parts: [ + String( + "", + ), + ], + }, + ), + ), + }, + GritNodePatternArg { + slot_index: 2, + pattern: AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_LOGICAL_EXPRESSION, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: Dynamic( + Snippet( + DynamicSnippet { + parts: [ + String( + "", + ), + ], + }, + ), + ), + }, + GritNodePatternArg { + slot_index: 1, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + AMP2, + ), + equivalence_class: None, + text: "&&", + }, + ), + }, + GritNodePatternArg { + slot_index: 2, + pattern: AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_CALL_EXPRESSION, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: Variable( + Variable { + scope: 0, + index: 0, + }, + ), + }, + GritNodePatternArg { + slot_index: 1, + pattern: Dynamic( + Snippet( + DynamicSnippet { + parts: [ + String( + "", + ), + ], + }, + ), + ), + }, + GritNodePatternArg { + slot_index: 2, + pattern: Dynamic( + Snippet( + DynamicSnippet { + parts: [ + String( + "", + ), + ], + }, + ), + ), + }, + GritNodePatternArg { + slot_index: 3, + pattern: AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_CALL_ARGUMENTS, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + L_PAREN, + ), + equivalence_class: None, + text: "(", + }, + ), + }, + GritNodePatternArg { + slot_index: 1, + pattern: List( + List { + patterns: [], + }, + ), + }, + GritNodePatternArg { + slot_index: 2, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + R_PAREN, + ), + equivalence_class: None, + text: ")", + }, + ), + }, + ], + }, + ), + }, + ], + }, + ), + }, + ], + }, + ), + }, + ], + }, + ), + ), + ], + source: "µfn && µfn()", + dynamic_snippet: Some( + Snippet( + DynamicSnippet { + parts: [ + String( + "µfn && µfn()", + ), + ], + }, + ), + ), + }, + ) + "###); + } } diff --git a/crates/biome_grit_patterns/tests/quick_test.rs b/crates/biome_grit_patterns/tests/quick_test.rs index 7ba6434394d3..da32c54aeda7 100644 --- a/crates/biome_grit_patterns/tests/quick_test.rs +++ b/crates/biome_grit_patterns/tests/quick_test.rs @@ -7,7 +7,7 @@ use biome_js_syntax::JsFileSource; #[ignore] #[test] fn test_query() { - let parse_grit_result = parse_grit("`hello`"); + let parse_grit_result = parse_grit("`foo.$x && foo.$x()`"); if !parse_grit_result.diagnostics().is_empty() { panic!("Cannot parse query:\n{:?}", parse_grit_result.diagnostics()); } @@ -23,11 +23,7 @@ fn test_query() { println!("Diagnostics from compiling query:\n{:?}", query.diagnostics); } - let body = r#" -function hello() { - console - .log("hello"); -} + let body = r#"foo.bar && foo.bar(); "#; let file = GritTargetFile { diff --git a/crates/biome_grit_patterns/tests/specs/ts/duplicateVariable.snap b/crates/biome_grit_patterns/tests/specs/ts/duplicateVariable.snap index 1abfe7d07e54..f630bc8da8d6 100644 --- a/crates/biome_grit_patterns/tests/specs/ts/duplicateVariable.snap +++ b/crates/biome_grit_patterns/tests/specs/ts/duplicateVariable.snap @@ -6,6 +6,7 @@ SnapshotResult { messages: [], matched_ranges: [ "2:1-2:13", + "6:1-6:21", ], rewritten_files: [], created_files: [], diff --git a/crates/biome_grit_patterns/tests/specs/ts/duplicateVariable.ts b/crates/biome_grit_patterns/tests/specs/ts/duplicateVariable.ts index 24cc9070b4ab..d650099eae96 100644 --- a/crates/biome_grit_patterns/tests/specs/ts/duplicateVariable.ts +++ b/crates/biome_grit_patterns/tests/specs/ts/duplicateVariable.ts @@ -3,3 +3,4 @@ foo && foo(); foo && bar(); foo && foo.bar(); bar || bar(); +foo.bar && foo.bar(); diff --git a/crates/biome_js_formatter/src/js/any/name.rs b/crates/biome_js_formatter/src/js/any/name.rs index 2b1009b7c0ac..baed5ea71fb3 100644 --- a/crates/biome_js_formatter/src/js/any/name.rs +++ b/crates/biome_js_formatter/src/js/any/name.rs @@ -8,6 +8,7 @@ impl FormatRule for FormatAnyJsName { type Context = JsFormatContext; fn fmt(&self, node: &AnyJsName, f: &mut JsFormatter) -> FormatResult<()> { match node { + AnyJsName::JsMetavariable(node) => node.format().fmt(f), AnyJsName::JsName(node) => node.format().fmt(f), AnyJsName::JsPrivateName(node) => node.format().fmt(f), } diff --git a/crates/biome_js_parser/src/syntax/expr.rs b/crates/biome_js_parser/src/syntax/expr.rs index a013705ff47c..640a6d702ad1 100644 --- a/crates/biome_js_parser/src/syntax/expr.rs +++ b/crates/biome_js_parser/src/syntax/expr.rs @@ -1034,6 +1034,7 @@ pub(super) fn parse_private_name(p: &mut JsParser) -> ParsedSyntax { pub(super) fn parse_any_name(p: &mut JsParser) -> ParsedSyntax { match p.cur() { T![#] => parse_private_name(p), + t if t.is_metavariable() => parse_metavariable(p), _ => parse_name(p), } } @@ -1304,6 +1305,7 @@ fn parse_primary_expression(p: &mut JsParser, context: ExpressionContext) -> Par } let complete = match p.cur() { + t if t.is_metavariable() => return parse_metavariable(p), T![this] => { // test js this_expr // this @@ -1491,8 +1493,6 @@ fn parse_primary_expression(p: &mut JsParser, context: ExpressionContext) -> Par // let a = b; T![<] if Jsx.is_supported(p) => return parse_jsx_tag_expression(p), - t if t.is_metavariable() => return parse_metavariable(p), - // test_err js primary_expr_invalid_recovery // let a = \; foo(); t if t.is_contextual_keyword() || t.is_future_reserved_keyword() => { diff --git a/crates/biome_js_parser/test_data/inline/ok/metavar.rast b/crates/biome_js_parser/test_data/inline/ok/metavar.rast index c93d2ca1cb25..8ac314759a3e 100644 --- a/crates/biome_js_parser/test_data/inline/ok/metavar.rast +++ b/crates/biome_js_parser/test_data/inline/ok/metavar.rast @@ -9,12 +9,12 @@ JsModule { type_token: missing (optional), default_specifier: JsDefaultImportSpecifier { local_name: JsMetavariable { - value_token: GRIT_METAVARIABLE@7..23 "μdefaultImport" [] [Whitespace(" ")], + value_token: GRIT_METAVARIABLE@7..23 "µdefaultImport" [] [Whitespace(" ")], }, }, from_token: FROM_KW@23..28 "from" [] [Whitespace(" ")], source: JsMetavariable { - value_token: GRIT_METAVARIABLE@28..36 "μsource" [] [], + value_token: GRIT_METAVARIABLE@28..36 "µsource" [] [], }, assertion: missing (optional), }, @@ -30,14 +30,14 @@ JsModule { JsShorthandNamedImportSpecifier { type_token: missing (optional), local_name: JsMetavariable { - value_token: GRIT_METAVARIABLE@47..60 "μnamedImport" [] [], + value_token: GRIT_METAVARIABLE@47..60 "µnamedImport" [] [], }, }, COMMA@60..62 "," [] [Whitespace(" ")], JsShorthandNamedImportSpecifier { type_token: TYPE_KW@62..67 "type" [] [Whitespace(" ")], local_name: JsMetavariable { - value_token: GRIT_METAVARIABLE@67..79 "μnamedType" [] [Whitespace(" ")], + value_token: GRIT_METAVARIABLE@67..79 "µnamedType" [] [Whitespace(" ")], }, }, ], @@ -45,7 +45,7 @@ JsModule { }, from_token: FROM_KW@81..86 "from" [] [Whitespace(" ")], source: JsMetavariable { - value_token: GRIT_METAVARIABLE@86..94 "μsource" [] [], + value_token: GRIT_METAVARIABLE@86..94 "µsource" [] [], }, assertion: missing (optional), }, @@ -53,7 +53,7 @@ JsModule { }, JsExpressionStatement { expression: JsMetavariable { - value_token: GRIT_METAVARIABLE@95..108 "μstatement" [Newline("\n"), Newline("\n")] [], + value_token: GRIT_METAVARIABLE@95..108 "µstatement" [Newline("\n"), Newline("\n")] [], }, semicolon_token: SEMICOLON@108..109 ";" [] [], }, @@ -77,7 +77,7 @@ JsModule { statements: JsStatementList [ JsExpressionStatement { expression: JsMetavariable { - value_token: GRIT_METAVARIABLE@127..143 "μstatement" [Newline("\n"), Whitespace(" ")] [], + value_token: GRIT_METAVARIABLE@127..143 "µstatement" [Newline("\n"), Whitespace(" ")] [], }, semicolon_token: SEMICOLON@143..144 ";" [] [], }, @@ -94,7 +94,7 @@ JsModule { initializer: JsInitializerClause { eq_token: EQ@159..161 "=" [] [Whitespace(" ")], expression: JsMetavariable { - value_token: GRIT_METAVARIABLE@161..173 "μexpression" [] [], + value_token: GRIT_METAVARIABLE@161..173 "µexpression" [] [], }, }, }, @@ -119,7 +119,7 @@ JsModule { l_curly_token: L_CURLY@188..189 "{" [] [], members: JsClassMemberList [ JsMetavariable { - value_token: GRIT_METAVARIABLE@189..207 "μclassMember" [Newline("\n"), Whitespace(" ")] [], + value_token: GRIT_METAVARIABLE@189..207 "µclassMember" [Newline("\n"), Whitespace(" ")] [], }, JsEmptyClassMember { semicolon_token: SEMICOLON@207..208 ";" [] [], @@ -138,7 +138,7 @@ JsModule { properties: JsObjectBindingPatternPropertyList [ JsObjectBindingPatternProperty { member: JsMetavariable { - value_token: GRIT_METAVARIABLE@220..225 "μkey" [] [], + value_token: GRIT_METAVARIABLE@220..225 "µkey" [] [], }, colon_token: COLON@225..227 ":" [] [Whitespace(" ")], pattern: JsIdentifierBinding { @@ -157,11 +157,11 @@ JsModule { members: JsObjectMemberList [ JsPropertyObjectMember { name: JsMetavariable { - value_token: GRIT_METAVARIABLE@237..242 "μkey" [] [], + value_token: GRIT_METAVARIABLE@237..242 "µkey" [] [], }, colon_token: COLON@242..244 ":" [] [Whitespace(" ")], value: JsMetavariable { - value_token: GRIT_METAVARIABLE@244..252 "μvalue" [] [Whitespace(" ")], + value_token: GRIT_METAVARIABLE@244..252 "µvalue" [] [Whitespace(" ")], }, }, ], @@ -178,7 +178,7 @@ JsModule { function_token: FUNCTION_KW@254..265 "function" [Newline("\n"), Newline("\n")] [Whitespace(" ")], star_token: missing (optional), id: JsMetavariable { - value_token: GRIT_METAVARIABLE@265..279 "μfunctionName" [] [], + value_token: GRIT_METAVARIABLE@265..279 "µfunctionName" [] [], }, type_parameters: missing (optional), parameters: JsParameters { @@ -197,12 +197,12 @@ JsModule { TsTypeAliasDeclaration { type_token: TYPE_KW@284..291 "type" [Newline("\n"), Newline("\n")] [Whitespace(" ")], binding_identifier: JsMetavariable { - value_token: GRIT_METAVARIABLE@291..298 "μType" [] [Whitespace(" ")], + value_token: GRIT_METAVARIABLE@291..298 "µType" [] [Whitespace(" ")], }, type_parameters: missing (optional), eq_token: EQ@298..300 "=" [] [Whitespace(" ")], ty: JsMetavariable { - value_token: GRIT_METAVARIABLE@300..311 "μOtherType" [] [], + value_token: GRIT_METAVARIABLE@300..311 "µOtherType" [] [], }, semicolon_token: SEMICOLON@311..312 ";" [] [], }, @@ -221,10 +221,10 @@ JsModule { 0: (empty) 1: JS_DEFAULT_IMPORT_SPECIFIER@7..23 0: JS_METAVARIABLE@7..23 - 0: GRIT_METAVARIABLE@7..23 "μdefaultImport" [] [Whitespace(" ")] + 0: GRIT_METAVARIABLE@7..23 "µdefaultImport" [] [Whitespace(" ")] 2: FROM_KW@23..28 "from" [] [Whitespace(" ")] 3: JS_METAVARIABLE@28..36 - 0: GRIT_METAVARIABLE@28..36 "μsource" [] [] + 0: GRIT_METAVARIABLE@28..36 "µsource" [] [] 4: (empty) 2: SEMICOLON@36..37 ";" [] [] 1: JS_IMPORT@37..95 @@ -237,21 +237,21 @@ JsModule { 0: JS_SHORTHAND_NAMED_IMPORT_SPECIFIER@47..60 0: (empty) 1: JS_METAVARIABLE@47..60 - 0: GRIT_METAVARIABLE@47..60 "μnamedImport" [] [] + 0: GRIT_METAVARIABLE@47..60 "µnamedImport" [] [] 1: COMMA@60..62 "," [] [Whitespace(" ")] 2: JS_SHORTHAND_NAMED_IMPORT_SPECIFIER@62..79 0: TYPE_KW@62..67 "type" [] [Whitespace(" ")] 1: JS_METAVARIABLE@67..79 - 0: GRIT_METAVARIABLE@67..79 "μnamedType" [] [Whitespace(" ")] + 0: GRIT_METAVARIABLE@67..79 "µnamedType" [] [Whitespace(" ")] 2: R_CURLY@79..81 "}" [] [Whitespace(" ")] 2: FROM_KW@81..86 "from" [] [Whitespace(" ")] 3: JS_METAVARIABLE@86..94 - 0: GRIT_METAVARIABLE@86..94 "μsource" [] [] + 0: GRIT_METAVARIABLE@86..94 "µsource" [] [] 4: (empty) 2: SEMICOLON@94..95 ";" [] [] 2: JS_EXPRESSION_STATEMENT@95..109 0: JS_METAVARIABLE@95..108 - 0: GRIT_METAVARIABLE@95..108 "μstatement" [Newline("\n"), Newline("\n")] [] + 0: GRIT_METAVARIABLE@95..108 "µstatement" [Newline("\n"), Newline("\n")] [] 1: SEMICOLON@108..109 ";" [] [] 3: JS_FUNCTION_DECLARATION@109..176 0: (empty) @@ -271,7 +271,7 @@ JsModule { 2: JS_STATEMENT_LIST@127..174 0: JS_EXPRESSION_STATEMENT@127..144 0: JS_METAVARIABLE@127..143 - 0: GRIT_METAVARIABLE@127..143 "μstatement" [Newline("\n"), Whitespace(" ")] [] + 0: GRIT_METAVARIABLE@127..143 "µstatement" [Newline("\n"), Whitespace(" ")] [] 1: SEMICOLON@143..144 ";" [] [] 1: JS_VARIABLE_STATEMENT@144..174 0: JS_VARIABLE_DECLARATION@144..173 @@ -285,7 +285,7 @@ JsModule { 2: JS_INITIALIZER_CLAUSE@159..173 0: EQ@159..161 "=" [] [Whitespace(" ")] 1: JS_METAVARIABLE@161..173 - 0: GRIT_METAVARIABLE@161..173 "μexpression" [] [] + 0: GRIT_METAVARIABLE@161..173 "µexpression" [] [] 1: SEMICOLON@173..174 ";" [] [] 3: R_CURLY@174..176 "}" [Newline("\n")] [] 4: JS_CLASS_DECLARATION@176..210 @@ -300,7 +300,7 @@ JsModule { 7: L_CURLY@188..189 "{" [] [] 8: JS_CLASS_MEMBER_LIST@189..208 0: JS_METAVARIABLE@189..207 - 0: GRIT_METAVARIABLE@189..207 "μclassMember" [Newline("\n"), Whitespace(" ")] [] + 0: GRIT_METAVARIABLE@189..207 "µclassMember" [Newline("\n"), Whitespace(" ")] [] 1: JS_EMPTY_CLASS_MEMBER@207..208 0: SEMICOLON@207..208 ";" [] [] 9: R_CURLY@208..210 "}" [Newline("\n")] [] @@ -315,7 +315,7 @@ JsModule { 1: JS_OBJECT_BINDING_PATTERN_PROPERTY_LIST@220..231 0: JS_OBJECT_BINDING_PATTERN_PROPERTY@220..231 0: JS_METAVARIABLE@220..225 - 0: GRIT_METAVARIABLE@220..225 "μkey" [] [] + 0: GRIT_METAVARIABLE@220..225 "µkey" [] [] 1: COLON@225..227 ":" [] [Whitespace(" ")] 2: JS_IDENTIFIER_BINDING@227..231 0: IDENT@227..231 "key" [] [Whitespace(" ")] @@ -329,10 +329,10 @@ JsModule { 1: JS_OBJECT_MEMBER_LIST@237..252 0: JS_PROPERTY_OBJECT_MEMBER@237..252 0: JS_METAVARIABLE@237..242 - 0: GRIT_METAVARIABLE@237..242 "μkey" [] [] + 0: GRIT_METAVARIABLE@237..242 "µkey" [] [] 1: COLON@242..244 ":" [] [Whitespace(" ")] 2: JS_METAVARIABLE@244..252 - 0: GRIT_METAVARIABLE@244..252 "μvalue" [] [Whitespace(" ")] + 0: GRIT_METAVARIABLE@244..252 "µvalue" [] [Whitespace(" ")] 2: R_CURLY@252..253 "}" [] [] 1: SEMICOLON@253..254 ";" [] [] 6: JS_FUNCTION_DECLARATION@254..284 @@ -340,7 +340,7 @@ JsModule { 1: FUNCTION_KW@254..265 "function" [Newline("\n"), Newline("\n")] [Whitespace(" ")] 2: (empty) 3: JS_METAVARIABLE@265..279 - 0: GRIT_METAVARIABLE@265..279 "μfunctionName" [] [] + 0: GRIT_METAVARIABLE@265..279 "µfunctionName" [] [] 4: (empty) 5: JS_PARAMETERS@279..282 0: L_PAREN@279..280 "(" [] [] @@ -355,10 +355,10 @@ JsModule { 7: TS_TYPE_ALIAS_DECLARATION@284..312 0: TYPE_KW@284..291 "type" [Newline("\n"), Newline("\n")] [Whitespace(" ")] 1: JS_METAVARIABLE@291..298 - 0: GRIT_METAVARIABLE@291..298 "μType" [] [Whitespace(" ")] + 0: GRIT_METAVARIABLE@291..298 "µType" [] [Whitespace(" ")] 2: (empty) 3: EQ@298..300 "=" [] [Whitespace(" ")] 4: JS_METAVARIABLE@300..311 - 0: GRIT_METAVARIABLE@300..311 "μOtherType" [] [] + 0: GRIT_METAVARIABLE@300..311 "µOtherType" [] [] 5: SEMICOLON@311..312 ";" [] [] 4: EOF@312..313 "" [Newline("\n")] [] diff --git a/crates/biome_js_parser/test_data/inline/ok/metavar.ts b/crates/biome_js_parser/test_data/inline/ok/metavar.ts index 15950a21d32e..7fb524ac341d 100644 --- a/crates/biome_js_parser/test_data/inline/ok/metavar.ts +++ b/crates/biome_js_parser/test_data/inline/ok/metavar.ts @@ -1,19 +1,19 @@ -import μdefaultImport from μsource; -import { μnamedImport, type μnamedType } from μsource; +import µdefaultImport from µsource; +import { µnamedImport, type µnamedType } from µsource; -μstatement; +µstatement; function foo() { - μstatement; - const bar = μexpression; + µstatement; + const bar = µexpression; } class Foo { - μclassMember; + µclassMember; } -const { μkey: key } = { μkey: μvalue }; +const { µkey: key } = { µkey: µvalue }; -function μfunctionName() {} +function µfunctionName() {} -type μType = μOtherType; +type µType = µOtherType; diff --git a/crates/biome_js_syntax/src/generated/nodes.rs b/crates/biome_js_syntax/src/generated/nodes.rs index 7d70247700cc..6e7e5697353b 100644 --- a/crates/biome_js_syntax/src/generated/nodes.rs +++ b/crates/biome_js_syntax/src/generated/nodes.rs @@ -14608,10 +14608,17 @@ impl AnyJsModuleSource { } #[derive(Clone, PartialEq, Eq, Hash, Serialize)] pub enum AnyJsName { + JsMetavariable(JsMetavariable), JsName(JsName), JsPrivateName(JsPrivateName), } impl AnyJsName { + pub fn as_js_metavariable(&self) -> Option<&JsMetavariable> { + match &self { + AnyJsName::JsMetavariable(item) => Some(item), + _ => None, + } + } pub fn as_js_name(&self) -> Option<&JsName> { match &self { AnyJsName::JsName(item) => Some(item), @@ -33008,6 +33015,11 @@ impl From for SyntaxElement { node.into() } } +impl From for AnyJsName { + fn from(node: JsMetavariable) -> AnyJsName { + AnyJsName::JsMetavariable(node) + } +} impl From for AnyJsName { fn from(node: JsName) -> AnyJsName { AnyJsName::JsName(node) @@ -33020,12 +33032,15 @@ impl From for AnyJsName { } impl AstNode for AnyJsName { type Language = Language; - const KIND_SET: SyntaxKindSet = JsName::KIND_SET.union(JsPrivateName::KIND_SET); + const KIND_SET: SyntaxKindSet = JsMetavariable::KIND_SET + .union(JsName::KIND_SET) + .union(JsPrivateName::KIND_SET); fn can_cast(kind: SyntaxKind) -> bool { - matches!(kind, JS_NAME | JS_PRIVATE_NAME) + matches!(kind, JS_METAVARIABLE | JS_NAME | JS_PRIVATE_NAME) } fn cast(syntax: SyntaxNode) -> Option { let res = match syntax.kind() { + JS_METAVARIABLE => AnyJsName::JsMetavariable(JsMetavariable { syntax }), JS_NAME => AnyJsName::JsName(JsName { syntax }), JS_PRIVATE_NAME => AnyJsName::JsPrivateName(JsPrivateName { syntax }), _ => return None, @@ -33034,12 +33049,14 @@ impl AstNode for AnyJsName { } fn syntax(&self) -> &SyntaxNode { match self { + AnyJsName::JsMetavariable(it) => &it.syntax, AnyJsName::JsName(it) => &it.syntax, AnyJsName::JsPrivateName(it) => &it.syntax, } } fn into_syntax(self) -> SyntaxNode { match self { + AnyJsName::JsMetavariable(it) => it.syntax, AnyJsName::JsName(it) => it.syntax, AnyJsName::JsPrivateName(it) => it.syntax, } @@ -33048,6 +33065,7 @@ impl AstNode for AnyJsName { impl std::fmt::Debug for AnyJsName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + AnyJsName::JsMetavariable(it) => std::fmt::Debug::fmt(it, f), AnyJsName::JsName(it) => std::fmt::Debug::fmt(it, f), AnyJsName::JsPrivateName(it) => std::fmt::Debug::fmt(it, f), } @@ -33056,6 +33074,7 @@ impl std::fmt::Debug for AnyJsName { impl From for SyntaxNode { fn from(n: AnyJsName) -> SyntaxNode { match n { + AnyJsName::JsMetavariable(it) => it.into(), AnyJsName::JsName(it) => it.into(), AnyJsName::JsPrivateName(it) => it.into(), } diff --git a/crates/biome_js_syntax/src/identifier_ext.rs b/crates/biome_js_syntax/src/identifier_ext.rs index 61fb4f2d3e61..fb1782bc6f8f 100644 --- a/crates/biome_js_syntax/src/identifier_ext.rs +++ b/crates/biome_js_syntax/src/identifier_ext.rs @@ -3,7 +3,9 @@ use crate::{ JsLiteralExportName, JsReferenceIdentifier, JsSyntaxKind, JsSyntaxToken, JsxReferenceIdentifier, }; -use biome_rowan::{declare_node_union, AstNode, SyntaxNodeOptionExt, SyntaxResult, TokenText}; +use biome_rowan::{ + declare_node_union, AstNode, SyntaxError, SyntaxNodeOptionExt, SyntaxResult, TokenText, +}; declare_node_union! { pub AnyJsIdentifierUsage = JsReferenceIdentifier | JsIdentifierAssignment | JsxReferenceIdentifier @@ -94,6 +96,7 @@ impl AnyJsName { match self { AnyJsName::JsName(name) => name.value_token(), AnyJsName::JsPrivateName(name) => name.value_token(), + AnyJsName::JsMetavariable(_) => Err(SyntaxError::UnexpectedMetavariable), } } } diff --git a/crates/biome_parser/src/lexer.rs b/crates/biome_parser/src/lexer.rs index 17b0cff7d783..6f9ecbbb0965 100644 --- a/crates/biome_parser/src/lexer.rs +++ b/crates/biome_parser/src/lexer.rs @@ -268,9 +268,9 @@ pub trait Lexer<'src> { /// Check if the lexer starts a grit metavariable fn is_metavariable_start(&mut self) -> bool { let current_char = self.current_char_unchecked(); - if current_char == 'μ' { + if current_char == 'µ' { let current_char_length = current_char.len_utf8(); - // μ[a-zA-Z_][a-zA-Z0-9_]* + // µ[a-zA-Z_][a-zA-Z0-9_]* if matches!( self.byte_at(current_char_length), Some(b'a'..=b'z' | b'A'..=b'Z' | b'_') @@ -278,7 +278,7 @@ pub trait Lexer<'src> { return true; } - // μ... + // µ... if self.byte_at(current_char_length) == Some(b'.') && self.byte_at(current_char_length + 1) == Some(b'.') && self.byte_at(current_char_length + 2) == Some(b'.') @@ -289,20 +289,20 @@ pub trait Lexer<'src> { false } - /// Consume a grit metavariable(μ[a-zA-Z_][a-zA-Z0-9_]*|μ...) + /// Consume a grit metavariable(µ[a-zA-Z_][a-zA-Z0-9_]*|µ...) /// https://github.com/getgrit/gritql/blob/8f3f077d078ccaf0618510bba904a06309c2435e/resources/language-metavariables/tree-sitter-css/grammar.js#L388 fn consume_metavariable(&mut self, kind: T) -> T { debug_assert!(self.is_metavariable_start()); - // SAFETY: We know the current character is μ. + // SAFETY: We know the current character is µ. let current_char = self.current_char_unchecked(); self.advance(current_char.len_utf8()); if self.current_byte() == Some(b'.') { - // SAFETY: We know that the current token is μ... + // SAFETY: We know that the current token is µ... self.advance(3); } else { - // μ[a-zA-Z_][a-zA-Z0-9_]* + // µ[a-zA-Z_][a-zA-Z0-9_]* self.advance(1); while let Some(chr) = self.current_byte() { match chr { diff --git a/xtask/codegen/js.ungram b/xtask/codegen/js.ungram index b8f72a0765e8..dc9cbe91ae65 100644 --- a/xtask/codegen/js.ungram +++ b/xtask/codegen/js.ungram @@ -1572,7 +1572,7 @@ JsPrivateName = AnyJsName = JsName | JsPrivateName - + | JsMetavariable AnyJsFunction = JsFunctionExpression