#![deny(missing_docs)]
pub mod container;
pub mod counter_style;
pub mod custom_media;
pub mod document;
pub mod font_face;
pub mod font_palette_values;
pub mod import;
pub mod keyframes;
pub mod layer;
pub mod media;
pub mod namespace;
pub mod nesting;
pub mod page;
pub mod property;
pub mod style;
pub mod supports;
pub mod unknown;
pub mod viewport;
use self::font_palette_values::FontPaletteValuesRule;
use self::layer::{LayerBlockRule, LayerStatementRule};
use self::property::PropertyRule;
use crate::context::PropertyHandlerContext;
use crate::declaration::DeclarationHandler;
use crate::dependencies::{Dependency, ImportDependency};
use crate::error::{MinifyError, ParserError, PrinterError, PrinterErrorKind};
use crate::parser::{DefaultAtRule, TopLevelRuleParser};
use crate::prefixes::Feature;
use crate::printer::Printer;
use crate::rules::keyframes::KeyframesName;
use crate::selector::{downlevel_selectors, get_prefix, is_equivalent};
use crate::stylesheet::ParserOptions;
use crate::targets::Browsers;
use crate::traits::ToCss;
use crate::values::string::CowArcStr;
use crate::vendor_prefix::VendorPrefix;
use crate::visitor::{Visit, VisitTypes, Visitor};
use container::ContainerRule;
use counter_style::CounterStyleRule;
use cssparser::{parse_one_rule, AtRuleParser, ParseError, Parser, ParserInput};
use custom_media::CustomMediaRule;
use document::MozDocumentRule;
use font_face::FontFaceRule;
use import::ImportRule;
use keyframes::KeyframesRule;
use media::MediaRule;
use namespace::NamespaceRule;
use nesting::NestingRule;
use page::PageRule;
use serde::Serialize;
use std::collections::{HashMap, HashSet};
use style::StyleRule;
use supports::SupportsRule;
use unknown::UnknownAtRule;
use viewport::ViewportRule;
pub(crate) trait ToCssWithContext<'a, 'i, T> {
fn to_css_with_context<W>(
&self,
dest: &mut Printer<W>,
context: Option<&StyleContext<'a, 'i, T>>,
) -> Result<(), PrinterError>
where
W: std::fmt::Write;
}
pub(crate) struct StyleContext<'a, 'i, T> {
pub rule: &'a StyleRule<'i, T>,
pub parent: Option<&'a StyleContext<'a, 'i, T>>,
}
#[derive(PartialEq, Eq, Debug, Clone, Copy, Serialize)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct Location {
pub source_index: u32,
pub line: u32,
pub column: u32,
}
#[derive(Debug, PartialEq, Clone, Visit)]
#[visit(visit_rule, RULES)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type", content = "value", rename_all = "kebab-case")
)]
pub enum CssRule<'i, R = DefaultAtRule> {
#[cfg_attr(feature = "serde", serde(borrow))]
Media(MediaRule<'i, R>),
Import(ImportRule<'i>),
Style(StyleRule<'i, R>),
Keyframes(KeyframesRule<'i>),
FontFace(FontFaceRule<'i>),
FontPaletteValues(FontPaletteValuesRule<'i>),
Page(PageRule<'i>),
Supports(SupportsRule<'i, R>),
CounterStyle(CounterStyleRule<'i>),
Namespace(NamespaceRule<'i>),
MozDocument(MozDocumentRule<'i, R>),
Nesting(NestingRule<'i, R>),
Viewport(ViewportRule<'i>),
CustomMedia(CustomMediaRule<'i>),
LayerStatement(LayerStatementRule<'i>),
LayerBlock(LayerBlockRule<'i, R>),
Property(PropertyRule<'i>),
Container(ContainerRule<'i, R>),
Ignored,
Unknown(UnknownAtRule<'i>),
Custom(R),
}
impl<'a, 'i, T: ToCss> ToCssWithContext<'a, 'i, T> for CssRule<'i, T> {
fn to_css_with_context<W>(
&self,
dest: &mut Printer<W>,
context: Option<&StyleContext<'a, 'i, T>>,
) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
match self {
CssRule::Media(media) => media.to_css_with_context(dest, context),
CssRule::Import(import) => import.to_css(dest),
CssRule::Style(style) => style.to_css_with_context(dest, context),
CssRule::Keyframes(keyframes) => keyframes.to_css(dest),
CssRule::FontFace(font_face) => font_face.to_css(dest),
CssRule::FontPaletteValues(f) => f.to_css(dest),
CssRule::Page(font_face) => font_face.to_css(dest),
CssRule::Supports(supports) => supports.to_css_with_context(dest, context),
CssRule::CounterStyle(counter_style) => counter_style.to_css(dest),
CssRule::Namespace(namespace) => namespace.to_css(dest),
CssRule::MozDocument(document) => document.to_css(dest),
CssRule::Nesting(nesting) => nesting.to_css_with_context(dest, context),
CssRule::Viewport(viewport) => viewport.to_css(dest),
CssRule::CustomMedia(custom_media) => custom_media.to_css(dest),
CssRule::LayerStatement(layer) => layer.to_css(dest),
CssRule::LayerBlock(layer) => layer.to_css(dest),
CssRule::Property(property) => property.to_css(dest),
CssRule::Container(container) => container.to_css_with_context(dest, context),
CssRule::Unknown(unknown) => unknown.to_css(dest),
CssRule::Custom(rule) => rule.to_css(dest).map_err(|_| PrinterError {
kind: PrinterErrorKind::FmtError,
loc: None,
}),
CssRule::Ignored => Ok(()),
}
}
}
impl<'i, T> CssRule<'i, T> {
pub fn parse<'t, P: AtRuleParser<'i, AtRule = T>>(
input: &mut Parser<'i, 't>,
options: &mut ParserOptions<'_, 'i, P>,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let (_, rule) = parse_one_rule(input, &mut TopLevelRuleParser::new(options))?;
Ok(rule)
}
pub fn parse_string<P: AtRuleParser<'i, AtRule = T>>(
input: &'i str,
mut options: ParserOptions<'_, 'i, P>,
) -> Result<Self, ParseError<'i, ParserError<'i>>> {
let mut input = ParserInput::new(input);
let mut parser = Parser::new(&mut input);
Self::parse(&mut parser, &mut options)
}
}
impl<'i, T: ToCss> ToCss for CssRule<'i, T> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
self.to_css_with_context(dest, None)
}
}
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CssRuleList<'i, R = DefaultAtRule>(
#[cfg_attr(feature = "serde", serde(borrow))] pub Vec<CssRule<'i, R>>,
);
impl<'i, T: Visit<'i, T, V>, V: Visitor<'i, T>> Visit<'i, T, V> for CssRuleList<'i, T> {
const CHILD_TYPES: VisitTypes = VisitTypes::all();
fn visit_children(&mut self, visitor: &mut V) {
self.0.visit(visitor)
}
}
pub(crate) struct MinifyContext<'a, 'i> {
pub targets: &'a Option<Browsers>,
pub handler: &'a mut DeclarationHandler<'i>,
pub important_handler: &'a mut DeclarationHandler<'i>,
pub handler_context: &'a mut PropertyHandlerContext<'i, 'a>,
pub unused_symbols: &'a HashSet<String>,
pub custom_media: Option<HashMap<CowArcStr<'i>, CustomMediaRule<'i>>>,
pub css_modules: bool,
}
impl<'i, T> CssRuleList<'i, T> {
pub(crate) fn minify(
&mut self,
context: &mut MinifyContext<'_, 'i>,
parent_is_unused: bool,
) -> Result<(), MinifyError> {
let mut keyframe_rules = HashMap::new();
let mut rules = Vec::new();
for mut rule in self.0.drain(..) {
match &mut rule {
CssRule::Keyframes(keyframes) => {
if context.unused_symbols.contains(match &keyframes.name {
KeyframesName::Ident(ident) => ident.0.as_ref(),
KeyframesName::Custom(string) => string.as_ref(),
}) {
continue;
}
keyframes.minify(context);
macro_rules! set_prefix {
($keyframes: ident) => {
if $keyframes.vendor_prefix.contains(VendorPrefix::None) {
if let Some(targets) = context.targets {
$keyframes.vendor_prefix = Feature::AtKeyframes.prefixes_for(*targets)
}
}
};
}
if let Some(existing_idx) = keyframe_rules.get(&keyframes.name) {
if let Some(CssRule::Keyframes(existing)) = &mut rules.get_mut(*existing_idx) {
if existing.keyframes == keyframes.keyframes {
existing.vendor_prefix |= keyframes.vendor_prefix;
set_prefix!(existing);
continue;
}
}
}
set_prefix!(keyframes);
keyframe_rules.insert(keyframes.name.clone(), rules.len());
if let Some(targets) = context.targets {
let fallbacks = keyframes.get_fallbacks(*targets);
rules.push(rule);
rules.extend(fallbacks);
continue;
}
}
CssRule::CustomMedia(_) => {
if context.custom_media.is_some() {
continue;
}
}
CssRule::Media(media) => {
if let Some(CssRule::Media(last_rule)) = rules.last_mut() {
if last_rule.query == media.query {
last_rule.rules.0.extend(media.rules.0.drain(..));
last_rule.minify(context, parent_is_unused)?;
continue;
}
}
if media.minify(context, parent_is_unused)? {
continue;
}
}
CssRule::Supports(supports) => {
if let Some(CssRule::Supports(last_rule)) = rules.last_mut() {
if last_rule.condition == supports.condition {
last_rule.rules.0.extend(supports.rules.0.drain(..));
last_rule.minify(context, parent_is_unused)?;
continue;
}
}
supports.minify(context, parent_is_unused)?;
if supports.rules.0.is_empty() {
continue;
}
}
CssRule::Container(container) => {
if let Some(CssRule::Container(last_rule)) = rules.last_mut() {
if last_rule.name == container.name && last_rule.condition == container.condition {
last_rule.rules.0.extend(container.rules.0.drain(..));
last_rule.minify(context, parent_is_unused)?;
continue;
}
}
if container.minify(context, parent_is_unused)? {
continue;
}
}
CssRule::LayerBlock(layer) => {
if let Some(CssRule::LayerBlock(last_rule)) = rules.last_mut() {
if last_rule.name == layer.name {
last_rule.rules.0.extend(layer.rules.0.drain(..));
last_rule.minify(context, parent_is_unused)?;
continue;
}
}
if layer.minify(context, parent_is_unused)? {
continue;
}
}
CssRule::MozDocument(document) => document.minify(context)?,
CssRule::Style(style) => {
if parent_is_unused || style.minify(context, parent_is_unused)? {
continue;
}
if let Some(targets) = context.targets {
style.vendor_prefix = get_prefix(&style.selectors);
if style.vendor_prefix.contains(VendorPrefix::None) {
style.vendor_prefix = downlevel_selectors(&mut style.selectors, *targets);
}
}
let mut merged = false;
if let Some(CssRule::Style(last_style_rule)) = rules.last_mut() {
if merge_style_rules(style, last_style_rule, context) {
while rules.len() >= 2 {
let len = rules.len();
let (a, b) = rules.split_at_mut(len - 1);
if let (CssRule::Style(last), CssRule::Style(prev)) = (&mut b[0], &mut a[len - 2]) {
if merge_style_rules(last, prev, context) {
rules.pop();
continue;
}
}
break;
}
merged = true;
}
}
let supports = context.handler_context.get_supports_rules(&style);
let logical = context.handler_context.get_logical_rules(&style);
if !merged && !style.is_empty() {
rules.push(rule);
}
if !logical.is_empty() {
let mut logical = CssRuleList(logical);
logical.minify(context, parent_is_unused)?;
rules.extend(logical.0)
}
rules.extend(supports);
continue;
}
CssRule::CounterStyle(counter_style) => {
if context.unused_symbols.contains(counter_style.name.0.as_ref()) {
continue;
}
}
CssRule::Nesting(nesting) => {
if nesting.minify(context, parent_is_unused)? {
continue;
}
}
CssRule::FontPaletteValues(f) => {
if context.unused_symbols.contains(f.name.0.as_ref()) {
continue;
}
f.minify(context, parent_is_unused);
if let Some(targets) = context.targets {
let fallbacks = f.get_fallbacks(*targets);
rules.push(rule);
rules.extend(fallbacks);
continue;
}
}
CssRule::Property(property) => {
if context.unused_symbols.contains(property.name.0.as_ref()) {
continue;
}
}
_ => {}
}
rules.push(rule)
}
self.0 = rules;
Ok(())
}
}
fn merge_style_rules<'i, T>(
style: &mut StyleRule<'i, T>,
last_style_rule: &mut StyleRule<'i, T>,
context: &mut MinifyContext<'_, 'i>,
) -> bool {
if style.selectors == last_style_rule.selectors
&& style.is_compatible(*context.targets)
&& last_style_rule.is_compatible(*context.targets)
&& style.rules.0.is_empty()
&& last_style_rule.rules.0.is_empty()
&& (!context.css_modules || style.loc.source_index == last_style_rule.loc.source_index)
{
last_style_rule
.declarations
.declarations
.extend(style.declarations.declarations.drain(..));
last_style_rule
.declarations
.important_declarations
.extend(style.declarations.important_declarations.drain(..));
last_style_rule
.declarations
.minify(context.handler, context.important_handler, context.handler_context);
return true;
} else if style.declarations == last_style_rule.declarations
&& style.rules.0.is_empty()
&& last_style_rule.rules.0.is_empty()
{
if style.is_compatible(*context.targets) && last_style_rule.is_compatible(*context.targets) {
last_style_rule.selectors.0.extend(style.selectors.0.drain(..));
return true;
}
if !style.vendor_prefix.is_empty()
&& !last_style_rule.vendor_prefix.is_empty()
&& !last_style_rule.vendor_prefix.contains(style.vendor_prefix)
&& is_equivalent(&style.selectors, &last_style_rule.selectors)
{
if style.vendor_prefix.contains(VendorPrefix::None) {
last_style_rule.vendor_prefix = style.vendor_prefix;
} else {
last_style_rule.vendor_prefix |= style.vendor_prefix;
}
return true;
}
}
false
}
impl<'i, T: ToCss> ToCss for CssRuleList<'i, T> {
fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
self.to_css_with_context(dest, None)
}
}
impl<'a, 'i, T: ToCss> ToCssWithContext<'a, 'i, T> for CssRuleList<'i, T> {
fn to_css_with_context<W>(
&self,
dest: &mut Printer<W>,
context: Option<&StyleContext<'a, 'i, T>>,
) -> Result<(), PrinterError>
where
W: std::fmt::Write,
{
let mut first = true;
let mut last_without_block = false;
for rule in &self.0 {
if let CssRule::Ignored = &rule {
continue;
}
if let CssRule::Import(rule) = &rule {
if dest.remove_imports {
let dep = if dest.dependencies.is_some() {
Some(Dependency::Import(ImportDependency::new(&rule, dest.filename())))
} else {
None
};
if let Some(dependencies) = &mut dest.dependencies {
dependencies.push(dep.unwrap());
continue;
}
}
}
if first {
first = false;
} else {
if !dest.minify
&& !(last_without_block
&& matches!(
rule,
CssRule::Import(..) | CssRule::Namespace(..) | CssRule::LayerStatement(..)
))
{
dest.write_char('\n')?;
}
dest.newline()?;
}
rule.to_css_with_context(dest, context)?;
last_without_block = matches!(
rule,
CssRule::Import(..) | CssRule::Namespace(..) | CssRule::LayerStatement(..)
);
}
Ok(())
}
}