decomment 0.2.0

Remove comments from source text in 13 languages without touching strings, templates, or regex literals.
Documentation
//! Every mode against every flag, and the interactions between them.

use decomment::{strip, Language, Mode, Options};

fn run(source: &str, mode: Mode, preserve: bool, bang: bool) -> String {
    let mut options = Options::new(Language::JavaScript);
    options.mode = mode;
    options.preserve_newlines = preserve;
    options.keep_bang_comments = bang;
    strip(source, options).output
}

#[test]
fn all_removes_both_forms() {
    assert_eq!(
        run("a // x\nb /* y */ c", Mode::All, false, false),
        "a \nb  c"
    );
}

#[test]
fn line_removes_only_line_comments() {
    assert_eq!(
        run("a // x\nb /* y */ c", Mode::Line, false, false),
        "a \nb /* y */ c"
    );
}

#[test]
fn block_removes_only_block_comments() {
    assert_eq!(
        run("a // x\nb /* y */ c", Mode::Block, false, false),
        "a // x\nb  c"
    );
}

#[test]
fn first_removes_one_comment_and_stops_looking() {
    assert_eq!(
        run("a // x\nb /* y */ c // z", Mode::First, false, false),
        "a \nb /* y */ c // z"
    );
}

#[test]
fn first_on_input_with_no_comment_changes_nothing() {
    assert_eq!(run("a b c", Mode::First, false, false), "a b c");
}

#[test]
fn all_is_not_line_then_block() {
    // A `//` inside a block comment is not a line comment, so `Line` alone
    // leaves the whole block in place.
    assert_eq!(run("/* // */", Mode::All, false, false), "");
    assert_eq!(run("/* // */", Mode::Line, false, false), "/* // */");
    assert_eq!(run("/* // */", Mode::Block, false, false), "");

    assert_eq!(run("// /*", Mode::All, false, false), "");
    assert_eq!(run("// /*", Mode::Block, false, false), "// /*");
    assert_eq!(run("// /*", Mode::Line, false, false), "");
}

#[test]
fn preserve_newlines_leaves_line_comments_alone() {
    // A line comment holds no terminator, so there is nothing to restore.
    assert_eq!(run("a // x\nb", Mode::All, true, false), "a \nb");
}

#[test]
fn preserve_newlines_emits_every_removed_terminator_in_order() {
    assert_eq!(
        run("a /* 1\n2\r\n3\r4 */ b", Mode::All, true, false),
        "a \n\r\n\r b"
    );
}

#[test]
fn preserve_newlines_holds_under_mode_block() {
    assert_eq!(
        run("a // x\nb /* y\nz */ c", Mode::Block, true, false),
        "a // x\nb \n c"
    );
}

#[test]
fn keep_bang_keeps_the_banner_and_removes_the_rest() {
    assert_eq!(
        run("/*! banner */ a /* drop */ b", Mode::All, false, true),
        "/*! banner */ a  b"
    );
    assert_eq!(
        run("//! banner\n// drop\n", Mode::All, false, true),
        "//! banner\n\n"
    );
}

#[test]
fn keep_bang_applies_to_every_language() {
    for (language, source, expected) in [
        (Language::Python, "#! keep\n# drop\n", "#! keep\n\n"),
        (Language::Sql, "--! keep\n-- drop\n", "--! keep\n\n"),
        (
            Language::Html,
            "<!--! keep --><!-- drop -->",
            "<!--! keep -->",
        ),
        (Language::Css, "/*! keep */ /* drop */", "/*! keep */ "),
    ] {
        let mut options = Options::new(language);
        options.keep_bang_comments = true;
        assert_eq!(strip(source, options).output, expected);
    }
}

#[test]
fn keep_bang_and_first_pick_the_first_removable_comment() {
    assert_eq!(
        run("/*! keep */ /* one */ /* two */", Mode::First, false, true),
        "/*! keep */  /* two */"
    );
}

#[test]
fn every_flag_composes_with_every_mode() {
    let source = "a /*! keep\n1 */ b /* drop\n2 */ c // tail\n";
    assert_eq!(
        run(source, Mode::First, true, true),
        "a /*! keep\n1 */ b \n c // tail\n"
    );
    assert_eq!(
        run(source, Mode::Block, true, true),
        "a /*! keep\n1 */ b \n c // tail\n"
    );
    assert_eq!(
        run(source, Mode::Line, true, true),
        "a /*! keep\n1 */ b /* drop\n2 */ c \n"
    );
    assert_eq!(
        run(source, Mode::All, true, true),
        "a /*! keep\n1 */ b \n c \n"
    );
}

#[test]
fn default_options_are_all_and_both_flags_off() {
    let options = Options::new(Language::JavaScript);
    assert_eq!(options.mode, Mode::default());
    assert_eq!(options.mode, Mode::All);
    assert!(!options.preserve_newlines);
    assert!(!options.keep_bang_comments);
}

#[test]
fn options_are_copied_not_moved() {
    let options = Options::new(Language::Go);
    assert_eq!(strip("a // b", options).output, "a ");
    assert_eq!(strip("c // d", options).output, "c ");
}