deslop 0.2.0

A static analyzer that spots low-context and AI-assisted code patterns across naming, concurrency, security, performance, and test quality.
Documentation
use super::super::FixtureWorkspace;

fn assert_rules_present(report: &deslop::ScanReport, rule_ids: &[&str]) {
    for rule_id in rule_ids {
        assert!(
            report
                .findings
                .iter()
                .any(|finding| finding.rule_id == *rule_id),
            "expected rule {rule_id} to fire"
        );
    }
}

fn assert_rules_absent(report: &deslop::ScanReport, rule_ids: &[&str]) {
    for rule_id in rule_ids {
        assert!(
            !report
                .findings
                .iter()
                .any(|finding| finding.rule_id == *rule_id),
            "did not expect rule {rule_id} to fire"
        );
    }
}

const MLOPS_RULES: &[&str] = &[
    "pandas_iterrows_in_loop",
    "pandas_apply_with_simple_vectorizable_op",
    "pandas_concat_in_loop",
    "pandas_read_csv_without_dtypes",
    "pandas_to_dict_records_in_loop",
    "pandas_merge_without_validation",
    "pandas_full_dataframe_print_in_production",
    "pandas_eval_string_manipulation",
    "pandas_copy_in_loop",
    "numpy_python_loop_over_array",
    "numpy_append_in_loop",
    "numpy_vstack_hstack_in_loop",
    "model_eval_mode_missing",
    "torch_no_grad_missing_in_inference",
    "training_loop_without_zero_grad",
    "llm_api_call_in_loop_without_batching",
    "prompt_template_string_concat_in_loop",
    "hardcoded_api_key_in_source",
    "wandb_mlflow_log_in_tight_loop",
    "global_state_in_data_pipeline",
    "print_metrics_instead_of_logging",
    "entire_dataframe_copied_for_transform",
];

const ADVANCED_MLOPS_RULES: &[&str] = &[
    "vector_store_client_created_per_request",
    "langchain_chain_built_per_request",
    "tokenizer_encode_in_loop_without_cache",
];

#[test]
fn test_python_mlops_positive() {
    let workspace = FixtureWorkspace::new();
    workspace.write_files(&[(
        "pkg/mlops_code.py",
        python_fixture!("integration/mlops/mlops_positive.txt"),
    )]);

    let report = workspace.scan();

    assert_rules_present(&report, MLOPS_RULES);
}

#[test]
fn test_python_mlops_clean() {
    let workspace = FixtureWorkspace::new();
    workspace.write_files(&[(
        "pkg/mlops_code.py",
        python_fixture!("integration/mlops/mlops_clean.txt"),
    )]);

    let report = workspace.scan();

    assert_rules_absent(&report, MLOPS_RULES);
}

#[test]
fn test_python_mlops_phase3_advanced_positive() {
    let workspace = FixtureWorkspace::new();
    workspace.write_files(&[(
        "pkg/mlops_phase3_advanced.py",
        python_fixture!("integration/mlops/mlops_phase3_advanced_positive.txt"),
    )]);

    let report = workspace.scan();

    assert_rules_present(&report, ADVANCED_MLOPS_RULES);
}

#[test]
fn test_python_mlops_phase3_advanced_clean() {
    let workspace = FixtureWorkspace::new();
    workspace.write_files(&[(
        "pkg/mlops_phase3_advanced.py",
        python_fixture!("integration/mlops/mlops_phase3_advanced_clean.txt"),
    )]);

    let report = workspace.scan();

    assert_rules_absent(&report, ADVANCED_MLOPS_RULES);
}