Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for syntax_type and definiens_node #319

Merged
merged 6 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 82 additions & 21 deletions tree-sitter-stack-graphs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,35 @@
//! }
//! ```
//!
//! ### Annotating nodes with syntax type information
//!
//! You can annotate any stack graph node with information about its syntax type. To do this, add a `syntax_type`
//! attribute, whose value is a string indicating the syntax type.
//!
//! ``` skip
//! (function_definition name: (identifier) @id) @func {
//! node def
//! ; ...
//! attr (def) syntax_type = "function"
//! }
//! ```
//!
//! ### Annotating definitions with definiens information
//!
//! You cannot annotate definitions with a definiens, which is the thing the definition covers. For example, for
//! a function definition, the definiens would be the function body. To do this, add a `definiens_node` attribute,
//! whose value is a syntax node that spans the definiens.
//!
//! ``` skip
//! (function_definition name: (identifier) @id body: (_) @body) @func {
//! node def
//! ; ...
//! attr (def) definiens_node = @body
//! }
//! ```
//!
//! Definiens are optional and setting them to `#null` explicitly is allowed.
//!
//! ### Connecting stack graph nodes with edges
//!
//! To connect two stack graph nodes, use the `edge` statement to add an edge between them:
Expand Down Expand Up @@ -364,6 +393,7 @@ static SCOPE_TYPE: &'static str = "scope";

// Node attribute names
static DEBUG_ATTR_PREFIX: &'static str = "debug_";
static DEFINIENS_NODE: &'static str = "definiens_node";
static EMPTY_SOURCE_SPAN_ATTR: &'static str = "empty_source_span";
static IS_DEFINITION_ATTR: &'static str = "is_definition";
static IS_ENDPOINT_ATTR: &'static str = "is_endpoint";
Expand All @@ -372,13 +402,14 @@ static IS_REFERENCE_ATTR: &'static str = "is_reference";
static SCOPE_ATTR: &'static str = "scope";
static SOURCE_NODE_ATTR: &'static str = "source_node";
static SYMBOL_ATTR: &'static str = "symbol";
static SYNTAX_TYPE: &'static str = "syntax_type";
static TYPE_ATTR: &'static str = "type";

// Expected attributes per node type
static POP_SCOPED_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> =
Lazy::new(|| HashSet::from([TYPE_ATTR, SYMBOL_ATTR, IS_DEFINITION_ATTR]));
Lazy::new(|| HashSet::from([TYPE_ATTR, SYMBOL_ATTR, IS_DEFINITION_ATTR, DEFINIENS_NODE]));
static POP_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> =
Lazy::new(|| HashSet::from([TYPE_ATTR, SYMBOL_ATTR, IS_DEFINITION_ATTR]));
Lazy::new(|| HashSet::from([TYPE_ATTR, SYMBOL_ATTR, IS_DEFINITION_ATTR, DEFINIENS_NODE]));
static PUSH_SCOPED_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> =
Lazy::new(|| HashSet::from([TYPE_ATTR, SYMBOL_ATTR, SCOPE_ATTR, IS_REFERENCE_ATTR]));
static PUSH_SYMBOL_ATTRS: Lazy<HashSet<&'static str>> =
Expand Down Expand Up @@ -885,7 +916,7 @@ impl<'a> Builder<'a> {
NodeType::PushSymbol => self.load_push_symbol(node_ref)?,
NodeType::Scope => self.load_scope(node_ref)?,
};
self.load_span(node_ref, handle)?;
self.load_source_info(node_ref, handle)?;
self.load_node_debug_info(node_ref, handle)?;
}

Expand Down Expand Up @@ -1004,10 +1035,14 @@ impl<'a> Builder<'a> {
let id = self.node_id_for_graph_node(node_ref);
let is_definition = self.load_flag(node, IS_DEFINITION_ATTR)?;
self.verify_attributes(node, POP_SCOPED_SYMBOL_TYPE, &POP_SCOPED_SYMBOL_ATTRS);
Ok(self
let node_handle = self
.stack_graph
.add_pop_scoped_symbol_node(id, symbol, is_definition)
.unwrap())
.unwrap();
if is_definition {
self.load_definiens_info(node_ref, node_handle)?;
}
Ok(node_handle)
}

