vize_lightningcss 0.508.0

Vize fork of LightningCSS with bounded parser repairs and validated source-map archives
Documentation
//! Complete published alpha72 remapping oracle plus an authored physical @import contract.
use lightningcss::{
  bundler::{Bundler, FileProvider, ResolveResult, SourceProvider},
  printer::PrinterOptions,
  stylesheet::{MinifyOptions, ParserOptions},
};
use parcel_sourcemap::{Mapping, OriginalLocation, SourceMap};
use std::{
  collections::HashMap,
  fs, io,
  path::{Path, PathBuf},
};

const ORIGINAL_IMPORTED_CSS: &str = r###".imported {
      content: "yay, file support!";
    }

    .selector {
      margin: 1em;
      background-color: #f60;
    }

    .selector .nested {
      margin: 0.5em;
    }

    /*# sourceMappingURL=data:application/json;base64,ewoJInZlcnNpb24iOiAzLAoJInNvdXJjZVJvb3QiOiAicm9vdCIsCgkiZmlsZSI6ICJzdGRvdXQiLAoJInNvdXJjZXMiOiBbCgkJInN0ZGluIiwKCQkic2Fzcy9fdmFyaWFibGVzLnNjc3MiLAoJCSJzYXNzL19kZW1vLnNjc3MiCgldLAoJInNvdXJjZXNDb250ZW50IjogWwoJCSJAaW1wb3J0IFwiX3ZhcmlhYmxlc1wiO1xuQGltcG9ydCBcIl9kZW1vXCI7XG5cbi5zZWxlY3RvciB7XG4gIG1hcmdpbjogJHNpemU7XG4gIGJhY2tncm91bmQtY29sb3I6ICRicmFuZENvbG9yO1xuXG4gIC5uZXN0ZWQge1xuICAgIG1hcmdpbjogJHNpemUgLyAyO1xuICB9XG59IiwKCQkiJGJyYW5kQ29sb3I6ICNmNjA7XG4kc2l6ZTogMWVtOyIsCgkJIi5pbXBvcnRlZCB7XG4gIGNvbnRlbnQ6IFwieWF5LCBmaWxlIHN1cHBvcnQhXCI7XG59IgoJXSwKCSJtYXBwaW5ncyI6ICJBRUFBLFNBQVMsQ0FBQztFQUNSLE9BQU8sRUFBRSxvQkFBcUI7Q0FDL0I7O0FGQ0QsU0FBUyxDQUFDO0VBQ1IsTUFBTSxFQ0hELEdBQUc7RURJUixnQkFBZ0IsRUNMTCxJQUFJO0NEVWhCOztBQVBELFNBQVMsQ0FJUCxPQUFPLENBQUM7RUFDTixNQUFNLEVDUEgsS0FBRztDRFFQIiwKCSJuYW1lcyI6IFtdCn0= */"###;

const ORIGINAL_ENTRY_CSS: &str = r###"
        @import "/b.css";
        .a { color: red; }
      "###;

const ORIGINAL_EXPECTED_MAP: &str = r###"{"version":3,"sourceRoot":null,"mappings":"ACAA,uCCGA,2CAAA,8BFDQ","sources":["a.css","sass/_demo.scss","stdin"],"sourcesContent":["\n        @import \"/b.css\";\n        .a { color: red; }\n      ",".imported {\n  content: \"yay, file support!\";\n}","@import \"_variables\";\n@import \"_demo\";\n\n.selector {\n  margin: $size;\n  background-color: $brandColor;\n\n  .nested {\n    margin: $size / 2;\n  }\n}"],"names":[]}"###;

struct WholeFixtureProvider {
  files: HashMap<PathBuf, String>,
}
impl SourceProvider for WholeFixtureProvider {
  type Error = io::Error;
  fn read<'a>(&'a self, file: &Path) -> Result<&'a str, io::Error> {
    self
      .files
      .get(file)
      .map(String::as_str)
      .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, format!("fixture {}", file.display())))
  }
  fn resolve(&self, specifier: &str, originating_file: &Path) -> Result<ResolveResult, io::Error> {
    Ok(originating_file.with_file_name(specifier).into())
  }
}

