rulewright 0.1.2

Executable engineering standards for Rust workspaces
Documentation
#[cfg(test)]
use googletest::prelude::*;
use ra_ap_syntax::ast;

use crate::{AstCtx, Example, Violation};

use super::support::type_arg_last_ident;

#[rustfmt::skip]
const EXAMPLES: &[Example] = &[
    Example {
        label: "Box<Vec<T>>",
        code: "fn f() { let x: Box<Vec<i32>> = Box::new(vec![]); }",
        pass: false,
    },
    Example {
        label: "Box<String>",
        code: "fn f() { let x: Box<String> = Box::new(String::new()); }",
        pass: false,
    },
    Example {
        label: "Box<Box<T>>",
        code: "fn f() { let x: Box<Box<i32>> = Box::new(Box::new(0)); }",
        pass: false,
    },
    Example {
        label: "Box<dyn Trait>",
        code: "fn f() { let x: Box<dyn Trait> = Box::new(foo); }",
        pass: true,
    },
    Example {
        label: "comment and literal with patterns",
        code: "// Box<Vec<i32>> is bad\nconst NOTE: &str = \"Box<String> and Box<Box<T>>\";",
        pass: true,
    },
];

crate::ast_rule!(
    box_vec,
    "Ban `Box<Vec<T>>`, `Box<String>`, `Box<Box<T>>` (unnecessary double indirection).",
    "Box<Vec<T>> adds a pointless heap indirection since Vec already heap-allocates. Use Vec<T> or Box<[T]>.",
    Medium,
);

fn check_box_vec(ctx: &AstCtx<'_>) -> Vec<Violation> {
    ctx.nodes::<ast::PathType>()
        .filter_map(|path_type| {
            let path = path_type.path()?;
            let outer = path.segment()?;

            (outer.name_ref()?.text() == "Box")
                .then(|| type_arg_last_ident(&outer))
                .flatten()
                .and_then(|inner| banned_message(&inner))
                .map(|message| ctx.violation(&path_type, message))
        })
        .collect()
}

fn banned_message(inner: &str) -> Option<&'static str> {
    match inner {
        "Vec" => Some("Box<Vec<T>> is double indirection (use Vec<T> directly)"),
        "String" => Some("Box<String> is double indirection (use String directly)"),
        "Box" => Some("Box<Box<T>> is double indirection (use Box<T> directly)"),
        _ => None,
    }
}

crate::rulewright_ast_test!(check_box_vec, {
    crate::example_tests!(EXAMPLES, check_box_vec);

    #[gtest]
    fn rule_is_deliberately_not_fixable() -> Result<()> {
        let metadata = crate::all_rules()
            .into_iter()
            .find(|rule| rule.name == "rust_box_vec")
            .or_fail()?;

        verify_false!(metadata.fixable)
    }

    #[gtest]
    fn comments_and_literals_do_not_look_like_types() -> Result<()> {
        let source = r##"
            // Box<Vec<i32>>
            const ONE: &str = "Box<String>";
            const TWO: &str = r#"Box<Box<u8>>"#;
        "##;

        verify_true!(run(source).is_empty())
    }
});