From b9482ec3dea76b399c74ebda6b202dcae0eb76bd Mon Sep 17 00:00:00 2001 From: Arend van Beelen jr Date: Mon, 6 May 2024 19:22:32 +0200 Subject: [PATCH] chore: add Grit target node bindings (#2746) --- Cargo.lock | 2 + crates/biome_grit_patterns/Cargo.toml | 2 + .../biome_grit_patterns/src/grit_binding.rs | 15 +- .../biome_grit_patterns/src/grit_context.rs | 4 +- crates/biome_grit_patterns/src/grit_node.rs | 4 + .../src/grit_node_patterns.rs | 4 +- .../src/grit_target_language.rs | 123 +++++++- .../js_target_language.rs | 58 ++++ .../src/grit_target_node.rs | 298 ++++++++++++++++++ crates/biome_grit_patterns/src/grit_tree.rs | 12 +- crates/biome_grit_patterns/src/lib.rs | 3 +- crates/biome_js_syntax/src/generated/kind.rs | 1 + crates/biome_service/src/workspace/server.rs | 2 +- xtask/codegen/src/js_kinds_src.rs | 2 + 14 files changed, 496 insertions(+), 34 deletions(-) create mode 100644 crates/biome_grit_patterns/src/grit_target_language/js_target_language.rs create mode 100644 crates/biome_grit_patterns/src/grit_target_node.rs diff --git a/Cargo.lock b/Cargo.lock index a58ebae7eaf8..09432f138f32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -523,6 +523,8 @@ dependencies = [ "biome_diagnostics", "biome_grit_parser", "biome_grit_syntax", + "biome_js_syntax", + "biome_parser", "biome_rowan", "grit-pattern-matcher", "grit-util", diff --git a/crates/biome_grit_patterns/Cargo.toml b/crates/biome_grit_patterns/Cargo.toml index 2590839ad4b0..02d3be71bf71 100644 --- a/crates/biome_grit_patterns/Cargo.toml +++ b/crates/biome_grit_patterns/Cargo.toml @@ -17,6 +17,8 @@ biome_console = { workspace = true } biome_diagnostics = { workspace = true } biome_grit_parser = { workspace = true } biome_grit_syntax = { workspace = true } +biome_js_syntax = { workspace = true } +biome_parser = { workspace = true } biome_rowan = { workspace = true } grit-pattern-matcher = { version = "0.2" } grit-util = { version = "0.2" } diff --git a/crates/biome_grit_patterns/src/grit_binding.rs b/crates/biome_grit_patterns/src/grit_binding.rs index 407707c62e9f..3ae1d3fb36d9 100644 --- a/crates/biome_grit_patterns/src/grit_binding.rs +++ b/crates/biome_grit_patterns/src/grit_binding.rs @@ -1,5 +1,6 @@ use crate::{ - grit_context::GritQueryContext, grit_node::GritNode, grit_target_language::GritTargetLanguage, + grit_context::GritQueryContext, grit_target_language::GritTargetLanguage, + grit_target_node::GritTargetNode, }; use grit_pattern_matcher::{binding::Binding, constant::Constant}; use grit_util::{ByteRange, CodeRange, Range}; @@ -13,7 +14,7 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding { todo!() } - fn from_node(_node: GritNode) -> Self { + fn from_node(_node: GritTargetNode) -> Self { todo!() } @@ -25,7 +26,7 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding { todo!() } - fn singleton(&self) -> Option { + fn singleton(&self) -> Option { todo!() } @@ -90,7 +91,7 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding { todo!() } - fn as_node(&self) -> Option { + fn as_node(&self) -> Option { todo!() } @@ -98,11 +99,11 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding { todo!() } - fn list_items(&self) -> Option + Clone> { + fn list_items(&self) -> Option + Clone> { None:: } - fn parent_node(&self) -> Option { + fn parent_node(&self) -> Option { todo!() } @@ -123,7 +124,7 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding { struct TodoIterator; impl Iterator for TodoIterator { - type Item = GritNode; + type Item = GritTargetNode; fn next(&mut self) -> Option { todo!() diff --git a/crates/biome_grit_patterns/src/grit_context.rs b/crates/biome_grit_patterns/src/grit_context.rs index 09ac1a1a8049..f11de991d7ba 100644 --- a/crates/biome_grit_patterns/src/grit_context.rs +++ b/crates/biome_grit_patterns/src/grit_context.rs @@ -1,9 +1,9 @@ use crate::grit_binding::GritBinding; use crate::grit_code_snippet::GritCodeSnippet; use crate::grit_file::GritFile; -use crate::grit_node::GritNode; use crate::grit_node_patterns::{GritLeafNodePattern, GritNodePattern}; use crate::grit_target_language::GritTargetLanguage; +use crate::grit_target_node::GritTargetNode; use crate::grit_tree::GritTree; use crate::resolved_pattern::GritResolvedPattern; use anyhow::Result; @@ -18,7 +18,7 @@ use grit_util::AnalysisLogs; pub(crate) struct GritQueryContext; impl QueryContext for GritQueryContext { - type Node<'a> = GritNode; + type Node<'a> = GritTargetNode; type NodePattern = GritNodePattern; type LeafNodePattern = GritLeafNodePattern; type ExecContext<'a> = GritExecContext; diff --git a/crates/biome_grit_patterns/src/grit_node.rs b/crates/biome_grit_patterns/src/grit_node.rs index f59b15c66be9..6577ce2184d7 100644 --- a/crates/biome_grit_patterns/src/grit_node.rs +++ b/crates/biome_grit_patterns/src/grit_node.rs @@ -3,6 +3,10 @@ use biome_grit_syntax::GritSyntaxNode; use grit_util::{AstCursor, AstNode as GritAstNode, ByteRange, CodeRange}; use std::{borrow::Cow, ops::Deref, str::Utf8Error}; +/// Wrapper around `GritSyntaxNode` as produced by our internal Grit parser. +/// +/// This enables us to implement the [`GritAstNode`] trait on Grit nodes, which +/// offers a bunch of utilities used by our node compilers. #[derive(Clone, Debug)] pub struct GritNode(GritSyntaxNode); diff --git a/crates/biome_grit_patterns/src/grit_node_patterns.rs b/crates/biome_grit_patterns/src/grit_node_patterns.rs index eb8007069c86..8112b6059c8c 100644 --- a/crates/biome_grit_patterns/src/grit_node_patterns.rs +++ b/crates/biome_grit_patterns/src/grit_node_patterns.rs @@ -1,5 +1,5 @@ use crate::grit_context::{GritExecContext, GritQueryContext}; -use crate::grit_node::GritNode; +use crate::grit_target_node::GritTargetNode; use crate::resolved_pattern::GritResolvedPattern; use anyhow::Result; use grit_pattern_matcher::pattern::{ @@ -15,7 +15,7 @@ impl AstNodePattern for GritNodePattern { todo!() } - fn matches_kind_of(&self, _node: &GritNode) -> bool { + fn matches_kind_of(&self, _node: &GritTargetNode) -> bool { todo!() } } diff --git a/crates/biome_grit_patterns/src/grit_target_language.rs b/crates/biome_grit_patterns/src/grit_target_language.rs index 1654ec377083..d070e60de5b2 100644 --- a/crates/biome_grit_patterns/src/grit_target_language.rs +++ b/crates/biome_grit_patterns/src/grit_target_language.rs @@ -1,25 +1,116 @@ -use crate::grit_node::GritNode; +mod js_target_language; + +pub use js_target_language::JsTargetLanguage; + +use crate::grit_target_node::{GritTargetNode, GritTargetSyntaxKind}; +use biome_rowan::SyntaxKind; use grit_util::Language; -#[derive(Clone, Copy)] -pub struct GritTargetLanguage; +/// Generates the `GritTargetLanguage` enum. +/// +/// This enum contains a variant for every language that we support running Grit +/// queries on. We implement Grit's [`Language`] trait on this enum, and +/// implement the slightly more convenient [`GritTargetLanguageImpl`] for +/// creating language-specific implementations. +macro_rules! generate_target_language { + ($($language:ident),+) => { + #[derive(Clone, Debug)] + pub enum GritTargetLanguage { + $($language($language)),+ + } -impl Language for GritTargetLanguage { - type Node<'a> = GritNode; + $(impl From<$language> for GritTargetLanguage { + fn from(value: $language) -> Self { + Self::$language(value) + } + })+ - fn language_name(&self) -> &'static str { - todo!() - } + impl GritTargetLanguage { + fn metavariable_kind(&self) -> GritTargetSyntaxKind { + match self { + $(Self::$language(_) => $language::metavariable_kind().into()),+ + } + } - fn snippet_context_strings(&self) -> &[(&'static str, &'static str)] { - todo!() - } + fn is_alternative_metavariable_kind(&self, kind: GritTargetSyntaxKind) -> bool { + match self { + $(Self::$language(_) => $language::is_alternative_metavariable_kind(kind)),+ + } + } + } - fn is_comment(&self, _node: &Self::Node<'_>) -> bool { - todo!() - } + impl Language for GritTargetLanguage { + type Node<'a> = GritTargetNode; + + fn language_name(&self) -> &'static str { + match self { + $(Self::$language(language) => language.language_name()),+ + } + } + + fn snippet_context_strings(&self) -> &[(&'static str, &'static str)] { + match self { + $(Self::$language(language) => language.snippet_context_strings()),+ + } + } + + fn is_comment(&self, node: &GritTargetNode) -> bool { + match self { + $(Self::$language(language) => language.is_comment(node)),+ + } + } + + fn is_metavariable(&self, node: &GritTargetNode) -> bool { + node.kind() == self.metavariable_kind() + || (self.is_alternative_metavariable_kind(node.kind()) + && self.exact_replaced_variable_regex().is_match(&node.text_trimmed().to_string())) + } + } + }; +} + +generate_target_language! { + JsTargetLanguage +} + +/// Trait to be implemented by the language-specific implementations. +/// +/// This is used to make language implementations a little easier, by not +/// forcing them to reimplement methods that are common across implementations. +trait GritTargetLanguageImpl { + type Kind: SyntaxKind; + + fn language_name(&self) -> &'static str; + + /// Strings that provide context for parsing snippets. + /// + /// Snippet contexts help when a snippet is a valid AST subtree, but needs + /// to be in a larger tree to parse. For example, matching on a table name + /// like ` $schema.$table` in SQL is not valid SQL by itself, only when + /// surrounded by something like `SELECT x from $schema.$table` is the + /// snippet valid. + /// + /// This method returns a list of strings that are used to match the snippet + /// in the larger tree. For example, the SQL implementation returns + /// `["SELECT 1 from ", ";"]` to match a table name in a SQL query. + fn snippet_context_strings(&self) -> &[(&'static str, &'static str)]; + + /// Determines whether the given target node is a comment. + fn is_comment(&self, node: &GritTargetNode) -> bool; + + /// Returns the syntax kind for metavariables. + fn metavariable_kind() -> Self::Kind; - fn is_metavariable(&self, _node: &Self::Node<'_>) -> bool { - todo!() + /// Returns whether the given syntax kind is an "alternative" kind for + /// metavariables. + /// + /// For example, in JavaScript, the content of a template string may also + /// contain metavariables. + /// + /// Note that any node kind for which this returns `true` should have a + /// (trimmed) text representation which corresponds exactly to the + /// metavariable representation. + fn is_alternative_metavariable_kind(_kind: GritTargetSyntaxKind) -> bool { + false } } diff --git a/crates/biome_grit_patterns/src/grit_target_language/js_target_language.rs b/crates/biome_grit_patterns/src/grit_target_language/js_target_language.rs new file mode 100644 index 000000000000..e5b390d6cb47 --- /dev/null +++ b/crates/biome_grit_patterns/src/grit_target_language/js_target_language.rs @@ -0,0 +1,58 @@ +use super::GritTargetLanguageImpl; +use crate::grit_target_node::{GritTargetNode, GritTargetSyntaxKind}; +use biome_js_syntax::JsSyntaxKind; +use biome_parser::{token_set, TokenSet}; + +const COMMENT_KINDS: TokenSet = + token_set![JsSyntaxKind::COMMENT, JsSyntaxKind::MULTILINE_COMMENT]; + +#[derive(Clone, Debug)] +pub struct JsTargetLanguage; + +impl GritTargetLanguageImpl for JsTargetLanguage { + type Kind = JsSyntaxKind; + + fn language_name(&self) -> &'static str { + "JavaScript" + } + + fn snippet_context_strings(&self) -> &[(&'static str, &'static str)] { + &[ + ("", ""), + ("import ", " from 'GRIT_PACKAGE';"), + ("GRIT_VALUE ", " GRIT_VALUE"), + ("class GRIT_CLASS ", " {}"), + ("class GRIT_CLASS { ", " GRIT_PROP = 'GRIT_VALUE'; }"), + ("", " function GRIT_FUNCTION() {}"), + ("GRIT_OBJ = { ", " }"), + ("class GRIT_CLASS { ", " }"), + ("GRIT_VAR = ", ""), + ("", ""), + (""), + ("function GRIT_FN(", ") {}"), + ("var ", ";"), + ("", " class GRIT_CLASS {}"), + ("function GRIT_FN(GRIT_ARG:", ") { }"), + ("import { ", " } from 'GRIT_PACKAGE'"), + ("function GRIT_FN(GRIT_ARG", ") { }"), + ("GRIT_FN<{ ", " }>();"), + ] + } + + fn is_comment(&self, node: &GritTargetNode) -> bool { + node.kind() + .as_js_kind() + .map_or(false, |kind| COMMENT_KINDS.contains(kind)) + } + + fn metavariable_kind() -> Self::Kind { + JsSyntaxKind::JS_GRIT_METAVARIABLE + } + + fn is_alternative_metavariable_kind(kind: GritTargetSyntaxKind) -> bool { + kind.as_js_kind().map_or(false, |kind| { + kind == JsSyntaxKind::JS_TEMPLATE_ELEMENT_LIST + || kind == JsSyntaxKind::TS_TEMPLATE_ELEMENT_LIST + }) + } +} diff --git a/crates/biome_grit_patterns/src/grit_target_node.rs b/crates/biome_grit_patterns/src/grit_target_node.rs new file mode 100644 index 000000000000..7cdc02efdfb1 --- /dev/null +++ b/crates/biome_grit_patterns/src/grit_target_node.rs @@ -0,0 +1,298 @@ +use crate::util::TextRangeGritExt; +use biome_js_syntax::{JsSyntaxKind, JsSyntaxNode, JsSyntaxToken}; +use biome_rowan::{SyntaxNodeText, TextRange}; +use grit_util::{AstCursor, AstNode as GritAstNode, ByteRange, CodeRange}; +use std::{borrow::Cow, str::Utf8Error}; + +/// Generates the `GritTargetNode`, `GritTargetToken`, and +/// `GritTargetSyntaxKind` enums. +/// +/// These enums can represent nodes, tokens and kinds for all the languages we +/// support running Grit queries on. +/// +/// We intentionally use enums for these types, rather than using generics for +/// specifying specific types: +/// - If we used generics instead, those would infest all code using these +/// types, and we would end up with an explosion of generics all over the Grit +/// runtime. +/// - Using generics wouldn't only make the code itself a lot more complex, it +/// would inflate compile times and binary size as well. It's hard to say how +/// much this would matter, but there will be quite some code in the Grit +/// runtime, and each supported language would effectively require its own +/// binary instance of the entire runtime. +/// - Theoretically, this may enable us to run queries on mixed-language trees +/// in the future. Even though GritQL does not currently have syntax support +/// for this, it may allow us to one day query CSS rules inside a JS template +/// literal, for instance. +macro_rules! generate_target_node { + ($([$lang_node:ident, $lang_token:ident, $lang_kind:ident]),+) => { + #[derive(Clone, Debug, PartialEq)] + pub enum GritTargetNode { + $($lang_node($lang_node)),+ + } + + $(impl From<$lang_node> for GritTargetNode { + fn from(value: $lang_node) -> Self { + Self::$lang_node(value) + } + })+ + + impl GritTargetNode { + fn first_child(&self) -> Option { + match self { + $(Self::$lang_node(node) => node.first_child().map(Into::into)),+ + } + } + + fn first_token(&self) -> Option { + match self { + $(Self::$lang_node(node) => node.first_token().map(Into::into)),+ + } + } + + pub fn kind(&self) -> GritTargetSyntaxKind { + match self { + $(Self::$lang_node(node) => node.kind().into()),+ + } + } + + pub fn text(&self) -> SyntaxNodeText { + match self { + $(Self::$lang_node(node) => node.text()),+ + } + } + + pub fn text_trimmed(&self) -> SyntaxNodeText { + match self { + $(Self::$lang_node(node) => node.text_trimmed()),+ + } + } + + pub fn text_trimmed_range(&self) -> TextRange { + match self { + $(Self::$lang_node(node) => node.text_trimmed_range()),+ + } + } + } + + impl GritAstNode for GritTargetNode { + fn ancestors(&self) -> impl Iterator { + AncestorIterator::new(self) + } + + fn children(&self) -> impl Iterator { + ChildrenIterator::new(self) + } + + fn parent(&self) -> Option { + match self { + $(Self::$lang_node(node) => node.parent().map(Into::into)),+ + } + } + + fn next_named_node(&self) -> Option { + let mut current_node = Cow::Borrowed(self); + loop { + if let Some(sibling) = current_node.next_sibling() { + return Some(sibling); + } + current_node = Cow::Owned(current_node.parent()?); + } + } + + fn previous_named_node(&self) -> Option { + let mut current_node = Cow::Borrowed(self); + loop { + if let Some(sibling) = current_node.previous_sibling() { + return Some(sibling); + } + current_node = Cow::Owned(current_node.parent()?); + } + } + + fn next_sibling(&self) -> Option { + match self { + $(Self::$lang_node(node) => node.next_sibling().map(Into::into)),+ + } + } + + fn previous_sibling(&self) -> Option { + match self { + $(Self::$lang_node(node) => node.prev_sibling().map(Into::into)),+ + } + } + + fn text(&self) -> Result, Utf8Error> { + Ok(Cow::Owned(self.text_trimmed().to_string())) + } + + fn byte_range(&self) -> ByteRange { + self.text_trimmed_range().to_byte_range() + } + + fn code_range(&self) -> CodeRange { + let range = self.text_trimmed_range(); + CodeRange { + start: range.start().into(), + end: range.end().into(), + // Code ranges contain an address so they can quickly check whether + // a particular binding belongs to a given range or not. + address: self + .first_token() + .map(|token| token.text().as_ptr() as usize) + .unwrap_or_default(), + } + } + + fn full_source(&self) -> &str { + // This should not be a problem anytime soon, though we may want to + // reconsider when we implement rewrites. + unimplemented!("Full source of file not available") + } + + fn walk(&self) -> impl AstCursor { + GritTargetNodeCursor::new(self) + } + } + + #[derive(Clone, Debug)] + pub enum GritTargetToken { + $($lang_token($lang_token)),+ + } + + $(impl From<$lang_token> for GritTargetToken { + fn from(value: $lang_token) -> Self { + Self::$lang_token(value) + } + })+ + + impl GritTargetToken { + fn text(&self) -> &str { + match self { + $(Self::$lang_token(token) => token.text()),+ + } + } + } + + #[derive(Clone, Debug, PartialEq)] + pub enum GritTargetSyntaxKind { + $($lang_kind($lang_kind)),+ + } + + $(impl From<$lang_kind> for GritTargetSyntaxKind { + fn from(value: $lang_kind) -> Self { + Self::$lang_kind(value) + } + })+ + }; +} + +generate_target_node! { + [JsSyntaxNode, JsSyntaxToken, JsSyntaxKind] +} + +impl GritTargetSyntaxKind { + pub fn as_js_kind(&self) -> Option { + match self { + Self::JsSyntaxKind(kind) => Some(*kind), + } + } +} + +#[derive(Clone)] +pub struct AncestorIterator { + node: Option, +} + +impl AncestorIterator { + fn new(node: &GritTargetNode) -> Self { + Self { + node: Some(node.clone()), + } + } +} + +impl Iterator for AncestorIterator { + type Item = GritTargetNode; + + fn next(&mut self) -> Option { + let node = self.node.as_ref().cloned()?; + self.node = node.parent(); + Some(node) + } +} + +pub struct ChildrenIterator { + cursor: Option, +} + +impl ChildrenIterator { + fn new(node: &GritTargetNode) -> Self { + let mut cursor = GritTargetNodeCursor::new(node); + Self { + cursor: cursor.goto_first_child().then_some(cursor), + } + } +} + +impl Iterator for ChildrenIterator { + type Item = GritTargetNode; + + fn next(&mut self) -> Option { + let c = self.cursor.as_mut()?; + let node = c.node(); + if !c.goto_next_sibling() { + self.cursor = None; + } + Some(node) + } +} + +#[derive(Clone)] +struct GritTargetNodeCursor { + node: GritTargetNode, +} + +impl GritTargetNodeCursor { + fn new(node: &GritTargetNode) -> Self { + Self { node: node.clone() } + } +} + +impl AstCursor for GritTargetNodeCursor { + type Node = GritTargetNode; + + fn goto_first_child(&mut self) -> bool { + match self.node.first_child() { + Some(child) => { + self.node = child; + true + } + None => false, + } + } + + fn goto_parent(&mut self) -> bool { + match self.node.parent() { + Some(parent) => { + self.node = parent; + true + } + None => false, + } + } + + fn goto_next_sibling(&mut self) -> bool { + match self.node.next_sibling() { + Some(sibling) => { + self.node = sibling; + true + } + None => false, + } + } + + fn node(&self) -> Self::Node { + self.node.clone() + } +} diff --git a/crates/biome_grit_patterns/src/grit_tree.rs b/crates/biome_grit_patterns/src/grit_tree.rs index f12756c335c3..37852bc1fc99 100644 --- a/crates/biome_grit_patterns/src/grit_tree.rs +++ b/crates/biome_grit_patterns/src/grit_tree.rs @@ -1,15 +1,17 @@ -use crate::grit_node::GritNode; +use crate::grit_target_node::GritTargetNode; use grit_util::Ast; #[derive(Clone, Debug, PartialEq)] -pub(crate) struct GritTree; +pub(crate) struct GritTree { + root: GritTargetNode, +} impl Ast for GritTree { - type Node<'a> = GritNode + type Node<'a> = GritTargetNode where Self: 'a; - fn root_node(&self) -> GritNode { - todo!() + fn root_node(&self) -> GritTargetNode { + self.root.clone() } } diff --git a/crates/biome_grit_patterns/src/lib.rs b/crates/biome_grit_patterns/src/lib.rs index c4dbfbcc4995..e729bfe8aff1 100644 --- a/crates/biome_grit_patterns/src/lib.rs +++ b/crates/biome_grit_patterns/src/lib.rs @@ -9,6 +9,7 @@ mod grit_node; mod grit_node_patterns; mod grit_query; mod grit_target_language; +mod grit_target_node; mod grit_tree; mod pattern_compiler; mod resolved_pattern; @@ -17,7 +18,7 @@ mod variables; pub use errors::*; pub use grit_query::GritQuery; -pub use grit_target_language::GritTargetLanguage; +pub use grit_target_language::{GritTargetLanguage, JsTargetLanguage}; use biome_grit_parser::parse_grit; diff --git a/crates/biome_js_syntax/src/generated/kind.rs b/crates/biome_js_syntax/src/generated/kind.rs index c0e024b42e8c..dd426589255f 100644 --- a/crates/biome_js_syntax/src/generated/kind.rs +++ b/crates/biome_js_syntax/src/generated/kind.rs @@ -499,6 +499,7 @@ pub enum JsSyntaxKind { JSX_EXPRESSION_CHILD, JSX_SPREAD_CHILD, JSX_STRING, + JS_GRIT_METAVARIABLE, JS_BOGUS, JS_BOGUS_EXPRESSION, JS_BOGUS_STATEMENT, diff --git a/crates/biome_service/src/workspace/server.rs b/crates/biome_service/src/workspace/server.rs index f5223ea1af4c..0cf78d08a74d 100644 --- a/crates/biome_service/src/workspace/server.rs +++ b/crates/biome_service/src/workspace/server.rs @@ -757,7 +757,7 @@ impl Workspace for WorkspaceServer { ) -> Result { let pattern = biome_grit_patterns::compile_pattern( ¶ms.pattern, - biome_grit_patterns::GritTargetLanguage, + biome_grit_patterns::JsTargetLanguage.into(), )?; let pattern_id = PatternId::from("1234"); // TODO: Generate a real ID. self.patterns.insert(pattern_id.clone(), pattern); diff --git a/xtask/codegen/src/js_kinds_src.rs b/xtask/codegen/src/js_kinds_src.rs index c7da6f7fda4f..1b39ec4a3515 100644 --- a/xtask/codegen/src/js_kinds_src.rs +++ b/xtask/codegen/src/js_kinds_src.rs @@ -508,6 +508,8 @@ pub const JS_KINDS_SRC: KindsSrc = KindsSrc { "JSX_EXPRESSION_CHILD", "JSX_SPREAD_CHILD", "JSX_STRING", + // Grit metavariable + "JS_GRIT_METAVARIABLE", // bogus nodes JS "JS_BOGUS", "JS_BOGUS_EXPRESSION",