diff options
Diffstat (limited to 'crates/ra_analysis')
| -rw-r--r-- | crates/ra_analysis/src/completion.rs | 622 | ||||
| -rw-r--r-- | crates/ra_analysis/src/descriptors/module/scope.rs | 6 | ||||
| -rw-r--r-- | crates/ra_analysis/src/imp.rs | 10 | ||||
| -rw-r--r-- | crates/ra_analysis/src/lib.rs | 9 | ||||
| -rw-r--r-- | crates/ra_analysis/src/mock_analysis.rs | 71 | ||||
| -rw-r--r-- | crates/ra_analysis/tests/tests.rs | 56 |
6 files changed, 711 insertions, 63 deletions
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index 869ab5afb..340ae3f66 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs | |||
| @@ -1,7 +1,10 @@ | |||
| 1 | use ra_editor::{CompletionItem, find_node_at_offset}; | 1 | use rustc_hash::{FxHashMap, FxHashSet}; |
| 2 | use ra_editor::{find_node_at_offset}; | ||
| 2 | use ra_syntax::{ | 3 | use ra_syntax::{ |
| 3 | AtomEdit, File, TextUnit, AstNode, | 4 | AtomEdit, File, TextUnit, AstNode, SyntaxNodeRef, |
| 4 | ast, | 5 | algo::visit::{visitor, visitor_ctx, Visitor, VisitorCtx}, |
| 6 | ast::{self, AstChildren, LoopBodyOwner, ModuleItemOwner}, | ||
| 7 | SyntaxKind::*, | ||
| 5 | }; | 8 | }; |
| 6 | 9 | ||
| 7 | use crate::{ | 10 | use crate::{ |
| @@ -9,9 +12,21 @@ use crate::{ | |||
| 9 | input::FilesDatabase, | 12 | input::FilesDatabase, |
| 10 | db::{self, SyntaxDatabase}, | 13 | db::{self, SyntaxDatabase}, |
| 11 | descriptors::DescriptorDatabase, | 14 | descriptors::DescriptorDatabase, |
| 12 | descriptors::module::{ModuleTree, ModuleId}, | 15 | descriptors::function::FnScopes, |
| 16 | descriptors::module::{ModuleTree, ModuleId, ModuleScope}, | ||
| 13 | }; | 17 | }; |
| 14 | 18 | ||
| 19 | |||
| 20 | #[derive(Debug)] | ||
| 21 | pub struct CompletionItem { | ||
| 22 | /// What user sees in pop-up | ||
| 23 | pub label: String, | ||
| 24 | /// What string is used for filtering, defaults to label | ||
| 25 | pub lookup: Option<String>, | ||
| 26 | /// What is inserted, defaults to label | ||
| 27 | pub snippet: Option<String>, | ||
| 28 | } | ||
| 29 | |||
| 15 | pub(crate) fn resolve_based_completion(db: &db::RootDatabase, file_id: FileId, offset: TextUnit) -> Cancelable<Option<Vec<CompletionItem>>> { | 30 | pub(crate) fn resolve_based_completion(db: &db::RootDatabase, file_id: FileId, offset: TextUnit) -> Cancelable<Option<Vec<CompletionItem>>> { |
| 16 | let source_root_id = db.file_source_root(file_id); | 31 | let source_root_id = db.file_source_root(file_id); |
| 17 | let file = db.file_syntax(file_id); | 32 | let file = db.file_syntax(file_id); |
| @@ -73,3 +88,602 @@ fn crate_path(name_ref: ast::NameRef) -> Option<Vec<ast::NameRef>> { | |||
| 73 | res.reverse(); | 88 | res.reverse(); |
| 74 | Some(res) | 89 | Some(res) |
| 75 | } | 90 | } |
| 91 | |||
| 92 | |||
| 93 | pub(crate) fn scope_completion( | ||
| 94 | db: &db::RootDatabase, | ||
| 95 | file_id: FileId, | ||
| 96 | offset: TextUnit, | ||
| 97 | ) -> Option<Vec<CompletionItem>> { | ||
| 98 | let original_file = db.file_syntax(file_id); | ||
| 99 | // Insert a fake ident to get a valid parse tree | ||
| 100 | let file = { | ||
| 101 | let edit = AtomEdit::insert(offset, "intellijRulezz".to_string()); | ||
| 102 | original_file.reparse(&edit) | ||
| 103 | }; | ||
| 104 | let mut has_completions = false; | ||
| 105 | let mut res = Vec::new(); | ||
| 106 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(file.syntax(), offset) { | ||
| 107 | has_completions = true; | ||
| 108 | complete_name_ref(&file, name_ref, &mut res); | ||
| 109 | // special case, `trait T { fn foo(i_am_a_name_ref) {} }` | ||
| 110 | if is_node::<ast::Param>(name_ref.syntax()) { | ||
| 111 | param_completions(name_ref.syntax(), &mut res); | ||
| 112 | } | ||
| 113 | let name_range = name_ref.syntax().range(); | ||
| 114 | let top_node = name_ref | ||
| 115 | .syntax() | ||
| 116 | .ancestors() | ||
| 117 | .take_while(|it| it.range() == name_range) | ||
| 118 | .last() | ||
| 119 | .unwrap(); | ||
| 120 | match top_node.parent().map(|it| it.kind()) { | ||
| 121 | Some(ROOT) | Some(ITEM_LIST) => complete_mod_item_snippets(&mut res), | ||
| 122 | _ => (), | ||
| 123 | } | ||
| 124 | } | ||
| 125 | if let Some(name) = find_node_at_offset::<ast::Name>(file.syntax(), offset) { | ||
| 126 | if is_node::<ast::Param>(name.syntax()) { | ||
| 127 | has_completions = true; | ||
| 128 | param_completions(name.syntax(), &mut res); | ||
| 129 | } | ||
| 130 | } | ||
| 131 | if has_completions { | ||
| 132 | Some(res) | ||
| 133 | } else { | ||
| 134 | None | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | fn complete_module_items( | ||
| 139 | file: &File, | ||
| 140 | items: AstChildren<ast::ModuleItem>, | ||
| 141 | this_item: Option<ast::NameRef>, | ||
| 142 | acc: &mut Vec<CompletionItem>, | ||
| 143 | ) { | ||
| 144 | let scope = ModuleScope::from_items(items); | ||
| 145 | acc.extend( | ||
| 146 | scope | ||
| 147 | .entries() | ||
| 148 | .iter() | ||
| 149 | .filter(|entry| { | ||
| 150 | let syntax = entry.ptr().resolve(file); | ||
| 151 | Some(syntax.borrowed()) != this_item.map(|it| it.syntax()) | ||
| 152 | }) | ||
| 153 | .map(|entry| CompletionItem { | ||
| 154 | label: entry.name().to_string(), | ||
| 155 | lookup: None, | ||
| 156 | snippet: None, | ||
| 157 | }), | ||
| 158 | ); | ||
| 159 | } | ||
| 160 | |||
| 161 | fn complete_name_ref( | ||
| 162 | file: &File, | ||
| 163 | name_ref: ast::NameRef, | ||
| 164 | acc: &mut Vec<CompletionItem>, | ||
| 165 | ) { | ||
| 166 | if !is_node::<ast::Path>(name_ref.syntax()) { | ||
| 167 | return; | ||
| 168 | } | ||
| 169 | let mut visited_fn = false; | ||
| 170 | for node in name_ref.syntax().ancestors() { | ||
| 171 | if let Some(items) = visitor() | ||
| 172 | .visit::<ast::Root, _>(|it| Some(it.items())) | ||
| 173 | .visit::<ast::Module, _>(|it| Some(it.item_list()?.items())) | ||
| 174 | .accept(node) | ||
| 175 | { | ||
| 176 | if let Some(items) = items { | ||
| 177 | complete_module_items(file, items, Some(name_ref), acc); | ||
| 178 | } | ||
| 179 | break; | ||
| 180 | } else if !visited_fn { | ||
| 181 | if let Some(fn_def) = ast::FnDef::cast(node) { | ||
| 182 | visited_fn = true; | ||
| 183 | complete_expr_keywords(&file, fn_def, name_ref, acc); | ||
| 184 | complete_expr_snippets(acc); | ||
| 185 | let scopes = FnScopes::new(fn_def); | ||
| 186 | complete_fn(name_ref, &scopes, acc); | ||
| 187 | } | ||
| 188 | } | ||
| 189 | } | ||
| 190 | } | ||
| 191 | |||
| 192 | fn param_completions(ctx: SyntaxNodeRef, acc: &mut Vec<CompletionItem>) { | ||
| 193 | let mut params = FxHashMap::default(); | ||
| 194 | for node in ctx.ancestors() { | ||
| 195 | let _ = visitor_ctx(&mut params) | ||
| 196 | .visit::<ast::Root, _>(process) | ||
| 197 | .visit::<ast::ItemList, _>(process) | ||
| 198 | .accept(node); | ||
| 199 | } | ||
| 200 | params | ||
| 201 | .into_iter() | ||
| 202 | .filter_map(|(label, (count, param))| { | ||
| 203 | let lookup = param.pat()?.syntax().text().to_string(); | ||
| 204 | if count < 2 { | ||
| 205 | None | ||
| 206 | } else { | ||
| 207 | Some((label, lookup)) | ||
| 208 | } | ||
| 209 | }) | ||
| 210 | .for_each(|(label, lookup)| { | ||
| 211 | acc.push(CompletionItem { | ||
| 212 | label, | ||
| 213 | lookup: Some(lookup), | ||
| 214 | snippet: None, | ||
| 215 | }) | ||
| 216 | }); | ||
| 217 | |||
| 218 | fn process<'a, N: ast::FnDefOwner<'a>>( | ||
| 219 | node: N, | ||
| 220 | params: &mut FxHashMap<String, (u32, ast::Param<'a>)>, | ||
| 221 | ) { | ||
| 222 | node.functions() | ||
| 223 | .filter_map(|it| it.param_list()) | ||
| 224 | .flat_map(|it| it.params()) | ||
| 225 | .for_each(|param| { | ||
| 226 | let text = param.syntax().text().to_string(); | ||
| 227 | params.entry(text).or_insert((0, param)).0 += 1; | ||
| 228 | }) | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | fn is_node<'a, N: AstNode<'a>>(node: SyntaxNodeRef<'a>) -> bool { | ||
| 233 | match node.ancestors().filter_map(N::cast).next() { | ||
| 234 | None => false, | ||
| 235 | Some(n) => n.syntax().range() == node.range(), | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | fn complete_expr_keywords( | ||
| 240 | file: &File, | ||
| 241 | fn_def: ast::FnDef, | ||
| 242 | name_ref: ast::NameRef, | ||
| 243 | acc: &mut Vec<CompletionItem>, | ||
| 244 | ) { | ||
| 245 | acc.push(keyword("if", "if $0 {}")); | ||
| 246 | acc.push(keyword("match", "match $0 {}")); | ||
| 247 | acc.push(keyword("while", "while $0 {}")); | ||
| 248 | acc.push(keyword("loop", "loop {$0}")); | ||
| 249 | |||
| 250 | if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) { | ||
| 251 | if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) { | ||
| 252 | if if_expr.syntax().range().end() < name_ref.syntax().range().start() { | ||
| 253 | acc.push(keyword("else", "else {$0}")); | ||
| 254 | acc.push(keyword("else if", "else if $0 {}")); | ||
| 255 | } | ||
| 256 | } | ||
| 257 | } | ||
| 258 | if is_in_loop_body(name_ref) { | ||
| 259 | acc.push(keyword("continue", "continue")); | ||
| 260 | acc.push(keyword("break", "break")); | ||
| 261 | } | ||
| 262 | acc.extend(complete_return(fn_def, name_ref)); | ||
| 263 | } | ||
| 264 | |||
| 265 | fn is_in_loop_body(name_ref: ast::NameRef) -> bool { | ||
| 266 | for node in name_ref.syntax().ancestors() { | ||
| 267 | if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { | ||
| 268 | break; | ||
| 269 | } | ||
| 270 | let loop_body = visitor() | ||
| 271 | .visit::<ast::ForExpr, _>(LoopBodyOwner::loop_body) | ||
| 272 | .visit::<ast::WhileExpr, _>(LoopBodyOwner::loop_body) | ||
| 273 | .visit::<ast::LoopExpr, _>(LoopBodyOwner::loop_body) | ||
| 274 | .accept(node); | ||
| 275 | if let Some(Some(body)) = loop_body { | ||
| 276 | if name_ref.syntax().range().is_subrange(&body.syntax().range()) { | ||
| 277 | return true; | ||
| 278 | } | ||
| 279 | } | ||
| 280 | } | ||
| 281 | false | ||
| 282 | } | ||
| 283 | |||
| 284 | fn complete_return(fn_def: ast::FnDef, name_ref: ast::NameRef) -> Option<CompletionItem> { | ||
| 285 | // let is_last_in_block = name_ref.syntax().ancestors().filter_map(ast::Expr::cast) | ||
| 286 | // .next() | ||
| 287 | // .and_then(|it| it.syntax().parent()) | ||
| 288 | // .and_then(ast::Block::cast) | ||
| 289 | // .is_some(); | ||
| 290 | |||
| 291 | // if is_last_in_block { | ||
| 292 | // return None; | ||
| 293 | // } | ||
| 294 | |||
| 295 | let is_stmt = match name_ref | ||
| 296 | .syntax() | ||
| 297 | .ancestors() | ||
| 298 | .filter_map(ast::ExprStmt::cast) | ||
| 299 | .next() | ||
| 300 | { | ||
| 301 | None => false, | ||
| 302 | Some(expr_stmt) => expr_stmt.syntax().range() == name_ref.syntax().range(), | ||
| 303 | }; | ||
| 304 | let snip = match (is_stmt, fn_def.ret_type().is_some()) { | ||
| 305 | (true, true) => "return $0;", | ||
| 306 | (true, false) => "return;", | ||
| 307 | (false, true) => "return $0", | ||
| 308 | (false, false) => "return", | ||
| 309 | }; | ||
| 310 | Some(keyword("return", snip)) | ||
| 311 | } | ||
| 312 | |||
| 313 | fn keyword(kw: &str, snip: &str) -> CompletionItem { | ||
| 314 | CompletionItem { | ||
| 315 | label: kw.to_string(), | ||
| 316 | lookup: None, | ||
| 317 | snippet: Some(snip.to_string()), | ||
| 318 | } | ||
| 319 | } | ||
| 320 | |||
| 321 | fn complete_expr_snippets(acc: &mut Vec<CompletionItem>) { | ||
| 322 | acc.push(CompletionItem { | ||
| 323 | label: "pd".to_string(), | ||
| 324 | lookup: None, | ||
| 325 | snippet: Some("eprintln!(\"$0 = {:?}\", $0);".to_string()), | ||
| 326 | }); | ||
| 327 | acc.push(CompletionItem { | ||
| 328 | label: "ppd".to_string(), | ||
| 329 | lookup: None, | ||
| 330 | snippet: Some("eprintln!(\"$0 = {:#?}\", $0);".to_string()), | ||
| 331 | }); | ||
| 332 | } | ||
| 333 | |||
| 334 | fn complete_mod_item_snippets(acc: &mut Vec<CompletionItem>) { | ||
| 335 | acc.push(CompletionItem { | ||
| 336 | label: "tfn".to_string(), | ||
| 337 | lookup: None, | ||
| 338 | snippet: Some("#[test]\nfn $1() {\n $0\n}".to_string()), | ||
| 339 | }); | ||
| 340 | acc.push(CompletionItem { | ||
| 341 | label: "pub(crate)".to_string(), | ||
| 342 | lookup: None, | ||
| 343 | snippet: Some("pub(crate) $0".to_string()), | ||
| 344 | }) | ||
| 345 | } | ||
| 346 | |||
| 347 | fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<CompletionItem>) { | ||
| 348 | let mut shadowed = FxHashSet::default(); | ||
| 349 | acc.extend( | ||
| 350 | scopes | ||
| 351 | .scope_chain(name_ref.syntax()) | ||
| 352 | .flat_map(|scope| scopes.entries(scope).iter()) | ||
| 353 | .filter(|entry| shadowed.insert(entry.name())) | ||
| 354 | .map(|entry| CompletionItem { | ||
| 355 | label: entry.name().to_string(), | ||
| 356 | lookup: None, | ||
| 357 | snippet: None, | ||
| 358 | }), | ||
| 359 | ); | ||
| 360 | if scopes.self_param.is_some() { | ||
| 361 | acc.push(CompletionItem { | ||
| 362 | label: "self".to_string(), | ||
| 363 | lookup: None, | ||
| 364 | snippet: None, | ||
| 365 | }) | ||
| 366 | } | ||
| 367 | } | ||
| 368 | |||
| 369 | #[cfg(test)] | ||
| 370 | mod tests { | ||
| 371 | use test_utils::{assert_eq_dbg, extract_offset}; | ||
| 372 | |||
| 373 | use crate::FileId; | ||
| 374 | use crate::mock_analysis::MockAnalysis; | ||
| 375 | |||
| 376 | use super::*; | ||
| 377 | |||
| 378 | fn check_scope_completion(code: &str, expected_completions: &str) { | ||
| 379 | let (off, code) = extract_offset(&code); | ||
| 380 | let analysis = MockAnalysis::with_files(&[("/main.rs", &code)]).analysis(); | ||
| 381 | let file_id = FileId(1); | ||
| 382 | let completions = scope_completion(&analysis.imp.db, file_id, off) | ||
| 383 | .unwrap() | ||
| 384 | .into_iter() | ||
| 385 | .filter(|c| c.snippet.is_none()) | ||
| 386 | .collect::<Vec<_>>(); | ||
| 387 | assert_eq_dbg(expected_completions, &completions); | ||
| 388 | } | ||
| 389 | |||
| 390 | fn check_snippet_completion(code: &str, expected_completions: &str) { | ||
| 391 | let (off, code) = extract_offset(&code); | ||
| 392 | let analysis = MockAnalysis::with_files(&[("/main.rs", &code)]).analysis(); | ||
| 393 | let file_id = FileId(1); | ||
| 394 | let completions = scope_completion(&analysis.imp.db, file_id, off) | ||
| 395 | .unwrap() | ||
| 396 | .into_iter() | ||
| 397 | .filter(|c| c.snippet.is_some()) | ||
| 398 | .collect::<Vec<_>>(); | ||
| 399 | assert_eq_dbg(expected_completions, &completions); | ||
| 400 | } | ||
| 401 | |||
| 402 | #[test] | ||
| 403 | fn test_completion_let_scope() { | ||
| 404 | check_scope_completion( | ||
| 405 | r" | ||
| 406 | fn quux(x: i32) { | ||
| 407 | let y = 92; | ||
| 408 | 1 + <|>; | ||
| 409 | let z = (); | ||
| 410 | } | ||
| 411 | ", | ||
| 412 | r#"[CompletionItem { label: "y", lookup: None, snippet: None }, | ||
| 413 | CompletionItem { label: "x", lookup: None, snippet: None }, | ||
| 414 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
| 415 | ); | ||
| 416 | } | ||
| 417 | |||
| 418 | #[test] | ||
| 419 | fn test_completion_if_let_scope() { | ||
| 420 | check_scope_completion( | ||
| 421 | r" | ||
| 422 | fn quux() { | ||
| 423 | if let Some(x) = foo() { | ||
| 424 | let y = 92; | ||
| 425 | }; | ||
| 426 | if let Some(a) = bar() { | ||
| 427 | let b = 62; | ||
| 428 | 1 + <|> | ||
| 429 | } | ||
| 430 | } | ||
| 431 | ", | ||
| 432 | r#"[CompletionItem { label: "b", lookup: None, snippet: None }, | ||
| 433 | CompletionItem { label: "a", lookup: None, snippet: None }, | ||
| 434 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
| 435 | ); | ||
| 436 | } | ||
| 437 | |||
| 438 | #[test] | ||
| 439 | fn test_completion_for_scope() { | ||
| 440 | check_scope_completion( | ||
| 441 | r" | ||
| 442 | fn quux() { | ||
| 443 | for x in &[1, 2, 3] { | ||
| 444 | <|> | ||
| 445 | } | ||
| 446 | } | ||
| 447 | ", | ||
| 448 | r#"[CompletionItem { label: "x", lookup: None, snippet: None }, | ||
| 449 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
| 450 | ); | ||
| 451 | } | ||
| 452 | |||
| 453 | #[test] | ||
| 454 | fn test_completion_mod_scope() { | ||
| 455 | check_scope_completion( | ||
| 456 | r" | ||
| 457 | struct Foo; | ||
| 458 | enum Baz {} | ||
| 459 | fn quux() { | ||
| 460 | <|> | ||
| 461 | } | ||
| 462 | ", | ||
| 463 | r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, | ||
| 464 | CompletionItem { label: "Baz", lookup: None, snippet: None }, | ||
| 465 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
| 466 | ); | ||
| 467 | } | ||
| 468 | |||
| 469 | #[test] | ||
| 470 | fn test_completion_mod_scope_no_self_use() { | ||
| 471 | check_scope_completion( | ||
| 472 | r" | ||
| 473 | use foo<|>; | ||
| 474 | ", | ||
| 475 | r#"[]"#, | ||
| 476 | ); | ||
| 477 | } | ||
| 478 | |||
| 479 | #[test] | ||
| 480 | fn test_completion_mod_scope_nested() { | ||
| 481 | check_scope_completion( | ||
| 482 | r" | ||
| 483 | struct Foo; | ||
| 484 | mod m { | ||
| 485 | struct Bar; | ||
| 486 | fn quux() { <|> } | ||
| 487 | } | ||
| 488 | ", | ||
| 489 | r#"[CompletionItem { label: "Bar", lookup: None, snippet: None }, | ||
| 490 | CompletionItem { label: "quux", lookup: None, snippet: None }]"#, | ||
| 491 | ); | ||
| 492 | } | ||
| 493 | |||
| 494 | #[test] | ||
| 495 | fn test_complete_type() { | ||
| 496 | check_scope_completion( | ||
| 497 | r" | ||
| 498 | struct Foo; | ||
| 499 | fn x() -> <|> | ||
| 500 | ", | ||
| 501 | r#"[CompletionItem { label: "Foo", lookup: None, snippet: None }, | ||
| 502 | CompletionItem { label: "x", lookup: None, snippet: None }]"#, | ||
| 503 | ) | ||
| 504 | } | ||
| 505 | |||
| 506 | #[test] | ||
| 507 | fn test_complete_shadowing() { | ||
| 508 | check_scope_completion( | ||
| 509 | r" | ||
| 510 | fn foo() -> { | ||
| 511 | let bar = 92; | ||
| 512 | { | ||
| 513 | let bar = 62; | ||
| 514 | <|> | ||
| 515 | } | ||
| 516 | } | ||
| 517 | ", | ||
| 518 | r#"[CompletionItem { label: "bar", lookup: None, snippet: None }, | ||
| 519 | CompletionItem { label: "foo", lookup: None, snippet: None }]"#, | ||
| 520 | ) | ||
| 521 | } | ||
| 522 | |||
| 523 | #[test] | ||
| 524 | fn test_complete_self() { | ||
| 525 | check_scope_completion( | ||
| 526 | r" | ||
| 527 | impl S { fn foo(&self) { <|> } } | ||
| 528 | ", | ||
| 529 | r#"[CompletionItem { label: "self", lookup: None, snippet: None }]"#, | ||
| 530 | ) | ||
| 531 | } | ||
| 532 | |||
| 533 | #[test] | ||
| 534 | fn test_completion_kewords() { | ||
| 535 | check_snippet_completion(r" | ||
| 536 | fn quux() { | ||
| 537 | <|> | ||
| 538 | } | ||
| 539 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
| 540 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
| 541 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
| 542 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
| 543 | CompletionItem { label: "return", lookup: None, snippet: Some("return") }, | ||
| 544 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
| 545 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
| 546 | } | ||
| 547 | |||
| 548 | #[test] | ||
| 549 | fn test_completion_else() { | ||
| 550 | check_snippet_completion(r" | ||
| 551 | fn quux() { | ||
| 552 | if true { | ||
| 553 | () | ||
| 554 | } <|> | ||
| 555 | } | ||
| 556 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
| 557 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
| 558 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
| 559 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
| 560 | CompletionItem { label: "else", lookup: None, snippet: Some("else {$0}") }, | ||
| 561 | CompletionItem { label: "else if", lookup: None, snippet: Some("else if $0 {}") }, | ||
| 562 | CompletionItem { label: "return", lookup: None, snippet: Some("return") }, | ||
| 563 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
| 564 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
| 565 | } | ||
| 566 | |||
| 567 | #[test] | ||
| 568 | fn test_completion_return_value() { | ||
| 569 | check_snippet_completion(r" | ||
| 570 | fn quux() -> i32 { | ||
| 571 | <|> | ||
| 572 | 92 | ||
| 573 | } | ||
| 574 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
| 575 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
| 576 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
| 577 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
| 578 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0;") }, | ||
| 579 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
| 580 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
| 581 | check_snippet_completion(r" | ||
| 582 | fn quux() { | ||
| 583 | <|> | ||
| 584 | 92 | ||
| 585 | } | ||
| 586 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
| 587 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
| 588 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
| 589 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
| 590 | CompletionItem { label: "return", lookup: None, snippet: Some("return;") }, | ||
| 591 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
| 592 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
| 593 | } | ||
| 594 | |||
| 595 | #[test] | ||
| 596 | fn test_completion_return_no_stmt() { | ||
| 597 | check_snippet_completion(r" | ||
| 598 | fn quux() -> i32 { | ||
| 599 | match () { | ||
| 600 | () => <|> | ||
| 601 | } | ||
| 602 | } | ||
| 603 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
| 604 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
| 605 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
| 606 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
| 607 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, | ||
| 608 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
| 609 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
| 610 | } | ||
| 611 | |||
| 612 | #[test] | ||
| 613 | fn test_continue_break_completion() { | ||
| 614 | check_snippet_completion(r" | ||
| 615 | fn quux() -> i32 { | ||
| 616 | loop { <|> } | ||
| 617 | } | ||
| 618 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
| 619 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
| 620 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
| 621 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
| 622 | CompletionItem { label: "continue", lookup: None, snippet: Some("continue") }, | ||
| 623 | CompletionItem { label: "break", lookup: None, snippet: Some("break") }, | ||
| 624 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, | ||
| 625 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
| 626 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
| 627 | check_snippet_completion(r" | ||
| 628 | fn quux() -> i32 { | ||
| 629 | loop { || { <|> } } | ||
| 630 | } | ||
| 631 | ", r#"[CompletionItem { label: "if", lookup: None, snippet: Some("if $0 {}") }, | ||
| 632 | CompletionItem { label: "match", lookup: None, snippet: Some("match $0 {}") }, | ||
| 633 | CompletionItem { label: "while", lookup: None, snippet: Some("while $0 {}") }, | ||
| 634 | CompletionItem { label: "loop", lookup: None, snippet: Some("loop {$0}") }, | ||
| 635 | CompletionItem { label: "return", lookup: None, snippet: Some("return $0") }, | ||
| 636 | CompletionItem { label: "pd", lookup: None, snippet: Some("eprintln!(\"$0 = {:?}\", $0);") }, | ||
| 637 | CompletionItem { label: "ppd", lookup: None, snippet: Some("eprintln!(\"$0 = {:#?}\", $0);") }]"#); | ||
| 638 | } | ||
| 639 | |||
| 640 | #[test] | ||
| 641 | fn test_param_completion_last_param() { | ||
| 642 | check_scope_completion(r" | ||
| 643 | fn foo(file_id: FileId) {} | ||
| 644 | fn bar(file_id: FileId) {} | ||
| 645 | fn baz(file<|>) {} | ||
| 646 | ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); | ||
| 647 | } | ||
| 648 | |||
| 649 | #[test] | ||
| 650 | fn test_param_completion_nth_param() { | ||
| 651 | check_scope_completion(r" | ||
| 652 | fn foo(file_id: FileId) {} | ||
| 653 | fn bar(file_id: FileId) {} | ||
| 654 | fn baz(file<|>, x: i32) {} | ||
| 655 | ", r#"[CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); | ||
| 656 | } | ||
| 657 | |||
| 658 | #[test] | ||
| 659 | fn test_param_completion_trait_param() { | ||
| 660 | check_scope_completion(r" | ||
| 661 | pub(crate) trait SourceRoot { | ||
| 662 | pub fn contains(&self, file_id: FileId) -> bool; | ||
| 663 | pub fn module_map(&self) -> &ModuleMap; | ||
| 664 | pub fn lines(&self, file_id: FileId) -> &LineIndex; | ||
| 665 | pub fn syntax(&self, file<|>) | ||
| 666 | } | ||
| 667 | ", r#"[CompletionItem { label: "self", lookup: None, snippet: None }, | ||
| 668 | CompletionItem { label: "SourceRoot", lookup: None, snippet: None }, | ||
| 669 | CompletionItem { label: "file_id: FileId", lookup: Some("file_id"), snippet: None }]"#); | ||
| 670 | } | ||
| 671 | |||
| 672 | #[test] | ||
| 673 | fn test_item_snippets() { | ||
| 674 | // check_snippet_completion(r" | ||
| 675 | // <|> | ||
| 676 | // ", | ||
| 677 | // r##"[CompletionItem { label: "tfn", lookup: None, snippet: Some("#[test]\nfn $1() {\n $0\n}") }]"##, | ||
| 678 | // ); | ||
| 679 | check_snippet_completion(r" | ||
| 680 | #[cfg(test)] | ||
| 681 | mod tests { | ||
| 682 | <|> | ||
| 683 | } | ||
| 684 | ", | ||
| 685 | r##"[CompletionItem { label: "tfn", lookup: None, snippet: Some("#[test]\nfn $1() {\n $0\n}") }, | ||
| 686 | CompletionItem { label: "pub(crate)", lookup: None, snippet: Some("pub(crate) $0") }]"##, | ||
| 687 | ); | ||
| 688 | } | ||
| 689 | } | ||
diff --git a/crates/ra_analysis/src/descriptors/module/scope.rs b/crates/ra_analysis/src/descriptors/module/scope.rs index 0f8f325ab..846b8b44f 100644 --- a/crates/ra_analysis/src/descriptors/module/scope.rs +++ b/crates/ra_analysis/src/descriptors/module/scope.rs | |||
| @@ -30,8 +30,12 @@ enum EntryKind { | |||
| 30 | 30 | ||
| 31 | impl ModuleScope { | 31 | impl ModuleScope { |
| 32 | pub fn new(file: &File) -> ModuleScope { | 32 | pub fn new(file: &File) -> ModuleScope { |
| 33 | ModuleScope::from_items(file.ast().items()) | ||
| 34 | } | ||
| 35 | |||
| 36 | pub fn from_items<'a>(items: impl Iterator<Item = ast::ModuleItem<'a>>) -> ModuleScope { | ||
| 33 | let mut entries = Vec::new(); | 37 | let mut entries = Vec::new(); |
| 34 | for item in file.ast().items() { | 38 | for item in items { |
| 35 | let entry = match item { | 39 | let entry = match item { |
| 36 | ast::ModuleItem::StructDef(item) => Entry::new(item), | 40 | ast::ModuleItem::StructDef(item) => Entry::new(item), |
| 37 | ast::ModuleItem::EnumDef(item) => Entry::new(item), | 41 | ast::ModuleItem::EnumDef(item) => Entry::new(item), |
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs index 6473a1dbc..38d4b6a23 100644 --- a/crates/ra_analysis/src/imp.rs +++ b/crates/ra_analysis/src/imp.rs | |||
| @@ -3,7 +3,7 @@ use std::{ | |||
| 3 | sync::Arc, | 3 | sync::Arc, |
| 4 | }; | 4 | }; |
| 5 | 5 | ||
| 6 | use ra_editor::{self, find_node_at_offset, FileSymbol, LineIndex, LocalEdit, CompletionItem}; | 6 | use ra_editor::{self, find_node_at_offset, FileSymbol, LineIndex, LocalEdit}; |
| 7 | use ra_syntax::{ | 7 | use ra_syntax::{ |
| 8 | ast::{self, ArgListOwner, Expr, NameOwner}, | 8 | ast::{self, ArgListOwner, Expr, NameOwner}, |
| 9 | AstNode, File, SmolStr, | 9 | AstNode, File, SmolStr, |
| @@ -26,6 +26,7 @@ use crate::{ | |||
| 26 | module::{ModuleTree, Problem}, | 26 | module::{ModuleTree, Problem}, |
| 27 | function::{FnDescriptor, FnId}, | 27 | function::{FnDescriptor, FnId}, |
| 28 | }, | 28 | }, |
| 29 | completion::{scope_completion, resolve_based_completion, CompletionItem}, | ||
| 29 | symbol_index::SymbolIndex, | 30 | symbol_index::SymbolIndex, |
| 30 | syntax_ptr::SyntaxPtrDatabase, | 31 | syntax_ptr::SyntaxPtrDatabase, |
| 31 | CrateGraph, CrateId, Diagnostic, FileId, FileResolver, FileSystemEdit, Position, | 32 | CrateGraph, CrateId, Diagnostic, FileId, FileResolver, FileSystemEdit, Position, |
| @@ -179,7 +180,7 @@ impl AnalysisHostImpl { | |||
| 179 | 180 | ||
| 180 | #[derive(Debug)] | 181 | #[derive(Debug)] |
| 181 | pub(crate) struct AnalysisImpl { | 182 | pub(crate) struct AnalysisImpl { |
| 182 | db: db::RootDatabase, | 183 | pub(crate) db: db::RootDatabase, |
| 183 | } | 184 | } |
| 184 | 185 | ||
| 185 | impl AnalysisImpl { | 186 | impl AnalysisImpl { |
| @@ -249,12 +250,11 @@ impl AnalysisImpl { | |||
| 249 | pub fn completions(&self, file_id: FileId, offset: TextUnit) -> Cancelable<Option<Vec<CompletionItem>>> { | 250 | pub fn completions(&self, file_id: FileId, offset: TextUnit) -> Cancelable<Option<Vec<CompletionItem>>> { |
| 250 | let mut res = Vec::new(); | 251 | let mut res = Vec::new(); |
| 251 | let mut has_completions = false; | 252 | let mut has_completions = false; |
| 252 | let file = self.file_syntax(file_id); | 253 | if let Some(scope_based) = scope_completion(&self.db, file_id, offset) { |
| 253 | if let Some(scope_based) = ra_editor::scope_completion(&file, offset) { | ||
| 254 | res.extend(scope_based); | 254 | res.extend(scope_based); |
| 255 | has_completions = true; | 255 | has_completions = true; |
| 256 | } | 256 | } |
| 257 | if let Some(scope_based) = crate::completion::resolve_based_completion(&self.db, file_id, offset)? { | 257 | if let Some(scope_based) = resolve_based_completion(&self.db, file_id, offset)? { |
| 258 | res.extend(scope_based); | 258 | res.extend(scope_based); |
| 259 | has_completions = true; | 259 | has_completions = true; |
| 260 | } | 260 | } |
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs index a77c9a5fa..776010281 100644 --- a/crates/ra_analysis/src/lib.rs +++ b/crates/ra_analysis/src/lib.rs | |||
| @@ -13,6 +13,7 @@ mod imp; | |||
| 13 | mod symbol_index; | 13 | mod symbol_index; |
| 14 | mod completion; | 14 | mod completion; |
| 15 | mod syntax_ptr; | 15 | mod syntax_ptr; |
| 16 | mod mock_analysis; | ||
| 16 | 17 | ||
| 17 | use std::{ | 18 | use std::{ |
| 18 | fmt, | 19 | fmt, |
| @@ -30,10 +31,12 @@ use crate::{ | |||
| 30 | 31 | ||
| 31 | pub use crate::{ | 32 | pub use crate::{ |
| 32 | descriptors::function::FnDescriptor, | 33 | descriptors::function::FnDescriptor, |
| 33 | input::{FileId, FileResolver, CrateGraph, CrateId} | 34 | completion::CompletionItem, |
| 35 | input::{FileId, FileResolver, CrateGraph, CrateId}, | ||
| 36 | mock_analysis::MockAnalysis, | ||
| 34 | }; | 37 | }; |
| 35 | pub use ra_editor::{ | 38 | pub use ra_editor::{ |
| 36 | CompletionItem, FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, | 39 | FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, |
| 37 | RunnableKind, StructureNode, | 40 | RunnableKind, StructureNode, |
| 38 | }; | 41 | }; |
| 39 | 42 | ||
| @@ -197,7 +200,7 @@ impl Query { | |||
| 197 | 200 | ||
| 198 | #[derive(Debug)] | 201 | #[derive(Debug)] |
| 199 | pub struct Analysis { | 202 | pub struct Analysis { |
| 200 | imp: AnalysisImpl, | 203 | pub(crate) imp: AnalysisImpl, |
| 201 | } | 204 | } |
| 202 | 205 | ||
| 203 | impl Analysis { | 206 | impl Analysis { |
diff --git a/crates/ra_analysis/src/mock_analysis.rs b/crates/ra_analysis/src/mock_analysis.rs new file mode 100644 index 000000000..1c1dbee7c --- /dev/null +++ b/crates/ra_analysis/src/mock_analysis.rs | |||
| @@ -0,0 +1,71 @@ | |||
| 1 | |||
| 2 | use std::sync::Arc; | ||
| 3 | |||
| 4 | use relative_path::{RelativePath, RelativePathBuf}; | ||
| 5 | |||
| 6 | use crate::{ | ||
| 7 | AnalysisChange, Analysis, AnalysisHost, FileId, FileResolver, | ||
| 8 | }; | ||
| 9 | |||
| 10 | /// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis | ||
| 11 | /// from a set of in-memory files. | ||
| 12 | #[derive(Debug, Default)] | ||
| 13 | pub struct MockAnalysis { | ||
| 14 | files: Vec<(String, String)>, | ||
| 15 | } | ||
| 16 | |||
| 17 | impl MockAnalysis { | ||
| 18 | pub fn new() -> MockAnalysis { | ||
| 19 | MockAnalysis::default() | ||
| 20 | } | ||
| 21 | pub fn with_files(files: &[(&str, &str)]) -> MockAnalysis { | ||
| 22 | let files = files.iter() | ||
| 23 | .map(|it| (it.0.to_string(), it.1.to_string())) | ||
| 24 | .collect(); | ||
| 25 | MockAnalysis { files } | ||
| 26 | } | ||
| 27 | pub fn analysis_host(self) -> AnalysisHost { | ||
| 28 | let mut host = AnalysisHost::new(); | ||
| 29 | let mut file_map = Vec::new(); | ||
| 30 | let mut change = AnalysisChange::new(); | ||
| 31 | for (id, (path, contents)) in self.files.into_iter().enumerate() { | ||
| 32 | let file_id = FileId((id + 1) as u32); | ||
| 33 | assert!(path.starts_with('/')); | ||
| 34 | let path = RelativePathBuf::from_path(&path[1..]).unwrap(); | ||
| 35 | change.add_file(file_id, contents); | ||
| 36 | file_map.push((file_id, path)); | ||
| 37 | } | ||
| 38 | change.set_file_resolver(Arc::new(FileMap(file_map))); | ||
| 39 | host.apply_change(change); | ||
| 40 | host | ||
| 41 | } | ||
| 42 | pub fn analysis(self) -> Analysis { | ||
| 43 | self.analysis_host().analysis() | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | #[derive(Debug)] | ||
| 48 | struct FileMap(Vec<(FileId, RelativePathBuf)>); | ||
| 49 | |||
| 50 | impl FileMap { | ||
| 51 | fn iter<'a>(&'a self) -> impl Iterator<Item = (FileId, &'a RelativePath)> + 'a { | ||
| 52 | self.0 | ||
| 53 | .iter() | ||
| 54 | .map(|(id, path)| (*id, path.as_relative_path())) | ||
| 55 | } | ||
| 56 | |||
| 57 | fn path(&self, id: FileId) -> &RelativePath { | ||
| 58 | self.iter().find(|&(it, _)| it == id).unwrap().1 | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | impl FileResolver for FileMap { | ||
| 63 | fn file_stem(&self, id: FileId) -> String { | ||
| 64 | self.path(id).file_stem().unwrap().to_string() | ||
| 65 | } | ||
| 66 | fn resolve(&self, id: FileId, rel: &RelativePath) -> Option<FileId> { | ||
| 67 | let path = self.path(id).join(rel).normalize(); | ||
| 68 | let id = self.iter().find(|&(_, p)| path == p)?.0; | ||
| 69 | Some(id) | ||
| 70 | } | ||
| 71 | } | ||
diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs index 806e1fb34..f5683aec5 100644 --- a/crates/ra_analysis/tests/tests.rs +++ b/crates/ra_analysis/tests/tests.rs | |||
| @@ -5,62 +5,16 @@ extern crate relative_path; | |||
| 5 | extern crate rustc_hash; | 5 | extern crate rustc_hash; |
| 6 | extern crate test_utils; | 6 | extern crate test_utils; |
| 7 | 7 | ||
| 8 | use std::{ | ||
| 9 | sync::Arc, | ||
| 10 | }; | ||
| 11 | |||
| 12 | use ra_syntax::TextRange; | 8 | use ra_syntax::TextRange; |
| 13 | use relative_path::{RelativePath, RelativePathBuf}; | ||
| 14 | use test_utils::{assert_eq_dbg, extract_offset}; | 9 | use test_utils::{assert_eq_dbg, extract_offset}; |
| 15 | 10 | ||
| 16 | use ra_analysis::{ | 11 | use ra_analysis::{ |
| 17 | AnalysisChange, Analysis, AnalysisHost, CrateGraph, CrateId, FileId, FileResolver, FnDescriptor, | 12 | MockAnalysis, |
| 13 | AnalysisChange, Analysis, CrateGraph, CrateId, FileId, FnDescriptor, | ||
| 18 | }; | 14 | }; |
| 19 | 15 | ||
| 20 | #[derive(Debug)] | ||
| 21 | struct FileMap(Vec<(FileId, RelativePathBuf)>); | ||
| 22 | |||
| 23 | impl FileMap { | ||
| 24 | fn iter<'a>(&'a self) -> impl Iterator<Item = (FileId, &'a RelativePath)> + 'a { | ||
| 25 | self.0 | ||
| 26 | .iter() | ||
| 27 | .map(|(id, path)| (*id, path.as_relative_path())) | ||
| 28 | } | ||
| 29 | |||
| 30 | fn path(&self, id: FileId) -> &RelativePath { | ||
| 31 | self.iter().find(|&(it, _)| it == id).unwrap().1 | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | impl FileResolver for FileMap { | ||
| 36 | fn file_stem(&self, id: FileId) -> String { | ||
| 37 | self.path(id).file_stem().unwrap().to_string() | ||
| 38 | } | ||
| 39 | fn resolve(&self, id: FileId, rel: &RelativePath) -> Option<FileId> { | ||
| 40 | let path = self.path(id).join(rel).normalize(); | ||
| 41 | let id = self.iter().find(|&(_, p)| path == p)?.0; | ||
| 42 | Some(id) | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | fn analysis_host(files: &[(&str, &str)]) -> AnalysisHost { | ||
| 47 | let mut host = AnalysisHost::new(); | ||
| 48 | let mut file_map = Vec::new(); | ||
| 49 | let mut change = AnalysisChange::new(); | ||
| 50 | for (id, &(path, contents)) in files.iter().enumerate() { | ||
| 51 | let file_id = FileId((id + 1) as u32); | ||
| 52 | assert!(path.starts_with('/')); | ||
| 53 | let path = RelativePathBuf::from_path(&path[1..]).unwrap(); | ||
| 54 | change.add_file(file_id, contents.to_string()); | ||
| 55 | file_map.push((file_id, path)); | ||
| 56 | } | ||
| 57 | change.set_file_resolver(Arc::new(FileMap(file_map))); | ||
| 58 | host.apply_change(change); | ||
| 59 | host | ||
| 60 | } | ||
| 61 | |||
| 62 | fn analysis(files: &[(&str, &str)]) -> Analysis { | 16 | fn analysis(files: &[(&str, &str)]) -> Analysis { |
| 63 | analysis_host(files).analysis() | 17 | MockAnalysis::with_files(files).analysis() |
| 64 | } | 18 | } |
| 65 | 19 | ||
| 66 | fn get_signature(text: &str) -> (FnDescriptor, Option<usize>) { | 20 | fn get_signature(text: &str) -> (FnDescriptor, Option<usize>) { |
| @@ -125,7 +79,9 @@ fn test_resolve_parent_module() { | |||
| 125 | 79 | ||
| 126 | #[test] | 80 | #[test] |
| 127 | fn test_resolve_crate_root() { | 81 | fn test_resolve_crate_root() { |
| 128 | let mut host = analysis_host(&[("/lib.rs", "mod foo;"), ("/foo.rs", "")]); | 82 | let mut host = MockAnalysis::with_files( |
| 83 | &[("/lib.rs", "mod foo;"), ("/foo.rs", "")] | ||
| 84 | ).analysis_host(); | ||
| 129 | let snap = host.analysis(); | 85 | let snap = host.analysis(); |
| 130 | assert!(snap.crate_for(FileId(2)).unwrap().is_empty()); | 86 | assert!(snap.crate_for(FileId(2)).unwrap().is_empty()); |
| 131 | 87 | ||
