vize_lightningcss 0.526.0

Vize fork of LightningCSS with bounded parser repairs and validated source-map archives
Documentation
use lightningcss::bundler::{ResolveResult, SourceProvider};
use serde_json::{json, Value};
use std::{
  collections::HashMap,
  fs::{File, OpenOptions},
  io::{self, Write},
  path::{Path, PathBuf},
  sync::{Condvar, Mutex},
  time::Duration,
};

struct State {
  events: File,
  sequence: usize,
  read_gate: bool,
}

pub struct Provider {
  files: HashMap<PathBuf, String>,
  aliases: HashMap<String, PathBuf>,
  entry: String,
  first: Option<String>,
  read_path: Option<PathBuf>,
  threads: usize,
  state: Mutex<State>,
  wake: Condvar,
}

impl Provider {
  pub fn new(case: &Value, threads: usize, events: PathBuf) -> Self {
    Self {
      files: case["files"]
        .as_object()
        .unwrap()
        .iter()
        .map(|(name, source)| (PathBuf::from(name), source.as_str().unwrap().to_owned()))
        .collect(),
      aliases: case["aliases"]
        .as_object()
        .unwrap()
        .iter()
        .map(|(name, path)| (name.clone(), PathBuf::from(path.as_str().unwrap())))
        .collect(),
      entry: case["entry"].as_str().unwrap().to_owned(),
      first: case["forceReadBeforeResolve"]["specifier"].as_str().map(str::to_owned),
      read_path: case["forceReadBeforeResolve"]["path"].as_str().map(PathBuf::from),
      threads,
      state: Mutex::new(State {
        events: OpenOptions::new().create_new(true).write(true).open(events).unwrap(),
        sequence: 0,
        read_gate: false,
      }),
      wake: Condvar::new(),
    }
  }

  fn event(state: &mut State, value: Value) {
    let value = json!({"sequence": state.sequence, "event": value});
    serde_json::to_writer(&mut state.events, &value).unwrap();
    writeln!(state.events).unwrap();
    state.events.flush().unwrap();
    state.sequence += 1;
  }
}

impl SourceProvider for Provider {
  type Error = io::Error;

  fn read<'a>(&'a self, file: &Path) -> Result<&'a str, Self::Error> {
    {
      let mut state = self.state.lock().unwrap();
      Self::event(&mut state, json!({"type": "read", "path": file}));
      if self.read_path.as_deref() == Some(file) {
        state.read_gate = true;
        self.wake.notify_all();
      }
    }
    self
      .files
      .get(file)
      .map(String::as_str)
      .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, format!("fixture missing {}", file.display())))
  }

  fn resolve(&self, specifier: &str, originating_file: &Path) -> Result<ResolveResult, Self::Error> {
    let mut state = self.state.lock().unwrap();
    Self::event(
      &mut state,
      json!({"type": "resolve-start", "specifier": specifier, "origin": originating_file}),
    );
    let gated =
      self.threads > 1 && originating_file == Path::new(&self.entry) && self.first.as_deref() == Some(specifier);
    if gated {
      let (next, timeout) = self
        .wake
        .wait_timeout_while(state, Duration::from_secs(3), |state| !state.read_gate)
        .unwrap();
      state = next;
      if timeout.timed_out() && !state.read_gate {
        Self::event(&mut state, json!({"type": "gate-timeout", "specifier": specifier}));
        return Err(io::Error::new(
          io::ErrorKind::TimedOut,
          "later provider.read did not precede first resolve",
        ));
      }
    }
    Self::event(
      &mut state,
      json!({"type": "resolve-finish", "specifier": specifier, "origin": originating_file, "gated": gated}),
    );
    if specifier.starts_with("https:") {
      return Ok(ResolveResult::External(specifier.to_owned()));
    }
    Ok(
      self
        .aliases
        .get(specifier)
        .cloned()
        .unwrap_or_else(|| originating_file.with_file_name(specifier))
        .into(),
    )
  }
}