Skip to main content

native/oxc_ex_nif/src/options.rs

use std::collections::BTreeMap;

use rustler::{types::map::MapIterator, ListIterator, Term};

use crate::atoms;

fn get_key<'a>(term: Term<'a>, atom_key: rustler::Atom, _string_key: &str) -> Option<Term<'a>> {
    term.map_get(atom_key).ok()
}

fn get_bool(term: Term<'_>, atom_key: rustler::Atom, string_key: &str) -> Option<bool> {
    get_key(term, atom_key, string_key)?.decode::<bool>().ok()
}

fn get_string(term: Term<'_>, atom_key: rustler::Atom, string_key: &str) -> Option<String> {
    string_from_term(get_key(term, atom_key, string_key)?)
}

fn get_optional_string(
    term: Term<'_>,
    atom_key: rustler::Atom,
    string_key: &str,
) -> Option<Option<String>> {
    let value = get_key(term, atom_key, string_key)?;

    if is_nil(value) {
        Some(None)
    } else {
        string_from_term(value).map(Some)
    }
}

fn get_string_list(
    term: Term<'_>,
    atom_key: rustler::Atom,
    string_key: &str,
) -> Option<Vec<String>> {
    let value = get_key(term, atom_key, string_key)?;
    let iter = value.decode::<ListIterator>().ok()?;

    let mut strings = Vec::new();
    for item in iter {
        strings.push(string_from_term(item)?);
    }
    Some(strings)
}

fn get_term_list<'a>(
    term: Term<'a>,
    atom_key: rustler::Atom,
    string_key: &str,
) -> Option<Vec<Term<'a>>> {
    let value = get_key(term, atom_key, string_key)?;
    let iter = value.decode::<ListIterator>().ok()?;
    Some(iter.collect())
}

fn get_string_map(
    term: Term<'_>,
    atom_key: rustler::Atom,
    string_key: &str,
) -> Option<BTreeMap<String, String>> {
    let value = get_key(term, atom_key, string_key)?;
    let iter = MapIterator::new(value)?;

    let mut map = BTreeMap::new();
    for (key, value) in iter {
        map.insert(string_from_term(key)?, string_from_term(value)?);
    }
    Some(map)
}

fn string_from_term(term: Term<'_>) -> Option<String> {
    term.decode::<String>()
        .ok()
        .or_else(|| term.atom_to_string().ok())
}

fn is_nil(term: Term<'_>) -> bool {
    term.is_atom() && term.atom_to_string().ok().as_deref() == Some("nil")
}

pub fn default_jsx_runtime() -> String {
    "automatic".to_string()
}

pub fn default_format() -> String {
    "iife".to_string()
}

pub struct TransformInput {
    pub jsx_runtime: String,
    pub jsx_factory: String,
    pub jsx_fragment: String,
    pub import_source: String,
    pub target: String,
    pub sourcemap: bool,
}

impl Default for TransformInput {
    fn default() -> Self {
        Self {
            jsx_runtime: default_jsx_runtime(),
            jsx_factory: String::new(),
            jsx_fragment: String::new(),
            import_source: String::new(),
            target: String::new(),
            sourcemap: false,
        }
    }
}

impl TransformInput {
    pub fn from_term(term: Term<'_>) -> Self {
        let mut opts = Self::default();

        if let Some(value) = get_string(term, atoms::jsx(), "jsx") {
            opts.jsx_runtime = value;
        }
        if let Some(value) = get_string(term, atoms::jsx_factory(), "jsx_factory") {
            opts.jsx_factory = value;
        }
        if let Some(value) = get_string(term, atoms::jsx_fragment(), "jsx_fragment") {
            opts.jsx_fragment = value;
        }
        if let Some(value) = get_string(term, atoms::import_source(), "import_source") {
            opts.import_source = value;
        }
        if let Some(value) = get_string(term, atoms::target(), "target") {
            opts.target = value;
        }
        if let Some(value) = get_bool(term, atoms::sourcemap(), "sourcemap") {
            opts.sourcemap = value;
        }

        opts
    }
}

pub struct MinifyInput {
    pub mangle: bool,
}

impl Default for MinifyInput {
    fn default() -> Self {
        Self { mangle: true }
    }
}

impl MinifyInput {
    pub fn from_term(term: Term<'_>) -> Self {
        let mut opts = Self::default();

        if let Some(value) = get_bool(term, atoms::mangle(), "mangle") {
            opts.mangle = value;
        }

        opts
    }
}

pub struct BundleFile<'a> {
    pub path: String,
    pub source: Term<'a>,
}

impl<'a> BundleFile<'a> {
    pub fn from_term(term: Term<'a>) -> Option<Self> {
        Some(Self {
            path: get_string(term, atoms::path(), "path")?,
            source: get_key(term, atoms::source(), "source").filter(|term| !is_nil(*term))?,
        })
    }
}

#[derive(Default)]
pub struct BundleEntry<'a> {
    pub name: Option<String>,
    pub import: String,
    pub source: Option<Term<'a>>,
}

impl<'a> BundleEntry<'a> {
    pub fn from_term(term: Term<'a>) -> Option<Self> {
        let import = get_string(term, atoms::import(), "import")?;
        let name = get_optional_string(term, atoms::name(), "name").flatten();
        let source = get_key(term, atoms::source(), "source").filter(|term| !is_nil(*term));

        Some(Self {
            name,
            import,
            source,
        })
    }
}

