mz_deploy/lsp/
references.rs1use crate::project::compiler::cache::ProjectCache;
32use std::path::Path;
33use tower_lsp::lsp_types::{Location, Range, Url};
34
35use super::goto_definition;
36
37pub(super) fn find_references(
46 parts: &[String],
47 file_uri: &Url,
48 root: &Path,
49 project_cache: &ProjectCache,
50 include_declaration: bool,
51) -> Vec<Location> {
52 let id = match goto_definition::resolve_object_id(parts, file_uri, root) {
53 Some(id) => id,
54 None => return Vec::new(),
55 };
56
57 let mut locations = Vec::new();
58
59 if include_declaration {
60 if let Some(cached_obj) = project_cache.get_object(&id) {
61 if let Some(loc) = file_location(root, &cached_obj.file_path) {
62 locations.push(loc);
63 }
64 }
65 }
66
67 for dep_id in project_cache.get_dependents(&id) {
68 if let Some(cached_obj) = project_cache.get_object(&dep_id) {
69 if let Some(loc) = file_location(root, &cached_obj.file_path) {
70 locations.push(loc);
71 }
72 }
73 }
74
75 locations
76}
77
78fn file_location(root: &Path, file_path: &str) -> Option<Location> {
80 let full_path = root.join(file_path);
81 let uri = Url::from_file_path(&full_path).ok()?;
82 Some(Location {
83 uri,
84 range: Range::default(),
85 })
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::project::compiler::cache::ProjectCache;
92 use std::path::Path;
93
94 #[cfg_attr(miri, ignore)] #[mz_ore::test]
96 fn object_with_dependents() {
97 let (root, cache) = build_test_project_cache();
98 let file_uri = Url::from_file_path(root.path().join("models/mydb/public/bar.sql")).unwrap();
99
100 let locations =
102 find_references(&["foo".to_string()], &file_uri, root.path(), &cache, false);
103 assert_eq!(locations.len(), 1);
104 let expected = Url::from_file_path(root.path().join("models/mydb/public/bar.sql")).unwrap();
105 assert_eq!(locations[0].uri, expected);
106 }
107
108 #[cfg_attr(miri, ignore)] #[mz_ore::test]
110 fn object_with_dependents_include_declaration() {
111 let (root, cache) = build_test_project_cache();
112 let file_uri = Url::from_file_path(root.path().join("models/mydb/public/bar.sql")).unwrap();
113
114 let locations = find_references(&["foo".to_string()], &file_uri, root.path(), &cache, true);
115 assert_eq!(locations.len(), 2);
116 let def_uri = Url::from_file_path(root.path().join("models/mydb/public/foo.sql")).unwrap();
118 assert_eq!(locations[0].uri, def_uri);
119 }
120
121 #[cfg_attr(miri, ignore)] #[mz_ore::test]
123 fn object_with_no_dependents() {
124 let (root, cache) = build_test_project_cache();
125 let file_uri = Url::from_file_path(root.path().join("models/mydb/public/foo.sql")).unwrap();
126
127 let locations =
129 find_references(&["bar".to_string()], &file_uri, root.path(), &cache, false);
130 assert!(locations.is_empty());
131 }
132
133 #[cfg_attr(miri, ignore)] #[mz_ore::test]
135 fn unknown_identifier_returns_empty() {
136 let (root, cache) = build_test_project_cache();
137 let file_uri = Url::from_file_path(root.path().join("models/mydb/public/bar.sql")).unwrap();
138
139 let locations = find_references(
140 &["nonexistent".to_string()],
141 &file_uri,
142 root.path(),
143 &cache,
144 false,
145 );
146 assert!(locations.is_empty());
147 }
148
149 #[cfg_attr(miri, ignore)] #[mz_ore::test]
151 fn transitive_dependents() {
152 let (root, cache) = build_chain_project_cache();
153 let file_uri = Url::from_file_path(root.path().join("models/mydb/public/c.sql")).unwrap();
154
155 let locations = find_references(&["a".to_string()], &file_uri, root.path(), &cache, false);
157 assert_eq!(locations.len(), 1);
158 let expected = Url::from_file_path(root.path().join("models/mydb/public/b.sql")).unwrap();
159 assert_eq!(locations[0].uri, expected);
160 }
161
162 fn build_test_project_cache() -> (tempfile::TempDir, ProjectCache) {
164 let root = tempfile::tempdir().unwrap();
165 let models = root.path().join("models/mydb/public");
166 std::fs::create_dir_all(&models).unwrap();
167 std::fs::write(models.join("foo.sql"), "CREATE VIEW foo AS SELECT 1 AS id;").unwrap();
168 std::fs::write(
169 models.join("bar.sql"),
170 "CREATE VIEW bar AS SELECT * FROM foo;",
171 )
172 .unwrap();
173 write_project_toml(root.path());
174
175 let _project = crate::project::plan_sync(
176 &crate::fs::FileSystem::new(),
177 root.path(),
178 None,
179 None,
180 &Default::default(),
181 )
182 .expect("project should compile");
183 let cache = ProjectCache::open(root.path(), "", None, &Default::default())
184 .expect("cache should open")
185 .expect("cache DB should exist");
186 (root, cache)
187 }
188
189 fn build_chain_project_cache() -> (tempfile::TempDir, ProjectCache) {
191 let root = tempfile::tempdir().unwrap();
192 let models = root.path().join("models/mydb/public");
193 std::fs::create_dir_all(&models).unwrap();
194 std::fs::write(models.join("a.sql"), "CREATE VIEW a AS SELECT 1 AS id;").unwrap();
195 std::fs::write(models.join("b.sql"), "CREATE VIEW b AS SELECT * FROM a;").unwrap();
196 std::fs::write(models.join("c.sql"), "CREATE VIEW c AS SELECT * FROM b;").unwrap();
197 write_project_toml(root.path());
198
199 let _project = crate::project::plan_sync(
200 &crate::fs::FileSystem::new(),
201 root.path(),
202 None,
203 None,
204 &Default::default(),
205 )
206 .expect("project should compile");
207 let cache = ProjectCache::open(root.path(), "", None, &Default::default())
208 .expect("cache should open")
209 .expect("cache DB should exist");
210 (root, cache)
211 }
212
213 fn write_project_toml(root: &Path) {
214 std::fs::write(root.join("project.toml"), "[project]\nname = \"test\"\n").unwrap();
215 }
216}