mod common;
use common::cli_command;
use predicates::str::contains;
use std::fs;
use tempfile::TempDir;
fn create_temp_rust_file(content: &str) -> (TempDir, std::path::PathBuf) {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let file_path = temp_dir.path().join("test.rs");
fs::write(&file_path, content).expect("Failed to write test file");
(temp_dir, file_path)
}
#[test]
fn test_rustdoc_command_no_issues() {
let content = r#"//! # My Crate
//!
//! This is a well-formatted module documentation that provides a comprehensive
//! introduction to the crate's functionality and purpose.
//!
//! ## Features
//!
//! - Feature one
//! - Feature two
fn main() {}
"#;
let (_temp_dir, file_path) = create_temp_rust_file(content);
cli_command()
.arg("rustdoc")
.arg(&file_path)
.assert()
.success()
.stdout(contains("No issues found"));
}
#[test]
fn test_rustdoc_command_finds_violations() {
let content = r#"//! #Missing space after hash
//!
//! Some content.
fn main() {}
"#;
let (_temp_dir, file_path) = create_temp_rust_file(content);
cli_command()
.arg("rustdoc")
.arg(&file_path)
.assert()
.success()
.stdout(contains("MD018"))
.stdout(contains("No space after hash"));
}
#[test]
fn test_rustdoc_command_correct_line_numbers() {
let content = r#"// Regular comment
// Another comment
//! # Title
//!
//! ##Bad heading here
fn main() {}
"#;
let (_temp_dir, file_path) = create_temp_rust_file(content);
let assert = cli_command().arg("rustdoc").arg(&file_path).assert();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert!(
stdout.contains(":5:"),
"Expected line 5 in output, got: {}",
stdout
);
}
#[test]
fn test_rustdoc_command_preserves_indentation() {
let content = r#"//! # Lists
//!
//! - Item 1
//! - Nested item
//! - Deeply nested
fn main() {}
"#;
let (_temp_dir, file_path) = create_temp_rust_file(content);
cli_command()
.arg("rustdoc")
.arg(&file_path)
.assert()
.success()
.stdout(contains("No issues found"));
}
#[test]
fn test_rustdoc_command_handles_code_blocks() {
let content = r#"//! # Example
//!
//! ```rust
//! fn example() {
//! println!("Hello");
//! }
//! ```
fn main() {}
"#;
let (_temp_dir, file_path) = create_temp_rust_file(content);
cli_command()
.arg("rustdoc")
.arg(&file_path)
.assert()
.success()
.stdout(contains("No issues found"));
}
#[test]
fn test_rustdoc_command_no_module_docs() {
let content = r#"/// This is an item doc, not module doc
fn foo() {}
/// Another item doc
struct Bar;
"#;
let (_temp_dir, file_path) = create_temp_rust_file(content);
cli_command()
.arg("rustdoc")
.arg(&file_path)
.assert()
.success()
.stdout(contains("No issues found"));
}
#[test]
fn test_rustdoc_command_directory_recursive() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let src_dir = temp_dir.path().join("src");
fs::create_dir_all(&src_dir).expect("Failed to create src directory");
fs::write(
src_dir.join("lib.rs"),
r#"//! # Library
//!
//! ##Bad heading
pub mod foo;
"#,
)
.unwrap();
fs::write(
src_dir.join("foo.rs"),
r#"//! # Foo Module
//!
//! ##Another bad heading
"#,
)
.unwrap();
let assert = cli_command()
.arg("rustdoc")
.arg(temp_dir.path())
.assert()
.success();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert!(stdout.contains("lib.rs"), "Expected lib.rs in output");
assert!(stdout.contains("foo.rs"), "Expected foo.rs in output");
assert!(
stdout.contains("MD018"),
"Expected MD018 violations in output"
);
}
#[test]
fn test_rustdoc_command_skips_target_directory() {
let temp_dir = TempDir::new().expect("Failed to create temp directory");
let src_dir = temp_dir.path().join("src");
let target_dir = temp_dir.path().join("target");
fs::create_dir_all(&src_dir).expect("Failed to create src directory");
fs::create_dir_all(&target_dir).expect("Failed to create target directory");
fs::write(
src_dir.join("lib.rs"),
r#"//! # Library
//!
//! Good documentation.
"#,
)
.unwrap();
fs::write(
target_dir.join("generated.rs"),
r#"//! ##Bad heading in target
"#,
)
.unwrap();
cli_command()
.arg("rustdoc")
.arg(temp_dir.path())
.assert()
.success()
.stdout(contains("No issues found"));
}
#[test]
fn test_rustdoc_command_with_disable_flag() {
let content = r#"//! #Missing space
//!
//! Some content.
fn main() {}
"#;
let (_temp_dir, file_path) = create_temp_rust_file(content);
cli_command()
.arg("rustdoc")
.arg(&file_path)
.arg("--disable")
.arg("MD018")
.assert()
.success()
.stdout(contains("No issues found"));
}
#[test]
fn test_rustdoc_command_json_output() {
let content = r#"//! #Bad heading
fn main() {}
"#;
let (_temp_dir, file_path) = create_temp_rust_file(content);
let assert = cli_command()
.arg("rustdoc")
.arg(&file_path)
.arg("--output")
.arg("json")
.assert()
.success();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("Output should be valid JSON");
assert!(parsed.get("total_violations").is_some());
assert!(parsed.get("files").is_some());
}
#[test]
fn test_rustdoc_default_disabled_rules() {
let content = r#"//! This module provides utilities for processing data.
//!
//! It includes various helper functions.
fn main() {}
"#;
let (_temp_dir, file_path) = create_temp_rust_file(content);
let assert = cli_command().arg("rustdoc").arg(&file_path).assert();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert!(
!stdout.contains("MD041"),
"MD041 should be disabled by default for rustdoc"
);
}
#[test]
fn test_rustdoc_stops_at_regular_comment() {
let content = r#"//! First doc block
//!
//! Some content.
// This regular comment should stop extraction
//! This should NOT be linted (second block after regular comment)
//! ##Bad heading that should be ignored
fn main() {}
"#;
let (_temp_dir, file_path) = create_temp_rust_file(content);
let assert = cli_command().arg("rustdoc").arg(&file_path).assert();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert!(
!stdout.contains("MD018"),
"Should not lint content after regular comment break"
);
}