fn load_pop_symbol(&mut self, node_ref: GraphNodeRef) -> Result<Handle<Node>, BuildError> {
Expand All @@ -1020,10 +1055,14 @@ impl<'a> Builder<'a> {
let id = self.node_id_for_graph_node(node_ref);
let is_definition = self.load_flag(node, IS_DEFINITION_ATTR)?;
self.verify_attributes(node, POP_SYMBOL_TYPE, &POP_SYMBOL_ATTRS);
Ok(self
let node_handle = self
.stack_graph
.add_pop_symbol_node(id, symbol, is_definition)
.unwrap())
.unwrap();
if is_definition {
self.load_definiens_info(node_ref, node_handle)?;
}
Ok(node_handle)
}

fn load_push_scoped_symbol(
Expand Down Expand Up @@ -1091,28 +1130,50 @@ impl<'a> Builder<'a> {
}
}

fn load_span(
fn load_source_info(
&mut self,
node_ref: GraphNodeRef,
node_handle: Handle<Node>,
) -> Result<(), BuildError> {
let node = &self.graph[node_ref];
let source_node = match node.attributes.get(SOURCE_NODE_ATTR) {
Some(source_node) => &self.graph[source_node.as_syntax_node_ref()?],
None => return Ok(()),
if let Some(source_node) = node.attributes.get(SOURCE_NODE_ATTR) {
let source_node = &self.graph[source_node.as_syntax_node_ref()?];
let mut span = self.span_calculator.for_node(source_node);
if match node.attributes.get(EMPTY_SOURCE_SPAN_ATTR) {
Some(empty_source_span) => empty_source_span.as_boolean()?,
None => false,
} {
span.end = span.start.clone();
}
let containing_line = &self.source[span.start.containing_line.clone()];
let containing_line = self.stack_graph.add_string(containing_line);
let source_info = self.stack_graph.source_info_mut(node_handle);
source_info.span = span;
source_info.containing_line = ControlledOption::some(containing_line);
};
let mut span = self.span_calculator.for_node(source_node);
if match node.attributes.get(EMPTY_SOURCE_SPAN_ATTR) {
Some(empty_source_span) => empty_source_span.as_boolean()?,
None => false,
} {
span.end = span.start.clone();
if let Some(syntax_type) = node.attributes.get(SYNTAX_TYPE) {
let syntax_type = syntax_type.as_str()?;
let interned_string = self.stack_graph.add_string(syntax_type);
let source_info = self.stack_graph.source_info_mut(node_handle);
source_info.syntax_type = interned_string.into();
}
let containing_line = &self.source[span.start.containing_line.clone()];
let containing_line = self.stack_graph.add_string(containing_line);
Ok(())
}

fn load_definiens_info(
&mut self,
node_ref: GraphNodeRef,
node_handle: Handle<Node>,
) -> Result<(), BuildError> {
let node = &self.graph[node_ref];
let definiens_node = match node.attributes.get(DEFINIENS_NODE) {
Some(Value::Null) => return Ok(()),
Some(definiens_node) => &self.graph[definiens_node.as_syntax_node_ref()?],
None => return Ok(()),
};
let span = self.span_calculator.for_node(definiens_node);
let source_info = self.stack_graph.source_info_mut(node_handle);
source_info.span = span;
source_info.containing_line = ControlledOption::some(containing_line);
source_info.definiens_span = span;
Ok(())
}

Expand Down
73 changes: 73 additions & 0 deletions tree-sitter-stack-graphs/tests/it/nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,76 @@ fn can_calculate_spans() {
let trimmed_line = &python[source_info.span.start.trimmed_line.clone()];
assert_eq!(trimmed_line, "a");
}

#[test]
fn can_set_definiens() {
let tsg = r#"
(function_definition name:(_)@name body:(_)@body) {
node result
attr (result) type = "pop_symbol", symbol = (source-text @name), source_node = @name, is_definition
attr (result) definiens_node = @body
}
"#;
let python = r#"
def foo():
pass
"#;

let (graph, file) = build_stack_graph(python, tsg).unwrap();
let node_handle = graph.nodes_for_file(file).next().unwrap();
let source_info = graph.source_info(node_handle).unwrap();

let actual_span = format!(
"{}:{}-{}:{}",
source_info.definiens_span.start.line,
source_info.definiens_span.start.column.utf8_offset,
source_info.definiens_span.end.line,
source_info.definiens_span.end.column.utf8_offset,
);
assert_eq!("2:8-2:12", actual_span)
}

#[test]
fn can_set_null_definiens() {
let tsg = r#"
(function_definition name:(_)@name) {
node result
attr (result) type = "pop_symbol", symbol = (source-text @name), source_node = @name, is_definition
attr (result) definiens_node = #null
}
"#;
let python = r#"
def foo():
pass
"#;

let (graph, file) = build_stack_graph(python, tsg).unwrap();
let node_handle = graph.nodes_for_file(file).next().unwrap();
let source_info = graph.source_info(node_handle).unwrap();
assert_eq!(lsp_positions::Span::default(), source_info.definiens_span)
}

#[test]
fn can_set_syntax_type() {
let tsg = r#"
(function_definition) {
node result
attr (result) syntax_type = "function"
}
"#;
let python = r#"
def foo():
pass
"#;

let (graph, file) = build_stack_graph(python, tsg).unwrap();
let node_handle = graph.nodes_for_file(file).next().unwrap();
let source_info = graph.source_info(node_handle).unwrap();

let syntax_type = source_info
.syntax_type
.into_option()
.map(|s| &graph[s])
.unwrap_or("MISSING");
assert_eq!("function", syntax_type)
}
Loading