mod arena;
mod ast;
mod ast_writer;
mod builtins;
mod deprecation;
mod diag;
mod emit;
mod error;
mod eval;
mod fxhash;
mod host_fn;
mod importer;
mod musl_math;
mod parser;
mod ryu;
mod sass_parser;
mod scanner;
mod selector;
mod sourcemap;
mod value;
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub use arena::{set_arena_bytes, ScopedAlloc};
pub use error::Error;
pub use host_fn::{host_value_op, HostFunction};
pub use importer::{
CanonicalUrl, CanonicalizeContext, DependencySet, FsImporter, Importer, ImporterError, ImporterResult,
};
pub use sourcemap::SourceMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum OutputStyle {
#[default]
Expanded,
Compressed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Syntax {
#[default]
Scss,
Sass,
Css,
}
pub struct Options<'a> {
pub style: OutputStyle,
pub syntax: Syntax,
pub importer: Option<&'a dyn Importer>,
pub url: Option<&'a str>,
pub unicode: bool,
pub source_map_include_sources: bool,
pub(crate) functions: Vec<host_fn::HostFn>,
pub(crate) warn: Option<WarnHandler>,
pub(crate) quiet_deps: Option<DependencySet>,
pub(crate) silenced_deprecations: Vec<String>,
pub charset: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WarnKind {
Warn,
Debug,
}
#[non_exhaustive]
pub struct WarnEvent<'a> {
pub kind: WarnKind,
pub deprecation: bool,
pub deprecation_id: &'a str,
pub message: &'a str,
pub formatted: &'a str,
pub url: &'a str,
pub line: usize,
pub path: &'a str,
}
pub type WarnHandler = std::rc::Rc<dyn Fn(&WarnEvent<'_>)>;
impl Default for Options<'_> {
fn default() -> Self {
Options {
style: OutputStyle::default(),
syntax: Syntax::default(),
importer: None,
url: None,
unicode: true,
source_map_include_sources: false,
functions: Vec::new(),
warn: None,
quiet_deps: None,
silenced_deprecations: Vec::new(),
charset: true,
}
}
}
impl<'a> Options<'a> {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_style(mut self, style: OutputStyle) -> Self {
self.style = style;
self
}
#[must_use]
pub fn with_syntax(mut self, syntax: Syntax) -> Self {
self.syntax = syntax;
self
}
#[must_use]
pub fn with_importer(mut self, importer: &'a dyn Importer) -> Self {
self.importer = Some(importer);
self
}
#[must_use]
pub fn with_url(mut self, url: &'a str) -> Self {
self.url = Some(url);
self
}
#[must_use]
pub fn with_unicode(mut self, unicode: bool) -> Self {
self.unicode = unicode;
self
}
#[must_use]
pub fn with_source_map_include_sources(mut self, include: bool) -> Self {
self.source_map_include_sources = include;
self
}
#[must_use]
pub fn with_function(mut self, signature: &str, callback: host_fn::HostFunction) -> Self {
let (name, params) = host_fn::parse_signature(signature);
self.functions.push(host_fn::HostFn {
name,
params,
callback,
});
self
}
#[must_use]
pub fn with_warn_handler(mut self, handler: WarnHandler) -> Self {
self.warn = Some(handler);
self
}
#[must_use]
pub fn with_quiet_deps(mut self, deps: DependencySet) -> Self {
self.quiet_deps = Some(deps);
self
}
#[must_use]
pub fn with_silenced_deprecations<I, S>(mut self, ids: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.silenced_deprecations = ids.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub fn with_charset(mut self, charset: bool) -> Self {
self.charset = charset;
self
}
}
pub fn compile(source: &str, options: &Options<'_>) -> Result<String, Error> {
let guard = arena::Scope::enter();
let result = compile_inner(source, options);
let outermost = arena::leave_no_reset();
let owned = result.clone();
drop(result);
if outermost {
arena::reset();
}
std::mem::forget(guard);
owned
}
#[derive(Clone, Debug)]
pub struct CompileResult {
pub css: String,
pub source_map: SourceMap,
}
pub fn compile_with_source_map(source: &str, options: &Options<'_>) -> Result<CompileResult, Error> {
let guard = arena::Scope::enter();
let result = compile_inner_sm(source, options);
let outermost = arena::leave_no_reset();
let owned = result.clone();
drop(result);
if outermost {
arena::reset();
}
std::mem::forget(guard);
owned
}
fn basename(url: &str) -> &str {
url.rsplit('/').next().unwrap_or(url)
}
fn compile_inner_sm(source: &str, options: &Options<'_>) -> Result<CompileResult, Error> {
let _dep_scope = options.quiet_deps.as_ref().map(|d| d.enter_compile());
let glyphs = if options.unicode {
diag::GlyphSet::Unicode
} else {
diag::GlyphSet::Ascii
};
let sheet = match options.syntax {
Syntax::Scss => parser::parse(source),
Syntax::Css => parser::parse_plain_css(source),
Syntax::Sass => sass_parser::parse(source),
}
.and_then(|sheet| {
if !matches!(options.syntax, Syntax::Css) {
eval::validate_declarations(&sheet)?;
}
Ok(sheet)
});
let sheet = match sheet {
Ok(s) => s,
Err(mut e) => {
if let Some(url) = options.url {
if e.rendered.is_none() && e.has_position() {
let span = diag::trim_empty_span_to_content(
source,
diag::Span {
line: e.line,
col: e.col,
length: e.length,
},
);
e.line = span.line;
e.col = span.col;
e.rendered = Some(diag::render_error(&e.message, source, url, span, glyphs));
}
}
return Err(e);
}
};
let entry_name = options.url.unwrap_or("stdin");
let mut ev = eval::Evaluator::new(eval::EvalOptions {
style: options.style,
importer: options.importer,
functions: &options.functions,
source,
url: entry_name,
glyphs,
warn: options.warn.as_ref(),
quiet_deps: options.quiet_deps.as_ref(),
silenced_deprecations: &options.silenced_deprecations,
plain_css: matches!(options.syntax, Syntax::Css),
source_map: true,
});
let mut out = Vec::new();
ev.eval_sheet(&sheet, &mut out)?;
let (css, body_off, collector) = emit::emit_with_map(&out, options.style, options.charset);
let mappings = collector.finalize(&css, body_off);
let (sources, sources_content) =
ev.source_table(mappings.source_ids(), options.source_map_include_sources);
let mappings = mappings.encode();
let source_map = SourceMap {
file: Some(basename(entry_name).to_string()),
sources,
sources_content,
mappings,
};
Ok(CompileResult { css, source_map })
}
fn compile_inner(source: &str, options: &Options<'_>) -> Result<String, Error> {
let _dep_scope = options.quiet_deps.as_ref().map(|d| d.enter_compile());
let glyphs_for = || {
if options.unicode {
diag::GlyphSet::Unicode
} else {
diag::GlyphSet::Ascii
}
};
let sheet = match options.syntax {
Syntax::Scss => parser::parse(source),
Syntax::Css => parser::parse_plain_css(source),
Syntax::Sass => sass_parser::parse(source),
}
.and_then(|sheet| {
if !matches!(options.syntax, Syntax::Css) {
eval::validate_declarations(&sheet)?;
}
Ok(sheet)
});
let sheet = match sheet {
Ok(s) => s,
Err(mut e) => {
if let Some(url) = options.url {
if e.rendered.is_none() && e.has_position() {
let span = diag::trim_empty_span_to_content(
source,
diag::Span {
line: e.line,
col: e.col,
length: e.length,
},
);
e.line = span.line;
e.col = span.col;
e.rendered = Some(diag::render_error(&e.message, source, url, span, glyphs_for()));
}
}
return Err(e);
}
};
let (diag_source, diag_url) = match options.url {
Some(url) => (source, url),
None => ("", ""),
};
let glyphs = if options.unicode {
diag::GlyphSet::Unicode
} else {
diag::GlyphSet::Ascii
};
let mut ev = eval::Evaluator::new(eval::EvalOptions {
style: options.style,
importer: options.importer,
functions: &options.functions,
source: diag_source,
url: diag_url,
glyphs,
warn: options.warn.as_ref(),
quiet_deps: options.quiet_deps.as_ref(),
silenced_deprecations: &options.silenced_deprecations,
plain_css: matches!(options.syntax, Syntax::Css),
source_map: false,
});
let mut out = Vec::new();
ev.eval_sheet(&sheet, &mut out)?;
Ok(emit::emit(&out, options.style, options.charset))
}