// -*- coding: utf-8 -*- // ------------------------------------------------------------------------------------------------ // Copyright © 2021, stack-graphs authors. // Licensed under either of Apache License, Version 2.0, or MIT license, at your option. // Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. // ------------------------------------------------------------------------------------------------ use pretty_assertions::assert_eq; use stack_graphs::arena::Handle; use stack_graphs::graph::File; use stack_graphs::graph::StackGraph; use tree_sitter_stack_graphs::BuildError; use super::build_stack_graph; fn build_and_check_stack_graph_nodes( python_source: &str, tsg_source: &str, expected_nodes: &[&str], ) { let (graph, file) = build_stack_graph(python_source, tsg_source).expect("Could not load stack graph"); check_stack_graph_nodes(&graph, file, expected_nodes); } pub(super) fn check_stack_graph_nodes( graph: &StackGraph, file: Handle, expected_nodes: &[&str], ) { let actual_nodes = graph .nodes_for_file(file) .map(|handle| graph[handle].display(&graph).to_string()) .collect::>(); assert_eq!(expected_nodes, actual_nodes); } #[test] fn can_create_definition_node() { let tsg = r#" (identifier) @id { node result attr (result) type = "pop_symbol", symbol = (source-text @id), is_definition } "#; let python = "a"; build_and_check_stack_graph_nodes(python, tsg, &["[test.py(0) definition a]"]); } #[test] fn cannot_create_definition_node_without_symbol() { let tsg = r#" (identifier) { node result attr (result) type = "pop_symbol", is_definition } "#; let python = "a"; let result = build_stack_graph(python, tsg); assert!(matches!(result, Err(BuildError::MissingSymbol(_)))); } #[test] fn can_create_drop_node() { let tsg = r#" (identifier) { node result attr (result) type = "drop_scopes" } "#; let python = "a"; build_and_check_stack_graph_nodes(python, tsg, &["[test.py(0) drop scopes]"]); } #[test] fn can_create_exported_node() { let tsg = r#" (identifier) { node result attr (result) is_exported } "#; let python = "a"; build_and_check_stack_graph_nodes(python, tsg, &["[test.py(0) exported scope]"]); } #[test] fn can_create_endpoint_node() { let tsg = r#" (identifier) { node result attr (result) is_endpoint } "#; let python = "a"; build_and_check_stack_graph_nodes(python, tsg, &["[test.py(0) exported scope]"]); } #[test] fn can_create_implicit_internal_node() { let tsg = r#" (identifier) { node result } "#; let python = "a"; build_and_check_stack_graph_nodes(python, tsg, &["[test.py(0) scope]"]); } #[test] fn can_create_explicit_internal_node() { let tsg = r#" (identifier) { node result attr (result) type = "scope" } "#; let python = "a"; build_and_check_stack_graph_nodes(python, tsg, &["[test.py(0) scope]"]); } #[test] fn can_create_pop_symbol_node() { let tsg = r#" (identifier) @id { node result attr (result) type = "pop_symbol", symbol = (source-text @id) } "#; let python = "a"; build_and_check_stack_graph_nodes(python, tsg, &["[test.py(0) pop a]"]); } #[test] fn cannot_create_pop_symbol_node_without_symbol() { let tsg = r#" (identifier) { node result attr (result) type = "pop_symbol" } "#; let python = "a"; let result = build_stack_graph(python, tsg); assert!(matches!(result, Err(BuildError::MissingSymbol(_)))); } #[test] fn can_create_pop_scoped_symbol_node() { let tsg = r#" (identifier) @id { node result attr (result) type = "pop_scoped_symbol", symbol = (source-text @id) } "#; let python = "a"; build_and_check_stack_graph_nodes(python, tsg, &["[test.py(0) pop scoped a]"]); } #[test] fn cannot_create_pop_scoped_symbol_node_without_symbol() { let tsg = r#" (identifier) { node result attr (result) type = "pop_scoped_symbol" } "#; let python = "a"; let result = build_stack_graph(python, tsg); assert!(matches!(result, Err(BuildError::MissingSymbol(_)))); } #[test] fn can_create_push_node() { let tsg = r#" (identifier) @id { node result attr (result) type = "push_symbol", symbol = (source-text @id) } "#; let python = "a"; build_and_check_stack_graph_nodes(python, tsg, &["[test.py(0) push a]"]); } #[test] fn cannot_create_push_symbol_node_without_symbol() { let tsg = r#" (identifier) { node result attr (result) type = "push_symbol" } "#; let python = "a"; let result = build_stack_graph(python, tsg); assert!(matches!(result, Err(BuildError::MissingSymbol(_)))); } #[test] fn can_create_push_scoped_node() { let tsg = r#" (identifier) @id { node scope attr (scope) is_exported node result attr (result) type = "push_scoped_symbol", symbol = (source-text @id), scope = scope } "#; let python = "a"; build_and_check_stack_graph_nodes( python, tsg, &[ "[test.py(0) exported scope]", // "[test.py(1) push scoped a test.py(0)]", ], ); } #[test] fn cannot_create_push_scoped_symbol_node_without_symbol() { let tsg = r#" (identifier) { node scope attr (scope) is_exported node result attr (result) type = "push_scoped_symbol", scope = scope } "#; let python = "a"; let result = build_stack_graph(python, tsg); assert!(matches!(result, Err(BuildError::MissingSymbol(_)))); } #[test] fn can_create_reference_node() { let tsg = r#" (identifier) @id { node result attr (result) type = "push_symbol", symbol = (source-text @id), is_reference } "#; let python = "a"; build_and_check_stack_graph_nodes(python, tsg, &["[test.py(0) reference a]"]); } #[test] fn cannot_create_reference_node_without_symbol() { let tsg = r#" (identifier) { node result attr (result) type = "push_symbol", is_reference } "#; let python = "a"; let result = build_stack_graph(python, tsg); assert!(matches!(result, Err(BuildError::MissingSymbol(_)))); } #[test] fn can_calculate_spans() { let tsg = r#" (identifier) @id { node result attr (result) type = "pop_symbol", symbol = "test", source_node = @id, is_definition } "#; let python = " a "; 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 span = format!( "{}:{}-{}:{}", source_info.span.start.line, source_info.span.start.column.utf8_offset, source_info.span.end.line, source_info.span.end.column.utf8_offset, ); assert_eq!("0:2-0:3", span); let containing_line = source_info.containing_line.into_option().unwrap(); let containing_line = &graph[containing_line]; assert_eq!(containing_line, " a "); 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) }