Skip to content

Commit 85d4856

Browse files
feat(cli): show file paths in --list-skipped output
add file path to --list-skipped output format: src/ffi.rs: ffi_callback (extern). track relative path in SkippedFunction struct. update integration test for new format.
1 parent 4e6d4e1 commit 85d4856

File tree

3 files changed

+60
-17
lines changed

3 files changed

+60
-17
lines changed

src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ fn build_project(
277277

278278
if list_skipped {
279279
for s in &skipped {
280-
println!("{} ({})", s.name, s.reason);
280+
println!("{}: {} ({})", s.path.display(), s.name, s.reason);
281281
}
282282
return Ok(None);
283283
}

src/resolve.rs

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ impl std::fmt::Display for SkipReason {
4545
pub struct SkippedFunction {
4646
pub name: String,
4747
pub reason: SkipReason,
48+
/// File path relative to the project root (e.g. "src/ffi.rs").
49+
pub path: PathBuf,
4850
}
4951

5052
/// Result of target resolution: resolved targets plus any skipped functions.
@@ -68,6 +70,14 @@ pub fn resolve_targets(
6870
) -> Result<ResolveResult, Error> {
6971
let rs_files = walk_rs_files(src_dir)?;
7072

73+
// Compute a relative path like "src/main.rs" from an absolute file path.
74+
let project_root = src_dir.parent().unwrap_or(src_dir);
75+
let rel_path = |file: &Path| -> PathBuf {
76+
file.strip_prefix(project_root)
77+
.unwrap_or(file)
78+
.to_path_buf()
79+
};
80+
7181
let mut results: Vec<ResolvedTarget> = Vec::new();
7282
let mut skipped: Vec<SkippedFunction> = Vec::new();
7383

@@ -78,7 +88,7 @@ pub fn resolve_targets(
7888
path: file.clone(),
7989
source,
8090
})?;
81-
let (all_fns, file_skipped) = extract_functions(&source, file);
91+
let (all_fns, file_skipped) = extract_functions(&source, file, rel_path(file));
8292
if !all_fns.is_empty() {
8393
merge_into(&mut results, file, all_fns);
8494
}
@@ -95,7 +105,8 @@ pub fn resolve_targets(
95105
source,
96106
}
97107
})?;
98-
let (all_fns, file_skipped) = extract_functions(&source, file);
108+
let (all_fns, file_skipped) =
109+
extract_functions(&source, file, rel_path(file));
99110
let matched: Vec<String> = all_fns
100111
.into_iter()
101112
.filter(|name| {
@@ -138,7 +149,8 @@ pub fn resolve_targets(
138149
source,
139150
}
140151
})?;
141-
let (all_fns, file_skipped) = extract_functions(&source, file);
152+
let (all_fns, file_skipped) =
153+
extract_functions(&source, file, rel_path(file));
142154
if !all_fns.is_empty() {
143155
merge_into(&mut results, file, all_fns);
144156
}
@@ -167,7 +179,8 @@ pub fn resolve_targets(
167179
source,
168180
}
169181
})?;
170-
let (all_fns, file_skipped) = extract_functions(&source, file);
182+
let (all_fns, file_skipped) =
183+
extract_functions(&source, file, rel_path(file));
171184
if !all_fns.is_empty() {
172185
merge_into(&mut results, file, all_fns);
173186
}
@@ -251,7 +264,11 @@ fn walk_rs_files_inner(dir: &Path, out: &mut Vec<PathBuf>) -> Result<(), Error>
251264
///
252265
/// Functions annotated with `#[test]` and items inside `#[cfg(test)]` modules
253266
/// are excluded -- they are not useful instrumentation targets.
254-
fn extract_functions(source: &str, path: &Path) -> (Vec<String>, Vec<SkippedFunction>) {
267+
fn extract_functions(
268+
source: &str,
269+
path: &Path,
270+
rel_path: PathBuf,
271+
) -> (Vec<String>, Vec<SkippedFunction>) {
255272
let syntax = match syn::parse_file(source) {
256273
Ok(f) => f,
257274
Err(e) => {
@@ -260,7 +277,13 @@ fn extract_functions(source: &str, path: &Path) -> (Vec<String>, Vec<SkippedFunc
260277
}
261278
};
262279

263-
let mut collector = FnCollector::default();
280+
let mut collector = FnCollector {
281+
functions: Vec::new(),
282+
skipped: Vec::new(),
283+
path: rel_path,
284+
current_impl: None,
285+
current_trait: None,
286+
};
264287
collector.visit_file(&syntax);
265288
(collector.functions, collector.skipped)
266289
}
@@ -307,10 +330,11 @@ pub(crate) fn classify_skip(sig: &syn::Signature) -> Option<SkipReason> {
307330
}
308331

309332
/// AST visitor that collects function names from a parsed Rust file.
310-
#[derive(Default)]
311333
struct FnCollector {
312334
functions: Vec<String>,
313335
skipped: Vec<SkippedFunction>,
336+
/// File path relative to the project root (e.g. "src/core.rs").
337+
path: PathBuf,
314338
/// When inside an `impl` block, holds the type name (e.g. "Resolver").
315339
current_impl: Option<String>,
316340
/// When inside a `trait` block, holds the trait name.
@@ -331,6 +355,7 @@ impl<'ast> Visit<'ast> for FnCollector {
331355
self.skipped.push(SkippedFunction {
332356
name: node.sig.ident.to_string(),
333357
reason,
358+
path: self.path.clone(),
334359
});
335360
} else {
336361
self.functions.push(node.sig.ident.to_string());
@@ -358,6 +383,7 @@ impl<'ast> Visit<'ast> for FnCollector {
358383
self.skipped.push(SkippedFunction {
359384
name: qualified,
360385
reason,
386+
path: self.path.clone(),
361387
});
362388
} else {
363389
self.functions.push(qualified);
@@ -385,6 +411,7 @@ impl<'ast> Visit<'ast> for FnCollector {
385411
self.skipped.push(SkippedFunction {
386412
name: qualified,
387413
reason,
414+
path: self.path.clone(),
388415
});
389416
} else {
390417
self.functions.push(qualified);
@@ -449,7 +476,8 @@ fn build_suggestion_hint(specs: &[TargetSpec], rs_files: &[PathBuf]) -> String {
449476
let Ok(source) = std::fs::read_to_string(file) else {
450477
continue;
451478
};
452-
let (fns, _skipped) = extract_functions(&source, file);
479+
// path unused — only collecting function names for suggestions
480+
let (fns, _skipped) = extract_functions(&source, file, PathBuf::new());
453481
all_names.extend(fns);
454482
}
455483
all_names.sort();
@@ -993,6 +1021,11 @@ mod tests {
9931021
);
9941022
assert_eq!(result.skipped[0].name, "dangerous");
9951023
assert_eq!(result.skipped[0].reason, SkipReason::Unsafe);
1024+
assert_eq!(
1025+
result.skipped[0].path,
1026+
Path::new("src/special_fns.rs"),
1027+
"skipped function should have relative file path"
1028+
);
9961029
}
9971030

9981031
#[test]
@@ -1030,6 +1063,16 @@ mod tests {
10301063
"should report unsafe impl method as skipped: {skipped_names:?}"
10311064
);
10321065

1066+
// All skipped functions from this file should have the correct relative path.
1067+
for s in &result.skipped {
1068+
assert_eq!(
1069+
s.path,
1070+
Path::new("src/special_fns.rs"),
1071+
"skipped fn '{}' should have relative path src/special_fns.rs",
1072+
s.name
1073+
);
1074+
}
1075+
10331076
let all_fns: Vec<&str> = result
10341077
.targets
10351078
.iter()

tests/special_fns.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -159,26 +159,26 @@ fn list_skipped_shows_excluded_functions() {
159159
"piano build --list-skipped failed:\nstderr: {stderr}\nstdout: {stdout}"
160160
);
161161

162-
// Should list all skipped functions with reasons.
162+
// Should list all skipped functions with file paths and reasons.
163163
assert!(
164-
stdout.contains("dangerous (unsafe)"),
165-
"should list unsafe fn: {stdout}"
164+
stdout.contains("src/main.rs: dangerous (unsafe)"),
165+
"should list unsafe fn with path: {stdout}"
166166
);
167167
assert!(
168-
stdout.contains("fixed_size (const)"),
169-
"should list const fn: {stdout}"
168+
stdout.contains("src/main.rs: fixed_size (const)"),
169+
"should list const fn with path: {stdout}"
170170
);
171171
assert!(
172-
stdout.contains("ffi_callback (extern)"),
173-
"should list extern fn: {stdout}"
172+
stdout.contains("src/main.rs: ffi_callback (extern)"),
173+
"should list extern fn with path: {stdout}"
174174
);
175175

176176
// Should NOT list normal functions.
177177
assert!(
178178
!stdout.contains("normal_work"),
179179
"should not list normal fn: {stdout}"
180180
);
181-
assert!(!stdout.contains("main"), "should not list main: {stdout}");
181+
assert!(!stdout.contains("main ("), "should not list main: {stdout}");
182182
}
183183

184184
#[test]

0 commit comments

Comments
 (0)