aboutsummaryrefslogtreecommitdiff
path: root/crates/libanalysis
diff options
context:
space:
mode:
Diffstat (limited to 'crates/libanalysis')
-rw-r--r--crates/libanalysis/Cargo.toml1
-rw-r--r--crates/libanalysis/src/lib.rs83
-rw-r--r--crates/libanalysis/src/module_map.rs80
-rw-r--r--crates/libanalysis/tests/tests.rs72
4 files changed, 171 insertions, 65 deletions
diff --git a/crates/libanalysis/Cargo.toml b/crates/libanalysis/Cargo.toml
index a8ef5e5f4..5aca84f0e 100644
--- a/crates/libanalysis/Cargo.toml
+++ b/crates/libanalysis/Cargo.toml
@@ -4,6 +4,7 @@ version = "0.1.0"
4authors = ["Aleksey Kladov <[email protected]>"] 4authors = ["Aleksey Kladov <[email protected]>"]
5 5
6[dependencies] 6[dependencies]
7relative-path = "0.3.7"
7log = "0.4.2" 8log = "0.4.2"
8failure = "0.1.2" 9failure = "0.1.2"
9parking_lot = "0.6.3" 10parking_lot = "0.6.3"
diff --git a/crates/libanalysis/src/lib.rs b/crates/libanalysis/src/lib.rs
index fe2c3c2e6..96d10a087 100644
--- a/crates/libanalysis/src/lib.rs
+++ b/crates/libanalysis/src/lib.rs
@@ -8,13 +8,13 @@ extern crate libsyntax2;
8extern crate libeditor; 8extern crate libeditor;
9extern crate fst; 9extern crate fst;
10extern crate rayon; 10extern crate rayon;
11extern crate relative_path;
11 12
12mod symbol_index; 13mod symbol_index;
13mod module_map; 14mod module_map;
14 15
15use std::{ 16use std::{
16 fmt, 17 fmt,
17 path::{Path, PathBuf},
18 panic, 18 panic,
19 sync::{ 19 sync::{
20 Arc, 20 Arc,
@@ -24,6 +24,7 @@ use std::{
24 time::Instant, 24 time::Instant,
25}; 25};
26 26
27use relative_path::{RelativePath,RelativePathBuf};
27use once_cell::sync::OnceCell; 28use once_cell::sync::OnceCell;
28use rayon::prelude::*; 29use rayon::prelude::*;
29 30
@@ -37,13 +38,16 @@ use libeditor::{Diagnostic, LineIndex, FileSymbol, find_node_at_offset};
37 38
38use self::{ 39use self::{
39 symbol_index::FileSymbols, 40 symbol_index::FileSymbols,
40 module_map::{ModuleMap, ChangeKind}, 41 module_map::{ModuleMap, ChangeKind, Problem},
41}; 42};
42pub use self::symbol_index::Query; 43pub use self::symbol_index::Query;
43 44
44pub type Result<T> = ::std::result::Result<T, ::failure::Error>; 45pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
45 46
46pub type FileResolver = dyn Fn(FileId, &Path) -> Option<FileId> + Send + Sync; 47pub trait FileResolver: Send + Sync + 'static {
48 fn file_stem(&self, id: FileId) -> String;
49 fn resolve(&self, id: FileId, path: &RelativePath) -> Option<FileId>;
50}
47 51
48#[derive(Debug)] 52#[derive(Debug)]
49pub struct WorldState { 53pub struct WorldState {
@@ -84,7 +88,7 @@ impl WorldState {
84 88
85 pub fn snapshot( 89 pub fn snapshot(
86 &self, 90 &self,
87 file_resolver: impl Fn(FileId, &Path) -> Option<FileId> + 'static + Send + Sync, 91 file_resolver: impl FileResolver,
88 ) -> World { 92 ) -> World {
89 World { 93 World {
90 needs_reindex: AtomicBool::new(false), 94 needs_reindex: AtomicBool::new(false),
@@ -132,8 +136,20 @@ impl WorldState {
132} 136}
133 137
134#[derive(Debug)] 138#[derive(Debug)]
135pub enum QuickFix { 139pub struct QuickFix {
136 CreateFile(PathBuf), 140 pub fs_ops: Vec<FsOp>,
141}
142
143#[derive(Debug)]
144pub enum FsOp {
145 CreateFile {
146 anchor: FileId,
147 path: RelativePathBuf,
148 },
149 MoveFile {
150 file: FileId,
151 path: RelativePathBuf,
152 }
137} 153}
138 154
139impl World { 155impl World {
@@ -221,20 +237,49 @@ impl World {
221 .into_iter() 237 .into_iter()
222 .map(|d| (d, None)) 238 .map(|d| (d, None))
223 .collect::<Vec<_>>(); 239 .collect::<Vec<_>>();
224 for module in syntax.ast().modules() { 240
225 if module.has_semi() && self.resolve_module(file_id, module).is_empty() { 241 self.data.module_map.problems(
226 if let Some(name) = module.name() { 242 file_id,
227 let d = Diagnostic { 243 &*self.file_resolver,
228 range: name.syntax().range(), 244 &|file_id| self.file_syntax(file_id).unwrap(),
229 msg: "unresolved module".to_string(), 245 |name_node, problem| {
230 }; 246 let (diag, fix) = match problem {
231 let quick_fix = self.data.module_map.suggested_child_mod_path(module) 247 Problem::UnresolvedModule { candidate } => {
232 .map(QuickFix::CreateFile); 248 let diag = Diagnostic {
233 249 range: name_node.syntax().range(),
234 res.push((d, quick_fix)) 250 msg: "unresolved module".to_string(),
235 } 251 };
252 let fix = QuickFix {
253 fs_ops: vec![FsOp::CreateFile {
254 anchor: file_id,
255 path: candidate.clone(),
256 }]
257 };
258 (diag, fix)
259 }
260 Problem::NotDirOwner { move_to, candidate } => {
261 let diag = Diagnostic {
262 range: name_node.syntax().range(),
263 msg: "can't declare module at this location".to_string(),
264 };
265 let fix = QuickFix {
266 fs_ops: vec![
267 FsOp::MoveFile {
268 file: file_id,
269 path: move_to.clone(),
270 },
271 FsOp::CreateFile {
272 anchor: file_id,
273 path: move_to.join(candidate),
274 }
275 ],
276 };
277 (diag, fix)
278 }
279 };
280 res.push((diag, Some(fix)))
236 } 281 }
237 } 282 );
238 Ok(res) 283 Ok(res)
239 } 284 }
240 285
diff --git a/crates/libanalysis/src/module_map.rs b/crates/libanalysis/src/module_map.rs
index 4f480591e..b65569c46 100644
--- a/crates/libanalysis/src/module_map.rs
+++ b/crates/libanalysis/src/module_map.rs
@@ -1,6 +1,4 @@
1use std::{ 1use relative_path::RelativePathBuf;
2 path::{PathBuf},
3};
4 2
5use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; 3use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
6use libsyntax2::{ 4use libsyntax2::{
@@ -43,6 +41,18 @@ struct Link {
43 owner: ModuleId, 41 owner: ModuleId,
44 syntax: SyntaxNode, 42 syntax: SyntaxNode,
45 points_to: Vec<ModuleId>, 43 points_to: Vec<ModuleId>,
44 problem: Option<Problem>,
45}
46
47#[derive(Clone, Debug)]
48pub enum Problem {
49 UnresolvedModule {
50 candidate: RelativePathBuf,
51 },
52 NotDirOwner {
53 move_to: RelativePathBuf,
54 candidate: RelativePathBuf,
55 }
46} 56}
47 57
48impl ModuleMap { 58impl ModuleMap {
@@ -93,9 +103,24 @@ impl ModuleMap {
93 res 103 res
94 } 104 }
95 105
96 pub fn suggested_child_mod_path(&self, m: ast::Module) -> Option<PathBuf> { 106 pub fn problems(
97 let name = m.name()?; 107 &self,
98 Some(PathBuf::from(format!("../{}.rs", name.text()))) 108 file: FileId,
109 file_resolver: &FileResolver,
110 syntax_provider: &SyntaxProvider,
111 mut cb: impl FnMut(ast::Name, &Problem),
112 ) {
113 let module = self.file2module(file);
114 let links = self.links(file_resolver, syntax_provider);
115 links
116 .links
117 .iter()
118 .filter(|link| link.owner == module)
119 .filter_map(|link| {
120 let problem = link.problem.as_ref()?;
121 Some((link, problem))
122 })
123 .for_each(|(link, problem)| cb(link.name_node(), problem))
99 } 124 }
100 125
101 fn links( 126 fn links(
@@ -176,14 +201,17 @@ impl Link {
176 owner, 201 owner,
177 syntax: module.syntax().owned(), 202 syntax: module.syntax().owned(),
178 points_to: Vec::new(), 203 points_to: Vec::new(),
204 problem: None,
179 }; 205 };
180 Some(link) 206 Some(link)
181 } 207 }
182 208
183 fn name(&self) -> SmolStr { 209 fn name(&self) -> SmolStr {
184 self.ast().name() 210 self.name_node().text()
185 .unwrap() 211 }
186 .text() 212
213 fn name_node(&self) -> ast::Name {
214 self.ast().name().unwrap()
187 } 215 }
188 216
189 fn ast(&self) -> ast::Module { 217 fn ast(&self) -> ast::Module {
@@ -192,14 +220,30 @@ impl Link {
192 } 220 }
193 221
194 fn resolve(&mut self, file_resolver: &FileResolver) { 222 fn resolve(&mut self, file_resolver: &FileResolver) {
195 let name = self.name(); 223 let mod_name = file_resolver.file_stem(self.owner.0);
196 let paths = &[ 224 let is_dir_owner =
197 PathBuf::from(format!("../{}.rs", name)), 225 mod_name == "mod" || mod_name == "lib" || mod_name == "main";
198 PathBuf::from(format!("../{}/mod.rs", name)), 226
199 ]; 227 let file_mod = RelativePathBuf::from(format!("../{}.rs", self.name()));
200 self.points_to = paths.iter() 228 let dir_mod = RelativePathBuf::from(format!("../{}/mod.rs", self.name()));
201 .filter_map(|path| file_resolver(self.owner.0, path)) 229 if is_dir_owner {
202 .map(ModuleId) 230 self.points_to = [&file_mod, &dir_mod].iter()
203 .collect(); 231 .filter_map(|path| file_resolver.resolve(self.owner.0, path))
232 .map(ModuleId)
233 .collect();
234 self.problem = if self.points_to.is_empty() {
235 Some(Problem::UnresolvedModule {
236 candidate: file_mod,
237 })
238 } else {
239 None
240 }
241 } else {
242 self.points_to = Vec::new();
243 self.problem = Some(Problem::NotDirOwner {
244 move_to: RelativePathBuf::from(format!("../{}/mod.rs", mod_name)),
245 candidate: file_mod,
246 });
247 }
204 } 248 }
205} 249}
diff --git a/crates/libanalysis/tests/tests.rs b/crates/libanalysis/tests/tests.rs
index 7c2950ccc..e378ab986 100644
--- a/crates/libanalysis/tests/tests.rs
+++ b/crates/libanalysis/tests/tests.rs
@@ -1,11 +1,39 @@
1extern crate libanalysis; 1extern crate libanalysis;
2extern crate relative_path;
2extern crate test_utils; 3extern crate test_utils;
3 4
4use std::path::PathBuf; 5use std::path::{Path};
5 6
6use libanalysis::{WorldState, FileId}; 7use relative_path::RelativePath;
8use libanalysis::{WorldState, FileId, FileResolver};
7use test_utils::assert_eq_dbg; 9use test_utils::assert_eq_dbg;
8 10
11struct FileMap(&'static [(u32, &'static str)]);
12
13impl FileMap {
14 fn path(&self, id: FileId) -> &'static Path {
15 let s = self.0.iter()
16 .find(|it| it.0 == id.0)
17 .unwrap()
18 .1;
19 Path::new(s)
20 }
21}
22
23impl FileResolver for FileMap {
24 fn file_stem(&self, id: FileId) -> String {
25 self.path(id).file_stem().unwrap().to_str().unwrap().to_string()
26 }
27 fn resolve(&self, id: FileId, rel: &RelativePath) -> Option<FileId> {
28 let path = rel.to_path(self.path(id));
29 let path = path.to_str().unwrap();
30 let path = RelativePath::new(&path[1..]).normalize();
31 let &(id, _) = self.0.iter()
32 .find(|it| path == RelativePath::new(&it.1[1..]).normalize())?;
33 Some(FileId(id))
34 }
35}
36
9 37
10#[test] 38#[test]
11fn test_resolve_module() { 39fn test_resolve_module() {
@@ -13,14 +41,10 @@ fn test_resolve_module() {
13 world.change_file(FileId(1), Some("mod foo;".to_string())); 41 world.change_file(FileId(1), Some("mod foo;".to_string()));
14 world.change_file(FileId(2), Some("".to_string())); 42 world.change_file(FileId(2), Some("".to_string()));
15 43
16 let snap = world.snapshot(|id, path| { 44 let snap = world.snapshot(FileMap(&[
17 assert_eq!(id, FileId(1)); 45 (1, "/lib.rs"),
18 if path == PathBuf::from("../foo/mod.rs") { 46 (2, "/foo.rs"),
19 return None; 47 ]));
20 }
21 assert_eq!(path, PathBuf::from("../foo.rs"));
22 Some(FileId(2))
23 });
24 let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into()) 48 let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into())
25 .unwrap(); 49 .unwrap();
26 assert_eq_dbg( 50 assert_eq_dbg(
@@ -28,14 +52,10 @@ fn test_resolve_module() {
28 &symbols, 52 &symbols,
29 ); 53 );
30 54
31 let snap = world.snapshot(|id, path| { 55 let snap = world.snapshot(FileMap(&[
32 assert_eq!(id, FileId(1)); 56 (1, "/lib.rs"),
33 if path == PathBuf::from("../foo.rs") { 57 (2, "/foo/mod.rs")
34 return None; 58 ]));
35 }
36 assert_eq!(path, PathBuf::from("../foo/mod.rs"));
37 Some(FileId(2))
38 });
39 let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into()) 59 let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into())
40 .unwrap(); 60 .unwrap();
41 assert_eq_dbg( 61 assert_eq_dbg(
@@ -49,11 +69,11 @@ fn test_unresolved_module_diagnostic() {
49 let mut world = WorldState::new(); 69 let mut world = WorldState::new();
50 world.change_file(FileId(1), Some("mod foo;".to_string())); 70 world.change_file(FileId(1), Some("mod foo;".to_string()));
51 71
52 let snap = world.snapshot(|_id, _path| None); 72 let snap = world.snapshot(FileMap(&[(1, "/lib.rs")]));
53 let diagnostics = snap.diagnostics(FileId(1)).unwrap(); 73 let diagnostics = snap.diagnostics(FileId(1)).unwrap();
54 assert_eq_dbg( 74 assert_eq_dbg(
55 r#"[(Diagnostic { range: [4; 7), msg: "unresolved module" }, 75 r#"[(Diagnostic { range: [4; 7), msg: "unresolved module" },
56 Some(CreateFile("../foo.rs")))]"#, 76 Some(QuickFix { fs_ops: [CreateFile { anchor: FileId(1), path: "../foo.rs" }] }))]"#,
57 &diagnostics, 77 &diagnostics,
58 ); 78 );
59} 79}
@@ -64,14 +84,10 @@ fn test_resolve_parent_module() {
64 world.change_file(FileId(1), Some("mod foo;".to_string())); 84 world.change_file(FileId(1), Some("mod foo;".to_string()));
65 world.change_file(FileId(2), Some("".to_string())); 85 world.change_file(FileId(2), Some("".to_string()));
66 86
67 let snap = world.snapshot(|id, path| { 87 let snap = world.snapshot(FileMap(&[
68 assert_eq!(id, FileId(1)); 88 (1, "/lib.rs"),
69 if path == PathBuf::from("../foo/mod.rs") { 89 (2, "/foo.rs"),
70 return None; 90 ]));
71 }
72 assert_eq!(path, PathBuf::from("../foo.rs"));
73 Some(FileId(2))
74 });
75 let symbols = snap.parent_module(FileId(2)); 91 let symbols = snap.parent_module(FileId(2));
76 assert_eq_dbg( 92 assert_eq_dbg(
77 r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#, 93 r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#,