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};
12use crate::parser::DefaultAtRule;
13use crate::printer::Printer;
14use crate::rules::{CssRule, 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.current.should_compile_selectors() {
177 self.vendor_prefix = downlevel_selectors(self.selectors.0.as_mut_slice(), context.targets.current);
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.current, Nesting);
249 let must_preserve_rule = |rule: &CssRule<'i, T>| matches!(rule, CssRule::Unknown(_));
250 let has_preserved_rules = !supports_nesting && self.rules.0.iter().any(must_preserve_rule);
251 let has_hoisted_rules = !supports_nesting && self.rules.0.iter().any(|rule| !must_preserve_rule(rule));
252 let len = self.declarations.declarations.len() + self.declarations.important_declarations.len();
253 let has_declarations = supports_nesting || len > 0 || self.rules.0.is_empty() || has_preserved_rules;
254
255 if has_declarations {
256 #[cfg(feature = "sourcemap")]
257 dest.add_mapping(self.loc);
258 self.selectors.to_css(dest)?;
259 dest.whitespace()?;
260 dest.write_char('{')?;
261 dest.indent();
262 if len > 0 {
263 dest.newline()?;
264 }
265
266 self.declarations.to_css_declarations(
267 dest,
268 (supports_nesting && !self.rules.0.is_empty()) || has_preserved_rules,
269 &self.selectors,
270 self.loc.source_index,
271 )?;
272 }
273
274 macro_rules! newline {
275 () => {
276 if !dest.minify && (supports_nesting || len > 0 || has_preserved_rules) && !self.rules.0.is_empty() {
277 if len > 0 || has_preserved_rules {
278 dest.write_char('\n')?;
279 }
280 dest.newline()?;
281 }
282 };
283 }
284
285 macro_rules! end {
286 () => {
287 if has_declarations {
288 dest.dedent();
289 dest.newline()?;
290 dest.write_char('}')?;
291 }
292 };
293 }
294
295 if supports_nesting {
297 newline!();
298 self.rules.to_css(dest)?;
299 end!();
300 } else {
301 let mut first = true;
302 for rule in self.rules.0.iter().filter(|rule| must_preserve_rule(rule)) {
303 if !dest.minify || first {
304 dest.newline()?;
305 }
306 rule.to_css(dest)?;
307 first = false;
308 }
309 end!();
310
311 if has_hoisted_rules {
312 newline!();
313 dest.with_context(&self.selectors, |dest| {
314 let mut first = true;
315 for rule in self.rules.0.iter().filter(|rule| !must_preserve_rule(rule)) {
316 if first {
317 first = false;
318 } else {
319 if !dest.minify {
320 dest.write_char('\n')?;
321 }
322 dest.newline()?;
323 }
324 rule.to_css(dest)?;
325 }
326
327 Ok::<(), PrinterError>(())
328 })?;
329 }
330 }
331
332 Ok(())
333 }
334}