diff --git a/languages/tree-sitter-stack-graphs-python/src/stack-graphs.tsg b/languages/tree-sitter-stack-graphs-python/src/stack-graphs.tsg index 6b5efd35f..5fb407ae8 100644 --- a/languages/tree-sitter-stack-graphs-python/src/stack-graphs.tsg +++ b/languages/tree-sitter-stack-graphs-python/src/stack-graphs.tsg @@ -13,6 +13,7 @@ ;; ^^^^^^^^^^^^^^^^ global FILE_PATH +global ROOT_PATH = "" global ROOT_NODE global JUMP_TO_SCOPE_NODE @@ -272,7 +273,9 @@ inherit .parent_module node grandparent_module_ref_node var grandparent_module_ref = grandparent_module_ref_node - scan FILE_PATH { + ; get the file path relative to the root path + let rel_path = (replace FILE_PATH ROOT_PATH "") + scan rel_path { "([^/]+)/" { node def_dot diff --git a/languages/tree-sitter-stack-graphs-python/test/root_path.py b/languages/tree-sitter-stack-graphs-python/test/root_path.py new file mode 100644 index 000000000..23937db7c --- /dev/null +++ b/languages/tree-sitter-stack-graphs-python/test/root_path.py @@ -0,0 +1,21 @@ +# ------ path: foo/bar/module.py -----------# +# ------ global: ROOT_PATH=foo/bar -----------# + +foo = 42 + +# ------ path: foo/bar/baz/module.py -----------# +# ------ global: ROOT_PATH=foo/bar -----------# + +bar = "hello" + +# ------ path: foo/bar/main.py -------------# +# ------ global: ROOT_PATH=foo/bar -----------# + +from module import foo +from baz.module import bar + +print(foo) +# ^ defined: 4, 14 + +print(bar) +# ^ defined: 9, 15 diff --git a/tree-sitter-stack-graphs/src/cli/index.rs b/tree-sitter-stack-graphs/src/cli/index.rs index e8704f99d..6425d8ca7 100644 --- a/tree-sitter-stack-graphs/src/cli/index.rs +++ b/tree-sitter-stack-graphs/src/cli/index.rs @@ -42,6 +42,7 @@ use crate::BuildError; use crate::CancelAfterDuration; use crate::CancellationFlag; use crate::NoCancellation; +use crate::{FILE_PATH_VAR, ROOT_PATH_VAR}; #[derive(Args)] pub struct IndexArgs { @@ -429,7 +430,16 @@ impl<'a> Indexer<'a> { ) -> std::result::Result<(), BuildErrorWithSource<'b>> { let relative_source_path = source_path.strip_prefix(source_root).unwrap(); if let Some(lc) = lcs.primary { - let globals = Variables::new(); + let mut globals = Variables::new(); + + globals + .add(FILE_PATH_VAR.into(), source_path.to_str().unwrap().into()) + .expect("failed to add file path variable"); + + globals + .add(ROOT_PATH_VAR.into(), source_root.to_str().unwrap().into()) + .expect("failed to add root path variable"); + lc.sgl .build_stack_graph_into(graph, file, source, &globals, cancellation_flag) .map_err(|inner| BuildErrorWithSource { diff --git a/tree-sitter-stack-graphs/src/cli/test.rs b/tree-sitter-stack-graphs/src/cli/test.rs index c7f73e7ce..790a6c245 100644 --- a/tree-sitter-stack-graphs/src/cli/test.rs +++ b/tree-sitter-stack-graphs/src/cli/test.rs @@ -43,6 +43,7 @@ use crate::test::Test; use crate::test::TestResult; use crate::CancelAfterDuration; use crate::CancellationFlag; +use crate::FILE_PATH_VAR; #[derive(Args)] #[clap(after_help = r#"PATH SPECIFICATIONS: @@ -316,7 +317,18 @@ impl TestArgs { &mut Some(test_fragment.source.as_ref()), )? { globals.clear(); + test_fragment.add_globals_to(&mut globals); + + if globals.get(&FILE_PATH_VAR.into()).is_none() { + globals + .add( + FILE_PATH_VAR.into(), + test_fragment.path.to_str().unwrap().into(), + ) + .expect("failed to add file path variable"); + } + lc.sgl.build_stack_graph_into( &mut test.graph, test_fragment.file, diff --git a/tree-sitter-stack-graphs/src/lib.rs b/tree-sitter-stack-graphs/src/lib.rs index 86651b6be..1a3e344b1 100644 --- a/tree-sitter-stack-graphs/src/lib.rs +++ b/tree-sitter-stack-graphs/src/lib.rs @@ -435,9 +435,16 @@ static SCOPE_ATTRS: Lazy> = static PRECEDENCE_ATTR: &'static str = "precedence"; // Global variables -static ROOT_NODE_VAR: &'static str = "ROOT_NODE"; -static JUMP_TO_SCOPE_NODE_VAR: &'static str = "JUMP_TO_SCOPE_NODE"; -static FILE_PATH_VAR: &'static str = "FILE_PATH"; +/// Name of the variable used to pass the root node. +pub const ROOT_NODE_VAR: &'static str = "ROOT_NODE"; +/// Name of the variable used to pass the jump-to-scope node. +pub const JUMP_TO_SCOPE_NODE_VAR: &'static str = "JUMP_TO_SCOPE_NODE"; +/// Name of the variable used to pass the file path. +/// If a root path is given, it should be a descendant the root path. +pub const FILE_PATH_VAR: &'static str = "FILE_PATH"; +/// Name of the variable used to pass the root path. +/// If given, should be an ancestor of the file path. +pub const ROOT_PATH_VAR: &'static str = "ROOT_PATH"; /// Holds information about how to construct stack graphs for a particular language. pub struct StackGraphLanguage { @@ -635,22 +642,18 @@ impl<'a> Builder<'a> { let tree = parse_errors.into_tree(); let mut globals = Variables::nested(globals); + if globals.get(&ROOT_NODE_VAR.into()).is_none() { let root_node = self.inject_node(NodeID::root()); globals .add(ROOT_NODE_VAR.into(), root_node.into()) .expect("Failed to set ROOT_NODE"); } + let jump_to_scope_node = self.inject_node(NodeID::jump_to()); globals .add(JUMP_TO_SCOPE_NODE_VAR.into(), jump_to_scope_node.into()) .expect("Failed to set JUMP_TO_SCOPE_NODE"); - if globals.get(&FILE_PATH_VAR.into()).is_none() { - let file_name = self.stack_graph[self.file].to_string(); - globals - .add(FILE_PATH_VAR.into(), file_name.into()) - .expect("Failed to set FILE_PATH"); - } let mut config = ExecutionConfig::new(&self.sgl.functions, &globals) .lazy(true) diff --git a/tree-sitter-stack-graphs/src/loader.rs b/tree-sitter-stack-graphs/src/loader.rs index 9ffc1d674..977522528 100644 --- a/tree-sitter-stack-graphs/src/loader.rs +++ b/tree-sitter-stack-graphs/src/loader.rs @@ -29,6 +29,9 @@ use tree_sitter_loader::Loader as TsLoader; use crate::CancellationFlag; use crate::FileAnalyzer; use crate::StackGraphLanguage; +use crate::FILE_PATH_VAR; + +const BUILTINS_FILENAME: &str = ""; pub static DEFAULT_TSG_PATHS: Lazy> = Lazy::new(|| vec![LoadPath::Grammar("queries/stack-graphs".into())]); @@ -75,10 +78,18 @@ impl LanguageConfiguration { let mut builtins = StackGraph::new(); if let Some((builtins_path, builtins_source)) = builtins_source { let mut builtins_globals = Variables::new(); + if let Some(builtins_config) = builtins_config { Loader::load_globals_from_config_str(builtins_config, &mut builtins_globals)?; } - let file = builtins.add_file("").unwrap(); + + if builtins_globals.get(&FILE_PATH_VAR.into()).is_none() { + builtins_globals + .add(FILE_PATH_VAR.into(), BUILTINS_FILENAME.into()) + .expect("failed to add file path variable"); + } + + let file = builtins.add_file(BUILTINS_FILENAME).unwrap(); sgl.build_stack_graph_into( &mut builtins, file, @@ -325,9 +336,18 @@ impl Loader { graph: &mut StackGraph, cancellation_flag: &dyn CancellationFlag, ) -> Result<(), LoadError<'a>> { - let file = graph.add_file(&path.to_string_lossy()).unwrap(); + let file_name = path.to_string_lossy(); + let file = graph.add_file(&file_name).unwrap(); let mut globals = Variables::new(); + Self::load_globals_from_config_str(&config, &mut globals)?; + + if globals.get(&FILE_PATH_VAR.into()).is_none() { + globals + .add(FILE_PATH_VAR.into(), BUILTINS_FILENAME.into()) + .expect("failed to add file path variable"); + } + sgl.build_stack_graph_into(graph, file, &source, &globals, cancellation_flag) .map_err(|err| LoadError::Builtins { inner: err, diff --git a/tree-sitter-stack-graphs/tests/it/builder.rs b/tree-sitter-stack-graphs/tests/it/builder.rs index 661e8b973..14476111e 100644 --- a/tree-sitter-stack-graphs/tests/it/builder.rs +++ b/tree-sitter-stack-graphs/tests/it/builder.rs @@ -9,6 +9,7 @@ use stack_graphs::graph::StackGraph; use tree_sitter_graph::Variables; use tree_sitter_stack_graphs::NoCancellation; use tree_sitter_stack_graphs::StackGraphLanguage; +use tree_sitter_stack_graphs::FILE_PATH_VAR; use crate::edges::check_stack_graph_edges; use crate::nodes::check_stack_graph_nodes; @@ -22,12 +23,18 @@ fn can_support_preexisting_nodes() { "#; let python = "pass"; + let file_name = "test.py"; + let mut graph = StackGraph::new(); - let file = graph.get_or_create_file("test.py"); + let file = graph.get_or_create_file(file_name); let node_id = graph.new_node_id(file); let _preexisting_node = graph.add_scope_node(node_id, true).unwrap(); - let globals = Variables::new(); + let mut globals = Variables::new(); + globals + .add(FILE_PATH_VAR.into(), file_name.into()) + .expect("failed to add file path variable"); + let language = StackGraphLanguage::from_str(tree_sitter_python::language(), tsg).unwrap(); language .build_stack_graph_into(&mut graph, file, python, &globals, &NoCancellation) @@ -45,8 +52,10 @@ fn can_support_injected_nodes() { "#; let python = "pass"; + let file_name = "test.py"; + let mut graph = StackGraph::new(); - let file = graph.get_or_create_file("test.py"); + let file = graph.get_or_create_file(file_name); let node_id = graph.new_node_id(file); let _preexisting_node = graph.add_scope_node(node_id, true).unwrap(); @@ -54,6 +63,10 @@ fn can_support_injected_nodes() { let mut builder = language.builder_into_stack_graph(&mut graph, file, python); let mut globals = Variables::new(); + globals + .add(FILE_PATH_VAR.into(), file_name.into()) + .expect("failed to add file path variable"); + globals .add("EXT_NODE".into(), builder.inject_node(node_id).into()) .expect("Failed to add EXT_NODE variable"); diff --git a/tree-sitter-stack-graphs/tests/it/main.rs b/tree-sitter-stack-graphs/tests/it/main.rs index c1e37a40e..33ee19241 100644 --- a/tree-sitter-stack-graphs/tests/it/main.rs +++ b/tree-sitter-stack-graphs/tests/it/main.rs @@ -5,6 +5,8 @@ // Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. // ------------------------------------------------------------------------------------------------ +use std::path::Path; + use stack_graphs::arena::Handle; use stack_graphs::graph::File; use stack_graphs::graph::StackGraph; @@ -12,6 +14,7 @@ use tree_sitter_graph::Variables; use tree_sitter_stack_graphs::BuildError; use tree_sitter_stack_graphs::NoCancellation; use tree_sitter_stack_graphs::StackGraphLanguage; +use tree_sitter_stack_graphs::FILE_PATH_VAR; mod builder; mod edges; @@ -23,11 +26,18 @@ pub(self) fn build_stack_graph( python_source: &str, tsg_source: &str, ) -> Result<(StackGraph, Handle), BuildError> { + let file_name = "test.py"; let language = StackGraphLanguage::from_str(tree_sitter_python::language(), tsg_source).unwrap(); let mut graph = StackGraph::new(); - let file = graph.get_or_create_file("test.py"); - let globals = Variables::new(); + let file = graph.get_or_create_file(file_name); + let mut globals = Variables::new(); + let source_path = Path::new(file_name); + + globals + .add(FILE_PATH_VAR.into(), source_path.to_str().unwrap().into()) + .expect("failed to add file path variable"); + language.build_stack_graph_into(&mut graph, file, python_source, &globals, &NoCancellation)?; Ok((graph, file)) } diff --git a/tree-sitter-stack-graphs/tests/it/test.rs b/tree-sitter-stack-graphs/tests/it/test.rs index 98ec3ab96..4c630d57a 100644 --- a/tree-sitter-stack-graphs/tests/it/test.rs +++ b/tree-sitter-stack-graphs/tests/it/test.rs @@ -5,6 +5,7 @@ // Please see the LICENSE-APACHE or LICENSE-MIT files in this distribution for license details. // ------------------------------------------------------------------------------------------------ +use crate::FILE_PATH_VAR; use once_cell::sync::Lazy; use pretty_assertions::assert_eq; use stack_graphs::arena::Handle; @@ -94,10 +95,22 @@ fn check_test( expected_successes + expected_failures, assertion_count, ); + let mut globals = Variables::new(); for fragments in &test.fragments { globals.clear(); + fragments.add_globals_to(&mut globals); + + if globals.get(&FILE_PATH_VAR.into()).is_none() { + globals + .add( + FILE_PATH_VAR.into(), + fragments.path.to_str().unwrap().into(), + ) + .expect("failed to add file path variable"); + } + build_stack_graph_into( &mut test.graph, fragments.file,