mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-13 13:48:50 +00:00
add support of feature flag for runnables #4464
Signed-off-by: Benjamin Coenen <5719034+bnjjj@users.noreply.github.com>
This commit is contained in:
parent
ebaa05a447
commit
c6143742bd
7 changed files with 212 additions and 19 deletions
|
@ -33,6 +33,36 @@ impl CfgExpr {
|
||||||
CfgExpr::Not(pred) => pred.fold(query).map(|s| !s),
|
CfgExpr::Not(pred) => pred.fold(query).map(|s| !s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return minimal features needed
|
||||||
|
pub fn minimal_features_needed(&self) -> Option<Vec<SmolStr>> {
|
||||||
|
let mut features = vec![];
|
||||||
|
self.collect_minimal_features_needed(&mut features);
|
||||||
|
if features.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(features)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_minimal_features_needed(&self, features: &mut Vec<SmolStr>) {
|
||||||
|
match self {
|
||||||
|
CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.clone()),
|
||||||
|
CfgExpr::All(preds) => {
|
||||||
|
preds.iter().for_each(|cfg| cfg.collect_minimal_features_needed(features));
|
||||||
|
}
|
||||||
|
CfgExpr::Any(preds) => {
|
||||||
|
for cfg in preds {
|
||||||
|
let len_features = features.len();
|
||||||
|
cfg.collect_minimal_features_needed(features);
|
||||||
|
if len_features != features.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_cfg(tt: &Subtree) -> CfgExpr {
|
pub fn parse_cfg(tt: &Subtree) -> CfgExpr {
|
||||||
|
@ -88,13 +118,17 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use mbe::ast_to_token_tree;
|
use mbe::{ast_to_token_tree, TokenMap};
|
||||||
use ra_syntax::ast::{self, AstNode};
|
use ra_syntax::ast::{self, AstNode};
|
||||||
|
|
||||||
fn assert_parse_result(input: &str, expected: CfgExpr) {
|
fn get_token_tree_generated(input: &str) -> (tt::Subtree, TokenMap) {
|
||||||
let source_file = ast::SourceFile::parse(input).ok().unwrap();
|
let source_file = ast::SourceFile::parse(input).ok().unwrap();
|
||||||
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
|
let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
|
||||||
let (tt, _) = ast_to_token_tree(&tt).unwrap();
|
ast_to_token_tree(&tt).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_parse_result(input: &str, expected: CfgExpr) {
|
||||||
|
let (tt, _) = get_token_tree_generated(input);
|
||||||
assert_eq!(parse_cfg(&tt), expected);
|
assert_eq!(parse_cfg(&tt), expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,4 +163,32 @@ mod tests {
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cfg_expr_minimal_features_needed() {
|
||||||
|
let (subtree, _) = get_token_tree_generated(r#"#![cfg(feature = "baz")]"#);
|
||||||
|
let cfg_expr = parse_cfg(&subtree);
|
||||||
|
|
||||||
|
assert_eq!(cfg_expr.minimal_features_needed().unwrap(), vec![SmolStr::new("baz")]);
|
||||||
|
|
||||||
|
let (subtree, _) =
|
||||||
|
get_token_tree_generated(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#);
|
||||||
|
let cfg_expr = parse_cfg(&subtree);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
cfg_expr.minimal_features_needed().unwrap(),
|
||||||
|
vec![SmolStr::new("baz"), SmolStr::new("foo")]
|
||||||
|
);
|
||||||
|
|
||||||
|
let (subtree, _) =
|
||||||
|
get_token_tree_generated(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#);
|
||||||
|
let cfg_expr = parse_cfg(&subtree);
|
||||||
|
|
||||||
|
assert_eq!(cfg_expr.minimal_features_needed().unwrap(), vec![SmolStr::new("baz")]);
|
||||||
|
|
||||||
|
let (subtree, _) = get_token_tree_generated(r#"#![cfg(foo)]"#);
|
||||||
|
let cfg_expr = parse_cfg(&subtree);
|
||||||
|
|
||||||
|
assert!(cfg_expr.minimal_features_needed().is_none());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ pub use crate::{
|
||||||
|
|
||||||
pub use hir_def::{
|
pub use hir_def::{
|
||||||
adt::StructKind,
|
adt::StructKind,
|
||||||
|
attr::Attrs,
|
||||||
body::scope::ExprScopes,
|
body::scope::ExprScopes,
|
||||||
builtin_type::BuiltinType,
|
builtin_type::BuiltinType,
|
||||||
docs::Documentation,
|
docs::Documentation,
|
||||||
|
|
|
@ -81,7 +81,7 @@ impl Attrs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Attrs {
|
pub fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Attrs {
|
||||||
let hygiene = Hygiene::new(db.upcast(), owner.file_id);
|
let hygiene = Hygiene::new(db.upcast(), owner.file_id);
|
||||||
Attrs::new(owner.value, &hygiene)
|
Attrs::new(owner.value, &hygiene)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ use crate::{
|
||||||
AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId,
|
AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A subset of Exander that only deals with cfg attributes. We only need it to
|
/// A subset of Expander that only deals with cfg attributes. We only need it to
|
||||||
/// avoid cyclic queries in crate def map during enum processing.
|
/// avoid cyclic queries in crate def map during enum processing.
|
||||||
pub(crate) struct CfgExpander {
|
pub(crate) struct CfgExpander {
|
||||||
cfg_options: CfgOptions,
|
cfg_options: CfgOptions,
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
//! FIXME: write short doc here
|
//! FIXME: write short doc here
|
||||||
|
|
||||||
use hir::Semantics;
|
use hir::{Attrs, HirFileId, InFile, Semantics};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ra_ide_db::RootDatabase;
|
use ra_ide_db::RootDatabase;
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner},
|
ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner},
|
||||||
match_ast, SyntaxNode, TextRange,
|
match_ast, SmolStr, SyntaxNode, TextRange,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::FileId;
|
use crate::FileId;
|
||||||
|
@ -16,6 +16,7 @@ use std::fmt::Display;
|
||||||
pub struct Runnable {
|
pub struct Runnable {
|
||||||
pub range: TextRange,
|
pub range: TextRange,
|
||||||
pub kind: RunnableKind,
|
pub kind: RunnableKind,
|
||||||
|
pub features_needed: Option<Vec<SmolStr>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -45,20 +46,24 @@ pub enum RunnableKind {
|
||||||
pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
|
pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
|
||||||
let sema = Semantics::new(db);
|
let sema = Semantics::new(db);
|
||||||
let source_file = sema.parse(file_id);
|
let source_file = sema.parse(file_id);
|
||||||
source_file.syntax().descendants().filter_map(|i| runnable(&sema, i)).collect()
|
source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> {
|
fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> {
|
||||||
match_ast! {
|
match_ast! {
|
||||||
match item {
|
match item {
|
||||||
ast::FnDef(it) => runnable_fn(sema, it),
|
ast::FnDef(it) => runnable_fn(sema, it, file_id),
|
||||||
ast::Module(it) => runnable_mod(sema, it),
|
ast::Module(it) => runnable_mod(sema, it, file_id),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Runnable> {
|
fn runnable_fn(
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
fn_def: ast::FnDef,
|
||||||
|
file_id: FileId,
|
||||||
|
) -> Option<Runnable> {
|
||||||
let name_string = fn_def.name()?.text().to_string();
|
let name_string = fn_def.name()?.text().to_string();
|
||||||
|
|
||||||
let kind = if name_string == "main" {
|
let kind = if name_string == "main" {
|
||||||
|
@ -89,7 +94,11 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Some(Runnable { range: fn_def.syntax().text_range(), kind })
|
|
||||||
|
let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def));
|
||||||
|
let features_needed = get_features_needed(attrs);
|
||||||
|
|
||||||
|
Some(Runnable { range: fn_def.syntax().text_range(), kind, features_needed })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -125,7 +134,11 @@ fn has_doc_test(fn_def: &ast::FnDef) -> bool {
|
||||||
fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```"))
|
fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> {
|
fn runnable_mod(
|
||||||
|
sema: &Semantics<RootDatabase>,
|
||||||
|
module: ast::Module,
|
||||||
|
file_id: FileId,
|
||||||
|
) -> Option<Runnable> {
|
||||||
let has_test_function = module
|
let has_test_function = module
|
||||||
.item_list()?
|
.item_list()?
|
||||||
.items()
|
.items()
|
||||||
|
@ -138,11 +151,34 @@ fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<R
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let range = module.syntax().text_range();
|
let range = module.syntax().text_range();
|
||||||
let module = sema.to_def(&module)?;
|
let module_def = sema.to_def(&module)?;
|
||||||
|
|
||||||
let path =
|
let path = module_def
|
||||||
module.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::");
|
.path_to_root(sema.db)
|
||||||
Some(Runnable { range, kind: RunnableKind::TestMod { path } })
|
.into_iter()
|
||||||
|
.rev()
|
||||||
|
.filter_map(|it| it.name(sema.db))
|
||||||
|
.join("::");
|
||||||
|
|
||||||
|
let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module));
|
||||||
|
let features_needed = get_features_needed(attrs);
|
||||||
|
|
||||||
|
Some(Runnable { range, kind: RunnableKind::TestMod { path }, features_needed })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_features_needed(attrs: Attrs) -> Option<Vec<SmolStr>> {
|
||||||
|
let cfg_expr = attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree));
|
||||||
|
let features_needed = cfg_expr.fold(vec![], |mut acc, cfg| {
|
||||||
|
if let Some(features_needed) = cfg.minimal_features_needed() {
|
||||||
|
acc.extend(features_needed);
|
||||||
|
}
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
if features_needed.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(features_needed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -174,6 +210,7 @@ mod tests {
|
||||||
Runnable {
|
Runnable {
|
||||||
range: 1..21,
|
range: 1..21,
|
||||||
kind: Bin,
|
kind: Bin,
|
||||||
|
features_needed: None,
|
||||||
},
|
},
|
||||||
Runnable {
|
Runnable {
|
||||||
range: 22..46,
|
range: 22..46,
|
||||||
|
@ -185,6 +222,7 @@ mod tests {
|
||||||
ignore: false,
|
ignore: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
features_needed: None,
|
||||||
},
|
},
|
||||||
Runnable {
|
Runnable {
|
||||||
range: 47..81,
|
range: 47..81,
|
||||||
|
@ -196,6 +234,7 @@ mod tests {
|
||||||
ignore: true,
|
ignore: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
features_needed: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"###
|
"###
|
||||||
|
@ -223,6 +262,7 @@ mod tests {
|
||||||
Runnable {
|
Runnable {
|
||||||
range: 1..21,
|
range: 1..21,
|
||||||
kind: Bin,
|
kind: Bin,
|
||||||
|
features_needed: None,
|
||||||
},
|
},
|
||||||
Runnable {
|
Runnable {
|
||||||
range: 22..64,
|
range: 22..64,
|
||||||
|
@ -231,6 +271,7 @@ mod tests {
|
||||||
"foo",
|
"foo",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
features_needed: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"###
|
"###
|
||||||
|
@ -258,6 +299,7 @@ mod tests {
|
||||||
kind: TestMod {
|
kind: TestMod {
|
||||||
path: "test_mod",
|
path: "test_mod",
|
||||||
},
|
},
|
||||||
|
features_needed: None,
|
||||||
},
|
},
|
||||||
Runnable {
|
Runnable {
|
||||||
range: 28..57,
|
range: 28..57,
|
||||||
|
@ -269,6 +311,7 @@ mod tests {
|
||||||
ignore: false,
|
ignore: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
features_needed: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"###
|
"###
|
||||||
|
@ -298,6 +341,7 @@ mod tests {
|
||||||
kind: TestMod {
|
kind: TestMod {
|
||||||
path: "foo::test_mod",
|
path: "foo::test_mod",
|
||||||
},
|
},
|
||||||
|
features_needed: None,
|
||||||
},
|
},
|
||||||
Runnable {
|
Runnable {
|
||||||
range: 46..79,
|
range: 46..79,
|
||||||
|
@ -309,6 +353,7 @@ mod tests {
|
||||||
ignore: false,
|
ignore: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
features_needed: None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"###
|
"###
|
||||||
|
@ -340,6 +385,7 @@ mod tests {
|
||||||
kind: TestMod {
|
kind: TestMod {
|
||||||
path: "foo::bar::test_mod",
|
path: "foo::bar::test_mod",
|
||||||
},
|
},
|
||||||
|
features_needed: None,
|
||||||
},
|
},
|
||||||
Runnable {
|
Runnable {
|
||||||
range: 68..105,
|
range: 68..105,
|
||||||
|
@ -351,6 +397,80 @@ mod tests {
|
||||||
ignore: false,
|
ignore: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
features_needed: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_runnables_with_feature() {
|
||||||
|
let (analysis, pos) = analysis_and_position(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs crate:foo cfg:feature=foo
|
||||||
|
<|> //empty
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "foo")]
|
||||||
|
fn test_foo1() {}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let runnables = analysis.runnables(pos.file_id).unwrap();
|
||||||
|
assert_debug_snapshot!(&runnables,
|
||||||
|
@r###"
|
||||||
|
[
|
||||||
|
Runnable {
|
||||||
|
range: 1..58,
|
||||||
|
kind: Test {
|
||||||
|
test_id: Name(
|
||||||
|
"test_foo1",
|
||||||
|
),
|
||||||
|
attr: TestAttr {
|
||||||
|
ignore: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
features_needed: Some(
|
||||||
|
[
|
||||||
|
"foo",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_runnables_with_features() {
|
||||||
|
let (analysis, pos) = analysis_and_position(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs crate:foo cfg:feature=foo,feature=bar
|
||||||
|
<|> //empty
|
||||||
|
#[test]
|
||||||
|
#[cfg(all(feature = "foo", feature = "bar"))]
|
||||||
|
fn test_foo1() {}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let runnables = analysis.runnables(pos.file_id).unwrap();
|
||||||
|
assert_debug_snapshot!(&runnables,
|
||||||
|
@r###"
|
||||||
|
[
|
||||||
|
Runnable {
|
||||||
|
range: 1..80,
|
||||||
|
kind: Test {
|
||||||
|
test_id: Name(
|
||||||
|
"test_foo1",
|
||||||
|
),
|
||||||
|
attr: TestAttr {
|
||||||
|
ignore: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
features_needed: Some(
|
||||||
|
[
|
||||||
|
"foo",
|
||||||
|
"bar",
|
||||||
|
],
|
||||||
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"###
|
"###
|
||||||
|
|
|
@ -4,6 +4,7 @@ use ra_ide::{FileId, RunnableKind, TestId};
|
||||||
use ra_project_model::{self, ProjectWorkspace, TargetKind};
|
use ra_project_model::{self, ProjectWorkspace, TargetKind};
|
||||||
|
|
||||||
use crate::{world::WorldSnapshot, Result};
|
use crate::{world::WorldSnapshot, Result};
|
||||||
|
use ra_syntax::SmolStr;
|
||||||
|
|
||||||
/// Abstract representation of Cargo target.
|
/// Abstract representation of Cargo target.
|
||||||
///
|
///
|
||||||
|
@ -20,6 +21,7 @@ impl CargoTargetSpec {
|
||||||
pub(crate) fn runnable_args(
|
pub(crate) fn runnable_args(
|
||||||
spec: Option<CargoTargetSpec>,
|
spec: Option<CargoTargetSpec>,
|
||||||
kind: &RunnableKind,
|
kind: &RunnableKind,
|
||||||
|
features_needed: &Option<Vec<SmolStr>>,
|
||||||
) -> Result<(Vec<String>, Vec<String>)> {
|
) -> Result<(Vec<String>, Vec<String>)> {
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
let mut extra_args = Vec::new();
|
let mut extra_args = Vec::new();
|
||||||
|
@ -73,6 +75,13 @@ impl CargoTargetSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(features_needed) = features_needed {
|
||||||
|
features_needed.iter().for_each(|feature| {
|
||||||
|
args.push("--features".to_string());
|
||||||
|
args.push(feature.to_string());
|
||||||
|
});
|
||||||
|
}
|
||||||
Ok((args, extra_args))
|
Ok((args, extra_args))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1012,7 +1012,8 @@ fn to_lsp_runnable(
|
||||||
) -> Result<lsp_ext::Runnable> {
|
) -> Result<lsp_ext::Runnable> {
|
||||||
let spec = CargoTargetSpec::for_file(world, file_id)?;
|
let spec = CargoTargetSpec::for_file(world, file_id)?;
|
||||||
let target = spec.as_ref().map(|s| s.target.clone());
|
let target = spec.as_ref().map(|s| s.target.clone());
|
||||||
let (args, extra_args) = CargoTargetSpec::runnable_args(spec, &runnable.kind)?;
|
let (args, extra_args) =
|
||||||
|
CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.features_needed)?;
|
||||||
let line_index = world.analysis().file_line_index(file_id)?;
|
let line_index = world.analysis().file_line_index(file_id)?;
|
||||||
let label = match &runnable.kind {
|
let label = match &runnable.kind {
|
||||||
RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
|
RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
|
||||||
|
|
Loading…
Reference in a new issue