|
4 | 4 |
|
5 | 5 | extern crate rustc_span;
|
6 | 6 |
|
| 7 | +use cargo_metadata::MetadataCommand; |
7 | 8 | use clippy_utils::diagnostics::span_lint_and_help;
|
8 | 9 | use once_cell::sync::Lazy;
|
9 | 10 | use regex::Regex;
|
10 | 11 | use rustc_lint::{LateContext, LateLintPass};
|
11 | 12 | use rustc_span::{BytePos, FileName, Span, SyntaxContext};
|
12 |
| -use std::path::Path; |
13 | 13 |
|
14 | 14 | dylint_linting::declare_late_lint! {
|
15 | 15 | /// ### What it does
|
@@ -85,36 +85,63 @@ impl<'tcx> LateLintPass<'tcx> for NonexistentPathInComment {
|
85 | 85 |
|
86 | 86 | fn check_comment(cx: &LateContext<'_>, span: Span, comment_text: &str, filename: &FileName) {
|
87 | 87 | let base_dir = match filename {
|
88 |
| - FileName::Real(real_filename) => Path::new(real_filename.local_path().unwrap()) |
| 88 | + FileName::Real(real_filename) => real_filename |
| 89 | + .local_path() |
| 90 | + .expect("failed getting path") |
89 | 91 | .parent()
|
90 | 92 | .unwrap()
|
91 | 93 | .to_path_buf(),
|
92 | 94 | _ => return,
|
93 | 95 | };
|
94 | 96 |
|
95 |
| - for captures in PATH_REGEX.captures_iter(comment_text) { |
96 |
| - let path_str = &captures[1]; |
| 97 | + let metadata = MetadataCommand::new() |
| 98 | + .current_dir(&base_dir) |
| 99 | + .no_deps() |
| 100 | + .exec() |
| 101 | + .expect("failed getting metadata"); |
| 102 | + |
| 103 | + for caps in PATH_REGEX.captures_iter(comment_text) { |
| 104 | + let path_str = &caps[1]; |
97 | 105 | let full_path = base_dir.join(path_str);
|
98 | 106 |
|
99 |
| - if !full_path.exists() { |
100 |
| - let path_start = captures.get(1).unwrap().start(); |
101 |
| - let path_end = captures.get(1).unwrap().end(); |
102 |
| - let path_span = Span::new( |
103 |
| - span.lo() + BytePos(path_start as u32), |
104 |
| - span.lo() + BytePos(path_end as u32), |
105 |
| - span.ctxt(), |
106 |
| - None, |
107 |
| - ); |
108 |
| - |
109 |
| - span_lint_and_help( |
110 |
| - cx, |
111 |
| - NONEXISTENT_PATH_IN_COMMENT, |
112 |
| - path_span, |
113 |
| - "referenced path does not exist", |
114 |
| - None, |
115 |
| - "verify the path is correct or remove the reference", |
116 |
| - ); |
| 107 | + if full_path.exists() { |
| 108 | + continue; |
| 109 | + } |
| 110 | + |
| 111 | + if let Some(root_pkg) = metadata.root_package() { |
| 112 | + if let Some(manifest_parent) = root_pkg.manifest_path.parent() { |
| 113 | + let manifest_dir = manifest_parent.as_std_path(); |
| 114 | + |
| 115 | + let candidate_from_root = |
| 116 | + if let Some(stripped) = path_str.strip_prefix(&root_pkg.name) { |
| 117 | + let stripped = stripped.strip_prefix('/').unwrap_or(stripped); |
| 118 | + manifest_dir.join(stripped) |
| 119 | + } else { |
| 120 | + manifest_dir.join(path_str) |
| 121 | + }; |
| 122 | + |
| 123 | + if candidate_from_root.exists() { |
| 124 | + continue; |
| 125 | + } |
| 126 | + } |
117 | 127 | }
|
| 128 | + |
| 129 | + let path_start = caps.get(1).unwrap().start(); |
| 130 | + let path_end = caps.get(1).unwrap().end(); |
| 131 | + let path_span = Span::new( |
| 132 | + span.lo() + BytePos(path_start as u32), |
| 133 | + span.lo() + BytePos(path_end as u32), |
| 134 | + span.ctxt(), |
| 135 | + None, |
| 136 | + ); |
| 137 | + span_lint_and_help( |
| 138 | + cx, |
| 139 | + NONEXISTENT_PATH_IN_COMMENT, |
| 140 | + path_span, |
| 141 | + "referenced path does not exist", |
| 142 | + None, |
| 143 | + "verify the path is correct or remove the reference", |
| 144 | + ); |
118 | 145 | }
|
119 | 146 | }
|
120 | 147 |
|
|
0 commit comments