aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_analysis/src/runnables.rs
blob: 216209098d3b99c4e9cf99f91a12e3281f4f811f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
use itertools::Itertools;
use ra_syntax::{
    ast::{self, AstNode, NameOwner, ModuleItemOwner},
    TextRange, SyntaxNodeRef,
};
use ra_db::{Cancelable, SyntaxDatabase};

use crate::{db::RootDatabase, FileId};

#[derive(Debug)]
pub struct Runnable {
    pub range: TextRange,
    pub kind: RunnableKind,
}

#[derive(Debug)]
pub enum RunnableKind {
    Test { name: String },
    TestMod { path: String },
    Bin,
}

pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Cancelable<Vec<Runnable>> {
    let source_file = db.source_file(file_id);
    let res = source_file
        .syntax()
        .descendants()
        .filter_map(|i| runnable(db, file_id, i))
        .collect();
    Ok(res)
}

fn runnable(db: &RootDatabase, file_id: FileId, item: SyntaxNodeRef) -> Option<Runnable> {
    if let Some(fn_def) = ast::FnDef::cast(item) {
        runnable_fn(fn_def)
    } else if let Some(m) = ast::Module::cast(item) {
        runnable_mod(db, file_id, m)
    } else {
        None
    }
}

fn runnable_fn(fn_def: ast::FnDef) -> Option<Runnable> {
    let name = fn_def.name()?.text();
    let kind = if name == "main" {
        RunnableKind::Bin
    } else if fn_def.has_atom_attr("test") {
        RunnableKind::Test {
            name: name.to_string(),
        }
    } else {
        return None;
    };
    Some(Runnable {
        range: fn_def.syntax().range(),
        kind,
    })
}

fn runnable_mod(db: &RootDatabase, file_id: FileId, module: ast::Module) -> Option<Runnable> {
    let has_test_function = module
        .item_list()?
        .items()
        .filter_map(|it| match it {
            ast::ModuleItem::FnDef(it) => Some(it),
            _ => None,
        })
        .any(|f| f.has_atom_attr("test"));
    if !has_test_function {
        return None;
    }
    let range = module.syntax().range();
    let module =
        hir::source_binder::module_from_child_node(db, file_id, module.syntax()).ok()??;

    // FIXME: thread cancellation instead of `.ok`ing
    let path = module
        .path_to_root(db)
        .ok()?
        .into_iter()
        .rev()
        .filter_map(|it| it.name(db).ok())
        .filter_map(|it| it)
        .join("::");
    Some(Runnable {
        range,
        kind: RunnableKind::TestMod { path },
    })
}