aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_cfg/src/cfg_expr.rs10
-rw-r--r--crates/ra_db/src/fixture.rs70
-rw-r--r--crates/ra_db/src/input.rs15
-rw-r--r--crates/ra_hir/src/lib.rs1
-rw-r--r--crates/ra_hir_def/src/attr.rs2
-rw-r--r--crates/ra_hir_def/src/body.rs2
-rw-r--r--crates/ra_ide/src/call_hierarchy.rs29
-rw-r--r--crates/ra_ide/src/mock_analysis.rs123
-rw-r--r--crates/ra_ide/src/runnables.rs189
-rw-r--r--crates/ra_ide/src/snapshots/highlighting.html2
-rw-r--r--crates/ra_ide/src/syntax_highlighting.rs4
-rw-r--r--crates/ra_ide/src/syntax_highlighting/tags.rs6
-rw-r--r--crates/rust-analyzer/Cargo.toml3
-rw-r--r--crates/rust-analyzer/src/cargo_target_spec.rs8
-rw-r--r--crates/rust-analyzer/src/main_loop/handlers.rs81
-rw-r--r--crates/rust-analyzer/src/semantic_tokens.rs1
-rw-r--r--crates/rust-analyzer/src/to_proto.rs1
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/support.rs2
-rw-r--r--crates/test_utils/Cargo.toml4
-rw-r--r--crates/test_utils/src/lib.rs160
20 files changed, 591 insertions, 122 deletions
diff --git a/crates/ra_cfg/src/cfg_expr.rs b/crates/ra_cfg/src/cfg_expr.rs
index 39d71851c..85b100c6a 100644
--- a/crates/ra_cfg/src/cfg_expr.rs
+++ b/crates/ra_cfg/src/cfg_expr.rs
@@ -88,13 +88,17 @@ fn next_cfg_expr(it: &mut SliceIter<tt::TokenTree>) -> Option<CfgExpr> {
88mod tests { 88mod tests {
89 use super::*; 89 use super::*;
90 90
91 use mbe::ast_to_token_tree; 91 use mbe::{ast_to_token_tree, TokenMap};
92 use ra_syntax::ast::{self, AstNode}; 92 use ra_syntax::ast::{self, AstNode};
93 93
94 fn assert_parse_result(input: &str, expected: CfgExpr) { 94 fn get_token_tree_generated(input: &str) -> (tt::Subtree, TokenMap) {
95 let source_file = ast::SourceFile::parse(input).ok().unwrap(); 95 let source_file = ast::SourceFile::parse(input).ok().unwrap();
96 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); 96 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
97 let (tt, _) = ast_to_token_tree(&tt).unwrap(); 97 ast_to_token_tree(&tt).unwrap()
98 }
99
100 fn assert_parse_result(input: &str, expected: CfgExpr) {
101 let (tt, _) = get_token_tree_generated(input);
98 assert_eq!(parse_cfg(&tt), expected); 102 assert_eq!(parse_cfg(&tt), expected);
99 } 103 }
100 104
diff --git a/crates/ra_db/src/fixture.rs b/crates/ra_db/src/fixture.rs
index f8f767091..f6b50c67c 100644
--- a/crates/ra_db/src/fixture.rs
+++ b/crates/ra_db/src/fixture.rs
@@ -63,7 +63,7 @@ use std::sync::Arc;
63 63
64use ra_cfg::CfgOptions; 64use ra_cfg::CfgOptions;
65use rustc_hash::FxHashMap; 65use rustc_hash::FxHashMap;
66use test_utils::{extract_offset, parse_fixture, parse_single_fixture, CURSOR_MARKER}; 66use test_utils::{extract_offset, parse_fixture, parse_single_fixture, FixtureMeta, CURSOR_MARKER};
67 67
68use crate::{ 68use crate::{
69 input::CrateName, CrateGraph, CrateId, Edition, Env, FileId, FilePosition, RelativePathBuf, 69 input::CrateName, CrateGraph, CrateId, Edition, Env, FileId, FilePosition, RelativePathBuf,
@@ -113,7 +113,7 @@ fn with_single_file(db: &mut dyn SourceDatabaseExt, ra_fixture: &str) -> FileId
113 let fixture = parse_single_fixture(ra_fixture); 113 let fixture = parse_single_fixture(ra_fixture);
114 114
115 let crate_graph = if let Some(entry) = fixture { 115 let crate_graph = if let Some(entry) = fixture {
116 let meta = match parse_meta(&entry.meta) { 116 let meta = match ParsedMeta::from(&entry.meta) {
117 ParsedMeta::File(it) => it, 117 ParsedMeta::File(it) => it,
118 _ => panic!("with_single_file only support file meta"), 118 _ => panic!("with_single_file only support file meta"),
119 }; 119 };
@@ -170,7 +170,7 @@ fn with_files(db: &mut dyn SourceDatabaseExt, fixture: &str) -> Option<FilePosit
170 let mut file_position = None; 170 let mut file_position = None;
171 171
172 for entry in fixture.iter() { 172 for entry in fixture.iter() {
173 let meta = match parse_meta(&entry.meta) { 173 let meta = match ParsedMeta::from(&entry.meta) {
174 ParsedMeta::Root { path } => { 174 ParsedMeta::Root { path } => {
175 let source_root = std::mem::replace(&mut source_root, SourceRoot::new_local()); 175 let source_root = std::mem::replace(&mut source_root, SourceRoot::new_local());
176 db.set_source_root(source_root_id, Arc::new(source_root)); 176 db.set_source_root(source_root_id, Arc::new(source_root));
@@ -258,53 +258,25 @@ struct FileMeta {
258 env: Env, 258 env: Env,
259} 259}
260 260
261//- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo) 261impl From<&FixtureMeta> for ParsedMeta {
262fn parse_meta(meta: &str) -> ParsedMeta { 262 fn from(meta: &FixtureMeta) -> Self {
263 let components = meta.split_ascii_whitespace().collect::<Vec<_>>(); 263 match meta {
264 264 FixtureMeta::Root { path } => {
265 if components[0] == "root" { 265 // `Self::Root` causes a false warning: 'variant is never constructed: `Root` '
266 let path: RelativePathBuf = components[1].into(); 266 // see https://github.com/rust-lang/rust/issues/69018
267 assert!(path.starts_with("/") && path.ends_with("/")); 267 ParsedMeta::Root { path: path.to_owned() }
268 return ParsedMeta::Root { path };
269 }
270
271 let path: RelativePathBuf = components[0].into();
272 assert!(path.starts_with("/"));
273
274 let mut krate = None;
275 let mut deps = Vec::new();
276 let mut edition = Edition::Edition2018;
277 let mut cfg = CfgOptions::default();
278 let mut env = Env::default();
279 for component in components[1..].iter() {
280 let (key, value) = split1(component, ':').unwrap();
281 match key {
282 "crate" => krate = Some(value.to_string()),
283 "deps" => deps = value.split(',').map(|it| it.to_string()).collect(),
284 "edition" => edition = Edition::from_str(&value).unwrap(),
285 "cfg" => {
286 for key in value.split(',') {
287 match split1(key, '=') {
288 None => cfg.insert_atom(key.into()),
289 Some((k, v)) => cfg.insert_key_value(k.into(), v.into()),
290 }
291 }
292 }
293 "env" => {
294 for key in value.split(',') {
295 if let Some((k, v)) = split1(key, '=') {
296 env.set(k, v.into());
297 }
298 }
299 } 268 }
300 _ => panic!("bad component: {:?}", component), 269 FixtureMeta::File(f) => Self::File(FileMeta {
270 path: f.path.to_owned().into(),
271 krate: f.crate_name.to_owned().into(),
272 deps: f.deps.to_owned(),
273 cfg: f.cfg.to_owned(),
274 edition: f
275 .edition
276 .as_ref()
277 .map_or(Edition::Edition2018, |v| Edition::from_str(&v).unwrap()),
278 env: Env::from(f.env.iter()),
279 }),
301 } 280 }
302 } 281 }
303
304 ParsedMeta::File(FileMeta { path, krate, deps, edition, cfg, env })
305}
306
307fn split1(haystack: &str, delim: char) -> Option<(&str, &str)> {
308 let idx = haystack.find(delim)?;
309 Some((&haystack[..idx], &haystack[idx + delim.len_utf8()..]))
310} 282}
diff --git a/crates/ra_db/src/input.rs b/crates/ra_db/src/input.rs
index ab14e2d5e..4d2d3b48a 100644
--- a/crates/ra_db/src/input.rs
+++ b/crates/ra_db/src/input.rs
@@ -311,6 +311,21 @@ impl fmt::Display for Edition {
311 } 311 }
312} 312}
313 313
314impl<'a, T> From<T> for Env
315where
316 T: Iterator<Item = (&'a String, &'a String)>,
317{
318 fn from(iter: T) -> Self {
319 let mut result = Self::default();
320
321 for (k, v) in iter {
322 result.entries.insert(k.to_owned(), v.to_owned());
323 }
324
325 result
326 }
327}
328
314impl Env { 329impl Env {
315 pub fn set(&mut self, env: &str, value: String) { 330 pub fn set(&mut self, env: &str, value: String) {
316 self.entries.insert(env.to_owned(), value); 331 self.entries.insert(env.to_owned(), value);
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs
index c5df4ac24..3364a822f 100644
--- a/crates/ra_hir/src/lib.rs
+++ b/crates/ra_hir/src/lib.rs
@@ -62,6 +62,7 @@ pub use crate::{
62 62
63pub use hir_def::{ 63pub use hir_def::{
64 adt::StructKind, 64 adt::StructKind,
65 attr::Attrs,
65 body::scope::ExprScopes, 66 body::scope::ExprScopes,
66 builtin_type::BuiltinType, 67 builtin_type::BuiltinType,
67 docs::Documentation, 68 docs::Documentation,
diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs
index 576cd0c65..8b6c0bede 100644
--- a/crates/ra_hir_def/src/attr.rs
+++ b/crates/ra_hir_def/src/attr.rs
@@ -81,7 +81,7 @@ impl Attrs {
81 } 81 }
82 } 82 }
83 83
84 fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Attrs { 84 pub fn from_attrs_owner(db: &dyn DefDatabase, owner: InFile<&dyn AttrsOwner>) -> Attrs {
85 let hygiene = Hygiene::new(db.upcast(), owner.file_id); 85 let hygiene = Hygiene::new(db.upcast(), owner.file_id);
86 Attrs::new(owner.value, &hygiene) 86 Attrs::new(owner.value, &hygiene)
87 } 87 }
diff --git a/crates/ra_hir_def/src/body.rs b/crates/ra_hir_def/src/body.rs
index f5a7305dc..273036cee 100644
--- a/crates/ra_hir_def/src/body.rs
+++ b/crates/ra_hir_def/src/body.rs
@@ -29,7 +29,7 @@ use crate::{
29 AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId, 29 AsMacroCall, DefWithBodyId, HasModule, Lookup, ModuleId,
30}; 30};
31 31
32/// A subset of Exander that only deals with cfg attributes. We only need it to 32/// A subset of Expander that only deals with cfg attributes. We only need it to
33/// avoid cyclic queries in crate def map during enum processing. 33/// avoid cyclic queries in crate def map during enum processing.
34pub(crate) struct CfgExpander { 34pub(crate) struct CfgExpander {
35 cfg_options: CfgOptions, 35 cfg_options: CfgOptions,
diff --git a/crates/ra_ide/src/call_hierarchy.rs b/crates/ra_ide/src/call_hierarchy.rs
index 85d1f0cb1..defd8176f 100644
--- a/crates/ra_ide/src/call_hierarchy.rs
+++ b/crates/ra_ide/src/call_hierarchy.rs
@@ -246,6 +246,35 @@ mod tests {
246 } 246 }
247 247
248 #[test] 248 #[test]
249 fn test_call_hierarchy_in_tests_mod() {
250 check_hierarchy(
251 r#"
252 //- /lib.rs cfg:test
253 fn callee() {}
254 fn caller1() {
255 call<|>ee();
256 }
257
258 #[cfg(test)]
259 mod tests {
260 use super::*;
261
262 #[test]
263 fn test_caller() {
264 callee();
265 }
266 }
267 "#,
268 "callee FN_DEF FileId(1) 0..14 3..9",
269 &[
270 "caller1 FN_DEF FileId(1) 15..45 18..25 : [34..40]",
271 "test_caller FN_DEF FileId(1) 93..147 108..119 : [132..138]",
272 ],
273 &[],
274 );
275 }
276
277 #[test]
249 fn test_call_hierarchy_in_different_files() { 278 fn test_call_hierarchy_in_different_files() {
250 check_hierarchy( 279 check_hierarchy(
251 r#" 280 r#"
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs
index 2c13f206a..ad78d2d93 100644
--- a/crates/ra_ide/src/mock_analysis.rs
+++ b/crates/ra_ide/src/mock_analysis.rs
@@ -1,21 +1,81 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use std::str::FromStr;
3use std::sync::Arc; 4use std::sync::Arc;
4 5
5use ra_cfg::CfgOptions; 6use ra_cfg::CfgOptions;
6use ra_db::{CrateName, Env, RelativePathBuf}; 7use ra_db::{CrateName, Env, RelativePathBuf};
7use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER}; 8use test_utils::{extract_offset, extract_range, parse_fixture, FixtureEntry, CURSOR_MARKER};
8 9
9use crate::{ 10use crate::{
10 Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition::Edition2018, FileId, FilePosition, 11 Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition, FileId, FilePosition, FileRange,
11 FileRange, SourceRootId, 12 SourceRootId,
12}; 13};
13 14
15#[derive(Debug)]
16enum MockFileData {
17 Plain { path: String, content: String },
18 Fixture(FixtureEntry),
19}
20
21impl MockFileData {
22 fn new(path: String, content: String) -> Self {
23 // `Self::Plain` causes a false warning: 'variant is never constructed: `Plain` '
24 // see https://github.com/rust-lang/rust/issues/69018
25 MockFileData::Plain { path, content }
26 }
27
28 fn path(&self) -> &str {
29 match self {
30 MockFileData::Plain { path, .. } => path.as_str(),
31 MockFileData::Fixture(f) => f.meta.path().as_str(),
32 }
33 }
34
35 fn content(&self) -> &str {
36 match self {
37 MockFileData::Plain { content, .. } => content,
38 MockFileData::Fixture(f) => f.text.as_str(),
39 }
40 }
41
42 fn cfg_options(&self) -> CfgOptions {
43 match self {
44 MockFileData::Fixture(f) => {
45 f.meta.cfg_options().map_or_else(Default::default, |o| o.clone())
46 }
47 _ => CfgOptions::default(),
48 }
49 }
50
51 fn edition(&self) -> Edition {
52 match self {
53 MockFileData::Fixture(f) => {
54 f.meta.edition().map_or(Edition::Edition2018, |v| Edition::from_str(v).unwrap())
55 }
56 _ => Edition::Edition2018,
57 }
58 }
59
60 fn env(&self) -> Env {
61 match self {
62 MockFileData::Fixture(f) => Env::from(f.meta.env()),
63 _ => Env::default(),
64 }
65 }
66}
67
68impl From<FixtureEntry> for MockFileData {
69 fn from(fixture: FixtureEntry) -> Self {
70 Self::Fixture(fixture)
71 }
72}
73
14/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis 74/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis
15/// from a set of in-memory files. 75/// from a set of in-memory files.
16#[derive(Debug, Default)] 76#[derive(Debug, Default)]
17pub struct MockAnalysis { 77pub struct MockAnalysis {
18 files: Vec<(String, String)>, 78 files: Vec<MockFileData>,
19} 79}
20 80
21impl MockAnalysis { 81impl MockAnalysis {
@@ -35,7 +95,7 @@ impl MockAnalysis {
35 pub fn with_files(fixture: &str) -> MockAnalysis { 95 pub fn with_files(fixture: &str) -> MockAnalysis {
36 let mut res = MockAnalysis::new(); 96 let mut res = MockAnalysis::new();
37 for entry in parse_fixture(fixture) { 97 for entry in parse_fixture(fixture) {
38 res.add_file(&entry.meta, &entry.text); 98 res.add_file_fixture(entry);
39 } 99 }
40 res 100 res
41 } 101 }
@@ -48,30 +108,44 @@ impl MockAnalysis {
48 for entry in parse_fixture(fixture) { 108 for entry in parse_fixture(fixture) {
49 if entry.text.contains(CURSOR_MARKER) { 109 if entry.text.contains(CURSOR_MARKER) {
50 assert!(position.is_none(), "only one marker (<|>) per fixture is allowed"); 110 assert!(position.is_none(), "only one marker (<|>) per fixture is allowed");
51 position = Some(res.add_file_with_position(&entry.meta, &entry.text)); 111 position = Some(res.add_file_fixture_with_position(entry));
52 } else { 112 } else {
53 res.add_file(&entry.meta, &entry.text); 113 res.add_file_fixture(entry);
54 } 114 }
55 } 115 }
56 let position = position.expect("expected a marker (<|>)"); 116 let position = position.expect("expected a marker (<|>)");
57 (res, position) 117 (res, position)
58 } 118 }
59 119
120 pub fn add_file_fixture(&mut self, fixture: FixtureEntry) -> FileId {
121 let file_id = self.next_id();
122 self.files.push(MockFileData::from(fixture));
123 file_id
124 }
125
126 pub fn add_file_fixture_with_position(&mut self, mut fixture: FixtureEntry) -> FilePosition {
127 let (offset, text) = extract_offset(&fixture.text);
128 fixture.text = text;
129 let file_id = self.next_id();
130 self.files.push(MockFileData::from(fixture));
131 FilePosition { file_id, offset }
132 }
133
60 pub fn add_file(&mut self, path: &str, text: &str) -> FileId { 134 pub fn add_file(&mut self, path: &str, text: &str) -> FileId {
61 let file_id = FileId((self.files.len() + 1) as u32); 135 let file_id = self.next_id();
62 self.files.push((path.to_string(), text.to_string())); 136 self.files.push(MockFileData::new(path.to_string(), text.to_string()));
63 file_id 137 file_id
64 } 138 }
65 pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition { 139 pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition {
66 let (offset, text) = extract_offset(text); 140 let (offset, text) = extract_offset(text);
67 let file_id = FileId((self.files.len() + 1) as u32); 141 let file_id = self.next_id();
68 self.files.push((path.to_string(), text)); 142 self.files.push(MockFileData::new(path.to_string(), text));
69 FilePosition { file_id, offset } 143 FilePosition { file_id, offset }
70 } 144 }
71 pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange { 145 pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange {
72 let (range, text) = extract_range(text); 146 let (range, text) = extract_range(text);
73 let file_id = FileId((self.files.len() + 1) as u32); 147 let file_id = self.next_id();
74 self.files.push((path.to_string(), text)); 148 self.files.push(MockFileData::new(path.to_string(), text));
75 FileRange { file_id, range } 149 FileRange { file_id, range }
76 } 150 }
77 pub fn id_of(&self, path: &str) -> FileId { 151 pub fn id_of(&self, path: &str) -> FileId {
@@ -79,7 +153,7 @@ impl MockAnalysis {
79 .files 153 .files
80 .iter() 154 .iter()
81 .enumerate() 155 .enumerate()
82 .find(|(_, (p, _text))| path == p) 156 .find(|(_, data)| path == data.path())
83 .expect("no file in this mock"); 157 .expect("no file in this mock");
84 FileId(idx as u32 + 1) 158 FileId(idx as u32 + 1)
85 } 159 }
@@ -90,18 +164,21 @@ impl MockAnalysis {
90 change.add_root(source_root, true); 164 change.add_root(source_root, true);
91 let mut crate_graph = CrateGraph::default(); 165 let mut crate_graph = CrateGraph::default();
92 let mut root_crate = None; 166 let mut root_crate = None;
93 for (i, (path, contents)) in self.files.into_iter().enumerate() { 167 for (i, data) in self.files.into_iter().enumerate() {
168 let path = data.path();
94 assert!(path.starts_with('/')); 169 assert!(path.starts_with('/'));
95 let path = RelativePathBuf::from_path(&path[1..]).unwrap(); 170 let path = RelativePathBuf::from_path(&path[1..]).unwrap();
171 let cfg_options = data.cfg_options();
96 let file_id = FileId(i as u32 + 1); 172 let file_id = FileId(i as u32 + 1);
97 let cfg_options = CfgOptions::default(); 173 let edition = data.edition();
174 let env = data.env();
98 if path == "/lib.rs" || path == "/main.rs" { 175 if path == "/lib.rs" || path == "/main.rs" {
99 root_crate = Some(crate_graph.add_crate_root( 176 root_crate = Some(crate_graph.add_crate_root(
100 file_id, 177 file_id,
101 Edition2018, 178 edition,
102 None, 179 None,
103 cfg_options, 180 cfg_options,
104 Env::default(), 181 env,
105 Default::default(), 182 Default::default(),
106 Default::default(), 183 Default::default(),
107 )); 184 ));
@@ -109,10 +186,10 @@ impl MockAnalysis {
109 let crate_name = path.parent().unwrap().file_name().unwrap(); 186 let crate_name = path.parent().unwrap().file_name().unwrap();
110 let other_crate = crate_graph.add_crate_root( 187 let other_crate = crate_graph.add_crate_root(
111 file_id, 188 file_id,
112 Edition2018, 189 edition,
113 Some(CrateName::new(crate_name).unwrap()), 190 Some(CrateName::new(crate_name).unwrap()),
114 cfg_options, 191 cfg_options,
115 Env::default(), 192 env,
116 Default::default(), 193 Default::default(),
117 Default::default(), 194 Default::default(),
118 ); 195 );
@@ -122,7 +199,7 @@ impl MockAnalysis {
122 .unwrap(); 199 .unwrap();
123 } 200 }
124 } 201 }
125 change.add_file(source_root, file_id, path, Arc::new(contents)); 202 change.add_file(source_root, file_id, path, Arc::new(data.content().to_owned()));
126 } 203 }
127 change.set_crate_graph(crate_graph); 204 change.set_crate_graph(crate_graph);
128 host.apply_change(change); 205 host.apply_change(change);
@@ -131,6 +208,10 @@ impl MockAnalysis {
131 pub fn analysis(self) -> Analysis { 208 pub fn analysis(self) -> Analysis {
132 self.analysis_host().analysis() 209 self.analysis_host().analysis()
133 } 210 }
211
212 fn next_id(&self) -> FileId {
213 FileId((self.files.len() + 1) as u32)
214 }
134} 215}
135 216
136/// Creates analysis from a multi-file fixture, returns positions marked with <|>. 217/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs
index 131b8f307..6e7e47199 100644
--- a/crates/ra_ide/src/runnables.rs
+++ b/crates/ra_ide/src/runnables.rs
@@ -1,6 +1,6 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2 2
3use hir::{AsAssocItem, Semantics}; 3use hir::{AsAssocItem, Attrs, HirFileId, InFile, Semantics};
4use itertools::Itertools; 4use itertools::Itertools;
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
@@ -10,12 +10,14 @@ use ra_syntax::{
10 10
11use crate::FileId; 11use crate::FileId;
12use ast::DocCommentsOwner; 12use ast::DocCommentsOwner;
13use ra_cfg::CfgExpr;
13use std::fmt::Display; 14use std::fmt::Display;
14 15
15#[derive(Debug)] 16#[derive(Debug)]
16pub struct Runnable { 17pub struct Runnable {
17 pub range: TextRange, 18 pub range: TextRange,
18 pub kind: RunnableKind, 19 pub kind: RunnableKind,
20 pub cfg_exprs: Vec<CfgExpr>,
19} 21}
20 22
21#[derive(Debug)] 23#[derive(Debug)]
@@ -45,29 +47,33 @@ pub enum RunnableKind {
45pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 47pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
46 let sema = Semantics::new(db); 48 let sema = Semantics::new(db);
47 let source_file = sema.parse(file_id); 49 let source_file = sema.parse(file_id);
48 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i)).collect() 50 source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect()
49} 51}
50 52
51fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> { 53fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode, file_id: FileId) -> Option<Runnable> {
52 match_ast! { 54 match_ast! {
53 match item { 55 match item {
54 ast::FnDef(it) => runnable_fn(sema, it), 56 ast::FnDef(it) => runnable_fn(sema, it, file_id),
55 ast::Module(it) => runnable_mod(sema, it), 57 ast::Module(it) => runnable_mod(sema, it, file_id),
56 _ => None, 58 _ => None,
57 } 59 }
58 } 60 }
59} 61}
60 62
61fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Runnable> { 63fn runnable_fn(
64 sema: &Semantics<RootDatabase>,
65 fn_def: ast::FnDef,
66 file_id: FileId,
67) -> Option<Runnable> {
62 let name_string = fn_def.name()?.text().to_string(); 68 let name_string = fn_def.name()?.text().to_string();
63 69
64 let kind = if name_string == "main" { 70 let kind = if name_string == "main" {
65 RunnableKind::Bin 71 RunnableKind::Bin
66 } else { 72 } else {
67 let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) { 73 let test_id = match sema.to_def(&fn_def).map(|def| def.module(sema.db)) {
68 let def = sema.to_def(&fn_def)?; 74 Some(module) => {
69 let impl_trait_name = 75 let def = sema.to_def(&fn_def)?;
70 def.as_assoc_item(sema.db).and_then(|assoc_item| { 76 let impl_trait_name = def.as_assoc_item(sema.db).and_then(|assoc_item| {
71 match assoc_item.container(sema.db) { 77 match assoc_item.container(sema.db) {
72 hir::AssocItemContainer::Trait(trait_item) => { 78 hir::AssocItemContainer::Trait(trait_item) => {
73 Some(trait_item.name(sema.db).to_string()) 79 Some(trait_item.name(sema.db).to_string())
@@ -79,25 +85,25 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
79 } 85 }
80 }); 86 });
81 87
82 let path_iter = module 88 let path_iter = module
83 .path_to_root(sema.db) 89 .path_to_root(sema.db)
84 .into_iter() 90 .into_iter()
85 .rev() 91 .rev()
86 .filter_map(|it| it.name(sema.db)) 92 .filter_map(|it| it.name(sema.db))
87 .map(|name| name.to_string()); 93 .map(|name| name.to_string());
88 94
89 let path = if let Some(impl_trait_name) = impl_trait_name { 95 let path = if let Some(impl_trait_name) = impl_trait_name {
90 path_iter 96 path_iter
91 .chain(std::iter::once(impl_trait_name)) 97 .chain(std::iter::once(impl_trait_name))
92 .chain(std::iter::once(name_string)) 98 .chain(std::iter::once(name_string))
93 .join("::") 99 .join("::")
94 } else { 100 } else {
95 path_iter.chain(std::iter::once(name_string)).join("::") 101 path_iter.chain(std::iter::once(name_string)).join("::")
96 }; 102 };
97 103
98 TestId::Path(path) 104 TestId::Path(path)
99 } else { 105 }
100 TestId::Name(name_string) 106 None => TestId::Name(name_string),
101 }; 107 };
102 108
103 if has_test_related_attribute(&fn_def) { 109 if has_test_related_attribute(&fn_def) {
@@ -111,7 +117,12 @@ fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Run
111 return None; 117 return None;
112 } 118 }
113 }; 119 };
114 Some(Runnable { range: fn_def.syntax().text_range(), kind }) 120
121 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &fn_def));
122 let cfg_exprs =
123 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
124
125 Some(Runnable { range: fn_def.syntax().text_range(), kind, cfg_exprs })
115} 126}
116 127
117#[derive(Debug)] 128#[derive(Debug)]
@@ -147,7 +158,11 @@ fn has_doc_test(fn_def: &ast::FnDef) -> bool {
147 fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```")) 158 fn_def.doc_comment_text().map_or(false, |comment| comment.contains("```"))
148} 159}
149 160
150fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> { 161fn runnable_mod(
162 sema: &Semantics<RootDatabase>,
163 module: ast::Module,
164 file_id: FileId,
165) -> Option<Runnable> {
151 let has_test_function = module 166 let has_test_function = module
152 .item_list()? 167 .item_list()?
153 .items() 168 .items()
@@ -160,11 +175,20 @@ fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<R
160 return None; 175 return None;
161 } 176 }
162 let range = module.syntax().text_range(); 177 let range = module.syntax().text_range();
163 let module = sema.to_def(&module)?; 178 let module_def = sema.to_def(&module)?;
164 179
165 let path = 180 let path = module_def
166 module.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::"); 181 .path_to_root(sema.db)
167 Some(Runnable { range, kind: RunnableKind::TestMod { path } }) 182 .into_iter()
183 .rev()
184 .filter_map(|it| it.name(sema.db))
185 .join("::");
186
187 let attrs = Attrs::from_attrs_owner(sema.db, InFile::new(HirFileId::from(file_id), &module));
188 let cfg_exprs =
189 attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect();
190
191 Some(Runnable { range, kind: RunnableKind::TestMod { path }, cfg_exprs })
168} 192}
169 193
170#[cfg(test)] 194#[cfg(test)]
@@ -196,6 +220,7 @@ mod tests {
196 Runnable { 220 Runnable {
197 range: 1..21, 221 range: 1..21,
198 kind: Bin, 222 kind: Bin,
223 cfg_exprs: [],
199 }, 224 },
200 Runnable { 225 Runnable {
201 range: 22..46, 226 range: 22..46,
@@ -207,6 +232,7 @@ mod tests {
207 ignore: false, 232 ignore: false,
208 }, 233 },
209 }, 234 },
235 cfg_exprs: [],
210 }, 236 },
211 Runnable { 237 Runnable {
212 range: 47..81, 238 range: 47..81,
@@ -218,6 +244,7 @@ mod tests {
218 ignore: true, 244 ignore: true,
219 }, 245 },
220 }, 246 },
247 cfg_exprs: [],
221 }, 248 },
222 ] 249 ]
223 "### 250 "###
@@ -245,6 +272,7 @@ mod tests {
245 Runnable { 272 Runnable {
246 range: 1..21, 273 range: 1..21,
247 kind: Bin, 274 kind: Bin,
275 cfg_exprs: [],
248 }, 276 },
249 Runnable { 277 Runnable {
250 range: 22..64, 278 range: 22..64,
@@ -253,6 +281,7 @@ mod tests {
253 "foo", 281 "foo",
254 ), 282 ),
255 }, 283 },
284 cfg_exprs: [],
256 }, 285 },
257 ] 286 ]
258 "### 287 "###
@@ -283,6 +312,7 @@ mod tests {
283 Runnable { 312 Runnable {
284 range: 1..21, 313 range: 1..21,
285 kind: Bin, 314 kind: Bin,
315 cfg_exprs: [],
286 }, 316 },
287 Runnable { 317 Runnable {
288 range: 51..105, 318 range: 51..105,
@@ -291,6 +321,7 @@ mod tests {
291 "Data::foo", 321 "Data::foo",
292 ), 322 ),
293 }, 323 },
324 cfg_exprs: [],
294 }, 325 },
295 ] 326 ]
296 "### 327 "###
@@ -318,6 +349,7 @@ mod tests {
318 kind: TestMod { 349 kind: TestMod {
319 path: "test_mod", 350 path: "test_mod",
320 }, 351 },
352 cfg_exprs: [],
321 }, 353 },
322 Runnable { 354 Runnable {
323 range: 28..57, 355 range: 28..57,
@@ -329,6 +361,7 @@ mod tests {
329 ignore: false, 361 ignore: false,
330 }, 362 },
331 }, 363 },
364 cfg_exprs: [],
332 }, 365 },
333 ] 366 ]
334 "### 367 "###
@@ -358,6 +391,7 @@ mod tests {
358 kind: TestMod { 391 kind: TestMod {
359 path: "foo::test_mod", 392 path: "foo::test_mod",
360 }, 393 },
394 cfg_exprs: [],
361 }, 395 },
362 Runnable { 396 Runnable {
363 range: 46..79, 397 range: 46..79,
@@ -369,6 +403,7 @@ mod tests {
369 ignore: false, 403 ignore: false,
370 }, 404 },
371 }, 405 },
406 cfg_exprs: [],
372 }, 407 },
373 ] 408 ]
374 "### 409 "###
@@ -400,6 +435,7 @@ mod tests {
400 kind: TestMod { 435 kind: TestMod {
401 path: "foo::bar::test_mod", 436 path: "foo::bar::test_mod",
402 }, 437 },
438 cfg_exprs: [],
403 }, 439 },
404 Runnable { 440 Runnable {
405 range: 68..105, 441 range: 68..105,
@@ -411,6 +447,89 @@ mod tests {
411 ignore: false, 447 ignore: false,
412 }, 448 },
413 }, 449 },
450 cfg_exprs: [],
451 },
452 ]
453 "###
454 );
455 }
456
457 #[test]
458 fn test_runnables_with_feature() {
459 let (analysis, pos) = analysis_and_position(
460 r#"
461 //- /lib.rs crate:foo cfg:feature=foo
462 <|> //empty
463 #[test]
464 #[cfg(feature = "foo")]
465 fn test_foo1() {}
466 "#,
467 );
468 let runnables = analysis.runnables(pos.file_id).unwrap();
469 assert_debug_snapshot!(&runnables,
470 @r###"
471 [
472 Runnable {
473 range: 1..58,
474 kind: Test {
475 test_id: Path(
476 "test_foo1",
477 ),
478 attr: TestAttr {
479 ignore: false,
480 },
481 },
482 cfg_exprs: [
483 KeyValue {
484 key: "feature",
485 value: "foo",
486 },
487 ],
488 },
489 ]
490 "###
491 );
492 }
493
494 #[test]
495 fn test_runnables_with_features() {
496 let (analysis, pos) = analysis_and_position(
497 r#"
498 //- /lib.rs crate:foo cfg:feature=foo,feature=bar
499 <|> //empty
500 #[test]
501 #[cfg(all(feature = "foo", feature = "bar"))]
502 fn test_foo1() {}
503 "#,
504 );
505 let runnables = analysis.runnables(pos.file_id).unwrap();
506 assert_debug_snapshot!(&runnables,
507 @r###"
508 [
509 Runnable {
510 range: 1..80,
511 kind: Test {
512 test_id: Path(
513 "test_foo1",
514 ),
515 attr: TestAttr {
516 ignore: false,
517 },
518 },
519 cfg_exprs: [
520 All(
521 [
522 KeyValue {
523 key: "feature",
524 value: "foo",
525 },
526 KeyValue {
527 key: "feature",
528 value: "bar",
529 },
530 ],
531 ),
532 ],
414 }, 533 },
415 ] 534 ]
416 "### 535 "###
diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html
index 635fe5cf9..2ceadf2fc 100644
--- a/crates/ra_ide/src/snapshots/highlighting.html
+++ b/crates/ra_ide/src/snapshots/highlighting.html
@@ -27,7 +27,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
27.keyword.unsafe { color: #BC8383; font-weight: bold; } 27.keyword.unsafe { color: #BC8383; font-weight: bold; }
28.control { font-style: italic; } 28.control { font-style: italic; }
29</style> 29</style>
30<pre><code><span class="attribute">#[derive(Clone, Debug)]</span> 30<pre><code><span class="attribute">#[</span><span class="function attribute">derive</span><span class="attribute">(Clone, Debug)]</span>
31<span class="keyword">struct</span> <span class="struct declaration">Foo</span> { 31<span class="keyword">struct</span> <span class="struct declaration">Foo</span> {
32 <span class="keyword">pub</span> <span class="field declaration">x</span>: <span class="builtin_type">i32</span>, 32 <span class="keyword">pub</span> <span class="field declaration">x</span>: <span class="builtin_type">i32</span>,
33 <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>, 33 <span class="keyword">pub</span> <span class="field declaration">y</span>: <span class="builtin_type">i32</span>,
diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs
index be57eeb0a..b55cf748d 100644
--- a/crates/ra_ide/src/syntax_highlighting.rs
+++ b/crates/ra_ide/src/syntax_highlighting.rs
@@ -361,7 +361,9 @@ fn highlight_element(
361 } 361 }
362 362
363 // Highlight references like the definitions they resolve to 363 // Highlight references like the definitions they resolve to
364 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => return None, 364 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
365 Highlight::from(HighlightTag::Function) | HighlightModifier::Attribute
366 }
365 NAME_REF => { 367 NAME_REF => {
366 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap(); 368 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
367 match classify_name_ref(sema, &name_ref) { 369 match classify_name_ref(sema, &name_ref) {
diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs
index be1a0f12b..33e6619ec 100644
--- a/crates/ra_ide/src/syntax_highlighting/tags.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tags.rs
@@ -45,8 +45,10 @@ pub enum HighlightTag {
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
46#[repr(u8)] 46#[repr(u8)]
47pub enum HighlightModifier { 47pub enum HighlightModifier {
48 /// Used to differentiate individual elements within attributes.
49 Attribute = 0,
48 /// Used with keywords like `if` and `break`. 50 /// Used with keywords like `if` and `break`.
49 ControlFlow = 0, 51 ControlFlow,
50 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is 52 /// `foo` in `fn foo(x: i32)` is a definition, `foo` in `foo(90 + 2)` is
51 /// not. 53 /// not.
52 Definition, 54 Definition,
@@ -95,6 +97,7 @@ impl fmt::Display for HighlightTag {
95 97
96impl HighlightModifier { 98impl HighlightModifier {
97 const ALL: &'static [HighlightModifier] = &[ 99 const ALL: &'static [HighlightModifier] = &[
100 HighlightModifier::Attribute,
98 HighlightModifier::ControlFlow, 101 HighlightModifier::ControlFlow,
99 HighlightModifier::Definition, 102 HighlightModifier::Definition,
100 HighlightModifier::Mutable, 103 HighlightModifier::Mutable,
@@ -103,6 +106,7 @@ impl HighlightModifier {
103 106
104 fn as_str(self) -> &'static str { 107 fn as_str(self) -> &'static str {
105 match self { 108 match self {
109 HighlightModifier::Attribute => "attribute",
106 HighlightModifier::ControlFlow => "control", 110 HighlightModifier::ControlFlow => "control",
107 HighlightModifier::Definition => "declaration", 111 HighlightModifier::Definition => "declaration",
108 HighlightModifier::Mutable => "mutable", 112 HighlightModifier::Mutable => "mutable",
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml
index 9b2d29b1d..65b487db3 100644
--- a/crates/rust-analyzer/Cargo.toml
+++ b/crates/rust-analyzer/Cargo.toml
@@ -40,6 +40,7 @@ ra_project_model = { path = "../ra_project_model" }
40ra_syntax = { path = "../ra_syntax" } 40ra_syntax = { path = "../ra_syntax" }
41ra_text_edit = { path = "../ra_text_edit" } 41ra_text_edit = { path = "../ra_text_edit" }
42ra_vfs = "0.6.0" 42ra_vfs = "0.6.0"
43ra_cfg = { path = "../ra_cfg"}
43 44
44# This should only be used in CLI 45# This should only be used in CLI
45ra_db = { path = "../ra_db" } 46ra_db = { path = "../ra_db" }
@@ -55,6 +56,8 @@ winapi = "0.3.8"
55tempfile = "3.1.0" 56tempfile = "3.1.0"
56insta = "0.16.0" 57insta = "0.16.0"
57test_utils = { path = "../test_utils" } 58test_utils = { path = "../test_utils" }
59mbe = { path = "../ra_mbe", package = "ra_mbe" }
60tt = { path = "../ra_tt", package = "ra_tt" }
58 61
59[features] 62[features]
60jemalloc = [ "ra_prof/jemalloc" ] 63jemalloc = [ "ra_prof/jemalloc" ]
diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs
index 5e5a17943..441fb61df 100644
--- a/crates/rust-analyzer/src/cargo_target_spec.rs
+++ b/crates/rust-analyzer/src/cargo_target_spec.rs
@@ -4,6 +4,7 @@ use ra_ide::{FileId, RunnableKind, TestId};
4use ra_project_model::{self, ProjectWorkspace, TargetKind}; 4use ra_project_model::{self, ProjectWorkspace, TargetKind};
5 5
6use crate::{world::WorldSnapshot, Result}; 6use crate::{world::WorldSnapshot, Result};
7use ra_syntax::SmolStr;
7 8
8/// Abstract representation of Cargo target. 9/// Abstract representation of Cargo target.
9/// 10///
@@ -20,6 +21,7 @@ impl CargoTargetSpec {
20 pub(crate) fn runnable_args( 21 pub(crate) fn runnable_args(
21 spec: Option<CargoTargetSpec>, 22 spec: Option<CargoTargetSpec>,
22 kind: &RunnableKind, 23 kind: &RunnableKind,
24 features_needed: &Vec<SmolStr>,
23 ) -> Result<(Vec<String>, Vec<String>)> { 25 ) -> Result<(Vec<String>, Vec<String>)> {
24 let mut args = Vec::new(); 26 let mut args = Vec::new();
25 let mut extra_args = Vec::new(); 27 let mut extra_args = Vec::new();
@@ -73,6 +75,12 @@ impl CargoTargetSpec {
73 } 75 }
74 } 76 }
75 } 77 }
78
79 features_needed.iter().for_each(|feature| {
80 args.push("--features".to_string());
81 args.push(feature.to_string());
82 });
83
76 Ok((args, extra_args)) 84 Ok((args, extra_args))
77 } 85 }
78 86
diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs
index ba6857556..2aaff3ea4 100644
--- a/crates/rust-analyzer/src/main_loop/handlers.rs
+++ b/crates/rust-analyzer/src/main_loop/handlers.rs
@@ -17,13 +17,14 @@ use lsp_types::{
17 SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult, 17 SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult,
18 SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, Url, WorkspaceEdit, 18 SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, Url, WorkspaceEdit,
19}; 19};
20use ra_cfg::CfgExpr;
20use ra_ide::{ 21use ra_ide::{
21 FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope, 22 FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope,
22 TextEdit, 23 TextEdit,
23}; 24};
24use ra_prof::profile; 25use ra_prof::profile;
25use ra_project_model::TargetKind; 26use ra_project_model::TargetKind;
26use ra_syntax::{AstNode, SyntaxKind, TextRange, TextSize}; 27use ra_syntax::{AstNode, SmolStr, SyntaxKind, TextRange, TextSize};
27use rustc_hash::FxHashMap; 28use rustc_hash::FxHashMap;
28use serde::{Deserialize, Serialize}; 29use serde::{Deserialize, Serialize};
29use serde_json::to_value; 30use serde_json::to_value;
@@ -978,7 +979,12 @@ fn to_lsp_runnable(
978) -> Result<lsp_ext::Runnable> { 979) -> Result<lsp_ext::Runnable> {
979 let spec = CargoTargetSpec::for_file(world, file_id)?; 980 let spec = CargoTargetSpec::for_file(world, file_id)?;
980 let target = spec.as_ref().map(|s| s.target.clone()); 981 let target = spec.as_ref().map(|s| s.target.clone());
981 let (args, extra_args) = CargoTargetSpec::runnable_args(spec, &runnable.kind)?; 982 let mut features_needed = vec![];
983 for cfg_expr in &runnable.cfg_exprs {
984 collect_minimal_features_needed(cfg_expr, &mut features_needed);
985 }
986 let (args, extra_args) =
987 CargoTargetSpec::runnable_args(spec, &runnable.kind, &features_needed)?;
982 let line_index = world.analysis().file_line_index(file_id)?; 988 let line_index = world.analysis().file_line_index(file_id)?;
983 let label = match &runnable.kind { 989 let label = match &runnable.kind {
984 RunnableKind::Test { test_id, .. } => format!("test {}", test_id), 990 RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
@@ -1004,6 +1010,26 @@ fn to_lsp_runnable(
1004 }) 1010 })
1005} 1011}
1006 1012
1013/// Fill minimal features needed
1014fn collect_minimal_features_needed(cfg_expr: &CfgExpr, features: &mut Vec<SmolStr>) {
1015 match cfg_expr {
1016 CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.clone()),
1017 CfgExpr::All(preds) => {
1018 preds.iter().for_each(|cfg| collect_minimal_features_needed(cfg, features));
1019 }
1020 CfgExpr::Any(preds) => {
1021 for cfg in preds {
1022 let len_features = features.len();
1023 collect_minimal_features_needed(cfg, features);
1024 if len_features != features.len() {
1025 break;
1026 }
1027 }
1028 }
1029 _ => {}
1030 }
1031}
1032
1007pub fn handle_inlay_hints( 1033pub fn handle_inlay_hints(
1008 world: WorldSnapshot, 1034 world: WorldSnapshot,
1009 params: InlayHintsParams, 1035 params: InlayHintsParams,
@@ -1140,3 +1166,54 @@ pub fn handle_semantic_tokens_range(
1140 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); 1166 let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights);
1141 Ok(Some(semantic_tokens.into())) 1167 Ok(Some(semantic_tokens.into()))
1142} 1168}
1169
1170#[cfg(test)]
1171mod tests {
1172 use super::*;
1173
1174 use mbe::{ast_to_token_tree, TokenMap};
1175 use ra_cfg::parse_cfg;
1176 use ra_syntax::{
1177 ast::{self, AstNode},
1178 SmolStr,
1179 };
1180
1181 fn get_token_tree_generated(input: &str) -> (tt::Subtree, TokenMap) {
1182 let source_file = ast::SourceFile::parse(input).ok().unwrap();
1183 let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap();
1184 ast_to_token_tree(&tt).unwrap()
1185 }
1186
1187 #[test]
1188 fn test_cfg_expr_minimal_features_needed() {
1189 let (subtree, _) = get_token_tree_generated(r#"#![cfg(feature = "baz")]"#);
1190 let cfg_expr = parse_cfg(&subtree);
1191 let mut min_features = vec![];
1192 collect_minimal_features_needed(&cfg_expr, &mut min_features);
1193
1194 assert_eq!(min_features, vec![SmolStr::new("baz")]);
1195
1196 let (subtree, _) =
1197 get_token_tree_generated(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#);
1198 let cfg_expr = parse_cfg(&subtree);
1199
1200 let mut min_features = vec![];
1201 collect_minimal_features_needed(&cfg_expr, &mut min_features);
1202 assert_eq!(min_features, vec![SmolStr::new("baz"), SmolStr::new("foo")]);
1203
1204 let (subtree, _) =
1205 get_token_tree_generated(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#);
1206 let cfg_expr = parse_cfg(&subtree);
1207
1208 let mut min_features = vec![];
1209 collect_minimal_features_needed(&cfg_expr, &mut min_features);
1210 assert_eq!(min_features, vec![SmolStr::new("baz")]);
1211
1212 let (subtree, _) = get_token_tree_generated(r#"#![cfg(foo)]"#);
1213 let cfg_expr = parse_cfg(&subtree);
1214
1215 let mut min_features = vec![];
1216 collect_minimal_features_needed(&cfg_expr, &mut min_features);
1217 assert!(min_features.is_empty());
1218 }
1219}
diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs
index 2dc5cb119..90a6257ee 100644
--- a/crates/rust-analyzer/src/semantic_tokens.rs
+++ b/crates/rust-analyzer/src/semantic_tokens.rs
@@ -67,6 +67,7 @@ define_semantic_token_modifiers![
67 (CONTROL_FLOW, "controlFlow"), 67 (CONTROL_FLOW, "controlFlow"),
68 (MUTABLE, "mutable"), 68 (MUTABLE, "mutable"),
69 (UNSAFE, "unsafe"), 69 (UNSAFE, "unsafe"),
70 (ATTRIBUTE_MODIFIER, "attribute"),
70]; 71];
71 72
72#[derive(Default)] 73#[derive(Default)]
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 7dd7d9416..672e47e41 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -307,6 +307,7 @@ fn semantic_token_type_and_modifiers(
307 307
308 for modifier in highlight.modifiers.iter() { 308 for modifier in highlight.modifiers.iter() {
309 let modifier = match modifier { 309 let modifier = match modifier {
310 HighlightModifier::Attribute => semantic_tokens::ATTRIBUTE_MODIFIER,
310 HighlightModifier::Definition => lsp_types::SemanticTokenModifier::DECLARATION, 311 HighlightModifier::Definition => lsp_types::SemanticTokenModifier::DECLARATION,
311 HighlightModifier::ControlFlow => semantic_tokens::CONTROL_FLOW, 312 HighlightModifier::ControlFlow => semantic_tokens::CONTROL_FLOW,
312 HighlightModifier::Mutable => semantic_tokens::MUTABLE, 313 HighlightModifier::Mutable => semantic_tokens::MUTABLE,
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs
index 9acbae066..66a6f4d54 100644
--- a/crates/rust-analyzer/tests/heavy_tests/support.rs
+++ b/crates/rust-analyzer/tests/heavy_tests/support.rs
@@ -68,7 +68,7 @@ impl<'a> Project<'a> {
68 let mut paths = vec![]; 68 let mut paths = vec![];
69 69
70 for entry in parse_fixture(self.fixture) { 70 for entry in parse_fixture(self.fixture) {
71 let path = tmp_dir.path().join(entry.meta); 71 let path = tmp_dir.path().join(entry.meta.path().as_str());
72 fs::create_dir_all(path.parent().unwrap()).unwrap(); 72 fs::create_dir_all(path.parent().unwrap()).unwrap();
73 fs::write(path.as_path(), entry.text.as_bytes()).unwrap(); 73 fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
74 paths.push((path, entry.text)); 74 paths.push((path, entry.text));
diff --git a/crates/test_utils/Cargo.toml b/crates/test_utils/Cargo.toml
index 8ec986bcb..4d185b01c 100644
--- a/crates/test_utils/Cargo.toml
+++ b/crates/test_utils/Cargo.toml
@@ -11,3 +11,7 @@ doctest = false
11difference = "2.0.0" 11difference = "2.0.0"
12text-size = "1.0.0" 12text-size = "1.0.0"
13serde_json = "1.0.48" 13serde_json = "1.0.48"
14relative-path = "1.0.0"
15rustc-hash = "1.1.0"
16
17ra_cfg = { path = "../ra_cfg" } \ No newline at end of file
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index be2cfbaa2..1bd97215c 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -14,6 +14,10 @@ use std::{
14 path::{Path, PathBuf}, 14 path::{Path, PathBuf},
15}; 15};
16 16
17pub use ra_cfg::CfgOptions;
18
19pub use relative_path::{RelativePath, RelativePathBuf};
20pub use rustc_hash::FxHashMap;
17use serde_json::Value; 21use serde_json::Value;
18use text_size::{TextRange, TextSize}; 22use text_size::{TextRange, TextSize};
19 23
@@ -157,10 +161,82 @@ pub fn add_cursor(text: &str, offset: TextSize) -> String {
157 161
158#[derive(Debug, Eq, PartialEq)] 162#[derive(Debug, Eq, PartialEq)]
159pub struct FixtureEntry { 163pub struct FixtureEntry {
160 pub meta: String, 164 pub meta: FixtureMeta,
161 pub text: String, 165 pub text: String,
162} 166}
163 167
168#[derive(Debug, Eq, PartialEq)]
169pub enum FixtureMeta {
170 Root { path: RelativePathBuf },
171 File(FileMeta),
172}
173
174#[derive(Debug, Eq, PartialEq)]
175pub struct FileMeta {
176 pub path: RelativePathBuf,
177 pub crate_name: Option<String>,
178 pub deps: Vec<String>,
179 pub cfg: CfgOptions,
180 pub edition: Option<String>,
181 pub env: FxHashMap<String, String>,
182}
183
184impl FixtureMeta {
185 pub fn path(&self) -> &RelativePath {
186 match self {
187 FixtureMeta::Root { path } => &path,
188 FixtureMeta::File(f) => &f.path,
189 }
190 }
191
192 pub fn crate_name(&self) -> Option<&String> {
193 match self {
194 FixtureMeta::File(f) => f.crate_name.as_ref(),
195 _ => None,
196 }
197 }
198
199 pub fn cfg_options(&self) -> Option<&CfgOptions> {
200 match self {
201 FixtureMeta::File(f) => Some(&f.cfg),
202 _ => None,
203 }
204 }
205
206 pub fn edition(&self) -> Option<&String> {
207 match self {
208 FixtureMeta::File(f) => f.edition.as_ref(),
209 _ => None,
210 }
211 }
212
213 pub fn env(&self) -> impl Iterator<Item = (&String, &String)> {
214 struct EnvIter<'a> {
215 iter: Option<std::collections::hash_map::Iter<'a, String, String>>,
216 }
217
218 impl<'a> EnvIter<'a> {
219 fn new(meta: &'a FixtureMeta) -> Self {
220 Self {
221 iter: match meta {
222 FixtureMeta::File(f) => Some(f.env.iter()),
223 _ => None,
224 },
225 }
226 }
227 }
228
229 impl<'a> Iterator for EnvIter<'a> {
230 type Item = (&'a String, &'a String);
231 fn next(&mut self) -> Option<Self::Item> {
232 self.iter.as_mut().and_then(|i| i.next())
233 }
234 }
235
236 EnvIter::new(self)
237 }
238}
239
164/// Parses text which looks like this: 240/// Parses text which looks like this:
165/// 241///
166/// ```not_rust 242/// ```not_rust
@@ -169,8 +245,8 @@ pub struct FixtureEntry {
169/// line 2 245/// line 2
170/// // - other meta 246/// // - other meta
171/// ``` 247/// ```
172pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> { 248pub fn parse_fixture(ra_fixture: &str) -> Vec<FixtureEntry> {
173 let fixture = indent_first_line(fixture); 249 let fixture = indent_first_line(ra_fixture);
174 let margin = fixture_margin(&fixture); 250 let margin = fixture_margin(&fixture);
175 251
176 let mut lines = fixture 252 let mut lines = fixture
@@ -200,6 +276,7 @@ The offending line: {:?}"#,
200 for line in lines.by_ref() { 276 for line in lines.by_ref() {
201 if line.starts_with("//-") { 277 if line.starts_with("//-") {
202 let meta = line["//-".len()..].trim().to_string(); 278 let meta = line["//-".len()..].trim().to_string();
279 let meta = parse_meta(&meta);
203 res.push(FixtureEntry { meta, text: String::new() }) 280 res.push(FixtureEntry { meta, text: String::new() })
204 } else if let Some(entry) = res.last_mut() { 281 } else if let Some(entry) = res.last_mut() {
205 entry.text.push_str(line); 282 entry.text.push_str(line);
@@ -209,6 +286,57 @@ The offending line: {:?}"#,
209 res 286 res
210} 287}
211 288
289//- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo
290fn parse_meta(meta: &str) -> FixtureMeta {
291 let components = meta.split_ascii_whitespace().collect::<Vec<_>>();
292
293 if components[0] == "root" {
294 let path: RelativePathBuf = components[1].into();
295 assert!(path.starts_with("/") && path.ends_with("/"));
296 return FixtureMeta::Root { path };
297 }
298
299 let path: RelativePathBuf = components[0].into();
300 assert!(path.starts_with("/"));
301
302 let mut krate = None;
303 let mut deps = Vec::new();
304 let mut edition = None;
305 let mut cfg = CfgOptions::default();
306 let mut env = FxHashMap::default();
307 for component in components[1..].iter() {
308 let (key, value) = split1(component, ':').unwrap();
309 match key {
310 "crate" => krate = Some(value.to_string()),
311 "deps" => deps = value.split(',').map(|it| it.to_string()).collect(),
312 "edition" => edition = Some(value.to_string()),
313 "cfg" => {
314 for key in value.split(',') {
315 match split1(key, '=') {
316 None => cfg.insert_atom(key.into()),
317 Some((k, v)) => cfg.insert_key_value(k.into(), v.into()),
318 }
319 }
320 }
321 "env" => {
322 for key in value.split(',') {
323 if let Some((k, v)) = split1(key, '=') {
324 env.insert(k.into(), v.into());
325 }
326 }
327 }
328 _ => panic!("bad component: {:?}", component),
329 }
330 }
331
332 FixtureMeta::File(FileMeta { path, crate_name: krate, deps, edition, cfg, env })
333}
334
335fn split1(haystack: &str, delim: char) -> Option<(&str, &str)> {
336 let idx = haystack.find(delim)?;
337 Some((&haystack[..idx], &haystack[idx + delim.len_utf8()..]))
338}
339
212/// Adjusts the indentation of the first line to the minimum indentation of the rest of the lines. 340/// Adjusts the indentation of the first line to the minimum indentation of the rest of the lines.
213/// This allows fixtures to start off in a different indentation, e.g. to align the first line with 341/// This allows fixtures to start off in a different indentation, e.g. to align the first line with
214/// the other lines visually: 342/// the other lines visually:
@@ -288,13 +416,33 @@ struct Bar;
288 ) 416 )
289} 417}
290 418
419#[test]
420fn parse_fixture_gets_full_meta() {
421 let parsed = parse_fixture(
422 r"
423 //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo
424 mod m;
425 ",
426 );
427 assert_eq!(1, parsed.len());
428
429 let parsed = &parsed[0];
430 assert_eq!("mod m;\n\n", parsed.text);
431
432 let meta = &parsed.meta;
433 assert_eq!("foo", meta.crate_name().unwrap());
434 assert_eq!("/lib.rs", meta.path());
435 assert!(meta.cfg_options().is_some());
436 assert_eq!(2, meta.env().count());
437}
438
291/// Same as `parse_fixture`, except it allow empty fixture 439/// Same as `parse_fixture`, except it allow empty fixture
292pub fn parse_single_fixture(fixture: &str) -> Option<FixtureEntry> { 440pub fn parse_single_fixture(ra_fixture: &str) -> Option<FixtureEntry> {
293 if !fixture.lines().any(|it| it.trim_start().starts_with("//-")) { 441 if !ra_fixture.lines().any(|it| it.trim_start().starts_with("//-")) {
294 return None; 442 return None;
295 } 443 }
296 444
297 let fixtures = parse_fixture(fixture); 445 let fixtures = parse_fixture(ra_fixture);
298 if fixtures.len() > 1 { 446 if fixtures.len() > 1 {
299 panic!("too many fixtures"); 447 panic!("too many fixtures");
300 } 448 }