1use std::hash::{Hash, Hasher};
4use std::ops::Range;
5
6use super::Location;
7use super::MinifyContext;
8use crate::context::DeclarationContext;
9use crate::declaration::DeclarationBlock;
10use crate::error::ParserError;
11use crate::error::{MinifyError, PrinterError, PrinterErrorKind};
12use crate::parser::DefaultAtRule;
13use crate::printer::Printer;
14use crate::rules::CssRuleList;
15use crate::selector::{
16 downlevel_selectors, get_prefix, is_compatible, is_pure_css_modules_selector, is_unused, SelectorList,
17};
18use crate::targets::{should_compile, Targets};
19use crate::traits::ToCss;
20use crate::vendor_prefix::VendorPrefix;
21#[cfg(feature = "visitor")]
22use crate::visitor::Visit;
23use cssparser::*;
24
25#[derive(Debug, PartialEq, Clone)]
27#[cfg_attr(feature = "visitor", derive(Visit))]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
30#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
31pub struct StyleRule<'i, R = DefaultAtRule> {
32 #[cfg_attr(feature = "serde", serde(borrow))]
34 pub selectors: SelectorList<'i>,
35 #[cfg_attr(feature = "serde", serde(skip, default = "VendorPrefix::empty"))]
37 #[cfg_attr(feature = "visitor", skip_visit)]
38 pub vendor_prefix: VendorPrefix,
39 #[cfg_attr(feature = "serde", serde(default))]
41 pub declarations: DeclarationBlock<'i>,
42 #[cfg_attr(feature = "serde", serde(default = "default_rule_list::<R>"))]
44 pub rules: CssRuleList<'i, R>,
45 #[cfg_attr(feature = "visitor", skip_visit)]
47 pub loc: Location,
48}
49
50#[cfg(feature = "serde")]
51fn default_rule_list<'i, R>() -> CssRuleList<'i, R> {
52 CssRuleList(Vec::new())
53}
54
55impl<'i, T: Clone> StyleRule<'i, T> {
56 pub(crate) fn minify(
57 &mut self,
58 context: &mut MinifyContext<'_, 'i>,
59 parent_is_unused: bool,
60 ) -> Result<bool, MinifyError> {
61 let mut unused = false;
62 if !context.unused_symbols.is_empty() {
63 if is_unused(&mut self.selectors.0.iter(), &context.unused_symbols, parent_is_unused) {
64 if self.rules.0.is_empty() {
65 return Ok(true);
66 }
67
68 self.declarations.declarations.clear();
69 self.declarations.important_declarations.clear();
70 unused = true;
71 }
72 }
73
74 let pure_css_modules = context.pure_css_modules;
75 if context.pure_css_modules {
76 if !self.selectors.0.iter().all(is_pure_css_modules_selector) {
77 return Err(MinifyError {
78 kind: crate::error::MinifyErrorKind::ImpureCSSModuleSelector,
79 loc: self.loc,
80 });
81 }
82
83 context.pure_css_modules = false;
85 }
86
87 context.handler_context.context = DeclarationContext::StyleRule;
88 self
89 .declarations
90 .minify(context.handler, context.important_handler, &mut context.handler_context);
91 context.handler_context.context = DeclarationContext::None;
92
93 if !self.rules.0.is_empty() {
94 let mut handler_context = context.handler_context.child(DeclarationContext::StyleRule);
95 std::mem::swap(&mut context.handler_context, &mut handler_context);
96 self.rules.minify(context, unused)?;
97 context.handler_context = handler_context;
98 if unused && self.rules.0.is_empty() {
99 return Ok(true);
100 }
101 }
102
103 context.pure_css_modules = pure_css_modules;
104 Ok(false)
105 }
106}
107
108impl<'i, T> StyleRule<'i, T> {
109 pub fn is_empty(&self) -> bool {
111 self.selectors.0.is_empty() || (self.declarations.is_empty() && self.rules.0.is_empty())
112 }
113
114 pub fn is_compatible(&self, targets: Targets) -> bool {
117 is_compatible(&self.selectors.0, targets)
118 }
119
120 pub fn property_location<'t>(
125 &self,
126 code: &'i str,
127 index: usize,
128 ) -> Result<(Range<SourceLocation>, Range<SourceLocation>), ParseError<'i, ParserError<'i>>> {
129 let mut input = ParserInput::new(code);
130 let mut parser = Parser::new(&mut input);
131
132 parse_at(&mut parser, self.loc, |parser| {
134 parser.parse_until_before(Delimiter::CurlyBracketBlock, |parser| {
136 while parser.next().is_ok() {}
137 Ok(())
138 })?;
139
140 parser.expect_curly_bracket_block()?;
141 parser.parse_nested_block(|parser| {
142 let loc = self.declarations.property_location(parser, index);
143 while parser.next().is_ok() {}
144 loc
145 })
146 })
147 }
148
149 #[inline]
152 pub(crate) fn hash_key(&self) -> u64 {
153 let mut hasher = ahash::AHasher::default();
154 self.selectors.hash(&mut hasher);
155 for (property, _) in self.declarations.iter() {
156 property.property_id().hash(&mut hasher);
157 }
158 hasher.finish()
159 }
160
161 #[inline]
164 pub(crate) fn is_duplicate(&self, other_rule: &StyleRule<'i, T>) -> bool {
165 self.declarations.len() == other_rule.declarations.len()
166 && self.selectors == other_rule.selectors
167 && self
168 .declarations
169 .iter()
170 .zip(other_rule.declarations.iter())
171 .all(|((a, _), (b, _))| a.property_id() == b.property_id())
172 }
173
174 pub(crate) fn update_prefix(&mut self, context: &mut MinifyContext<'_, 'i>) {
175 self.vendor_prefix = get_prefix(&self.selectors);
176 if self.vendor_prefix.contains(VendorPrefix::None) && context.targets.should_compile_selectors() {
177 self.vendor_prefix = downlevel_selectors(self.selectors.0.as_mut_slice(), *context.targets);
178 }
179 }
180}
181
182fn parse_at<'i, 't, T, F>(
183 parser: &mut Parser<'i, 't>,
184 dest: Location,
185 parse: F,
186) -> Result<T, ParseError<'i, ParserError<'i>>>
187where
188 F: Copy + for<'tt> FnOnce(&mut Parser<'i, 'tt>) -> Result<T, ParseError<'i, ParserError<'i>>>,
189{
190 loop {
191 let loc = parser.current_source_location();
192 if loc.line >= dest.line || (loc.line == dest.line && loc.column >= dest.column) {
193 return parse(parser);
194 }
195
196 match parser.next()? {
197 Token::CurlyBracketBlock => {
198 let res = parser.parse_nested_block(|parser| {
200 let res = parse_at(parser, dest, parse);
201 while parser.next().is_ok() {}
202 res
203 });
204
205 if let Ok(v) = res {
206 return Ok(v);
207 }
208 }
209 _ => {}
210 }
211 }
212}
213
214impl<'a, 'i, T: ToCss> ToCss for StyleRule<'i, T> {
215 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
216 where
217 W: std::fmt::Write,
218 {
219 if self.vendor_prefix.is_empty() {
220 self.to_css_base(dest)
221 } else {
222 let mut first_rule = true;
223 for prefix in self.vendor_prefix {
224 if first_rule {
225 first_rule = false;
226 } else {
227 if !dest.minify {
228 dest.write_char('\n')?; }
230 dest.newline()?;
231 }
232 dest.vendor_prefix = prefix;
233 self.to_css_base(dest)?;
234 }
235
236 dest.vendor_prefix = VendorPrefix::empty();
237 Ok(())
238 }
239 }
240}
241
242impl<'a, 'i, T: ToCss> StyleRule<'i, T> {
243 fn to_css_base<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
244 where
245 W: std::fmt::Write,
246 {
247 let supports_nesting = self.rules.0.is_empty() || !should_compile!(dest.targets, Nesting);
249 let len = self.declarations.declarations.len() + self.declarations.important_declarations.len();
250 let has_declarations = supports_nesting || len > 0 || self.rules.0.is_empty();
251
252 if has_declarations {
253 #[cfg(feature = "sourcemap")]
254 dest.add_mapping(self.loc);
255 self.selectors.to_css(dest)?;
256 dest.whitespace()?;
257 dest.write_char('{')?;
258 dest.indent();
259
260 let mut i = 0;
261 macro_rules! write {
262 ($decls: ident, $important: literal) => {
263 for decl in &self.declarations.$decls {
264 if let crate::properties::Property::Composes(composes) = &decl {
267 if dest.is_nested() && dest.css_module.is_some() {
268 return Err(dest.error(PrinterErrorKind::InvalidComposesNesting, composes.loc));
269 }
270
271 if let Some(css_module) = &mut dest.css_module {
272 css_module
273 .handle_composes(&self.selectors, &composes, self.loc.source_index)
274 .map_err(|e| dest.error(e, composes.loc))?;
275 continue;
276 }
277 }
278
279 dest.newline()?;
280 decl.to_css(dest, $important)?;
281 if i != len - 1 || !dest.minify || (supports_nesting && !self.rules.0.is_empty()) {
282 dest.write_char(';')?;
283 }
284
285 i += 1;
286 }
287 };
288 }
289
290 write!(declarations, false);
291 write!(important_declarations, true);
292 }
293
294 macro_rules! newline {
295 () => {
296 if !dest.minify && (supports_nesting || len > 0) && !self.rules.0.is_empty() {
297 if len > 0 {
298 dest.write_char('\n')?;
299 }
300 dest.newline()?;
301 }
302 };
303 }
304
305 macro_rules! end {
306 () => {
307 if has_declarations {
308 dest.dedent();
309 dest.newline()?;
310 dest.write_char('}')?;
311 }
312 };
313 }
314
315 if supports_nesting {
317 newline!();
318 self.rules.to_css(dest)?;
319 end!();
320 } else {
321 end!();
322 newline!();
323 dest.with_context(&self.selectors, |dest| self.rules.to_css(dest))?;
324 }
325
326 Ok(())
327 }
328}