diff options
author | Mikhail Rakhmanov <[email protected]> | 2020-06-13 07:42:15 +0100 |
---|---|---|
committer | Mikhail Rakhmanov <[email protected]> | 2020-06-13 07:42:15 +0100 |
commit | 16bbf4ab7f132e6e5e5318dccdef9a5d71afdd7f (patch) | |
tree | 4b79fa8c046be56b02427ba843e70cdf3ac05767 /crates/ra_hir_def/src/import_map.rs | |
parent | eeb8b9e236796da8734ba81a49164864497f7226 (diff) | |
parent | b56ad148db0c69eb279c225f45d324b4e80e7367 (diff) |
Merge branch 'master' into keyword_completion
# Conflicts:
# docs/user/generated_features.adoc
Diffstat (limited to 'crates/ra_hir_def/src/import_map.rs')
-rw-r--r-- | crates/ra_hir_def/src/import_map.rs | 679 |
1 files changed, 679 insertions, 0 deletions
diff --git a/crates/ra_hir_def/src/import_map.rs b/crates/ra_hir_def/src/import_map.rs new file mode 100644 index 000000000..68e20d06b --- /dev/null +++ b/crates/ra_hir_def/src/import_map.rs | |||
@@ -0,0 +1,679 @@ | |||
1 | //! A map of all publicly exported items in a crate. | ||
2 | |||
3 | use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc}; | ||
4 | |||
5 | use fst::{self, Streamer}; | ||
6 | use indexmap::{map::Entry, IndexMap}; | ||
7 | use ra_db::CrateId; | ||
8 | use rustc_hash::FxHasher; | ||
9 | |||
10 | use crate::{ | ||
11 | db::DefDatabase, | ||
12 | item_scope::ItemInNs, | ||
13 | path::{ModPath, PathKind}, | ||
14 | visibility::Visibility, | ||
15 | ModuleDefId, ModuleId, | ||
16 | }; | ||
17 | |||
18 | type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>; | ||
19 | |||
20 | /// Item import details stored in the `ImportMap`. | ||
21 | #[derive(Debug, Clone, Eq, PartialEq)] | ||
22 | pub struct ImportInfo { | ||
23 | /// A path that can be used to import the item, relative to the crate's root. | ||
24 | pub path: ModPath, | ||
25 | /// The module containing this item. | ||
26 | pub container: ModuleId, | ||
27 | } | ||
28 | |||
29 | /// A map from publicly exported items to the path needed to import/name them from a downstream | ||
30 | /// crate. | ||
31 | /// | ||
32 | /// Reexports of items are taken into account, ie. if something is exported under multiple | ||
33 | /// names, the one with the shortest import path will be used. | ||
34 | /// | ||
35 | /// Note that all paths are relative to the containing crate's root, so the crate name still needs | ||
36 | /// to be prepended to the `ModPath` before the path is valid. | ||
37 | pub struct ImportMap { | ||
38 | map: FxIndexMap<ItemInNs, ImportInfo>, | ||
39 | |||
40 | /// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the | ||
41 | /// values returned by running `fst`. | ||
42 | /// | ||
43 | /// Since a path can refer to multiple items due to namespacing, we store all items with the | ||
44 | /// same path right after each other. This allows us to find all items after the FST gives us | ||
45 | /// the index of the first one. | ||
46 | importables: Vec<ItemInNs>, | ||
47 | fst: fst::Map<Vec<u8>>, | ||
48 | } | ||
49 | |||
50 | impl ImportMap { | ||
51 | pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc<Self> { | ||
52 | let _p = ra_prof::profile("import_map_query"); | ||
53 | let def_map = db.crate_def_map(krate); | ||
54 | let mut import_map = FxIndexMap::with_capacity_and_hasher(64, Default::default()); | ||
55 | |||
56 | // We look only into modules that are public(ly reexported), starting with the crate root. | ||
57 | let empty = ModPath { kind: PathKind::Plain, segments: vec![] }; | ||
58 | let root = ModuleId { krate, local_id: def_map.root }; | ||
59 | let mut worklist = vec![(root, empty)]; | ||
60 | while let Some((module, mod_path)) = worklist.pop() { | ||
61 | let ext_def_map; | ||
62 | let mod_data = if module.krate == krate { | ||
63 | &def_map[module.local_id] | ||
64 | } else { | ||
65 | // The crate might reexport a module defined in another crate. | ||
66 | ext_def_map = db.crate_def_map(module.krate); | ||
67 | &ext_def_map[module.local_id] | ||
68 | }; | ||
69 | |||
70 | let visible_items = mod_data.scope.entries().filter_map(|(name, per_ns)| { | ||
71 | let per_ns = per_ns.filter_visibility(|vis| vis == Visibility::Public); | ||
72 | if per_ns.is_none() { | ||
73 | None | ||
74 | } else { | ||
75 | Some((name, per_ns)) | ||
76 | } | ||
77 | }); | ||
78 | |||
79 | for (name, per_ns) in visible_items { | ||
80 | let mk_path = || { | ||
81 | let mut path = mod_path.clone(); | ||
82 | path.segments.push(name.clone()); | ||
83 | path | ||
84 | }; | ||
85 | |||
86 | for item in per_ns.iter_items() { | ||
87 | let path = mk_path(); | ||
88 | match import_map.entry(item) { | ||
89 | Entry::Vacant(entry) => { | ||
90 | entry.insert(ImportInfo { path, container: module }); | ||
91 | } | ||
92 | Entry::Occupied(mut entry) => { | ||
93 | // If the new path is shorter, prefer that one. | ||
94 | if path.len() < entry.get().path.len() { | ||
95 | *entry.get_mut() = ImportInfo { path, container: module }; | ||
96 | } else { | ||
97 | continue; | ||
98 | } | ||
99 | } | ||
100 | } | ||
101 | |||
102 | // If we've just added a path to a module, descend into it. We might traverse | ||
103 | // modules multiple times, but only if the new path to it is shorter than the | ||
104 | // first (else we `continue` above). | ||
105 | if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() { | ||
106 | worklist.push((mod_id, mk_path())); | ||
107 | } | ||
108 | } | ||
109 | } | ||
110 | } | ||
111 | |||
112 | let mut importables = import_map.iter().collect::<Vec<_>>(); | ||
113 | |||
114 | importables.sort_by(cmp); | ||
115 | |||
116 | // Build the FST, taking care not to insert duplicate values. | ||
117 | |||
118 | let mut builder = fst::MapBuilder::memory(); | ||
119 | let mut last_batch_start = 0; | ||
120 | |||
121 | for idx in 0..importables.len() { | ||
122 | if let Some(next_item) = importables.get(idx + 1) { | ||
123 | if cmp(&importables[last_batch_start], next_item) == Ordering::Equal { | ||
124 | continue; | ||
125 | } | ||
126 | } | ||
127 | |||
128 | let start = last_batch_start; | ||
129 | last_batch_start = idx + 1; | ||
130 | |||
131 | let key = fst_path(&importables[start].1.path); | ||
132 | |||
133 | builder.insert(key, start as u64).unwrap(); | ||
134 | } | ||
135 | |||
136 | let fst = fst::Map::new(builder.into_inner().unwrap()).unwrap(); | ||
137 | let importables = importables.iter().map(|(item, _)| **item).collect(); | ||
138 | |||
139 | Arc::new(Self { map: import_map, fst, importables }) | ||
140 | } | ||
141 | |||
142 | /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root. | ||
143 | pub fn path_of(&self, item: ItemInNs) -> Option<&ModPath> { | ||
144 | Some(&self.map.get(&item)?.path) | ||
145 | } | ||
146 | |||
147 | pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> { | ||
148 | self.map.get(&item) | ||
149 | } | ||
150 | } | ||
151 | |||
152 | impl PartialEq for ImportMap { | ||
153 | fn eq(&self, other: &Self) -> bool { | ||
154 | // `fst` and `importables` are built from `map`, so we don't need to compare them. | ||
155 | self.map == other.map | ||
156 | } | ||
157 | } | ||
158 | |||
159 | impl Eq for ImportMap {} | ||
160 | |||
161 | impl fmt::Debug for ImportMap { | ||
162 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
163 | let mut importable_paths: Vec<_> = self | ||
164 | .map | ||
165 | .iter() | ||
166 | .map(|(item, info)| { | ||
167 | let ns = match item { | ||
168 | ItemInNs::Types(_) => "t", | ||
169 | ItemInNs::Values(_) => "v", | ||
170 | ItemInNs::Macros(_) => "m", | ||
171 | }; | ||
172 | format!("- {} ({})", info.path, ns) | ||
173 | }) | ||
174 | .collect(); | ||
175 | |||
176 | importable_paths.sort(); | ||
177 | f.write_str(&importable_paths.join("\n")) | ||
178 | } | ||
179 | } | ||
180 | |||
181 | fn fst_path(path: &ModPath) -> String { | ||
182 | let mut s = path.to_string(); | ||
183 | s.make_ascii_lowercase(); | ||
184 | s | ||
185 | } | ||
186 | |||
187 | fn cmp((_, lhs): &(&ItemInNs, &ImportInfo), (_, rhs): &(&ItemInNs, &ImportInfo)) -> Ordering { | ||
188 | let lhs_str = fst_path(&lhs.path); | ||
189 | let rhs_str = fst_path(&rhs.path); | ||
190 | lhs_str.cmp(&rhs_str) | ||
191 | } | ||
192 | |||
193 | #[derive(Debug)] | ||
194 | pub struct Query { | ||
195 | query: String, | ||
196 | lowercased: String, | ||
197 | anchor_end: bool, | ||
198 | case_sensitive: bool, | ||
199 | limit: usize, | ||
200 | } | ||
201 | |||
202 | impl Query { | ||
203 | pub fn new(query: &str) -> Self { | ||
204 | Self { | ||
205 | lowercased: query.to_lowercase(), | ||
206 | query: query.to_string(), | ||
207 | anchor_end: false, | ||
208 | case_sensitive: false, | ||
209 | limit: usize::max_value(), | ||
210 | } | ||
211 | } | ||
212 | |||
213 | /// Only returns items whose paths end with the (case-insensitive) query string as their last | ||
214 | /// segment. | ||
215 | pub fn anchor_end(self) -> Self { | ||
216 | Self { anchor_end: true, ..self } | ||
217 | } | ||
218 | |||
219 | /// Limits the returned number of items to `limit`. | ||
220 | pub fn limit(self, limit: usize) -> Self { | ||
221 | Self { limit, ..self } | ||
222 | } | ||
223 | |||
224 | /// Respect casing of the query string when matching. | ||
225 | pub fn case_sensitive(self) -> Self { | ||
226 | Self { case_sensitive: true, ..self } | ||
227 | } | ||
228 | } | ||
229 | |||
230 | /// Searches dependencies of `krate` for an importable path matching `query`. | ||
231 | /// | ||
232 | /// This returns a list of items that could be imported from dependencies of `krate`. | ||
233 | pub fn search_dependencies<'a>( | ||
234 | db: &'a dyn DefDatabase, | ||
235 | krate: CrateId, | ||
236 | query: Query, | ||
237 | ) -> Vec<ItemInNs> { | ||
238 | let _p = ra_prof::profile("search_dependencies").detail(|| format!("{:?}", query)); | ||
239 | |||
240 | let graph = db.crate_graph(); | ||
241 | let import_maps: Vec<_> = | ||
242 | graph[krate].dependencies.iter().map(|dep| db.import_map(dep.crate_id)).collect(); | ||
243 | |||
244 | let automaton = fst::automaton::Subsequence::new(&query.lowercased); | ||
245 | |||
246 | let mut op = fst::map::OpBuilder::new(); | ||
247 | for map in &import_maps { | ||
248 | op = op.add(map.fst.search(&automaton)); | ||
249 | } | ||
250 | |||
251 | let mut stream = op.union(); | ||
252 | let mut res = Vec::new(); | ||
253 | while let Some((_, indexed_values)) = stream.next() { | ||
254 | for indexed_value in indexed_values { | ||
255 | let import_map = &import_maps[indexed_value.index]; | ||
256 | let importables = &import_map.importables[indexed_value.value as usize..]; | ||
257 | |||
258 | // Path shared by the importable items in this group. | ||
259 | let path = &import_map.map[&importables[0]].path; | ||
260 | |||
261 | if query.anchor_end { | ||
262 | // Last segment must match query. | ||
263 | let last = path.segments.last().unwrap().to_string(); | ||
264 | if last.to_lowercase() != query.lowercased { | ||
265 | continue; | ||
266 | } | ||
267 | } | ||
268 | |||
269 | // Add the items from this `ModPath` group. Those are all subsequent items in | ||
270 | // `importables` whose paths match `path`. | ||
271 | let iter = importables.iter().copied().take_while(|item| { | ||
272 | let item_path = &import_map.map[item].path; | ||
273 | fst_path(item_path) == fst_path(path) | ||
274 | }); | ||
275 | |||
276 | if query.case_sensitive { | ||
277 | // FIXME: This does not do a subsequence match. | ||
278 | res.extend(iter.filter(|item| { | ||
279 | let item_path = &import_map.map[item].path; | ||
280 | item_path.to_string().contains(&query.query) | ||
281 | })); | ||
282 | } else { | ||
283 | res.extend(iter); | ||
284 | } | ||
285 | |||
286 | if res.len() >= query.limit { | ||
287 | res.truncate(query.limit); | ||
288 | return res; | ||
289 | } | ||
290 | } | ||
291 | } | ||
292 | |||
293 | res | ||
294 | } | ||
295 | |||
296 | #[cfg(test)] | ||
297 | mod tests { | ||
298 | use super::*; | ||
299 | use crate::test_db::TestDB; | ||
300 | use insta::assert_snapshot; | ||
301 | use itertools::Itertools; | ||
302 | use ra_db::fixture::WithFixture; | ||
303 | use ra_db::{SourceDatabase, Upcast}; | ||
304 | |||
305 | fn import_map(ra_fixture: &str) -> String { | ||
306 | let db = TestDB::with_files(ra_fixture); | ||
307 | let crate_graph = db.crate_graph(); | ||
308 | |||
309 | let s = crate_graph | ||
310 | .iter() | ||
311 | .filter_map(|krate| { | ||
312 | let cdata = &crate_graph[krate]; | ||
313 | let name = cdata.display_name.as_ref()?; | ||
314 | |||
315 | let map = db.import_map(krate); | ||
316 | |||
317 | Some(format!("{}:\n{:?}", name, map)) | ||
318 | }) | ||
319 | .join("\n"); | ||
320 | s | ||
321 | } | ||
322 | |||
323 | fn search_dependencies_of(ra_fixture: &str, krate_name: &str, query: Query) -> String { | ||
324 | let db = TestDB::with_files(ra_fixture); | ||
325 | let crate_graph = db.crate_graph(); | ||
326 | let krate = crate_graph | ||
327 | .iter() | ||
328 | .find(|krate| { | ||
329 | crate_graph[*krate].display_name.as_ref().map(|n| n.to_string()) | ||
330 | == Some(krate_name.to_string()) | ||
331 | }) | ||
332 | .unwrap(); | ||
333 | |||
334 | search_dependencies(db.upcast(), krate, query) | ||
335 | .into_iter() | ||
336 | .filter_map(|item| { | ||
337 | let mark = match item { | ||
338 | ItemInNs::Types(_) => "t", | ||
339 | ItemInNs::Values(_) => "v", | ||
340 | ItemInNs::Macros(_) => "m", | ||
341 | }; | ||
342 | item.krate(db.upcast()).map(|krate| { | ||
343 | let map = db.import_map(krate); | ||
344 | let path = map.path_of(item).unwrap(); | ||
345 | format!( | ||
346 | "{}::{} ({})", | ||
347 | crate_graph[krate].display_name.as_ref().unwrap(), | ||
348 | path, | ||
349 | mark | ||
350 | ) | ||
351 | }) | ||
352 | }) | ||
353 | .join("\n") | ||
354 | } | ||
355 | |||
356 | #[test] | ||
357 | fn smoke() { | ||
358 | let map = import_map( | ||
359 | r" | ||
360 | //- /main.rs crate:main deps:lib | ||
361 | |||
362 | mod private { | ||
363 | pub use lib::Pub; | ||
364 | pub struct InPrivateModule; | ||
365 | } | ||
366 | |||
367 | pub mod publ1 { | ||
368 | use lib::Pub; | ||
369 | } | ||
370 | |||
371 | pub mod real_pub { | ||
372 | pub use lib::Pub; | ||
373 | } | ||
374 | pub mod real_pu2 { // same path length as above | ||
375 | pub use lib::Pub; | ||
376 | } | ||
377 | |||
378 | //- /lib.rs crate:lib | ||
379 | pub struct Pub {} | ||
380 | pub struct Pub2; // t + v | ||
381 | struct Priv; | ||
382 | ", | ||
383 | ); | ||
384 | |||
385 | assert_snapshot!(map, @r###" | ||
386 | main: | ||
387 | - publ1 (t) | ||
388 | - real_pu2 (t) | ||
389 | - real_pub (t) | ||
390 | - real_pub::Pub (t) | ||
391 | lib: | ||
392 | - Pub (t) | ||
393 | - Pub2 (t) | ||
394 | - Pub2 (v) | ||
395 | "###); | ||
396 | } | ||
397 | |||
398 | #[test] | ||
399 | fn prefers_shortest_path() { | ||
400 | let map = import_map( | ||
401 | r" | ||
402 | //- /main.rs crate:main | ||
403 | |||
404 | pub mod sub { | ||
405 | pub mod subsub { | ||
406 | pub struct Def {} | ||
407 | } | ||
408 | |||
409 | pub use super::sub::subsub::Def; | ||
410 | } | ||
411 | ", | ||
412 | ); | ||
413 | |||
414 | assert_snapshot!(map, @r###" | ||
415 | main: | ||
416 | - sub (t) | ||
417 | - sub::Def (t) | ||
418 | - sub::subsub (t) | ||
419 | "###); | ||
420 | } | ||
421 | |||
422 | #[test] | ||
423 | fn type_reexport_cross_crate() { | ||
424 | // Reexports need to be visible from a crate, even if the original crate exports the item | ||
425 | // at a shorter path. | ||
426 | let map = import_map( | ||
427 | r" | ||
428 | //- /main.rs crate:main deps:lib | ||
429 | pub mod m { | ||
430 | pub use lib::S; | ||
431 | } | ||
432 | //- /lib.rs crate:lib | ||
433 | pub struct S; | ||
434 | ", | ||
435 | ); | ||
436 | |||
437 | assert_snapshot!(map, @r###" | ||
438 | main: | ||
439 | - m (t) | ||
440 | - m::S (t) | ||
441 | - m::S (v) | ||
442 | lib: | ||
443 | - S (t) | ||
444 | - S (v) | ||
445 | "###); | ||
446 | } | ||
447 | |||
448 | #[test] | ||
449 | fn macro_reexport() { | ||
450 | let map = import_map( | ||
451 | r" | ||
452 | //- /main.rs crate:main deps:lib | ||
453 | pub mod m { | ||
454 | pub use lib::pub_macro; | ||
455 | } | ||
456 | //- /lib.rs crate:lib | ||
457 | #[macro_export] | ||
458 | macro_rules! pub_macro { | ||
459 | () => {}; | ||
460 | } | ||
461 | ", | ||
462 | ); | ||
463 | |||
464 | assert_snapshot!(map, @r###" | ||
465 | main: | ||
466 | - m (t) | ||
467 | - m::pub_macro (m) | ||
468 | lib: | ||
469 | - pub_macro (m) | ||
470 | "###); | ||
471 | } | ||
472 | |||
473 | #[test] | ||
474 | fn module_reexport() { | ||
475 | // Reexporting modules from a dependency adds all contents to the import map. | ||
476 | let map = import_map( | ||
477 | r" | ||
478 | //- /main.rs crate:main deps:lib | ||
479 | pub use lib::module as reexported_module; | ||
480 | //- /lib.rs crate:lib | ||
481 | pub mod module { | ||
482 | pub struct S; | ||
483 | } | ||
484 | ", | ||
485 | ); | ||
486 | |||
487 | assert_snapshot!(map, @r###" | ||
488 | main: | ||
489 | - reexported_module (t) | ||
490 | - reexported_module::S (t) | ||
491 | - reexported_module::S (v) | ||
492 | lib: | ||
493 | - module (t) | ||
494 | - module::S (t) | ||
495 | - module::S (v) | ||
496 | "###); | ||
497 | } | ||
498 | |||
499 | #[test] | ||
500 | fn cyclic_module_reexport() { | ||
501 | // A cyclic reexport does not hang. | ||
502 | let map = import_map( | ||
503 | r" | ||
504 | //- /lib.rs crate:lib | ||
505 | pub mod module { | ||
506 | pub struct S; | ||
507 | pub use super::sub::*; | ||
508 | } | ||
509 | |||
510 | pub mod sub { | ||
511 | pub use super::module; | ||
512 | } | ||
513 | ", | ||
514 | ); | ||
515 | |||
516 | assert_snapshot!(map, @r###" | ||
517 | lib: | ||
518 | - module (t) | ||
519 | - module::S (t) | ||
520 | - module::S (v) | ||
521 | - sub (t) | ||
522 | "###); | ||
523 | } | ||
524 | |||
525 | #[test] | ||
526 | fn private_macro() { | ||
527 | let map = import_map( | ||
528 | r" | ||
529 | //- /lib.rs crate:lib | ||
530 | macro_rules! private_macro { | ||
531 | () => {}; | ||
532 | } | ||
533 | ", | ||
534 | ); | ||
535 | |||
536 | assert_snapshot!(map, @r###" | ||
537 | lib: | ||
538 | "###); | ||
539 | } | ||
540 | |||
541 | #[test] | ||
542 | fn namespacing() { | ||
543 | let map = import_map( | ||
544 | r" | ||
545 | //- /lib.rs crate:lib | ||
546 | pub struct Thing; // t + v | ||
547 | #[macro_export] | ||
548 | macro_rules! Thing { // m | ||
549 | () => {}; | ||
550 | } | ||
551 | ", | ||
552 | ); | ||
553 | |||
554 | assert_snapshot!(map, @r###" | ||
555 | lib: | ||
556 | - Thing (m) | ||
557 | - Thing (t) | ||
558 | - Thing (v) | ||
559 | "###); | ||
560 | |||
561 | let map = import_map( | ||
562 | r" | ||
563 | //- /lib.rs crate:lib | ||
564 | pub mod Thing {} // t | ||
565 | #[macro_export] | ||
566 | macro_rules! Thing { // m | ||
567 | () => {}; | ||
568 | } | ||
569 | ", | ||
570 | ); | ||
571 | |||
572 | assert_snapshot!(map, @r###" | ||
573 | lib: | ||
574 | - Thing (m) | ||
575 | - Thing (t) | ||
576 | "###); | ||
577 | } | ||
578 | |||
579 | #[test] | ||
580 | fn search() { | ||
581 | let ra_fixture = r#" | ||
582 | //- /main.rs crate:main deps:dep | ||
583 | //- /dep.rs crate:dep deps:tdep | ||
584 | use tdep::fmt as fmt_dep; | ||
585 | pub mod fmt { | ||
586 | pub trait Display { | ||
587 | fn fmt(); | ||
588 | } | ||
589 | } | ||
590 | #[macro_export] | ||
591 | macro_rules! Fmt { | ||
592 | () => {}; | ||
593 | } | ||
594 | pub struct Fmt; | ||
595 | |||
596 | pub fn format() {} | ||
597 | pub fn no() {} | ||
598 | |||
599 | //- /tdep.rs crate:tdep | ||
600 | pub mod fmt { | ||
601 | pub struct NotImportableFromMain; | ||
602 | } | ||
603 | "#; | ||
604 | |||
605 | let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt")); | ||
606 | assert_snapshot!(res, @r###" | ||
607 | dep::fmt (t) | ||
608 | dep::Fmt (t) | ||
609 | dep::Fmt (v) | ||
610 | dep::Fmt (m) | ||
611 | dep::fmt::Display (t) | ||
612 | dep::format (v) | ||
613 | "###); | ||
614 | |||
615 | let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt").anchor_end()); | ||
616 | assert_snapshot!(res, @r###" | ||
617 | dep::fmt (t) | ||
618 | dep::Fmt (t) | ||
619 | dep::Fmt (v) | ||
620 | dep::Fmt (m) | ||
621 | "###); | ||
622 | } | ||
623 | |||
624 | #[test] | ||
625 | fn search_casing() { | ||
626 | let ra_fixture = r#" | ||
627 | //- /main.rs crate:main deps:dep | ||
628 | //- /dep.rs crate:dep | ||
629 | |||
630 | pub struct fmt; | ||
631 | pub struct FMT; | ||
632 | "#; | ||
633 | |||
634 | let res = search_dependencies_of(ra_fixture, "main", Query::new("FMT")); | ||
635 | |||
636 | assert_snapshot!(res, @r###" | ||
637 | dep::fmt (t) | ||
638 | dep::fmt (v) | ||
639 | dep::FMT (t) | ||
640 | dep::FMT (v) | ||
641 | "###); | ||
642 | |||
643 | let res = search_dependencies_of(ra_fixture, "main", Query::new("FMT").case_sensitive()); | ||
644 | |||
645 | assert_snapshot!(res, @r###" | ||
646 | dep::FMT (t) | ||
647 | dep::FMT (v) | ||
648 | "###); | ||
649 | } | ||
650 | |||
651 | #[test] | ||
652 | fn search_limit() { | ||
653 | let res = search_dependencies_of( | ||
654 | r#" | ||
655 | //- /main.rs crate:main deps:dep | ||
656 | //- /dep.rs crate:dep | ||
657 | pub mod fmt { | ||
658 | pub trait Display { | ||
659 | fn fmt(); | ||
660 | } | ||
661 | } | ||
662 | #[macro_export] | ||
663 | macro_rules! Fmt { | ||
664 | () => {}; | ||
665 | } | ||
666 | pub struct Fmt; | ||
667 | |||
668 | pub fn format() {} | ||
669 | pub fn no() {} | ||
670 | "#, | ||
671 | "main", | ||
672 | Query::new("").limit(2), | ||
673 | ); | ||
674 | assert_snapshot!(res, @r###" | ||
675 | dep::fmt (t) | ||
676 | dep::Fmt (t) | ||
677 | "###); | ||
678 | } | ||
679 | } | ||