ide_db/
helpers.rs

1//! Random assortment of ide helpers for high-level ide features that don't fit in any other module.
2
3use std::collections::VecDeque;
4
5use base_db::SourceDatabase;
6use hir::{Crate, ItemInNs, ModuleDef, Name, Semantics};
7use span::{Edition, FileId};
8use syntax::{
9    AstToken, SyntaxKind, SyntaxToken, ToSmolStr, TokenAtOffset,
10    ast::{self, make},
11};
12
13use crate::{
14    RootDatabase,
15    defs::{Definition, IdentClass},
16    generated,
17};
18
19pub fn item_name(db: &RootDatabase, item: ItemInNs) -> Option<Name> {
20    match item {
21        ItemInNs::Types(module_def_id) => module_def_id.name(db),
22        ItemInNs::Values(module_def_id) => module_def_id.name(db),
23        ItemInNs::Macros(macro_def_id) => Some(macro_def_id.name(db)),
24    }
25}
26
27/// Picks the token with the highest rank returned by the passed in function.
28pub fn pick_best_token(
29    tokens: TokenAtOffset<SyntaxToken>,
30    f: impl Fn(SyntaxKind) -> usize,
31) -> Option<SyntaxToken> {
32    tokens.max_by_key(move |t| f(t.kind()))
33}
34pub fn pick_token<T: AstToken>(mut tokens: TokenAtOffset<SyntaxToken>) -> Option<T> {
35    tokens.find_map(T::cast)
36}
37
38/// Converts the mod path struct into its ast representation.
39pub fn mod_path_to_ast(path: &hir::ModPath, edition: Edition) -> ast::Path {
40    let _p = tracing::info_span!("mod_path_to_ast").entered();
41
42    let mut segments = Vec::new();
43    let mut is_abs = false;
44    match path.kind {
45        hir::PathKind::Plain => {}
46        hir::PathKind::SELF => segments.push(make::path_segment_self()),
47        hir::PathKind::Super(n) => segments.extend((0..n).map(|_| make::path_segment_super())),
48        hir::PathKind::DollarCrate(_) | hir::PathKind::Crate => {
49            segments.push(make::path_segment_crate())
50        }
51        hir::PathKind::Abs => is_abs = true,
52    }
53
54    segments.extend(path.segments().iter().map(|segment| {
55        make::path_segment(make::name_ref(&segment.display_no_db(edition).to_smolstr()))
56    }));
57    make::path_from_segments(segments, is_abs)
58}
59
60/// Iterates all `ModuleDef`s and `Impl` blocks of the given file.
61pub fn visit_file_defs(
62    sema: &Semantics<'_, RootDatabase>,
63    file_id: FileId,
64    cb: &mut dyn FnMut(Definition),
65) {
66    let db = sema.db;
67    let module = match sema.file_to_module_def(file_id) {
68        Some(it) => it,
69        None => return,
70    };
71    let mut defs: VecDeque<_> = module.declarations(db).into();
72    while let Some(def) = defs.pop_front() {
73        if let ModuleDef::Module(submodule) = def
74            && submodule.is_inline(db)
75        {
76            defs.extend(submodule.declarations(db));
77            submodule.impl_defs(db).into_iter().for_each(|impl_| cb(impl_.into()));
78        }
79        cb(def.into());
80    }
81    module.impl_defs(db).into_iter().for_each(|impl_| cb(impl_.into()));
82
83    let is_root = module.is_crate_root();
84    module
85        .legacy_macros(db)
86        .into_iter()
87        // don't show legacy macros declared in the crate-root that were already covered in declarations earlier
88        .filter(|it| !(is_root && it.is_macro_export(db)))
89        .for_each(|mac| cb(mac.into()));
90}
91
92/// Checks if the given lint is equal or is contained by the other lint which may or may not be a group.
93pub fn lint_eq_or_in_group(lint: &str, lint_is: &str) -> bool {
94    if lint == lint_is {
95        return true;
96    }
97
98    if let Some(group) = generated::lints::DEFAULT_LINT_GROUPS
99        .iter()
100        .chain(generated::lints::CLIPPY_LINT_GROUPS.iter())
101        .chain(generated::lints::RUSTDOC_LINT_GROUPS.iter())
102        .find(|&check| check.lint.label == lint_is)
103    {
104        group.children.contains(&lint)
105    } else {
106        false
107    }
108}
109
110pub fn is_editable_crate(krate: Crate, db: &RootDatabase) -> bool {
111    let root_file = krate.root_file(db);
112    let source_root_id = db.file_source_root(root_file).source_root_id(db);
113    !db.source_root(source_root_id).source_root(db).is_library
114}
115
116// FIXME: This is a weird function
117pub fn get_definition(
118    sema: &Semantics<'_, RootDatabase>,
119    token: SyntaxToken,
120) -> Option<Definition> {
121    for token in sema.descend_into_macros_exact(token) {
122        let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops);
123        if let Some(&[x]) = def.as_deref() {
124            return Some(x);
125        }
126    }
127    None
128}