#[test]
fn complete_original_input_map_remapping() {
  let provider = WholeFixtureProvider {
    files: [
      (PathBuf::from("/a.css"), ORIGINAL_ENTRY_CSS.to_owned()),
      (PathBuf::from("/b.css"), ORIGINAL_IMPORTED_CSS.to_owned()),
    ]
    .iter()
    .cloned()
    .collect(),
  };
  let mut map = SourceMap::new("/");
  let mut bundler = Bundler::new(&provider, Some(&mut map), ParserOptions::default());
  let mut stylesheet = bundler.bundle(Path::new("/a.css")).unwrap();
  stylesheet.minify(MinifyOptions::default()).unwrap();
  let result = stylesheet
    .to_css(PrinterOptions {
      source_map: Some(&mut map),
      minify: true,
      ..Default::default()
    })
    .unwrap();
  assert!(!result.code.is_empty());
  // The full pre-existing published fixture expected string is immutable;
  // no candidate-generated snapshot or selected map field replaces it.
  assert_eq!(map.to_json(None).unwrap(), ORIGINAL_EXPECTED_MAP);
  let mut decoded = SourceMap::from_json("/", ORIGINAL_EXPECTED_MAP).unwrap();
  assert_eq!(decoded.to_json(None).unwrap(), ORIGINAL_EXPECTED_MAP);
  assert_eq!(decoded.get_sources(), map.get_sources());
  assert_eq!(decoded.get_sources_content(), map.get_sources_content());
  assert_eq!(decoded.get_names(), map.get_names());
  assert_eq!(decoded.get_mappings(), map.get_mappings());
}

#[test]
fn complete_physical_import_code_and_map() {
  let directory = assert_fs::TempDir::new().unwrap();
  let entry = "@import \"b.css\";\n.a { opacity: 1 }\n";
  let imported = ".b { opacity: .5 }\n";
  fs::write(directory.path().join("a.css"), entry).unwrap();
  fs::write(directory.path().join("b.css"), imported).unwrap();
  let mut map = SourceMap::new(directory.path().to_str().unwrap());
  let provider = FileProvider::new();
  let mut bundler = Bundler::new(&provider, Some(&mut map), ParserOptions::default());
  let mut stylesheet = bundler.bundle(&directory.path().join("a.css")).unwrap();
  stylesheet.minify(MinifyOptions::default()).unwrap();
  let result = stylesheet
    .to_css(PrinterOptions {
      source_map: Some(&mut map),
      minify: true,
      ..Default::default()
    })
    .unwrap();
  assert_eq!(result.code, ".b{opacity:.5}.a{opacity:1}");
  assert!(result.exports.is_none());
  assert!(result.references.is_none());
  assert!(result.dependencies.is_none());
  assert_eq!(map.get_sources(), &vec!["a.css".to_owned(), "b.css".to_owned()]);
  assert_eq!(map.get_sources_content(), &vec![entry.to_owned(), imported.to_owned()]);
  assert!(map.get_names().is_empty());
  assert_eq!(
    map.get_mappings(),
    vec![
      Mapping {
        generated_line: 0,
        generated_column: 0,
        original: Some(OriginalLocation::new(0, 0, 1, None))
      },
      Mapping {
        generated_line: 0,
        generated_column: 14,
        original: Some(OriginalLocation::new(1, 0, 0, None))
      },
    ]
  );
  let expected = serde_json::json!({
      "version": 3, "sourceRoot": null, "mappings": "ACAA,cDCA",
      "sources": ["a.css", "b.css"], "sourcesContent": [entry, imported], "names": []
  });
  assert_eq!(
    serde_json::from_str::<serde_json::Value>(&map.to_json(None).unwrap()).unwrap(),
    expected
  );
  // Reusing the actual map APIs must preserve every decoded location/content field.
  let mut decoded = SourceMap::from_json(directory.path().to_str().unwrap(), &map.to_json(None).unwrap()).unwrap();
  assert_eq!(decoded.get_mappings(), map.get_mappings());
  assert_eq!(decoded.to_json(None).unwrap(), map.to_json(None).unwrap());
}