kcode-k1-web-code-document 0.1.0

Represent and chunk one authored K1 Web code document
Documentation
use kcode_k1_transaction_id::TxId;
use kcode_k1_web_code_document::{CodeDocument, Language};
use kcode_k1_web_package::{AuthorityId, SourceFile, SourcePackage, WebFamily, WebId};
use semver::Version;

fn id() -> WebId {
    WebId::new(
        WebFamily::new(AuthorityId::new(TxId::from_bytes([1; 12])), "alpha").unwrap(),
        Version::new(1, 2, 3),
    )
    .unwrap()
}
fn docs(dependencies: &str) -> Vec<u8> {
    format!("<!-- k1-web/v1\n{{\"dependencies\":{dependencies}}}\n-->\nBody\r\n").into_bytes()
}
fn document(language: Language, code: &str) -> CodeDocument {
    CodeDocument::new(id(), docs("[]"), language, code.as_bytes().to_vec()).unwrap()
}
fn files(source: &SourcePackage) -> Vec<&str> {
    source.files().iter().map(|f| f.path()).collect()
}
fn joined(value: &CodeDocument) -> Vec<u8> {
    value
        .chunk_ranges()
        .into_iter()
        .flat_map(|r| value.code()[r].iter().copied())
        .collect()
}

#[test]
fn exact_runtime_shapes() {
    let cases = [
        (
            Language::JavaScript,
            "export async function runTests(){}",
            vec!["Code.js", "Documentation.md", "k1-web.json"],
            "Code.js",
        ),
        (
            Language::Html,
            "<!doctype html><script>globalThis.runTests=async()=>{};</script>",
            vec![
                "Code.html",
                "Documentation.md",
                "k1-entry.js",
                "k1-tests.js",
                "k1-web.json",
            ],
            "k1-entry.js",
        ),
        (
            Language::Css,
            "body{color:red}",
            vec![
                "Code.css",
                "Documentation.md",
                "k1-entry.js",
                "k1-tests.js",
                "k1-web.json",
            ],
            "k1-entry.js",
        ),
    ];
    for (language, code, paths, entry) in cases {
        let value = document(language, code);
        let source = value.to_source_package().unwrap();
        assert_eq!(files(&source), paths);
        assert_eq!(source.entry(), entry);
        assert_eq!(
            source
                .files()
                .iter()
                .find(|f| f.path() == language.code_path())
                .unwrap()
                .bytes(),
            code.as_bytes()
        );
        assert_eq!(CodeDocument::from_source_package(&source).unwrap(), value);
    }
}

#[test]
fn header_is_strict() {
    let authority = AuthorityId::new(TxId::from_bytes([2; 12])).to_string();
    let dep = format!(r#"[{{"authority":"{authority}","name":"beta","selector":"1"}}]"#);
    assert_eq!(
        CodeDocument::new(id(), docs(&dep), Language::Css, b"a{}".to_vec())
            .unwrap()
            .dependencies()[0]
            .name(),
        "beta"
    );
    let invalid = [
        " {}",
        "{\"dependencies\":[],\"extra\":0}",
        "{\"dependencies\":[{\"authority\":\"\",\"name\":\"a\",\"selector\":\"*\"}]}",
        "{\"dependencies\":[{\"authority\":\"bad\",\"name\":\"a\",\"selector\":\"*\"}]}",
    ];
    for json in invalid {
        let text = format!("<!-- k1-web/v1\n{json}\n-->\n");
        assert!(
            CodeDocument::new(id(), text.into_bytes(), Language::Css, b"a{}".to_vec()).is_err(),
            "{json}"
        );
    }
    let duplicate = format!("[{0},{0}]", &dep[1..dep.len() - 1]);
    assert!(CodeDocument::new(id(), docs(&duplicate), Language::Css, b"a{{}}".to_vec()).is_err());
}

#[test]
fn recovery_rejects_extra_or_changed_projection() {
    let value = document(Language::Html, "<p>ok</p>");
    let source = value.to_source_package().unwrap();
    let mut extra = source.files().to_vec();
    extra.push(SourceFile::new("extra.bin", vec![1]));
    let extra = SourcePackage::new(id(), extra).unwrap();
    assert!(CodeDocument::from_source_package(&extra).is_err());
    let changed = source
        .files()
        .iter()
        .map(|f| {
            if f.path() == "k1-entry.js" {
                SourceFile::new(f.path(), b"export {};".to_vec())
            } else {
                f.clone()
            }
        })
        .collect();
    let changed = SourcePackage::new(id(), changed).unwrap();
    assert!(CodeDocument::from_source_package(&changed).is_err());
}

#[test]
fn chunks_preserve_bom_crlf_and_merge_short_tail() {
    let code = format!(
        "\u{feff}const a=`{}`;\r\nconst b=/{}/;\r\nconst c='{}';\r\n",
        "x".repeat(850),
        "y".repeat(850),
        "z".repeat(100)
    );
    let value = document(Language::JavaScript, &code);
    let ranges = value.chunk_ranges();
    assert_eq!(joined(&value), code.as_bytes());
    assert_eq!(ranges.len(), 2);
    assert!(ranges[1].clone().count() > 800);
    assert_eq!(value.code().last(), Some(&b'\n'));
}

#[test]
fn javascript_safety_and_large_construct() {
    let unit = "/** docs */\nexport class A{m(){const x=`a${/x[;}]/.test('x') ? `{}` : '//'};return x;} n=1;}\n";
    let value = document(Language::JavaScript, &unit.repeat(30));
    assert_eq!(joined(&value), value.code());
    assert!(value.chunk_ranges().len() > 1);
    let large = document(
        Language::JavaScript,
        &format!("const value=\"{}\";", "x".repeat(2000)),
    );
    assert_eq!(large.chunk_ranges().len(), 1);
    assert_eq!(
        document(Language::JavaScript, "const x='unterminated")
            .chunk_ranges()
            .len(),
        1
    );
}

#[test]
fn css_and_html_safety() {
    let css = "@media screen{.a{background:url(\"data:x;y\");/* } */color:red}.b{content:'}';}}\n"
        .repeat(30);
    let value = document(Language::Css, &css);
    assert_eq!(joined(&value), value.code());
    assert!(value.chunk_ranges().len() > 1);
    let html = "<!--x--><section data-x='>&amp;'><p>text &amp; more</p><script>const x=`<b>${/x/.test('x')}</b>`;</script><style>.a{background:url('x;y')}</style></section>".repeat(20);
    let value = document(Language::Html, &html);
    assert_eq!(joined(&value), value.code());
    assert!(value.chunk_ranges().len() > 1);
    for bad in ["a{", "<div>"] {
        let language = if bad.starts_with('<') {
            Language::Html
        } else {
            Language::Css
        };
        assert_eq!(document(language, bad).chunk_ranges().len(), 1);
    }
}

#[test]
fn empty_and_small_are_single_ranges() {
    let empty = document(Language::Css, "").chunk_ranges();
    assert_eq!(empty.len(), 1);
    assert_eq!(empty[0], 0..0);
    let small = document(Language::Html, "<p>small</p>").chunk_ranges();
    assert_eq!(small.len(), 1);
    assert_eq!(small[0], 0..12);
}