use super::*;
const UNDOCUMENTED_FUNCTIONS_FIXTURE: &str = "\
pub fn alpha(seed: u32) -> u32 {
let mut total = 0;
for value in 0..seed {
total += value;
}
total
}
pub fn beta(seed: u32) -> u32 {
let mut total = 1;
for value in 1..seed {
total *= value;
}
total
}
";
fn undocumented_functions_project() -> tempfile::TempDir {
let dir = tempdir().expect("tempdir");
fs::write(dir.path().join("README.md"), "# Fixture\n").expect("readme write");
fs::write(dir.path().join("sample.rs"), UNDOCUMENTED_FUNCTIONS_FIXTURE).expect("fixture write");
dir
}
fn full_file_options() -> AnalysisOptions {
AnalysisOptions {
paths: vec![PathBuf::from("sample.rs")],
no_config: true,
no_baseline: true,
..default_test_options()
}
}
fn changed_region_options(ranges: &str, scope: ChangedScope) -> AnalysisOptions {
AnalysisOptions {
diff: Some(DiffSelection::ExplicitRanges {
ranges: ranges.to_string(),
scope,
}),
..full_file_options()
}
}
#[test]
pub(crate) fn changed_ranges_symbol_scope_widens_to_enclosing_function() {
let _guard = analysis_lock();
let project = undocumented_functions_project();
let full = run_project_analysis(project.path(), full_file_options())
.expect("full-file analysis succeeds");
let total = full.findings.len();
assert!(
["alpha", "beta"].iter().all(|name| {
full.findings.iter().any(|finding| {
finding.rule_id == "docs.missing-public-doc"
&& finding.symbol.as_deref() == Some(name)
})
}),
"fixture must yield a missing-public-doc finding for both functions; got {:?}",
full.findings
.iter()
.map(|finding| (&finding.rule_id, &finding.symbol))
.collect::<Vec<_>>()
);
let symbol = run_project_analysis(
project.path(),
changed_region_options("4-4", ChangedScope::Symbol),
)
.expect("symbol-scope analysis succeeds");
assert!(
symbol
.findings
.iter()
.any(|finding| finding.symbol.as_deref() == Some("alpha")),
"symbol scope must keep alpha's signature finding for a body-only change"
);
assert!(
!symbol
.findings
.iter()
.any(|finding| finding.symbol.as_deref() == Some("beta")),
"beta is outside the changed symbol and must be suppressed"
);
assert!(
symbol.findings.len() < total,
"something outside alpha must be suppressed"
);
assert_eq!(
symbol.suppressed_count,
Some(total - symbol.findings.len()),
"suppressed_count must account for every finding outside the changed symbol"
);
let hunk = run_project_analysis(
project.path(),
changed_region_options("4-4", ChangedScope::Hunk),
)
.expect("hunk-scope analysis succeeds");
assert!(
hunk.findings.is_empty(),
"hunk scope must not keep findings off the changed line; got {:?}",
hunk.findings
.iter()
.map(|finding| (&finding.rule_id, finding.line))
.collect::<Vec<_>>()
);
assert_eq!(hunk.suppressed_count, Some(total));
}
#[test]
pub(crate) fn no_baseline_resurfaces_baselined_findings_in_changed_region() {
let _guard = analysis_lock();
let project = undocumented_functions_project();
run_project_analysis(
project.path(),
AnalysisOptions {
generate_baseline: Some(PathBuf::from("baseline.json")),
..full_file_options()
},
)
.expect("baseline generation succeeds");
let with_baseline = run_project_analysis(
project.path(),
AnalysisOptions {
baseline: Some(PathBuf::from("baseline.json")),
no_baseline: false,
..changed_region_options("4-4", ChangedScope::Symbol)
},
)
.expect("baseline-applied analysis succeeds");
assert!(
!with_baseline
.findings
.iter()
.any(|finding| finding.symbol.as_deref() == Some("alpha")),
"an applied baseline must suppress alpha even inside the changed region"
);
assert!(
with_baseline
.baseline
.as_ref()
.is_some_and(|baseline| baseline.suppressed >= 1),
"baseline report must record the suppression"
);
let no_baseline = run_project_analysis(
project.path(),
AnalysisOptions {
baseline: Some(PathBuf::from("baseline.json")),
no_baseline: true,
..changed_region_options("4-4", ChangedScope::Symbol)
},
)
.expect("no-baseline analysis succeeds");
assert!(
no_baseline.baseline.is_none(),
"--no-baseline must leave report.baseline None even with --baseline set"
);
assert!(
no_baseline.findings.iter().any(|finding| {
finding.rule_id == "docs.missing-public-doc"
&& finding.symbol.as_deref() == Some("alpha")
}),
"--no-baseline must re-surface the baselined finding inside the changed region"
);
}
#[test]
pub(crate) fn changed_region_json_carries_top_level_suppressed_count_and_scopes_findings() {
let _guard = analysis_lock();
let project = undocumented_functions_project();
let full = run_project_analysis(project.path(), full_file_options())
.expect("full-file analysis succeeds");
let full_json = render_report(&full, OutputFormat::Json);
assert!(
!full_json.contains("suppressedCount"),
"full-tree JSON must omit suppressedCount entirely:\n{full_json}"
);
let scoped = run_project_analysis(
project.path(),
changed_region_options("4-4", ChangedScope::Symbol),
)
.expect("changed-region analysis succeeds");
let expected_suppressed = scoped
.suppressed_count
.expect("a diff run must set suppressed_count");
assert!(
expected_suppressed >= 1,
"at least beta must be suppressed outside the changed symbol"
);
let rendered = render_report(&scoped, OutputFormat::Json);
assert!(
rendered.contains("\"suppressedCount\""),
"scoped JSON must carry the top-level suppressedCount key:\n{rendered}"
);
let value: serde_json::Value = serde_json::from_str(&rendered).expect("scoped JSON parses");
assert_eq!(
value["suppressedCount"].as_u64(),
Some(expected_suppressed as u64),
"top-level suppressedCount must equal the out-of-region total"
);
let findings = value["findings"].as_array().expect("findings array");
assert!(
findings.iter().any(|finding| finding["symbol"] == "alpha"),
"scoped JSON must keep the in-region (alpha) finding"
);
assert!(
findings.iter().all(|finding| finding["symbol"] != "beta"),
"scoped JSON must exclude the out-of-region (beta) finding"
);
}