use core::fmt::Arguments;
use bun_alloc::Arena as Bump;
use bun_alloc::{ArenaVec as BumpVec, ArenaVecExt as _};
use bun_collections::ArrayHashMap;
use crate as css;
pub use crate::Error;
pub struct CssModule<'a> {
pub config: &'a Config,
pub sources: &'a Vec<Box<[u8]>>,
pub hashes: BumpVec<'a, &'a [u8]>,
pub exports_by_source_index: BumpVec<'a, CssModuleExports<'a>>,
pub references: &'a mut CssModuleReferences<'a>,
}
impl<'a> CssModule<'a> {
pub fn new(
bump: &'a Bump,
config: &'a Config,
sources: &'a Vec<Box<[u8]>>,
project_root: Option<&[u8]>,
references: &'a mut CssModuleReferences<'a>,
) -> CssModule<'a> {
let hashes = 'hashes: {
let mut hashes = BumpVec::with_capacity_in(sources.len(), bump);
for path in sources.iter() {
let mut alloced = false;
let source: &[u8] = 'source: {
if let Some(root) = project_root {
if bun_paths::is_absolute(root) {
alloced = true;
break 'source bump.alloc_slice_copy(
bun_paths::resolve_path::relative(root, path.as_ref()),
);
}
}
break 'source path.as_ref();
};
let _ = alloced;
hashes.push(hash(
bump,
format_args!("{}", bstr::BStr::new(source)),
matches!(config.pattern.segments.at(0), Segment::Hash),
));
}
break 'hashes hashes;
};
let exports_by_source_index = 'exports_by_source_index: {
let mut exports_by_source_index = BumpVec::with_capacity_in(sources.len(), bump);
for _ in 0..sources.len() {
exports_by_source_index.push(CssModuleExports::default());
}
break 'exports_by_source_index exports_by_source_index;
};
CssModule {
config,
sources,
references,
hashes,
exports_by_source_index,
}
}
pub fn get_reference(&mut self, bump: &'a Bump, name: &'a [u8], source_index: u32) {
use bun_collections::array_hash_map::MapEntry;
match self.exports_by_source_index[source_index as usize].entry(name) {
MapEntry::Occupied(mut o) => {
o.get_mut().is_referenced = true;
}
MapEntry::Vacant(v) => {
v.insert(CssModuleExport {
name: self.config.pattern.write_to_string(
bump,
BumpVec::new_in(bump),
self.hashes[source_index as usize],
self.sources[source_index as usize].as_ref(),
name,
),
composes: BumpVec::new_in(bump),
is_referenced: true,
});
}
}
}
pub fn reference_dashed(
&mut self,
bump: &'a Bump,
name: &'a [u8],
from: Option<css::css_properties::css_modules::Specifier>,
specifier_path: Option<&'a [u8]>,
source_index: u32,
) -> Option<&'a [u8]> {
use css::css_properties::css_modules::Specifier;
let (reference, key): (CssModuleReference<'a>, &'a [u8]) = match from {
Some(Specifier::Global) => return Some(&name[2..]),
Some(Specifier::ImportRecordIndex(_)) => {
let path = specifier_path
.expect("specifier_path required for Specifier::ImportRecordIndex");
(
CssModuleReference::Dependency {
name: &name[2..],
specifier: path,
},
path,
)
}
None => {
use bun_collections::array_hash_map::MapEntry;
match self.exports_by_source_index[source_index as usize].entry(name) {
MapEntry::Occupied(mut o) => {
o.get_mut().is_referenced = true;
}
MapEntry::Vacant(v) => {
let mut res = BumpVec::new_in(bump);
res.extend_from_slice(b"--");
v.insert(CssModuleExport {
name: self.config.pattern.write_to_string(
bump,
res,
self.hashes[source_index as usize],
self.sources[source_index as usize].as_ref(),
&name[2..],
),
composes: BumpVec::new_in(bump),
is_referenced: true,
});
}
}
return None;
}
};
let the_hash = hash(
bump,
format_args!(
"{}_{}_{}",
bstr::BStr::new(self.hashes[source_index as usize]),
bstr::BStr::new(name),
bstr::BStr::new(key)
),
false,
);
let mut k = BumpVec::with_capacity_in(2 + the_hash.len(), bump);
k.extend_from_slice(b"--");
k.extend_from_slice(the_hash);
let _ = self.references.put(k.into_bump_slice(), reference);
Some(the_hash)
}
pub fn handle_composes(
&mut self,
_dest: &mut css::Printer,
selectors: &css::selector::parser::SelectorList,
_composes: &css::css_properties::css_modules::Composes,
_source_index: u32,
) -> css::Maybe<(), css::PrinterErrorKind> {
for sel in selectors.v.slice() {
if sel.len() == 1
&& matches!(
sel.components[0],
css::selector::parser::Component::Class(_)
)
{
continue;
}
return Err(css::PrinterErrorKind::invalid_composes_selector);
}
Ok(())
}
pub fn add_dashed(&mut self, bump: &'a Bump, local: &'a [u8], source_index: u32) {
use bun_collections::array_hash_map::MapEntry;
if let MapEntry::Vacant(v) =
self.exports_by_source_index[source_index as usize].entry(local)
{
v.insert(CssModuleExport {
name: self.config.pattern.write_to_string_with_prefix(
bump,
b"--",
self.hashes[source_index as usize],
self.sources[source_index as usize].as_ref(),
&local[2..],
),
composes: BumpVec::new_in(bump),
is_referenced: false,
});
}
}
pub fn add_local(
&mut self,
bump: &'a Bump,
exported: &'a [u8],
local: &'a [u8],
source_index: u32,
) {
use bun_collections::array_hash_map::MapEntry;
if let MapEntry::Vacant(v) =
self.exports_by_source_index[source_index as usize].entry(exported)
{
v.insert(CssModuleExport {
name: self.config.pattern.write_to_string(
bump,
BumpVec::new_in(bump),
self.hashes[source_index as usize],
self.sources[source_index as usize].as_ref(),
local,
),
composes: BumpVec::new_in(bump),
is_referenced: false,
});
}
}
}
pub struct Config {
pub pattern: Pattern,
pub dashed_idents: bool,
pub animation: bool,
pub grid: bool,
pub custom_idents: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
pattern: Pattern::default(),
dashed_idents: false,
animation: true,
grid: true,
custom_idents: true,
}
}
}
pub struct Pattern {
pub segments: crate::SmallList<Segment, 3>,
}
impl Default for Pattern {
fn default() -> Self {
Self {
segments: crate::SmallList::init_inlined(&[
Segment::Local,
Segment::Literal(b"_"),
Segment::Hash,
]),
}
}
}
impl Pattern {
pub fn write(
&self,
hash_: &[u8],
path: &[u8],
local: &[u8],
mut writefn: impl FnMut(&[u8], /* replace_dots: */ bool),
) {
for segment in self.segments.slice() {
match segment {
Segment::Literal(s) => {
writefn(s, false);
}
Segment::Name => {
let stem = bun_paths::stem(path);
if bun_core::index_of(stem, b".").is_some() {
writefn(stem, true);
} else {
writefn(stem, false);
}
}
Segment::Local => {
writefn(local, false);
}
Segment::Hash => {
writefn(hash_, false);
}
}
}
}
pub fn write_to_string_with_prefix<'a>(
&self,
bump: &'a Bump,
prefix: &'static [u8],
hash_: &[u8],
path: &[u8],
local: &[u8],
) -> &'a [u8] {
let mut res: BumpVec<'a, u8> = BumpVec::new_in(bump);
self.write(hash_, path, local, |slice: &[u8], replace_dots: bool| {
res.extend_from_slice(prefix);
if replace_dots {
let start = res.len();
res.extend_from_slice(slice);
let end = res.len();
for c in &mut res[start..end] {
if *c == b'.' {
*c = b'-';
}
}
return;
}
res.extend_from_slice(slice);
});
res.into_bump_slice()
}
pub fn write_to_string<'a>(
&self,
_bump: &'a Bump,
res_: BumpVec<'a, u8>,
hash_: &[u8],
path: &[u8],
local: &[u8],
) -> &'a [u8] {
let mut res = res_;
self.write(hash_, path, local, |slice: &[u8], replace_dots: bool| {
if replace_dots {
let start = res.len();
res.extend_from_slice(slice);
let end = res.len();
for c in &mut res[start..end] {
if *c == b'.' {
*c = b'-';
}
}
return;
}
res.extend_from_slice(slice);
});
res.into_bump_slice()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Segment {
Literal(&'static [u8]),
Name,
Local,
Hash,
}
pub type CssModuleExports<'a> = ArrayHashMap<&'a [u8], CssModuleExport<'a>>;
pub type CssModuleReferences<'a> = ArrayHashMap<&'a [u8], CssModuleReference<'a>>;
pub struct CssModuleExport<'a> {
pub name: &'a [u8],
pub composes: BumpVec<'a, CssModuleReference<'a>>,
pub is_referenced: bool,
}
pub enum CssModuleReference<'a> {
Local {
name: &'a [u8],
},
Global {
name: &'a [u8],
},
Dependency {
name: &'a [u8],
specifier: &'a [u8],
},
}
impl<'a> CssModuleReference<'a> {
pub fn eql(&self, other: &Self) -> bool {
match (self, other) {
(Self::Local { name: a }, Self::Local { name: b }) => a == b,
(Self::Global { name: a }, Self::Global { name: b }) => a == b,
(
Self::Dependency {
name: an,
specifier: asp,
},
Self::Dependency {
name: bn,
specifier: bsp,
},
) => an == bn && asp == bsp,
_ => false,
}
}
}
#[inline]
pub fn hash<'a>(bump: &'a Bump, args: Arguments<'_>, at_start: bool) -> &'a [u8] {
bun_base64::wyhash_url_safe(bump, args, at_start)
}