|
1 | 1 | //! lint on multiple versions of a crate being used
|
2 | 2 |
|
3 | 3 | use crate::utils::{run_lints, span_lint};
|
| 4 | +use rustc_hir::def_id::LOCAL_CRATE; |
4 | 5 | use rustc_hir::{Crate, CRATE_HIR_ID};
|
5 | 6 | use rustc_lint::{LateContext, LateLintPass};
|
6 | 7 | use rustc_session::{declare_lint_pass, declare_tool_lint};
|
7 | 8 | use rustc_span::source_map::DUMMY_SP;
|
8 | 9 |
|
| 10 | +use cargo_metadata::{DependencyKind, MetadataCommand, Node, Package, PackageId}; |
| 11 | +use if_chain::if_chain; |
9 | 12 | use itertools::Itertools;
|
10 | 13 |
|
11 | 14 | declare_clippy_lint! {
|
@@ -39,32 +42,61 @@ impl LateLintPass<'_, '_> for MultipleCrateVersions {
|
39 | 42 | return;
|
40 | 43 | }
|
41 | 44 |
|
42 |
| - let metadata = if let Ok(metadata) = cargo_metadata::MetadataCommand::new().exec() { |
| 45 | + let metadata = if let Ok(metadata) = MetadataCommand::new().exec() { |
43 | 46 | metadata
|
44 | 47 | } else {
|
45 | 48 | span_lint(cx, MULTIPLE_CRATE_VERSIONS, DUMMY_SP, "could not read cargo metadata");
|
46 |
| - |
47 | 49 | return;
|
48 | 50 | };
|
49 | 51 |
|
| 52 | + let local_name = cx.tcx.crate_name(LOCAL_CRATE).as_str(); |
50 | 53 | let mut packages = metadata.packages;
|
51 | 54 | packages.sort_by(|a, b| a.name.cmp(&b.name));
|
52 | 55 |
|
53 |
| - for (name, group) in &packages.into_iter().group_by(|p| p.name.clone()) { |
54 |
| - let group: Vec<cargo_metadata::Package> = group.collect(); |
| 56 | + if_chain! { |
| 57 | + if let Some(resolve) = &metadata.resolve; |
| 58 | + if let Some(local_id) = packages |
| 59 | + .iter() |
| 60 | + .find_map(|p| if p.name == *local_name { Some(&p.id) } else { None }); |
| 61 | + then { |
| 62 | + for (name, group) in &packages.iter().group_by(|p| p.name.clone()) { |
| 63 | + let group: Vec<&Package> = group.collect(); |
| 64 | + |
| 65 | + if group.len() <= 1 { |
| 66 | + continue; |
| 67 | + } |
55 | 68 |
|
56 |
| - if group.len() > 1 { |
57 |
| - let mut versions: Vec<_> = group.into_iter().map(|p| p.version).collect(); |
58 |
| - versions.sort(); |
59 |
| - let versions = versions.iter().join(", "); |
| 69 | + if group.iter().all(|p| is_normal_dep(&resolve.nodes, local_id, &p.id)) { |
| 70 | + let mut versions: Vec<_> = group.into_iter().map(|p| &p.version).collect(); |
| 71 | + versions.sort(); |
| 72 | + let versions = versions.iter().join(", "); |
60 | 73 |
|
61 |
| - span_lint( |
62 |
| - cx, |
63 |
| - MULTIPLE_CRATE_VERSIONS, |
64 |
| - DUMMY_SP, |
65 |
| - &format!("multiple versions for dependency `{}`: {}", name, versions), |
66 |
| - ); |
| 74 | + span_lint( |
| 75 | + cx, |
| 76 | + MULTIPLE_CRATE_VERSIONS, |
| 77 | + DUMMY_SP, |
| 78 | + &format!("multiple versions for dependency `{}`: {}", name, versions), |
| 79 | + ); |
| 80 | + } |
| 81 | + } |
67 | 82 | }
|
68 | 83 | }
|
69 | 84 | }
|
70 | 85 | }
|
| 86 | + |
| 87 | +fn is_normal_dep(nodes: &[Node], local_id: &PackageId, dep_id: &PackageId) -> bool { |
| 88 | + fn depends_on(node: &Node, dep_id: &PackageId) -> bool { |
| 89 | + node.deps.iter().any(|dep| { |
| 90 | + dep.pkg == *dep_id |
| 91 | + && dep |
| 92 | + .dep_kinds |
| 93 | + .iter() |
| 94 | + .any(|info| matches!(info.kind, DependencyKind::Normal)) |
| 95 | + }) |
| 96 | + } |
| 97 | + |
| 98 | + nodes |
| 99 | + .iter() |
| 100 | + .filter(|node| depends_on(node, dep_id)) |
| 101 | + .any(|node| node.id == *local_id || is_normal_dep(nodes, local_id, &node.id)) |
| 102 | +} |
0 commit comments