mirror of
https://github.com/github/codeql.git
synced 2026-05-14 19:29:28 +02:00
Yeast: Propagate source locations to synthetic nodes
Synthetic nodes created by desugaring rules now inherit the source range of the original matched node. This fixes invalid TRAP locations (previously (0,0)-(0,0)) for desugared nodes. - Node gains a source_range field used as fallback for position/byte methods when content is not a Range - BuildCtx stores the matched node's range and passes it to all created nodes - Rule::try_rule extracts the source range from the matched node and passes it through the transform closure Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -623,9 +623,9 @@ pub fn parse_rule_top(input: TokenStream) -> Result<TokenStream> {
|
||||
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<tree_sitter::Range>| {
|
||||
#(#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
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -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<tree_sitter::Range>,
|
||||
}
|
||||
|
||||
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<tree_sitter::Range>) -> 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,17 @@ impl Ast {
|
||||
content: NodeContent,
|
||||
fields: BTreeMap<FieldId, Vec<Id>>,
|
||||
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<tree_sitter::Range>,
|
||||
) -> 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<tree_sitter::Range>) -> 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<FieldId, Vec<Id>>,
|
||||
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<tree_sitter::Range>,
|
||||
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<tree_sitter::Range> for NodeContent {
|
||||
|
||||
pub struct Rule {
|
||||
query: QueryNode,
|
||||
transform: Box<dyn Fn(&mut Ast, Captures, &tree_builder::FreshScope) -> Vec<Id>>,
|
||||
transform: Box<dyn Fn(&mut Ast, Captures, &tree_builder::FreshScope, Option<tree_sitter::Range>) -> Vec<Id>>,
|
||||
}
|
||||
|
||||
impl Rule {
|
||||
pub fn new(query: QueryNode, transform: Box<dyn Fn(&mut Ast, Captures, &tree_builder::FreshScope) -> Vec<Id>>) -> Self {
|
||||
pub fn new(query: QueryNode, transform: Box<dyn Fn(&mut Ast, Captures, &tree_builder::FreshScope, Option<tree_sitter::Range>) -> Vec<Id>>) -> 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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user