diff --git a/shared/yeast-macros/src/parse.rs b/shared/yeast-macros/src/parse.rs index 670ede99096..01694685d1f 100644 --- a/shared/yeast-macros/src/parse.rs +++ b/shared/yeast-macros/src/parse.rs @@ -623,9 +623,9 @@ pub fn parse_rule_top(input: TokenStream) -> Result { Ok(quote! { { let __query = #query_code; - yeast::Rule::new(__query, Box::new(|__ast: &mut yeast::Ast, __captures: yeast::captures::Captures, __fresh: &yeast::tree_builder::FreshScope| { + yeast::Rule::new(__query, Box::new(|__ast: &mut yeast::Ast, __captures: yeast::captures::Captures, __fresh: &yeast::tree_builder::FreshScope, __source_range: Option| { #(#bindings)* - let mut #ctx_ident = yeast::build::BuildCtx::new(__ast, &__captures, __fresh); + let mut #ctx_ident = yeast::build::BuildCtx::with_source_range(__ast, &__captures, __fresh, __source_range); #transform_body })) } diff --git a/shared/yeast/src/build.rs b/shared/yeast/src/build.rs index 81cfea78f85..a108a487d32 100644 --- a/shared/yeast/src/build.rs +++ b/shared/yeast/src/build.rs @@ -13,6 +13,8 @@ pub struct BuildCtx<'a> { pub ast: &'a mut Ast, pub captures: &'a Captures, pub fresh: &'a FreshScope, + /// Source range of the matched node, inherited by synthetic nodes. + pub source_range: Option, } impl<'a> BuildCtx<'a> { @@ -21,6 +23,16 @@ impl<'a> BuildCtx<'a> { ast, captures, fresh, + source_range: None, + } + } + + pub fn with_source_range(ast: &'a mut Ast, captures: &'a Captures, fresh: &'a FreshScope, source_range: Option) -> Self { + Self { + ast, + captures, + fresh, + source_range, } } @@ -53,18 +65,18 @@ impl<'a> BuildCtx<'a> { }) .collect(); self.ast - .create_node(kind_id, NodeContent::DynamicString(String::new()), field_map, true) + .create_node_with_range(kind_id, NodeContent::DynamicString(String::new()), field_map, true, self.source_range) } /// Create a leaf node with a fixed string content. pub fn literal(&mut self, kind: &'static str, value: &str) -> Id { - self.ast.create_named_token(kind, value.to_string()) + self.ast.create_named_token_with_range(kind, value.to_string(), self.source_range) } /// Create a leaf node with an auto-generated unique name. pub fn fresh(&mut self, kind: &'static str, name: &str) -> Id { let generated = self.fresh.resolve(name); - self.ast.create_named_token(kind, generated) + self.ast.create_named_token_with_range(kind, generated, self.source_range) } /// Create a node for unnamed children (the synthetic "child" field). @@ -87,6 +99,6 @@ impl<'a> BuildCtx<'a> { field_map.insert(CHILD_FIELD, children); } self.ast - .create_node(kind_id, NodeContent::DynamicString(String::new()), field_map, true) + .create_node_with_range(kind_id, NodeContent::DynamicString(String::new()), field_map, true, self.source_range) } } diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index 8141f84613f..59a21f6a37c 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -205,6 +205,17 @@ impl Ast { content: NodeContent, fields: BTreeMap>, is_named: bool, + ) -> Id { + self.create_node_with_range(kind, content, children, is_named, None) + } + + pub fn create_node_with_range( + &mut self, + kind: KindId, + content: NodeContent, + children: Vec<(FieldId, Id)>, + is_named: bool, + source_range: Option, ) -> Id { let id = self.nodes.len(); self.nodes.push(Node { @@ -217,11 +228,16 @@ impl Ast { is_error: false, is_extra: false, is_named, + source_range, }); id } pub fn create_named_token(&mut self, kind: &'static str, content: String) -> Id { + self.create_named_token_with_range(kind, content, None) + } + + pub fn create_named_token_with_range(&mut self, kind: &'static str, content: String, source_range: Option) -> Id { let kind_id = self.language.id_for_node_kind(kind, true); let id = self.nodes.len(); self.nodes.push(Node { @@ -231,6 +247,7 @@ impl Ast { is_named: true, is_missing: false, is_error: false, + source_range, is_extra: false, fields: BTreeMap::new(), content: NodeContent::DynamicString(content), @@ -318,6 +335,7 @@ impl Ast { content: NodeContent::String("x = 1"), is_missing: false, is_error: false, + source_range: None, is_extra: false, is_named: true, }, @@ -330,6 +348,7 @@ impl Ast { content: NodeContent::String("x"), is_missing: false, is_error: false, + source_range: None, is_extra: false, is_named: true, }, @@ -342,6 +361,7 @@ impl Ast { content: NodeContent::String("="), is_missing: false, is_error: false, + source_range: None, is_extra: false, is_named: false, }, @@ -354,6 +374,7 @@ impl Ast { content: NodeContent::String("1"), is_missing: false, is_error: false, + source_range: None, is_extra: false, is_named: true, }, @@ -388,6 +409,10 @@ pub struct Node { kind_name: &'static str, pub(crate) fields: BTreeMap>, pub(crate) content: NodeContent, + /// For synthetic nodes, the source range of the original node they + /// were desugared from. Used for location information in TRAP output. + #[serde(skip)] + source_range: Option, is_named: bool, is_missing: bool, is_extra: bool, @@ -439,28 +464,34 @@ impl Node { pub fn start_position(&self) -> tree_sitter::Point { match self.content { NodeContent::Range(range) => range.start_point, - _ => self.fake_point(), + _ => self.source_range.map_or_else( + || self.fake_point(), + |r| r.start_point, + ), } } pub fn end_position(&self) -> tree_sitter::Point { match self.content { NodeContent::Range(range) => range.end_point, - _ => self.fake_point(), + _ => self.source_range.map_or_else( + || self.fake_point(), + |r| r.end_point, + ), } } pub fn start_byte(&self) -> usize { match self.content { NodeContent::Range(range) => range.start_byte, - _ => 0, + _ => self.source_range.map_or(0, |r| r.start_byte), } } pub fn end_byte(&self) -> usize { match self.content { NodeContent::Range(range) => range.end_byte, - _ => 0, + _ => self.source_range.map_or(0, |r| r.end_byte), } } @@ -500,11 +531,11 @@ impl From for NodeContent { pub struct Rule { query: QueryNode, - transform: Box Vec>, + transform: Box) -> Vec>, } impl Rule { - pub fn new(query: QueryNode, transform: Box Vec>) -> Self { + pub fn new(query: QueryNode, transform: Box) -> Vec>) -> Self { Self { query, transform } } @@ -512,7 +543,14 @@ impl Rule { let mut captures = Captures::new(); if self.query.do_match(ast, node, &mut captures).unwrap() { fresh.next_scope(); - Some((self.transform)(ast, captures, fresh)) + // Get the source range of the matched node for location info + let source_range = ast.get_node(node).and_then(|n| { + match n.content { + NodeContent::Range(r) => Some(r), + _ => n.source_range, + } + }); + Some((self.transform)(ast, captures, fresh, source_range)) } else { None } diff --git a/shared/yeast/src/visitor.rs b/shared/yeast/src/visitor.rs index ed6acfca0ea..b8e5f4469cb 100644 --- a/shared/yeast/src/visitor.rs +++ b/shared/yeast/src/visitor.rs @@ -68,6 +68,7 @@ impl Visitor { is_named: n.is_named(), is_extra: n.is_extra(), is_error: n.is_error(), + source_range: None, }, parent: self.current, });