#[derive(Default)]
pub struct BundleOptions<'a> {
    pub entries: Vec<BundleEntry<'a>>,
    pub files: Vec<BundleFile<'a>>,
    pub entry: String,
    pub cwd: String,
    pub outdir: Option<String>,
    pub format: String,
    pub exports: String,
    pub minify: bool,
    pub treeshake: bool,
    pub banner: Option<String>,
    pub footer: Option<String>,
    pub preamble: Option<String>,
    pub define: BTreeMap<String, String>,
    pub module_types: BTreeMap<String, String>,
    pub external: Vec<String>,
    pub preserve_entry_signatures: String,
    pub conditions: Vec<String>,
    pub main_fields: Vec<String>,
    pub modules: Vec<String>,
    pub sourcemap: bool,
    pub drop_console: bool,
    pub jsx_runtime: String,
    pub jsx_factory: String,
    pub jsx_fragment: String,
    pub import_source: String,
    pub target: String,
    pub entry_file_names: Option<String>,
    pub chunk_file_names: Option<String>,
    pub asset_file_names: Option<String>,
}

impl<'a> BundleOptions<'a> {
    pub fn from_term(term: Term<'a>) -> Self {
        let mut opts = Self {
            format: default_format(),
            jsx_runtime: default_jsx_runtime(),
            ..Self::default()
        };

        if let Some(value) = get_term_list(term, atoms::entries(), "entries") {
            opts.entries = value
                .into_iter()
                .filter_map(BundleEntry::from_term)
                .collect();
        }
        if let Some(value) = get_term_list(term, atoms::files(), "files") {
            opts.files = value
                .into_iter()
                .filter_map(BundleFile::from_term)
                .collect();
        }
        if let Some(value) = get_string(term, atoms::entry(), "entry") {
            opts.entry = value;
        }
        if let Some(value) = get_string(term, atoms::cwd(), "cwd") {
            opts.cwd = value;
        }
        if let Some(value) = get_optional_string(term, atoms::outdir(), "outdir") {
            opts.outdir = value;
        }
        if let Some(value) = get_string(term, atoms::format(), "format") {
            opts.format = value;
        }
        if let Some(value) = get_string(term, atoms::exports(), "exports") {
            opts.exports = value;
        }
        if let Some(value) = get_bool(term, atoms::minify(), "minify") {
            opts.minify = value;
        }
        if let Some(value) = get_bool(term, atoms::treeshake(), "treeshake") {
            opts.treeshake = value;
        }
        if let Some(value) = get_optional_string(term, atoms::banner(), "banner") {
            opts.banner = value;
        }
        if let Some(value) = get_optional_string(term, atoms::footer(), "footer") {
            opts.footer = value;
        }
        if let Some(value) = get_optional_string(term, atoms::preamble(), "preamble") {
            opts.preamble = value;
        }
        if let Some(value) = get_string_map(term, atoms::define(), "define") {
            opts.define = value;
        }
        if let Some(value) = get_string_map(term, atoms::module_types(), "module_types") {
            opts.module_types = value;
        }
        if let Some(value) = get_string_list(term, atoms::external(), "external") {
            opts.external = value;
        }
        if let Some(value) = get_string(
            term,
            atoms::preserve_entry_signatures(),
            "preserve_entry_signatures",
        ) {
            opts.preserve_entry_signatures = value;
        }
        if let Some(value) = get_string_list(term, atoms::conditions(), "conditions") {
            opts.conditions = value;
        }
        if let Some(value) = get_string_list(term, atoms::main_fields(), "main_fields") {
            opts.main_fields = value;
        }
        if let Some(value) = get_string_list(term, atoms::modules(), "modules") {
            opts.modules = value;
        }
        if let Some(value) = get_bool(term, atoms::sourcemap(), "sourcemap") {
            opts.sourcemap = value;
        }
        if let Some(value) = get_bool(term, atoms::drop_console(), "drop_console") {
            opts.drop_console = value;
        }
        if let Some(value) = get_string(term, atoms::jsx(), "jsx") {
            opts.jsx_runtime = value;
        }
        if let Some(value) = get_string(term, atoms::jsx_factory(), "jsx_factory") {
            opts.jsx_factory = value;
        }
        if let Some(value) = get_string(term, atoms::jsx_fragment(), "jsx_fragment") {
            opts.jsx_fragment = value;
        }
        if let Some(value) = get_string(term, atoms::import_source(), "import_source") {
            opts.import_source = value;
        }
        if let Some(value) = get_string(term, atoms::target(), "target") {
            opts.target = value;
        }
        if let Some(value) =
            get_optional_string(term, atoms::entry_file_names(), "entry_file_names")
        {
            opts.entry_file_names = value;
        }
        if let Some(value) =
            get_optional_string(term, atoms::chunk_file_names(), "chunk_file_names")
        {
            opts.chunk_file_names = value;
        }
        if let Some(value) =
            get_optional_string(term, atoms::asset_file_names(), "asset_file_names")
        {
            opts.asset_file_names = value;
        }

        opts
    }
}