use crate::compat::Feature;
use crate::context::{DeclarationContext, PropertyHandlerContext};
use crate::css_modules::{CssModule, CssModuleExports, CssModuleReferences};
use crate::declaration::{DeclarationBlock, DeclarationHandler};
use crate::dependencies::Dependency;
use crate::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterError, PrinterErrorKind};
use crate::parser::{DefaultAtRuleParser, TopLevelRuleParser};
use crate::printer::Printer;
use crate::rules::{CssRule, CssRuleList, MinifyContext};
use crate::targets::Browsers;
use crate::traits::ToCss;
use crate::visitor::{Visit, VisitTypes, Visitor};
use cssparser::{AtRuleParser, Parser, ParserInput, RuleListParser};
use parcel_sourcemap::SourceMap;
use std::collections::{HashMap, HashSet};
pub use crate::parser::ParserOptions;
pub use crate::printer::PrinterOptions;
pub use crate::printer::PseudoClasses;
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct StyleSheet<'i, 'o, T: AtRuleParser<'i> = DefaultAtRuleParser> {
#[cfg_attr(
feature = "serde",
serde(
borrow,
bound(
serialize = "T::AtRule: serde::Serialize",
deserialize = "T::AtRule: serde::Deserialize<'de>"
)
)
)]
pub rules: CssRuleList<'i, T::AtRule>,
pub sources: Vec<String>,
pub(crate) source_map_urls: Vec<Option<String>>,
#[cfg_attr(feature = "serde", serde(skip))]
options: ParserOptions<'o, 'i, T>,
}
#[derive(Default)]
pub struct MinifyOptions {
pub targets: Option<Browsers>,
pub unused_symbols: HashSet<String>,
}
#[derive(Debug)]
pub struct ToCssResult {
pub code: String,
pub exports: Option<CssModuleExports>,
pub references: Option<CssModuleReferences>,
pub dependencies: Option<Vec<Dependency>>,
}
impl<'i, 'o, T: AtRuleParser<'i>> StyleSheet<'i, 'o, T>
where
T::AtRule: ToCss,
{
pub fn new(
sources: Vec<String>,
rules: CssRuleList<'i, T::AtRule>,
options: ParserOptions<'o, 'i, T>,
) -> StyleSheet<'i, 'o, T> {
StyleSheet {
sources,
source_map_urls: Vec::new(),
rules,
options,
}
}
pub fn parse(code: &'i str, mut options: ParserOptions<'o, 'i, T>) -> Result<Self, Error<ParserError<'i>>> {
let mut input = ParserInput::new(&code);
let mut parser = Parser::new(&mut input);
let mut rule_list_parser =
RuleListParser::new_for_stylesheet(&mut parser, TopLevelRuleParser::new(&mut options));
let mut rules = vec![];
while let Some(rule) = rule_list_parser.next() {
let rule = match rule {
Ok((_, CssRule::Ignored)) => continue,
Ok((_, rule)) => rule,
Err((e, _)) => {
let options = &mut rule_list_parser.parser.options;
if options.error_recovery {
options.warn(e);
continue;
}
return Err(Error::from(e, options.filename.clone()));
}
};
rules.push(rule)
}
Ok(StyleSheet {
sources: vec![options.filename.clone()],
source_map_urls: vec![parser.current_source_map_url().map(|s| s.to_owned())],
rules: CssRuleList(rules),
options,
})
}
pub fn source_map_url(&self, source_index: usize) -> Option<&String> {
self.source_map_urls.get(source_index)?.as_ref()
}
pub fn source_map(&self, source_index: usize) -> Option<SourceMap> {
SourceMap::from_data_url("/", self.source_map_url(source_index)?).ok()
}
pub fn minify(&mut self, options: MinifyOptions) -> Result<(), Error<MinifyErrorKind>> {
let mut context = PropertyHandlerContext::new(options.targets, &options.unused_symbols);
let mut handler = DeclarationHandler::new(options.targets);
let mut important_handler = DeclarationHandler::new(options.targets);
let custom_media = if self.options.custom_media
&& options.targets.is_some()
&& !Feature::CustomMediaQueries.is_compatible(options.targets.unwrap())
{
let mut custom_media = HashMap::new();
for rule in &self.rules.0 {
if let CssRule::CustomMedia(rule) = rule {
custom_media.insert(rule.name.0.clone(), rule.clone());
}
}
Some(custom_media)
} else {
None
};
let mut ctx = MinifyContext {
targets: &options.targets,
handler: &mut handler,
important_handler: &mut important_handler,
handler_context: &mut context,
unused_symbols: &options.unused_symbols,
custom_media,
css_modules: self.options.css_modules.is_some(),
};
self.rules.minify(&mut ctx, false).map_err(|e| Error {
kind: e.kind,
loc: Some(ErrorLocation::new(
e.loc,
self.sources[e.loc.source_index as usize].clone(),
)),
})?;
Ok(())
}
pub fn to_css(&self, options: PrinterOptions) -> Result<ToCssResult, Error<PrinterErrorKind>> {
let mut dest = String::with_capacity(1);
let mut printer = Printer::new(&mut dest, options);
printer.sources = Some(&self.sources);
if printer.source_map.is_some() {
printer.source_maps = self.sources.iter().enumerate().map(|(i, _)| self.source_map(i)).collect();
}
if let Some(config) = &self.options.css_modules {
let mut references = HashMap::new();
printer.css_module = Some(CssModule::new(config, &self.sources, &mut references));
self.rules.to_css(&mut printer)?;
printer.newline()?;
Ok(ToCssResult {
dependencies: printer.dependencies,
exports: Some(std::mem::take(
&mut printer.css_module.unwrap().exports_by_source_index[0],
)),
code: dest,
references: Some(references),
})
} else {
self.rules.to_css(&mut printer)?;
printer.newline()?;
Ok(ToCssResult {
dependencies: printer.dependencies,
code: dest,
exports: None,
references: None,
})
}
}
}
impl<'i, 'o, T, V> Visit<'i, T::AtRule, V> for StyleSheet<'i, 'o, T>
where
T: AtRuleParser<'i>,
T::AtRule: Visit<'i, T::AtRule, V>,
V: Visitor<'i, T::AtRule>,
{
const CHILD_TYPES: VisitTypes = VisitTypes::all();
fn visit_children(&mut self, visitor: &mut V) {
self.rules.visit(visitor)
}
}
#[derive(Visit)]
pub struct StyleAttribute<'i> {
pub declarations: DeclarationBlock<'i>,
#[skip_visit]
sources: Vec<String>,
}
impl<'i> StyleAttribute<'i> {
pub fn parse<T>(
code: &'i str,
options: ParserOptions<'_, 'i, T>,
) -> Result<StyleAttribute<'i>, Error<ParserError<'i>>> {
let mut input = ParserInput::new(&code);
let mut parser = Parser::new(&mut input);
Ok(StyleAttribute {
declarations: DeclarationBlock::parse(&mut parser, &options).map_err(|e| Error::from(e, "".into()))?,
sources: vec![options.filename],
})
}
pub fn minify(&mut self, options: MinifyOptions) {
let mut context = PropertyHandlerContext::new(options.targets, &options.unused_symbols);
let mut handler = DeclarationHandler::new(options.targets);
let mut important_handler = DeclarationHandler::new(options.targets);
context.context = DeclarationContext::StyleAttribute;
self.declarations.minify(&mut handler, &mut important_handler, &mut context);
}
pub fn to_css(&self, options: PrinterOptions) -> Result<ToCssResult, PrinterError> {
assert!(
options.source_map.is_none(),
"Source maps are not supported for style attributes"
);
let mut dest = String::with_capacity(1);
let mut printer = Printer::new(&mut dest, options);
printer.sources = Some(&self.sources);
self.declarations.to_css(&mut printer)?;
Ok(ToCssResult {
dependencies: printer.dependencies,
code: dest,
exports: None,
references: None,
})
}
}