rscheck-cli 0.1.0-alpha.3

CLI frontend for the rscheck policy engine.
Documentation
use super::AbsoluteModulePathsRule;
use crate::analysis::{SourceFile, Workspace};
use crate::config::{Level, Policy, RuleSettings};
use crate::emit::ReportEmitter;
use crate::fix::apply_text_edits;
use crate::rules::{Rule, RuleContext};
use std::path::PathBuf;

fn ws_with_single_file(code: &str) -> Workspace {
    let root = PathBuf::from(".");
    let path = root.join("rscheck_test.rs");
    let ast = syn::parse_file(code).ok();
    Workspace {
        root,
        files: vec![SourceFile {
            path,
            text: code.to_string(),
            ast,
            parse_error: None,
        }],
    }
}

#[test]
fn flags_absolute_module_paths_and_allows_crate_root_specials() {
    let ws = ws_with_single_file(
        r#"
use ::foo::bar;

const MY_CONST: i32 = 1;

fn f() {
    let _ = ::std::mem::size_of::<u8>();
    let _p: &std::path::Path = std::path::Path::new("x");
    let _ = crate::MY_CONST;
    crate::static_function();
    crate::my_macro!();
}
"#,
    );

    let mut cfg = Policy::default();
    cfg.rules.insert(
        "architecture.qualified_module_paths".to_string(),
        RuleSettings {
            level: Some(Level::Deny),
            options: toml::Table::new(),
        },
    );
    let mut emitter = ReportEmitter::new();
    AbsoluteModulePathsRule.run(&ws, &RuleContext { policy: &cfg }, &mut emitter);

    assert!(emitter.findings.iter().any(|f| f.message.contains("::std")));
    assert!(
        emitter
            .findings
            .iter()
            .any(|f| f.message.contains("std::path::Path"))
    );

    assert!(
        !emitter
            .findings
            .iter()
            .any(|f| f.message.contains("crate::MY_CONST"))
    );
    assert!(
        !emitter
            .findings
            .iter()
            .any(|f| f.message.contains("crate::static_function"))
    );
    assert!(
        !emitter
            .findings
            .iter()
            .any(|f| f.message.contains("crate::my_macro"))
    );
}

#[test]
fn emits_safe_fix_for_std_type_path() {
    let code = r#"
fn f() {
    let _p: std::path::PathBuf = std::path::PathBuf::from(".");
}
"#;
    let ws = ws_with_single_file(code);

    let mut cfg = Policy::default();
    cfg.rules.insert(
        "architecture.qualified_module_paths".to_string(),
        RuleSettings {
            level: Some(Level::Deny),
            options: toml::Table::new(),
        },
    );
    let mut emitter = ReportEmitter::new();
    AbsoluteModulePathsRule.run(&ws, &RuleContext { policy: &cfg }, &mut emitter);

    let finding = emitter
        .findings
        .iter()
        .find(|f| f.message.contains("std::path::PathBuf"))
        .expect("expected PathBuf finding");
    assert!(!finding.fixes.is_empty());

    let fix = &finding.fixes[0];
    let new_code = apply_text_edits(code, &fix.edits).expect("apply edits");
    assert!(new_code.contains("use std::path::PathBuf;"));
    assert!(new_code.contains("let _p: PathBuf"));
    assert!(new_code.contains("PathBuf::from"));
}

#[test]
fn marks_fix_unsafe_when_local_name_conflicts() {
    let code = r#"
fn f() {
    let PathBuf = 1;
    let _p: std::path::PathBuf = std::path::PathBuf::from(".");
}
"#;
    let ws = ws_with_single_file(code);

    let mut cfg = Policy::default();
    cfg.rules.insert(
        "architecture.qualified_module_paths".to_string(),
        RuleSettings {
            level: Some(Level::Deny),
            options: toml::Table::new(),
        },
    );
    let mut emitter = ReportEmitter::new();
    AbsoluteModulePathsRule.run(&ws, &RuleContext { policy: &cfg }, &mut emitter);

    let finding = emitter
        .findings
        .iter()
        .find(|f| f.message.contains("std::path::PathBuf"))
        .expect("expected PathBuf finding");
    assert_eq!(finding.fixes[0].safety, crate::report::FixSafety::Unsafe);
}