1use std::path::{Path, PathBuf};
6
7use ra_ap_cfg::{self as cfg};
8use ra_ap_hir::{self as hir, AsAssocItem as _, HasAttrs as _};
9use ra_ap_ide::{self as ide};
10use ra_ap_ide_db::{self as ide_db};
11use ra_ap_load_cargo::{self as load_cargo};
12use ra_ap_paths::{self as paths};
13use ra_ap_project_model::{self as project_model};
14use ra_ap_syntax::{self as syntax, AstNode as _, ast};
15use ra_ap_vfs::{self as vfs};
16
17use crate::{
18 item::{ItemCfgAttr, ItemTestAttr},
19 options::{GeneralOptions, ProjectOptions},
20};
21
22pub struct LoadOptions {
23 pub cfg_test: bool,
25
26 pub sysroot: bool,
28}
29
30pub fn load_workspace(
31 general_options: &GeneralOptions,
32 project_options: &ProjectOptions,
33 load_options: &LoadOptions,
34) -> anyhow::Result<(hir::Crate, ide::AnalysisHost, vfs::Vfs, ide::Edition)> {
35 let project_path = project_options.manifest_path.as_path().canonicalize()?;
36
37 let project_path = dunce::simplified(&project_path).to_path_buf();
42
43 let cargo_config = cargo_config(project_options, load_options);
44 let load_config = load_config();
45
46 let progress = |string| {
47 tracing::info!("Cargo analysis progress: {}", string);
48 eprintln!("Cargo analysis progress: {string}");
49 };
50
51 let mut project_workspace = load_project_workspace(&project_path, &cargo_config, &progress)?;
52
53 let (package, target) = select_package_and_target(&project_workspace, project_options)?;
54
55 if general_options.verbose {
56 eprintln!();
57 eprintln!("crate");
58 eprintln!("└── package: {}", package.name);
59 eprintln!(" └── target: {}", target.name);
60 eprintln!();
61 }
62
63 let edition = package.edition;
64
65 if load_config.load_out_dirs_from_check {
66 let build_scripts = project_workspace.run_build_scripts(&cargo_config, &progress)?;
67 project_workspace.set_build_scripts(build_scripts)
68 }
69
70 let (db, vfs, _proc_macro_client) =
71 ra_ap_load_cargo::load_workspace(project_workspace, &cargo_config.extra_env, &load_config)?;
72
73 let host = ide::AnalysisHost::with_database(db);
74
75 let krate = find_crate(host.raw_database(), &vfs, &target)?;
76
77 Ok((krate, host, vfs, edition))
78}
79
80pub fn cargo_config(
81 project_options: &ProjectOptions,
82 load_options: &LoadOptions,
83) -> project_model::CargoConfig {
84 let all_targets = false;
85
86 let cfg_overrides = match load_options.cfg_test {
88 true => project_model::CfgOverrides {
89 global: cfg::CfgDiff::new(
90 vec![cfg::CfgAtom::Flag(hir::Symbol::intern("test"))],
91 Vec::new(),
92 ),
93 selective: Default::default(),
94 },
95 false => project_model::CfgOverrides {
96 global: cfg::CfgDiff::new(
97 Vec::new(),
98 vec![cfg::CfgAtom::Flag(hir::Symbol::intern("test"))],
99 ),
100 selective: Default::default(),
101 },
102 };
103
104 let extra_args = vec![];
105
106 let extra_env = ide_db::FxHashMap::default();
108
109 let extra_includes = vec![];
110
111 let features = if project_options.all_features {
113 project_model::CargoFeatures::All
114 } else {
115 project_model::CargoFeatures::Selected {
116 features: project_options.features.clone(),
117 no_default_features: project_options.no_default_features,
118 }
119 };
120
121 let invocation_strategy = project_model::InvocationStrategy::PerWorkspace;
122
123 let no_deps: bool = true; let run_build_script_command = None;
126
127 let rustc_source = None;
129
130 let set_test = load_options.cfg_test;
131
132 let sysroot = if load_options.sysroot {
134 Some(project_model::RustLibSource::Discover)
135 } else {
136 None
137 };
138
139 let sysroot_src = None;
140
141 let target = project_options.target.clone();
143
144 let target_dir = None;
145
146 let wrap_rustc_in_build_scripts = false;
149
150 project_model::CargoConfig {
151 all_targets,
152 cfg_overrides,
153 extra_args,
154 extra_env,
155 extra_includes,
156 features,
157 invocation_strategy,
158 no_deps,
159 run_build_script_command,
160 rustc_source,
161 set_test,
162 sysroot_src,
163 sysroot,
164 target_dir,
165 target,
166 wrap_rustc_in_build_scripts,
167 }
168}
169
170pub fn load_config() -> load_cargo::LoadCargoConfig {
171 let load_out_dirs_from_check = false;
174 let prefill_caches = false;
175 let with_proc_macro_server = load_cargo::ProcMacroServerChoice::Sysroot;
176
177 load_cargo::LoadCargoConfig {
178 load_out_dirs_from_check,
179 prefill_caches,
180 with_proc_macro_server,
181 }
182}
183
184pub fn load_project_workspace(
185 project_path: &Path,
186 cargo_config: &project_model::CargoConfig,
187 progress: &(dyn Fn(String) + Sync),
188) -> anyhow::Result<project_model::ProjectWorkspace> {
189 let path_buf = std::env::current_dir()?.join(project_path);
190 let utf8_path_buf = paths::Utf8PathBuf::from_path_buf(path_buf).unwrap();
191 let root = paths::AbsPathBuf::assert(utf8_path_buf);
192 let root = project_model::ProjectManifest::discover_single(root.as_path())?;
193
194 project_model::ProjectWorkspace::load(root, cargo_config, &progress)
195}
196
197pub fn select_package_and_target(
198 project_workspace: &project_model::ProjectWorkspace,
199 options: &ProjectOptions,
200) -> anyhow::Result<(project_model::PackageData, project_model::TargetData)> {
201 let cargo_workspace = match project_workspace.kind {
202 project_model::ProjectWorkspaceKind::Cargo { ref cargo, .. } => Ok(cargo),
203 project_model::ProjectWorkspaceKind::Json { .. } => {
204 Err(anyhow::anyhow!("Unexpected JSON workspace"))
205 }
206 project_model::ProjectWorkspaceKind::DetachedFile { .. } => {
207 Err(anyhow::anyhow!("Unexpected detached files"))
208 }
209 }?;
210
211 let package_idx = select_package(cargo_workspace, options)?;
212 let package = cargo_workspace[package_idx].clone();
213 tracing::debug!("Selected package: {:#?}", package.name);
214
215 let target_idx = select_target(cargo_workspace, package_idx, options)?;
216 let target = cargo_workspace[target_idx].clone();
217 tracing::debug!("Selected target: {:#?}", target.name);
218
219 Ok((package, target))
220}
221
222pub fn select_package(
223 workspace: &project_model::CargoWorkspace,
224 options: &ProjectOptions,
225) -> anyhow::Result<project_model::Package> {
226 let packages: Vec<_> = workspace
227 .packages()
228 .filter(|idx| workspace[*idx].is_member)
229 .collect();
230
231 let package_count = packages.len();
232
233 if package_count < 1 {
236 anyhow::bail!("no packages found");
237 }
238
239 let package_list_items: Vec<_> = packages
242 .iter()
243 .map(|package_idx| {
244 let package = &workspace[*package_idx];
245 format!("- {}", package.name)
246 })
247 .collect();
248
249 let package_list = package_list_items.join("\n");
250
251 if let Some(package_name) = &options.package {
254 let package_idx = packages.into_iter().find(|package_idx| {
255 let package = &workspace[*package_idx];
256 package.name == *package_name
257 });
258
259 return package_idx.ok_or_else(|| {
260 anyhow::anyhow!(
261 indoc::indoc! {
262 "No package found with name {:?}.
263
264 Packages present in workspace:
265 {}
266 "
267 },
268 package_name,
269 package_list,
270 )
271 });
272 }
273
274 if package_count == 1 {
277 return Ok(packages[0]);
278 }
279
280 Err(anyhow::anyhow!(
281 indoc::indoc! {
282 "Multiple packages present in workspace,
283 please explicitly select one via --package flag.
284
285 Packages present in workspace:
286 {}
287 "
288 },
289 package_list
290 ))
291}
292
293pub fn select_target(
294 workspace: &project_model::CargoWorkspace,
295 package_idx: project_model::Package,
296 options: &ProjectOptions,
297) -> anyhow::Result<project_model::Target> {
298 let package = &workspace[package_idx];
299
300 let targets: Vec<_> = package
303 .targets
304 .iter()
305 .cloned()
306 .filter(|target_idx| {
307 let target = &workspace[*target_idx];
308 match target.kind {
309 project_model::TargetKind::Bin => true,
310 project_model::TargetKind::Lib { .. } => true,
311 project_model::TargetKind::Example => false,
312 project_model::TargetKind::Test => false,
313 project_model::TargetKind::Bench => false,
314 project_model::TargetKind::Other => false,
315 project_model::TargetKind::BuildScript => false,
316 }
317 })
318 .collect();
319
320 let target_count = targets.len();
321
322 if target_count < 1 {
325 anyhow::bail!("no targets found");
326 }
327
328 let target_list_items: Vec<_> = targets
331 .iter()
332 .map(|target_idx| {
333 let target = &workspace[*target_idx];
334 match target.kind {
335 project_model::TargetKind::Bin => {
336 format!("- {} (--bin {})", target.name, target.name)
337 }
338 project_model::TargetKind::Lib { .. } => format!("- {} (--lib)", target.name),
339 project_model::TargetKind::Example => unreachable!(),
340 project_model::TargetKind::Test => unreachable!(),
341 project_model::TargetKind::Bench => unreachable!(),
342 project_model::TargetKind::Other => unreachable!(),
343 project_model::TargetKind::BuildScript => unreachable!(),
344 }
345 })
346 .collect();
347
348 let target_list = target_list_items.join("\n");
349
350 if options.lib {
353 let target = targets.into_iter().find(|target_idx| {
354 let target = &workspace[*target_idx];
355 matches!(target.kind, project_model::TargetKind::Lib { .. })
356 });
357
358 return target.ok_or_else(|| {
359 anyhow::anyhow!(
360 indoc::indoc! {
361 "No library target found.
362
363 Targets present in package:
364 {}
365 "
366 },
367 target_list,
368 )
369 });
370 }
371
372 if let Some(bin_name) = &options.bin {
373 let target = targets.into_iter().find(|target_idx| {
374 let target = &workspace[*target_idx];
375 (target.kind == project_model::TargetKind::Bin) && (target.name == bin_name[..])
376 });
377
378 return target.ok_or_else(|| {
379 anyhow::anyhow!(
380 indoc::indoc! {
381 "No binary target found with name {:?}.
382
383 Targets present in package:
384 {}
385 "
386 },
387 bin_name,
388 target_list,
389 )
390 });
391 }
392
393 if target_count == 1 {
396 return Ok(targets[0]);
397 }
398
399 Err(anyhow::anyhow!(
400 indoc::indoc! {
401 "Multiple targets present in package,
402 please explicitly select one via --lib or --bin flag.
403
404 Targets present in package:
405 {}
406 "
407 },
408 target_list
409 ))
410}
411
412pub fn find_crate(
413 db: &ide::RootDatabase,
414 vfs: &vfs::Vfs,
415 target: &project_model::TargetData,
416) -> anyhow::Result<hir::Crate> {
417 let crates = hir::Crate::all(db);
418
419 let target_root_path = target.root.as_path();
420
421 let krate = crates.into_iter().find(|krate| {
422 let vfs_path = vfs.file_path(krate.root_file(db));
423 let crate_root_path = vfs_path.as_path().unwrap();
424
425 crate_root_path == target_root_path
426 });
427
428 krate.ok_or_else(|| anyhow::anyhow!("Crate not found"))
429}
430
431pub(crate) fn crate_name(krate: hir::Crate, db: &ide::RootDatabase) -> String {
432 let display_name = krate.display_name(db).unwrap().to_string();
434
435 display_name.replace('-', "_")
437}
438
439pub(crate) fn krate(module_def_hir: hir::ModuleDef, db: &ide::RootDatabase) -> Option<hir::Crate> {
440 module(module_def_hir, db).map(|module| module.krate())
441}
442
443pub(crate) fn module(
444 module_def_hir: hir::ModuleDef,
445 db: &ide::RootDatabase,
446) -> Option<hir::Module> {
447 match module_def_hir {
448 hir::ModuleDef::Module(module) => Some(module),
449 module_def_hir => module_def_hir.module(db),
450 }
451}
452
453pub(crate) fn display_name(
454 module_def_hir: hir::ModuleDef,
455 db: &ide::RootDatabase,
456 edition: ide::Edition,
457) -> String {
458 match module_def_hir {
459 hir::ModuleDef::Module(module_hir) => {
460 if module_hir.is_crate_root() {
461 crate_name(module_hir.krate(), db)
462 } else {
463 module_hir
464 .name(db)
465 .map(|name| name.display(db, edition).to_string())
466 .expect("name")
467 }
468 }
469 hir::ModuleDef::Const(const_hir) => {
470 if let Some(name) = const_hir.name(db) {
471 name.display(db, edition).to_string()
472 } else {
473 "_".to_owned()
474 }
475 }
476 module_def_hir => module_def_hir
477 .name(db)
478 .map(|name| name.display(db, edition).to_string())
479 .expect("name"),
480 }
481}
482
483pub(crate) fn display_path(
484 module_def_hir: hir::ModuleDef,
485 db: &ide::RootDatabase,
486 edition: ide::Edition,
487) -> String {
488 path(module_def_hir, db, edition).unwrap_or_else(|| "<anonymous>".to_owned())
489}
490
491pub(crate) fn path(
492 module_def_hir: hir::ModuleDef,
493 db: &ide::RootDatabase,
494 edition: ide::Edition,
495) -> Option<String> {
496 let mut path = String::new();
497
498 let krate = krate(module_def_hir, db);
499
500 if let Some(crate_name) = krate.map(|krate| crate_name(krate, db)) {
502 path.push_str(&crate_name);
503 }
504
505 let relative_path = match module_def_hir {
507 hir::ModuleDef::Function(function_hir) => {
508 if let Some(assoc_item_hir) = function_hir.as_assoc_item(db) {
509 assoc_item_path(assoc_item_hir, db, edition)
510 } else {
511 hir::ModuleDef::Function(function_hir).canonical_path(db, edition)
512 }
513 }
514 hir::ModuleDef::Const(const_hir) => {
515 if let Some(assoc_item_hir) = const_hir.as_assoc_item(db) {
516 assoc_item_path(assoc_item_hir, db, edition)
517 } else {
518 hir::ModuleDef::Const(const_hir).canonical_path(db, edition)
519 }
520 }
521 hir::ModuleDef::TypeAlias(type_alias_hir) => {
522 if let Some(assoc_item_hir) = type_alias_hir.as_assoc_item(db) {
523 assoc_item_path(assoc_item_hir, db, edition)
524 } else {
525 hir::ModuleDef::TypeAlias(type_alias_hir).canonical_path(db, edition)
526 }
527 }
528 hir::ModuleDef::BuiltinType(builtin_type_hir) => {
529 Some(builtin_type_hir.name().display(db, edition).to_string())
530 }
531 module_def_hir => module_def_hir.canonical_path(db, edition),
532 };
533
534 if let Some(relative_path) = relative_path {
535 if !path.is_empty() {
536 path.push_str("::");
537 }
538 path.push_str(&relative_path);
539 }
540
541 if path.is_empty() { None } else { Some(path) }
542}
543
544fn assoc_item_path(
545 assoc_item_hir: hir::AssocItem,
546 db: &ide::RootDatabase,
547 edition: ide::Edition,
548) -> Option<String> {
549 let name = match assoc_item_hir {
550 hir::AssocItem::Function(function_hir) => hir::ModuleDef::Function(function_hir)
551 .name(db)
552 .map(|name| name.display(db, edition).to_string()),
553 hir::AssocItem::Const(const_hir) => hir::ModuleDef::Const(const_hir)
554 .name(db)
555 .map(|name| name.display(db, edition).to_string()),
556 hir::AssocItem::TypeAlias(type_alias_hir) => hir::ModuleDef::TypeAlias(type_alias_hir)
557 .name(db)
558 .map(|name| name.display(db, edition).to_string()),
559 };
560
561 let name = name?;
562
563 let container_path = match assoc_item_hir.container(db) {
564 hir::AssocItemContainer::Trait(trait_hir) => {
565 hir::ModuleDef::Trait(trait_hir).canonical_path(db, edition)
566 }
567 hir::AssocItemContainer::Impl(impl_hir) => impl_hir
568 .self_ty(db)
569 .as_adt()
570 .and_then(|adt_hir| hir::ModuleDef::Adt(adt_hir).canonical_path(db, edition)),
571 };
572
573 let container_path = container_path?;
574
575 Some(format!("{container_path}::{name}"))
576}
577
578pub(crate) fn parse_ast<N: syntax::AstNode>(text: &str) -> N {
580 let parse = syntax::SourceFile::parse(text, ide::Edition::CURRENT);
581 let node = match parse.tree().syntax().descendants().find_map(N::cast) {
582 Some(it) => it,
583 None => {
584 let node = std::any::type_name::<N>();
585 panic!("Failed to make ast node `{node}` from text {text}")
586 }
587 };
588 let node = node.clone_subtree();
589 assert_eq!(node.syntax().text_range().start(), 0.into());
590 node
591}
592
593pub(crate) fn parse_use_tree(path_expr: &str) -> ast::UseTree {
594 parse_ast(&format!("use {path_expr};"))
595}
596
597pub(crate) fn is_test_function(function: hir::Function, db: &ide::RootDatabase) -> bool {
598 let attrs = function.attrs(db);
599 let key = hir::Symbol::intern("test");
600 attrs.by_key(key).exists()
601}
602
603pub fn cfgs(hir: hir::ModuleDef, db: &ide::RootDatabase) -> Vec<cfg::CfgExpr> {
604 let cfg = match cfg(hir, db) {
605 Some(cfg) => cfg,
606 None => return vec![],
607 };
608
609 match cfg {
610 cfg::CfgExpr::Invalid => vec![],
611 cfg @ cfg::CfgExpr::Atom(_) => vec![cfg],
612 cfg::CfgExpr::All(cfgs) => cfgs.to_vec(),
613 cfg @ cfg::CfgExpr::Any(_) => vec![cfg],
614 cfg @ cfg::CfgExpr::Not(_) => vec![cfg],
615 }
616}
617
618pub fn cfg(hir: hir::ModuleDef, db: &ide::RootDatabase) -> Option<cfg::CfgExpr> {
619 match hir {
620 hir::ModuleDef::Module(r#mod) => r#mod.attrs(db).cfg(),
621 hir::ModuleDef::Function(r#fn) => r#fn.attrs(db).cfg(),
622 hir::ModuleDef::Adt(adt) => adt.attrs(db).cfg(),
623 hir::ModuleDef::Variant(r#variant) => r#variant.attrs(db).cfg(),
624 hir::ModuleDef::Const(r#const) => r#const.attrs(db).cfg(),
625 hir::ModuleDef::Static(r#static) => r#static.attrs(db).cfg(),
626 hir::ModuleDef::Trait(r#trait) => r#trait.attrs(db).cfg(),
627 hir::ModuleDef::TraitAlias(trait_type) => trait_type.attrs(db).cfg(),
628 hir::ModuleDef::TypeAlias(type_alias) => type_alias.attrs(db).cfg(),
629 hir::ModuleDef::BuiltinType(_builtin_type) => None,
630 hir::ModuleDef::Macro(_) => None,
631 }
632}
633
634pub fn cfg_attrs(module_def_hir: hir::ModuleDef, db: &ide::RootDatabase) -> Vec<ItemCfgAttr> {
635 cfgs(module_def_hir, db)
636 .iter()
637 .filter_map(ItemCfgAttr::new)
638 .collect()
639}
640
641pub fn test_attr(module_def_hir: hir::ModuleDef, db: &ide::RootDatabase) -> Option<ItemTestAttr> {
642 let function = match module_def_hir {
643 hir::ModuleDef::Function(function) => function,
644 _ => return None,
645 };
646
647 if is_test_function(function, db) {
648 Some(ItemTestAttr)
649 } else {
650 None
651 }
652}
653
654pub fn module_file(module: hir::Module, db: &ide::RootDatabase, vfs: &vfs::Vfs) -> Option<PathBuf> {
655 let module_source = module.definition_source(db);
656 let is_file_module: bool = match &module_source.value {
657 hir::ModuleSource::SourceFile(_) => true,
658 hir::ModuleSource::Module(_) => false,
659 hir::ModuleSource::BlockExpr(_) => false,
660 };
661
662 if !is_file_module {
663 return None;
664 }
665
666 let file_id = module_source.file_id.original_file(db);
667 let vfs_path = vfs.file_path(file_id.file_id(db));
668 let abs_path = vfs_path.as_path().expect("Could not convert to path");
669
670 let path: &Path = abs_path.as_ref();
671
672 let file_extension = path.extension().and_then(|ext| ext.to_str());
673
674 if file_extension != Some("rs") {
675 return None;
676 }
677
678 Some(path.to_owned())
679}
680
681pub fn moduledef_is_crate(module_def_hir: hir::ModuleDef, _db: &ide::RootDatabase) -> bool {
682 let hir::ModuleDef::Module(module) = module_def_hir else {
683 return false;
684 };
685 module.is_crate_root()
686}
687
688#[allow(dead_code)]
689pub(crate) fn has_test_cfg(attrs: &[ItemCfgAttr]) -> bool {
690 attrs.iter().any(|attr| match attr {
691 ItemCfgAttr::Flag(flag) => flag == "test",
692 _ => false,
693 })
694}
695
696#[allow(dead_code)]
697pub(crate) fn name(
698 module_def_hir: hir::ModuleDef,
699 db: &ide::RootDatabase,
700 edition: ide::Edition,
701) -> String {
702 display_name(module_def_hir, db, edition)
703}
704
705#[allow(dead_code)]
706pub(crate) fn use_tree_matches_item_path(use_tree: &ast::UseTree, path: &str) -> bool {
707 let use_path = match use_tree.path() {
709 Some(p) => p,
710 None => return false,
711 };
712
713 let segments: Vec<String> = use_path
714 .segments()
715 .filter_map(|seg| seg.name_ref())
716 .map(|name| name.text().to_string())
717 .collect();
718
719 let path_parts: Vec<&str> = path.split("::").collect();
720
721 if segments.len() > path_parts.len() {
722 return false;
723 }
724
725 segments.iter().zip(path_parts.iter()).all(|(a, b)| a == b)
726}
727
728#[cfg(test)]
729mod tests {
730 use super::*;
731 use std::path::PathBuf;
732
733 #[test]
734 fn test_load_options_creation() {
735 let opts = LoadOptions {
736 cfg_test: true,
737 sysroot: false,
738 };
739 assert!(opts.cfg_test);
740 assert!(!opts.sysroot);
741 }
742
743 #[test]
744 fn test_cargo_config_with_test_enabled() {
745 let project_opts = ProjectOptions {
746 lib: true,
747 bin: None,
748 package: None,
749 no_default_features: false,
750 all_features: false,
751 features: vec![],
752 target: None,
753 manifest_path: PathBuf::from("Cargo.toml"),
754 };
755
756 let load_opts = LoadOptions {
757 cfg_test: true,
758 sysroot: false,
759 };
760
761 let _config = cargo_config(&project_opts, &load_opts);
762
763 assert!(load_opts.cfg_test);
765 }
766
767 #[test]
768 fn test_cargo_config_with_test_disabled() {
769 let project_opts = ProjectOptions {
770 lib: true,
771 bin: None,
772 package: None,
773 no_default_features: false,
774 all_features: false,
775 features: vec![],
776 target: None,
777 manifest_path: PathBuf::from("Cargo.toml"),
778 };
779
780 let load_opts = LoadOptions {
781 cfg_test: false,
782 sysroot: false,
783 };
784
785 let _config = cargo_config(&project_opts, &load_opts);
786
787 assert!(!load_opts.cfg_test);
789 }
790
791 #[test]
792 fn test_load_config_creation() {
793 let config = load_config();
794 assert!(!config.load_out_dirs_from_check);
796 }
797
798 #[test]
799 fn test_parse_use_tree() {
800 let use_tree = parse_use_tree("std::collections::HashMap");
801 assert!(use_tree.path().is_some());
802
803 let path = use_tree.path().unwrap();
804 let segments: Vec<_> = path
805 .segments()
806 .filter_map(|seg| seg.name_ref())
807 .map(|name| name.text().to_string())
808 .collect();
809
810 assert_eq!(segments, vec!["std", "collections", "HashMap"]);
811 }
812
813 #[test]
814 fn test_use_tree_matches_item_path() {
815 let use_tree = parse_use_tree("std::collections");
816 assert!(use_tree_matches_item_path(
817 &use_tree,
818 "std::collections::HashMap"
819 ));
820 assert!(use_tree_matches_item_path(&use_tree, "std::collections"));
821 assert!(!use_tree_matches_item_path(&use_tree, "std::io"));
822
823 let use_tree = parse_use_tree("std");
824 assert!(use_tree_matches_item_path(&use_tree, "std::collections"));
825 assert!(!use_tree_matches_item_path(&use_tree, "core::mem"));
826 }
827
828 #[test]
829 fn test_parse_ast_function() {
830 use syntax::ast::HasName;
831 let func: ast::Fn = parse_ast("fn test() {}");
832 assert_eq!(func.name().unwrap().text(), "test");
833 }
834
835 #[test]
836 fn test_has_test_cfg() {
837 let attrs = vec![
838 ItemCfgAttr::Flag("test".to_string()),
839 ItemCfgAttr::KeyValue("target_os".to_string(), "linux".to_string()),
840 ];
841 assert!(has_test_cfg(&attrs));
842
843 let attrs = vec![ItemCfgAttr::KeyValue(
844 "target_os".to_string(),
845 "linux".to_string(),
846 )];
847 assert!(!has_test_cfg(&attrs));
848 }
849
850 #[test]
851 fn test_crate_name_canonicalization() {
852 let display_name = "my-crate-name".to_string();
855 let canonicalized = display_name.replace('-', "_");
856 assert_eq!(canonicalized, "my_crate_name");
857 }
858}