declutter-diff 0.1.2

Review AI-generated diffs with comments, tests, imports, logging and formatting toggled in and out
Documentation
use declutter::classify::classify;
use declutter::diff::{Hunk, RowKind};
use declutter::lang::Lang;
use declutter::project::LayerMode;
use declutter::review::{ChangeStatus, DiffModes, FileChange, FileReview, Layers, Summary};

fn review(path: &str, old: &str, new: &str) -> FileReview {
    FileReview::new(FileChange {
        path: path.to_string(),
        old_path: None,
        status: ChangeStatus::Modified,
        old: Some(old.to_string()),
        new: Some(new.to_string()),
        binary: false,
    })
}

fn changes(file: &FileReview, modes: DiffModes) -> Vec<String> {
    file.view(modes)
        .hunks
        .iter()
        .flat_map(Hunk::changed)
        .map(|row| {
            let sign = if row.kind == RowKind::Removed {
                '-'
            } else {
                '+'
            };
            format!("{sign}{}", row.text)
        })
        .collect()
}

fn texts<'a>(src: &'a str, spans: &[std::ops::Range<usize>]) -> Vec<&'a str> {
    spans.iter().map(|span| &src[span.clone()]).collect()
}

fn modes(imports: LayerMode, logging: LayerMode) -> DiffModes {
    DiffModes {
        imports,
        logging,
        ..DiffModes::SHOWN
    }
}

#[test]
fn imports_are_found_in_every_language() {
    let swift = "import Foundation\n@testable import Cart\nlet a = 1\n";
    let ts = "import { a } from './a';\nexport { b } from './b';\nexport const c = 1;\n";
    let py = "import os\nfrom a import b\nfrom __future__ import annotations\nx = 1\n";

    let found = |lang, src| texts(src, &classify(lang, src).expect("parses").imports);
    assert_eq!(
        found(Lang::Swift, swift),
        ["import Foundation", "@testable import Cart"]
    );
    assert_eq!(
        found(Lang::TypeScript, ts),
        ["import { a } from './a';", "export { b } from './b';"]
    );
    assert_eq!(
        found(Lang::Python, py),
        [
            "import os",
            "from a import b",
            "from __future__ import annotations"
        ]
    );
}

#[test]
fn logging_statements_are_found_but_other_calls_are_not() {
    let swift = "func f() {\n    print(\"x\")\n    logger.debug(\"y \\(a)\")\n    Self.logger.info(\"z\")\n    dialog.error(\"keep\")\n    let v = log(2)\n}\n";
    let ts = "function f() {\n  console.log('x');\n  this.logger.warn('y');\n  catalog.error('keep');\n  const v = console.log;\n}\n";
    let py = "def f():\n    print('x')\n    logging.info('y')\n    self.log.debug('z')\n    v = print\n    catalog.error('keep')\n";

    let found = |lang, src| texts(src, &classify(lang, src).expect("parses").logging);
    assert_eq!(
        found(Lang::Swift, swift),
        [
            "print(\"x\")",
            "logger.debug(\"y \\(a)\")",
            "Self.logger.info(\"z\")"
        ]
    );
    assert_eq!(
        found(Lang::TypeScript, ts),
        ["console.log('x');", "this.logger.warn('y');"]
    );
    assert_eq!(
        found(Lang::Python, py),
        ["print('x')", "logging.info('y')", "self.log.debug('z')"]
    );
}

#[test]
fn hiding_logging_shows_only_the_real_change() {
    let file = review(
        "cart.ts",
        "function total(xs) {\n  return sum(xs);\n}\n",
        "function total(xs) {\n  console.log('total', xs);\n  return sum(xs) * 2;\n}\n",
    );

    assert_eq!(
        changes(&file, modes(LayerMode::Shown, LayerMode::Hidden)),
        ["-  return sum(xs);", "+  return sum(xs) * 2;"]
    );
    assert_eq!(
        changes(&file, modes(LayerMode::Shown, LayerMode::Only)),
        ["+  console.log('total', xs);"]
    );
}

#[test]
fn hidden_layers_combine() {
    let file = review(
        "cart.py",
        "import os\nx = 1\n",
        "import os\nimport sys\n# why\nprint(x)\nx = 1\n",
    );

    let all = DiffModes {
        comments: LayerMode::Hidden,
        ..modes(LayerMode::Hidden, LayerMode::Hidden)
    };
    assert!(file.view(all).hunks.is_empty());
    assert_eq!(file.view(all).hidden_hunks, 1);
    assert!(
        !file
            .view(modes(LayerMode::Hidden, LayerMode::Shown))
            .hunks
            .is_empty()
    );

    let layers = Layers {
        imports: LayerMode::Hidden,
        logging: LayerMode::Hidden,
        ..Layers::default()
    };
    assert_eq!(
        Summary::new(std::slice::from_ref(&file), layers).status_line(layers),
        "comments: hidden · imports: hidden · logging: hidden · showing 0 of 1 hunks · 1 comment/import/logging-only hunk hidden · 1 comment/import/logging-only file"
    );
}

#[test]
fn a_layer_set_to_only_wins_over_hidden_ones() {
    let file = review(
        "rate.py",
        "# Percent.\nrate = 5\n",
        "# Fraction.\nprint(rate)\nrate = 0.05\n",
    );
    let comments_only = DiffModes {
        comments: LayerMode::Only,
        ..modes(LayerMode::Shown, LayerMode::Hidden)
    };

    assert_eq!(
        changes(&file, comments_only),
        ["-# Percent.", "+# Fraction."]
    );
}