From 73e30a50f62947e87131a40b5ccf307ae8b03e05 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Mon, 4 Apr 2022 21:19:50 +0200 Subject: [PATCH] Format rust source code using rustfmt "rustfmt's style is nobody's favourite, but rustfmt is everybody's favourite." --- .github/workflows/test.yml | 1 + node/src/lib.rs | 129 +- selectors/attr.rs | 270 +- selectors/bloom.rs | 554 +- selectors/build.rs | 25 +- selectors/builder.rs | 468 +- selectors/context.rs | 434 +- selectors/matching.rs | 1415 ++-- selectors/nth_index_cache.rs | 48 +- selectors/parser.rs | 5833 ++++++++-------- selectors/sink.rs | 16 +- selectors/tree.rs | 238 +- selectors/visitor.rs | 78 +- src/bundler.rs | 683 +- src/compat.rs | 751 +- src/context.rs | 122 +- src/css_modules.rs | 70 +- src/declaration.rs | 145 +- src/dependencies.rs | 34 +- src/error.rs | 95 +- src/lib.rs | 10588 ++++++++++++++++++++--------- src/logical.rs | 13 +- src/macros.rs | 6 +- src/main.rs | 39 +- src/media_query.rs | 404 +- src/parser.rs | 545 +- src/prefixes.rs | 401 +- src/printer.rs | 44 +- src/properties/align.rs | 292 +- src/properties/animation.rs | 177 +- src/properties/background.rs | 208 +- src/properties/border.rs | 308 +- src/properties/border_image.rs | 163 +- src/properties/border_radius.rs | 131 +- src/properties/box_shadow.rs | 52 +- src/properties/css_modules.rs | 33 +- src/properties/custom.rs | 201 +- src/properties/display.rs | 154 +- src/properties/effects.rs | 65 +- src/properties/flex.rs | 270 +- src/properties/font.rs | 191 +- src/properties/grid.rs | 402 +- src/properties/list.rs | 68 +- src/properties/margin_padding.rs | 16 +- src/properties/masking.rs | 280 +- src/properties/mod.rs | 119 +- src/properties/outline.rs | 23 +- src/properties/overflow.rs | 46 +- src/properties/position.rs | 40 +- src/properties/prefix_handler.rs | 12 +- src/properties/size.rs | 91 +- src/properties/svg.rs | 80 +- src/properties/text.rs | 248 +- src/properties/transform.rs | 701 +- src/properties/transition.rs | 133 +- src/properties/ui.rs | 82 +- src/rules/counter_style.rs | 11 +- src/rules/custom_media.rs | 13 +- src/rules/document.rs | 11 +- src/rules/font_face.rs | 95 +- src/rules/font_palette_values.rs | 103 +- src/rules/import.rs | 24 +- src/rules/keyframes.rs | 109 +- src/rules/layer.rs | 35 +- src/rules/media.rs | 29 +- src/rules/mod.rs | 203 +- src/rules/namespace.rs | 15 +- src/rules/nesting.rs | 25 +- src/rules/page.rs | 35 +- src/rules/property.rs | 50 +- src/rules/style.rs | 80 +- src/rules/supports.rs | 56 +- src/rules/viewport.rs | 13 +- src/selector.rs | 1256 ++-- src/stylesheet.rs | 79 +- src/targets.rs | 2 +- src/traits.rs | 42 +- src/values/alpha.rs | 13 +- src/values/angle.rs | 27 +- src/values/calc.rs | 111 +- src/values/color.rs | 729 +- src/values/easing.rs | 26 +- src/values/gradient.rs | 534 +- src/values/ident.rs | 20 +- src/values/image.rs | 130 +- src/values/length.rs | 157 +- src/values/mod.rs | 26 +- src/values/number.rs | 29 +- src/values/percentage.rs | 152 +- src/values/position.rs | 117 +- src/values/ratio.rs | 13 +- src/values/rect.rs | 140 +- src/values/resolution.rs | 19 +- src/values/shape.rs | 79 +- src/values/size.rs | 21 +- src/values/string.rs | 16 +- src/values/syntax.rs | 241 +- src/values/time.rs | 17 +- src/values/url.rs | 23 +- src/vendor_prefix.rs | 15 +- 100 files changed, 19275 insertions(+), 13701 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 21c31ca2..61793857 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,4 +8,5 @@ jobs: steps: - uses: actions/checkout@v2 - uses: Swatinem/rust-cache@v1 + - run: cargo fmt - run: cargo test diff --git a/node/src/lib.rs b/node/src/lib.rs index 7ee9dfa0..c04d0cf7 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -2,16 +2,18 @@ #[global_allocator] static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; -use std::collections::HashSet; -use std::path::Path; -use serde::{Serialize, Deserialize}; -use parcel_css::stylesheet::{StyleSheet, StyleAttribute, ParserOptions, PrinterOptions, MinifyOptions, PseudoClasses}; -use parcel_css::targets::Browsers; +use parcel_css::bundler::{BundleErrorKind, Bundler, FileProvider, SourceProvider}; use parcel_css::css_modules::CssModuleExports; use parcel_css::dependencies::Dependency; -use parcel_css::error::{ParserError, Error, ErrorLocation, MinifyErrorKind, PrinterErrorKind}; -use parcel_css::bundler::{FileProvider, Bundler, BundleErrorKind, SourceProvider}; +use parcel_css::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterErrorKind}; +use parcel_css::stylesheet::{ + MinifyOptions, ParserOptions, PrinterOptions, PseudoClasses, StyleAttribute, StyleSheet, +}; +use parcel_css::targets::Browsers; use parcel_sourcemap::SourceMap; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; +use std::path::Path; // --------------------------------------------- @@ -24,7 +26,7 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn transform(config_val: JsValue) -> Result { let config: Config = from_value(config_val).map_err(JsValue::from)?; - let code = unsafe { std::str::from_utf8_unchecked(&config.code) }; + let code = unsafe { std::str::from_utf8_unchecked(&config.code) }; let res = compile(code, &config)?; let serializer = Serializer::new().serialize_maps_as_objects(true); res.serialize(&serializer).map_err(JsValue::from) @@ -42,10 +44,10 @@ pub fn transform_style_attribute(config_val: JsValue) -> Result { mappings: String, sources: &'a Vec, sources_content: &'a Vec, - names: &'a Vec + names: &'a Vec, } #[derive(Serialize)] @@ -65,7 +67,7 @@ struct TransformResult { #[serde(with = "serde_bytes")] map: Option>, exports: Option, - dependencies: Option> + dependencies: Option>, } #[cfg(not(target_arch = "wasm32"))] @@ -73,12 +75,12 @@ struct TransformResult { fn transform(ctx: CallContext) -> napi::Result { let opts = ctx.get::(0)?; let config: Config = ctx.env.from_js_value(opts)?; - let code = unsafe { std::str::from_utf8_unchecked(&config.code) }; + let code = unsafe { std::str::from_utf8_unchecked(&config.code) }; let res = compile(code, &config); match res { Ok(res) => ctx.env.to_js_value(&res), - Err(err) => err.throw(ctx, Some(code)) + Err(err) => err.throw(ctx, Some(code)), } } @@ -92,7 +94,7 @@ fn transform_style_attribute(ctx: CallContext) -> napi::Result { match res { Ok(res) => ctx.env.to_js_value(&res), - Err(err) => err.throw(ctx, Some(code)) + Err(err) => err.throw(ctx, Some(code)), } } @@ -108,13 +110,23 @@ fn bundle(ctx: CallContext) -> napi::Result { Ok(res) => ctx.env.to_js_value(&res), Err(err) => { let code = match &err { - CompileError::ParseError(Error { loc: Some(ErrorLocation { filename, .. }), .. }) | - CompileError::PrinterError(Error { loc: Some(ErrorLocation { filename, .. }), .. }) | - CompileError::MinifyError(Error { loc: Some(ErrorLocation { filename, .. }), .. }) | - CompileError::BundleError(Error { loc: Some(ErrorLocation { filename, .. }), .. }) => { - Some(fs.read(Path::new(filename))?) - }, - _ => None + CompileError::ParseError(Error { + loc: Some(ErrorLocation { filename, .. }), + .. + }) + | CompileError::PrinterError(Error { + loc: Some(ErrorLocation { filename, .. }), + .. + }) + | CompileError::MinifyError(Error { + loc: Some(ErrorLocation { filename, .. }), + .. + }) + | CompileError::BundleError(Error { + loc: Some(ErrorLocation { filename, .. }), + .. + }) => Some(fs.read(Path::new(filename))?), + _ => None, }; err.throw(ctx, code) } @@ -146,7 +158,7 @@ struct Config { pub css_modules: Option, pub analyze_dependencies: Option, pub pseudo_classes: Option, - pub unused_symbols: Option> + pub unused_symbols: Option>, } #[derive(Debug, Deserialize)] @@ -160,7 +172,7 @@ struct BundleConfig { pub css_modules: Option, pub analyze_dependencies: Option, pub pseudo_classes: Option, - pub unused_symbols: Option> + pub unused_symbols: Option>, } #[derive(Debug, Deserialize)] @@ -170,7 +182,7 @@ struct OwnedPseudoClasses { pub active: Option, pub focus: Option, pub focus_visible: Option, - pub focus_within: Option + pub focus_within: Option, } impl<'a> Into> for &'a OwnedPseudoClasses { @@ -180,7 +192,7 @@ impl<'a> Into> for &'a OwnedPseudoClasses { active: self.active.as_deref(), focus: self.focus.as_deref(), focus_visible: self.focus_visible.as_deref(), - focus_within: self.focus_within.as_deref() + focus_within: self.focus_within.as_deref(), } } } @@ -191,20 +203,24 @@ struct Drafts { #[serde(default)] nesting: bool, #[serde(default)] - custom_media: bool + custom_media: bool, } fn compile<'i>(code: &'i str, config: &Config) -> Result> { let drafts = config.drafts.as_ref(); - let mut stylesheet = StyleSheet::parse(config.filename.clone(), &code, ParserOptions { - nesting: matches!(drafts, Some(d) if d.nesting), - custom_media: matches!(drafts, Some(d) if d.custom_media), - css_modules: config.css_modules.unwrap_or(false), - source_index: 0 - })?; + let mut stylesheet = StyleSheet::parse( + config.filename.clone(), + &code, + ParserOptions { + nesting: matches!(drafts, Some(d) if d.nesting), + custom_media: matches!(drafts, Some(d) if d.custom_media), + css_modules: config.css_modules.unwrap_or(false), + source_index: 0, + }, + )?; stylesheet.minify(MinifyOptions { targets: config.targets, - unused_symbols: config.unused_symbols.clone().unwrap_or_default() + unused_symbols: config.unused_symbols.clone().unwrap_or_default(), })?; let mut source_map = if config.source_map.unwrap_or(false) { @@ -221,7 +237,7 @@ fn compile<'i>(code: &'i str, config: &Config) -> Result(code: &'i str, config: &Config) -> Result(fs: &'i FileProvider, config: &BundleConfig) -> Result(fs: &'i FileProvider, config: &BundleConfig) -> Result(fs: &'i FileProvider, config: &BundleConfig) -> Result(source_map: &mut SourceMap) -> Result, Compile mappings: unsafe { String::from_utf8_unchecked(vlq_output) }, sources: source_map.get_sources(), sources_content: source_map.get_sources_content(), - names: source_map.get_names() + names: source_map.get_names(), }; Ok(serde_json::to_vec(&sm).unwrap()) @@ -306,7 +322,7 @@ struct AttrConfig { pub code: Vec, pub targets: Option, pub minify: Option, - pub analyze_dependencies: Option + pub analyze_dependencies: Option, } #[derive(Serialize)] @@ -314,7 +330,7 @@ struct AttrConfig { struct AttrResult { #[serde(with = "serde_bytes")] code: Vec, - dependencies: Option> + dependencies: Option>, } fn compile_attr<'i>(code: &'i str, config: &AttrConfig) -> Result> { @@ -328,11 +344,11 @@ fn compile_attr<'i>(code: &'i str, config: &AttrConfig) -> Result { MinifyError(Error), PrinterError(Error), SourceMapError(parcel_sourcemap::SourceMapError), - BundleError(Error>) + BundleError(Error>), } impl<'i> CompileError<'i> { @@ -351,7 +367,7 @@ impl<'i> CompileError<'i> { CompileError::MinifyError(err) => err.kind.reason(), CompileError::PrinterError(err) => err.kind.reason(), CompileError::BundleError(err) => err.kind.reason(), - _ => "Unknown error".into() + _ => "Unknown error".into(), } } @@ -363,17 +379,16 @@ impl<'i> CompileError<'i> { CompileError::PrinterError(Error { kind, .. }) => ctx.env.to_js_value(kind)?, CompileError::MinifyError(Error { kind, .. }) => ctx.env.to_js_value(kind)?, CompileError::BundleError(Error { kind, .. }) => ctx.env.to_js_value(kind)?, - _ => ctx.env.get_null()?.into_unknown() + _ => ctx.env.get_null()?.into_unknown(), }; match self { - CompileError::ParseError(Error { loc, .. }) | - CompileError::PrinterError(Error { loc, .. }) | - CompileError::MinifyError(Error { loc, .. }) | - CompileError::BundleError(Error { loc, .. }) => { + CompileError::ParseError(Error { loc, .. }) + | CompileError::PrinterError(Error { loc, .. }) + | CompileError::MinifyError(Error { loc, .. }) + | CompileError::BundleError(Error { loc, .. }) => { // Generate an error with location information. - let syntax_error = ctx.env.get_global()? - .get_named_property::("SyntaxError")?; + let syntax_error = ctx.env.get_global()?.get_named_property::("SyntaxError")?; let reason = ctx.env.create_string_from_std(reason)?; let mut obj = syntax_error.new(&[reason])?; if let Some(loc) = loc { @@ -393,8 +408,8 @@ impl<'i> CompileError<'i> { obj.set_named_property("data", data)?; ctx.env.throw(obj)?; Ok(ctx.env.get_undefined()?.into_unknown()) - }, - _ => Err(self.into()) + } + _ => Err(self.into()), } } } @@ -434,7 +449,7 @@ impl<'i> From> for napi::Error { fn from(e: CompileError) -> napi::Error { match e { CompileError::SourceMapError(e) => napi::Error::from_reason(e.to_string()), - _ => napi::Error::new(napi::Status::GenericFailure, e.reason()) + _ => napi::Error::new(napi::Status::GenericFailure, e.reason()), } } } @@ -444,7 +459,7 @@ impl<'i> From> for wasm_bindgen::JsValue { fn from(e: CompileError) -> wasm_bindgen::JsValue { match e { CompileError::SourceMapError(e) => js_sys::Error::new(&e.to_string()).into(), - _ => js_sys::Error::new(&e.reason()).into() + _ => js_sys::Error::new(&e.reason()).into(), } } } diff --git a/selectors/attr.rs b/selectors/attr.rs index 364afa53..567fb7bd 100644 --- a/selectors/attr.rs +++ b/selectors/attr.rs @@ -8,125 +8,117 @@ use std::fmt; #[derive(Clone, Eq, PartialEq)] pub struct AttrSelectorWithOptionalNamespace<'i, Impl: SelectorImpl<'i>> { - pub namespace: Option>, - pub local_name: Impl::LocalName, - pub local_name_lower: Impl::LocalName, - pub operation: ParsedAttrSelectorOperation, - pub never_matches: bool, + pub namespace: Option>, + pub local_name: Impl::LocalName, + pub local_name_lower: Impl::LocalName, + pub operation: ParsedAttrSelectorOperation, + pub never_matches: bool, } impl<'i, Impl: SelectorImpl<'i>> AttrSelectorWithOptionalNamespace<'i, Impl> { - pub fn namespace(&self) -> Option> { - self.namespace.as_ref().map(|ns| match ns { - NamespaceConstraint::Any => NamespaceConstraint::Any, - NamespaceConstraint::Specific((_, ref url)) => NamespaceConstraint::Specific(url), - }) - } + pub fn namespace(&self) -> Option> { + self.namespace.as_ref().map(|ns| match ns { + NamespaceConstraint::Any => NamespaceConstraint::Any, + NamespaceConstraint::Specific((_, ref url)) => NamespaceConstraint::Specific(url), + }) + } } #[derive(Clone, Eq, PartialEq)] pub enum NamespaceConstraint { - Any, + Any, - /// Empty string for no namespace - Specific(NamespaceUrl), + /// Empty string for no namespace + Specific(NamespaceUrl), } #[derive(Clone, Eq, PartialEq)] pub enum ParsedAttrSelectorOperation { - Exists, - WithValue { - operator: AttrSelectorOperator, - case_sensitivity: ParsedCaseSensitivity, - expected_value: AttrValue, - }, + Exists, + WithValue { + operator: AttrSelectorOperator, + case_sensitivity: ParsedCaseSensitivity, + expected_value: AttrValue, + }, } #[derive(Clone, Eq, PartialEq)] pub enum AttrSelectorOperation { - Exists, - WithValue { - operator: AttrSelectorOperator, - case_sensitivity: CaseSensitivity, - expected_value: AttrValue, - }, + Exists, + WithValue { + operator: AttrSelectorOperator, + case_sensitivity: CaseSensitivity, + expected_value: AttrValue, + }, } impl AttrSelectorOperation { - pub fn eval_str(&self, element_attr_value: &str) -> bool - where - AttrValue: AsRef, - { - match *self { - AttrSelectorOperation::Exists => true, - AttrSelectorOperation::WithValue { - operator, - case_sensitivity, - ref expected_value, - } => operator.eval_str( - element_attr_value, - expected_value.as_ref(), - case_sensitivity, - ), - } + pub fn eval_str(&self, element_attr_value: &str) -> bool + where + AttrValue: AsRef, + { + match *self { + AttrSelectorOperation::Exists => true, + AttrSelectorOperation::WithValue { + operator, + case_sensitivity, + ref expected_value, + } => operator.eval_str(element_attr_value, expected_value.as_ref(), case_sensitivity), } + } } #[derive(Clone, Copy, Eq, PartialEq)] pub enum AttrSelectorOperator { - Equal, - Includes, - DashMatch, - Prefix, - Substring, - Suffix, + Equal, + Includes, + DashMatch, + Prefix, + Substring, + Suffix, } impl ToCss for AttrSelectorOperator { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - // https://drafts.csswg.org/cssom/#serializing-selectors - // See "attribute selector". - dest.write_str(match *self { - AttrSelectorOperator::Equal => "=", - AttrSelectorOperator::Includes => "~=", - AttrSelectorOperator::DashMatch => "|=", - AttrSelectorOperator::Prefix => "^=", - AttrSelectorOperator::Substring => "*=", - AttrSelectorOperator::Suffix => "$=", - }) - } + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + // https://drafts.csswg.org/cssom/#serializing-selectors + // See "attribute selector". + dest.write_str(match *self { + AttrSelectorOperator::Equal => "=", + AttrSelectorOperator::Includes => "~=", + AttrSelectorOperator::DashMatch => "|=", + AttrSelectorOperator::Prefix => "^=", + AttrSelectorOperator::Substring => "*=", + AttrSelectorOperator::Suffix => "$=", + }) + } } impl AttrSelectorOperator { - pub fn eval_str( - self, - element_attr_value: &str, - attr_selector_value: &str, - case_sensitivity: CaseSensitivity, - ) -> bool { - let e = element_attr_value.as_bytes(); - let s = attr_selector_value.as_bytes(); - let case = case_sensitivity; - match self { - AttrSelectorOperator::Equal => case.eq(e, s), - AttrSelectorOperator::Prefix => e.len() >= s.len() && case.eq(&e[..s.len()], s), - AttrSelectorOperator::Suffix => { - e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s) - }, - AttrSelectorOperator::Substring => { - case.contains(element_attr_value, attr_selector_value) - }, - AttrSelectorOperator::Includes => element_attr_value - .split(SELECTOR_WHITESPACE) - .any(|part| case.eq(part.as_bytes(), s)), - AttrSelectorOperator::DashMatch => { - case.eq(e, s) || (e.get(s.len()) == Some(&b'-') && case.eq(&e[..s.len()], s)) - }, - } + pub fn eval_str( + self, + element_attr_value: &str, + attr_selector_value: &str, + case_sensitivity: CaseSensitivity, + ) -> bool { + let e = element_attr_value.as_bytes(); + let s = attr_selector_value.as_bytes(); + let case = case_sensitivity; + match self { + AttrSelectorOperator::Equal => case.eq(e, s), + AttrSelectorOperator::Prefix => e.len() >= s.len() && case.eq(&e[..s.len()], s), + AttrSelectorOperator::Suffix => e.len() >= s.len() && case.eq(&e[(e.len() - s.len())..], s), + AttrSelectorOperator::Substring => case.contains(element_attr_value, attr_selector_value), + AttrSelectorOperator::Includes => element_attr_value + .split(SELECTOR_WHITESPACE) + .any(|part| case.eq(part.as_bytes(), s)), + AttrSelectorOperator::DashMatch => { + case.eq(e, s) || (e.get(s.len()) == Some(&b'-') && case.eq(&e[..s.len()], s)) + } } + } } /// The definition of whitespace per CSS Selectors Level 3 § 4. @@ -134,70 +126,68 @@ pub static SELECTOR_WHITESPACE: &[char] = &[' ', '\t', '\n', '\r', '\x0C']; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ParsedCaseSensitivity { - // 's' was specified. - ExplicitCaseSensitive, - // 'i' was specified. - AsciiCaseInsensitive, - // No flags were specified and HTML says this is a case-sensitive attribute. - CaseSensitive, - // No flags were specified and HTML says this is a case-insensitive attribute. - AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument, + // 's' was specified. + ExplicitCaseSensitive, + // 'i' was specified. + AsciiCaseInsensitive, + // No flags were specified and HTML says this is a case-sensitive attribute. + CaseSensitive, + // No flags were specified and HTML says this is a case-insensitive attribute. + AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument, } impl ParsedCaseSensitivity { - pub fn to_unconditional(self, is_html_element_in_html_document: bool) -> CaseSensitivity { - match self { - ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument - if is_html_element_in_html_document => - { - CaseSensitivity::AsciiCaseInsensitive - }, - ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => { - CaseSensitivity::CaseSensitive - }, - ParsedCaseSensitivity::CaseSensitive | ParsedCaseSensitivity::ExplicitCaseSensitive => { - CaseSensitivity::CaseSensitive - }, - ParsedCaseSensitivity::AsciiCaseInsensitive => CaseSensitivity::AsciiCaseInsensitive, - } + pub fn to_unconditional(self, is_html_element_in_html_document: bool) -> CaseSensitivity { + match self { + ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument + if is_html_element_in_html_document => + { + CaseSensitivity::AsciiCaseInsensitive + } + ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => CaseSensitivity::CaseSensitive, + ParsedCaseSensitivity::CaseSensitive | ParsedCaseSensitivity::ExplicitCaseSensitive => { + CaseSensitivity::CaseSensitive + } + ParsedCaseSensitivity::AsciiCaseInsensitive => CaseSensitivity::AsciiCaseInsensitive, } + } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum CaseSensitivity { - CaseSensitive, - AsciiCaseInsensitive, + CaseSensitive, + AsciiCaseInsensitive, } impl CaseSensitivity { - pub fn eq(self, a: &[u8], b: &[u8]) -> bool { - match self { - CaseSensitivity::CaseSensitive => a == b, - CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b), - } + pub fn eq(self, a: &[u8], b: &[u8]) -> bool { + match self { + CaseSensitivity::CaseSensitive => a == b, + CaseSensitivity::AsciiCaseInsensitive => a.eq_ignore_ascii_case(b), } - - pub fn contains(self, haystack: &str, needle: &str) -> bool { - match self { - CaseSensitivity::CaseSensitive => haystack.contains(needle), - CaseSensitivity::AsciiCaseInsensitive => { - if let Some((&n_first_byte, n_rest)) = needle.as_bytes().split_first() { - haystack.bytes().enumerate().any(|(i, byte)| { - if !byte.eq_ignore_ascii_case(&n_first_byte) { - return false; - } - let after_this_byte = &haystack.as_bytes()[i + 1..]; - match after_this_byte.get(..n_rest.len()) { - None => false, - Some(haystack_slice) => haystack_slice.eq_ignore_ascii_case(n_rest), - } - }) - } else { - // any_str.contains("") == true, - // though these cases should be handled with *NeverMatches and never go here. - true - } - }, + } + + pub fn contains(self, haystack: &str, needle: &str) -> bool { + match self { + CaseSensitivity::CaseSensitive => haystack.contains(needle), + CaseSensitivity::AsciiCaseInsensitive => { + if let Some((&n_first_byte, n_rest)) = needle.as_bytes().split_first() { + haystack.bytes().enumerate().any(|(i, byte)| { + if !byte.eq_ignore_ascii_case(&n_first_byte) { + return false; + } + let after_this_byte = &haystack.as_bytes()[i + 1..]; + match after_this_byte.get(..n_rest.len()) { + None => false, + Some(haystack_slice) => haystack_slice.eq_ignore_ascii_case(n_rest), + } + }) + } else { + // any_str.contains("") == true, + // though these cases should be handled with *NeverMatches and never go here. + true } + } } + } } diff --git a/selectors/bloom.rs b/selectors/bloom.rs index 98461d1b..bbfbee45 100644 --- a/selectors/bloom.rs +++ b/selectors/bloom.rs @@ -67,356 +67,352 @@ pub type BloomFilter = CountingBloomFilter; #[derive(Clone, Default)] pub struct CountingBloomFilter where - S: BloomStorage, + S: BloomStorage, { - storage: S, + storage: S, } impl CountingBloomFilter where - S: BloomStorage, + S: BloomStorage, { - /// Creates a new bloom filter. - #[inline] - pub fn new() -> Self { - Default::default() - } - - #[inline] - pub fn clear(&mut self) { - self.storage = Default::default(); - } - - // Slow linear accessor to make sure the bloom filter is zeroed. This should - // never be used in release builds. - #[cfg(debug_assertions)] - pub fn is_zeroed(&self) -> bool { - self.storage.is_zeroed() - } - - #[cfg(not(debug_assertions))] - pub fn is_zeroed(&self) -> bool { - unreachable!() - } - - /// Inserts an item with a particular hash into the bloom filter. - #[inline] - pub fn insert_hash(&mut self, hash: u32) { - self.storage.adjust_first_slot(hash, true); - self.storage.adjust_second_slot(hash, true); - } - - /// Removes an item with a particular hash from the bloom filter. - #[inline] - pub fn remove_hash(&mut self, hash: u32) { - self.storage.adjust_first_slot(hash, false); - self.storage.adjust_second_slot(hash, false); - } - - /// Check whether the filter might contain an item with the given hash. - /// This can sometimes return true even if the item is not in the filter, - /// but will never return false for items that are actually in the filter. - #[inline] - pub fn might_contain_hash(&self, hash: u32) -> bool { - !self.storage.first_slot_is_empty(hash) && !self.storage.second_slot_is_empty(hash) - } + /// Creates a new bloom filter. + #[inline] + pub fn new() -> Self { + Default::default() + } + + #[inline] + pub fn clear(&mut self) { + self.storage = Default::default(); + } + + // Slow linear accessor to make sure the bloom filter is zeroed. This should + // never be used in release builds. + #[cfg(debug_assertions)] + pub fn is_zeroed(&self) -> bool { + self.storage.is_zeroed() + } + + #[cfg(not(debug_assertions))] + pub fn is_zeroed(&self) -> bool { + unreachable!() + } + + /// Inserts an item with a particular hash into the bloom filter. + #[inline] + pub fn insert_hash(&mut self, hash: u32) { + self.storage.adjust_first_slot(hash, true); + self.storage.adjust_second_slot(hash, true); + } + + /// Removes an item with a particular hash from the bloom filter. + #[inline] + pub fn remove_hash(&mut self, hash: u32) { + self.storage.adjust_first_slot(hash, false); + self.storage.adjust_second_slot(hash, false); + } + + /// Check whether the filter might contain an item with the given hash. + /// This can sometimes return true even if the item is not in the filter, + /// but will never return false for items that are actually in the filter. + #[inline] + pub fn might_contain_hash(&self, hash: u32) -> bool { + !self.storage.first_slot_is_empty(hash) && !self.storage.second_slot_is_empty(hash) + } } impl Debug for CountingBloomFilter where - S: BloomStorage, + S: BloomStorage, { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut slots_used = 0; - for i in 0..ARRAY_SIZE { - if !self.storage.slot_is_empty(i) { - slots_used += 1; - } - } - write!(f, "BloomFilter({}/{})", slots_used, ARRAY_SIZE) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut slots_used = 0; + for i in 0..ARRAY_SIZE { + if !self.storage.slot_is_empty(i) { + slots_used += 1; + } + } + write!(f, "BloomFilter({}/{})", slots_used, ARRAY_SIZE) + } } pub trait BloomStorage: Clone + Default { - fn slot_is_empty(&self, index: usize) -> bool; - fn adjust_slot(&mut self, index: usize, increment: bool); - fn is_zeroed(&self) -> bool; - - #[inline] - fn first_slot_is_empty(&self, hash: u32) -> bool { - self.slot_is_empty(Self::first_slot_index(hash)) - } - - #[inline] - fn second_slot_is_empty(&self, hash: u32) -> bool { - self.slot_is_empty(Self::second_slot_index(hash)) - } - - #[inline] - fn adjust_first_slot(&mut self, hash: u32, increment: bool) { - self.adjust_slot(Self::first_slot_index(hash), increment) - } - - #[inline] - fn adjust_second_slot(&mut self, hash: u32, increment: bool) { - self.adjust_slot(Self::second_slot_index(hash), increment) - } - - #[inline] - fn first_slot_index(hash: u32) -> usize { - hash1(hash) as usize - } - - #[inline] - fn second_slot_index(hash: u32) -> usize { - hash2(hash) as usize - } + fn slot_is_empty(&self, index: usize) -> bool; + fn adjust_slot(&mut self, index: usize, increment: bool); + fn is_zeroed(&self) -> bool; + + #[inline] + fn first_slot_is_empty(&self, hash: u32) -> bool { + self.slot_is_empty(Self::first_slot_index(hash)) + } + + #[inline] + fn second_slot_is_empty(&self, hash: u32) -> bool { + self.slot_is_empty(Self::second_slot_index(hash)) + } + + #[inline] + fn adjust_first_slot(&mut self, hash: u32, increment: bool) { + self.adjust_slot(Self::first_slot_index(hash), increment) + } + + #[inline] + fn adjust_second_slot(&mut self, hash: u32, increment: bool) { + self.adjust_slot(Self::second_slot_index(hash), increment) + } + + #[inline] + fn first_slot_index(hash: u32) -> usize { + hash1(hash) as usize + } + + #[inline] + fn second_slot_index(hash: u32) -> usize { + hash2(hash) as usize + } } /// Storage class for a CountingBloomFilter that has 8-bit counters. pub struct BloomStorageU8 { - counters: [u8; ARRAY_SIZE], + counters: [u8; ARRAY_SIZE], } impl BloomStorage for BloomStorageU8 { - #[inline] - fn adjust_slot(&mut self, index: usize, increment: bool) { - let slot = &mut self.counters[index]; - if *slot != 0xff { - // full - if increment { - *slot += 1; - } else { - *slot -= 1; - } - } - } - - #[inline] - fn slot_is_empty(&self, index: usize) -> bool { - self.counters[index] == 0 - } - - #[inline] - fn is_zeroed(&self) -> bool { - self.counters.iter().all(|x| *x == 0) - } + #[inline] + fn adjust_slot(&mut self, index: usize, increment: bool) { + let slot = &mut self.counters[index]; + if *slot != 0xff { + // full + if increment { + *slot += 1; + } else { + *slot -= 1; + } + } + } + + #[inline] + fn slot_is_empty(&self, index: usize) -> bool { + self.counters[index] == 0 + } + + #[inline] + fn is_zeroed(&self) -> bool { + self.counters.iter().all(|x| *x == 0) + } } impl Default for BloomStorageU8 { - fn default() -> Self { - BloomStorageU8 { - counters: [0; ARRAY_SIZE], - } + fn default() -> Self { + BloomStorageU8 { + counters: [0; ARRAY_SIZE], } + } } impl Clone for BloomStorageU8 { - fn clone(&self) -> Self { - BloomStorageU8 { - counters: self.counters, - } + fn clone(&self) -> Self { + BloomStorageU8 { + counters: self.counters, } + } } /// Storage class for a CountingBloomFilter that has 1-bit counters. pub struct BloomStorageBool { - counters: [u8; ARRAY_SIZE / 8], + counters: [u8; ARRAY_SIZE / 8], } impl BloomStorage for BloomStorageBool { - #[inline] - fn adjust_slot(&mut self, index: usize, increment: bool) { - let bit = 1 << (index % 8); - let byte = &mut self.counters[index / 8]; - - // Since we have only one bit for storage, decrementing it - // should never do anything. Assert against an accidental - // decrementing of a bit that was never set. - assert!( - increment || (*byte & bit) != 0, - "should not decrement if slot is already false" - ); - - if increment { - *byte |= bit; - } - } - - #[inline] - fn slot_is_empty(&self, index: usize) -> bool { - let bit = 1 << (index % 8); - (self.counters[index / 8] & bit) == 0 - } - - #[inline] - fn is_zeroed(&self) -> bool { - self.counters.iter().all(|x| *x == 0) - } + #[inline] + fn adjust_slot(&mut self, index: usize, increment: bool) { + let bit = 1 << (index % 8); + let byte = &mut self.counters[index / 8]; + + // Since we have only one bit for storage, decrementing it + // should never do anything. Assert against an accidental + // decrementing of a bit that was never set. + assert!( + increment || (*byte & bit) != 0, + "should not decrement if slot is already false" + ); + + if increment { + *byte |= bit; + } + } + + #[inline] + fn slot_is_empty(&self, index: usize) -> bool { + let bit = 1 << (index % 8); + (self.counters[index / 8] & bit) == 0 + } + + #[inline] + fn is_zeroed(&self) -> bool { + self.counters.iter().all(|x| *x == 0) + } } impl Default for BloomStorageBool { - fn default() -> Self { - BloomStorageBool { - counters: [0; ARRAY_SIZE / 8], - } + fn default() -> Self { + BloomStorageBool { + counters: [0; ARRAY_SIZE / 8], } + } } impl Clone for BloomStorageBool { - fn clone(&self) -> Self { - BloomStorageBool { - counters: self.counters, - } + fn clone(&self) -> Self { + BloomStorageBool { + counters: self.counters, } + } } #[inline] fn hash1(hash: u32) -> u32 { - hash & KEY_MASK + hash & KEY_MASK } #[inline] fn hash2(hash: u32) -> u32 { - (hash >> KEY_SIZE) & KEY_MASK + (hash >> KEY_SIZE) & KEY_MASK } #[test] fn create_and_insert_some_stuff() { - use fxhash::FxHasher; - use std::hash::{Hash, Hasher}; - use std::mem::transmute; - - fn hash_as_str(i: usize) -> u32 { - let mut hasher = FxHasher::default(); - let s = i.to_string(); - s.hash(&mut hasher); - let hash: u64 = hasher.finish(); - (hash >> 32) as u32 ^ (hash as u32) - } + use fxhash::FxHasher; + use std::hash::{Hash, Hasher}; + use std::mem::transmute; - let mut bf = BloomFilter::new(); + fn hash_as_str(i: usize) -> u32 { + let mut hasher = FxHasher::default(); + let s = i.to_string(); + s.hash(&mut hasher); + let hash: u64 = hasher.finish(); + (hash >> 32) as u32 ^ (hash as u32) + } - // Statically assert that ARRAY_SIZE is a multiple of 8, which - // BloomStorageBool relies on. - unsafe { - transmute::<[u8; ARRAY_SIZE % 8], [u8; 0]>([]); - } + let mut bf = BloomFilter::new(); - for i in 0_usize..1000 { - bf.insert_hash(hash_as_str(i)); - } + // Statically assert that ARRAY_SIZE is a multiple of 8, which + // BloomStorageBool relies on. + unsafe { + transmute::<[u8; ARRAY_SIZE % 8], [u8; 0]>([]); + } - for i in 0_usize..1000 { - assert!(bf.might_contain_hash(hash_as_str(i))); - } + for i in 0_usize..1000 { + bf.insert_hash(hash_as_str(i)); + } - let false_positives = (1001_usize..2000) - .filter(|i| bf.might_contain_hash(hash_as_str(*i))) - .count(); + for i in 0_usize..1000 { + assert!(bf.might_contain_hash(hash_as_str(i))); + } - assert!(false_positives < 190, "{} is not < 190", false_positives); // 19%. + let false_positives = (1001_usize..2000).filter(|i| bf.might_contain_hash(hash_as_str(*i))).count(); - for i in 0_usize..100 { - bf.remove_hash(hash_as_str(i)); - } + assert!(false_positives < 190, "{} is not < 190", false_positives); // 19%. - for i in 100_usize..1000 { - assert!(bf.might_contain_hash(hash_as_str(i))); - } + for i in 0_usize..100 { + bf.remove_hash(hash_as_str(i)); + } - let false_positives = (0_usize..100) - .filter(|i| bf.might_contain_hash(hash_as_str(*i))) - .count(); + for i in 100_usize..1000 { + assert!(bf.might_contain_hash(hash_as_str(i))); + } - assert!(false_positives < 20, "{} is not < 20", false_positives); // 20%. + let false_positives = (0_usize..100).filter(|i| bf.might_contain_hash(hash_as_str(*i))).count(); - bf.clear(); + assert!(false_positives < 20, "{} is not < 20", false_positives); // 20%. - for i in 0_usize..2000 { - assert!(!bf.might_contain_hash(hash_as_str(i))); - } + bf.clear(); + + for i in 0_usize..2000 { + assert!(!bf.might_contain_hash(hash_as_str(i))); + } } #[cfg(feature = "bench")] #[cfg(test)] mod bench { - extern crate test; - use super::BloomFilter; - - #[derive(Default)] - struct HashGenerator(u32); - - impl HashGenerator { - fn next(&mut self) -> u32 { - // Each hash is split into two twelve-bit segments, which are used - // as an index into an array. We increment each by 64 so that we - // hit the next cache line, and then another 1 so that our wrapping - // behavior leads us to different entries each time. - // - // Trying to simulate cold caches is rather difficult with the cargo - // benchmarking setup, so it may all be moot depending on the number - // of iterations that end up being run. But we might as well. - self.0 += (65) + (65 << super::KEY_SIZE); - self.0 - } - } - - #[bench] - fn create_insert_1000_remove_100_lookup_100(b: &mut test::Bencher) { - b.iter(|| { - let mut gen1 = HashGenerator::default(); - let mut gen2 = HashGenerator::default(); - let mut bf = BloomFilter::new(); - for _ in 0_usize..1000 { - bf.insert_hash(gen1.next()); - } - for _ in 0_usize..100 { - bf.remove_hash(gen2.next()); - } - for _ in 100_usize..200 { - test::black_box(bf.might_contain_hash(gen2.next())); - } - }); - } - - #[bench] - fn might_contain_10(b: &mut test::Bencher) { - let bf = BloomFilter::new(); - let mut gen = HashGenerator::default(); - b.iter(|| { - for _ in 0..10 { - test::black_box(bf.might_contain_hash(gen.next())); - } - }); - } - - #[bench] - fn clear(b: &mut test::Bencher) { - let mut bf = Box::new(BloomFilter::new()); - b.iter(|| test::black_box(&mut bf).clear()); - } - - #[bench] - fn insert_10(b: &mut test::Bencher) { - let mut bf = BloomFilter::new(); - let mut gen = HashGenerator::default(); - b.iter(|| { - for _ in 0..10 { - test::black_box(bf.insert_hash(gen.next())); - } - }); - } - - #[bench] - fn remove_10(b: &mut test::Bencher) { - let mut bf = BloomFilter::new(); - let mut gen = HashGenerator::default(); - // Note: this will underflow, and that's ok. - b.iter(|| { - for _ in 0..10 { - bf.remove_hash(gen.next()) - } - }); - } + extern crate test; + use super::BloomFilter; + + #[derive(Default)] + struct HashGenerator(u32); + + impl HashGenerator { + fn next(&mut self) -> u32 { + // Each hash is split into two twelve-bit segments, which are used + // as an index into an array. We increment each by 64 so that we + // hit the next cache line, and then another 1 so that our wrapping + // behavior leads us to different entries each time. + // + // Trying to simulate cold caches is rather difficult with the cargo + // benchmarking setup, so it may all be moot depending on the number + // of iterations that end up being run. But we might as well. + self.0 += (65) + (65 << super::KEY_SIZE); + self.0 + } + } + + #[bench] + fn create_insert_1000_remove_100_lookup_100(b: &mut test::Bencher) { + b.iter(|| { + let mut gen1 = HashGenerator::default(); + let mut gen2 = HashGenerator::default(); + let mut bf = BloomFilter::new(); + for _ in 0_usize..1000 { + bf.insert_hash(gen1.next()); + } + for _ in 0_usize..100 { + bf.remove_hash(gen2.next()); + } + for _ in 100_usize..200 { + test::black_box(bf.might_contain_hash(gen2.next())); + } + }); + } + + #[bench] + fn might_contain_10(b: &mut test::Bencher) { + let bf = BloomFilter::new(); + let mut gen = HashGenerator::default(); + b.iter(|| { + for _ in 0..10 { + test::black_box(bf.might_contain_hash(gen.next())); + } + }); + } + + #[bench] + fn clear(b: &mut test::Bencher) { + let mut bf = Box::new(BloomFilter::new()); + b.iter(|| test::black_box(&mut bf).clear()); + } + + #[bench] + fn insert_10(b: &mut test::Bencher) { + let mut bf = BloomFilter::new(); + let mut gen = HashGenerator::default(); + b.iter(|| { + for _ in 0..10 { + test::black_box(bf.insert_hash(gen.next())); + } + }); + } + + #[bench] + fn remove_10(b: &mut test::Bencher) { + let mut bf = BloomFilter::new(); + let mut gen = HashGenerator::default(); + // Note: this will underflow, and that's ok. + b.iter(|| { + for _ in 0..10 { + bf.remove_hash(gen.next()) + } + }); + } } diff --git a/selectors/build.rs b/selectors/build.rs index c5c38039..945bb9bb 100644 --- a/selectors/build.rs +++ b/selectors/build.rs @@ -10,20 +10,19 @@ use std::io::{BufWriter, Write}; use std::path::Path; fn main() { - let path = Path::new(&env::var_os("OUT_DIR").unwrap()) - .join("ascii_case_insensitive_html_attributes.rs"); - let mut file = BufWriter::new(File::create(&path).unwrap()); + let path = Path::new(&env::var_os("OUT_DIR").unwrap()).join("ascii_case_insensitive_html_attributes.rs"); + let mut file = BufWriter::new(File::create(&path).unwrap()); - let mut set = phf_codegen::Set::new(); - for name in ASCII_CASE_INSENSITIVE_HTML_ATTRIBUTES.split_whitespace() { - set.entry(name); - } - write!( - &mut file, - "{{ static SET: ::phf::Set<&'static str> = {}; &SET }}", - set.build(), - ) - .unwrap(); + let mut set = phf_codegen::Set::new(); + for name in ASCII_CASE_INSENSITIVE_HTML_ATTRIBUTES.split_whitespace() { + set.entry(name); + } + write!( + &mut file, + "{{ static SET: ::phf::Set<&'static str> = {}; &SET }}", + set.build(), + ) + .unwrap(); } /// diff --git a/selectors/builder.rs b/selectors/builder.rs index 27bdfa09..b428db6b 100644 --- a/selectors/builder.rs +++ b/selectors/builder.rs @@ -37,149 +37,147 @@ use std::slice; /// allocated Selector and leaves the builder in a drained state. #[derive(Debug)] pub struct SelectorBuilder<'i, Impl: SelectorImpl<'i>> { - /// The entire sequence of simple selectors, from left to right, without combinators. - /// - /// We make this large because the result of parsing a selector is fed into a new - /// Arc-ed allocation, so any spilled vec would be a wasted allocation. Also, - /// Components are large enough that we don't have much cache locality benefit - /// from reserving stack space for fewer of them. - simple_selectors: SmallVec<[Component<'i, Impl>; 32]>, - /// The combinators, and the length of the compound selector to their left. - combinators: SmallVec<[(Combinator, usize); 16]>, - /// The length of the current compount selector. - current_len: usize, + /// The entire sequence of simple selectors, from left to right, without combinators. + /// + /// We make this large because the result of parsing a selector is fed into a new + /// Arc-ed allocation, so any spilled vec would be a wasted allocation. Also, + /// Components are large enough that we don't have much cache locality benefit + /// from reserving stack space for fewer of them. + simple_selectors: SmallVec<[Component<'i, Impl>; 32]>, + /// The combinators, and the length of the compound selector to their left. + combinators: SmallVec<[(Combinator, usize); 16]>, + /// The length of the current compount selector. + current_len: usize, } impl<'i, Impl: SelectorImpl<'i>> Default for SelectorBuilder<'i, Impl> { - #[inline(always)] - fn default() -> Self { - SelectorBuilder { - simple_selectors: SmallVec::new(), - combinators: SmallVec::new(), - current_len: 0, - } + #[inline(always)] + fn default() -> Self { + SelectorBuilder { + simple_selectors: SmallVec::new(), + combinators: SmallVec::new(), + current_len: 0, } + } } impl<'i, Impl: SelectorImpl<'i>> Push> for SelectorBuilder<'i, Impl> { - fn push(&mut self, value: Component<'i, Impl>) { - self.push_simple_selector(value); - } + fn push(&mut self, value: Component<'i, Impl>) { + self.push_simple_selector(value); + } } impl<'i, Impl: SelectorImpl<'i>> SelectorBuilder<'i, Impl> { - /// Pushes a simple selector onto the current compound selector. - #[inline(always)] - pub fn push_simple_selector(&mut self, ss: Component<'i, Impl>) { - assert!(!ss.is_combinator()); - self.simple_selectors.push(ss); - self.current_len += 1; - } + /// Pushes a simple selector onto the current compound selector. + #[inline(always)] + pub fn push_simple_selector(&mut self, ss: Component<'i, Impl>) { + assert!(!ss.is_combinator()); + self.simple_selectors.push(ss); + self.current_len += 1; + } - /// Completes the current compound selector and starts a new one, delimited - /// by the given combinator. - #[inline(always)] - pub fn push_combinator(&mut self, c: Combinator) { - self.combinators.push((c, self.current_len)); - self.current_len = 0; - } + /// Completes the current compound selector and starts a new one, delimited + /// by the given combinator. + #[inline(always)] + pub fn push_combinator(&mut self, c: Combinator) { + self.combinators.push((c, self.current_len)); + self.current_len = 0; + } - /// Returns true if combinators have ever been pushed to this builder. - #[inline(always)] - pub fn has_combinators(&self) -> bool { - !self.combinators.is_empty() - } + /// Returns true if combinators have ever been pushed to this builder. + #[inline(always)] + pub fn has_combinators(&self) -> bool { + !self.combinators.is_empty() + } - /// Consumes the builder, producing a Selector. - #[inline(always)] - pub fn build( - &mut self, - parsed_pseudo: bool, - parsed_slotted: bool, - parsed_part: bool, - ) -> (SpecificityAndFlags, Vec>) { - // Compute the specificity and flags. - let specificity = specificity(self.simple_selectors.iter()); - let mut flags = SelectorFlags::empty(); - if parsed_pseudo { - flags |= SelectorFlags::HAS_PSEUDO; - } - if parsed_slotted { - flags |= SelectorFlags::HAS_SLOTTED; - } - if parsed_part { - flags |= SelectorFlags::HAS_PART; - } - self.build_with_specificity_and_flags(SpecificityAndFlags { specificity, flags }) + /// Consumes the builder, producing a Selector. + #[inline(always)] + pub fn build( + &mut self, + parsed_pseudo: bool, + parsed_slotted: bool, + parsed_part: bool, + ) -> (SpecificityAndFlags, Vec>) { + // Compute the specificity and flags. + let specificity = specificity(self.simple_selectors.iter()); + let mut flags = SelectorFlags::empty(); + if parsed_pseudo { + flags |= SelectorFlags::HAS_PSEUDO; } + if parsed_slotted { + flags |= SelectorFlags::HAS_SLOTTED; + } + if parsed_part { + flags |= SelectorFlags::HAS_PART; + } + self.build_with_specificity_and_flags(SpecificityAndFlags { specificity, flags }) + } - /// Builds with an explicit SpecificityAndFlags. This is separated from build() so - /// that unit tests can pass an explicit specificity. - #[inline(always)] - pub fn build_with_specificity_and_flags( - &mut self, - spec: SpecificityAndFlags, - ) -> (SpecificityAndFlags, Vec>) { - // Use a raw pointer to be able to call set_len despite "borrowing" the slice. - // This is similar to SmallVec::drain, but we use a slice here because - // we’re gonna traverse it non-linearly. - let raw_simple_selectors: *const [Component] = &*self.simple_selectors; - unsafe { - // Panic-safety: if SelectorBuilderIter is not iterated to the end, - // some simple selectors will safely leak. - self.simple_selectors.set_len(0) - } - let (rest, current) = split_from_end(unsafe { &*raw_simple_selectors }, self.current_len); - let iter = SelectorBuilderIter { - current_simple_selectors: current.iter(), - rest_of_simple_selectors: rest, - combinators: self.combinators.drain(..).rev(), - }; - - (spec, iter.collect()) + /// Builds with an explicit SpecificityAndFlags. This is separated from build() so + /// that unit tests can pass an explicit specificity. + #[inline(always)] + pub fn build_with_specificity_and_flags( + &mut self, + spec: SpecificityAndFlags, + ) -> (SpecificityAndFlags, Vec>) { + // Use a raw pointer to be able to call set_len despite "borrowing" the slice. + // This is similar to SmallVec::drain, but we use a slice here because + // we’re gonna traverse it non-linearly. + let raw_simple_selectors: *const [Component] = &*self.simple_selectors; + unsafe { + // Panic-safety: if SelectorBuilderIter is not iterated to the end, + // some simple selectors will safely leak. + self.simple_selectors.set_len(0) } + let (rest, current) = split_from_end(unsafe { &*raw_simple_selectors }, self.current_len); + let iter = SelectorBuilderIter { + current_simple_selectors: current.iter(), + rest_of_simple_selectors: rest, + combinators: self.combinators.drain(..).rev(), + }; + + (spec, iter.collect()) + } } struct SelectorBuilderIter<'a, 'i, Impl: SelectorImpl<'i>> { - current_simple_selectors: slice::Iter<'a, Component<'i, Impl>>, - rest_of_simple_selectors: &'a [Component<'i, Impl>], - combinators: iter::Rev>, + current_simple_selectors: slice::Iter<'a, Component<'i, Impl>>, + rest_of_simple_selectors: &'a [Component<'i, Impl>], + combinators: iter::Rev>, } impl<'a, 'i, Impl: SelectorImpl<'i>> ExactSizeIterator for SelectorBuilderIter<'a, 'i, Impl> { - fn len(&self) -> usize { - self.current_simple_selectors.len() + - self.rest_of_simple_selectors.len() + - self.combinators.len() - } + fn len(&self) -> usize { + self.current_simple_selectors.len() + self.rest_of_simple_selectors.len() + self.combinators.len() + } } impl<'a, 'i, Impl: SelectorImpl<'i>> Iterator for SelectorBuilderIter<'a, 'i, Impl> { - type Item = Component<'i, Impl>; - #[inline(always)] - fn next(&mut self) -> Option { - if let Some(simple_selector_ref) = self.current_simple_selectors.next() { - // Move a simple selector out of this slice iterator. - // This is safe because we’ve called SmallVec::set_len(0) above, - // so SmallVec::drop won’t drop this simple selector. - unsafe { Some(ptr::read(simple_selector_ref)) } - } else { - self.combinators.next().map(|(combinator, len)| { - let (rest, current) = split_from_end(self.rest_of_simple_selectors, len); - self.rest_of_simple_selectors = rest; - self.current_simple_selectors = current.iter(); - Component::Combinator(combinator) - }) - } + type Item = Component<'i, Impl>; + #[inline(always)] + fn next(&mut self) -> Option { + if let Some(simple_selector_ref) = self.current_simple_selectors.next() { + // Move a simple selector out of this slice iterator. + // This is safe because we’ve called SmallVec::set_len(0) above, + // so SmallVec::drop won’t drop this simple selector. + unsafe { Some(ptr::read(simple_selector_ref)) } + } else { + self.combinators.next().map(|(combinator, len)| { + let (rest, current) = split_from_end(self.rest_of_simple_selectors, len); + self.rest_of_simple_selectors = rest; + self.current_simple_selectors = current.iter(); + Component::Combinator(combinator) + }) } + } - fn size_hint(&self) -> (usize, Option) { - (self.len(), Some(self.len())) - } + fn size_hint(&self) -> (usize, Option) { + (self.len(), Some(self.len())) + } } fn split_from_end(s: &[T], at: usize) -> (&[T], &[T]) { - s.split_at(s.len() - at) + s.split_at(s.len() - at) } bitflags! { @@ -194,159 +192,157 @@ bitflags! { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct SpecificityAndFlags { - /// There are two free bits here, since we use ten bits for each specificity - /// kind (id, class, element). - pub(crate) specificity: u32, - /// There's padding after this field due to the size of the flags. - pub(crate) flags: SelectorFlags, + /// There are two free bits here, since we use ten bits for each specificity + /// kind (id, class, element). + pub(crate) specificity: u32, + /// There's padding after this field due to the size of the flags. + pub(crate) flags: SelectorFlags, } impl SpecificityAndFlags { - #[inline] - pub fn specificity(&self) -> u32 { - self.specificity - } + #[inline] + pub fn specificity(&self) -> u32 { + self.specificity + } - #[inline] - pub fn has_pseudo_element(&self) -> bool { - self.flags.intersects(SelectorFlags::HAS_PSEUDO) - } + #[inline] + pub fn has_pseudo_element(&self) -> bool { + self.flags.intersects(SelectorFlags::HAS_PSEUDO) + } - #[inline] - pub fn is_slotted(&self) -> bool { - self.flags.intersects(SelectorFlags::HAS_SLOTTED) - } + #[inline] + pub fn is_slotted(&self) -> bool { + self.flags.intersects(SelectorFlags::HAS_SLOTTED) + } - #[inline] - pub fn is_part(&self) -> bool { - self.flags.intersects(SelectorFlags::HAS_PART) - } + #[inline] + pub fn is_part(&self) -> bool { + self.flags.intersects(SelectorFlags::HAS_PART) + } } const MAX_10BIT: u32 = (1u32 << 10) - 1; #[derive(Add, AddAssign, Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)] struct Specificity { - id_selectors: u32, - class_like_selectors: u32, - element_selectors: u32, + id_selectors: u32, + class_like_selectors: u32, + element_selectors: u32, } impl From for Specificity { - #[inline] - fn from(value: u32) -> Specificity { - assert!(value <= MAX_10BIT << 20 | MAX_10BIT << 10 | MAX_10BIT); - Specificity { - id_selectors: value >> 20, - class_like_selectors: (value >> 10) & MAX_10BIT, - element_selectors: value & MAX_10BIT, - } + #[inline] + fn from(value: u32) -> Specificity { + assert!(value <= MAX_10BIT << 20 | MAX_10BIT << 10 | MAX_10BIT); + Specificity { + id_selectors: value >> 20, + class_like_selectors: (value >> 10) & MAX_10BIT, + element_selectors: value & MAX_10BIT, } + } } impl From for u32 { - #[inline] - fn from(specificity: Specificity) -> u32 { - cmp::min(specificity.id_selectors, MAX_10BIT) << 20 | - cmp::min(specificity.class_like_selectors, MAX_10BIT) << 10 | - cmp::min(specificity.element_selectors, MAX_10BIT) - } + #[inline] + fn from(specificity: Specificity) -> u32 { + cmp::min(specificity.id_selectors, MAX_10BIT) << 20 + | cmp::min(specificity.class_like_selectors, MAX_10BIT) << 10 + | cmp::min(specificity.element_selectors, MAX_10BIT) + } } fn specificity<'i, Impl>(iter: slice::Iter>) -> u32 where - Impl: SelectorImpl<'i>, + Impl: SelectorImpl<'i>, { - complex_selector_specificity(iter).into() + complex_selector_specificity(iter).into() } fn complex_selector_specificity<'i, Impl>(iter: slice::Iter>) -> Specificity where - Impl: SelectorImpl<'i>, + Impl: SelectorImpl<'i>, { - fn simple_selector_specificity<'i, Impl>( - simple_selector: &Component<'i, Impl>, - specificity: &mut Specificity, - ) where - Impl: SelectorImpl<'i>, - { - match *simple_selector { - Component::Combinator(..) => { - unreachable!("Found combinator in simple selectors vector?"); - }, - Component::Part(..) | Component::PseudoElement(..) | Component::LocalName(..) => { - specificity.element_selectors += 1 - }, - Component::Slotted(ref selector) => { - specificity.element_selectors += 1; - // Note that due to the way ::slotted works we only compete with - // other ::slotted rules, so the above rule doesn't really - // matter, but we do it still for consistency with other - // pseudo-elements. - // - // See: https://github.com/w3c/csswg-drafts/issues/1915 - *specificity += Specificity::from(selector.specificity()); - }, - Component::Host(ref selector) => { - specificity.class_like_selectors += 1; - if let Some(ref selector) = *selector { - // See: https://github.com/w3c/csswg-drafts/issues/1915 - *specificity += Specificity::from(selector.specificity()); - } - }, - Component::ID(..) => { - specificity.id_selectors += 1; - }, - Component::Class(..) | - Component::AttributeInNoNamespace { .. } | - Component::AttributeInNoNamespaceExists { .. } | - Component::AttributeOther(..) | - Component::FirstChild | - Component::LastChild | - Component::OnlyChild | - Component::Root | - Component::Empty | - Component::Scope | - Component::NthChild(..) | - Component::NthLastChild(..) | - Component::NthOfType(..) | - Component::NthLastOfType(..) | - Component::FirstOfType | - Component::LastOfType | - Component::OnlyOfType | - Component::NonTSPseudoClass(..) => { - specificity.class_like_selectors += 1; - }, - Component::Negation(ref list) | Component::Is(ref list) => { - // https://drafts.csswg.org/selectors/#specificity-rules: - // - // The specificity of an :is() pseudo-class is replaced by the - // specificity of the most specific complex selector in its - // selector list argument. - let mut max = 0; - for selector in &**list { - max = std::cmp::max(selector.specificity(), max); - } - *specificity += Specificity::from(max); - }, - Component::Where(..) | - Component::Has(..) | - Component::ExplicitUniversalType | - Component::ExplicitAnyNamespace | - Component::ExplicitNoNamespace | - Component::DefaultNamespace(..) | - Component::Namespace(..) => { - // Does not affect specificity - }, - Component::Nesting => { - // TODO - } + fn simple_selector_specificity<'i, Impl>(simple_selector: &Component<'i, Impl>, specificity: &mut Specificity) + where + Impl: SelectorImpl<'i>, + { + match *simple_selector { + Component::Combinator(..) => { + unreachable!("Found combinator in simple selectors vector?"); + } + Component::Part(..) | Component::PseudoElement(..) | Component::LocalName(..) => { + specificity.element_selectors += 1 + } + Component::Slotted(ref selector) => { + specificity.element_selectors += 1; + // Note that due to the way ::slotted works we only compete with + // other ::slotted rules, so the above rule doesn't really + // matter, but we do it still for consistency with other + // pseudo-elements. + // + // See: https://github.com/w3c/csswg-drafts/issues/1915 + *specificity += Specificity::from(selector.specificity()); + } + Component::Host(ref selector) => { + specificity.class_like_selectors += 1; + if let Some(ref selector) = *selector { + // See: https://github.com/w3c/csswg-drafts/issues/1915 + *specificity += Specificity::from(selector.specificity()); + } + } + Component::ID(..) => { + specificity.id_selectors += 1; + } + Component::Class(..) + | Component::AttributeInNoNamespace { .. } + | Component::AttributeInNoNamespaceExists { .. } + | Component::AttributeOther(..) + | Component::FirstChild + | Component::LastChild + | Component::OnlyChild + | Component::Root + | Component::Empty + | Component::Scope + | Component::NthChild(..) + | Component::NthLastChild(..) + | Component::NthOfType(..) + | Component::NthLastOfType(..) + | Component::FirstOfType + | Component::LastOfType + | Component::OnlyOfType + | Component::NonTSPseudoClass(..) => { + specificity.class_like_selectors += 1; + } + Component::Negation(ref list) | Component::Is(ref list) => { + // https://drafts.csswg.org/selectors/#specificity-rules: + // + // The specificity of an :is() pseudo-class is replaced by the + // specificity of the most specific complex selector in its + // selector list argument. + let mut max = 0; + for selector in &**list { + max = std::cmp::max(selector.specificity(), max); } + *specificity += Specificity::from(max); + } + Component::Where(..) + | Component::Has(..) + | Component::ExplicitUniversalType + | Component::ExplicitAnyNamespace + | Component::ExplicitNoNamespace + | Component::DefaultNamespace(..) + | Component::Namespace(..) => { + // Does not affect specificity + } + Component::Nesting => { + // TODO + } } + } - let mut specificity = Default::default(); - for simple_selector in iter { - simple_selector_specificity(&simple_selector, &mut specificity); - } - specificity + let mut specificity = Default::default(); + for simple_selector in iter { + simple_selector_specificity(&simple_selector, &mut specificity); + } + specificity } diff --git a/selectors/context.rs b/selectors/context.rs index 09c5367e..bf142785 100644 --- a/selectors/context.rs +++ b/selectors/context.rs @@ -14,58 +14,56 @@ use crate::tree::{Element, OpaqueElement}; /// in presence of pseudo-elements. #[derive(Clone, Copy, Debug, PartialEq)] pub enum MatchingMode { - /// Don't ignore any pseudo-element selectors. - Normal, + /// Don't ignore any pseudo-element selectors. + Normal, - /// Ignores any stateless pseudo-element selectors in the rightmost sequence - /// of simple selectors. - /// - /// This is useful, for example, to match against ::before when you aren't a - /// pseudo-element yourself. - /// - /// For example, in presence of `::before:hover`, it would never match, but - /// `::before` would be ignored as in "matching". - /// - /// It's required for all the selectors you match using this mode to have a - /// pseudo-element. - ForStatelessPseudoElement, + /// Ignores any stateless pseudo-element selectors in the rightmost sequence + /// of simple selectors. + /// + /// This is useful, for example, to match against ::before when you aren't a + /// pseudo-element yourself. + /// + /// For example, in presence of `::before:hover`, it would never match, but + /// `::before` would be ignored as in "matching". + /// + /// It's required for all the selectors you match using this mode to have a + /// pseudo-element. + ForStatelessPseudoElement, } /// The mode to use when matching unvisited and visited links. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum VisitedHandlingMode { - /// All links are matched as if they are unvisted. - AllLinksUnvisited, - /// All links are matched as if they are visited and unvisited (both :link - /// and :visited match). - /// - /// This is intended to be used from invalidation code, to be conservative - /// about whether we need to restyle a link. - AllLinksVisitedAndUnvisited, - /// A element's "relevant link" is the element being matched if it is a link - /// or the nearest ancestor link. The relevant link is matched as though it - /// is visited, and all other links are matched as if they are unvisited. - RelevantLinkVisited, + /// All links are matched as if they are unvisted. + AllLinksUnvisited, + /// All links are matched as if they are visited and unvisited (both :link + /// and :visited match). + /// + /// This is intended to be used from invalidation code, to be conservative + /// about whether we need to restyle a link. + AllLinksVisitedAndUnvisited, + /// A element's "relevant link" is the element being matched if it is a link + /// or the nearest ancestor link. The relevant link is matched as though it + /// is visited, and all other links are matched as if they are unvisited. + RelevantLinkVisited, } impl VisitedHandlingMode { - #[inline] - pub fn matches_visited(&self) -> bool { - matches!( - *self, - VisitedHandlingMode::RelevantLinkVisited | - VisitedHandlingMode::AllLinksVisitedAndUnvisited - ) - } + #[inline] + pub fn matches_visited(&self) -> bool { + matches!( + *self, + VisitedHandlingMode::RelevantLinkVisited | VisitedHandlingMode::AllLinksVisitedAndUnvisited + ) + } - #[inline] - pub fn matches_unvisited(&self) -> bool { - matches!( - *self, - VisitedHandlingMode::AllLinksUnvisited | - VisitedHandlingMode::AllLinksVisitedAndUnvisited - ) - } + #[inline] + pub fn matches_unvisited(&self) -> bool { + matches!( + *self, + VisitedHandlingMode::AllLinksUnvisited | VisitedHandlingMode::AllLinksVisitedAndUnvisited + ) + } } /// Which quirks mode is this document in. @@ -73,22 +71,22 @@ impl VisitedHandlingMode { /// See: https://quirks.spec.whatwg.org/ #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum QuirksMode { - /// Quirks mode. - Quirks, - /// Limited quirks mode. - LimitedQuirks, - /// No quirks mode. - NoQuirks, + /// Quirks mode. + Quirks, + /// Limited quirks mode. + LimitedQuirks, + /// No quirks mode. + NoQuirks, } impl QuirksMode { - #[inline] - pub fn classes_and_ids_case_sensitivity(self) -> CaseSensitivity { - match self { - QuirksMode::NoQuirks | QuirksMode::LimitedQuirks => CaseSensitivity::CaseSensitive, - QuirksMode::Quirks => CaseSensitivity::AsciiCaseInsensitive, - } + #[inline] + pub fn classes_and_ids_case_sensitivity(self) -> CaseSensitivity { + match self { + QuirksMode::NoQuirks | QuirksMode::LimitedQuirks => CaseSensitivity::CaseSensitive, + QuirksMode::Quirks => CaseSensitivity::AsciiCaseInsensitive, } + } } /// Data associated with the matching process for a element. This context is @@ -96,196 +94,192 @@ impl QuirksMode { /// transient data that applies to only a single selector. pub struct MatchingContext<'a, 'i, Impl> where - Impl: SelectorImpl<'i>, + Impl: SelectorImpl<'i>, { - /// Input with the matching mode we should use when matching selectors. - matching_mode: MatchingMode, - /// Input with the bloom filter used to fast-reject selectors. - pub bloom_filter: Option<&'a BloomFilter>, - /// An optional cache to speed up nth-index-like selectors. - pub nth_index_cache: Option<&'a mut NthIndexCache>, - /// The element which is going to match :scope pseudo-class. It can be - /// either one :scope element, or the scoping element. - /// - /// Note that, although in theory there can be multiple :scope elements, - /// in current specs, at most one is specified, and when there is one, - /// scoping element is not relevant anymore, so we use a single field for - /// them. - /// - /// When this is None, :scope will match the root element. - /// - /// See https://drafts.csswg.org/selectors-4/#scope-pseudo - pub scope_element: Option, + /// Input with the matching mode we should use when matching selectors. + matching_mode: MatchingMode, + /// Input with the bloom filter used to fast-reject selectors. + pub bloom_filter: Option<&'a BloomFilter>, + /// An optional cache to speed up nth-index-like selectors. + pub nth_index_cache: Option<&'a mut NthIndexCache>, + /// The element which is going to match :scope pseudo-class. It can be + /// either one :scope element, or the scoping element. + /// + /// Note that, although in theory there can be multiple :scope elements, + /// in current specs, at most one is specified, and when there is one, + /// scoping element is not relevant anymore, so we use a single field for + /// them. + /// + /// When this is None, :scope will match the root element. + /// + /// See https://drafts.csswg.org/selectors-4/#scope-pseudo + pub scope_element: Option, - /// The current shadow host we're collecting :host rules for. - pub current_host: Option, + /// The current shadow host we're collecting :host rules for. + pub current_host: Option, - /// Controls how matching for links is handled. - visited_handling: VisitedHandlingMode, + /// Controls how matching for links is handled. + visited_handling: VisitedHandlingMode, - /// The current nesting level of selectors that we're matching. - /// - /// FIXME(emilio): Consider putting the mutable stuff in a Cell, then make - /// MatchingContext immutable again. - nesting_level: usize, + /// The current nesting level of selectors that we're matching. + /// + /// FIXME(emilio): Consider putting the mutable stuff in a Cell, then make + /// MatchingContext immutable again. + nesting_level: usize, - /// Whether we're inside a negation or not. - in_negation: bool, + /// Whether we're inside a negation or not. + in_negation: bool, - /// An optional hook function for checking whether a pseudo-element - /// should match when matching_mode is ForStatelessPseudoElement. - pub pseudo_element_matching_fn: Option<&'a dyn Fn(&Impl::PseudoElement) -> bool>, + /// An optional hook function for checking whether a pseudo-element + /// should match when matching_mode is ForStatelessPseudoElement. + pub pseudo_element_matching_fn: Option<&'a dyn Fn(&Impl::PseudoElement) -> bool>, - /// Extra implementation-dependent matching data. - pub extra_data: Impl::ExtraMatchingData, + /// Extra implementation-dependent matching data. + pub extra_data: Impl::ExtraMatchingData, - quirks_mode: QuirksMode, - classes_and_ids_case_sensitivity: CaseSensitivity, - _impl: ::std::marker::PhantomData, + quirks_mode: QuirksMode, + classes_and_ids_case_sensitivity: CaseSensitivity, + _impl: ::std::marker::PhantomData, } impl<'a, 'i, Impl> MatchingContext<'a, 'i, Impl> where - Impl: SelectorImpl<'i>, + Impl: SelectorImpl<'i>, { - /// Constructs a new `MatchingContext`. - pub fn new( - matching_mode: MatchingMode, - bloom_filter: Option<&'a BloomFilter>, - nth_index_cache: Option<&'a mut NthIndexCache>, - quirks_mode: QuirksMode, - ) -> Self { - Self::new_for_visited( - matching_mode, - bloom_filter, - nth_index_cache, - VisitedHandlingMode::AllLinksUnvisited, - quirks_mode, - ) - } + /// Constructs a new `MatchingContext`. + pub fn new( + matching_mode: MatchingMode, + bloom_filter: Option<&'a BloomFilter>, + nth_index_cache: Option<&'a mut NthIndexCache>, + quirks_mode: QuirksMode, + ) -> Self { + Self::new_for_visited( + matching_mode, + bloom_filter, + nth_index_cache, + VisitedHandlingMode::AllLinksUnvisited, + quirks_mode, + ) + } - /// Constructs a new `MatchingContext` for use in visited matching. - pub fn new_for_visited( - matching_mode: MatchingMode, - bloom_filter: Option<&'a BloomFilter>, - nth_index_cache: Option<&'a mut NthIndexCache>, - visited_handling: VisitedHandlingMode, - quirks_mode: QuirksMode, - ) -> Self { - Self { - matching_mode, - bloom_filter, - visited_handling, - nth_index_cache, - quirks_mode, - classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(), - scope_element: None, - current_host: None, - nesting_level: 0, - in_negation: false, - pseudo_element_matching_fn: None, - extra_data: Default::default(), - _impl: ::std::marker::PhantomData, - } + /// Constructs a new `MatchingContext` for use in visited matching. + pub fn new_for_visited( + matching_mode: MatchingMode, + bloom_filter: Option<&'a BloomFilter>, + nth_index_cache: Option<&'a mut NthIndexCache>, + visited_handling: VisitedHandlingMode, + quirks_mode: QuirksMode, + ) -> Self { + Self { + matching_mode, + bloom_filter, + visited_handling, + nth_index_cache, + quirks_mode, + classes_and_ids_case_sensitivity: quirks_mode.classes_and_ids_case_sensitivity(), + scope_element: None, + current_host: None, + nesting_level: 0, + in_negation: false, + pseudo_element_matching_fn: None, + extra_data: Default::default(), + _impl: ::std::marker::PhantomData, } + } - /// Whether we're matching a nested selector. - #[inline] - pub fn is_nested(&self) -> bool { - self.nesting_level != 0 - } + /// Whether we're matching a nested selector. + #[inline] + pub fn is_nested(&self) -> bool { + self.nesting_level != 0 + } - /// Whether we're matching inside a :not(..) selector. - #[inline] - pub fn in_negation(&self) -> bool { - self.in_negation - } + /// Whether we're matching inside a :not(..) selector. + #[inline] + pub fn in_negation(&self) -> bool { + self.in_negation + } - /// The quirks mode of the document. - #[inline] - pub fn quirks_mode(&self) -> QuirksMode { - self.quirks_mode - } + /// The quirks mode of the document. + #[inline] + pub fn quirks_mode(&self) -> QuirksMode { + self.quirks_mode + } - /// The matching-mode for this selector-matching operation. - #[inline] - pub fn matching_mode(&self) -> MatchingMode { - self.matching_mode - } + /// The matching-mode for this selector-matching operation. + #[inline] + pub fn matching_mode(&self) -> MatchingMode { + self.matching_mode + } - /// The case-sensitivity for class and ID selectors - #[inline] - pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity { - self.classes_and_ids_case_sensitivity - } + /// The case-sensitivity for class and ID selectors + #[inline] + pub fn classes_and_ids_case_sensitivity(&self) -> CaseSensitivity { + self.classes_and_ids_case_sensitivity + } - /// Runs F with a deeper nesting level. - #[inline] - pub fn nest(&mut self, f: F) -> R - where - F: FnOnce(&mut Self) -> R, - { - self.nesting_level += 1; - let result = f(self); - self.nesting_level -= 1; - result - } + /// Runs F with a deeper nesting level. + #[inline] + pub fn nest(&mut self, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + self.nesting_level += 1; + let result = f(self); + self.nesting_level -= 1; + result + } - /// Runs F with a deeper nesting level, and marking ourselves in a negation, - /// for a :not(..) selector, for example. - #[inline] - pub fn nest_for_negation(&mut self, f: F) -> R - where - F: FnOnce(&mut Self) -> R, - { - let old_in_negation = self.in_negation; - self.in_negation = true; - let result = self.nest(f); - self.in_negation = old_in_negation; - result - } + /// Runs F with a deeper nesting level, and marking ourselves in a negation, + /// for a :not(..) selector, for example. + #[inline] + pub fn nest_for_negation(&mut self, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + let old_in_negation = self.in_negation; + self.in_negation = true; + let result = self.nest(f); + self.in_negation = old_in_negation; + result + } - #[inline] - pub fn visited_handling(&self) -> VisitedHandlingMode { - self.visited_handling - } + #[inline] + pub fn visited_handling(&self) -> VisitedHandlingMode { + self.visited_handling + } - /// Runs F with a different VisitedHandlingMode. - #[inline] - pub fn with_visited_handling_mode( - &mut self, - handling_mode: VisitedHandlingMode, - f: F, - ) -> R - where - F: FnOnce(&mut Self) -> R, - { - let original_handling_mode = self.visited_handling; - self.visited_handling = handling_mode; - let result = f(self); - self.visited_handling = original_handling_mode; - result - } + /// Runs F with a different VisitedHandlingMode. + #[inline] + pub fn with_visited_handling_mode(&mut self, handling_mode: VisitedHandlingMode, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + let original_handling_mode = self.visited_handling; + self.visited_handling = handling_mode; + let result = f(self); + self.visited_handling = original_handling_mode; + result + } - /// Runs F with a given shadow host which is the root of the tree whose - /// rules we're matching. - #[inline] - pub fn with_shadow_host(&mut self, host: Option, f: F) -> R - where - E: Element<'i>, - F: FnOnce(&mut Self) -> R, - { - let original_host = self.current_host.take(); - self.current_host = host.map(|h| h.opaque()); - let result = f(self); - self.current_host = original_host; - result - } + /// Runs F with a given shadow host which is the root of the tree whose + /// rules we're matching. + #[inline] + pub fn with_shadow_host(&mut self, host: Option, f: F) -> R + where + E: Element<'i>, + F: FnOnce(&mut Self) -> R, + { + let original_host = self.current_host.take(); + self.current_host = host.map(|h| h.opaque()); + let result = f(self); + self.current_host = original_host; + result + } - /// Returns the current shadow host whose shadow root we're matching rules - /// against. - #[inline] - pub fn shadow_host(&self) -> Option { - self.current_host - } + /// Returns the current shadow host whose shadow root we're matching rules + /// against. + #[inline] + pub fn shadow_host(&self) -> Option { + self.current_host + } } diff --git a/selectors/matching.rs b/selectors/matching.rs index 22dea86b..ceb8cae9 100644 --- a/selectors/matching.rs +++ b/selectors/matching.rs @@ -45,73 +45,74 @@ bitflags! { } impl ElementSelectorFlags { - /// Returns the subset of flags that apply to the element. - pub fn for_self(self) -> ElementSelectorFlags { - self & (ElementSelectorFlags::HAS_EMPTY_SELECTOR) - } - - /// Returns the subset of flags that apply to the parent. - pub fn for_parent(self) -> ElementSelectorFlags { - self & (ElementSelectorFlags::HAS_SLOW_SELECTOR | - ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS | - ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) - } + /// Returns the subset of flags that apply to the element. + pub fn for_self(self) -> ElementSelectorFlags { + self & (ElementSelectorFlags::HAS_EMPTY_SELECTOR) + } + + /// Returns the subset of flags that apply to the parent. + pub fn for_parent(self) -> ElementSelectorFlags { + self + & (ElementSelectorFlags::HAS_SLOW_SELECTOR + | ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS + | ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR) + } } /// Holds per-compound-selector data. struct LocalMatchingContext<'a, 'b: 'a, 'i, Impl: SelectorImpl<'i>> { - shared: &'a mut MatchingContext<'b, 'i, Impl>, - matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk, + shared: &'a mut MatchingContext<'b, 'i, Impl>, + matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk, } #[inline(always)] pub fn matches_selector_list<'i, E>( - selector_list: &SelectorList<'i, E::Impl>, - element: &E, - context: &mut MatchingContext<'_, 'i, E::Impl>, + selector_list: &SelectorList<'i, E::Impl>, + element: &E, + context: &mut MatchingContext<'_, 'i, E::Impl>, ) -> bool where - E: Element<'i>, + E: Element<'i>, { - // This is pretty much any(..) but manually inlined because the compiler - // refuses to do so from querySelector / querySelectorAll. - for selector in &selector_list.0 { - let matches = matches_selector(selector, 0, None, element, context, &mut |_, _| {}); + // This is pretty much any(..) but manually inlined because the compiler + // refuses to do so from querySelector / querySelectorAll. + for selector in &selector_list.0 { + let matches = matches_selector(selector, 0, None, element, context, &mut |_, _| {}); - if matches { - return true; - } + if matches { + return true; } + } - false + false } #[inline(always)] fn may_match(hashes: &AncestorHashes, bf: &BloomFilter) -> bool { - // Check the first three hashes. Note that we can check for zero before - // masking off the high bits, since if any of the first three hashes is - // zero the fourth will be as well. We also take care to avoid the - // special-case complexity of the fourth hash until we actually reach it, - // because we usually don't. - // - // To be clear: this is all extremely hot. - for i in 0..3 { - let packed = hashes.packed_hashes[i]; - if packed == 0 { - // No more hashes left - unable to fast-reject. - return true; - } + // Check the first three hashes. Note that we can check for zero before + // masking off the high bits, since if any of the first three hashes is + // zero the fourth will be as well. We also take care to avoid the + // special-case complexity of the fourth hash until we actually reach it, + // because we usually don't. + // + // To be clear: this is all extremely hot. + for i in 0..3 { + let packed = hashes.packed_hashes[i]; + if packed == 0 { + // No more hashes left - unable to fast-reject. + return true; + } - if !bf.might_contain_hash(packed & BLOOM_HASH_MASK) { - // Hooray! We fast-rejected on this hash. - return false; - } + if !bf.might_contain_hash(packed & BLOOM_HASH_MASK) { + // Hooray! We fast-rejected on this hash. + return false; } + } - // Now do the slighty-more-complex work of synthesizing the fourth hash, - // and check it against the filter if it exists. - let fourth = hashes.fourth_hash(); - fourth == 0 || bf.might_contain_hash(fourth) + // Now do the slighty-more-complex work of synthesizing the fourth hash, + // and check it against the filter if it exists. + let fourth = hashes.fourth_hash(); + fourth == 0 || bf.might_contain_hash(fourth) } /// A result of selector matching, includes 3 failure types, @@ -158,10 +159,10 @@ fn may_match(hashes: &AncestorHashes, bf: &BloomFilter) -> bool { /// "b1 + c1 > b2 ~ " doesn't match and restart matching from "d1". #[derive(Clone, Copy, Eq, PartialEq)] enum SelectorMatchingResult { - Matched, - NotMatchedAndRestartFromClosestLaterSibling, - NotMatchedAndRestartFromClosestDescendant, - NotMatchedGlobally, + Matched, + NotMatchedAndRestartFromClosestLaterSibling, + NotMatchedAndRestartFromClosestDescendant, + NotMatchedGlobally, } /// Whether the :hover and :active quirk applies. @@ -169,8 +170,8 @@ enum SelectorMatchingResult { /// https://quirks.spec.whatwg.org/#the-active-and-hover-quirk #[derive(Clone, Copy, Debug, PartialEq)] enum MatchesHoverAndActiveQuirk { - Yes, - No, + Yes, + No, } /// Matches a selector, fast-rejecting against a bloom filter. @@ -183,39 +184,39 @@ enum MatchesHoverAndActiveQuirk { /// (which the caller can store inline with the selector pointer). #[inline(always)] pub fn matches_selector<'i, E, F>( - selector: &Selector<'i, E::Impl>, - offset: usize, - hashes: Option<&AncestorHashes>, - element: &E, - context: &mut MatchingContext<'_, 'i, E::Impl>, - flags_setter: &mut F, + selector: &Selector<'i, E::Impl>, + offset: usize, + hashes: Option<&AncestorHashes>, + element: &E, + context: &mut MatchingContext<'_, 'i, E::Impl>, + flags_setter: &mut F, ) -> bool where - E: Element<'i>, - F: FnMut(&E, ElementSelectorFlags), + E: Element<'i>, + F: FnMut(&E, ElementSelectorFlags), { - // Use the bloom filter to fast-reject. - if let Some(hashes) = hashes { - if let Some(filter) = context.bloom_filter { - if !may_match(hashes, filter) { - return false; - } - } + // Use the bloom filter to fast-reject. + if let Some(hashes) = hashes { + if let Some(filter) = context.bloom_filter { + if !may_match(hashes, filter) { + return false; + } } + } - matches_complex_selector(selector.iter_from(offset), element, context, flags_setter) + matches_complex_selector(selector.iter_from(offset), element, context, flags_setter) } /// Whether a compound selector matched, and whether it was the rightmost /// selector inside the complex selector. pub enum CompoundSelectorMatchingResult { - /// The selector was fully matched. - FullyMatched, - /// The compound selector matched, and the next combinator offset is - /// `next_combinator_offset`. - Matched { next_combinator_offset: usize }, - /// The selector didn't match. - NotMatched, + /// The selector was fully matched. + FullyMatched, + /// The compound selector matched, and the next combinator offset is + /// `next_combinator_offset`. + Matched { next_combinator_offset: usize }, + /// The selector didn't match. + NotMatched, } /// Matches a compound selector belonging to `selector`, starting at offset @@ -226,786 +227,734 @@ pub enum CompoundSelectorMatchingResult { /// NOTE(emilio): This doesn't allow to match in the leftmost sequence of the /// complex selector, but it happens to be the case we don't need it. pub fn matches_compound_selector_from<'i, E>( - selector: &Selector<'i, E::Impl>, - mut from_offset: usize, - context: &mut MatchingContext<'_, 'i, E::Impl>, - element: &E, + selector: &Selector<'i, E::Impl>, + mut from_offset: usize, + context: &mut MatchingContext<'_, 'i, E::Impl>, + element: &E, ) -> CompoundSelectorMatchingResult where - E: Element<'i>, + E: Element<'i>, { - if cfg!(debug_assertions) && from_offset != 0 { - selector.combinator_at_parse_order(from_offset - 1); // This asserts. + if cfg!(debug_assertions) && from_offset != 0 { + selector.combinator_at_parse_order(from_offset - 1); // This asserts. + } + + let mut local_context = LocalMatchingContext { + shared: context, + matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk::No, + }; + + // Find the end of the selector or the next combinator, then match + // backwards, so that we match in the same order as + // matches_complex_selector, which is usually faster. + let start_offset = from_offset; + for component in selector.iter_raw_parse_order_from(from_offset) { + if matches!(*component, Component::Combinator(..)) { + debug_assert_ne!(from_offset, 0, "Selector started with a combinator?"); + break; } - let mut local_context = LocalMatchingContext { - shared: context, - matches_hover_and_active_quirk: MatchesHoverAndActiveQuirk::No, - }; - - // Find the end of the selector or the next combinator, then match - // backwards, so that we match in the same order as - // matches_complex_selector, which is usually faster. - let start_offset = from_offset; - for component in selector.iter_raw_parse_order_from(from_offset) { - if matches!(*component, Component::Combinator(..)) { - debug_assert_ne!(from_offset, 0, "Selector started with a combinator?"); - break; - } - - from_offset += 1; - } - - debug_assert!(from_offset >= 1); - debug_assert!(from_offset <= selector.len()); - - let iter = selector.iter_from(selector.len() - from_offset); - debug_assert!( - iter.clone().next().is_some() || - (from_offset != selector.len() && - matches!( - selector.combinator_at_parse_order(from_offset), - Combinator::SlotAssignment | Combinator::PseudoElement - )), - "Got the math wrong: {:?} | {:?} | {} {}", - selector, - selector.iter_raw_match_order().as_slice(), - from_offset, - start_offset - ); - - for component in iter { - if !matches_simple_selector(component, element, &mut local_context, &mut |_, _| {}) { - return CompoundSelectorMatchingResult::NotMatched; - } + from_offset += 1; + } + + debug_assert!(from_offset >= 1); + debug_assert!(from_offset <= selector.len()); + + let iter = selector.iter_from(selector.len() - from_offset); + debug_assert!( + iter.clone().next().is_some() + || (from_offset != selector.len() + && matches!( + selector.combinator_at_parse_order(from_offset), + Combinator::SlotAssignment | Combinator::PseudoElement + )), + "Got the math wrong: {:?} | {:?} | {} {}", + selector, + selector.iter_raw_match_order().as_slice(), + from_offset, + start_offset + ); + + for component in iter { + if !matches_simple_selector(component, element, &mut local_context, &mut |_, _| {}) { + return CompoundSelectorMatchingResult::NotMatched; } + } - if from_offset != selector.len() { - return CompoundSelectorMatchingResult::Matched { - next_combinator_offset: from_offset, - }; - } + if from_offset != selector.len() { + return CompoundSelectorMatchingResult::Matched { + next_combinator_offset: from_offset, + }; + } - CompoundSelectorMatchingResult::FullyMatched + CompoundSelectorMatchingResult::FullyMatched } /// Matches a complex selector. #[inline(always)] pub fn matches_complex_selector<'i, E, F>( - mut iter: SelectorIter<'_, 'i, E::Impl>, - element: &E, - context: &mut MatchingContext<'_, 'i, E::Impl>, - flags_setter: &mut F, + mut iter: SelectorIter<'_, 'i, E::Impl>, + element: &E, + context: &mut MatchingContext<'_, 'i, E::Impl>, + flags_setter: &mut F, ) -> bool where - E: Element<'i>, - F: FnMut(&E, ElementSelectorFlags), + E: Element<'i>, + F: FnMut(&E, ElementSelectorFlags), { - // If this is the special pseudo-element mode, consume the ::pseudo-element - // before proceeding, since the caller has already handled that part. - if context.matching_mode() == MatchingMode::ForStatelessPseudoElement && !context.is_nested() { - // Consume the pseudo. - match *iter.next().unwrap() { - Component::PseudoElement(ref pseudo) => { - if let Some(ref f) = context.pseudo_element_matching_fn { - if !f(pseudo) { - return false; - } - } - }, - _ => { - debug_assert!( - false, - "Used MatchingMode::ForStatelessPseudoElement \ - in a non-pseudo selector" - ); - }, - } - - if !iter.matches_for_stateless_pseudo_element() { + // If this is the special pseudo-element mode, consume the ::pseudo-element + // before proceeding, since the caller has already handled that part. + if context.matching_mode() == MatchingMode::ForStatelessPseudoElement && !context.is_nested() { + // Consume the pseudo. + match *iter.next().unwrap() { + Component::PseudoElement(ref pseudo) => { + if let Some(ref f) = context.pseudo_element_matching_fn { + if !f(pseudo) { return false; + } } + } + _ => { + debug_assert!( + false, + "Used MatchingMode::ForStatelessPseudoElement \ + in a non-pseudo selector" + ); + } + } - // Advance to the non-pseudo-element part of the selector. - let next_sequence = iter.next_sequence().unwrap(); - debug_assert_eq!(next_sequence, Combinator::PseudoElement); + if !iter.matches_for_stateless_pseudo_element() { + return false; } - let result = - matches_complex_selector_internal(iter, element, context, flags_setter, Rightmost::Yes); + // Advance to the non-pseudo-element part of the selector. + let next_sequence = iter.next_sequence().unwrap(); + debug_assert_eq!(next_sequence, Combinator::PseudoElement); + } - matches!(result, SelectorMatchingResult::Matched) + let result = matches_complex_selector_internal(iter, element, context, flags_setter, Rightmost::Yes); + + matches!(result, SelectorMatchingResult::Matched) } #[inline] fn matches_hover_and_active_quirk<'i, Impl: SelectorImpl<'i>>( - selector_iter: &SelectorIter<'_, 'i, Impl>, - context: &MatchingContext<'_, 'i, Impl>, - rightmost: Rightmost, + selector_iter: &SelectorIter<'_, 'i, Impl>, + context: &MatchingContext<'_, 'i, Impl>, + rightmost: Rightmost, ) -> MatchesHoverAndActiveQuirk { - if context.quirks_mode() != QuirksMode::Quirks { - return MatchesHoverAndActiveQuirk::No; - } - - if context.is_nested() { - return MatchesHoverAndActiveQuirk::No; - } - - // This compound selector had a pseudo-element to the right that we - // intentionally skipped. - if rightmost == Rightmost::Yes && - context.matching_mode() == MatchingMode::ForStatelessPseudoElement - { - return MatchesHoverAndActiveQuirk::No; - } - - let all_match = selector_iter.clone().all(|simple| match *simple { - Component::LocalName(_) | - Component::AttributeInNoNamespaceExists { .. } | - Component::AttributeInNoNamespace { .. } | - Component::AttributeOther(_) | - Component::ID(_) | - Component::Class(_) | - Component::PseudoElement(_) | - Component::Negation(_) | - Component::FirstChild | - Component::LastChild | - Component::OnlyChild | - Component::Empty | - Component::NthChild(_, _) | - Component::NthLastChild(_, _) | - Component::NthOfType(_, _) | - Component::NthLastOfType(_, _) | - Component::FirstOfType | - Component::LastOfType | - Component::OnlyOfType => false, - Component::NonTSPseudoClass(ref pseudo_class) => pseudo_class.is_active_or_hover(), - _ => true, - }); - - if all_match { - MatchesHoverAndActiveQuirk::Yes - } else { - MatchesHoverAndActiveQuirk::No - } + if context.quirks_mode() != QuirksMode::Quirks { + return MatchesHoverAndActiveQuirk::No; + } + + if context.is_nested() { + return MatchesHoverAndActiveQuirk::No; + } + + // This compound selector had a pseudo-element to the right that we + // intentionally skipped. + if rightmost == Rightmost::Yes && context.matching_mode() == MatchingMode::ForStatelessPseudoElement { + return MatchesHoverAndActiveQuirk::No; + } + + let all_match = selector_iter.clone().all(|simple| match *simple { + Component::LocalName(_) + | Component::AttributeInNoNamespaceExists { .. } + | Component::AttributeInNoNamespace { .. } + | Component::AttributeOther(_) + | Component::ID(_) + | Component::Class(_) + | Component::PseudoElement(_) + | Component::Negation(_) + | Component::FirstChild + | Component::LastChild + | Component::OnlyChild + | Component::Empty + | Component::NthChild(_, _) + | Component::NthLastChild(_, _) + | Component::NthOfType(_, _) + | Component::NthLastOfType(_, _) + | Component::FirstOfType + | Component::LastOfType + | Component::OnlyOfType => false, + Component::NonTSPseudoClass(ref pseudo_class) => pseudo_class.is_active_or_hover(), + _ => true, + }); + + if all_match { + MatchesHoverAndActiveQuirk::Yes + } else { + MatchesHoverAndActiveQuirk::No + } } #[derive(Clone, Copy, PartialEq)] enum Rightmost { - Yes, - No, + Yes, + No, } #[inline(always)] fn next_element_for_combinator<'i, E>( - element: &E, - combinator: Combinator, - selector: &SelectorIter<'_, 'i, E::Impl>, - context: &MatchingContext<'_, 'i, E::Impl>, + element: &E, + combinator: Combinator, + selector: &SelectorIter<'_, 'i, E::Impl>, + context: &MatchingContext<'_, 'i, E::Impl>, ) -> Option where - E: Element<'i>, + E: Element<'i>, { - match combinator { - Combinator::NextSibling | Combinator::LaterSibling => element.prev_sibling_element(), - Combinator::Child | Combinator::Descendant => { - match element.parent_element() { - Some(e) => return Some(e), - None => {}, - } - - if !element.parent_node_is_shadow_root() { - return None; - } - - // https://drafts.csswg.org/css-scoping/#host-element-in-tree: - // - // For the purpose of Selectors, a shadow host also appears in - // its shadow tree, with the contents of the shadow tree treated - // as its children. (In other words, the shadow host is treated as - // replacing the shadow root node.) - // - // and also: - // - // When considered within its own shadow trees, the shadow host is - // featureless. Only the :host, :host(), and :host-context() - // pseudo-classes are allowed to match it. - // - // Since we know that the parent is a shadow root, we necessarily - // are in a shadow tree of the host, and the next selector will only - // match if the selector is a featureless :host selector. - if !selector.clone().is_featureless_host_selector() { - return None; - } - - element.containing_shadow_host() - }, - Combinator::Part => element.containing_shadow_host(), - Combinator::SlotAssignment => { - debug_assert!(element - .assigned_slot() - .map_or(true, |s| s.is_html_slot_element())); - let scope = context.current_host?; - let mut current_slot = element.assigned_slot()?; - while current_slot.containing_shadow_host().unwrap().opaque() != scope { - current_slot = current_slot.assigned_slot()?; - } - Some(current_slot) - }, - Combinator::PseudoElement => element.pseudo_element_originating_element(), + match combinator { + Combinator::NextSibling | Combinator::LaterSibling => element.prev_sibling_element(), + Combinator::Child | Combinator::Descendant => { + match element.parent_element() { + Some(e) => return Some(e), + None => {} + } + + if !element.parent_node_is_shadow_root() { + return None; + } + + // https://drafts.csswg.org/css-scoping/#host-element-in-tree: + // + // For the purpose of Selectors, a shadow host also appears in + // its shadow tree, with the contents of the shadow tree treated + // as its children. (In other words, the shadow host is treated as + // replacing the shadow root node.) + // + // and also: + // + // When considered within its own shadow trees, the shadow host is + // featureless. Only the :host, :host(), and :host-context() + // pseudo-classes are allowed to match it. + // + // Since we know that the parent is a shadow root, we necessarily + // are in a shadow tree of the host, and the next selector will only + // match if the selector is a featureless :host selector. + if !selector.clone().is_featureless_host_selector() { + return None; + } + + element.containing_shadow_host() } + Combinator::Part => element.containing_shadow_host(), + Combinator::SlotAssignment => { + debug_assert!(element.assigned_slot().map_or(true, |s| s.is_html_slot_element())); + let scope = context.current_host?; + let mut current_slot = element.assigned_slot()?; + while current_slot.containing_shadow_host().unwrap().opaque() != scope { + current_slot = current_slot.assigned_slot()?; + } + Some(current_slot) + } + Combinator::PseudoElement => element.pseudo_element_originating_element(), + } } fn matches_complex_selector_internal<'i, E, F>( - mut selector_iter: SelectorIter<'_, 'i, E::Impl>, - element: &E, - context: &mut MatchingContext<'_, 'i, E::Impl>, - flags_setter: &mut F, - rightmost: Rightmost, + mut selector_iter: SelectorIter<'_, 'i, E::Impl>, + element: &E, + context: &mut MatchingContext<'_, 'i, E::Impl>, + flags_setter: &mut F, + rightmost: Rightmost, ) -> SelectorMatchingResult where - E: Element<'i>, - F: FnMut(&E, ElementSelectorFlags), + E: Element<'i>, + F: FnMut(&E, ElementSelectorFlags), { - debug!( - "Matching complex selector {:?} for {:?}", - selector_iter, element - ); - - let matches_compound_selector = matches_compound_selector( - &mut selector_iter, - element, - context, - flags_setter, - rightmost, - ); - - let combinator = selector_iter.next_sequence(); - if combinator.map_or(false, |c| c.is_sibling()) { - flags_setter( - element, - ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS, - ); - } + debug!("Matching complex selector {:?} for {:?}", selector_iter, element); - if !matches_compound_selector { - return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling; - } + let matches_compound_selector = + matches_compound_selector(&mut selector_iter, element, context, flags_setter, rightmost); - let combinator = match combinator { - None => return SelectorMatchingResult::Matched, - Some(c) => c, - }; + let combinator = selector_iter.next_sequence(); + if combinator.map_or(false, |c| c.is_sibling()) { + flags_setter(element, ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS); + } - let candidate_not_found = match combinator { - Combinator::NextSibling | Combinator::LaterSibling => { - SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant - }, - Combinator::Child | - Combinator::Descendant | - Combinator::SlotAssignment | - Combinator::Part | - Combinator::PseudoElement => SelectorMatchingResult::NotMatchedGlobally, - }; + if !matches_compound_selector { + return SelectorMatchingResult::NotMatchedAndRestartFromClosestLaterSibling; + } - let mut next_element = - next_element_for_combinator(element, combinator, &selector_iter, &context); + let combinator = match combinator { + None => return SelectorMatchingResult::Matched, + Some(c) => c, + }; - // Stop matching :visited as soon as we find a link, or a combinator for - // something that isn't an ancestor. - let mut visited_handling = if element.is_link() || combinator.is_sibling() { - VisitedHandlingMode::AllLinksUnvisited - } else { - context.visited_handling() + let candidate_not_found = match combinator { + Combinator::NextSibling | Combinator::LaterSibling => { + SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant + } + Combinator::Child + | Combinator::Descendant + | Combinator::SlotAssignment + | Combinator::Part + | Combinator::PseudoElement => SelectorMatchingResult::NotMatchedGlobally, + }; + + let mut next_element = next_element_for_combinator(element, combinator, &selector_iter, &context); + + // Stop matching :visited as soon as we find a link, or a combinator for + // something that isn't an ancestor. + let mut visited_handling = if element.is_link() || combinator.is_sibling() { + VisitedHandlingMode::AllLinksUnvisited + } else { + context.visited_handling() + }; + + loop { + let element = match next_element { + None => return candidate_not_found, + Some(next_element) => next_element, }; - loop { - let element = match next_element { - None => return candidate_not_found, - Some(next_element) => next_element, - }; - - let result = context.with_visited_handling_mode(visited_handling, |context| { - matches_complex_selector_internal( - selector_iter.clone(), - &element, - context, - flags_setter, - Rightmost::No, - ) - }); - - match (result, combinator) { - // Return the status immediately. - (SelectorMatchingResult::Matched, _) | - (SelectorMatchingResult::NotMatchedGlobally, _) | - (_, Combinator::NextSibling) => { - return result; - }, - - // Upgrade the failure status to - // NotMatchedAndRestartFromClosestDescendant. - (_, Combinator::PseudoElement) | (_, Combinator::Child) => { - return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant; - }, - - // If the failure status is - // NotMatchedAndRestartFromClosestDescendant and combinator is - // Combinator::LaterSibling, give up this Combinator::LaterSibling - // matching and restart from the closest descendant combinator. - ( - SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, - Combinator::LaterSibling, - ) => { - return result; - }, - - // The Combinator::Descendant combinator and the status is - // NotMatchedAndRestartFromClosestLaterSibling or - // NotMatchedAndRestartFromClosestDescendant, or the - // Combinator::LaterSibling combinator and the status is - // NotMatchedAndRestartFromClosestDescendant, we can continue to - // matching on the next candidate element. - _ => {}, - } + let result = context.with_visited_handling_mode(visited_handling, |context| { + matches_complex_selector_internal(selector_iter.clone(), &element, context, flags_setter, Rightmost::No) + }); - if element.is_link() { - visited_handling = VisitedHandlingMode::AllLinksUnvisited; - } + match (result, combinator) { + // Return the status immediately. + (SelectorMatchingResult::Matched, _) + | (SelectorMatchingResult::NotMatchedGlobally, _) + | (_, Combinator::NextSibling) => { + return result; + } + + // Upgrade the failure status to + // NotMatchedAndRestartFromClosestDescendant. + (_, Combinator::PseudoElement) | (_, Combinator::Child) => { + return SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant; + } + + // If the failure status is + // NotMatchedAndRestartFromClosestDescendant and combinator is + // Combinator::LaterSibling, give up this Combinator::LaterSibling + // matching and restart from the closest descendant combinator. + (SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant, Combinator::LaterSibling) => { + return result; + } + + // The Combinator::Descendant combinator and the status is + // NotMatchedAndRestartFromClosestLaterSibling or + // NotMatchedAndRestartFromClosestDescendant, or the + // Combinator::LaterSibling combinator and the status is + // NotMatchedAndRestartFromClosestDescendant, we can continue to + // matching on the next candidate element. + _ => {} + } - next_element = next_element_for_combinator(&element, combinator, &selector_iter, &context); + if element.is_link() { + visited_handling = VisitedHandlingMode::AllLinksUnvisited; } + + next_element = next_element_for_combinator(&element, combinator, &selector_iter, &context); + } } #[inline] fn matches_local_name<'i, E>(element: &E, local_name: &LocalName<'i, E::Impl>) -> bool where - E: Element<'i>, + E: Element<'i>, { - let name = select_name( - element.is_html_element_in_html_document(), - &local_name.name, - &local_name.lower_name, - ) - .borrow(); - element.has_local_name(name) + let name = select_name( + element.is_html_element_in_html_document(), + &local_name.name, + &local_name.lower_name, + ) + .borrow(); + element.has_local_name(name) } /// Determines whether the given element matches the given compound selector. #[inline] fn matches_compound_selector<'i, E, F>( - selector_iter: &mut SelectorIter<'_, 'i, E::Impl>, - element: &E, - context: &mut MatchingContext<'_, 'i, E::Impl>, - flags_setter: &mut F, - rightmost: Rightmost, + selector_iter: &mut SelectorIter<'_, 'i, E::Impl>, + element: &E, + context: &mut MatchingContext<'_, 'i, E::Impl>, + flags_setter: &mut F, + rightmost: Rightmost, ) -> bool where - E: Element<'i>, - F: FnMut(&E, ElementSelectorFlags), + E: Element<'i>, + F: FnMut(&E, ElementSelectorFlags), { - let matches_hover_and_active_quirk = - matches_hover_and_active_quirk(&selector_iter, context, rightmost); - - // Handle some common cases first. - // We may want to get rid of this at some point if we can make the - // generic case fast enough. - let mut selector = selector_iter.next(); - if let Some(&Component::LocalName(ref local_name)) = selector { - if !matches_local_name(element, local_name) { - return false; - } - selector = selector_iter.next(); + let matches_hover_and_active_quirk = matches_hover_and_active_quirk(&selector_iter, context, rightmost); + + // Handle some common cases first. + // We may want to get rid of this at some point if we can make the + // generic case fast enough. + let mut selector = selector_iter.next(); + if let Some(&Component::LocalName(ref local_name)) = selector { + if !matches_local_name(element, local_name) { + return false; } - let class_and_id_case_sensitivity = context.classes_and_ids_case_sensitivity(); - if let Some(&Component::ID(ref id)) = selector { - if !element.has_id(id, class_and_id_case_sensitivity) { - return false; - } - selector = selector_iter.next(); + selector = selector_iter.next(); + } + let class_and_id_case_sensitivity = context.classes_and_ids_case_sensitivity(); + if let Some(&Component::ID(ref id)) = selector { + if !element.has_id(id, class_and_id_case_sensitivity) { + return false; } - while let Some(&Component::Class(ref class)) = selector { - if !element.has_class(class, class_and_id_case_sensitivity) { - return false; - } - selector = selector_iter.next(); + selector = selector_iter.next(); + } + while let Some(&Component::Class(ref class)) = selector { + if !element.has_class(class, class_and_id_case_sensitivity) { + return false; } - let selector = match selector { - Some(s) => s, - None => return true, - }; - - let mut local_context = LocalMatchingContext { - shared: context, - matches_hover_and_active_quirk, - }; - iter::once(selector) - .chain(selector_iter) - .all(|simple| matches_simple_selector(simple, element, &mut local_context, flags_setter)) + selector = selector_iter.next(); + } + let selector = match selector { + Some(s) => s, + None => return true, + }; + + let mut local_context = LocalMatchingContext { + shared: context, + matches_hover_and_active_quirk, + }; + iter::once(selector) + .chain(selector_iter) + .all(|simple| matches_simple_selector(simple, element, &mut local_context, flags_setter)) } /// Determines whether the given element matches the given single selector. fn matches_simple_selector<'i, E, F>( - selector: &Component<'i, E::Impl>, - element: &E, - context: &mut LocalMatchingContext<'_, '_, 'i, E::Impl>, - flags_setter: &mut F, + selector: &Component<'i, E::Impl>, + element: &E, + context: &mut LocalMatchingContext<'_, '_, 'i, E::Impl>, + flags_setter: &mut F, ) -> bool where - E: Element<'i>, - F: FnMut(&E, ElementSelectorFlags), + E: Element<'i>, + F: FnMut(&E, ElementSelectorFlags), { - debug_assert!(context.shared.is_nested() || !context.shared.in_negation()); - - match *selector { - Component::Combinator(_) => unreachable!(), - Component::Part(ref parts) => { - let mut hosts = SmallVec::<[E; 4]>::new(); - - let mut host = match element.containing_shadow_host() { - Some(h) => h, - None => return false, - }; - - let current_host = context.shared.current_host; - if current_host != Some(host.opaque()) { - loop { - let outer_host = host.containing_shadow_host(); - if outer_host.as_ref().map(|h| h.opaque()) == current_host { - break; - } - let outer_host = match outer_host { - Some(h) => h, - None => return false, - }; - // TODO(emilio): if worth it, we could early return if - // host doesn't have the exportparts attribute. - hosts.push(host); - host = outer_host; - } - } - - // Translate the part into the right scope. - parts.iter().all(|part| { - let mut part = part.clone(); - for host in hosts.iter().rev() { - part = match host.imported_part(&part) { - Some(p) => p, - None => return false, - }; - } - element.is_part(&part) - }) - }, - Component::Slotted(ref selector) => { - // are never flattened tree slottables. - !element.is_html_slot_element() && - context.shared.nest(|context| { - matches_complex_selector(selector.iter(), element, context, flags_setter) - }) - }, - Component::PseudoElement(ref pseudo) => { - element.match_pseudo_element(pseudo, context.shared) - }, - Component::LocalName(ref local_name) => matches_local_name(element, local_name), - Component::ExplicitUniversalType | Component::ExplicitAnyNamespace => true, - Component::Namespace(_, ref url) | Component::DefaultNamespace(ref url) => { - element.has_namespace(&url.borrow()) - }, - Component::ExplicitNoNamespace => { - let ns = crate::parser::namespace_empty_string::(); - element.has_namespace(&ns.borrow()) - }, - Component::ID(ref id) => { - element.has_id(id, context.shared.classes_and_ids_case_sensitivity()) - }, - Component::Class(ref class) => { - element.has_class(class, context.shared.classes_and_ids_case_sensitivity()) - }, - Component::AttributeInNoNamespaceExists { - ref local_name, - ref local_name_lower, - } => { - let is_html = element.is_html_element_in_html_document(); - element.attr_matches( - &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::()), - select_name(is_html, local_name, local_name_lower), - &AttrSelectorOperation::Exists, - ) + debug_assert!(context.shared.is_nested() || !context.shared.in_negation()); + + match *selector { + Component::Combinator(_) => unreachable!(), + Component::Part(ref parts) => { + let mut hosts = SmallVec::<[E; 4]>::new(); + + let mut host = match element.containing_shadow_host() { + Some(h) => h, + None => return false, + }; + + let current_host = context.shared.current_host; + if current_host != Some(host.opaque()) { + loop { + let outer_host = host.containing_shadow_host(); + if outer_host.as_ref().map(|h| h.opaque()) == current_host { + break; + } + let outer_host = match outer_host { + Some(h) => h, + None => return false, + }; + // TODO(emilio): if worth it, we could early return if + // host doesn't have the exportparts attribute. + hosts.push(host); + host = outer_host; + } + } + + // Translate the part into the right scope. + parts.iter().all(|part| { + let mut part = part.clone(); + for host in hosts.iter().rev() { + part = match host.imported_part(&part) { + Some(p) => p, + None => return false, + }; + } + element.is_part(&part) + }) + } + Component::Slotted(ref selector) => { + // are never flattened tree slottables. + !element.is_html_slot_element() + && context + .shared + .nest(|context| matches_complex_selector(selector.iter(), element, context, flags_setter)) + } + Component::PseudoElement(ref pseudo) => element.match_pseudo_element(pseudo, context.shared), + Component::LocalName(ref local_name) => matches_local_name(element, local_name), + Component::ExplicitUniversalType | Component::ExplicitAnyNamespace => true, + Component::Namespace(_, ref url) | Component::DefaultNamespace(ref url) => { + element.has_namespace(&url.borrow()) + } + Component::ExplicitNoNamespace => { + let ns = crate::parser::namespace_empty_string::(); + element.has_namespace(&ns.borrow()) + } + Component::ID(ref id) => element.has_id(id, context.shared.classes_and_ids_case_sensitivity()), + Component::Class(ref class) => element.has_class(class, context.shared.classes_and_ids_case_sensitivity()), + Component::AttributeInNoNamespaceExists { + ref local_name, + ref local_name_lower, + } => { + let is_html = element.is_html_element_in_html_document(); + element.attr_matches( + &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::()), + select_name(is_html, local_name, local_name_lower), + &AttrSelectorOperation::Exists, + ) + } + Component::AttributeInNoNamespace { + ref local_name, + ref value, + operator, + case_sensitivity, + never_matches, + } => { + if never_matches { + return false; + } + let is_html = element.is_html_element_in_html_document(); + element.attr_matches( + &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::()), + local_name, + &AttrSelectorOperation::WithValue { + operator, + case_sensitivity: case_sensitivity.to_unconditional(is_html), + expected_value: value, }, - Component::AttributeInNoNamespace { - ref local_name, - ref value, + ) + } + Component::AttributeOther(ref attr_sel) => { + if attr_sel.never_matches { + return false; + } + let is_html = element.is_html_element_in_html_document(); + let empty_string; + let namespace = match attr_sel.namespace() { + Some(ns) => ns, + None => { + empty_string = crate::parser::namespace_empty_string::(); + NamespaceConstraint::Specific(&empty_string) + } + }; + element.attr_matches( + &namespace, + select_name(is_html, &attr_sel.local_name, &attr_sel.local_name_lower), + &match attr_sel.operation { + ParsedAttrSelectorOperation::Exists => AttrSelectorOperation::Exists, + ParsedAttrSelectorOperation::WithValue { operator, case_sensitivity, - never_matches, - } => { - if never_matches { - return false; - } - let is_html = element.is_html_element_in_html_document(); - element.attr_matches( - &NamespaceConstraint::Specific(&crate::parser::namespace_empty_string::()), - local_name, - &AttrSelectorOperation::WithValue { - operator, - case_sensitivity: case_sensitivity.to_unconditional(is_html), - expected_value: value, - }, - ) - }, - Component::AttributeOther(ref attr_sel) => { - if attr_sel.never_matches { - return false; - } - let is_html = element.is_html_element_in_html_document(); - let empty_string; - let namespace = match attr_sel.namespace() { - Some(ns) => ns, - None => { - empty_string = crate::parser::namespace_empty_string::(); - NamespaceConstraint::Specific(&empty_string) - }, - }; - element.attr_matches( - &namespace, - select_name(is_html, &attr_sel.local_name, &attr_sel.local_name_lower), - &match attr_sel.operation { - ParsedAttrSelectorOperation::Exists => AttrSelectorOperation::Exists, - ParsedAttrSelectorOperation::WithValue { - operator, - case_sensitivity, - ref expected_value, - } => AttrSelectorOperation::WithValue { - operator, - case_sensitivity: case_sensitivity.to_unconditional(is_html), - expected_value, - }, - }, - ) - }, - Component::NonTSPseudoClass(ref pc) => { - if context.matches_hover_and_active_quirk == MatchesHoverAndActiveQuirk::Yes && - !context.shared.is_nested() && - pc.is_active_or_hover() && - !element.is_link() - { - return false; - } - - element.match_non_ts_pseudo_class(pc, &mut context.shared, flags_setter) - }, - Component::FirstChild => matches_first_child(element, flags_setter), - Component::LastChild => matches_last_child(element, flags_setter), - Component::OnlyChild => { - matches_first_child(element, flags_setter) && matches_last_child(element, flags_setter) - }, - Component::Root => element.is_root(), - Component::Empty => { - flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR); - element.is_empty() - }, - Component::Host(ref selector) => { - context - .shared - .shadow_host() - .map_or(false, |host| host == element.opaque()) && - selector.as_ref().map_or(true, |selector| { - context.shared.nest(|context| { - matches_complex_selector(selector.iter(), element, context, flags_setter) - }) - }) - }, - Component::Scope => match context.shared.scope_element { - Some(ref scope_element) => element.opaque() == *scope_element, - None => element.is_root(), - }, - Component::NthChild(a, b) => { - matches_generic_nth_child(element, context, a, b, false, false, flags_setter) - }, - Component::NthLastChild(a, b) => { - matches_generic_nth_child(element, context, a, b, false, true, flags_setter) - }, - Component::NthOfType(a, b) => { - matches_generic_nth_child(element, context, a, b, true, false, flags_setter) - }, - Component::NthLastOfType(a, b) => { - matches_generic_nth_child(element, context, a, b, true, true, flags_setter) - }, - Component::FirstOfType => { - matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter) - }, - Component::LastOfType => { - matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter) - }, - Component::OnlyOfType => { - matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter) && - matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter) + ref expected_value, + } => AttrSelectorOperation::WithValue { + operator, + case_sensitivity: case_sensitivity.to_unconditional(is_html), + expected_value, + }, }, - Component::Is(ref list) | Component::Where(ref list) => context.shared.nest(|context| { - for selector in &**list { - if matches_complex_selector(selector.iter(), element, context, flags_setter) { - return true; - } - } - false - }), - Component::Negation(ref list) => context.shared.nest_for_negation(|context| { - for selector in &**list { - if matches_complex_selector(selector.iter(), element, context, flags_setter) { - return false; - } - } - true - }), - Component::Nesting | Component::Has(..) => unreachable!() + ) + } + Component::NonTSPseudoClass(ref pc) => { + if context.matches_hover_and_active_quirk == MatchesHoverAndActiveQuirk::Yes + && !context.shared.is_nested() + && pc.is_active_or_hover() + && !element.is_link() + { + return false; + } + + element.match_non_ts_pseudo_class(pc, &mut context.shared, flags_setter) + } + Component::FirstChild => matches_first_child(element, flags_setter), + Component::LastChild => matches_last_child(element, flags_setter), + Component::OnlyChild => { + matches_first_child(element, flags_setter) && matches_last_child(element, flags_setter) + } + Component::Root => element.is_root(), + Component::Empty => { + flags_setter(element, ElementSelectorFlags::HAS_EMPTY_SELECTOR); + element.is_empty() + } + Component::Host(ref selector) => { + context.shared.shadow_host().map_or(false, |host| host == element.opaque()) + && selector.as_ref().map_or(true, |selector| { + context + .shared + .nest(|context| matches_complex_selector(selector.iter(), element, context, flags_setter)) + }) } + Component::Scope => match context.shared.scope_element { + Some(ref scope_element) => element.opaque() == *scope_element, + None => element.is_root(), + }, + Component::NthChild(a, b) => matches_generic_nth_child(element, context, a, b, false, false, flags_setter), + Component::NthLastChild(a, b) => matches_generic_nth_child(element, context, a, b, false, true, flags_setter), + Component::NthOfType(a, b) => matches_generic_nth_child(element, context, a, b, true, false, flags_setter), + Component::NthLastOfType(a, b) => matches_generic_nth_child(element, context, a, b, true, true, flags_setter), + Component::FirstOfType => matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter), + Component::LastOfType => matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter), + Component::OnlyOfType => { + matches_generic_nth_child(element, context, 0, 1, true, false, flags_setter) + && matches_generic_nth_child(element, context, 0, 1, true, true, flags_setter) + } + Component::Is(ref list) | Component::Where(ref list) => context.shared.nest(|context| { + for selector in &**list { + if matches_complex_selector(selector.iter(), element, context, flags_setter) { + return true; + } + } + false + }), + Component::Negation(ref list) => context.shared.nest_for_negation(|context| { + for selector in &**list { + if matches_complex_selector(selector.iter(), element, context, flags_setter) { + return false; + } + } + true + }), + Component::Nesting | Component::Has(..) => unreachable!(), + } } #[inline(always)] fn select_name<'a, T>(is_html: bool, local_name: &'a T, local_name_lower: &'a T) -> &'a T { - if is_html { - local_name_lower - } else { - local_name - } + if is_html { + local_name_lower + } else { + local_name + } } #[inline] fn matches_generic_nth_child<'i, E, F>( - element: &E, - context: &mut LocalMatchingContext<'_, '_, 'i, E::Impl>, - a: i32, - b: i32, - is_of_type: bool, - is_from_end: bool, - flags_setter: &mut F, + element: &E, + context: &mut LocalMatchingContext<'_, '_, 'i, E::Impl>, + a: i32, + b: i32, + is_of_type: bool, + is_from_end: bool, + flags_setter: &mut F, ) -> bool where - E: Element<'i>, - F: FnMut(&E, ElementSelectorFlags), + E: Element<'i>, + F: FnMut(&E, ElementSelectorFlags), { - if element.ignores_nth_child_selectors() { - return false; - } - - flags_setter( - element, - if is_from_end { - ElementSelectorFlags::HAS_SLOW_SELECTOR - } else { - ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS - }, - ); - - // Grab a reference to the appropriate cache. - let mut cache = context - .shared - .nth_index_cache - .as_mut() - .map(|c| c.get(is_of_type, is_from_end)); - - // Lookup or compute the index. - let index = if let Some(i) = cache.as_mut().and_then(|c| c.lookup(element.opaque())) { - i + if element.ignores_nth_child_selectors() { + return false; + } + + flags_setter( + element, + if is_from_end { + ElementSelectorFlags::HAS_SLOW_SELECTOR } else { - let i = nth_child_index(element, is_of_type, is_from_end, cache.as_deref_mut()); - if let Some(c) = cache.as_mut() { - c.insert(element.opaque(), i) - } - i - }; - debug_assert_eq!( - index, - nth_child_index(element, is_of_type, is_from_end, None), - "invalid cache" - ); - - // Is there a non-negative integer n such that An+B=index? - match index.checked_sub(b) { - None => false, - Some(an) => match an.checked_div(a) { + ElementSelectorFlags::HAS_SLOW_SELECTOR_LATER_SIBLINGS + }, + ); + + // Grab a reference to the appropriate cache. + let mut cache = context.shared.nth_index_cache.as_mut().map(|c| c.get(is_of_type, is_from_end)); + + // Lookup or compute the index. + let index = if let Some(i) = cache.as_mut().and_then(|c| c.lookup(element.opaque())) { + i + } else { + let i = nth_child_index(element, is_of_type, is_from_end, cache.as_deref_mut()); + if let Some(c) = cache.as_mut() { + c.insert(element.opaque(), i) + } + i + }; + debug_assert_eq!( + index, + nth_child_index(element, is_of_type, is_from_end, None), + "invalid cache" + ); + + // Is there a non-negative integer n such that An+B=index? + match index.checked_sub(b) { + None => false, + Some(an) => match an.checked_div(a) { Some(n) => n >= 0 && a * n == an, None /* a == 0 */ => an == 0, }, - } + } } #[inline] fn nth_child_index<'i, E>( - element: &E, - is_of_type: bool, - is_from_end: bool, - mut cache: Option<&mut NthIndexCacheInner>, + element: &E, + is_of_type: bool, + is_from_end: bool, + mut cache: Option<&mut NthIndexCacheInner>, ) -> i32 where - E: Element<'i>, + E: Element<'i>, { - // The traversal mostly processes siblings left to right. So when we walk - // siblings to the right when computing NthLast/NthLastOfType we're unlikely - // to get cache hits along the way. As such, we take the hit of walking the - // siblings to the left checking the cache in the is_from_end case (this - // matches what Gecko does). The indices-from-the-left is handled during the - // regular look further below. - if let Some(ref mut c) = cache { - if is_from_end && !c.is_empty() { - let mut index: i32 = 1; - let mut curr = element.clone(); - while let Some(e) = curr.prev_sibling_element() { - curr = e; - if !is_of_type || element.is_same_type(&curr) { - if let Some(i) = c.lookup(curr.opaque()) { - return i - index; - } - index += 1; - } - } + // The traversal mostly processes siblings left to right. So when we walk + // siblings to the right when computing NthLast/NthLastOfType we're unlikely + // to get cache hits along the way. As such, we take the hit of walking the + // siblings to the left checking the cache in the is_from_end case (this + // matches what Gecko does). The indices-from-the-left is handled during the + // regular look further below. + if let Some(ref mut c) = cache { + if is_from_end && !c.is_empty() { + let mut index: i32 = 1; + let mut curr = element.clone(); + while let Some(e) = curr.prev_sibling_element() { + curr = e; + if !is_of_type || element.is_same_type(&curr) { + if let Some(i) = c.lookup(curr.opaque()) { + return i - index; + } + index += 1; } + } } + } - let mut index: i32 = 1; - let mut curr = element.clone(); - let next = |e: E| { - if is_from_end { - e.next_sibling_element() - } else { - e.prev_sibling_element() - } - }; - while let Some(e) = next(curr) { - curr = e; - if !is_of_type || element.is_same_type(&curr) { - // If we're computing indices from the left, check each element in the - // cache. We handle the indices-from-the-right case at the top of this - // function. - if !is_from_end { - if let Some(i) = cache.as_mut().and_then(|c| c.lookup(curr.opaque())) { - return i + index; - } - } - index += 1; + let mut index: i32 = 1; + let mut curr = element.clone(); + let next = |e: E| { + if is_from_end { + e.next_sibling_element() + } else { + e.prev_sibling_element() + } + }; + while let Some(e) = next(curr) { + curr = e; + if !is_of_type || element.is_same_type(&curr) { + // If we're computing indices from the left, check each element in the + // cache. We handle the indices-from-the-right case at the top of this + // function. + if !is_from_end { + if let Some(i) = cache.as_mut().and_then(|c| c.lookup(curr.opaque())) { + return i + index; } + } + index += 1; } + } - index + index } #[inline] fn matches_first_child<'i, E, F>(element: &E, flags_setter: &mut F) -> bool where - E: Element<'i>, - F: FnMut(&E, ElementSelectorFlags), + E: Element<'i>, + F: FnMut(&E, ElementSelectorFlags), { - flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); - element.prev_sibling_element().is_none() + flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + element.prev_sibling_element().is_none() } #[inline] fn matches_last_child<'i, E, F>(element: &E, flags_setter: &mut F) -> bool where - E: Element<'i>, - F: FnMut(&E, ElementSelectorFlags), + E: Element<'i>, + F: FnMut(&E, ElementSelectorFlags), { - flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); - element.next_sibling_element().is_none() + flags_setter(element, ElementSelectorFlags::HAS_EDGE_CHILD_SELECTOR); + element.next_sibling_element().is_none() } diff --git a/selectors/nth_index_cache.rs b/selectors/nth_index_cache.rs index 979c3100..2ca33e7b 100644 --- a/selectors/nth_index_cache.rs +++ b/selectors/nth_index_cache.rs @@ -12,22 +12,22 @@ use fxhash::FxHashMap; /// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1401855#c3 #[derive(Default)] pub struct NthIndexCache { - nth: NthIndexCacheInner, - nth_last: NthIndexCacheInner, - nth_of_type: NthIndexCacheInner, - nth_last_of_type: NthIndexCacheInner, + nth: NthIndexCacheInner, + nth_last: NthIndexCacheInner, + nth_of_type: NthIndexCacheInner, + nth_last_of_type: NthIndexCacheInner, } impl NthIndexCache { - /// Gets the appropriate cache for the given parameters. - pub fn get(&mut self, is_of_type: bool, is_from_end: bool) -> &mut NthIndexCacheInner { - match (is_of_type, is_from_end) { - (false, false) => &mut self.nth, - (false, true) => &mut self.nth_last, - (true, false) => &mut self.nth_of_type, - (true, true) => &mut self.nth_last_of_type, - } + /// Gets the appropriate cache for the given parameters. + pub fn get(&mut self, is_of_type: bool, is_from_end: bool) -> &mut NthIndexCacheInner { + match (is_of_type, is_from_end) { + (false, false) => &mut self.nth, + (false, true) => &mut self.nth_last, + (true, false) => &mut self.nth_of_type, + (true, true) => &mut self.nth_last_of_type, } + } } /// The concrete per-pseudo-class cache. @@ -35,18 +35,18 @@ impl NthIndexCache { pub struct NthIndexCacheInner(FxHashMap); impl NthIndexCacheInner { - /// Does a lookup for a given element in the cache. - pub fn lookup(&mut self, el: OpaqueElement) -> Option { - self.0.get(&el).copied() - } + /// Does a lookup for a given element in the cache. + pub fn lookup(&mut self, el: OpaqueElement) -> Option { + self.0.get(&el).copied() + } - /// Inserts an entry into the cache. - pub fn insert(&mut self, element: OpaqueElement, index: i32) { - self.0.insert(element, index); - } + /// Inserts an entry into the cache. + pub fn insert(&mut self, element: OpaqueElement, index: i32) { + self.0.insert(element, index); + } - /// Returns whether the cache is empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } + /// Returns whether the cache is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } } diff --git a/selectors/parser.rs b/selectors/parser.rs index b1bd3315..59dcdbb6 100644 --- a/selectors/parser.rs +++ b/selectors/parser.rs @@ -16,67 +16,67 @@ use cssparser::{CowRcStr, Delimiter, SourceLocation}; use cssparser::{Parser as CssParser, ToCss, Token}; use precomputed_hash::PrecomputedHash; use smallvec::SmallVec; -use std::borrow::{Borrow}; +use std::borrow::Borrow; use std::fmt::{self, Debug}; use std::iter::Rev; use std::slice; /// A trait that represents a pseudo-element. pub trait PseudoElement<'i>: Sized + ToCss { - /// The `SelectorImpl` this pseudo-element is used for. - type Impl: SelectorImpl<'i>; + /// The `SelectorImpl` this pseudo-element is used for. + type Impl: SelectorImpl<'i>; - /// Whether the pseudo-element supports a given state selector to the right - /// of it. - fn accepts_state_pseudo_classes(&self) -> bool { - false - } + /// Whether the pseudo-element supports a given state selector to the right + /// of it. + fn accepts_state_pseudo_classes(&self) -> bool { + false + } - /// Whether this pseudo-element is valid after a ::slotted(..) pseudo. - fn valid_after_slotted(&self) -> bool { - false - } + /// Whether this pseudo-element is valid after a ::slotted(..) pseudo. + fn valid_after_slotted(&self) -> bool { + false + } - fn is_webkit_scrollbar(&self) -> bool { - false - } + fn is_webkit_scrollbar(&self) -> bool { + false + } } /// A trait that represents a pseudo-class. pub trait NonTSPseudoClass<'i>: Sized + ToCss { - /// The `SelectorImpl` this pseudo-element is used for. - type Impl: SelectorImpl<'i>; + /// The `SelectorImpl` this pseudo-element is used for. + type Impl: SelectorImpl<'i>; - /// Whether this pseudo-class is :active or :hover. - fn is_active_or_hover(&self) -> bool; + /// Whether this pseudo-class is :active or :hover. + fn is_active_or_hover(&self) -> bool; - /// Whether this pseudo-class belongs to: - /// - /// https://drafts.csswg.org/selectors-4/#useraction-pseudos - fn is_user_action_state(&self) -> bool; + /// Whether this pseudo-class belongs to: + /// + /// https://drafts.csswg.org/selectors-4/#useraction-pseudos + fn is_user_action_state(&self) -> bool; - fn is_webkit_scrollbar_state(&self) -> bool { - false - } + fn is_webkit_scrollbar_state(&self) -> bool { + false + } - fn visit(&self, _visitor: &mut V) -> bool - where - V: SelectorVisitor<'i, Impl = Self::Impl>, - { - true - } + fn visit(&self, _visitor: &mut V) -> bool + where + V: SelectorVisitor<'i, Impl = Self::Impl>, + { + true + } } /// Returns a Cow::Borrowed if `s` is already ASCII lowercase, and a /// Cow::Owned if `s` had to be converted into ASCII lowercase. fn to_ascii_lowercase<'i>(s: CowRcStr<'i>) -> CowRcStr<'i> { - if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { - let mut string = s.to_string(); - string[first_uppercase..].make_ascii_lowercase(); - string.into() - } else { - s - } + if let Some(first_uppercase) = s.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { + let mut string = s.to_string(); + string[first_uppercase..].make_ascii_lowercase(); + string.into() + } else { + s + } } bitflags! { @@ -126,76 +126,76 @@ bitflags! { } impl SelectorParsingState { - #[inline] - fn allows_pseudos(self) -> bool { - // NOTE(emilio): We allow pseudos after ::part and such. - !self.intersects(Self::AFTER_PSEUDO_ELEMENT | Self::DISALLOW_PSEUDOS) - } - - #[inline] - fn allows_slotted(self) -> bool { - !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS) - } - - #[inline] - fn allows_part(self) -> bool { - !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS) - } - - // TODO(emilio): Maybe some of these should be allowed, but this gets us on - // the safe side for now, matching previous behavior. Gotta be careful with - // the ones like :-moz-any, which allow nested selectors but don't carry the - // state, and so on. - #[inline] - fn allows_custom_functional_pseudo_classes(self) -> bool { - !self.intersects(Self::AFTER_PSEUDO) - } - - #[inline] - fn allows_non_functional_pseudo_classes(self) -> bool { - !self.intersects(Self::AFTER_SLOTTED | Self::AFTER_NON_STATEFUL_PSEUDO_ELEMENT) - } - - #[inline] - fn allows_tree_structural_pseudo_classes(self) -> bool { - !self.intersects(Self::AFTER_PSEUDO) - } - - #[inline] - fn allows_combinators(self) -> bool { - !self.intersects(Self::DISALLOW_COMBINATORS) - } + #[inline] + fn allows_pseudos(self) -> bool { + // NOTE(emilio): We allow pseudos after ::part and such. + !self.intersects(Self::AFTER_PSEUDO_ELEMENT | Self::DISALLOW_PSEUDOS) + } + + #[inline] + fn allows_slotted(self) -> bool { + !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS) + } + + #[inline] + fn allows_part(self) -> bool { + !self.intersects(Self::AFTER_PSEUDO | Self::DISALLOW_PSEUDOS) + } + + // TODO(emilio): Maybe some of these should be allowed, but this gets us on + // the safe side for now, matching previous behavior. Gotta be careful with + // the ones like :-moz-any, which allow nested selectors but don't carry the + // state, and so on. + #[inline] + fn allows_custom_functional_pseudo_classes(self) -> bool { + !self.intersects(Self::AFTER_PSEUDO) + } + + #[inline] + fn allows_non_functional_pseudo_classes(self) -> bool { + !self.intersects(Self::AFTER_SLOTTED | Self::AFTER_NON_STATEFUL_PSEUDO_ELEMENT) + } + + #[inline] + fn allows_tree_structural_pseudo_classes(self) -> bool { + !self.intersects(Self::AFTER_PSEUDO) + } + + #[inline] + fn allows_combinators(self) -> bool { + !self.intersects(Self::DISALLOW_COMBINATORS) + } } pub type SelectorParseError<'i> = ParseError<'i, SelectorParseErrorKind<'i>>; #[derive(Clone, Debug, PartialEq)] pub enum SelectorParseErrorKind<'i> { - NoQualifiedNameInAttributeSelector(Token<'i>), - EmptySelector, - DanglingCombinator, - NonCompoundSelector, - NonPseudoElementAfterSlotted, - InvalidPseudoElementAfterSlotted, - InvalidPseudoElementInsideWhere, - InvalidPseudoClassBeforeWebKitScrollbar, - InvalidPseudoClassAfterWebKitScrollbar, - InvalidPseudoClassAfterPseudoElement, - InvalidState, - MissingNestingSelector, - MissingNestingPrefix, - UnexpectedTokenInAttributeSelector(Token<'i>), - PseudoElementExpectedColon(Token<'i>), - PseudoElementExpectedIdent(Token<'i>), - NoIdentForPseudo(Token<'i>), - UnsupportedPseudoClassOrElement(CowRcStr<'i>), - UnexpectedIdent(CowRcStr<'i>), - ExpectedNamespace(CowRcStr<'i>), - ExpectedBarInAttr(Token<'i>), - BadValueInAttr(Token<'i>), - InvalidQualNameInAttr(Token<'i>), - ExplicitNamespaceUnexpectedToken(Token<'i>), - ClassNeedsIdent(Token<'i>), + NoQualifiedNameInAttributeSelector(Token<'i>), + EmptySelector, + DanglingCombinator, + NonCompoundSelector, + NonPseudoElementAfterSlotted, + InvalidPseudoElementAfterSlotted, + InvalidPseudoElementInsideWhere, + InvalidPseudoClassBeforeWebKitScrollbar, + InvalidPseudoClassAfterWebKitScrollbar, + InvalidPseudoClassAfterPseudoElement, + InvalidState, + MissingNestingSelector, + MissingNestingPrefix, + UnexpectedTokenInAttributeSelector(Token<'i>), + PseudoElementExpectedColon(Token<'i>), + PseudoElementExpectedIdent(Token<'i>), + NoIdentForPseudo(Token<'i>), + UnsupportedPseudoClassOrElement(CowRcStr<'i>), + UnexpectedIdent(CowRcStr<'i>), + ExpectedNamespace(CowRcStr<'i>), + ExpectedBarInAttr(Token<'i>), + BadValueInAttr(Token<'i>), + InvalidQualNameInAttr(Token<'i>), + ExplicitNamespaceUnexpectedToken(Token<'i>), + ClassNeedsIdent(Token<'i>), } macro_rules! with_all_bounds { @@ -250,267 +250,245 @@ with_bounds! { } pub trait Parser<'i> { - type Impl: SelectorImpl<'i>; - type Error: 'i + From>; - - /// Whether to parse the `::slotted()` pseudo-element. - fn parse_slotted(&self) -> bool { - false - } - - /// Whether to parse the `::part()` pseudo-element. - fn parse_part(&self) -> bool { - false - } - - /// Whether to parse the `:where` pseudo-class. - fn parse_is_and_where(&self) -> bool { - false - } - - /// The error recovery that selector lists inside :is() and :where() have. - fn is_and_where_error_recovery(&self) -> ParseErrorRecovery { - ParseErrorRecovery::IgnoreInvalidSelector - } - - /// Whether the given function name is an alias for the `:is()` function. - fn is_is_alias(&self, _name: &str) -> bool { - false - } + type Impl: SelectorImpl<'i>; + type Error: 'i + From>; + + /// Whether to parse the `::slotted()` pseudo-element. + fn parse_slotted(&self) -> bool { + false + } + + /// Whether to parse the `::part()` pseudo-element. + fn parse_part(&self) -> bool { + false + } + + /// Whether to parse the `:where` pseudo-class. + fn parse_is_and_where(&self) -> bool { + false + } + + /// The error recovery that selector lists inside :is() and :where() have. + fn is_and_where_error_recovery(&self) -> ParseErrorRecovery { + ParseErrorRecovery::IgnoreInvalidSelector + } + + /// Whether the given function name is an alias for the `:is()` function. + fn is_is_alias(&self, _name: &str) -> bool { + false + } + + /// Whether to parse the `:host` pseudo-class. + fn parse_host(&self) -> bool { + false + } + + /// Parses non-tree-structural pseudo-classes. Tree structural pseudo-classes, + /// like `:first-child`, are built into this library. + /// + /// This function can return an "Err" pseudo-element in order to support CSS2.1 + /// pseudo-elements. + fn parse_non_ts_pseudo_class( + &self, + location: SourceLocation, + name: CowRcStr<'i>, + ) -> Result<>::NonTSPseudoClass, ParseError<'i, Self::Error>> { + Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name))) + } - /// Whether to parse the `:host` pseudo-class. - fn parse_host(&self) -> bool { - false - } + fn parse_non_ts_functional_pseudo_class<'t>( + &self, + name: CowRcStr<'i>, + arguments: &mut CssParser<'i, 't>, + ) -> Result<>::NonTSPseudoClass, ParseError<'i, Self::Error>> { + Err(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name))) + } - /// Parses non-tree-structural pseudo-classes. Tree structural pseudo-classes, - /// like `:first-child`, are built into this library. - /// - /// This function can return an "Err" pseudo-element in order to support CSS2.1 - /// pseudo-elements. - fn parse_non_ts_pseudo_class( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<>::NonTSPseudoClass, ParseError<'i, Self::Error>> { - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } + fn parse_pseudo_element( + &self, + location: SourceLocation, + name: CowRcStr<'i>, + ) -> Result<>::PseudoElement, ParseError<'i, Self::Error>> { + Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name))) + } - fn parse_non_ts_functional_pseudo_class<'t>( - &self, - name: CowRcStr<'i>, - arguments: &mut CssParser<'i, 't>, - ) -> Result<>::NonTSPseudoClass, ParseError<'i, Self::Error>> { - Err( - arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } + fn parse_functional_pseudo_element<'t>( + &self, + name: CowRcStr<'i>, + arguments: &mut CssParser<'i, 't>, + ) -> Result<>::PseudoElement, ParseError<'i, Self::Error>> { + Err(arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name))) + } - fn parse_pseudo_element( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result<>::PseudoElement, ParseError<'i, Self::Error>> { - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_functional_pseudo_element<'t>( - &self, - name: CowRcStr<'i>, - arguments: &mut CssParser<'i, 't>, - ) -> Result<>::PseudoElement, ParseError<'i, Self::Error>> { - Err( - arguments.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn default_namespace(&self) -> Option<>::NamespaceUrl> { - None - } + fn default_namespace(&self) -> Option<>::NamespaceUrl> { + None + } - fn namespace_for_prefix( - &self, - _prefix: &>::NamespacePrefix, - ) -> Option<>::NamespaceUrl> { - None - } + fn namespace_for_prefix( + &self, + _prefix: &>::NamespacePrefix, + ) -> Option<>::NamespaceUrl> { + None + } - fn is_nesting_allowed(&self) -> bool { - false - } + fn is_nesting_allowed(&self) -> bool { + false + } } #[derive(Clone, Debug, Eq, PartialEq)] -pub struct SelectorList<'i, Impl: SelectorImpl<'i>>( - pub SmallVec<[Selector<'i, Impl>; 1]>, -); +pub struct SelectorList<'i, Impl: SelectorImpl<'i>>(pub SmallVec<[Selector<'i, Impl>; 1]>); /// How to treat invalid selectors in a selector list. pub enum ParseErrorRecovery { - /// Discard the entire selector list, this is the default behavior for - /// almost all of CSS. - DiscardList, - /// Ignore invalid selectors, potentially creating an empty selector list. - /// - /// This is the error recovery mode of :is() and :where() - IgnoreInvalidSelector, + /// Discard the entire selector list, this is the default behavior for + /// almost all of CSS. + DiscardList, + /// Ignore invalid selectors, potentially creating an empty selector list. + /// + /// This is the error recovery mode of :is() and :where() + IgnoreInvalidSelector, } #[derive(Eq, PartialEq, Clone, Copy)] pub enum NestingRequirement { - None, - Prefixed, - Contained + None, + Prefixed, + Contained, } impl<'i, Impl: SelectorImpl<'i>> SelectorList<'i, Impl> { - /// Parse a comma-separated list of Selectors. - /// - /// - /// Return the Selectors or Err if there is an invalid selector. - pub fn parse<'t, P>( - parser: &P, - input: &mut CssParser<'i, 't>, - nesting_requirement: NestingRequirement, - ) -> Result> - where - P: Parser<'i, Impl = Impl>, - { - Self::parse_with_state( - parser, - input, - &mut SelectorParsingState::empty(), - ParseErrorRecovery::DiscardList, - nesting_requirement, - ) - } - - #[inline] - fn parse_with_state<'t, P>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: &mut SelectorParsingState, - recovery: ParseErrorRecovery, - nesting_requirement: NestingRequirement, - ) -> Result> - where - P: Parser<'i, Impl = Impl>, - { - let original_state = *state; - let mut values = SmallVec::new(); - loop { - let selector = input.parse_until_before(Delimiter::Comma, |input| { - let mut selector_state = original_state; - let result = parse_selector(parser, input, &mut selector_state, nesting_requirement); - if selector_state.contains(SelectorParsingState::AFTER_NESTING) { - state.insert(SelectorParsingState::AFTER_NESTING) - } - result - }); - - let was_ok = selector.is_ok(); - match selector { - Ok(selector) => values.push(selector), - Err(err) => match recovery { - ParseErrorRecovery::DiscardList => return Err(err), - ParseErrorRecovery::IgnoreInvalidSelector => {}, - }, - } + /// Parse a comma-separated list of Selectors. + /// + /// + /// Return the Selectors or Err if there is an invalid selector. + pub fn parse<'t, P>( + parser: &P, + input: &mut CssParser<'i, 't>, + nesting_requirement: NestingRequirement, + ) -> Result> + where + P: Parser<'i, Impl = Impl>, + { + Self::parse_with_state( + parser, + input, + &mut SelectorParsingState::empty(), + ParseErrorRecovery::DiscardList, + nesting_requirement, + ) + } + + #[inline] + fn parse_with_state<'t, P>( + parser: &P, + input: &mut CssParser<'i, 't>, + state: &mut SelectorParsingState, + recovery: ParseErrorRecovery, + nesting_requirement: NestingRequirement, + ) -> Result> + where + P: Parser<'i, Impl = Impl>, + { + let original_state = *state; + let mut values = SmallVec::new(); + loop { + let selector = input.parse_until_before(Delimiter::Comma, |input| { + let mut selector_state = original_state; + let result = parse_selector(parser, input, &mut selector_state, nesting_requirement); + if selector_state.contains(SelectorParsingState::AFTER_NESTING) { + state.insert(SelectorParsingState::AFTER_NESTING) + } + result + }); + + let was_ok = selector.is_ok(); + match selector { + Ok(selector) => values.push(selector), + Err(err) => match recovery { + ParseErrorRecovery::DiscardList => return Err(err), + ParseErrorRecovery::IgnoreInvalidSelector => {} + }, + } - loop { - match input.next() { - Err(_) => return Ok(SelectorList(values)), - Ok(&Token::Comma) => break, - Ok(_) => { - debug_assert!(!was_ok, "Shouldn't have got a selector if getting here"); - }, - } - } + loop { + match input.next() { + Err(_) => return Ok(SelectorList(values)), + Ok(&Token::Comma) => break, + Ok(_) => { + debug_assert!(!was_ok, "Shouldn't have got a selector if getting here"); + } } + } } + } - #[inline] - fn parse_relative<'t, P>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: &mut SelectorParsingState, - recovery: ParseErrorRecovery, - ) -> Result> - where - P: Parser<'i, Impl = Impl>, - { - let original_state = *state; - let mut values = SmallVec::new(); - loop { - let selector = input.parse_until_before(Delimiter::Comma, |input| { - let mut selector_state = original_state; - let result = parse_relative_selector(parser, input, &mut selector_state); - if selector_state.contains(SelectorParsingState::AFTER_NESTING) { - state.insert(SelectorParsingState::AFTER_NESTING) - } - result - }); - - let was_ok = selector.is_ok(); - match selector { - Ok(selector) => values.push(selector), - Err(err) => match recovery { - ParseErrorRecovery::DiscardList => return Err(err), - ParseErrorRecovery::IgnoreInvalidSelector => {}, - }, - } + #[inline] + fn parse_relative<'t, P>( + parser: &P, + input: &mut CssParser<'i, 't>, + state: &mut SelectorParsingState, + recovery: ParseErrorRecovery, + ) -> Result> + where + P: Parser<'i, Impl = Impl>, + { + let original_state = *state; + let mut values = SmallVec::new(); + loop { + let selector = input.parse_until_before(Delimiter::Comma, |input| { + let mut selector_state = original_state; + let result = parse_relative_selector(parser, input, &mut selector_state); + if selector_state.contains(SelectorParsingState::AFTER_NESTING) { + state.insert(SelectorParsingState::AFTER_NESTING) + } + result + }); + + let was_ok = selector.is_ok(); + match selector { + Ok(selector) => values.push(selector), + Err(err) => match recovery { + ParseErrorRecovery::DiscardList => return Err(err), + ParseErrorRecovery::IgnoreInvalidSelector => {} + }, + } - loop { - match input.next() { - Err(_) => return Ok(SelectorList(values)), - Ok(&Token::Comma) => break, - Ok(_) => { - debug_assert!(!was_ok, "Shouldn't have got a selector if getting here"); - }, - } - } + loop { + match input.next() { + Err(_) => return Ok(SelectorList(values)), + Ok(&Token::Comma) => break, + Ok(_) => { + debug_assert!(!was_ok, "Shouldn't have got a selector if getting here"); + } } + } } + } - /// Creates a SelectorList from a Vec of selectors. Used in tests. - pub fn from_vec(v: Vec>) -> Self { - SelectorList(SmallVec::from_vec(v)) - } + /// Creates a SelectorList from a Vec of selectors. Used in tests. + pub fn from_vec(v: Vec>) -> Self { + SelectorList(SmallVec::from_vec(v)) + } } /// Parses one compound selector suitable for nested stuff like :-moz-any, etc. fn parse_inner_compound_selector<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: &mut SelectorParsingState, + parser: &P, + input: &mut CssParser<'i, 't>, + state: &mut SelectorParsingState, ) -> Result, ParseError<'i, P::Error>> where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl<'i>, + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl<'i>, { - let mut child_state = *state | SelectorParsingState::DISALLOW_PSEUDOS | SelectorParsingState::DISALLOW_COMBINATORS; - let result = parse_selector( - parser, - input, - &mut child_state, - NestingRequirement::None, - )?; - if child_state.contains(SelectorParsingState::AFTER_NESTING) { - state.insert(SelectorParsingState::AFTER_NESTING) - } - Ok(result) + let mut child_state = + *state | SelectorParsingState::DISALLOW_PSEUDOS | SelectorParsingState::DISALLOW_COMBINATORS; + let result = parse_selector(parser, input, &mut child_state, NestingRequirement::None)?; + if child_state.contains(SelectorParsingState::AFTER_NESTING) { + state.insert(SelectorParsingState::AFTER_NESTING) + } + Ok(result) } /// Ancestor hashes for the bloom filter. We precompute these and store them @@ -530,104 +508,98 @@ where /// hashes. #[derive(Clone, Debug, Eq, PartialEq)] pub struct AncestorHashes { - pub packed_hashes: [u32; 3], + pub packed_hashes: [u32; 3], } fn collect_ancestor_hashes<'i, Impl: SelectorImpl<'i>>( - iter: SelectorIter<'_, 'i, Impl>, - quirks_mode: QuirksMode, - hashes: &mut [u32; 4], - len: &mut usize, + iter: SelectorIter<'_, 'i, Impl>, + quirks_mode: QuirksMode, + hashes: &mut [u32; 4], + len: &mut usize, ) -> bool where - Impl::Identifier: PrecomputedHash, - Impl::LocalName: PrecomputedHash, - Impl::NamespaceUrl: PrecomputedHash, + Impl::Identifier: PrecomputedHash, + Impl::LocalName: PrecomputedHash, + Impl::NamespaceUrl: PrecomputedHash, { - for component in AncestorIter::new(iter) { - let hash = match *component { - Component::LocalName(LocalName { - ref name, - ref lower_name, - }) => { - // Only insert the local-name into the filter if it's all - // lowercase. Otherwise we would need to test both hashes, and - // our data structures aren't really set up for that. - if name != lower_name { - continue; - } - name.precomputed_hash() - }, - Component::DefaultNamespace(ref url) | Component::Namespace(_, ref url) => { - url.precomputed_hash() - }, - // In quirks mode, class and id selectors should match - // case-insensitively, so just avoid inserting them into the filter. - Component::ID(ref id) if quirks_mode != QuirksMode::Quirks => id.precomputed_hash(), - Component::Class(ref class) if quirks_mode != QuirksMode::Quirks => { - class.precomputed_hash() - }, - Component::Is(ref list) | Component::Where(ref list) => { - // :where and :is OR their selectors, so we can't put any hash - // in the filter if there's more than one selector, as that'd - // exclude elements that may match one of the other selectors. - if list.len() == 1 && - !collect_ancestor_hashes(list[0].iter(), quirks_mode, hashes, len) - { - return false; - } - continue; - }, - _ => continue, - }; + for component in AncestorIter::new(iter) { + let hash = match *component { + Component::LocalName(LocalName { + ref name, + ref lower_name, + }) => { + // Only insert the local-name into the filter if it's all + // lowercase. Otherwise we would need to test both hashes, and + // our data structures aren't really set up for that. + if name != lower_name { + continue; + } + name.precomputed_hash() + } + Component::DefaultNamespace(ref url) | Component::Namespace(_, ref url) => url.precomputed_hash(), + // In quirks mode, class and id selectors should match + // case-insensitively, so just avoid inserting them into the filter. + Component::ID(ref id) if quirks_mode != QuirksMode::Quirks => id.precomputed_hash(), + Component::Class(ref class) if quirks_mode != QuirksMode::Quirks => class.precomputed_hash(), + Component::Is(ref list) | Component::Where(ref list) => { + // :where and :is OR their selectors, so we can't put any hash + // in the filter if there's more than one selector, as that'd + // exclude elements that may match one of the other selectors. + if list.len() == 1 && !collect_ancestor_hashes(list[0].iter(), quirks_mode, hashes, len) { + return false; + } + continue; + } + _ => continue, + }; - hashes[*len] = hash & BLOOM_HASH_MASK; - *len += 1; - if *len == hashes.len() { - return false; - } + hashes[*len] = hash & BLOOM_HASH_MASK; + *len += 1; + if *len == hashes.len() { + return false; } - true + } + true } impl AncestorHashes { - pub fn new<'i, Impl: SelectorImpl<'i>>(selector: &Selector<'i, Impl>, quirks_mode: QuirksMode) -> Self - where - Impl::Identifier: PrecomputedHash, - Impl::LocalName: PrecomputedHash, - Impl::NamespaceUrl: PrecomputedHash, - { - // Compute ancestor hashes for the bloom filter. - let mut hashes = [0u32; 4]; - let mut len = 0; - collect_ancestor_hashes(selector.iter(), quirks_mode, &mut hashes, &mut len); - debug_assert!(len <= 4); - - // Now, pack the fourth hash (if it exists) into the upper byte of each of - // the other three hashes. - if len == 4 { - let fourth = hashes[3]; - hashes[0] |= (fourth & 0x000000ff) << 24; - hashes[1] |= (fourth & 0x0000ff00) << 16; - hashes[2] |= (fourth & 0x00ff0000) << 8; - } + pub fn new<'i, Impl: SelectorImpl<'i>>(selector: &Selector<'i, Impl>, quirks_mode: QuirksMode) -> Self + where + Impl::Identifier: PrecomputedHash, + Impl::LocalName: PrecomputedHash, + Impl::NamespaceUrl: PrecomputedHash, + { + // Compute ancestor hashes for the bloom filter. + let mut hashes = [0u32; 4]; + let mut len = 0; + collect_ancestor_hashes(selector.iter(), quirks_mode, &mut hashes, &mut len); + debug_assert!(len <= 4); - AncestorHashes { - packed_hashes: [hashes[0], hashes[1], hashes[2]], - } + // Now, pack the fourth hash (if it exists) into the upper byte of each of + // the other three hashes. + if len == 4 { + let fourth = hashes[3]; + hashes[0] |= (fourth & 0x000000ff) << 24; + hashes[1] |= (fourth & 0x0000ff00) << 16; + hashes[2] |= (fourth & 0x00ff0000) << 8; } - /// Returns the fourth hash, reassembled from parts. - pub fn fourth_hash(&self) -> u32 { - ((self.packed_hashes[0] & 0xff000000) >> 24) | - ((self.packed_hashes[1] & 0xff000000) >> 16) | - ((self.packed_hashes[2] & 0xff000000) >> 8) + AncestorHashes { + packed_hashes: [hashes[0], hashes[1], hashes[2]], } + } + + /// Returns the fourth hash, reassembled from parts. + pub fn fourth_hash(&self) -> u32 { + ((self.packed_hashes[0] & 0xff000000) >> 24) + | ((self.packed_hashes[1] & 0xff000000) >> 16) + | ((self.packed_hashes[2] & 0xff000000) >> 8) + } } pub fn namespace_empty_string<'i, Impl: SelectorImpl<'i>>() -> Impl::NamespaceUrl { - // Rust type’s default, not default namespace - Impl::NamespaceUrl::default() + // Rust type’s default, not default namespace + Impl::NamespaceUrl::default() } /// A Selector stores a sequence of simple selectors and combinators. The @@ -645,438 +617,421 @@ pub fn namespace_empty_string<'i, Impl: SelectorImpl<'i>>() -> Impl::NamespaceUr /// This reordering doesn't change the semantics of selector matching, and we /// handle it in to_css to make it invisible to serialization. #[derive(Clone, Eq, PartialEq)] -pub struct Selector<'i, Impl: SelectorImpl<'i>>( - SpecificityAndFlags, - Vec>, -); +pub struct Selector<'i, Impl: SelectorImpl<'i>>(SpecificityAndFlags, Vec>); impl<'i, Impl: SelectorImpl<'i>> Selector<'i, Impl> { - #[inline] - pub fn specificity(&self) -> u32 { - self.0.specificity() - } - - #[inline] - pub fn has_pseudo_element(&self) -> bool { - self.0.has_pseudo_element() - } - - #[inline] - pub fn is_slotted(&self) -> bool { - self.0.is_slotted() - } - - #[inline] - pub fn is_part(&self) -> bool { - self.0.is_part() - } - - #[inline] - pub fn parts(&self) -> Option<&[Impl::Identifier]> { - if !self.is_part() { - return None; - } - - let mut iter = self.iter(); - if self.has_pseudo_element() { - // Skip the pseudo-element. - for _ in &mut iter {} - - let combinator = iter.next_sequence()?; - debug_assert_eq!(combinator, Combinator::PseudoElement); - } - - for component in iter { - if let Component::Part(ref part) = *component { - return Some(part); - } - } - - debug_assert!(false, "is_part() lied somehow?"); - None - } - - #[inline] - pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> { - if !self.has_pseudo_element() { - return None; - } - - for component in self.iter() { - if let Component::PseudoElement(ref pseudo) = *component { - return Some(pseudo); - } - } - - debug_assert!(false, "has_pseudo_element lied!"); - None - } - - /// Whether this selector (pseudo-element part excluded) matches every element. - /// - /// Used for "pre-computed" pseudo-elements in components/style/stylist.rs - #[inline] - pub fn is_universal(&self) -> bool { - self.iter_raw_match_order().all(|c| { - matches!( - *c, - Component::ExplicitUniversalType | - Component::ExplicitAnyNamespace | - Component::Combinator(Combinator::PseudoElement) | - Component::PseudoElement(..) - ) - }) - } - - /// Returns an iterator over this selector in matching order (right-to-left). - /// When a combinator is reached, the iterator will return None, and - /// next_sequence() may be called to continue to the next sequence. - #[inline] - pub fn iter(&self) -> SelectorIter<'_, 'i, Impl> { - SelectorIter { - iter: self.iter_raw_match_order(), - next_combinator: None, - } - } - - /// Whether this selector is a featureless :host selector, with no - /// combinators to the left, and optionally has a pseudo-element to the - /// right. - #[inline] - pub fn is_featureless_host_selector_or_pseudo_element(&self) -> bool { - let mut iter = self.iter(); - if !self.has_pseudo_element() { - return iter.is_featureless_host_selector(); - } - - // Skip the pseudo-element. - for _ in &mut iter {} - - match iter.next_sequence() { - None => return false, - Some(combinator) => { - debug_assert_eq!(combinator, Combinator::PseudoElement); - }, - } - - iter.is_featureless_host_selector() - } - - /// Returns an iterator over this selector in matching order (right-to-left), - /// skipping the rightmost |offset| Components. - #[inline] - pub fn iter_from(&self, offset: usize) -> SelectorIter<'_, 'i, Impl> { - let iter = self.1[offset..].iter(); - SelectorIter { - iter, - next_combinator: None, - } - } - - /// Returns the combinator at index `index` (zero-indexed from the right), - /// or panics if the component is not a combinator. - #[inline] - pub fn combinator_at_match_order(&self, index: usize) -> Combinator { - match self.1[index] { - Component::Combinator(c) => c, - ref other => panic!( - "Not a combinator: {:?}, {:?}, index: {}", - other, self, index - ), - } + #[inline] + pub fn specificity(&self) -> u32 { + self.0.specificity() + } + + #[inline] + pub fn has_pseudo_element(&self) -> bool { + self.0.has_pseudo_element() + } + + #[inline] + pub fn is_slotted(&self) -> bool { + self.0.is_slotted() + } + + #[inline] + pub fn is_part(&self) -> bool { + self.0.is_part() + } + + #[inline] + pub fn parts(&self) -> Option<&[Impl::Identifier]> { + if !self.is_part() { + return None; + } + + let mut iter = self.iter(); + if self.has_pseudo_element() { + // Skip the pseudo-element. + for _ in &mut iter {} + + let combinator = iter.next_sequence()?; + debug_assert_eq!(combinator, Combinator::PseudoElement); + } + + for component in iter { + if let Component::Part(ref part) = *component { + return Some(part); + } + } + + debug_assert!(false, "is_part() lied somehow?"); + None + } + + #[inline] + pub fn pseudo_element(&self) -> Option<&Impl::PseudoElement> { + if !self.has_pseudo_element() { + return None; + } + + for component in self.iter() { + if let Component::PseudoElement(ref pseudo) = *component { + return Some(pseudo); + } + } + + debug_assert!(false, "has_pseudo_element lied!"); + None + } + + /// Whether this selector (pseudo-element part excluded) matches every element. + /// + /// Used for "pre-computed" pseudo-elements in components/style/stylist.rs + #[inline] + pub fn is_universal(&self) -> bool { + self.iter_raw_match_order().all(|c| { + matches!( + *c, + Component::ExplicitUniversalType + | Component::ExplicitAnyNamespace + | Component::Combinator(Combinator::PseudoElement) + | Component::PseudoElement(..) + ) + }) + } + + /// Returns an iterator over this selector in matching order (right-to-left). + /// When a combinator is reached, the iterator will return None, and + /// next_sequence() may be called to continue to the next sequence. + #[inline] + pub fn iter(&self) -> SelectorIter<'_, 'i, Impl> { + SelectorIter { + iter: self.iter_raw_match_order(), + next_combinator: None, + } + } + + /// Whether this selector is a featureless :host selector, with no + /// combinators to the left, and optionally has a pseudo-element to the + /// right. + #[inline] + pub fn is_featureless_host_selector_or_pseudo_element(&self) -> bool { + let mut iter = self.iter(); + if !self.has_pseudo_element() { + return iter.is_featureless_host_selector(); + } + + // Skip the pseudo-element. + for _ in &mut iter {} + + match iter.next_sequence() { + None => return false, + Some(combinator) => { + debug_assert_eq!(combinator, Combinator::PseudoElement); + } + } + + iter.is_featureless_host_selector() + } + + /// Returns an iterator over this selector in matching order (right-to-left), + /// skipping the rightmost |offset| Components. + #[inline] + pub fn iter_from(&self, offset: usize) -> SelectorIter<'_, 'i, Impl> { + let iter = self.1[offset..].iter(); + SelectorIter { + iter, + next_combinator: None, + } + } + + /// Returns the combinator at index `index` (zero-indexed from the right), + /// or panics if the component is not a combinator. + #[inline] + pub fn combinator_at_match_order(&self, index: usize) -> Combinator { + match self.1[index] { + Component::Combinator(c) => c, + ref other => panic!("Not a combinator: {:?}, {:?}, index: {}", other, self, index), + } + } + + /// Returns an iterator over the entire sequence of simple selectors and + /// combinators, in matching order (from right to left). + #[inline] + pub fn iter_raw_match_order(&self) -> slice::Iter> { + self.1.iter() + } + + /// Returns the combinator at index `index` (zero-indexed from the left), + /// or panics if the component is not a combinator. + #[inline] + pub fn combinator_at_parse_order(&self, index: usize) -> Combinator { + match self.1[self.len() - index - 1] { + Component::Combinator(c) => c, + ref other => panic!("Not a combinator: {:?}, {:?}, index: {}", other, self, index), + } + } + + /// Returns an iterator over the sequence of simple selectors and + /// combinators, in parse order (from left to right), starting from + /// `offset`. + #[inline] + pub fn iter_raw_parse_order_from(&self, offset: usize) -> Rev>> { + self.1[..self.len() - offset].iter().rev() + } + + /// Creates a Selector from a vec of Components, specified in parse order. Used in tests. + #[allow(unused)] + pub(crate) fn from_vec(vec: Vec>, specificity: u32, flags: SelectorFlags) -> Self { + let mut builder = SelectorBuilder::default(); + for component in vec.into_iter() { + if let Some(combinator) = component.as_combinator() { + builder.push_combinator(combinator); + } else { + builder.push_simple_selector(component); + } } + let spec = SpecificityAndFlags { specificity, flags }; + let (spec, components) = builder.build_with_specificity_and_flags(spec); + Selector(spec, components) + } - /// Returns an iterator over the entire sequence of simple selectors and - /// combinators, in matching order (from right to left). - #[inline] - pub fn iter_raw_match_order(&self) -> slice::Iter> { - self.1.iter() - } + pub fn from_vec2(vec: Vec>) -> Self { + let mut builder = SelectorBuilder::default(); + for component in vec.into_iter() { + if let Some(combinator) = component.as_combinator() { + builder.push_combinator(combinator); + } else { + builder.push_simple_selector(component); + } + } + let (spec, components) = builder.build(false, false, false); + Selector(spec, components) + } + + /// Returns count of simple selectors and combinators in the Selector. + #[inline] + pub fn len(&self) -> usize { + self.1.len() + } + + /// Traverse selector components inside `self`. + /// + /// Implementations of this method should call `SelectorVisitor` methods + /// or other impls of `Visit` as appropriate based on the fields of `Self`. + /// + /// A return value of `false` indicates terminating the traversal. + /// It should be propagated with an early return. + /// On the contrary, `true` indicates that all fields of `self` have been traversed: + /// + /// ```rust,ignore + /// if !visitor.visit_simple_selector(&self.some_simple_selector) { + /// return false; + /// } + /// if !self.some_component.visit(visitor) { + /// return false; + /// } + /// true + /// ``` + pub fn visit(&self, visitor: &mut V) -> bool + where + V: SelectorVisitor<'i, Impl = Impl>, + { + let mut current = self.iter(); + let mut combinator = None; + loop { + if !visitor.visit_complex_selector(combinator) { + return false; + } - /// Returns the combinator at index `index` (zero-indexed from the left), - /// or panics if the component is not a combinator. - #[inline] - pub fn combinator_at_parse_order(&self, index: usize) -> Combinator { - match self.1[self.len() - index - 1] { - Component::Combinator(c) => c, - ref other => panic!( - "Not a combinator: {:?}, {:?}, index: {}", - other, self, index - ), + for selector in &mut current { + if !selector.visit(visitor) { + return false; } - } + } - /// Returns an iterator over the sequence of simple selectors and - /// combinators, in parse order (from left to right), starting from - /// `offset`. - #[inline] - pub fn iter_raw_parse_order_from(&self, offset: usize) -> Rev>> { - self.1[..self.len() - offset].iter().rev() - } - - /// Creates a Selector from a vec of Components, specified in parse order. Used in tests. - #[allow(unused)] - pub(crate) fn from_vec( - vec: Vec>, - specificity: u32, - flags: SelectorFlags, - ) -> Self { - let mut builder = SelectorBuilder::default(); - for component in vec.into_iter() { - if let Some(combinator) = component.as_combinator() { - builder.push_combinator(combinator); - } else { - builder.push_simple_selector(component); - } - } - let spec = SpecificityAndFlags { specificity, flags }; - let (spec, components) = builder.build_with_specificity_and_flags(spec); - Selector(spec, components) - } - - pub fn from_vec2(vec: Vec>) -> Self { - let mut builder = SelectorBuilder::default(); - for component in vec.into_iter() { - if let Some(combinator) = component.as_combinator() { - builder.push_combinator(combinator); - } else { - builder.push_simple_selector(component); - } - } - let (spec, components) = builder.build(false, false, false); - Selector(spec, components) + combinator = current.next_sequence(); + if combinator.is_none() { + break; + } } - /// Returns count of simple selectors and combinators in the Selector. - #[inline] - pub fn len(&self) -> usize { - self.1.len() - } - - /// Traverse selector components inside `self`. - /// - /// Implementations of this method should call `SelectorVisitor` methods - /// or other impls of `Visit` as appropriate based on the fields of `Self`. - /// - /// A return value of `false` indicates terminating the traversal. - /// It should be propagated with an early return. - /// On the contrary, `true` indicates that all fields of `self` have been traversed: - /// - /// ```rust,ignore - /// if !visitor.visit_simple_selector(&self.some_simple_selector) { - /// return false; - /// } - /// if !self.some_component.visit(visitor) { - /// return false; - /// } - /// true - /// ``` - pub fn visit(&self, visitor: &mut V) -> bool - where - V: SelectorVisitor<'i, Impl = Impl>, - { - let mut current = self.iter(); - let mut combinator = None; - loop { - if !visitor.visit_complex_selector(combinator) { - return false; - } - - for selector in &mut current { - if !selector.visit(visitor) { - return false; - } - } - - combinator = current.next_sequence(); - if combinator.is_none() { - break; - } - } - - true - } + true + } } #[derive(Clone)] pub struct SelectorIter<'a, 'i, Impl: 'a + SelectorImpl<'i>> { - iter: slice::Iter<'a, Component<'i, Impl>>, - next_combinator: Option, + iter: slice::Iter<'a, Component<'i, Impl>>, + next_combinator: Option, } impl<'a, 'i, Impl: 'a + SelectorImpl<'i>> SelectorIter<'a, 'i, Impl> { - /// Prepares this iterator to point to the next sequence to the left, - /// returning the combinator if the sequence was found. - #[inline] - pub fn next_sequence(&mut self) -> Option { - self.next_combinator.take() - } - - /// Whether this selector is a featureless host selector, with no - /// combinators to the left. - #[inline] - pub(crate) fn is_featureless_host_selector(&mut self) -> bool { - self.selector_length() > 0 && - self.all(|component| matches!(*component, Component::Host(..))) && - self.next_sequence().is_none() - } - - #[inline] - pub(crate) fn matches_for_stateless_pseudo_element(&mut self) -> bool { - let first = match self.next() { - Some(c) => c, - // Note that this is the common path that we keep inline: the - // pseudo-element not having anything to its right. - None => return true, - }; - self.matches_for_stateless_pseudo_element_internal(first) - } - - #[inline(never)] - fn matches_for_stateless_pseudo_element_internal(&mut self, first: &Component<'i, Impl>) -> bool { - if !first.matches_for_stateless_pseudo_element() { - return false; - } - for component in self { - // The only other parser-allowed Components in this sequence are - // state pseudo-classes, or one of the other things that can contain - // them. - if !component.matches_for_stateless_pseudo_element() { - return false; - } - } - true + /// Prepares this iterator to point to the next sequence to the left, + /// returning the combinator if the sequence was found. + #[inline] + pub fn next_sequence(&mut self) -> Option { + self.next_combinator.take() + } + + /// Whether this selector is a featureless host selector, with no + /// combinators to the left. + #[inline] + pub(crate) fn is_featureless_host_selector(&mut self) -> bool { + self.selector_length() > 0 + && self.all(|component| matches!(*component, Component::Host(..))) + && self.next_sequence().is_none() + } + + #[inline] + pub(crate) fn matches_for_stateless_pseudo_element(&mut self) -> bool { + let first = match self.next() { + Some(c) => c, + // Note that this is the common path that we keep inline: the + // pseudo-element not having anything to its right. + None => return true, + }; + self.matches_for_stateless_pseudo_element_internal(first) + } + + #[inline(never)] + fn matches_for_stateless_pseudo_element_internal(&mut self, first: &Component<'i, Impl>) -> bool { + if !first.matches_for_stateless_pseudo_element() { + return false; + } + for component in self { + // The only other parser-allowed Components in this sequence are + // state pseudo-classes, or one of the other things that can contain + // them. + if !component.matches_for_stateless_pseudo_element() { + return false; + } } + true + } - /// Returns remaining count of the simple selectors and combinators in the Selector. - #[inline] - pub fn selector_length(&self) -> usize { - self.iter.len() - } + /// Returns remaining count of the simple selectors and combinators in the Selector. + #[inline] + pub fn selector_length(&self) -> usize { + self.iter.len() + } } impl<'a, 'i, Impl: SelectorImpl<'i>> Iterator for SelectorIter<'a, 'i, Impl> { - type Item = &'a Component<'i, Impl>; - - #[inline] - fn next(&mut self) -> Option { - debug_assert!( - self.next_combinator.is_none(), - "You should call next_sequence!" - ); - match *self.iter.next()? { - Component::Combinator(c) => { - self.next_combinator = Some(c); - None - }, - ref x => Some(x), - } + type Item = &'a Component<'i, Impl>; + + #[inline] + fn next(&mut self) -> Option { + debug_assert!(self.next_combinator.is_none(), "You should call next_sequence!"); + match *self.iter.next()? { + Component::Combinator(c) => { + self.next_combinator = Some(c); + None + } + ref x => Some(x), } + } } impl<'a, 'i, Impl: SelectorImpl<'i>> fmt::Debug for SelectorIter<'a, 'i, Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let iter = self.iter.clone().rev(); - for component in iter { - component.to_css(f)? - } - Ok(()) + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let iter = self.iter.clone().rev(); + for component in iter { + component.to_css(f)? } + Ok(()) + } } /// An iterator over all simple selectors belonging to ancestors. struct AncestorIter<'a, 'i, Impl: 'a + SelectorImpl<'i>>(SelectorIter<'a, 'i, Impl>); impl<'a, 'i, Impl: 'a + SelectorImpl<'i>> AncestorIter<'a, 'i, Impl> { - /// Creates an AncestorIter. The passed-in iterator is assumed to point to - /// the beginning of the child sequence, which will be skipped. - fn new(inner: SelectorIter<'a, 'i, Impl>) -> Self { - let mut result = AncestorIter(inner); - result.skip_until_ancestor(); - result - } - - /// Skips a sequence of simple selectors and all subsequent sequences until - /// a non-pseudo-element ancestor combinator is reached. - fn skip_until_ancestor(&mut self) { - loop { - while self.0.next().is_some() {} - // If this is ever changed to stop at the "pseudo-element" - // combinator, we will need to fix the way we compute hashes for - // revalidation selectors. - if self.0.next_sequence().map_or(true, |x| { - matches!(x, Combinator::Child | Combinator::Descendant) - }) { - break; - } - } + /// Creates an AncestorIter. The passed-in iterator is assumed to point to + /// the beginning of the child sequence, which will be skipped. + fn new(inner: SelectorIter<'a, 'i, Impl>) -> Self { + let mut result = AncestorIter(inner); + result.skip_until_ancestor(); + result + } + + /// Skips a sequence of simple selectors and all subsequent sequences until + /// a non-pseudo-element ancestor combinator is reached. + fn skip_until_ancestor(&mut self) { + loop { + while self.0.next().is_some() {} + // If this is ever changed to stop at the "pseudo-element" + // combinator, we will need to fix the way we compute hashes for + // revalidation selectors. + if self + .0 + .next_sequence() + .map_or(true, |x| matches!(x, Combinator::Child | Combinator::Descendant)) + { + break; + } } + } } impl<'a, 'i, Impl: SelectorImpl<'i>> Iterator for AncestorIter<'a, 'i, Impl> { - type Item = &'a Component<'i, Impl>; - fn next(&mut self) -> Option { - // Grab the next simple selector in the sequence if available. - let next = self.0.next(); - if next.is_some() { - return next; - } - - // See if there are more sequences. If so, skip any non-ancestor sequences. - if let Some(combinator) = self.0.next_sequence() { - if !matches!(combinator, Combinator::Child | Combinator::Descendant) { - self.skip_until_ancestor(); - } - } + type Item = &'a Component<'i, Impl>; + fn next(&mut self) -> Option { + // Grab the next simple selector in the sequence if available. + let next = self.0.next(); + if next.is_some() { + return next; + } - self.0.next() + // See if there are more sequences. If so, skip any non-ancestor sequences. + if let Some(combinator) = self.0.next_sequence() { + if !matches!(combinator, Combinator::Child | Combinator::Descendant) { + self.skip_until_ancestor(); + } } + + self.0.next() + } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Combinator { - Child, // > - Descendant, // space - NextSibling, // + - LaterSibling, // ~ - /// A dummy combinator we use to the left of pseudo-elements. - /// - /// It serializes as the empty string, and acts effectively as a child - /// combinator in most cases. If we ever actually start using a child - /// combinator for this, we will need to fix up the way hashes are computed - /// for revalidation selectors. - PseudoElement, - /// Another combinator used for ::slotted(), which represent the jump from - /// a node to its assigned slot. - SlotAssignment, - /// Another combinator used for `::part()`, which represents the jump from - /// the part to the containing shadow host. - Part, + Child, // > + Descendant, // space + NextSibling, // + + LaterSibling, // ~ + /// A dummy combinator we use to the left of pseudo-elements. + /// + /// It serializes as the empty string, and acts effectively as a child + /// combinator in most cases. If we ever actually start using a child + /// combinator for this, we will need to fix up the way hashes are computed + /// for revalidation selectors. + PseudoElement, + /// Another combinator used for ::slotted(), which represent the jump from + /// a node to its assigned slot. + SlotAssignment, + /// Another combinator used for `::part()`, which represents the jump from + /// the part to the containing shadow host. + Part, } impl Combinator { - /// Returns true if this combinator is a child or descendant combinator. - #[inline] - pub fn is_ancestor(&self) -> bool { - matches!( - *self, - Combinator::Child | - Combinator::Descendant | - Combinator::PseudoElement | - Combinator::SlotAssignment - ) - } - - /// Returns true if this combinator is a pseudo-element combinator. - #[inline] - pub fn is_pseudo_element(&self) -> bool { - matches!(*self, Combinator::PseudoElement) - } - - /// Returns true if this combinator is a next- or later-sibling combinator. - #[inline] - pub fn is_sibling(&self) -> bool { - matches!(*self, Combinator::NextSibling | Combinator::LaterSibling) - } + /// Returns true if this combinator is a child or descendant combinator. + #[inline] + pub fn is_ancestor(&self) -> bool { + matches!( + *self, + Combinator::Child | Combinator::Descendant | Combinator::PseudoElement | Combinator::SlotAssignment + ) + } + + /// Returns true if this combinator is a pseudo-element combinator. + #[inline] + pub fn is_pseudo_element(&self) -> bool { + matches!(*self, Combinator::PseudoElement) + } + + /// Returns true if this combinator is a next- or later-sibling combinator. + #[inline] + pub fn is_sibling(&self) -> bool { + matches!(*self, Combinator::NextSibling | Combinator::LaterSibling) + } } /// A CSS simple selector or combinator. We store both in the same enum for @@ -1085,630 +1040,613 @@ impl Combinator { /// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1357973 #[derive(Clone, Eq, PartialEq)] pub enum Component<'i, Impl: SelectorImpl<'i>> { - Combinator(Combinator), - - ExplicitAnyNamespace, - ExplicitNoNamespace, - DefaultNamespace(Impl::NamespaceUrl), - Namespace( - Impl::NamespacePrefix, - Impl::NamespaceUrl, - ), - - ExplicitUniversalType, - LocalName(LocalName<'i, Impl>), - - ID(Impl::Identifier), - Class(Impl::Identifier), - - AttributeInNoNamespaceExists { - local_name: Impl::LocalName, - local_name_lower: Impl::LocalName, - }, - // Used only when local_name is already lowercase. - AttributeInNoNamespace { - local_name: Impl::LocalName, - operator: AttrSelectorOperator, - value: Impl::AttrValue, - case_sensitivity: ParsedCaseSensitivity, - never_matches: bool, - }, - // Use a Box in the less common cases with more data to keep size_of::() small. - AttributeOther(Box>), - - /// Pseudo-classes - Negation(Box<[Selector<'i, Impl>]>), - FirstChild, - LastChild, - OnlyChild, - Root, - Empty, - Scope, - NthChild(i32, i32), - NthLastChild(i32, i32), - NthOfType(i32, i32), - NthLastOfType(i32, i32), - FirstOfType, - LastOfType, - OnlyOfType, - NonTSPseudoClass(Impl::NonTSPseudoClass), - /// The ::slotted() pseudo-element: - /// - /// https://drafts.csswg.org/css-scoping/#slotted-pseudo - /// - /// The selector here is a compound selector, that is, no combinators. - /// - /// NOTE(emilio): This should support a list of selectors, but as of this - /// writing no other browser does, and that allows them to put ::slotted() - /// in the rule hash, so we do that too. - /// - /// See https://github.com/w3c/csswg-drafts/issues/2158 - Slotted(Selector<'i, Impl>), - /// The `::part` pseudo-element. - /// https://drafts.csswg.org/css-shadow-parts/#part - Part(Box<[Impl::Identifier]>), - /// The `:host` pseudo-class: - /// - /// https://drafts.csswg.org/css-scoping/#host-selector - /// - /// NOTE(emilio): This should support a list of selectors, but as of this - /// writing no other browser does, and that allows them to put :host() - /// in the rule hash, so we do that too. - /// - /// See https://github.com/w3c/csswg-drafts/issues/2158 - Host(Option>), - /// The `:where` pseudo-class. - /// - /// https://drafts.csswg.org/selectors/#zero-matches - /// - /// The inner argument is conceptually a SelectorList, but we move the - /// selectors to the heap to keep Component small. - Where(Box<[Selector<'i, Impl>]>), - /// The `:is` pseudo-class. - /// - /// https://drafts.csswg.org/selectors/#matches-pseudo - /// - /// Same comment as above re. the argument. - Is(Box<[Selector<'i, Impl>]>), - /// The `:has` pseudo-class. - /// - /// https://www.w3.org/TR/selectors/#relational - Has(Box<[Selector<'i, Impl>]>), - /// An implementation-dependent pseudo-element selector. - PseudoElement(Impl::PseudoElement), - /// A nesting selector: - /// - /// https://drafts.csswg.org/css-nesting-1/#nest-selector - /// - /// NOTE: This is a parcel_css addition. - Nesting + Combinator(Combinator), + + ExplicitAnyNamespace, + ExplicitNoNamespace, + DefaultNamespace(Impl::NamespaceUrl), + Namespace(Impl::NamespacePrefix, Impl::NamespaceUrl), + + ExplicitUniversalType, + LocalName(LocalName<'i, Impl>), + + ID(Impl::Identifier), + Class(Impl::Identifier), + + AttributeInNoNamespaceExists { + local_name: Impl::LocalName, + local_name_lower: Impl::LocalName, + }, + // Used only when local_name is already lowercase. + AttributeInNoNamespace { + local_name: Impl::LocalName, + operator: AttrSelectorOperator, + value: Impl::AttrValue, + case_sensitivity: ParsedCaseSensitivity, + never_matches: bool, + }, + // Use a Box in the less common cases with more data to keep size_of::() small. + AttributeOther(Box>), + + /// Pseudo-classes + Negation(Box<[Selector<'i, Impl>]>), + FirstChild, + LastChild, + OnlyChild, + Root, + Empty, + Scope, + NthChild(i32, i32), + NthLastChild(i32, i32), + NthOfType(i32, i32), + NthLastOfType(i32, i32), + FirstOfType, + LastOfType, + OnlyOfType, + NonTSPseudoClass(Impl::NonTSPseudoClass), + /// The ::slotted() pseudo-element: + /// + /// https://drafts.csswg.org/css-scoping/#slotted-pseudo + /// + /// The selector here is a compound selector, that is, no combinators. + /// + /// NOTE(emilio): This should support a list of selectors, but as of this + /// writing no other browser does, and that allows them to put ::slotted() + /// in the rule hash, so we do that too. + /// + /// See https://github.com/w3c/csswg-drafts/issues/2158 + Slotted(Selector<'i, Impl>), + /// The `::part` pseudo-element. + /// https://drafts.csswg.org/css-shadow-parts/#part + Part(Box<[Impl::Identifier]>), + /// The `:host` pseudo-class: + /// + /// https://drafts.csswg.org/css-scoping/#host-selector + /// + /// NOTE(emilio): This should support a list of selectors, but as of this + /// writing no other browser does, and that allows them to put :host() + /// in the rule hash, so we do that too. + /// + /// See https://github.com/w3c/csswg-drafts/issues/2158 + Host(Option>), + /// The `:where` pseudo-class. + /// + /// https://drafts.csswg.org/selectors/#zero-matches + /// + /// The inner argument is conceptually a SelectorList, but we move the + /// selectors to the heap to keep Component small. + Where(Box<[Selector<'i, Impl>]>), + /// The `:is` pseudo-class. + /// + /// https://drafts.csswg.org/selectors/#matches-pseudo + /// + /// Same comment as above re. the argument. + Is(Box<[Selector<'i, Impl>]>), + /// The `:has` pseudo-class. + /// + /// https://www.w3.org/TR/selectors/#relational + Has(Box<[Selector<'i, Impl>]>), + /// An implementation-dependent pseudo-element selector. + PseudoElement(Impl::PseudoElement), + /// A nesting selector: + /// + /// https://drafts.csswg.org/css-nesting-1/#nest-selector + /// + /// NOTE: This is a parcel_css addition. + Nesting, } impl<'i, Impl: SelectorImpl<'i>> Component<'i, Impl> { - /// Returns true if this is a combinator. - pub fn is_combinator(&self) -> bool { - matches!(*self, Component::Combinator(_)) - } - - /// Returns the value as a combinator if applicable, None otherwise. - pub fn as_combinator(&self) -> Option { - match *self { - Component::Combinator(c) => Some(c), - _ => None, + /// Returns true if this is a combinator. + pub fn is_combinator(&self) -> bool { + matches!(*self, Component::Combinator(_)) + } + + /// Returns the value as a combinator if applicable, None otherwise. + pub fn as_combinator(&self) -> Option { + match *self { + Component::Combinator(c) => Some(c), + _ => None, + } + } + + /// Whether this component is valid after a pseudo-element. Only intended + /// for sanity-checking. + pub fn maybe_allowed_after_pseudo_element(&self) -> bool { + match *self { + Component::NonTSPseudoClass(..) => true, + Component::Negation(ref selectors) | Component::Is(ref selectors) | Component::Where(ref selectors) => { + selectors + .iter() + .all(|selector| selector.iter_raw_match_order().all(|c| c.maybe_allowed_after_pseudo_element())) + } + _ => false, + } + } + + /// Whether a given selector should match for stateless pseudo-elements. + /// + /// This is a bit subtle: Only selectors that return true in + /// `maybe_allowed_after_pseudo_element` should end up here, and + /// `NonTSPseudoClass` never matches (as it is a stateless pseudo after + /// all). + fn matches_for_stateless_pseudo_element(&self) -> bool { + debug_assert!( + self.maybe_allowed_after_pseudo_element(), + "Someone messed up pseudo-element parsing: {:?}", + *self + ); + match *self { + Component::Negation(ref selectors) => !selectors.iter().all(|selector| { + selector + .iter_raw_match_order() + .all(|c| c.matches_for_stateless_pseudo_element()) + }), + Component::Is(ref selectors) | Component::Where(ref selectors) => selectors.iter().any(|selector| { + selector + .iter_raw_match_order() + .all(|c| c.matches_for_stateless_pseudo_element()) + }), + _ => false, + } + } + + pub fn visit(&self, visitor: &mut V) -> bool + where + V: SelectorVisitor<'i, Impl = Impl>, + { + use self::Component::*; + if !visitor.visit_simple_selector(self) { + return false; + } + + match *self { + Slotted(ref selector) => { + if !selector.visit(visitor) { + return false; + } + } + Host(Some(ref selector)) => { + if !selector.visit(visitor) { + return false; + } + } + AttributeInNoNamespaceExists { + ref local_name, + ref local_name_lower, + } => { + if !visitor.visit_attribute_selector( + &NamespaceConstraint::Specific(&namespace_empty_string::()), + local_name, + local_name_lower, + ) { + return false; + } + } + AttributeInNoNamespace { + ref local_name, + never_matches, + .. + } if !never_matches => { + if !visitor.visit_attribute_selector( + &NamespaceConstraint::Specific(&namespace_empty_string::()), + local_name, + local_name, + ) { + return false; + } + } + AttributeOther(ref attr_selector) if !attr_selector.never_matches => { + let empty_string; + let namespace = match attr_selector.namespace() { + Some(ns) => ns, + None => { + empty_string = crate::parser::namespace_empty_string::(); + NamespaceConstraint::Specific(&empty_string) + } + }; + if !visitor.visit_attribute_selector( + &namespace, + &attr_selector.local_name, + &attr_selector.local_name_lower, + ) { + return false; } - } + } - /// Whether this component is valid after a pseudo-element. Only intended - /// for sanity-checking. - pub fn maybe_allowed_after_pseudo_element(&self) -> bool { - match *self { - Component::NonTSPseudoClass(..) => true, - Component::Negation(ref selectors) | - Component::Is(ref selectors) | - Component::Where(ref selectors) => selectors.iter().all(|selector| { - selector - .iter_raw_match_order() - .all(|c| c.maybe_allowed_after_pseudo_element()) - }), - _ => false, + NonTSPseudoClass(ref pseudo_class) => { + if !pseudo_class.visit(visitor) { + return false; } - } + } - /// Whether a given selector should match for stateless pseudo-elements. - /// - /// This is a bit subtle: Only selectors that return true in - /// `maybe_allowed_after_pseudo_element` should end up here, and - /// `NonTSPseudoClass` never matches (as it is a stateless pseudo after - /// all). - fn matches_for_stateless_pseudo_element(&self) -> bool { - debug_assert!( - self.maybe_allowed_after_pseudo_element(), - "Someone messed up pseudo-element parsing: {:?}", - *self - ); - match *self { - Component::Negation(ref selectors) => !selectors.iter().all(|selector| { - selector - .iter_raw_match_order() - .all(|c| c.matches_for_stateless_pseudo_element()) - }), - Component::Is(ref selectors) | Component::Where(ref selectors) => { - selectors.iter().any(|selector| { - selector - .iter_raw_match_order() - .all(|c| c.matches_for_stateless_pseudo_element()) - }) - }, - _ => false, + Negation(ref list) | Is(ref list) | Where(ref list) => { + if !visitor.visit_selector_list(&list) { + return false; } + } + _ => {} } - pub fn visit(&self, visitor: &mut V) -> bool - where - V: SelectorVisitor<'i, Impl = Impl>, - { - use self::Component::*; - if !visitor.visit_simple_selector(self) { - return false; - } - - match *self { - Slotted(ref selector) => { - if !selector.visit(visitor) { - return false; - } - }, - Host(Some(ref selector)) => { - if !selector.visit(visitor) { - return false; - } - }, - AttributeInNoNamespaceExists { - ref local_name, - ref local_name_lower, - } => { - if !visitor.visit_attribute_selector( - &NamespaceConstraint::Specific(&namespace_empty_string::()), - local_name, - local_name_lower, - ) { - return false; - } - }, - AttributeInNoNamespace { - ref local_name, - never_matches, - .. - } if !never_matches => { - if !visitor.visit_attribute_selector( - &NamespaceConstraint::Specific(&namespace_empty_string::()), - local_name, - local_name, - ) { - return false; - } - }, - AttributeOther(ref attr_selector) if !attr_selector.never_matches => { - let empty_string; - let namespace = match attr_selector.namespace() { - Some(ns) => ns, - None => { - empty_string = crate::parser::namespace_empty_string::(); - NamespaceConstraint::Specific(&empty_string) - }, - }; - if !visitor.visit_attribute_selector( - &namespace, - &attr_selector.local_name, - &attr_selector.local_name_lower, - ) { - return false; - } - }, - - NonTSPseudoClass(ref pseudo_class) => { - if !pseudo_class.visit(visitor) { - return false; - } - }, - - Negation(ref list) | Is(ref list) | Where(ref list) => { - if !visitor.visit_selector_list(&list) { - return false; - } - }, - _ => {}, - } - - true - } -} + true + } +} #[derive(Clone, Eq, PartialEq)] pub struct LocalName<'i, Impl: SelectorImpl<'i>> { - pub name: Impl::LocalName, - pub lower_name: Impl::LocalName, + pub name: Impl::LocalName, + pub lower_name: Impl::LocalName, } impl<'i, Impl: SelectorImpl<'i>> Debug for Selector<'i, Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("Selector(")?; - self.to_css(f)?; - write!(f, ", specificity = 0x{:x})", self.specificity()) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("Selector(")?; + self.to_css(f)?; + write!(f, ", specificity = 0x{:x})", self.specificity()) + } } impl<'i, Impl: SelectorImpl<'i>> Debug for Component<'i, Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(f) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.to_css(f) + } } impl<'i, Impl: SelectorImpl<'i>> Debug for AttrSelectorWithOptionalNamespace<'i, Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(f) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.to_css(f) + } } impl<'i, Impl: SelectorImpl<'i>> Debug for LocalName<'i, Impl> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.to_css(f) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.to_css(f) + } } fn serialize_selector_list<'a, 'i: 'a, Impl, I, W>(iter: I, dest: &mut W) -> fmt::Result where - Impl: SelectorImpl<'i>, - I: Iterator>, - W: fmt::Write, + Impl: SelectorImpl<'i>, + I: Iterator>, + W: fmt::Write, { - let mut first = true; - for selector in iter { - if !first { - dest.write_str(", ")?; - } - first = false; - selector.to_css(dest)?; + let mut first = true; + for selector in iter { + if !first { + dest.write_str(", ")?; } - Ok(()) + first = false; + selector.to_css(dest)?; + } + Ok(()) } impl<'i, Impl: SelectorImpl<'i>> ToCss for SelectorList<'i, Impl> { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - serialize_selector_list(self.0.iter(), dest) - } + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + serialize_selector_list(self.0.iter(), dest) + } } impl<'i, Impl: SelectorImpl<'i>> fmt::Display for SelectorList<'i, Impl> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { - Impl::to_css(self, f) - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { + Impl::to_css(self, f) + } } impl<'i, Impl: SelectorImpl<'i>> ToCss for Selector<'i, Impl> { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - // Compound selectors invert the order of their contents, so we need to - // undo that during serialization. - // - // This two-iterator strategy involves walking over the selector twice. - // We could do something more clever, but selector serialization probably - // isn't hot enough to justify it, and the stringification likely - // dominates anyway. - // - // NB: A parse-order iterator is a Rev<>, which doesn't expose as_slice(), - // which we need for |split|. So we split by combinators on a match-order - // sequence and then reverse. - - let mut combinators = self - .iter_raw_match_order() - .rev() - .filter_map(|x| x.as_combinator()); - let compound_selectors = self - .iter_raw_match_order() - .as_slice() - .split(|x| x.is_combinator()) - .rev(); - - let mut combinators_exhausted = false; - for compound in compound_selectors { - debug_assert!(!combinators_exhausted); - - // https://drafts.csswg.org/cssom/#serializing-selectors - if compound.is_empty() { - continue; - } - - // 1. If there is only one simple selector in the compound selectors - // which is a universal selector, append the result of - // serializing the universal selector to s. - // - // Check if `!compound.empty()` first--this can happen if we have - // something like `... > ::before`, because we store `>` and `::` - // both as combinators internally. - // - // If we are in this case, after we have serialized the universal - // selector, we skip Step 2 and continue with the algorithm. - let (can_elide_namespace, first_non_namespace) = match compound[0] { - Component::ExplicitAnyNamespace | - Component::ExplicitNoNamespace | - Component::Namespace(..) => (false, 1), - Component::DefaultNamespace(..) => (true, 1), - _ => (true, 0), - }; - let mut perform_step_2 = true; - let next_combinator = combinators.next(); - if first_non_namespace == compound.len() - 1 { - match (next_combinator, &compound[first_non_namespace]) { - // We have to be careful here, because if there is a - // pseudo element "combinator" there isn't really just - // the one simple selector. Technically this compound - // selector contains the pseudo element selector as well - // -- Combinator::PseudoElement, just like - // Combinator::SlotAssignment, don't exist in the - // spec. - (Some(Combinator::PseudoElement), _) | - (Some(Combinator::SlotAssignment), _) => (), - (_, &Component::ExplicitUniversalType) => { - // Iterate over everything so we serialize the namespace - // too. - for simple in compound.iter() { - simple.to_css(dest)?; - } - // Skip step 2, which is an "otherwise". - perform_step_2 = false; - }, - _ => (), - } + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + // Compound selectors invert the order of their contents, so we need to + // undo that during serialization. + // + // This two-iterator strategy involves walking over the selector twice. + // We could do something more clever, but selector serialization probably + // isn't hot enough to justify it, and the stringification likely + // dominates anyway. + // + // NB: A parse-order iterator is a Rev<>, which doesn't expose as_slice(), + // which we need for |split|. So we split by combinators on a match-order + // sequence and then reverse. + + let mut combinators = self.iter_raw_match_order().rev().filter_map(|x| x.as_combinator()); + let compound_selectors = self.iter_raw_match_order().as_slice().split(|x| x.is_combinator()).rev(); + + let mut combinators_exhausted = false; + for compound in compound_selectors { + debug_assert!(!combinators_exhausted); + + // https://drafts.csswg.org/cssom/#serializing-selectors + if compound.is_empty() { + continue; + } + + // 1. If there is only one simple selector in the compound selectors + // which is a universal selector, append the result of + // serializing the universal selector to s. + // + // Check if `!compound.empty()` first--this can happen if we have + // something like `... > ::before`, because we store `>` and `::` + // both as combinators internally. + // + // If we are in this case, after we have serialized the universal + // selector, we skip Step 2 and continue with the algorithm. + let (can_elide_namespace, first_non_namespace) = match compound[0] { + Component::ExplicitAnyNamespace | Component::ExplicitNoNamespace | Component::Namespace(..) => (false, 1), + Component::DefaultNamespace(..) => (true, 1), + _ => (true, 0), + }; + let mut perform_step_2 = true; + let next_combinator = combinators.next(); + if first_non_namespace == compound.len() - 1 { + match (next_combinator, &compound[first_non_namespace]) { + // We have to be careful here, because if there is a + // pseudo element "combinator" there isn't really just + // the one simple selector. Technically this compound + // selector contains the pseudo element selector as well + // -- Combinator::PseudoElement, just like + // Combinator::SlotAssignment, don't exist in the + // spec. + (Some(Combinator::PseudoElement), _) | (Some(Combinator::SlotAssignment), _) => (), + (_, &Component::ExplicitUniversalType) => { + // Iterate over everything so we serialize the namespace + // too. + for simple in compound.iter() { + simple.to_css(dest)?; } - - // 2. Otherwise, for each simple selector in the compound selectors - // that is not a universal selector of which the namespace prefix - // maps to a namespace that is not the default namespace - // serialize the simple selector and append the result to s. - // - // See https://github.com/w3c/csswg-drafts/issues/1606, which is - // proposing to change this to match up with the behavior asserted - // in cssom/serialize-namespaced-type-selectors.html, which the - // following code tries to match. - if perform_step_2 { - for simple in compound.iter() { - if let Component::ExplicitUniversalType = *simple { - // Can't have a namespace followed by a pseudo-element - // selector followed by a universal selector in the same - // compound selector, so we don't have to worry about the - // real namespace being in a different `compound`. - if can_elide_namespace { - continue; - } - } - simple.to_css(dest)?; - } + // Skip step 2, which is an "otherwise". + perform_step_2 = false; + } + _ => (), + } + } + + // 2. Otherwise, for each simple selector in the compound selectors + // that is not a universal selector of which the namespace prefix + // maps to a namespace that is not the default namespace + // serialize the simple selector and append the result to s. + // + // See https://github.com/w3c/csswg-drafts/issues/1606, which is + // proposing to change this to match up with the behavior asserted + // in cssom/serialize-namespaced-type-selectors.html, which the + // following code tries to match. + if perform_step_2 { + for simple in compound.iter() { + if let Component::ExplicitUniversalType = *simple { + // Can't have a namespace followed by a pseudo-element + // selector followed by a universal selector in the same + // compound selector, so we don't have to worry about the + // real namespace being in a different `compound`. + if can_elide_namespace { + continue; } - - // 3. If this is not the last part of the chain of the selector - // append a single SPACE (U+0020), followed by the combinator - // ">", "+", "~", ">>", "||", as appropriate, followed by another - // single SPACE (U+0020) if the combinator was not whitespace, to - // s. - match next_combinator { - Some(c) => c.to_css(dest)?, - None => combinators_exhausted = true, - }; - - // 4. If this is the last part of the chain of the selector and - // there is a pseudo-element, append "::" followed by the name of - // the pseudo-element, to s. - // - // (we handle this above) + } + simple.to_css(dest)?; } + } - Ok(()) + // 3. If this is not the last part of the chain of the selector + // append a single SPACE (U+0020), followed by the combinator + // ">", "+", "~", ">>", "||", as appropriate, followed by another + // single SPACE (U+0020) if the combinator was not whitespace, to + // s. + match next_combinator { + Some(c) => c.to_css(dest)?, + None => combinators_exhausted = true, + }; + + // 4. If this is the last part of the chain of the selector and + // there is a pseudo-element, append "::" followed by the name of + // the pseudo-element, to s. + // + // (we handle this above) } + + Ok(()) + } } impl ToCss for Combinator { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - match *self { - Combinator::Child => dest.write_str(" > "), - Combinator::Descendant => dest.write_str(" "), - Combinator::NextSibling => dest.write_str(" + "), - Combinator::LaterSibling => dest.write_str(" ~ "), - Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => Ok(()), - } + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + match *self { + Combinator::Child => dest.write_str(" > "), + Combinator::Descendant => dest.write_str(" "), + Combinator::NextSibling => dest.write_str(" + "), + Combinator::LaterSibling => dest.write_str(" ~ "), + Combinator::PseudoElement | Combinator::Part | Combinator::SlotAssignment => Ok(()), } + } } impl<'i, Impl: SelectorImpl<'i>> ToCss for Component<'i, Impl> { - fn to_css(&self, dest: &mut W) -> fmt::Result + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + use self::Component::*; + + /// Serialize values (part of the CSS Syntax spec, but currently only used here). + /// + fn write_affine(dest: &mut W, a: i32, b: i32) -> fmt::Result where - W: fmt::Write, + W: fmt::Write, { - use self::Component::*; + match (a, b) { + (0, 0) => dest.write_char('0'), - /// Serialize values (part of the CSS Syntax spec, but currently only used here). - /// - fn write_affine(dest: &mut W, a: i32, b: i32) -> fmt::Result - where - W: fmt::Write, - { - match (a, b) { - (0, 0) => dest.write_char('0'), + (1, 0) => dest.write_char('n'), + (-1, 0) => dest.write_str("-n"), + (_, 0) => write!(dest, "{}n", a), - (1, 0) => dest.write_char('n'), - (-1, 0) => dest.write_str("-n"), - (_, 0) => write!(dest, "{}n", a), + (0, _) => write!(dest, "{}", b), + (1, _) => write!(dest, "n{:+}", b), + (-1, _) => write!(dest, "-n{:+}", b), + (_, _) => write!(dest, "{}n{:+}", a, b), + } + } - (0, _) => write!(dest, "{}", b), - (1, _) => write!(dest, "n{:+}", b), - (-1, _) => write!(dest, "-n{:+}", b), - (_, _) => write!(dest, "{}n{:+}", a, b), - } + match *self { + Combinator(ref c) => c.to_css(dest), + Slotted(ref selector) => { + dest.write_str("::slotted(")?; + selector.to_css(dest)?; + dest.write_char(')') + } + Part(ref part_names) => { + dest.write_str("::part(")?; + for (i, name) in part_names.iter().enumerate() { + if i != 0 { + dest.write_char(' ')?; + } + name.to_css(dest)?; + } + dest.write_char(')') + } + PseudoElement(ref p) => p.to_css(dest), + ID(ref s) => { + dest.write_char('#')?; + s.to_css(dest) + } + Class(ref s) => { + dest.write_char('.')?; + s.to_css(dest) + } + LocalName(ref s) => s.to_css(dest), + ExplicitUniversalType => dest.write_char('*'), + + DefaultNamespace(_) => Ok(()), + ExplicitNoNamespace => dest.write_char('|'), + ExplicitAnyNamespace => dest.write_str("*|"), + Namespace(ref prefix, _) => { + prefix.to_css(dest)?; + dest.write_char('|') + } + + AttributeInNoNamespaceExists { ref local_name, .. } => { + dest.write_char('[')?; + local_name.to_css(dest)?; + dest.write_char(']') + } + AttributeInNoNamespace { + ref local_name, + operator, + ref value, + case_sensitivity, + .. + } => { + dest.write_char('[')?; + local_name.to_css(dest)?; + operator.to_css(dest)?; + dest.write_char('"')?; + value.to_css(dest)?; + dest.write_char('"')?; + match case_sensitivity { + ParsedCaseSensitivity::CaseSensitive + | ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {} + ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?, + ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?, } - + dest.write_char(']') + } + AttributeOther(ref attr_selector) => attr_selector.to_css(dest), + + // Pseudo-classes + FirstChild => dest.write_str(":first-child"), + LastChild => dest.write_str(":last-child"), + OnlyChild => dest.write_str(":only-child"), + Root => dest.write_str(":root"), + Empty => dest.write_str(":empty"), + Scope => dest.write_str(":scope"), + Host(ref selector) => { + dest.write_str(":host")?; + if let Some(ref selector) = *selector { + dest.write_char('(')?; + selector.to_css(dest)?; + dest.write_char(')')?; + } + Ok(()) + } + FirstOfType => dest.write_str(":first-of-type"), + LastOfType => dest.write_str(":last-of-type"), + OnlyOfType => dest.write_str(":only-of-type"), + NthChild(a, b) | NthLastChild(a, b) | NthOfType(a, b) | NthLastOfType(a, b) => { + match *self { + NthChild(_, _) => dest.write_str(":nth-child(")?, + NthLastChild(_, _) => dest.write_str(":nth-last-child(")?, + NthOfType(_, _) => dest.write_str(":nth-of-type(")?, + NthLastOfType(_, _) => dest.write_str(":nth-last-of-type(")?, + _ => unreachable!(), + } + write_affine(dest, a, b)?; + dest.write_char(')') + } + Is(ref list) | Where(ref list) | Negation(ref list) | Has(ref list) => { match *self { - Combinator(ref c) => c.to_css(dest), - Slotted(ref selector) => { - dest.write_str("::slotted(")?; - selector.to_css(dest)?; - dest.write_char(')') - }, - Part(ref part_names) => { - dest.write_str("::part(")?; - for (i, name) in part_names.iter().enumerate() { - if i != 0 { - dest.write_char(' ')?; - } - name.to_css(dest)?; - } - dest.write_char(')') - }, - PseudoElement(ref p) => p.to_css(dest), - ID(ref s) => { - dest.write_char('#')?; - s.to_css(dest) - }, - Class(ref s) => { - dest.write_char('.')?; - s.to_css(dest) - }, - LocalName(ref s) => s.to_css(dest), - ExplicitUniversalType => dest.write_char('*'), - - DefaultNamespace(_) => Ok(()), - ExplicitNoNamespace => dest.write_char('|'), - ExplicitAnyNamespace => dest.write_str("*|"), - Namespace(ref prefix, _) => { - prefix.to_css(dest)?; - dest.write_char('|') - }, - - AttributeInNoNamespaceExists { ref local_name, .. } => { - dest.write_char('[')?; - local_name.to_css(dest)?; - dest.write_char(']') - }, - AttributeInNoNamespace { - ref local_name, - operator, - ref value, - case_sensitivity, - .. - } => { - dest.write_char('[')?; - local_name.to_css(dest)?; - operator.to_css(dest)?; - dest.write_char('"')?; - value.to_css(dest)?; - dest.write_char('"')?; - match case_sensitivity { - ParsedCaseSensitivity::CaseSensitive | - ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}, - ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?, - ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?, - } - dest.write_char(']') - }, - AttributeOther(ref attr_selector) => attr_selector.to_css(dest), - - // Pseudo-classes - FirstChild => dest.write_str(":first-child"), - LastChild => dest.write_str(":last-child"), - OnlyChild => dest.write_str(":only-child"), - Root => dest.write_str(":root"), - Empty => dest.write_str(":empty"), - Scope => dest.write_str(":scope"), - Host(ref selector) => { - dest.write_str(":host")?; - if let Some(ref selector) = *selector { - dest.write_char('(')?; - selector.to_css(dest)?; - dest.write_char(')')?; - } - Ok(()) - }, - FirstOfType => dest.write_str(":first-of-type"), - LastOfType => dest.write_str(":last-of-type"), - OnlyOfType => dest.write_str(":only-of-type"), - NthChild(a, b) | NthLastChild(a, b) | NthOfType(a, b) | NthLastOfType(a, b) => { - match *self { - NthChild(_, _) => dest.write_str(":nth-child(")?, - NthLastChild(_, _) => dest.write_str(":nth-last-child(")?, - NthOfType(_, _) => dest.write_str(":nth-of-type(")?, - NthLastOfType(_, _) => dest.write_str(":nth-last-of-type(")?, - _ => unreachable!(), - } - write_affine(dest, a, b)?; - dest.write_char(')') - }, - Is(ref list) | Where(ref list) | Negation(ref list) | Has(ref list) => { - match *self { - Where(..) => dest.write_str(":where(")?, - Is(..) => dest.write_str(":is(")?, - Negation(..) => dest.write_str(":not(")?, - Has(..) => dest.write_str(":has(")?, - _ => unreachable!(), - } - serialize_selector_list(list.iter(), dest)?; - dest.write_str(")") - }, - NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest), - Nesting => dest.write_char('&') + Where(..) => dest.write_str(":where(")?, + Is(..) => dest.write_str(":is(")?, + Negation(..) => dest.write_str(":not(")?, + Has(..) => dest.write_str(":has(")?, + _ => unreachable!(), } + serialize_selector_list(list.iter(), dest)?; + dest.write_str(")") + } + NonTSPseudoClass(ref pseudo) => pseudo.to_css(dest), + Nesting => dest.write_char('&'), } + } } impl<'i, Impl: SelectorImpl<'i>> ToCss for AttrSelectorWithOptionalNamespace<'i, Impl> { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - dest.write_char('[')?; - match self.namespace { - Some(NamespaceConstraint::Specific((ref prefix, _))) => { - prefix.to_css(dest)?; - dest.write_char('|')? - }, - Some(NamespaceConstraint::Any) => dest.write_str("*|")?, - None => {}, - } - self.local_name.to_css(dest)?; - match self.operation { - ParsedAttrSelectorOperation::Exists => {}, - ParsedAttrSelectorOperation::WithValue { - operator, - case_sensitivity, - ref expected_value, - } => { - operator.to_css(dest)?; - dest.write_char('"')?; - expected_value.to_css(dest)?; - dest.write_char('"')?; - match case_sensitivity { - ParsedCaseSensitivity::CaseSensitive | - ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {}, - ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?, - ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?, - } - }, - } - dest.write_char(']') - } + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + dest.write_char('[')?; + match self.namespace { + Some(NamespaceConstraint::Specific((ref prefix, _))) => { + prefix.to_css(dest)?; + dest.write_char('|')? + } + Some(NamespaceConstraint::Any) => dest.write_str("*|")?, + None => {} + } + self.local_name.to_css(dest)?; + match self.operation { + ParsedAttrSelectorOperation::Exists => {} + ParsedAttrSelectorOperation::WithValue { + operator, + case_sensitivity, + ref expected_value, + } => { + operator.to_css(dest)?; + dest.write_char('"')?; + expected_value.to_css(dest)?; + dest.write_char('"')?; + match case_sensitivity { + ParsedCaseSensitivity::CaseSensitive + | ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument => {} + ParsedCaseSensitivity::AsciiCaseInsensitive => dest.write_str(" i")?, + ParsedCaseSensitivity::ExplicitCaseSensitive => dest.write_str(" s")?, + } + } + } + dest.write_char(']') + } } impl<'i, Impl: SelectorImpl<'i>> ToCss for LocalName<'i, Impl> { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - self.name.to_css(dest) - } + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + self.name.to_css(dest) + } } /// Build up a Selector. @@ -1716,232 +1654,228 @@ impl<'i, Impl: SelectorImpl<'i>> ToCss for LocalName<'i, Impl> { /// /// `Err` means invalid selector. fn parse_selector<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: &mut SelectorParsingState, - nesting_requirement: NestingRequirement, + parser: &P, + input: &mut CssParser<'i, 't>, + state: &mut SelectorParsingState, + nesting_requirement: NestingRequirement, ) -> Result, ParseError<'i, P::Error>> where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl<'i>, + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl<'i>, { - if nesting_requirement == NestingRequirement::Prefixed { - let state = input.state(); - if !input.expect_delim('&').is_ok() { - return Err(input.new_custom_error(SelectorParseErrorKind::MissingNestingPrefix)) - } - input.reset(&state); - } - - let mut builder = SelectorBuilder::default(); - - let mut has_pseudo_element = false; - let mut slotted = false; - let mut part = false; - 'outer_loop: loop { - // Parse a sequence of simple selectors. - let empty = parse_compound_selector(parser, state, input, &mut builder)?; - if empty { - return Err(input.new_custom_error(if builder.has_combinators() { - SelectorParseErrorKind::DanglingCombinator - } else { - SelectorParseErrorKind::EmptySelector - })); - } - - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT); - slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED); - part = state.intersects(SelectorParsingState::AFTER_PART); - debug_assert!(has_pseudo_element || slotted || part); + if nesting_requirement == NestingRequirement::Prefixed { + let state = input.state(); + if !input.expect_delim('&').is_ok() { + return Err(input.new_custom_error(SelectorParseErrorKind::MissingNestingPrefix)); + } + input.reset(&state); + } + + let mut builder = SelectorBuilder::default(); + + let mut has_pseudo_element = false; + let mut slotted = false; + let mut part = false; + 'outer_loop: loop { + // Parse a sequence of simple selectors. + let empty = parse_compound_selector(parser, state, input, &mut builder)?; + if empty { + return Err(input.new_custom_error(if builder.has_combinators() { + SelectorParseErrorKind::DanglingCombinator + } else { + SelectorParseErrorKind::EmptySelector + })); + } + + if state.intersects(SelectorParsingState::AFTER_PSEUDO) { + has_pseudo_element = state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT); + slotted = state.intersects(SelectorParsingState::AFTER_SLOTTED); + part = state.intersects(SelectorParsingState::AFTER_PART); + debug_assert!(has_pseudo_element || slotted || part); + break; + } + + // Parse a combinator. + let combinator; + let mut any_whitespace = false; + loop { + let before_this_token = input.state(); + match input.next_including_whitespace() { + Err(_e) => break 'outer_loop, + Ok(&Token::WhiteSpace(_)) => any_whitespace = true, + Ok(&Token::Delim('>')) => { + combinator = Combinator::Child; + break; + } + Ok(&Token::Delim('+')) => { + combinator = Combinator::NextSibling; + break; + } + Ok(&Token::Delim('~')) => { + combinator = Combinator::LaterSibling; + break; + } + Ok(_) => { + input.reset(&before_this_token); + if any_whitespace { + combinator = Combinator::Descendant; break; + } else { + break 'outer_loop; + } } - - // Parse a combinator. - let combinator; - let mut any_whitespace = false; - loop { - let before_this_token = input.state(); - match input.next_including_whitespace() { - Err(_e) => break 'outer_loop, - Ok(&Token::WhiteSpace(_)) => any_whitespace = true, - Ok(&Token::Delim('>')) => { - combinator = Combinator::Child; - break; - }, - Ok(&Token::Delim('+')) => { - combinator = Combinator::NextSibling; - break; - }, - Ok(&Token::Delim('~')) => { - combinator = Combinator::LaterSibling; - break; - }, - Ok(_) => { - input.reset(&before_this_token); - if any_whitespace { - combinator = Combinator::Descendant; - break; - } else { - break 'outer_loop; - } - }, - } - } - - if !state.allows_combinators() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - - builder.push_combinator(combinator); + } } - if nesting_requirement == NestingRequirement::Contained && !state.contains(SelectorParsingState::AFTER_NESTING) { - return Err(input.new_custom_error(SelectorParseErrorKind::MissingNestingSelector)) + if !state.allows_combinators() { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); } - let (spec, components) = builder.build(has_pseudo_element, slotted, part); - Ok(Selector(spec, components)) + builder.push_combinator(combinator); + } + + if nesting_requirement == NestingRequirement::Contained && !state.contains(SelectorParsingState::AFTER_NESTING) { + return Err(input.new_custom_error(SelectorParseErrorKind::MissingNestingSelector)); + } + + let (spec, components) = builder.build(has_pseudo_element, slotted, part); + Ok(Selector(spec, components)) } impl<'i, Impl: SelectorImpl<'i>> Selector<'i, Impl> { - /// Parse a selector, without any pseudo-element. - #[inline] - pub fn parse<'t, P>( - parser: &P, - input: &mut CssParser<'i, 't>, - ) -> Result> - where - P: Parser<'i, Impl = Impl>, - { - parse_selector(parser, input, &mut SelectorParsingState::empty(), NestingRequirement::None) - } + /// Parse a selector, without any pseudo-element. + #[inline] + pub fn parse<'t, P>(parser: &P, input: &mut CssParser<'i, 't>) -> Result> + where + P: Parser<'i, Impl = Impl>, + { + parse_selector( + parser, + input, + &mut SelectorParsingState::empty(), + NestingRequirement::None, + ) + } } fn parse_relative_selector<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: &mut SelectorParsingState, + parser: &P, + input: &mut CssParser<'i, 't>, + state: &mut SelectorParsingState, ) -> Result, ParseError<'i, P::Error>> where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl<'i>, + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl<'i>, { - // https://www.w3.org/TR/selectors-4/#parse-relative-selector - let s = input.state(); - let combinator = match input.next()? { - Token::Delim('>') => Some(Combinator::Child), - Token::Delim('+') => Some(Combinator::NextSibling), - Token::Delim('~') => Some(Combinator::LaterSibling), - _ => { - input.reset(&s); - None - } - }; - - let mut selector = parse_selector(parser, input, state, NestingRequirement::None)?; - if let Some(combinator) = combinator { - // https://www.w3.org/TR/selectors/#absolutizing - selector.1.push(Component::Combinator(combinator)); - selector.1.push(Component::Scope); + // https://www.w3.org/TR/selectors-4/#parse-relative-selector + let s = input.state(); + let combinator = match input.next()? { + Token::Delim('>') => Some(Combinator::Child), + Token::Delim('+') => Some(Combinator::NextSibling), + Token::Delim('~') => Some(Combinator::LaterSibling), + _ => { + input.reset(&s); + None } + }; + + let mut selector = parse_selector(parser, input, state, NestingRequirement::None)?; + if let Some(combinator) = combinator { + // https://www.w3.org/TR/selectors/#absolutizing + selector.1.push(Component::Combinator(combinator)); + selector.1.push(Component::Scope); + } - Ok(selector) + Ok(selector) } /// * `Err(())`: Invalid selector, abort /// * `Ok(false)`: Not a type selector, could be something else. `input` was not consumed. /// * `Ok(true)`: Length 0 (`*|*`), 1 (`*|E` or `ns|*`) or 2 (`|E` or `ns|E`) fn parse_type_selector<'i, 't, P, Impl, S>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, - sink: &mut S, + parser: &P, + input: &mut CssParser<'i, 't>, + state: SelectorParsingState, + sink: &mut S, ) -> Result> where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl<'i>, - S: Push>, + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl<'i>, + S: Push>, { - match parse_qualified_name(parser, input, /* in_attr_selector = */ false) { - Err(ParseError { - kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput), - .. - }) | - Ok(OptionalQName::None(_)) => Ok(false), - Ok(OptionalQName::Some(namespace, local_name)) => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - match namespace { - QNamePrefix::ImplicitAnyNamespace => {}, - QNamePrefix::ImplicitDefaultNamespace(url) => { - sink.push(Component::DefaultNamespace(url)) - }, - QNamePrefix::ExplicitNamespace(prefix, url) => { - sink.push(match parser.default_namespace() { - Some(ref default_url) if url == *default_url => { - Component::DefaultNamespace(url) - }, - _ => Component::Namespace(prefix, url), - }) - }, - QNamePrefix::ExplicitNoNamespace => sink.push(Component::ExplicitNoNamespace), - QNamePrefix::ExplicitAnyNamespace => { - match parser.default_namespace() { - // Element type selectors that have no namespace - // component (no namespace separator) represent elements - // without regard to the element's namespace (equivalent - // to "*|") unless a default namespace has been declared - // for namespaced selectors (e.g. in CSS, in the style - // sheet). If a default namespace has been declared, - // such selectors will represent only elements in the - // default namespace. - // -- Selectors § 6.1.1 - // So we'll have this act the same as the - // QNamePrefix::ImplicitAnyNamespace case. - None => {}, - Some(_) => sink.push(Component::ExplicitAnyNamespace), - } - }, - QNamePrefix::ImplicitNoNamespace => { - unreachable!() // Not returned with in_attr_selector = false - }, - } - match local_name { - Some(name) => sink.push(Component::LocalName(LocalName { - lower_name: to_ascii_lowercase(name.clone()).into(), - name: name.into(), - })), - None => sink.push(Component::ExplicitUniversalType), - } - Ok(true) - }, - Err(e) => Err(e), - } + match parse_qualified_name(parser, input, /* in_attr_selector = */ false) { + Err(ParseError { + kind: ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput), + .. + }) + | Ok(OptionalQName::None(_)) => Ok(false), + Ok(OptionalQName::Some(namespace, local_name)) => { + if state.intersects(SelectorParsingState::AFTER_PSEUDO) { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + } + match namespace { + QNamePrefix::ImplicitAnyNamespace => {} + QNamePrefix::ImplicitDefaultNamespace(url) => sink.push(Component::DefaultNamespace(url)), + QNamePrefix::ExplicitNamespace(prefix, url) => sink.push(match parser.default_namespace() { + Some(ref default_url) if url == *default_url => Component::DefaultNamespace(url), + _ => Component::Namespace(prefix, url), + }), + QNamePrefix::ExplicitNoNamespace => sink.push(Component::ExplicitNoNamespace), + QNamePrefix::ExplicitAnyNamespace => { + match parser.default_namespace() { + // Element type selectors that have no namespace + // component (no namespace separator) represent elements + // without regard to the element's namespace (equivalent + // to "*|") unless a default namespace has been declared + // for namespaced selectors (e.g. in CSS, in the style + // sheet). If a default namespace has been declared, + // such selectors will represent only elements in the + // default namespace. + // -- Selectors § 6.1.1 + // So we'll have this act the same as the + // QNamePrefix::ImplicitAnyNamespace case. + None => {} + Some(_) => sink.push(Component::ExplicitAnyNamespace), + } + } + QNamePrefix::ImplicitNoNamespace => { + unreachable!() // Not returned with in_attr_selector = false + } + } + match local_name { + Some(name) => sink.push(Component::LocalName(LocalName { + lower_name: to_ascii_lowercase(name.clone()).into(), + name: name.into(), + })), + None => sink.push(Component::ExplicitUniversalType), + } + Ok(true) + } + Err(e) => Err(e), + } } #[derive(Debug)] enum SimpleSelectorParseResult<'i, Impl: SelectorImpl<'i>> { - SimpleSelector(Component<'i, Impl>), - PseudoElement(Impl::PseudoElement), - SlottedPseudo(Selector<'i, Impl>), - PartPseudo(Box<[Impl::Identifier]>), + SimpleSelector(Component<'i, Impl>), + PseudoElement(Impl::PseudoElement), + SlottedPseudo(Selector<'i, Impl>), + PartPseudo(Box<[Impl::Identifier]>), } #[derive(Debug)] enum QNamePrefix<'i, Impl: SelectorImpl<'i>> { - ImplicitNoNamespace, // `foo` in attr selectors - ImplicitAnyNamespace, // `foo` in type selectors, without a default ns - ImplicitDefaultNamespace(Impl::NamespaceUrl), // `foo` in type selectors, with a default ns - ExplicitNoNamespace, // `|foo` - ExplicitAnyNamespace, // `*|foo` - ExplicitNamespace(Impl::NamespacePrefix, Impl::NamespaceUrl), // `prefix|foo` + ImplicitNoNamespace, // `foo` in attr selectors + ImplicitAnyNamespace, // `foo` in type selectors, without a default ns + ImplicitDefaultNamespace(Impl::NamespaceUrl), // `foo` in type selectors, with a default ns + ExplicitNoNamespace, // `|foo` + ExplicitAnyNamespace, // `*|foo` + ExplicitNamespace(Impl::NamespacePrefix, Impl::NamespaceUrl), // `prefix|foo` } enum OptionalQName<'i, Impl: SelectorImpl<'i>> { - Some(QNamePrefix<'i, Impl>, Option>), - None(Token<'i>), + Some(QNamePrefix<'i, Impl>, Option>), + None(Token<'i>), } /// * `Err(())`: Invalid selector, abort @@ -1949,325 +1883,303 @@ enum OptionalQName<'i, Impl: SelectorImpl<'i>> { /// but the token is still returned. /// * `Ok(Some(namespace, local_name))`: `None` for the local name means a `*` universal selector fn parse_qualified_name<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - in_attr_selector: bool, + parser: &P, + input: &mut CssParser<'i, 't>, + in_attr_selector: bool, ) -> Result, ParseError<'i, P::Error>> where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl<'i>, + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl<'i>, { - let default_namespace = |local_name| { - let namespace = match parser.default_namespace() { - Some(url) => QNamePrefix::ImplicitDefaultNamespace(url), - None => QNamePrefix::ImplicitAnyNamespace, - }; - Ok(OptionalQName::Some(namespace, local_name)) + let default_namespace = |local_name| { + let namespace = match parser.default_namespace() { + Some(url) => QNamePrefix::ImplicitDefaultNamespace(url), + None => QNamePrefix::ImplicitAnyNamespace, }; + Ok(OptionalQName::Some(namespace, local_name)) + }; - let explicit_namespace = |input: &mut CssParser<'i, 't>, namespace| { - let location = input.current_source_location(); - match input.next_including_whitespace() { - Ok(&Token::Delim('*')) if !in_attr_selector => Ok(OptionalQName::Some(namespace, None)), - Ok(&Token::Ident(ref local_name)) => { - Ok(OptionalQName::Some(namespace, Some(local_name.clone()))) - }, - Ok(t) if in_attr_selector => { - let e = SelectorParseErrorKind::InvalidQualNameInAttr(t.clone()); - Err(location.new_custom_error(e)) - }, - Ok(t) => Err(location.new_custom_error( - SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t.clone()), - )), - Err(e) => Err(e.into()), + let explicit_namespace = |input: &mut CssParser<'i, 't>, namespace| { + let location = input.current_source_location(); + match input.next_including_whitespace() { + Ok(&Token::Delim('*')) if !in_attr_selector => Ok(OptionalQName::Some(namespace, None)), + Ok(&Token::Ident(ref local_name)) => Ok(OptionalQName::Some(namespace, Some(local_name.clone()))), + Ok(t) if in_attr_selector => { + let e = SelectorParseErrorKind::InvalidQualNameInAttr(t.clone()); + Err(location.new_custom_error(e)) + } + Ok(t) => Err(location.new_custom_error(SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t.clone()))), + Err(e) => Err(e.into()), + } + }; + + let start = input.state(); + // FIXME: remove clone() when lifetimes are non-lexical + match input.next_including_whitespace().map(|t| t.clone()) { + Ok(Token::Ident(value)) => { + let after_ident = input.state(); + match input.next_including_whitespace() { + Ok(&Token::Delim('|')) => { + let prefix = value.clone().into(); + let result = parser.namespace_for_prefix(&prefix); + let url = result.ok_or( + after_ident + .source_location() + .new_custom_error(SelectorParseErrorKind::ExpectedNamespace(value)), + )?; + explicit_namespace(input, QNamePrefix::ExplicitNamespace(prefix, url)) } - }; - - let start = input.state(); - // FIXME: remove clone() when lifetimes are non-lexical - match input.next_including_whitespace().map(|t| t.clone()) { - Ok(Token::Ident(value)) => { - let after_ident = input.state(); - match input.next_including_whitespace() { - Ok(&Token::Delim('|')) => { - let prefix = value.clone().into(); - let result = parser.namespace_for_prefix(&prefix); - let url = result.ok_or( - after_ident - .source_location() - .new_custom_error(SelectorParseErrorKind::ExpectedNamespace(value)), - )?; - explicit_namespace(input, QNamePrefix::ExplicitNamespace(prefix, url)) - }, - _ => { - input.reset(&after_ident); - if in_attr_selector { - Ok(OptionalQName::Some( - QNamePrefix::ImplicitNoNamespace, - Some(value), - )) - } else { - default_namespace(Some(value)) - } - }, - } - }, - Ok(Token::Delim('*')) => { - let after_star = input.state(); - // FIXME: remove clone() when lifetimes are non-lexical - match input.next_including_whitespace().map(|t| t.clone()) { - Ok(Token::Delim('|')) => { - explicit_namespace(input, QNamePrefix::ExplicitAnyNamespace) - }, - result => { - input.reset(&after_star); - if in_attr_selector { - match result { - Ok(t) => Err(after_star - .source_location() - .new_custom_error(SelectorParseErrorKind::ExpectedBarInAttr(t))), - Err(e) => Err(e.into()), - } - } else { - default_namespace(None) - } - }, + _ => { + input.reset(&after_ident); + if in_attr_selector { + Ok(OptionalQName::Some(QNamePrefix::ImplicitNoNamespace, Some(value))) + } else { + default_namespace(Some(value)) + } + } + } + } + Ok(Token::Delim('*')) => { + let after_star = input.state(); + // FIXME: remove clone() when lifetimes are non-lexical + match input.next_including_whitespace().map(|t| t.clone()) { + Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitAnyNamespace), + result => { + input.reset(&after_star); + if in_attr_selector { + match result { + Ok(t) => Err( + after_star + .source_location() + .new_custom_error(SelectorParseErrorKind::ExpectedBarInAttr(t)), + ), + Err(e) => Err(e.into()), } - }, - Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitNoNamespace), - Ok(t) => { - input.reset(&start); - Ok(OptionalQName::None(t)) - }, - Err(e) => { - input.reset(&start); - Err(e.into()) - }, + } else { + default_namespace(None) + } + } + } + } + Ok(Token::Delim('|')) => explicit_namespace(input, QNamePrefix::ExplicitNoNamespace), + Ok(t) => { + input.reset(&start); + Ok(OptionalQName::None(t)) + } + Err(e) => { + input.reset(&start); + Err(e.into()) } + } } fn parse_attribute_selector<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, + parser: &P, + input: &mut CssParser<'i, 't>, ) -> Result, ParseError<'i, P::Error>> where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl<'i>, + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl<'i>, { - let namespace; - let local_name; - - input.skip_whitespace(); - - match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? { - OptionalQName::None(t) => { - return Err(input.new_custom_error( - SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t), - )); - }, - OptionalQName::Some(_, None) => unreachable!(), - OptionalQName::Some(ns, Some(ln)) => { - local_name = ln; - namespace = match ns { - QNamePrefix::ImplicitNoNamespace | QNamePrefix::ExplicitNoNamespace => None, - QNamePrefix::ExplicitNamespace(prefix, url) => { - Some(NamespaceConstraint::Specific((prefix, url))) - }, - QNamePrefix::ExplicitAnyNamespace => Some(NamespaceConstraint::Any), - QNamePrefix::ImplicitAnyNamespace | QNamePrefix::ImplicitDefaultNamespace(_) => { - unreachable!() // Not returned with in_attr_selector = true - }, - } - }, - } - - let location = input.current_source_location(); - let operator = match input.next() { - // [foo] - Err(_) => { - let local_name_lower = to_ascii_lowercase(local_name.clone()).into(); - let local_name = local_name.into(); - if let Some(namespace) = namespace { - return Ok(Component::AttributeOther(Box::new( - AttrSelectorWithOptionalNamespace { - namespace: Some(namespace), - local_name, - local_name_lower, - operation: ParsedAttrSelectorOperation::Exists, - never_matches: false, - }, - ))); - } else { - return Ok(Component::AttributeInNoNamespaceExists { - local_name, - local_name_lower, - }); - } - }, - - // [foo=bar] - Ok(&Token::Delim('=')) => AttrSelectorOperator::Equal, - // [foo~=bar] - Ok(&Token::IncludeMatch) => AttrSelectorOperator::Includes, - // [foo|=bar] - Ok(&Token::DashMatch) => AttrSelectorOperator::DashMatch, - // [foo^=bar] - Ok(&Token::PrefixMatch) => AttrSelectorOperator::Prefix, - // [foo*=bar] - Ok(&Token::SubstringMatch) => AttrSelectorOperator::Substring, - // [foo$=bar] - Ok(&Token::SuffixMatch) => AttrSelectorOperator::Suffix, - Ok(t) => { - return Err(location.new_custom_error( - SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(t.clone()), - )); - }, - }; - - let value = match input.expect_ident_or_string() { - Ok(t) => t.clone(), - Err(BasicParseError { - kind: BasicParseErrorKind::UnexpectedToken(t), - location, - }) => return Err(location.new_custom_error(SelectorParseErrorKind::BadValueInAttr(t))), - Err(e) => return Err(e.into()), - }; - let never_matches = match operator { - AttrSelectorOperator::Equal | AttrSelectorOperator::DashMatch => false, - - AttrSelectorOperator::Includes => value.is_empty() || value.contains(SELECTOR_WHITESPACE), - - AttrSelectorOperator::Prefix | - AttrSelectorOperator::Substring | - AttrSelectorOperator::Suffix => value.is_empty(), - }; - - let attribute_flags = parse_attribute_flags(input)?; - - let value = value.into(); - let case_sensitivity; - // copied from to_ascii_lowercase function, so we can know whether it is already lower case. - let (local_name_lower_cow, local_name_is_ascii_lowercase) = if let Some(first_uppercase) = local_name.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { - let mut string = local_name.to_string(); - string[first_uppercase..].make_ascii_lowercase(); - (string.into(), false) + let namespace; + let local_name; + + input.skip_whitespace(); + + match parse_qualified_name(parser, input, /* in_attr_selector = */ true)? { + OptionalQName::None(t) => { + return Err(input.new_custom_error(SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t))); + } + OptionalQName::Some(_, None) => unreachable!(), + OptionalQName::Some(ns, Some(ln)) => { + local_name = ln; + namespace = match ns { + QNamePrefix::ImplicitNoNamespace | QNamePrefix::ExplicitNoNamespace => None, + QNamePrefix::ExplicitNamespace(prefix, url) => Some(NamespaceConstraint::Specific((prefix, url))), + QNamePrefix::ExplicitAnyNamespace => Some(NamespaceConstraint::Any), + QNamePrefix::ImplicitAnyNamespace | QNamePrefix::ImplicitDefaultNamespace(_) => { + unreachable!() // Not returned with in_attr_selector = true + } + } + } + } + + let location = input.current_source_location(); + let operator = match input.next() { + // [foo] + Err(_) => { + let local_name_lower = to_ascii_lowercase(local_name.clone()).into(); + let local_name = local_name.into(); + if let Some(namespace) = namespace { + return Ok(Component::AttributeOther(Box::new(AttrSelectorWithOptionalNamespace { + namespace: Some(namespace), + local_name, + local_name_lower, + operation: ParsedAttrSelectorOperation::Exists, + never_matches: false, + }))); + } else { + return Ok(Component::AttributeInNoNamespaceExists { + local_name, + local_name_lower, + }); + } + } + + // [foo=bar] + Ok(&Token::Delim('=')) => AttrSelectorOperator::Equal, + // [foo~=bar] + Ok(&Token::IncludeMatch) => AttrSelectorOperator::Includes, + // [foo|=bar] + Ok(&Token::DashMatch) => AttrSelectorOperator::DashMatch, + // [foo^=bar] + Ok(&Token::PrefixMatch) => AttrSelectorOperator::Prefix, + // [foo*=bar] + Ok(&Token::SubstringMatch) => AttrSelectorOperator::Substring, + // [foo$=bar] + Ok(&Token::SuffixMatch) => AttrSelectorOperator::Suffix, + Ok(t) => { + return Err( + location.new_custom_error(SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(t.clone())), + ); + } + }; + + let value = match input.expect_ident_or_string() { + Ok(t) => t.clone(), + Err(BasicParseError { + kind: BasicParseErrorKind::UnexpectedToken(t), + location, + }) => return Err(location.new_custom_error(SelectorParseErrorKind::BadValueInAttr(t))), + Err(e) => return Err(e.into()), + }; + let never_matches = match operator { + AttrSelectorOperator::Equal | AttrSelectorOperator::DashMatch => false, + + AttrSelectorOperator::Includes => value.is_empty() || value.contains(SELECTOR_WHITESPACE), + + AttrSelectorOperator::Prefix | AttrSelectorOperator::Substring | AttrSelectorOperator::Suffix => { + value.is_empty() + } + }; + + let attribute_flags = parse_attribute_flags(input)?; + + let value = value.into(); + let case_sensitivity; + // copied from to_ascii_lowercase function, so we can know whether it is already lower case. + let (local_name_lower_cow, local_name_is_ascii_lowercase) = + if let Some(first_uppercase) = local_name.bytes().position(|byte| byte >= b'A' && byte <= b'Z') { + let mut string = local_name.to_string(); + string[first_uppercase..].make_ascii_lowercase(); + (string.into(), false) } else { - (local_name.clone(), true) + (local_name.clone(), true) }; - case_sensitivity = - attribute_flags.to_case_sensitivity(local_name_lower_cow.as_ref(), namespace.is_some()); - let local_name_lower = local_name_lower_cow.into(); - let local_name = local_name.into(); - if namespace.is_some() || !local_name_is_ascii_lowercase { - Ok(Component::AttributeOther(Box::new( - AttrSelectorWithOptionalNamespace { - namespace, - local_name, - local_name_lower, - never_matches, - operation: ParsedAttrSelectorOperation::WithValue { - operator, - case_sensitivity, - expected_value: value, - }, - }, - ))) - } else { - Ok(Component::AttributeInNoNamespace { - local_name, - operator, - value, - case_sensitivity, - never_matches, - }) - } + case_sensitivity = attribute_flags.to_case_sensitivity(local_name_lower_cow.as_ref(), namespace.is_some()); + let local_name_lower = local_name_lower_cow.into(); + let local_name = local_name.into(); + if namespace.is_some() || !local_name_is_ascii_lowercase { + Ok(Component::AttributeOther(Box::new(AttrSelectorWithOptionalNamespace { + namespace, + local_name, + local_name_lower, + never_matches, + operation: ParsedAttrSelectorOperation::WithValue { + operator, + case_sensitivity, + expected_value: value, + }, + }))) + } else { + Ok(Component::AttributeInNoNamespace { + local_name, + operator, + value, + case_sensitivity, + never_matches, + }) + } } /// An attribute selector can have 's' or 'i' as flags, or no flags at all. enum AttributeFlags { - // Matching should be case-sensitive ('s' flag). - CaseSensitive, - // Matching should be case-insensitive ('i' flag). - AsciiCaseInsensitive, - // No flags. Matching behavior depends on the name of the attribute. - CaseSensitivityDependsOnName, + // Matching should be case-sensitive ('s' flag). + CaseSensitive, + // Matching should be case-insensitive ('i' flag). + AsciiCaseInsensitive, + // No flags. Matching behavior depends on the name of the attribute. + CaseSensitivityDependsOnName, } impl AttributeFlags { - fn to_case_sensitivity(self, local_name: &str, have_namespace: bool) -> ParsedCaseSensitivity { - match self { - AttributeFlags::CaseSensitive => ParsedCaseSensitivity::ExplicitCaseSensitive, - AttributeFlags::AsciiCaseInsensitive => ParsedCaseSensitivity::AsciiCaseInsensitive, - AttributeFlags::CaseSensitivityDependsOnName => { - if !have_namespace && - include!(concat!( - env!("OUT_DIR"), - "/ascii_case_insensitive_html_attributes.rs" - )) - .contains(local_name) - { - ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument - } else { - ParsedCaseSensitivity::CaseSensitive - } - }, + fn to_case_sensitivity(self, local_name: &str, have_namespace: bool) -> ParsedCaseSensitivity { + match self { + AttributeFlags::CaseSensitive => ParsedCaseSensitivity::ExplicitCaseSensitive, + AttributeFlags::AsciiCaseInsensitive => ParsedCaseSensitivity::AsciiCaseInsensitive, + AttributeFlags::CaseSensitivityDependsOnName => { + if !have_namespace + && include!(concat!(env!("OUT_DIR"), "/ascii_case_insensitive_html_attributes.rs")).contains(local_name) + { + ParsedCaseSensitivity::AsciiCaseInsensitiveIfInHtmlElementInHtmlDocument + } else { + ParsedCaseSensitivity::CaseSensitive } + } } + } } -fn parse_attribute_flags<'i, 't>( - input: &mut CssParser<'i, 't>, -) -> Result> { - let location = input.current_source_location(); - let token = match input.next() { - Ok(t) => t, - Err(..) => { - // Selectors spec says language-defined; HTML says it depends on the - // exact attribute name. - return Ok(AttributeFlags::CaseSensitivityDependsOnName); - }, - }; +fn parse_attribute_flags<'i, 't>(input: &mut CssParser<'i, 't>) -> Result> { + let location = input.current_source_location(); + let token = match input.next() { + Ok(t) => t, + Err(..) => { + // Selectors spec says language-defined; HTML says it depends on the + // exact attribute name. + return Ok(AttributeFlags::CaseSensitivityDependsOnName); + } + }; - let ident = match *token { - Token::Ident(ref i) => i, - ref other => return Err(location.new_basic_unexpected_token_error(other.clone())), - }; + let ident = match *token { + Token::Ident(ref i) => i, + ref other => return Err(location.new_basic_unexpected_token_error(other.clone())), + }; - Ok(match_ignore_ascii_case! { - ident, - "i" => AttributeFlags::AsciiCaseInsensitive, - "s" => AttributeFlags::CaseSensitive, - _ => return Err(location.new_basic_unexpected_token_error(token.clone())), - }) + Ok(match_ignore_ascii_case! { + ident, + "i" => AttributeFlags::AsciiCaseInsensitive, + "s" => AttributeFlags::CaseSensitive, + _ => return Err(location.new_basic_unexpected_token_error(token.clone())), + }) } /// Level 3: Parse **one** simple_selector. (Though we might insert a second /// implied "|*" type selector.) fn parse_negation<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: &mut SelectorParsingState, + parser: &P, + input: &mut CssParser<'i, 't>, + state: &mut SelectorParsingState, ) -> Result, ParseError<'i, P::Error>> where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl<'i>, + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl<'i>, { - let mut child_state = *state | - SelectorParsingState::SKIP_DEFAULT_NAMESPACE | - SelectorParsingState::DISALLOW_PSEUDOS; - let list = SelectorList::parse_with_state( - parser, - input, - &mut child_state, - ParseErrorRecovery::DiscardList, - NestingRequirement::None, - )?; + let mut child_state = + *state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS; + let list = SelectorList::parse_with_state( + parser, + input, + &mut child_state, + ParseErrorRecovery::DiscardList, + NestingRequirement::None, + )?; - if child_state.contains(SelectorParsingState::AFTER_NESTING) { - state.insert(SelectorParsingState::AFTER_NESTING) - } + if child_state.contains(SelectorParsingState::AFTER_NESTING) { + state.insert(SelectorParsingState::AFTER_NESTING) + } - Ok(Component::Negation(list.0.into_vec().into_boxed_slice())) + Ok(Component::Negation(list.0.into_vec().into_boxed_slice())) } /// simple_selector_sequence @@ -2277,228 +2189,218 @@ where /// `Err(())` means invalid selector. /// `Ok(true)` is an empty selector fn parse_compound_selector<'i, 't, P, Impl>( - parser: &P, - state: &mut SelectorParsingState, - input: &mut CssParser<'i, 't>, - builder: &mut SelectorBuilder<'i, Impl>, + parser: &P, + state: &mut SelectorParsingState, + input: &mut CssParser<'i, 't>, + builder: &mut SelectorBuilder<'i, Impl>, ) -> Result> where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl<'i>, + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl<'i>, { - input.skip_whitespace(); - - let mut empty = true; - if parser.is_nesting_allowed() && input.try_parse(|input| input.expect_delim('&')).is_ok() { - state.insert(SelectorParsingState::AFTER_NESTING); - builder.push_simple_selector(Component::Nesting); - empty = false; - } - - if parse_type_selector(parser, input, *state, builder)? { - empty = false; - } - - loop { - let result = match parse_one_simple_selector(parser, input, state)? { - None => break, - Some(result) => result, - }; - - if empty { - if let Some(url) = parser.default_namespace() { - // If there was no explicit type selector, but there is a - // default namespace, there is an implicit "|*" type - // selector. Except for :host() or :not() / :is() / :where(), - // where we ignore it. - // - // https://drafts.csswg.org/css-scoping/#host-element-in-tree: - // - // When considered within its own shadow trees, the shadow - // host is featureless. Only the :host, :host(), and - // :host-context() pseudo-classes are allowed to match it. - // - // https://drafts.csswg.org/selectors-4/#featureless: - // - // A featureless element does not match any selector at all, - // except those it is explicitly defined to match. If a - // given selector is allowed to match a featureless element, - // it must do so while ignoring the default namespace. - // - // https://drafts.csswg.org/selectors-4/#matches - // - // Default namespace declarations do not affect the compound - // selector representing the subject of any selector within - // a :is() pseudo-class, unless that compound selector - // contains an explicit universal selector or type selector. - // - // (Similar quotes for :where() / :not()) - // - let ignore_default_ns = state - .intersects(SelectorParsingState::SKIP_DEFAULT_NAMESPACE) || - matches!( - result, - SimpleSelectorParseResult::SimpleSelector(Component::Host(..)) - ); - if !ignore_default_ns { - builder.push_simple_selector(Component::DefaultNamespace(url)); - } - } - } + input.skip_whitespace(); + + let mut empty = true; + if parser.is_nesting_allowed() && input.try_parse(|input| input.expect_delim('&')).is_ok() { + state.insert(SelectorParsingState::AFTER_NESTING); + builder.push_simple_selector(Component::Nesting); + empty = false; + } + + if parse_type_selector(parser, input, *state, builder)? { + empty = false; + } + + loop { + let result = match parse_one_simple_selector(parser, input, state)? { + None => break, + Some(result) => result, + }; - empty = false; - - match result { - SimpleSelectorParseResult::SimpleSelector(s) => { - builder.push_simple_selector(s); - }, - SimpleSelectorParseResult::PartPseudo(part_names) => { - state.insert(SelectorParsingState::AFTER_PART); - builder.push_combinator(Combinator::Part); - builder.push_simple_selector(Component::Part(part_names)); - }, - SimpleSelectorParseResult::SlottedPseudo(selector) => { - state.insert(SelectorParsingState::AFTER_SLOTTED); - builder.push_combinator(Combinator::SlotAssignment); - builder.push_simple_selector(Component::Slotted(selector)); - }, - SimpleSelectorParseResult::PseudoElement(p) => { - state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT); - if !p.accepts_state_pseudo_classes() { - state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT); - } - if p.is_webkit_scrollbar() { - state.insert(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR); - } - builder.push_combinator(Combinator::PseudoElement); - builder.push_simple_selector(Component::PseudoElement(p)); - }, - } - } - Ok(empty) + if empty { + if let Some(url) = parser.default_namespace() { + // If there was no explicit type selector, but there is a + // default namespace, there is an implicit "|*" type + // selector. Except for :host() or :not() / :is() / :where(), + // where we ignore it. + // + // https://drafts.csswg.org/css-scoping/#host-element-in-tree: + // + // When considered within its own shadow trees, the shadow + // host is featureless. Only the :host, :host(), and + // :host-context() pseudo-classes are allowed to match it. + // + // https://drafts.csswg.org/selectors-4/#featureless: + // + // A featureless element does not match any selector at all, + // except those it is explicitly defined to match. If a + // given selector is allowed to match a featureless element, + // it must do so while ignoring the default namespace. + // + // https://drafts.csswg.org/selectors-4/#matches + // + // Default namespace declarations do not affect the compound + // selector representing the subject of any selector within + // a :is() pseudo-class, unless that compound selector + // contains an explicit universal selector or type selector. + // + // (Similar quotes for :where() / :not()) + // + let ignore_default_ns = state.intersects(SelectorParsingState::SKIP_DEFAULT_NAMESPACE) + || matches!(result, SimpleSelectorParseResult::SimpleSelector(Component::Host(..))); + if !ignore_default_ns { + builder.push_simple_selector(Component::DefaultNamespace(url)); + } + } + } + + empty = false; + + match result { + SimpleSelectorParseResult::SimpleSelector(s) => { + builder.push_simple_selector(s); + } + SimpleSelectorParseResult::PartPseudo(part_names) => { + state.insert(SelectorParsingState::AFTER_PART); + builder.push_combinator(Combinator::Part); + builder.push_simple_selector(Component::Part(part_names)); + } + SimpleSelectorParseResult::SlottedPseudo(selector) => { + state.insert(SelectorParsingState::AFTER_SLOTTED); + builder.push_combinator(Combinator::SlotAssignment); + builder.push_simple_selector(Component::Slotted(selector)); + } + SimpleSelectorParseResult::PseudoElement(p) => { + state.insert(SelectorParsingState::AFTER_PSEUDO_ELEMENT); + if !p.accepts_state_pseudo_classes() { + state.insert(SelectorParsingState::AFTER_NON_STATEFUL_PSEUDO_ELEMENT); + } + if p.is_webkit_scrollbar() { + state.insert(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR); + } + builder.push_combinator(Combinator::PseudoElement); + builder.push_simple_selector(Component::PseudoElement(p)); + } + } + } + Ok(empty) } fn parse_is_or_where<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: &mut SelectorParsingState, - component: impl FnOnce(Box<[Selector<'i, Impl>]>) -> Component<'i, Impl>, + parser: &P, + input: &mut CssParser<'i, 't>, + state: &mut SelectorParsingState, + component: impl FnOnce(Box<[Selector<'i, Impl>]>) -> Component<'i, Impl>, ) -> Result, ParseError<'i, P::Error>> where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl<'i>, + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl<'i>, { - debug_assert!(parser.parse_is_and_where()); - // https://drafts.csswg.org/selectors/#matches-pseudo: - // - // Pseudo-elements cannot be represented by the matches-any - // pseudo-class; they are not valid within :is(). - // - let mut child_state = *state | - SelectorParsingState::SKIP_DEFAULT_NAMESPACE | - SelectorParsingState::DISALLOW_PSEUDOS; - let inner = SelectorList::parse_with_state( - parser, - input, - &mut child_state, - parser.is_and_where_error_recovery(), - NestingRequirement::None, - )?; - if child_state.contains(SelectorParsingState::AFTER_NESTING) { - state.insert(SelectorParsingState::AFTER_NESTING) - } - Ok(component(inner.0.into_vec().into_boxed_slice())) + debug_assert!(parser.parse_is_and_where()); + // https://drafts.csswg.org/selectors/#matches-pseudo: + // + // Pseudo-elements cannot be represented by the matches-any + // pseudo-class; they are not valid within :is(). + // + let mut child_state = + *state | SelectorParsingState::SKIP_DEFAULT_NAMESPACE | SelectorParsingState::DISALLOW_PSEUDOS; + let inner = SelectorList::parse_with_state( + parser, + input, + &mut child_state, + parser.is_and_where_error_recovery(), + NestingRequirement::None, + )?; + if child_state.contains(SelectorParsingState::AFTER_NESTING) { + state.insert(SelectorParsingState::AFTER_NESTING) + } + Ok(component(inner.0.into_vec().into_boxed_slice())) } fn parse_has<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: &mut SelectorParsingState, + parser: &P, + input: &mut CssParser<'i, 't>, + state: &mut SelectorParsingState, ) -> Result, ParseError<'i, P::Error>> where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl<'i>, + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl<'i>, { - let mut child_state = *state; - let inner = SelectorList::parse_relative( - parser, - input, - &mut child_state, - parser.is_and_where_error_recovery(), - )?; - if child_state.contains(SelectorParsingState::AFTER_NESTING) { - state.insert(SelectorParsingState::AFTER_NESTING) - } - Ok(Component::Has(inner.0.into_vec().into_boxed_slice())) + let mut child_state = *state; + let inner = SelectorList::parse_relative(parser, input, &mut child_state, parser.is_and_where_error_recovery())?; + if child_state.contains(SelectorParsingState::AFTER_NESTING) { + state.insert(SelectorParsingState::AFTER_NESTING) + } + Ok(Component::Has(inner.0.into_vec().into_boxed_slice())) } fn parse_functional_pseudo_class<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - name: CowRcStr<'i>, - state: &mut SelectorParsingState, + parser: &P, + input: &mut CssParser<'i, 't>, + name: CowRcStr<'i>, + state: &mut SelectorParsingState, ) -> Result, ParseError<'i, P::Error>> where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl<'i>, + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl<'i>, { - match_ignore_ascii_case! { &name, - "nth-child" => return parse_nth_pseudo_class(parser, input, *state, Component::NthChild), - "nth-of-type" => return parse_nth_pseudo_class(parser, input, *state, Component::NthOfType), - "nth-last-child" => return parse_nth_pseudo_class(parser, input, *state, Component::NthLastChild), - "nth-last-of-type" => return parse_nth_pseudo_class(parser, input, *state, Component::NthLastOfType), - "is" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Is), - "where" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Where), - "has" => return parse_has(parser, input, state), - "host" => { - if !state.allows_tree_structural_pseudo_classes() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input, state)?))); - }, - "not" => { - return parse_negation(parser, input, state) - }, - _ => {} - } - - if parser.parse_is_and_where() && parser.is_is_alias(&name) { - return parse_is_or_where(parser, input, state, Component::Is); - } - - if !state.allows_custom_functional_pseudo_classes() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - - P::parse_non_ts_functional_pseudo_class(parser, name, input).map(Component::NonTSPseudoClass) + match_ignore_ascii_case! { &name, + "nth-child" => return parse_nth_pseudo_class(parser, input, *state, Component::NthChild), + "nth-of-type" => return parse_nth_pseudo_class(parser, input, *state, Component::NthOfType), + "nth-last-child" => return parse_nth_pseudo_class(parser, input, *state, Component::NthLastChild), + "nth-last-of-type" => return parse_nth_pseudo_class(parser, input, *state, Component::NthLastOfType), + "is" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Is), + "where" if parser.parse_is_and_where() => return parse_is_or_where(parser, input, state, Component::Where), + "has" => return parse_has(parser, input, state), + "host" => { + if !state.allows_tree_structural_pseudo_classes() { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + } + return Ok(Component::Host(Some(parse_inner_compound_selector(parser, input, state)?))); + }, + "not" => { + return parse_negation(parser, input, state) + }, + _ => {} + } + + if parser.parse_is_and_where() && parser.is_is_alias(&name) { + return parse_is_or_where(parser, input, state, Component::Is); + } + + if !state.allows_custom_functional_pseudo_classes() { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + } + + P::parse_non_ts_functional_pseudo_class(parser, name, input).map(Component::NonTSPseudoClass) } fn parse_nth_pseudo_class<'i, 't, P, Impl, F>( - _: &P, - input: &mut CssParser<'i, 't>, - state: SelectorParsingState, - selector: F, + _: &P, + input: &mut CssParser<'i, 't>, + state: SelectorParsingState, + selector: F, ) -> Result, ParseError<'i, P::Error>> where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl<'i>, - F: FnOnce(i32, i32) -> Component<'i, Impl>, + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl<'i>, + F: FnOnce(i32, i32) -> Component<'i, Impl>, { - if !state.allows_tree_structural_pseudo_classes() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let (a, b) = parse_nth(input)?; - Ok(selector(a, b)) + if !state.allows_tree_structural_pseudo_classes() { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + } + let (a, b) = parse_nth(input)?; + Ok(selector(a, b)) } /// Returns whether the name corresponds to a CSS2 pseudo-element that /// can be specified with the single colon syntax (in addition to the /// double-colon syntax, which can be used for all pseudo-elements). fn is_css2_pseudo_element(name: &str) -> bool { - // ** Do not add to this list! ** - match_ignore_ascii_case! { name, - "before" | "after" | "first-line" | "first-letter" => true, - _ => false, - } + // ** Do not add to this list! ** + match_ignore_ascii_case! { name, + "before" | "after" | "first-line" | "first-letter" => true, + _ => false, + } } /// Parse a simple selector other than a type selector. @@ -2507,1036 +2409,995 @@ fn is_css2_pseudo_element(name: &str) -> bool { /// * `Ok(None)`: Not a simple selector, could be something else. `input` was not consumed. /// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element fn parse_one_simple_selector<'i, 't, P, Impl>( - parser: &P, - input: &mut CssParser<'i, 't>, - state: &mut SelectorParsingState, + parser: &P, + input: &mut CssParser<'i, 't>, + state: &mut SelectorParsingState, ) -> Result>, ParseError<'i, P::Error>> where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl<'i>, + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl<'i>, { - let start = input.state(); - let token = match input.next_including_whitespace().map(|t| t.clone()) { - Ok(t) => t, - Err(..) => { - input.reset(&start); - return Ok(None); - }, - }; - - Ok(Some(match token { - Token::IDHash(id) => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let id = Component::ID(id.into()); - SimpleSelectorParseResult::SimpleSelector(id) - }, - Token::Delim('.') => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let location = input.current_source_location(); - let class = match *input.next_including_whitespace()? { - Token::Ident(ref class) => class.clone(), - ref t => { - let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone()); - return Err(location.new_custom_error(e)); - }, - }; - let class = Component::Class(class.into()); - SimpleSelectorParseResult::SimpleSelector(class) - }, - Token::SquareBracketBlock => { - if state.intersects(SelectorParsingState::AFTER_PSEUDO) { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + let start = input.state(); + let token = match input.next_including_whitespace().map(|t| t.clone()) { + Ok(t) => t, + Err(..) => { + input.reset(&start); + return Ok(None); + } + }; + + Ok(Some(match token { + Token::IDHash(id) => { + if state.intersects(SelectorParsingState::AFTER_PSEUDO) { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + } + let id = Component::ID(id.into()); + SimpleSelectorParseResult::SimpleSelector(id) + } + Token::Delim('.') => { + if state.intersects(SelectorParsingState::AFTER_PSEUDO) { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + } + let location = input.current_source_location(); + let class = match *input.next_including_whitespace()? { + Token::Ident(ref class) => class.clone(), + ref t => { + let e = SelectorParseErrorKind::ClassNeedsIdent(t.clone()); + return Err(location.new_custom_error(e)); + } + }; + let class = Component::Class(class.into()); + SimpleSelectorParseResult::SimpleSelector(class) + } + Token::SquareBracketBlock => { + if state.intersects(SelectorParsingState::AFTER_PSEUDO) { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + } + let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?; + SimpleSelectorParseResult::SimpleSelector(attr) + } + Token::Colon => { + let location = input.current_source_location(); + let (is_single_colon, next_token) = match input.next_including_whitespace()?.clone() { + Token::Colon => (false, input.next_including_whitespace()?.clone()), + t => (true, t), + }; + let (name, is_functional) = match next_token { + Token::Ident(name) => (name, false), + Token::Function(name) => (name, true), + t => { + let e = SelectorParseErrorKind::PseudoElementExpectedIdent(t); + return Err(input.new_custom_error(e)); + } + }; + let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name); + if is_pseudo_element { + if !state.allows_pseudos() { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + } + let pseudo_element = if is_functional { + if P::parse_part(parser) && name.eq_ignore_ascii_case("part") { + if !state.allows_part() { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); } - let attr = input.parse_nested_block(|input| parse_attribute_selector(parser, input))?; - SimpleSelectorParseResult::SimpleSelector(attr) - }, - Token::Colon => { - let location = input.current_source_location(); - let (is_single_colon, next_token) = match input.next_including_whitespace()?.clone() { - Token::Colon => (false, input.next_including_whitespace()?.clone()), - t => (true, t), - }; - let (name, is_functional) = match next_token { - Token::Ident(name) => (name, false), - Token::Function(name) => (name, true), - t => { - let e = SelectorParseErrorKind::PseudoElementExpectedIdent(t); - return Err(input.new_custom_error(e)); - }, - }; - let is_pseudo_element = !is_single_colon || is_css2_pseudo_element(&name); - if is_pseudo_element { - if !state.allows_pseudos() { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - let pseudo_element = if is_functional { - if P::parse_part(parser) && name.eq_ignore_ascii_case("part") { - if !state.allows_part() { - return Err( - input.new_custom_error(SelectorParseErrorKind::InvalidState) - ); - } - let names = input.parse_nested_block(|input| { - let mut result = Vec::with_capacity(1); - result.push(input.expect_ident_cloned()?.into()); - while !input.is_exhausted() { - result.push(input.expect_ident_cloned()?.into()); - } - Ok(result.into_boxed_slice()) - })?; - return Ok(Some(SimpleSelectorParseResult::PartPseudo(names))); - } - if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") { - if !state.allows_slotted() { - return Err( - input.new_custom_error(SelectorParseErrorKind::InvalidState) - ); - } - let selector = input.parse_nested_block(|input| { - parse_inner_compound_selector(parser, input, state) - })?; - return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector))); - } - input.parse_nested_block(|input| { - P::parse_functional_pseudo_element(parser, name, input) - })? - } else { - P::parse_pseudo_element(parser, location, name)? - }; - - if state.intersects(SelectorParsingState::AFTER_SLOTTED) && - !pseudo_element.valid_after_slotted() - { - return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - SimpleSelectorParseResult::PseudoElement(pseudo_element) - } else { - let pseudo_class = if is_functional { - input.parse_nested_block(|input| { - parse_functional_pseudo_class(parser, input, name, state) - })? - } else { - parse_simple_pseudo_class(parser, location, name, *state)? - }; - SimpleSelectorParseResult::SimpleSelector(pseudo_class) + let names = input.parse_nested_block(|input| { + let mut result = Vec::with_capacity(1); + result.push(input.expect_ident_cloned()?.into()); + while !input.is_exhausted() { + result.push(input.expect_ident_cloned()?.into()); + } + Ok(result.into_boxed_slice()) + })?; + return Ok(Some(SimpleSelectorParseResult::PartPseudo(names))); + } + if P::parse_slotted(parser) && name.eq_ignore_ascii_case("slotted") { + if !state.allows_slotted() { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); } - }, - Token::Delim('&') if parser.is_nesting_allowed() => { - *state |= SelectorParsingState::AFTER_NESTING; - SimpleSelectorParseResult::SimpleSelector(Component::Nesting) - }, - _ => { - input.reset(&start); - return Ok(None); - }, - })) + let selector = + input.parse_nested_block(|input| parse_inner_compound_selector(parser, input, state))?; + return Ok(Some(SimpleSelectorParseResult::SlottedPseudo(selector))); + } + input.parse_nested_block(|input| P::parse_functional_pseudo_element(parser, name, input))? + } else { + P::parse_pseudo_element(parser, location, name)? + }; + + if state.intersects(SelectorParsingState::AFTER_SLOTTED) && !pseudo_element.valid_after_slotted() { + return Err(input.new_custom_error(SelectorParseErrorKind::InvalidState)); + } + SimpleSelectorParseResult::PseudoElement(pseudo_element) + } else { + let pseudo_class = if is_functional { + input.parse_nested_block(|input| parse_functional_pseudo_class(parser, input, name, state))? + } else { + parse_simple_pseudo_class(parser, location, name, *state)? + }; + SimpleSelectorParseResult::SimpleSelector(pseudo_class) + } + } + Token::Delim('&') if parser.is_nesting_allowed() => { + *state |= SelectorParsingState::AFTER_NESTING; + SimpleSelectorParseResult::SimpleSelector(Component::Nesting) + } + _ => { + input.reset(&start); + return Ok(None); + } + })) } fn parse_simple_pseudo_class<'i, P, Impl>( - parser: &P, - location: SourceLocation, - name: CowRcStr<'i>, - state: SelectorParsingState, + parser: &P, + location: SourceLocation, + name: CowRcStr<'i>, + state: SelectorParsingState, ) -> Result, ParseError<'i, P::Error>> where - P: Parser<'i, Impl = Impl>, - Impl: SelectorImpl<'i>, + P: Parser<'i, Impl = Impl>, + Impl: SelectorImpl<'i>, { - if !state.allows_non_functional_pseudo_classes() { - return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState)); - } - - if state.allows_tree_structural_pseudo_classes() { - match_ignore_ascii_case! { &name, - "first-child" => return Ok(Component::FirstChild), - "last-child" => return Ok(Component::LastChild), - "only-child" => return Ok(Component::OnlyChild), - "root" => return Ok(Component::Root), - "empty" => return Ok(Component::Empty), - "scope" => return Ok(Component::Scope), - "host" if P::parse_host(parser) => return Ok(Component::Host(None)), - "first-of-type" => return Ok(Component::FirstOfType), - "last-of-type" => return Ok(Component::LastOfType), - "only-of-type" => return Ok(Component::OnlyOfType), - _ => {}, - } - } + if !state.allows_non_functional_pseudo_classes() { + return Err(location.new_custom_error(SelectorParseErrorKind::InvalidState)); + } - let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name)?; - if state.intersects(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR) { - if !pseudo_class.is_webkit_scrollbar_state() { - return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassAfterWebKitScrollbar)); - } - } else if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) { - if !pseudo_class.is_user_action_state() { - return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassAfterPseudoElement)); - } - } else if pseudo_class.is_webkit_scrollbar_state() { - return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassBeforeWebKitScrollbar)); - } - Ok(Component::NonTSPseudoClass(pseudo_class)) + if state.allows_tree_structural_pseudo_classes() { + match_ignore_ascii_case! { &name, + "first-child" => return Ok(Component::FirstChild), + "last-child" => return Ok(Component::LastChild), + "only-child" => return Ok(Component::OnlyChild), + "root" => return Ok(Component::Root), + "empty" => return Ok(Component::Empty), + "scope" => return Ok(Component::Scope), + "host" if P::parse_host(parser) => return Ok(Component::Host(None)), + "first-of-type" => return Ok(Component::FirstOfType), + "last-of-type" => return Ok(Component::LastOfType), + "only-of-type" => return Ok(Component::OnlyOfType), + _ => {}, + } + } + + let pseudo_class = P::parse_non_ts_pseudo_class(parser, location, name)?; + if state.intersects(SelectorParsingState::AFTER_WEBKIT_SCROLLBAR) { + if !pseudo_class.is_webkit_scrollbar_state() { + return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassAfterWebKitScrollbar)); + } + } else if state.intersects(SelectorParsingState::AFTER_PSEUDO_ELEMENT) { + if !pseudo_class.is_user_action_state() { + return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassAfterPseudoElement)); + } + } else if pseudo_class.is_webkit_scrollbar_state() { + return Err(location.new_custom_error(SelectorParseErrorKind::InvalidPseudoClassBeforeWebKitScrollbar)); + } + Ok(Component::NonTSPseudoClass(pseudo_class)) } // NB: pub module in order to access the DummyParser #[cfg(test)] pub mod tests { - use super::*; - use crate::builder::SelectorFlags; - use crate::parser; - use cssparser::{serialize_identifier, Parser as CssParser, ParserInput, ToCss}; - use std::collections::HashMap; - use std::fmt; + use super::*; + use crate::builder::SelectorFlags; + use crate::parser; + use cssparser::{serialize_identifier, Parser as CssParser, ParserInput, ToCss}; + use std::collections::HashMap; + use std::fmt; + + #[derive(Clone, Debug, Eq, PartialEq)] + pub enum PseudoClass { + Hover, + Active, + Lang(String), + } + + #[derive(Clone, Debug, Eq, PartialEq)] + pub enum PseudoElement { + Before, + After, + } + + impl<'i> parser::PseudoElement<'i> for PseudoElement { + type Impl = DummySelectorImpl; - #[derive(Clone, Debug, Eq, PartialEq)] - pub enum PseudoClass { - Hover, - Active, - Lang(String), - } - - #[derive(Clone, Debug, Eq, PartialEq)] - pub enum PseudoElement { - Before, - After, + fn accepts_state_pseudo_classes(&self) -> bool { + true } - impl<'i> parser::PseudoElement<'i> for PseudoElement { - type Impl = DummySelectorImpl; - - fn accepts_state_pseudo_classes(&self) -> bool { - true - } - - fn valid_after_slotted(&self) -> bool { - true - } + fn valid_after_slotted(&self) -> bool { + true } + } - impl<'i> parser::NonTSPseudoClass<'i> for PseudoClass { - type Impl = DummySelectorImpl; + impl<'i> parser::NonTSPseudoClass<'i> for PseudoClass { + type Impl = DummySelectorImpl; - #[inline] - fn is_active_or_hover(&self) -> bool { - matches!(*self, PseudoClass::Active | PseudoClass::Hover) - } - - #[inline] - fn is_user_action_state(&self) -> bool { - self.is_active_or_hover() - } + #[inline] + fn is_active_or_hover(&self) -> bool { + matches!(*self, PseudoClass::Active | PseudoClass::Hover) } - impl ToCss for PseudoClass { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - match *self { - PseudoClass::Hover => dest.write_str(":hover"), - PseudoClass::Active => dest.write_str(":active"), - PseudoClass::Lang(ref lang) => { - dest.write_str(":lang(")?; - serialize_identifier(lang, dest)?; - dest.write_char(')') - }, - } - } + #[inline] + fn is_user_action_state(&self) -> bool { + self.is_active_or_hover() } + } - impl ToCss for PseudoElement { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - match *self { - PseudoElement::Before => dest.write_str("::before"), - PseudoElement::After => dest.write_str("::after"), - } + impl ToCss for PseudoClass { + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + match *self { + PseudoClass::Hover => dest.write_str(":hover"), + PseudoClass::Active => dest.write_str(":active"), + PseudoClass::Lang(ref lang) => { + dest.write_str(":lang(")?; + serialize_identifier(lang, dest)?; + dest.write_char(')') } + } } + } - #[derive(Clone, Debug, PartialEq)] - pub struct DummySelectorImpl; + impl ToCss for PseudoElement { + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + match *self { + PseudoElement::Before => dest.write_str("::before"), + PseudoElement::After => dest.write_str("::after"), + } + } + } + + #[derive(Clone, Debug, PartialEq)] + pub struct DummySelectorImpl; + + #[derive(Default)] + pub struct DummyParser { + default_ns: Option, + ns_prefixes: HashMap, + } + + impl DummyParser { + fn default_with_namespace(default_ns: DummyAtom) -> DummyParser { + DummyParser { + default_ns: Some(default_ns), + ns_prefixes: Default::default(), + } + } + } + + impl<'i> SelectorImpl<'i> for DummySelectorImpl { + type ExtraMatchingData = (); + type AttrValue = DummyAttrValue; + type Identifier = DummyAtom; + type LocalName = DummyAtom; + type NamespaceUrl = DummyAtom; + type NamespacePrefix = DummyAtom; + type BorrowedLocalName = DummyAtom; + type BorrowedNamespaceUrl = DummyAtom; + type NonTSPseudoClass = PseudoClass; + type PseudoElement = PseudoElement; + } + + #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] + pub struct DummyAttrValue(String); + + impl ToCss for DummyAttrValue { + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + use std::fmt::Write; - #[derive(Default)] - pub struct DummyParser { - default_ns: Option, - ns_prefixes: HashMap, + write!(cssparser::CssStringWriter::new(dest), "{}", &self.0) } + } - impl DummyParser { - fn default_with_namespace(default_ns: DummyAtom) -> DummyParser { - DummyParser { - default_ns: Some(default_ns), - ns_prefixes: Default::default(), - } - } + impl<'a> From<&'a str> for DummyAttrValue { + fn from(string: &'a str) -> Self { + Self(string.into()) } + } - impl<'i> SelectorImpl<'i> for DummySelectorImpl { - type ExtraMatchingData = (); - type AttrValue = DummyAttrValue; - type Identifier = DummyAtom; - type LocalName = DummyAtom; - type NamespaceUrl = DummyAtom; - type NamespacePrefix = DummyAtom; - type BorrowedLocalName = DummyAtom; - type BorrowedNamespaceUrl = DummyAtom; - type NonTSPseudoClass = PseudoClass; - type PseudoElement = PseudoElement; + impl<'a> From> for DummyAttrValue { + fn from(string: CowRcStr<'a>) -> Self { + Self(string.to_string()) } + } - #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] - pub struct DummyAttrValue(String); - - impl ToCss for DummyAttrValue { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - use std::fmt::Write; + #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] + pub struct DummyAtom(String); - write!(cssparser::CssStringWriter::new(dest), "{}", &self.0) - } + impl ToCss for DummyAtom { + fn to_css(&self, dest: &mut W) -> fmt::Result + where + W: fmt::Write, + { + serialize_identifier(&self.0, dest) } + } - impl<'a> From<&'a str> for DummyAttrValue { - fn from(string: &'a str) -> Self { - Self(string.into()) - } + impl From for DummyAtom { + fn from(string: String) -> Self { + DummyAtom(string) } + } - impl<'a> From> for DummyAttrValue { - fn from(string: CowRcStr<'a>) -> Self { - Self(string.to_string()) - } + impl<'a> From<&'a str> for DummyAtom { + fn from(string: &'a str) -> Self { + DummyAtom(string.into()) } + } - #[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] - pub struct DummyAtom(String); - - impl ToCss for DummyAtom { - fn to_css(&self, dest: &mut W) -> fmt::Result - where - W: fmt::Write, - { - serialize_identifier(&self.0, dest) - } + impl<'a> From> for DummyAtom { + fn from(string: CowRcStr<'a>) -> Self { + DummyAtom(string.to_string()) } + } - impl From for DummyAtom { - fn from(string: String) -> Self { - DummyAtom(string) - } - } + impl<'i> Parser<'i> for DummyParser { + type Impl = DummySelectorImpl; + type Error = SelectorParseErrorKind<'i>; - impl<'a> From<&'a str> for DummyAtom { - fn from(string: &'a str) -> Self { - DummyAtom(string.into()) - } + fn parse_slotted(&self) -> bool { + true } - impl<'a> From> for DummyAtom { - fn from(string: CowRcStr<'a>) -> Self { - DummyAtom(string.to_string()) - } + fn parse_is_and_where(&self) -> bool { + true } - impl<'i> Parser<'i> for DummyParser { - type Impl = DummySelectorImpl; - type Error = SelectorParseErrorKind<'i>; - - fn parse_slotted(&self) -> bool { - true - } - - fn parse_is_and_where(&self) -> bool { - true - } - - fn is_and_where_error_recovery(&self) -> ParseErrorRecovery { - ParseErrorRecovery::DiscardList - } - - fn parse_part(&self) -> bool { - true - } - - fn parse_non_ts_pseudo_class( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result> { - match_ignore_ascii_case! { &name, - "hover" => return Ok(PseudoClass::Hover), - "active" => return Ok(PseudoClass::Active), - _ => {} - } - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_non_ts_functional_pseudo_class<'t>( - &self, - name: CowRcStr<'i>, - parser: &mut CssParser<'i, 't>, - ) -> Result> { - match_ignore_ascii_case! { &name, - "lang" => { - let lang = parser.expect_ident_or_string()?.as_ref().to_owned(); - return Ok(PseudoClass::Lang(lang)); - }, - _ => {} - } - Err( - parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn parse_pseudo_element( - &self, - location: SourceLocation, - name: CowRcStr<'i>, - ) -> Result> { - match_ignore_ascii_case! { &name, - "before" => return Ok(PseudoElement::Before), - "after" => return Ok(PseudoElement::After), - _ => {} - } - Err( - location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement( - name, - )), - ) - } - - fn default_namespace(&self) -> Option { - self.default_ns.clone() - } - - fn namespace_for_prefix(&self, prefix: &DummyAtom) -> Option { - self.ns_prefixes.get(prefix).cloned() - } + fn is_and_where_error_recovery(&self) -> ParseErrorRecovery { + ParseErrorRecovery::DiscardList } - fn parse<'i>( - input: &'i str, - ) -> Result, SelectorParseError<'i>> { - parse_ns(input, &DummyParser::default()) - } - - fn parse_expected<'i, 'a>( - input: &'i str, - expected: Option<&'a str>, - ) -> Result, SelectorParseError<'i>> { - parse_ns_expected(input, &DummyParser::default(), expected) - } - - fn parse_ns<'i>( - input: &'i str, - parser: &DummyParser, - ) -> Result, SelectorParseError<'i>> { - parse_ns_expected(input, parser, None) - } - - fn parse_ns_expected<'i, 'a>( - input: &'i str, - parser: &DummyParser, - expected: Option<&'a str>, - ) -> Result, SelectorParseError<'i>> { - let mut parser_input = ParserInput::new(input); - let result = SelectorList::parse(parser, &mut CssParser::new(&mut parser_input), NestingRequirement::None); - if let Ok(ref selectors) = result { - assert_eq!(selectors.0.len(), 1); - // We can't assume that the serialized parsed selector will equal - // the input; for example, if there is no default namespace, '*|foo' - // should serialize to 'foo'. - assert_eq!( - selectors.0[0].to_css_string(), - match expected { - Some(x) => x, - None => input, - } - ); - } - result + fn parse_part(&self) -> bool { + true } - fn specificity(a: u32, b: u32, c: u32) -> u32 { - a << 20 | b << 10 | c - } - - #[test] - fn test_empty() { - let mut input = ParserInput::new(":empty"); - let list = SelectorList::parse(&DummyParser::default(), &mut CssParser::new(&mut input), NestingRequirement::None); - assert!(list.is_ok()); - } - - const MATHML: &str = "http://www.w3.org/1998/Math/MathML"; - const SVG: &str = "http://www.w3.org/2000/svg"; - - #[test] - fn test_parsing() { - assert!(parse("").is_err()); - assert!(parse(":lang(4)").is_err()); - assert!(parse(":lang(en US)").is_err()); - assert_eq!( - parse("EeÉ"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::LocalName(LocalName { - name: DummyAtom::from("EeÉ"), - lower_name: DummyAtom::from("eeÉ"), - })], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse("|e"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitNoNamespace, - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - // When the default namespace is not set, *| should be elided. - // https://github.com/servo/servo/pull/17537 - assert_eq!( - parse_expected("*|e", Some("e")), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - })], - specificity(0, 0, 1), - Default::default(), - )])) - ); - // When the default namespace is set, *| should _not_ be elided (as foo - // is no longer equivalent to *|foo--the former is only for foo in the - // default namespace). - // https://github.com/servo/servo/issues/16020 - assert_eq!( - parse_ns( - "*|e", - &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")) - ), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitAnyNamespace, - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse("*"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::ExplicitUniversalType], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse("|*"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitNoNamespace, - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_expected("*|*", Some("*")), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::ExplicitUniversalType], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns( - "*|*", - &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")) - ), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitAnyNamespace, - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse(".foo:lang(en-US)"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Class(DummyAtom::from("foo")), - Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())), - ], - specificity(0, 2, 0), - Default::default(), - )])) - ); - assert_eq!( - parse("#bar"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::ID(DummyAtom::from("bar"))], - specificity(1, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse("e.foo#bar"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - Component::Class(DummyAtom::from("foo")), - Component::ID(DummyAtom::from("bar")), - ], - specificity(1, 1, 1), - Default::default(), - )])) - ); - assert_eq!( - parse("e.foo #bar"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - Component::Class(DummyAtom::from("foo")), - Component::Combinator(Combinator::Descendant), - Component::ID(DummyAtom::from("bar")), - ], - specificity(1, 1, 1), - Default::default(), - )])) - ); - // Default namespace does not apply to attribute selectors - // https://github.com/mozilla/servo/pull/1652 - let mut parser = DummyParser::default(); - assert_eq!( - parse_ns("[Foo]", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::AttributeInNoNamespaceExists { - local_name: DummyAtom::from("Foo"), - local_name_lower: DummyAtom::from("foo"), - }], - specificity(0, 1, 0), - Default::default(), - )])) - ); - assert!(parse_ns("svg|circle", &parser).is_err()); - parser - .ns_prefixes - .insert(DummyAtom("svg".into()), DummyAtom(SVG.into())); - assert_eq!( - parse_ns("svg|circle", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Namespace(DummyAtom("svg".into()), SVG.into()), - Component::LocalName(LocalName { - name: DummyAtom::from("circle"), - lower_name: DummyAtom::from("circle"), - }), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse_ns("svg|*", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Namespace(DummyAtom("svg".into()), SVG.into()), - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - // Default namespace does not apply to attribute selectors - // https://github.com/mozilla/servo/pull/1652 - // but it does apply to implicit type selectors - // https://github.com/servo/rust-selectors/pull/82 - parser.default_ns = Some(MATHML.into()); - assert_eq!( - parse_ns("[Foo]", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::AttributeInNoNamespaceExists { - local_name: DummyAtom::from("Foo"), - local_name_lower: DummyAtom::from("foo"), - }, - ], - specificity(0, 1, 0), - Default::default(), - )])) - ); - // Default namespace does apply to type selectors - assert_eq!( - parse_ns("e", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse_ns("*", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns("*|*", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ExplicitAnyNamespace, - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - // Default namespace applies to universal and type selectors inside :not and :matches, - // but not otherwise. - assert_eq!( - parse_ns(":not(.cl)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::Negation( - vec![Selector::from_vec( - vec![Component::Class(DummyAtom::from("cl"))], - specificity(0, 1, 0), - Default::default(), - )] - .into_boxed_slice() - ), - ], - specificity(0, 1, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns(":not(*)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::Negation( - vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )] - .into_boxed_slice(), - ), - ], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns(":not(e)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::Negation( - vec![Selector::from_vec( - vec![ - Component::DefaultNamespace(MATHML.into()), - Component::LocalName(LocalName { - name: DummyAtom::from("e"), - lower_name: DummyAtom::from("e"), - }), - ], - specificity(0, 0, 1), - Default::default(), - ),] - .into_boxed_slice() - ), - ], - specificity(0, 0, 1), - Default::default(), - )])) - ); - assert_eq!( - parse("[attr|=\"foo\"]"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::AttributeInNoNamespace { - local_name: DummyAtom::from("attr"), - operator: AttrSelectorOperator::DashMatch, - value: DummyAttrValue::from("foo"), - never_matches: false, - case_sensitivity: ParsedCaseSensitivity::CaseSensitive, - }], - specificity(0, 1, 0), - Default::default(), - )])) - ); - // https://github.com/mozilla/servo/issues/1723 - assert_eq!( - parse("::before"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Combinator(Combinator::PseudoElement), - Component::PseudoElement(PseudoElement::Before), - ], - specificity(0, 0, 1), - SelectorFlags::HAS_PSEUDO, - )])) - ); - assert_eq!( - parse("::before:hover"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Combinator(Combinator::PseudoElement), - Component::PseudoElement(PseudoElement::Before), - Component::NonTSPseudoClass(PseudoClass::Hover), - ], - specificity(0, 1, 1), - SelectorFlags::HAS_PSEUDO, - )])) - ); - assert_eq!( - parse("::before:hover:hover"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::Combinator(Combinator::PseudoElement), - Component::PseudoElement(PseudoElement::Before), - Component::NonTSPseudoClass(PseudoClass::Hover), - Component::NonTSPseudoClass(PseudoClass::Hover), - ], - specificity(0, 2, 1), - SelectorFlags::HAS_PSEUDO, - )])) - ); - assert!(parse("::before:hover:lang(foo)").is_err()); - assert!(parse("::before:hover .foo").is_err()); - assert!(parse("::before .foo").is_err()); - assert!(parse("::before ~ bar").is_err()); - assert!(parse("::before:active").is_ok()); - - // https://github.com/servo/servo/issues/15335 - assert!(parse(":: before").is_err()); - assert_eq!( - parse("div ::after"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::LocalName(LocalName { - name: DummyAtom::from("div"), - lower_name: DummyAtom::from("div"), - }), - Component::Combinator(Combinator::Descendant), - Component::Combinator(Combinator::PseudoElement), - Component::PseudoElement(PseudoElement::After), - ], - specificity(0, 0, 2), - SelectorFlags::HAS_PSEUDO, - )])) - ); - assert_eq!( - parse("#d1 > .ok"), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![ - Component::ID(DummyAtom::from("d1")), - Component::Combinator(Combinator::Child), - Component::Class(DummyAtom::from("ok")), - ], - (1 << 20) + (1 << 10) + (0 << 0), - Default::default(), - )])) - ); - parser.default_ns = None; - assert!(parse(":not(#provel.old)").is_ok()); - assert!(parse(":not(#provel > old)").is_ok()); - assert!(parse("table[rules]:not([rules=\"none\"]):not([rules=\"\"])").is_ok()); - // https://github.com/servo/servo/issues/16017 - assert_eq!( - parse_ns(":not(*)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::Negation( - vec![Selector::from_vec( - vec![Component::ExplicitUniversalType], - specificity(0, 0, 0), - Default::default(), - )] - .into_boxed_slice() - )], - specificity(0, 0, 0), - Default::default(), - )])) - ); - assert_eq!( - parse_ns(":not(|*)", &parser), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::Negation( - vec![Selector::from_vec( - vec![ - Component::ExplicitNoNamespace, - Component::ExplicitUniversalType, - ], - specificity(0, 0, 0), - Default::default(), - )] - .into_boxed_slice(), - )], - specificity(0, 0, 0), - Default::default(), - )])) - ); - // *| should be elided if there is no default namespace. - // https://github.com/servo/servo/pull/17537 - assert_eq!( - parse_ns_expected(":not(*|*)", &parser, Some(":not(*)")), - Ok(SelectorList::from_vec(vec![Selector::from_vec( - vec![Component::Negation( - vec![Selector::from_vec( - vec![Component::ExplicitUniversalType], - specificity(0, 0, 0), - Default::default() - )] - .into_boxed_slice() - )], - specificity(0, 0, 0), - Default::default(), - )])) - ); - - assert!(parse("::slotted()").is_err()); - assert!(parse("::slotted(div)").is_ok()); - assert!(parse("::slotted(div).foo").is_err()); - assert!(parse("::slotted(div + bar)").is_err()); - assert!(parse("::slotted(div) + foo").is_err()); - - assert!(parse("::part()").is_err()); - assert!(parse("::part(42)").is_err()); - assert!(parse("::part(foo bar)").is_ok()); - assert!(parse("::part(foo):hover").is_ok()); - assert!(parse("::part(foo) + bar").is_err()); - - assert!(parse("div ::slotted(div)").is_ok()); - assert!(parse("div + slot::slotted(div)").is_ok()); - assert!(parse("div + slot::slotted(div.foo)").is_ok()); - assert!(parse("slot::slotted(div,foo)::first-line").is_err()); - assert!(parse("::slotted(div)::before").is_ok()); - assert!(parse("slot::slotted(div,foo)").is_err()); - - assert!(parse("foo:where()").is_err()); - assert!(parse("foo:where(div, foo, .bar baz)").is_ok()); - assert!(parse("foo:where(::before)").is_err()); - } - - #[test] - fn test_pseudo_iter() { - let selector = &parse("q::before").unwrap().0[0]; - assert!(!selector.is_universal()); - let mut iter = selector.iter(); - assert_eq!( - iter.next(), - Some(&Component::PseudoElement(PseudoElement::Before)) - ); - assert_eq!(iter.next(), None); - let combinator = iter.next_sequence(); - assert_eq!(combinator, Some(Combinator::PseudoElement)); - assert!(matches!(iter.next(), Some(&Component::LocalName(..)))); - assert_eq!(iter.next(), None); - assert_eq!(iter.next_sequence(), None); - } - - #[test] - fn test_universal() { - let selector = &parse_ns( - "*|*::before", - &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")), - ) - .unwrap() - .0[0]; - assert!(selector.is_universal()); - } - - #[test] - fn test_empty_pseudo_iter() { - let selector = &parse("::before").unwrap().0[0]; - assert!(selector.is_universal()); - let mut iter = selector.iter(); - assert_eq!( - iter.next(), - Some(&Component::PseudoElement(PseudoElement::Before)) - ); - assert_eq!(iter.next(), None); - assert_eq!(iter.next_sequence(), Some(Combinator::PseudoElement)); - assert_eq!(iter.next(), None); - assert_eq!(iter.next_sequence(), None); - } - - struct TestVisitor { - seen: Vec, - } - - impl SelectorVisitor for TestVisitor { - type Impl = DummySelectorImpl; - - fn visit_simple_selector(&mut self, s: &Component) -> bool { - let mut dest = String::new(); - s.to_css(&mut dest).unwrap(); - self.seen.push(dest); - true - } + fn parse_non_ts_pseudo_class( + &self, + location: SourceLocation, + name: CowRcStr<'i>, + ) -> Result> { + match_ignore_ascii_case! { &name, + "hover" => return Ok(PseudoClass::Hover), + "active" => return Ok(PseudoClass::Active), + _ => {} + } + Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name))) } - #[test] - fn visitor() { - let mut test_visitor = TestVisitor { seen: vec![] }; - parse(":not(:hover) ~ label").unwrap().0[0].visit(&mut test_visitor); - assert!(test_visitor.seen.contains(&":hover".into())); - - let mut test_visitor = TestVisitor { seen: vec![] }; - parse("::before:hover").unwrap().0[0].visit(&mut test_visitor); - assert!(test_visitor.seen.contains(&":hover".into())); + fn parse_non_ts_functional_pseudo_class<'t>( + &self, + name: CowRcStr<'i>, + parser: &mut CssParser<'i, 't>, + ) -> Result> { + match_ignore_ascii_case! { &name, + "lang" => { + let lang = parser.expect_ident_or_string()?.as_ref().to_owned(); + return Ok(PseudoClass::Lang(lang)); + }, + _ => {} + } + Err(parser.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name))) } + + fn parse_pseudo_element( + &self, + location: SourceLocation, + name: CowRcStr<'i>, + ) -> Result> { + match_ignore_ascii_case! { &name, + "before" => return Ok(PseudoElement::Before), + "after" => return Ok(PseudoElement::After), + _ => {} + } + Err(location.new_custom_error(SelectorParseErrorKind::UnsupportedPseudoClassOrElement(name))) + } + + fn default_namespace(&self) -> Option { + self.default_ns.clone() + } + + fn namespace_for_prefix(&self, prefix: &DummyAtom) -> Option { + self.ns_prefixes.get(prefix).cloned() + } + } + + fn parse<'i>(input: &'i str) -> Result, SelectorParseError<'i>> { + parse_ns(input, &DummyParser::default()) + } + + fn parse_expected<'i, 'a>( + input: &'i str, + expected: Option<&'a str>, + ) -> Result, SelectorParseError<'i>> { + parse_ns_expected(input, &DummyParser::default(), expected) + } + + fn parse_ns<'i>( + input: &'i str, + parser: &DummyParser, + ) -> Result, SelectorParseError<'i>> { + parse_ns_expected(input, parser, None) + } + + fn parse_ns_expected<'i, 'a>( + input: &'i str, + parser: &DummyParser, + expected: Option<&'a str>, + ) -> Result, SelectorParseError<'i>> { + let mut parser_input = ParserInput::new(input); + let result = SelectorList::parse(parser, &mut CssParser::new(&mut parser_input), NestingRequirement::None); + if let Ok(ref selectors) = result { + assert_eq!(selectors.0.len(), 1); + // We can't assume that the serialized parsed selector will equal + // the input; for example, if there is no default namespace, '*|foo' + // should serialize to 'foo'. + assert_eq!( + selectors.0[0].to_css_string(), + match expected { + Some(x) => x, + None => input, + } + ); + } + result + } + + fn specificity(a: u32, b: u32, c: u32) -> u32 { + a << 20 | b << 10 | c + } + + #[test] + fn test_empty() { + let mut input = ParserInput::new(":empty"); + let list = SelectorList::parse( + &DummyParser::default(), + &mut CssParser::new(&mut input), + NestingRequirement::None, + ); + assert!(list.is_ok()); + } + + const MATHML: &str = "http://www.w3.org/1998/Math/MathML"; + const SVG: &str = "http://www.w3.org/2000/svg"; + + #[test] + fn test_parsing() { + assert!(parse("").is_err()); + assert!(parse(":lang(4)").is_err()); + assert!(parse(":lang(en US)").is_err()); + assert_eq!( + parse("EeÉ"), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![Component::LocalName(LocalName { + name: DummyAtom::from("EeÉ"), + lower_name: DummyAtom::from("eeÉ"), + })], + specificity(0, 0, 1), + Default::default(), + )])) + ); + assert_eq!( + parse("|e"), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::ExplicitNoNamespace, + Component::LocalName(LocalName { + name: DummyAtom::from("e"), + lower_name: DummyAtom::from("e"), + }), + ], + specificity(0, 0, 1), + Default::default(), + )])) + ); + // When the default namespace is not set, *| should be elided. + // https://github.com/servo/servo/pull/17537 + assert_eq!( + parse_expected("*|e", Some("e")), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![Component::LocalName(LocalName { + name: DummyAtom::from("e"), + lower_name: DummyAtom::from("e"), + })], + specificity(0, 0, 1), + Default::default(), + )])) + ); + // When the default namespace is set, *| should _not_ be elided (as foo + // is no longer equivalent to *|foo--the former is only for foo in the + // default namespace). + // https://github.com/servo/servo/issues/16020 + assert_eq!( + parse_ns( + "*|e", + &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")) + ), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::ExplicitAnyNamespace, + Component::LocalName(LocalName { + name: DummyAtom::from("e"), + lower_name: DummyAtom::from("e"), + }), + ], + specificity(0, 0, 1), + Default::default(), + )])) + ); + assert_eq!( + parse("*"), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![Component::ExplicitUniversalType], + specificity(0, 0, 0), + Default::default(), + )])) + ); + assert_eq!( + parse("|*"), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![Component::ExplicitNoNamespace, Component::ExplicitUniversalType,], + specificity(0, 0, 0), + Default::default(), + )])) + ); + assert_eq!( + parse_expected("*|*", Some("*")), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![Component::ExplicitUniversalType], + specificity(0, 0, 0), + Default::default(), + )])) + ); + assert_eq!( + parse_ns( + "*|*", + &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")) + ), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![Component::ExplicitAnyNamespace, Component::ExplicitUniversalType,], + specificity(0, 0, 0), + Default::default(), + )])) + ); + assert_eq!( + parse(".foo:lang(en-US)"), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::Class(DummyAtom::from("foo")), + Component::NonTSPseudoClass(PseudoClass::Lang("en-US".to_owned())), + ], + specificity(0, 2, 0), + Default::default(), + )])) + ); + assert_eq!( + parse("#bar"), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![Component::ID(DummyAtom::from("bar"))], + specificity(1, 0, 0), + Default::default(), + )])) + ); + assert_eq!( + parse("e.foo#bar"), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::LocalName(LocalName { + name: DummyAtom::from("e"), + lower_name: DummyAtom::from("e"), + }), + Component::Class(DummyAtom::from("foo")), + Component::ID(DummyAtom::from("bar")), + ], + specificity(1, 1, 1), + Default::default(), + )])) + ); + assert_eq!( + parse("e.foo #bar"), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::LocalName(LocalName { + name: DummyAtom::from("e"), + lower_name: DummyAtom::from("e"), + }), + Component::Class(DummyAtom::from("foo")), + Component::Combinator(Combinator::Descendant), + Component::ID(DummyAtom::from("bar")), + ], + specificity(1, 1, 1), + Default::default(), + )])) + ); + // Default namespace does not apply to attribute selectors + // https://github.com/mozilla/servo/pull/1652 + let mut parser = DummyParser::default(); + assert_eq!( + parse_ns("[Foo]", &parser), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![Component::AttributeInNoNamespaceExists { + local_name: DummyAtom::from("Foo"), + local_name_lower: DummyAtom::from("foo"), + }], + specificity(0, 1, 0), + Default::default(), + )])) + ); + assert!(parse_ns("svg|circle", &parser).is_err()); + parser.ns_prefixes.insert(DummyAtom("svg".into()), DummyAtom(SVG.into())); + assert_eq!( + parse_ns("svg|circle", &parser), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::Namespace(DummyAtom("svg".into()), SVG.into()), + Component::LocalName(LocalName { + name: DummyAtom::from("circle"), + lower_name: DummyAtom::from("circle"), + }), + ], + specificity(0, 0, 1), + Default::default(), + )])) + ); + assert_eq!( + parse_ns("svg|*", &parser), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::Namespace(DummyAtom("svg".into()), SVG.into()), + Component::ExplicitUniversalType, + ], + specificity(0, 0, 0), + Default::default(), + )])) + ); + // Default namespace does not apply to attribute selectors + // https://github.com/mozilla/servo/pull/1652 + // but it does apply to implicit type selectors + // https://github.com/servo/rust-selectors/pull/82 + parser.default_ns = Some(MATHML.into()); + assert_eq!( + parse_ns("[Foo]", &parser), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::DefaultNamespace(MATHML.into()), + Component::AttributeInNoNamespaceExists { + local_name: DummyAtom::from("Foo"), + local_name_lower: DummyAtom::from("foo"), + }, + ], + specificity(0, 1, 0), + Default::default(), + )])) + ); + // Default namespace does apply to type selectors + assert_eq!( + parse_ns("e", &parser), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::DefaultNamespace(MATHML.into()), + Component::LocalName(LocalName { + name: DummyAtom::from("e"), + lower_name: DummyAtom::from("e"), + }), + ], + specificity(0, 0, 1), + Default::default(), + )])) + ); + assert_eq!( + parse_ns("*", &parser), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::DefaultNamespace(MATHML.into()), + Component::ExplicitUniversalType, + ], + specificity(0, 0, 0), + Default::default(), + )])) + ); + assert_eq!( + parse_ns("*|*", &parser), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![Component::ExplicitAnyNamespace, Component::ExplicitUniversalType,], + specificity(0, 0, 0), + Default::default(), + )])) + ); + // Default namespace applies to universal and type selectors inside :not and :matches, + // but not otherwise. + assert_eq!( + parse_ns(":not(.cl)", &parser), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::DefaultNamespace(MATHML.into()), + Component::Negation( + vec![Selector::from_vec( + vec![Component::Class(DummyAtom::from("cl"))], + specificity(0, 1, 0), + Default::default(), + )] + .into_boxed_slice() + ), + ], + specificity(0, 1, 0), + Default::default(), + )])) + ); + assert_eq!( + parse_ns(":not(*)", &parser), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::DefaultNamespace(MATHML.into()), + Component::Negation( + vec![Selector::from_vec( + vec![ + Component::DefaultNamespace(MATHML.into()), + Component::ExplicitUniversalType, + ], + specificity(0, 0, 0), + Default::default(), + )] + .into_boxed_slice(), + ), + ], + specificity(0, 0, 0), + Default::default(), + )])) + ); + assert_eq!( + parse_ns(":not(e)", &parser), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::DefaultNamespace(MATHML.into()), + Component::Negation( + vec![Selector::from_vec( + vec![ + Component::DefaultNamespace(MATHML.into()), + Component::LocalName(LocalName { + name: DummyAtom::from("e"), + lower_name: DummyAtom::from("e"), + }), + ], + specificity(0, 0, 1), + Default::default(), + ),] + .into_boxed_slice() + ), + ], + specificity(0, 0, 1), + Default::default(), + )])) + ); + assert_eq!( + parse("[attr|=\"foo\"]"), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![Component::AttributeInNoNamespace { + local_name: DummyAtom::from("attr"), + operator: AttrSelectorOperator::DashMatch, + value: DummyAttrValue::from("foo"), + never_matches: false, + case_sensitivity: ParsedCaseSensitivity::CaseSensitive, + }], + specificity(0, 1, 0), + Default::default(), + )])) + ); + // https://github.com/mozilla/servo/issues/1723 + assert_eq!( + parse("::before"), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::Combinator(Combinator::PseudoElement), + Component::PseudoElement(PseudoElement::Before), + ], + specificity(0, 0, 1), + SelectorFlags::HAS_PSEUDO, + )])) + ); + assert_eq!( + parse("::before:hover"), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::Combinator(Combinator::PseudoElement), + Component::PseudoElement(PseudoElement::Before), + Component::NonTSPseudoClass(PseudoClass::Hover), + ], + specificity(0, 1, 1), + SelectorFlags::HAS_PSEUDO, + )])) + ); + assert_eq!( + parse("::before:hover:hover"), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::Combinator(Combinator::PseudoElement), + Component::PseudoElement(PseudoElement::Before), + Component::NonTSPseudoClass(PseudoClass::Hover), + Component::NonTSPseudoClass(PseudoClass::Hover), + ], + specificity(0, 2, 1), + SelectorFlags::HAS_PSEUDO, + )])) + ); + assert!(parse("::before:hover:lang(foo)").is_err()); + assert!(parse("::before:hover .foo").is_err()); + assert!(parse("::before .foo").is_err()); + assert!(parse("::before ~ bar").is_err()); + assert!(parse("::before:active").is_ok()); + + // https://github.com/servo/servo/issues/15335 + assert!(parse(":: before").is_err()); + assert_eq!( + parse("div ::after"), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::LocalName(LocalName { + name: DummyAtom::from("div"), + lower_name: DummyAtom::from("div"), + }), + Component::Combinator(Combinator::Descendant), + Component::Combinator(Combinator::PseudoElement), + Component::PseudoElement(PseudoElement::After), + ], + specificity(0, 0, 2), + SelectorFlags::HAS_PSEUDO, + )])) + ); + assert_eq!( + parse("#d1 > .ok"), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![ + Component::ID(DummyAtom::from("d1")), + Component::Combinator(Combinator::Child), + Component::Class(DummyAtom::from("ok")), + ], + (1 << 20) + (1 << 10) + (0 << 0), + Default::default(), + )])) + ); + parser.default_ns = None; + assert!(parse(":not(#provel.old)").is_ok()); + assert!(parse(":not(#provel > old)").is_ok()); + assert!(parse("table[rules]:not([rules=\"none\"]):not([rules=\"\"])").is_ok()); + // https://github.com/servo/servo/issues/16017 + assert_eq!( + parse_ns(":not(*)", &parser), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![Component::Negation( + vec![Selector::from_vec( + vec![Component::ExplicitUniversalType], + specificity(0, 0, 0), + Default::default(), + )] + .into_boxed_slice() + )], + specificity(0, 0, 0), + Default::default(), + )])) + ); + assert_eq!( + parse_ns(":not(|*)", &parser), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![Component::Negation( + vec![Selector::from_vec( + vec![Component::ExplicitNoNamespace, Component::ExplicitUniversalType,], + specificity(0, 0, 0), + Default::default(), + )] + .into_boxed_slice(), + )], + specificity(0, 0, 0), + Default::default(), + )])) + ); + // *| should be elided if there is no default namespace. + // https://github.com/servo/servo/pull/17537 + assert_eq!( + parse_ns_expected(":not(*|*)", &parser, Some(":not(*)")), + Ok(SelectorList::from_vec(vec![Selector::from_vec( + vec![Component::Negation( + vec![Selector::from_vec( + vec![Component::ExplicitUniversalType], + specificity(0, 0, 0), + Default::default() + )] + .into_boxed_slice() + )], + specificity(0, 0, 0), + Default::default(), + )])) + ); + + assert!(parse("::slotted()").is_err()); + assert!(parse("::slotted(div)").is_ok()); + assert!(parse("::slotted(div).foo").is_err()); + assert!(parse("::slotted(div + bar)").is_err()); + assert!(parse("::slotted(div) + foo").is_err()); + + assert!(parse("::part()").is_err()); + assert!(parse("::part(42)").is_err()); + assert!(parse("::part(foo bar)").is_ok()); + assert!(parse("::part(foo):hover").is_ok()); + assert!(parse("::part(foo) + bar").is_err()); + + assert!(parse("div ::slotted(div)").is_ok()); + assert!(parse("div + slot::slotted(div)").is_ok()); + assert!(parse("div + slot::slotted(div.foo)").is_ok()); + assert!(parse("slot::slotted(div,foo)::first-line").is_err()); + assert!(parse("::slotted(div)::before").is_ok()); + assert!(parse("slot::slotted(div,foo)").is_err()); + + assert!(parse("foo:where()").is_err()); + assert!(parse("foo:where(div, foo, .bar baz)").is_ok()); + assert!(parse("foo:where(::before)").is_err()); + } + + #[test] + fn test_pseudo_iter() { + let selector = &parse("q::before").unwrap().0[0]; + assert!(!selector.is_universal()); + let mut iter = selector.iter(); + assert_eq!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before))); + assert_eq!(iter.next(), None); + let combinator = iter.next_sequence(); + assert_eq!(combinator, Some(Combinator::PseudoElement)); + assert!(matches!(iter.next(), Some(&Component::LocalName(..)))); + assert_eq!(iter.next(), None); + assert_eq!(iter.next_sequence(), None); + } + + #[test] + fn test_universal() { + let selector = &parse_ns( + "*|*::before", + &DummyParser::default_with_namespace(DummyAtom::from("https://mozilla.org")), + ) + .unwrap() + .0[0]; + assert!(selector.is_universal()); + } + + #[test] + fn test_empty_pseudo_iter() { + let selector = &parse("::before").unwrap().0[0]; + assert!(selector.is_universal()); + let mut iter = selector.iter(); + assert_eq!(iter.next(), Some(&Component::PseudoElement(PseudoElement::Before))); + assert_eq!(iter.next(), None); + assert_eq!(iter.next_sequence(), Some(Combinator::PseudoElement)); + assert_eq!(iter.next(), None); + assert_eq!(iter.next_sequence(), None); + } + + struct TestVisitor { + seen: Vec, + } + + impl SelectorVisitor for TestVisitor { + type Impl = DummySelectorImpl; + + fn visit_simple_selector(&mut self, s: &Component) -> bool { + let mut dest = String::new(); + s.to_css(&mut dest).unwrap(); + self.seen.push(dest); + true + } + } + + #[test] + fn visitor() { + let mut test_visitor = TestVisitor { seen: vec![] }; + parse(":not(:hover) ~ label").unwrap().0[0].visit(&mut test_visitor); + assert!(test_visitor.seen.contains(&":hover".into())); + + let mut test_visitor = TestVisitor { seen: vec![] }; + parse("::before:hover").unwrap().0[0].visit(&mut test_visitor); + assert!(test_visitor.seen.contains(&":hover".into())); + } } diff --git a/selectors/sink.rs b/selectors/sink.rs index dcdd7ff2..e8701c89 100644 --- a/selectors/sink.rs +++ b/selectors/sink.rs @@ -14,18 +14,18 @@ use smallvec::{Array, SmallVec}; /// type which `push` method does only tweak a byte when we only need to check /// for the presence of something. pub trait Push { - /// Push a value into self. - fn push(&mut self, value: T); + /// Push a value into self. + fn push(&mut self, value: T); } impl Push for Vec { - fn push(&mut self, value: T) { - Vec::push(self, value); - } + fn push(&mut self, value: T) { + Vec::push(self, value); + } } impl Push for SmallVec { - fn push(&mut self, value: A::Item) { - SmallVec::push(self, value); - } + fn push(&mut self, value: A::Item) { + SmallVec::push(self, value); + } } diff --git a/selectors/tree.rs b/selectors/tree.rs index 22ef3201..a7d2d7d8 100644 --- a/selectors/tree.rs +++ b/selectors/tree.rs @@ -18,130 +18,122 @@ pub struct OpaqueElement(NonNull<()>); unsafe impl Send for OpaqueElement {} impl OpaqueElement { - /// Creates a new OpaqueElement from an arbitrarily-typed pointer. - pub fn new(ptr: &T) -> Self { - unsafe { - OpaqueElement(NonNull::new_unchecked( - ptr as *const T as *const () as *mut (), - )) - } - } + /// Creates a new OpaqueElement from an arbitrarily-typed pointer. + pub fn new(ptr: &T) -> Self { + unsafe { OpaqueElement(NonNull::new_unchecked(ptr as *const T as *const () as *mut ())) } + } } pub trait Element<'i>: Sized + Clone + Debug { - type Impl: SelectorImpl<'i>; - - /// Converts self into an opaque representation. - fn opaque(&self) -> OpaqueElement; - - fn parent_element(&self) -> Option; - - /// Whether the parent node of this element is a shadow root. - fn parent_node_is_shadow_root(&self) -> bool; - - /// The host of the containing shadow root, if any. - fn containing_shadow_host(&self) -> Option; - - /// The parent of a given pseudo-element, after matching a pseudo-element - /// selector. - /// - /// This is guaranteed to be called in a pseudo-element. - fn pseudo_element_originating_element(&self) -> Option { - debug_assert!(self.is_pseudo_element()); - self.parent_element() - } - - /// Whether we're matching on a pseudo-element. - fn is_pseudo_element(&self) -> bool; - - /// Skips non-element nodes - fn prev_sibling_element(&self) -> Option; - - /// Skips non-element nodes - fn next_sibling_element(&self) -> Option; - - fn is_html_element_in_html_document(&self) -> bool; - - fn has_local_name(&self, local_name: &>::BorrowedLocalName) -> bool; - - /// Empty string for no namespace - fn has_namespace(&self, ns: &>::BorrowedNamespaceUrl) -> bool; - - /// Whether this element and the `other` element have the same local name and namespace. - fn is_same_type(&self, other: &Self) -> bool; - - fn attr_matches( - &self, - ns: &NamespaceConstraint<&>::NamespaceUrl>, - local_name: &>::LocalName, - operation: &AttrSelectorOperation<&>::AttrValue>, - ) -> bool; - - fn match_non_ts_pseudo_class( - &self, - pc: &>::NonTSPseudoClass, - context: &mut MatchingContext<'_, 'i, Self::Impl>, - flags_setter: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags); - - fn match_pseudo_element( - &self, - pe: &>::PseudoElement, - context: &mut MatchingContext<'_, 'i, Self::Impl>, - ) -> bool; - - /// Whether this element is a `link`. - fn is_link(&self) -> bool; - - /// Returns whether the element is an HTML element. - fn is_html_slot_element(&self) -> bool; - - /// Returns the assigned element this element is assigned to. - /// - /// Necessary for the `::slotted` pseudo-class. - fn assigned_slot(&self) -> Option { - None - } - - fn has_id( - &self, - id: &>::Identifier, - case_sensitivity: CaseSensitivity, - ) -> bool; - - fn has_class( - &self, - name: &>::Identifier, - case_sensitivity: CaseSensitivity, - ) -> bool; - - /// Returns the mapping from the `exportparts` attribute in the reverse - /// direction, that is, in an outer-tree -> inner-tree direction. - fn imported_part( - &self, - name: &>::Identifier, - ) -> Option<>::Identifier>; - - fn is_part(&self, name: &>::Identifier) -> bool; - - /// Returns whether this element matches `:empty`. - /// - /// That is, whether it does not contain any child element or any non-zero-length text node. - /// See http://dev.w3.org/csswg/selectors-3/#empty-pseudo - fn is_empty(&self) -> bool; - - /// Returns whether this element matches `:root`, - /// i.e. whether it is the root element of a document. - /// - /// Note: this can be false even if `.parent_element()` is `None` - /// if the parent node is a `DocumentFragment`. - fn is_root(&self) -> bool; - - /// Returns whether this element should ignore matching nth child - /// selector. - fn ignores_nth_child_selectors(&self) -> bool { - false - } + type Impl: SelectorImpl<'i>; + + /// Converts self into an opaque representation. + fn opaque(&self) -> OpaqueElement; + + fn parent_element(&self) -> Option; + + /// Whether the parent node of this element is a shadow root. + fn parent_node_is_shadow_root(&self) -> bool; + + /// The host of the containing shadow root, if any. + fn containing_shadow_host(&self) -> Option; + + /// The parent of a given pseudo-element, after matching a pseudo-element + /// selector. + /// + /// This is guaranteed to be called in a pseudo-element. + fn pseudo_element_originating_element(&self) -> Option { + debug_assert!(self.is_pseudo_element()); + self.parent_element() + } + + /// Whether we're matching on a pseudo-element. + fn is_pseudo_element(&self) -> bool; + + /// Skips non-element nodes + fn prev_sibling_element(&self) -> Option; + + /// Skips non-element nodes + fn next_sibling_element(&self) -> Option; + + fn is_html_element_in_html_document(&self) -> bool; + + fn has_local_name(&self, local_name: &>::BorrowedLocalName) -> bool; + + /// Empty string for no namespace + fn has_namespace(&self, ns: &>::BorrowedNamespaceUrl) -> bool; + + /// Whether this element and the `other` element have the same local name and namespace. + fn is_same_type(&self, other: &Self) -> bool; + + fn attr_matches( + &self, + ns: &NamespaceConstraint<&>::NamespaceUrl>, + local_name: &>::LocalName, + operation: &AttrSelectorOperation<&>::AttrValue>, + ) -> bool; + + fn match_non_ts_pseudo_class( + &self, + pc: &>::NonTSPseudoClass, + context: &mut MatchingContext<'_, 'i, Self::Impl>, + flags_setter: &mut F, + ) -> bool + where + F: FnMut(&Self, ElementSelectorFlags); + + fn match_pseudo_element( + &self, + pe: &>::PseudoElement, + context: &mut MatchingContext<'_, 'i, Self::Impl>, + ) -> bool; + + /// Whether this element is a `link`. + fn is_link(&self) -> bool; + + /// Returns whether the element is an HTML element. + fn is_html_slot_element(&self) -> bool; + + /// Returns the assigned element this element is assigned to. + /// + /// Necessary for the `::slotted` pseudo-class. + fn assigned_slot(&self) -> Option { + None + } + + fn has_id(&self, id: &>::Identifier, case_sensitivity: CaseSensitivity) -> bool; + + fn has_class( + &self, + name: &>::Identifier, + case_sensitivity: CaseSensitivity, + ) -> bool; + + /// Returns the mapping from the `exportparts` attribute in the reverse + /// direction, that is, in an outer-tree -> inner-tree direction. + fn imported_part( + &self, + name: &>::Identifier, + ) -> Option<>::Identifier>; + + fn is_part(&self, name: &>::Identifier) -> bool; + + /// Returns whether this element matches `:empty`. + /// + /// That is, whether it does not contain any child element or any non-zero-length text node. + /// See http://dev.w3.org/csswg/selectors-3/#empty-pseudo + fn is_empty(&self) -> bool; + + /// Returns whether this element matches `:root`, + /// i.e. whether it is the root element of a document. + /// + /// Note: this can be false even if `.parent_element()` is `None` + /// if the parent node is a `DocumentFragment`. + fn is_root(&self) -> bool; + + /// Returns whether this element should ignore matching nth child + /// selector. + fn ignores_nth_child_selectors(&self) -> bool { + false + } } diff --git a/selectors/visitor.rs b/selectors/visitor.rs index 1ba6306b..3ed03efe 100644 --- a/selectors/visitor.rs +++ b/selectors/visitor.rs @@ -14,44 +14,44 @@ use crate::parser::{Combinator, Component, Selector, SelectorImpl}; /// All the `visit_foo` methods return a boolean indicating whether the /// traversal should continue or not. pub trait SelectorVisitor<'i>: Sized { - /// The selector implementation this visitor wants to visit. - type Impl: SelectorImpl<'i>; - - /// Visit an attribute selector that may match (there are other selectors - /// that may never match, like those containing whitespace or the empty - /// string). - fn visit_attribute_selector( - &mut self, - _namespace: &NamespaceConstraint<&>::NamespaceUrl>, - _local_name: &>::LocalName, - _local_name_lower: &>::LocalName, - ) -> bool { - true - } - - /// Visit a simple selector. - fn visit_simple_selector(&mut self, _: &Component<'i, Self::Impl>) -> bool { - true - } - - /// Visit a nested selector list. The caller is responsible to call visit - /// into the internal selectors if / as needed. - /// - /// The default implementation does this. - fn visit_selector_list(&mut self, list: &[Selector<'i, Self::Impl>]) -> bool { - for nested in list { - if !nested.visit(self) { - return false; - } - } - true - } - - /// Visits a complex selector. - /// - /// Gets the combinator to the right of the selector, or `None` if the - /// selector is the rightmost one. - fn visit_complex_selector(&mut self, _combinator_to_right: Option) -> bool { - true + /// The selector implementation this visitor wants to visit. + type Impl: SelectorImpl<'i>; + + /// Visit an attribute selector that may match (there are other selectors + /// that may never match, like those containing whitespace or the empty + /// string). + fn visit_attribute_selector( + &mut self, + _namespace: &NamespaceConstraint<&>::NamespaceUrl>, + _local_name: &>::LocalName, + _local_name_lower: &>::LocalName, + ) -> bool { + true + } + + /// Visit a simple selector. + fn visit_simple_selector(&mut self, _: &Component<'i, Self::Impl>) -> bool { + true + } + + /// Visit a nested selector list. The caller is responsible to call visit + /// into the internal selectors if / as needed. + /// + /// The default implementation does this. + fn visit_selector_list(&mut self, list: &[Selector<'i, Self::Impl>]) -> bool { + for nested in list { + if !nested.visit(self) { + return false; + } } + true + } + + /// Visits a complex selector. + /// + /// Gets the combinator to the right of the selector, or `None` if the + /// selector is the rightmost one. + fn visit_complex_selector(&mut self, _combinator_to_right: Option) -> bool { + true + } } diff --git a/src/bundler.rs b/src/bundler.rs index 98bc785f..df7c0133 100644 --- a/src/bundler.rs +++ b/src/bundler.rs @@ -1,18 +1,30 @@ -use parcel_sourcemap::SourceMap; -use serde::Serialize; -use crate::{rules::{Location, layer::{LayerBlockRule, LayerName}}, error::ErrorLocation}; -use std::{fs, path::{Path, PathBuf}, sync::Mutex, collections::HashSet}; -use rayon::prelude::*; -use dashmap::DashMap; use crate::{ - stylesheet::{StyleSheet, ParserOptions}, - rules::{CssRule, CssRuleList, - media::MediaRule, - supports::{SupportsRule, SupportsCondition}, - import::ImportRule + error::ErrorLocation, + rules::{ + layer::{LayerBlockRule, LayerName}, + Location, }, +}; +use crate::{ + error::{Error, ParserError}, media_query::MediaList, - error::{Error, ParserError} + rules::{ + import::ImportRule, + media::MediaRule, + supports::{SupportsCondition, SupportsRule}, + CssRule, CssRuleList, + }, + stylesheet::{ParserOptions, StyleSheet}, +}; +use dashmap::DashMap; +use parcel_sourcemap::SourceMap; +use rayon::prelude::*; +use serde::Serialize; +use std::{ + collections::HashSet, + fs, + path::{Path, PathBuf}, + sync::Mutex, }; pub struct Bundler<'a, 's, P> { @@ -20,7 +32,7 @@ pub struct Bundler<'a, 's, P> { fs: &'a P, source_indexes: DashMap, stylesheets: Mutex>>, - options: ParserOptions + options: ParserOptions, } #[derive(Debug)] @@ -32,7 +44,7 @@ struct BundleStyleSheet<'i> { layer: Option>>, supports: Option>, media: MediaList<'i>, - loc: Location + loc: Location, } pub trait SourceProvider: Send + Sync { @@ -40,7 +52,7 @@ pub trait SourceProvider: Send + Sync { } pub struct FileProvider { - inputs: Mutex> + inputs: Mutex>, } impl FileProvider { @@ -80,14 +92,14 @@ pub enum BundleErrorKind<'i> { ParserError(ParserError<'i>), UnsupportedImportCondition, UnsupportedMediaBooleanLogic, - UnsupportedLayerCombination + UnsupportedLayerCombination, } impl<'i> From>> for Error> { fn from(err: Error>) -> Self { Error { kind: BundleErrorKind::ParserError(err.kind), - loc: err.loc + loc: err.loc, } } } @@ -99,7 +111,7 @@ impl<'i> BundleErrorKind<'i> { BundleErrorKind::ParserError(e) => e.reason(), BundleErrorKind::UnsupportedImportCondition => "Unsupported import condition".into(), BundleErrorKind::UnsupportedMediaBooleanLogic => "Unsupported boolean logic in @import media query".into(), - BundleErrorKind::UnsupportedLayerCombination => "Unsupported layer combination in @import".into() + BundleErrorKind::UnsupportedLayerCombination => "Unsupported layer combination in @import".into(), } } } @@ -111,23 +123,26 @@ impl<'a, 's, P: SourceProvider> Bundler<'a, 's, P> { fs, source_indexes: DashMap::new(), stylesheets: Mutex::new(Vec::new()), - options + options, } } pub fn bundle<'e>(&mut self, entry: &'e Path) -> Result, Error>> { // Phase 1: load and parse all files. This is done in parallel. - self.load_file(&entry, ImportRule { - url: "".into(), - layer: None, - supports: None, - media: MediaList::new(), - loc: Location { - source_index: 0, - line: 0, - column: 1 - } - })?; + self.load_file( + &entry, + ImportRule { + url: "".into(), + layer: None, + supports: None, + media: MediaList::new(), + loc: Location { + source_index: 0, + line: 0, + column: 1, + }, + }, + )?; // Phase 2: determine the order that the files should be concatenated. self.order(); @@ -136,24 +151,20 @@ impl<'a, 's, P: SourceProvider> Bundler<'a, 's, P> { let mut rules: Vec> = Vec::new(); self.inline(&mut rules); - let sources = self.stylesheets.get_mut() + let sources = self + .stylesheets + .get_mut() .unwrap() .iter() .flat_map(|s| s.stylesheet.as_ref().unwrap().sources.iter().cloned()) .collect(); - Ok(StyleSheet::new( - sources, - CssRuleList(rules), - self.options.clone() - )) + Ok(StyleSheet::new(sources, CssRuleList(rules), self.options.clone())) } fn find_filename(&self, source_index: u32) -> String { // This function is only used for error handling, so it's ok if this is a bit slow. - let entry = self.source_indexes.iter() - .find(|x| *x.value() == source_index) - .unwrap(); + let entry = self.source_indexes.iter().find(|x| *x.value() == source_index).unwrap(); entry.key().to_str().unwrap().into() } @@ -169,15 +180,13 @@ impl<'a, 's, P: SourceProvider> Bundler<'a, 's, P> { // We cannot combine a media query and a supports query from different @import rules. // e.g. @import "a.css" print; @import "a.css" supports(color: red); // This would require duplicating the actual rules in the file. - if (!rule.media.media_queries.is_empty() && !entry.supports.is_none()) || - (!entry.media.media_queries.is_empty() && !rule.supports.is_none()) { + if (!rule.media.media_queries.is_empty() && !entry.supports.is_none()) + || (!entry.media.media_queries.is_empty() && !rule.supports.is_none()) + { return Err(Error { kind: BundleErrorKind::UnsupportedImportCondition, - loc: Some(ErrorLocation::from( - rule.loc, - self.find_filename(rule.loc.source_index) - )) - }) + loc: Some(ErrorLocation::from(rule.loc, self.find_filename(rule.loc.source_index))), + }); } if rule.media.media_queries.is_empty() { @@ -200,17 +209,14 @@ impl<'a, 's, P: SourceProvider> Bundler<'a, 's, P> { if layer != existing_layer || (layer.is_none() && existing_layer.is_none()) { return Err(Error { kind: BundleErrorKind::UnsupportedLayerCombination, - loc: Some(ErrorLocation::from( - rule.loc, - self.find_filename(rule.loc.source_index) - )) - }) + loc: Some(ErrorLocation::from(rule.loc, self.find_filename(rule.loc.source_index))), + }); } } else { entry.layer = rule.layer; } } - + return Ok(*source_index); } None => { @@ -225,7 +231,7 @@ impl<'a, 's, P: SourceProvider> Bundler<'a, 's, P> { loc: rule.loc.clone(), dependencies: Vec::new(), parent_source_index: 0, - parent_dep_index: 0 + parent_dep_index: 0, }); source_index @@ -233,13 +239,10 @@ impl<'a, 's, P: SourceProvider> Bundler<'a, 's, P> { }; drop(stylesheets); // ensure we aren't holding the lock anymore - + let code = self.fs.read(file).map_err(|e| Error { kind: BundleErrorKind::IOError(e), - loc: Some(ErrorLocation::from( - rule.loc, - self.find_filename(rule.loc.source_index) - )) + loc: Some(ErrorLocation::from(rule.loc, self.find_filename(rule.loc.source_index))), })?; let mut opts = self.options.clone(); @@ -252,14 +255,13 @@ impl<'a, 's, P: SourceProvider> Bundler<'a, 's, P> { let _ = source_map.set_source_content(source_index as usize, code); } - let mut stylesheet = StyleSheet::parse( - filename.into(), - code, - opts, - )?; + let mut stylesheet = StyleSheet::parse(filename.into(), code, opts)?; // Collect and load dependencies for this stylesheet in parallel. - let dependencies: Result, _> = stylesheet.rules.0.par_iter_mut() + let dependencies: Result, _> = stylesheet + .rules + .0 + .par_iter_mut() .filter_map(|r| { // Prepend parent layer name to @layer statements. if let CssRule::LayerStatement(layer) = r { @@ -273,30 +275,32 @@ impl<'a, 's, P: SourceProvider> Bundler<'a, 's, P> { if let CssRule::Import(import) = r { let path = file.with_file_name(&*import.url); - // Combine media queries and supports conditions from parent + // Combine media queries and supports conditions from parent // stylesheet with @import rule using a logical and operator. let mut media = rule.media.clone(); let result = media.and(&import.media).map_err(|_| Error { kind: BundleErrorKind::UnsupportedMediaBooleanLogic, loc: Some(ErrorLocation::from( import.loc, - self.find_filename(import.loc.source_index) - )) + self.find_filename(import.loc.source_index), + )), }); if let Err(e) = result { - return Some(Err(e)) + return Some(Err(e)); } - let layer = if (rule.layer == Some(None) && import.layer.is_some()) || (import.layer == Some(None) && rule.layer.is_some()) { + let layer = if (rule.layer == Some(None) && import.layer.is_some()) + || (import.layer == Some(None) && rule.layer.is_some()) + { // Cannot combine anonymous layers return Some(Err(Error { kind: BundleErrorKind::UnsupportedLayerCombination, loc: Some(ErrorLocation::from( - import.loc, - self.find_filename(import.loc.source_index) - )) - })) + import.loc, + self.find_filename(import.loc.source_index), + )), + })); } else if let Some(Some(a)) = &rule.layer { if let Some(Some(b)) = &import.layer { let mut name = a.clone(); @@ -308,14 +312,17 @@ impl<'a, 's, P: SourceProvider> Bundler<'a, 's, P> { } else { import.layer.clone() }; - - let result = self.load_file(&path, ImportRule { - layer, - media, - supports: combine_supports(rule.supports.clone(), &import.supports), - url: "".into(), - loc: import.loc - }); + + let result = self.load_file( + &path, + ImportRule { + layer, + media, + supports: combine_supports(rule.supports.clone(), &import.supports), + url: "".into(), + loc: import.loc, + }, + ); Some(result) } else { @@ -323,7 +330,7 @@ impl<'a, 's, P: SourceProvider> Bundler<'a, 's, P> { } }) .collect(); - + let entry = &mut self.stylesheets.lock().unwrap()[source_index as usize]; entry.stylesheet = Some(stylesheet); entry.dependencies = dependencies?; @@ -332,15 +339,11 @@ impl<'a, 's, P: SourceProvider> Bundler<'a, 's, P> { } fn order(&mut self) { - process( - self.stylesheets.get_mut().unwrap(), - 0, - &mut HashSet::new() - ); + process(self.stylesheets.get_mut().unwrap(), 0, &mut HashSet::new()); fn process(stylesheets: &mut Vec>, source_index: u32, visited: &mut HashSet) { if visited.contains(&source_index) { - return + return; } visited.insert(source_index); @@ -359,11 +362,7 @@ impl<'a, 's, P: SourceProvider> Bundler<'a, 's, P> { } fn inline(&mut self, dest: &mut Vec>) { - process( - self.stylesheets.get_mut().unwrap(), - 0, - dest - ); + process(self.stylesheets.get_mut().unwrap(), 0, dest); fn process<'a>(stylesheets: &mut Vec>, source_index: u32, dest: &mut Vec>) { let stylesheet = &mut stylesheets[source_index as usize]; @@ -391,40 +390,34 @@ impl<'a, 's, P: SourceProvider> Bundler<'a, 's, P> { dest.push(layer); } CssRule::Ignored => {} - _ => break + _ => break, } } // Wrap rules in the appropriate @media and @supports rules. let stylesheet = &mut stylesheets[source_index as usize]; if !stylesheet.media.media_queries.is_empty() { - rules = vec![ - CssRule::Media(MediaRule { - query: std::mem::replace(&mut stylesheet.media, MediaList::new()), - rules: CssRuleList(rules), - loc: stylesheet.loc - }) - ] + rules = vec![CssRule::Media(MediaRule { + query: std::mem::replace(&mut stylesheet.media, MediaList::new()), + rules: CssRuleList(rules), + loc: stylesheet.loc, + })] } if stylesheet.supports.is_some() { - rules = vec![ - CssRule::Supports(SupportsRule { - condition: stylesheet.supports.take().unwrap(), - rules: CssRuleList(rules), - loc: stylesheet.loc - }) - ] + rules = vec![CssRule::Supports(SupportsRule { + condition: stylesheet.supports.take().unwrap(), + rules: CssRuleList(rules), + loc: stylesheet.loc, + })] } if stylesheet.layer.is_some() { - rules = vec![ - CssRule::LayerBlock(LayerBlockRule { - name: stylesheet.layer.take().unwrap(), - rules: CssRuleList(rules), - loc: stylesheet.loc - }) - ] + rules = vec![CssRule::LayerBlock(LayerBlockRule { + name: stylesheet.layer.take().unwrap(), + rules: CssRuleList(rules), + loc: stylesheet.loc, + })] } dest.extend(rules); @@ -432,7 +425,10 @@ impl<'a, 's, P: SourceProvider> Bundler<'a, 's, P> { } } -fn combine_supports<'a>(a: Option>, b: &Option>) -> Option> { +fn combine_supports<'a>( + a: Option>, + b: &Option>, +) -> Option> { if let Some(mut a) = a { if let Some(b) = b { a.and(b) @@ -446,12 +442,15 @@ fn combine_supports<'a>(a: Option>, b: &Option + map: HashMap, } impl SourceProvider for TestProvider { @@ -482,17 +481,45 @@ mod tests { } fn bundle_css_module(fs: TestProvider, entry: &str) -> String { - let mut bundler = Bundler::new(&fs, None, ParserOptions { css_modules: true, ..ParserOptions::default() }); + let mut bundler = Bundler::new( + &fs, + None, + ParserOptions { + css_modules: true, + ..ParserOptions::default() + }, + ); let stylesheet = bundler.bundle(Path::new(entry)).unwrap(); stylesheet.to_css(PrinterOptions::default()).unwrap().code } fn bundle_custom_media(fs: TestProvider, entry: &str) -> String { - let mut bundler = Bundler::new(&fs, None, ParserOptions { custom_media: true, ..ParserOptions::default() }); + let mut bundler = Bundler::new( + &fs, + None, + ParserOptions { + custom_media: true, + ..ParserOptions::default() + }, + ); let mut stylesheet = bundler.bundle(Path::new(entry)).unwrap(); - let targets = Some(Browsers { safari: Some(13 << 16 ), ..Browsers::default() }); - stylesheet.minify(MinifyOptions { targets, ..MinifyOptions::default() }).unwrap(); - stylesheet.to_css(PrinterOptions { targets, ..PrinterOptions::default() }).unwrap().code + let targets = Some(Browsers { + safari: Some(13 << 16), + ..Browsers::default() + }); + stylesheet + .minify(MinifyOptions { + targets, + ..MinifyOptions::default() + }) + .unwrap(); + stylesheet + .to_css(PrinterOptions { + targets, + ..PrinterOptions::default() + }) + .unwrap() + .code } fn error_test(fs: TestProvider, entry: &str) { @@ -500,22 +527,27 @@ mod tests { let res = bundler.bundle(Path::new(entry)); match res { Ok(_) => unreachable!(), - Err(e) => assert!(matches!(e.kind, BundleErrorKind::UnsupportedLayerCombination)) + Err(e) => assert!(matches!(e.kind, BundleErrorKind::UnsupportedLayerCombination)), } } #[test] fn test_bundle() { - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @import "b.css"; .a { color: red } "#, - "/b.css": r#" + "/b.css": r#" .b { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" .b { color: green; } @@ -523,18 +555,24 @@ mod tests { .a { color: red; } - "#}); + "#} + ); - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @import "b.css" print; .a { color: red } "#, - "/b.css": r#" + "/b.css": r#" .b { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" @media print { .b { color: green; @@ -544,18 +582,24 @@ mod tests { .a { color: red; } - "#}); + "#} + ); - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @import "b.css" supports(color: green); .a { color: red } "#, - "/b.css": r#" + "/b.css": r#" .b { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" @supports (color: green) { .b { color: green; @@ -565,18 +609,24 @@ mod tests { .a { color: red; } - "#}); + "#} + ); - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @import "b.css" supports(color: green) print; .a { color: red } "#, - "/b.css": r#" + "/b.css": r#" .b { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" @supports (color: green) { @media print { .b { @@ -588,19 +638,25 @@ mod tests { .a { color: red; } - "#}); + "#} + ); - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @import "b.css" print; @import "b.css" screen; .a { color: red } "#, - "/b.css": r#" + "/b.css": r#" .b { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" @media print, screen { .b { color: green; @@ -610,19 +666,25 @@ mod tests { .a { color: red; } - "#}); + "#} + ); - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @import "b.css" supports(color: red); @import "b.css" supports(foo: bar); .a { color: red } "#, - "/b.css": r#" + "/b.css": r#" .b { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" @supports ((color: red) or (foo: bar)) { .b { color: green; @@ -632,22 +694,28 @@ mod tests { .a { color: red; } - "#}); + "#} + ); - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @import "b.css" print; .a { color: red } "#, - "/b.css": r#" + "/b.css": r#" @import "c.css" (color); .b { color: yellow } "#, - "/c.css": r#" + "/c.css": r#" .c { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" @media print and (color) { .c { color: green; @@ -663,22 +731,28 @@ mod tests { .a { color: red; } - "#}); + "#} + ); - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @import "b.css"; .a { color: red } "#, - "/b.css": r#" + "/b.css": r#" @import "c.css"; "#, - "/c.css": r#" + "/c.css": r#" @import "a.css"; .c { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" .c { color: green; } @@ -686,18 +760,24 @@ mod tests { .a { color: red; } - "#}); + "#} + ); - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @import "b/c.css"; .a { color: red } "#, - "/b/c.css": r#" + "/b/c.css": r#" .b { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" .b { color: green; } @@ -705,18 +785,24 @@ mod tests { .a { color: red; } - "#}); + "#} + ); - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @import "./b/c.css"; .a { color: red } "#, - "/b/c.css": r#" + "/b/c.css": r#" .b { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" .b { color: green; } @@ -724,18 +810,24 @@ mod tests { .a { color: red; } - "#}); + "#} + ); - let res = bundle_css_module(fs! { - "/a.css": r#" + let res = bundle_css_module( + fs! { + "/a.css": r#" @import "b.css"; .a { color: red } "#, - "/b.css": r#" + "/b.css": r#" .a { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" ._6lixEq_a { color: green; } @@ -743,24 +835,30 @@ mod tests { ._6lixEq_a { color: red; } - "#}); + "#} + ); - let res = bundle_custom_media(fs! { - "/a.css": r#" + let res = bundle_custom_media( + fs! { + "/a.css": r#" @import "media.css"; @import "b.css"; .a { color: red } "#, - "/media.css": r#" + "/media.css": r#" @custom-media --foo print; "#, - "/b.css": r#" + "/b.css": r#" @media (--foo) { .a { color: green } } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" @media print { .a { color: green; @@ -770,18 +868,24 @@ mod tests { .a { color: red; } - "#}); + "#} + ); - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @import "b.css" layer(foo); .a { color: red } "#, - "/b.css": r#" + "/b.css": r#" .b { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" @layer foo { .b { color: green; @@ -791,18 +895,24 @@ mod tests { .a { color: red; } - "#}); + "#} + ); - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @import "b.css" layer; .a { color: red } "#, - "/b.css": r#" + "/b.css": r#" .b { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" @layer { .b { color: green; @@ -812,22 +922,28 @@ mod tests { .a { color: red; } - "#}); + "#} + ); - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @import "b.css" layer(foo); .a { color: red } "#, - "/b.css": r#" + "/b.css": r#" @import "c.css" layer(bar); .b { color: green } "#, - "/c.css": r#" + "/c.css": r#" .c { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" @layer foo.bar { .c { color: green; @@ -843,27 +959,35 @@ mod tests { .a { color: red; } - "#}); + "#} + ); - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @import "b.css" layer(foo); @import "b.css" layer(foo); "#, - "/b.css": r#" + "/b.css": r#" .b { color: green } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" @layer foo { .b { color: green; } } - "#}); + "#} + ); - let res = bundle(fs! { - "/a.css": r#" + let res = bundle( + fs! { + "/a.css": r#" @layer bar, foo; @import "b.css" layer(foo); @@ -873,7 +997,7 @@ mod tests { } } "#, - "/b.css": r#" + "/b.css": r#" @layer qux, baz; @import "c.css" layer(baz); @@ -883,13 +1007,17 @@ mod tests { } } "#, - "/c.css": r#" + "/c.css": r#" div { background: yellow; } "# - }, "/a.css"); - assert_eq!(res, indoc! { r#" + }, + "/a.css", + ); + assert_eq!( + res, + indoc! { r#" @layer bar, foo; @layer foo.qux, foo.baz; @@ -912,77 +1040,95 @@ mod tests { background: red; } } - "#}); + "#} + ); - error_test(fs! { - "/a.css": r#" + error_test( + fs! { + "/a.css": r#" @import "b.css" layer(foo); @import "b.css" layer(bar); "#, - "/b.css": r#" + "/b.css": r#" .b { color: red } "# - }, "/a.css"); + }, + "/a.css", + ); - error_test(fs! { - "/a.css": r#" + error_test( + fs! { + "/a.css": r#" @import "b.css" layer; @import "b.css" layer; "#, - "/b.css": r#" + "/b.css": r#" .b { color: red } "# - }, "/a.css"); - - error_test(fs! { - "/a.css": r#" + }, + "/a.css", + ); + + error_test( + fs! { + "/a.css": r#" @import "b.css" layer; .a { color: red } "#, - "/b.css": r#" + "/b.css": r#" @import "c.css" layer; .b { color: green } "#, - "/c.css": r#" + "/c.css": r#" .c { color: green } "# - }, "/a.css"); + }, + "/a.css", + ); - error_test(fs! { - "/a.css": r#" + error_test( + fs! { + "/a.css": r#" @import "b.css" layer; .a { color: red } "#, - "/b.css": r#" + "/b.css": r#" @import "c.css" layer(foo); .b { color: green } "#, - "/c.css": r#" + "/c.css": r#" .c { color: green } "# - }, "/a.css"); + }, + "/a.css", + ); - let res = bundle(fs! { - "/index.css": r#" + let res = bundle( + fs! { + "/index.css": r#" @import "a.css"; @import "b.css"; "#, - "/a.css": r#" + "/a.css": r#" @import "./c.css"; body { background: red; } "#, - "/b.css": r#" + "/b.css": r#" @import "./c.css"; body { color: red; } "#, - "/c.css": r#" + "/c.css": r#" body { background: white; color: black; } "# - }, "/index.css"); - assert_eq!(res, indoc! { r#" + }, + "/index.css", + ); + assert_eq!( + res, + indoc! { r#" body { background: red; } @@ -995,22 +1141,28 @@ mod tests { body { color: red; } - "#}); + "#} + ); - let res = bundle(fs! { - "/index.css": r#" + let res = bundle( + fs! { + "/index.css": r#" @import "a.css"; @import "b.css"; @import "a.css"; "#, - "/a.css": r#" + "/a.css": r#" body { background: green; } "#, - "/b.css": r#" + "/b.css": r#" body { background: red; } "# - }, "/index.css"); - assert_eq!(res, indoc! { r#" + }, + "/index.css", + ); + assert_eq!( + res, + indoc! { r#" body { background: red; } @@ -1018,7 +1170,8 @@ mod tests { body { background: green; } - "#}); + "#} + ); // let res = bundle(fs! { // "/a.css": r#" diff --git a/src/compat.rs b/src/compat.rs index a10263bd..4ceff057 100644 --- a/src/compat.rs +++ b/src/compat.rs @@ -53,7 +53,7 @@ pub enum Feature { PlaceContent, PlaceItems, PlaceSelf, - Shadowdomv1 + Shadowdomv1, } impl Feature { @@ -62,1676 +62,1709 @@ impl Feature { Feature::CssSel2 => { if let Some(version) = browsers.ie { if version < 458752 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 786432 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 131072 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 262144 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 196864 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 197120 { - return false + return false; } } if let Some(version) = browsers.android { if version < 131328 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 262144 { - return false + return false; } } } Feature::CssSel3 => { if let Some(version) = browsers.ie { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 786432 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 197888 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 262144 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 197120 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 591104 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 197120 { - return false + return false; } } if let Some(version) = browsers.android { if version < 131328 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 262144 { - return false + return false; } } } - Feature::CssGencontent | - Feature::CssFirstLine => { + Feature::CssGencontent | Feature::CssFirstLine => { if let Some(version) = browsers.ie { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 786432 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 131072 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 262144 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 196864 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 197120 { - return false + return false; } } if let Some(version) = browsers.android { if version < 131328 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 262144 { - return false + return false; } } } Feature::CssFirstLetter => { if let Some(version) = browsers.ie { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 786432 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 197888 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 327936 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 722432 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 327680 { - return false + return false; } } if let Some(version) = browsers.android { if version < 196608 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 262144 { - return false + return false; } } } Feature::CssInOutOfRange => { if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 3276800 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 3473408 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 655616 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 2621440 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 656128 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 327680 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::FormValidation => { if let Some(version) = browsers.ie { if version < 655360 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 786432 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 262144 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 655360 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 655616 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 655360 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 656128 { - return false + return false; } } if let Some(version) = browsers.android { if version < 263171 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 262144 { - return false + return false; } } } Feature::CssAnyLink => { if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 3276800 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 4259840 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 3407872 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 590336 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::CssDefaultPseudo => { if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 262144 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 3342336 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 655616 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 2490368 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 656128 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 327680 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::CssDirPseudo => { if let Some(version) = browsers.firefox { if version < 3211264 { - return false + return false; } } - if browsers.android.is_some() || browsers.chrome.is_some() || browsers.edge.is_some() || browsers.ie.is_some() || browsers.ios_saf.is_some() || browsers.opera.is_some() || browsers.safari.is_some() || browsers.samsung.is_some() { - return false + if browsers.android.is_some() + || browsers.chrome.is_some() + || browsers.edge.is_some() + || browsers.ie.is_some() + || browsers.ios_saf.is_some() + || browsers.opera.is_some() + || browsers.safari.is_some() + || browsers.samsung.is_some() + { + return false; } } Feature::CssFocusWithin => { if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 3407872 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 3932160 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 655616 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 3080192 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 656128 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 524800 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::CssFocusVisible => { if let Some(version) = browsers.edge { if version < 5636096 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 5570560 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 5636096 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 984064 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 4718592 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 984064 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 917504 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::CssIndeterminatePseudo => { if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 3342336 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 2555904 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 655616 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 1703936 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 656128 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 262144 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::CssMatchesPseudo => { if let Some(version) = browsers.edge { if version < 5767168 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 5111808 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 5767168 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 917504 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 4915200 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 917504 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 983040 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::CssOptionalPseudo => { if let Some(version) = browsers.ie { if version < 655360 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 786432 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 262144 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 983040 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 327680 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 983040 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 327680 { - return false + return false; } } if let Some(version) = browsers.android { if version < 131840 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 262144 { - return false + return false; } } } Feature::CssPlaceholderShown => { if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 3342336 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 3080192 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 2228224 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 327680 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::Dialog => { if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 6422528 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 2424832 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 984064 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 1572864 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 984064 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 262144 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::Fullscreen => { if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 4194304 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 4653056 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 786688 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 655616 { - return false + return false; } } - if browsers.android.is_some() || browsers.ie.is_some() || browsers.ios_saf.is_some() || browsers.safari.is_some() { - return false + if browsers.android.is_some() + || browsers.ie.is_some() + || browsers.ios_saf.is_some() + || browsers.safari.is_some() + { + return false; } } Feature::CssMarkerPseudo => { if let Some(version) = browsers.edge { if version < 5636096 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 4456448 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 5636096 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 4718592 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 721664 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 917504 { - return false + return false; } } if browsers.ie.is_some() || browsers.safari.is_some() { - return false + return false; } } Feature::CssPlaceholder => { if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 3342336 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 3735552 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 655616 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 2883584 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 656128 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 459264 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::CssSelection => { if let Some(version) = browsers.ie { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 786432 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 4063232 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 262144 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 196864 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 591104 { - return false + return false; } } if let Some(version) = browsers.android { if version < 263168 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 262144 { - return false + return false; } } if browsers.ios_saf.is_some() { - return false + return false; } } Feature::CssCaseInsensitive => { if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 3080192 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 3211264 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 2359296 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 327680 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::CssReadOnlyWrite => { if let Some(version) = browsers.edge { if version < 851968 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 5111808 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 2359296 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 1507328 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 262144 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::CssAutofill => { if let Some(version) = browsers.chrome { if version < 6291456 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 6291456 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 5636096 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 5373952 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 983040 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 983040 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if browsers.ie.is_some() || browsers.samsung.is_some() { - return false + return false; } } Feature::CssNamespaces => { if let Some(version) = browsers.ie { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 786432 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 131072 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 262144 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 262144 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 262656 { - return false + return false; } } if let Some(version) = browsers.android { if version < 131328 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 262144 { - return false + return false; } } } Feature::Shadowdomv1 => { if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 4128768 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 3473408 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 655360 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 2621440 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 720896 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 393728 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::CssRrggbbaa => { if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 3211264 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 4063232 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 655360 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 3407872 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 655360 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 524800 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } - Feature::CssNesting | - Feature::CustomMediaQueries | - Feature::MediaIntervalSyntax | - Feature::OklabColors => { + Feature::CssNesting | Feature::CustomMediaQueries | Feature::MediaIntervalSyntax | Feature::OklabColors => { return false } Feature::CssNotSelList => { if let Some(version) = browsers.edge { if version < 5767168 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 5505024 { - return false + return false; } } if let Some(version) = browsers.chrome { if version < 5767168 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 4915200 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.android { if version < 6488064 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 983040 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::CssHas => { if let Some(version) = browsers.safari { if version < 984064 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 984064 { - return false + return false; } } - if browsers.android.is_some() || browsers.chrome.is_some() || browsers.edge.is_some() || browsers.firefox.is_some() || browsers.ie.is_some() || browsers.opera.is_some() || browsers.samsung.is_some() { - return false + if browsers.android.is_some() + || browsers.chrome.is_some() + || browsers.edge.is_some() + || browsers.firefox.is_some() + || browsers.ie.is_some() + || browsers.opera.is_some() + || browsers.samsung.is_some() + { + return false; } } Feature::DoublePositionGradients => { if let Some(version) = browsers.chrome { if version < 4653056 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 4194304 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 3276800 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 786688 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 786944 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 655360 { - return false + return false; } } if let Some(version) = browsers.android { if version < 4653056 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::Clamp => { if let Some(version) = browsers.chrome { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 3735552 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 852224 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 852992 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 786432 { - return false + return false; } } if let Some(version) = browsers.android { if version < 5177344 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } - Feature::PlaceSelf | - Feature::PlaceItems => { + Feature::PlaceSelf | Feature::PlaceItems => { if let Some(version) = browsers.chrome { if version < 3866624 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 2949120 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 2818048 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 720896 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 720896 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 458752 { - return false + return false; } } if let Some(version) = browsers.android { if version < 3866624 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::PlaceContent => { if let Some(version) = browsers.chrome { if version < 3866624 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 2949120 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 2818048 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 589824 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 458752 { - return false + return false; } } if let Some(version) = browsers.android { if version < 3866624 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::OverflowShorthand => { if let Some(version) = browsers.chrome { if version < 4456448 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 3997696 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 3145728 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 655360 { - return false + return false; } } if let Some(version) = browsers.android { if version < 4456448 { - return false + return false; } } if browsers.ie.is_some() || browsers.ios_saf.is_some() || browsers.safari.is_some() { - return false + return false; } } Feature::MediaRangeSyntax => { if let Some(version) = browsers.firefox { if version < 4128768 { - return false + return false; } } - if browsers.android.is_some() || browsers.chrome.is_some() || browsers.edge.is_some() || browsers.ie.is_some() || browsers.ios_saf.is_some() || browsers.opera.is_some() || browsers.safari.is_some() || browsers.samsung.is_some() { - return false + if browsers.android.is_some() + || browsers.chrome.is_some() + || browsers.edge.is_some() + || browsers.ie.is_some() + || browsers.ios_saf.is_some() + || browsers.opera.is_some() + || browsers.safari.is_some() + || browsers.samsung.is_some() + { + return false; } } - Feature::LogicalBorders | - Feature::LogicalMargin | - Feature::LogicalPadding => { + Feature::LogicalBorders | Feature::LogicalMargin | Feature::LogicalPadding => { if let Some(version) = browsers.chrome { if version < 4521984 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 2686976 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 3145728 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 786688 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 786944 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 655360 { - return false + return false; } } if let Some(version) = browsers.android { if version < 4521984 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::LogicalBorderRadius => { if let Some(version) = browsers.chrome { if version < 5832704 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 5832704 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 4325376 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 4915200 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 983040 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 983040 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 983040 { - return false + return false; } } if let Some(version) = browsers.android { if version < 5832704 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::LogicalInset => { if let Some(version) = browsers.chrome { if version < 5701632 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 5701632 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 4128768 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 4063232 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 917760 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 918784 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 917504 { - return false + return false; } } if let Some(version) = browsers.android { if version < 5701632 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::LogicalSize => { if let Some(version) = browsers.chrome { if version < 3735552 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 2686976 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 2818048 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 786688 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 786944 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 327680 { - return false + return false; } } if let Some(version) = browsers.android { if version < 3735552 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } Feature::LogicalTextAlign => { if let Some(version) = browsers.chrome { if version < 1179648 { - return false + return false; } } if let Some(version) = browsers.edge { if version < 5177344 { - return false + return false; } } if let Some(version) = browsers.firefox { if version < 262144 { - return false + return false; } } if let Some(version) = browsers.opera { if version < 917504 { - return false + return false; } } if let Some(version) = browsers.safari { if version < 196864 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 131072 { - return false + return false; } } if let Some(version) = browsers.samsung { if version < 65536 { - return false + return false; } } if let Some(version) = browsers.android { if version < 2424832 { - return false + return false; } } if browsers.ie.is_some() { - return false + return false; } } - Feature::LabColors | - Feature::ColorFunction => { + Feature::LabColors | Feature::ColorFunction => { if let Some(version) = browsers.safari { if version < 983040 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 983040 { - return false + return false; } } - if browsers.android.is_some() || browsers.chrome.is_some() || browsers.edge.is_some() || browsers.firefox.is_some() || browsers.ie.is_some() || browsers.opera.is_some() || browsers.samsung.is_some() { - return false + if browsers.android.is_some() + || browsers.chrome.is_some() + || browsers.edge.is_some() + || browsers.firefox.is_some() + || browsers.ie.is_some() + || browsers.opera.is_some() + || browsers.samsung.is_some() + { + return false; } } Feature::P3Colors => { if let Some(version) = browsers.safari { if version < 655616 { - return false + return false; } } if let Some(version) = browsers.ios_saf { if version < 656128 { - return false + return false; } } - if browsers.android.is_some() || browsers.chrome.is_some() || browsers.edge.is_some() || browsers.firefox.is_some() || browsers.ie.is_some() || browsers.opera.is_some() || browsers.samsung.is_some() { - return false + if browsers.android.is_some() + || browsers.chrome.is_some() + || browsers.edge.is_some() + || browsers.firefox.is_some() + || browsers.ie.is_some() + || browsers.opera.is_some() + || browsers.samsung.is_some() + { + return false; } } } @@ -1743,63 +1776,63 @@ impl Feature { if targets.android.is_some() { browsers.android = targets.android; if self.is_compatible(browsers) { - return true + return true; } browsers.android = None; } if targets.chrome.is_some() { browsers.chrome = targets.chrome; if self.is_compatible(browsers) { - return true + return true; } browsers.chrome = None; } if targets.edge.is_some() { browsers.edge = targets.edge; if self.is_compatible(browsers) { - return true + return true; } browsers.edge = None; } if targets.firefox.is_some() { browsers.firefox = targets.firefox; if self.is_compatible(browsers) { - return true + return true; } browsers.firefox = None; } if targets.ie.is_some() { browsers.ie = targets.ie; if self.is_compatible(browsers) { - return true + return true; } browsers.ie = None; } if targets.ios_saf.is_some() { browsers.ios_saf = targets.ios_saf; if self.is_compatible(browsers) { - return true + return true; } browsers.ios_saf = None; } if targets.opera.is_some() { browsers.opera = targets.opera; if self.is_compatible(browsers) { - return true + return true; } browsers.opera = None; } if targets.safari.is_some() { browsers.safari = targets.safari; if self.is_compatible(browsers) { - return true + return true; } browsers.safari = None; } if targets.samsung.is_some() { browsers.samsung = targets.samsung; if self.is_compatible(browsers) { - return true + return true; } browsers.samsung = None; } diff --git a/src/context.rs b/src/context.rs index 6b0df27a..e4ca8f05 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,29 +1,28 @@ +use crate::compat::Feature; +use crate::declaration::{DeclarationBlock, DeclarationList}; use crate::logical::LogicalProperty; use crate::properties::custom::UnparsedProperty; +use crate::properties::{ + custom::{CustomProperty, Token, TokenList}, + Property, PropertyId, +}; +use crate::rules::supports::{SupportsCondition, SupportsRule}; use crate::rules::Location; -use crate::rules::supports::{SupportsRule, SupportsCondition}; -use crate::rules::{CssRule, CssRuleList, style::StyleRule}; -use parcel_selectors::SelectorList; +use crate::rules::{style::StyleRule, CssRule, CssRuleList}; use crate::selector::{SelectorIdent, SelectorString}; -use crate::declaration::{DeclarationBlock, DeclarationList}; -use crate::vendor_prefix::VendorPrefix; -use crate::compat::Feature; use crate::targets::Browsers; +use crate::vendor_prefix::VendorPrefix; +use parcel_selectors::SelectorList; use parcel_selectors::{ - parser::{Selector, Component}, - attr::{AttrSelectorOperator, ParsedCaseSensitivity} -}; -use crate::properties::{ - Property, - PropertyId, - custom::{CustomProperty, TokenList, Token}, + attr::{AttrSelectorOperator, ParsedCaseSensitivity}, + parser::{Component, Selector}, }; #[derive(Debug)] pub(crate) struct SupportsEntry<'i> { pub condition: SupportsCondition<'i>, pub declarations: Vec>, - pub important_declarations: Vec> + pub important_declarations: Vec>, } #[derive(Debug, PartialEq)] @@ -31,7 +30,7 @@ pub(crate) enum DeclarationContext { None, StyleRule, Keyframes, - StyleAttribute + StyleAttribute, } #[derive(Debug)] @@ -40,7 +39,7 @@ pub(crate) struct PropertyHandlerContext<'i> { pub used_logical: bool, pub is_important: bool, supports: Vec>, - pub context: DeclarationContext + pub context: DeclarationContext, } impl<'i> PropertyHandlerContext<'i> { @@ -50,7 +49,7 @@ impl<'i> PropertyHandlerContext<'i> { used_logical: false, is_important: false, supports: Vec::new(), - context: DeclarationContext::None + context: DeclarationContext::None, } } @@ -58,7 +57,7 @@ impl<'i> PropertyHandlerContext<'i> { // Don't convert logical properties in style attributes because // our fallbacks rely on extra rules to define --ltr and --rtl. if self.context == DeclarationContext::StyleAttribute { - return true + return true; } if let Some(targets) = self.targets { @@ -68,16 +67,29 @@ impl<'i> PropertyHandlerContext<'i> { } } - pub fn add_logical_property(&mut self, dest: &mut DeclarationList<'i>, property_id: PropertyId<'i>, ltr: Property<'i>, rtl: Property<'i>) { + pub fn add_logical_property( + &mut self, + dest: &mut DeclarationList<'i>, + property_id: PropertyId<'i>, + ltr: Property<'i>, + rtl: Property<'i>, + ) { self.used_logical = true; dest.push(Property::Logical(LogicalProperty { property_id, ltr: Some(Box::new(ltr)), - rtl: Some(Box::new(rtl)) + rtl: Some(Box::new(rtl)), })); } - pub fn add_inline_logical_properties(&mut self, dest: &mut DeclarationList<'i>, left: PropertyId<'i>, right: PropertyId<'i>, start: Option>, end: Option>) { + pub fn add_inline_logical_properties( + &mut self, + dest: &mut DeclarationList<'i>, + left: PropertyId<'i>, + right: PropertyId<'i>, + start: Option>, + end: Option>, + ) { self.used_logical = true; dest.push(Property::Logical(LogicalProperty { property_id: left, @@ -97,17 +109,15 @@ impl<'i> PropertyHandlerContext<'i> { macro_rules! style_rule { ($dir: ident, $ltr: expr, $rtl: expr) => { dest.0.push(CssRule::Style(StyleRule { - selectors: SelectorList(smallvec::smallvec![ - Selector::from_vec2(vec![ - Component::AttributeInNoNamespace { - local_name: SelectorIdent("dir".into()), - operator: AttrSelectorOperator::Equal, - value: SelectorString(stringify!($dir).into()), - case_sensitivity: ParsedCaseSensitivity::CaseSensitive, - never_matches: false - } - ]) - ]), + selectors: SelectorList(smallvec::smallvec![Selector::from_vec2(vec![ + Component::AttributeInNoNamespace { + local_name: SelectorIdent("dir".into()), + operator: AttrSelectorOperator::Equal, + value: SelectorString(stringify!($dir).into()), + case_sensitivity: ParsedCaseSensitivity::CaseSensitive, + never_matches: false + } + ])]), rules: CssRuleList(vec![]), vendor_prefix: VendorPrefix::empty(), declarations: DeclarationBlock { @@ -115,19 +125,19 @@ impl<'i> PropertyHandlerContext<'i> { declarations: vec![ Property::Custom(CustomProperty { name: "--ltr".into(), - value: TokenList(vec![$ltr.into()]) + value: TokenList(vec![$ltr.into()]), }), Property::Custom(CustomProperty { name: "--rtl".into(), - value: TokenList(vec![$rtl.into()]) - }) - ] + value: TokenList(vec![$rtl.into()]), + }), + ], }, loc: Location { source_index: 0, line: 0, - column: 1 - } + column: 1, + }, })); }; } @@ -140,7 +150,7 @@ impl<'i> PropertyHandlerContext<'i> { pub fn add_conditional_property(&mut self, condition: SupportsCondition<'i>, property: Property<'i>) { if self.context != DeclarationContext::StyleRule { - return + return; } if let Some(entry) = self.supports.iter_mut().find(|supports| condition == supports.condition) { @@ -167,9 +177,9 @@ impl<'i> PropertyHandlerContext<'i> { pub fn add_unparsed_fallbacks(&mut self, unparsed: &mut UnparsedProperty<'i>) { if self.context != DeclarationContext::StyleRule && self.context != DeclarationContext::StyleAttribute { - return + return; } - + if let Some(targets) = self.targets { let fallbacks = unparsed.value.get_fallbacks(targets); for (condition, fallback) in fallbacks { @@ -177,8 +187,8 @@ impl<'i> PropertyHandlerContext<'i> { condition, Property::Unparsed(UnparsedProperty { property_id: unparsed.property_id.clone(), - value: fallback - }) + value: fallback, + }), ); } } @@ -186,7 +196,7 @@ impl<'i> PropertyHandlerContext<'i> { pub fn get_supports_rules(&mut self, style_rule: &StyleRule<'i>) -> Vec> { if self.supports.is_empty() { - return Vec::new() + return Vec::new(); } let mut dest = Vec::new(); @@ -194,19 +204,17 @@ impl<'i> PropertyHandlerContext<'i> { for entry in supports { dest.push(CssRule::Supports(SupportsRule { condition: entry.condition, - rules: CssRuleList(vec![ - CssRule::Style(StyleRule { - selectors: style_rule.selectors.clone(), - vendor_prefix: VendorPrefix::None, - declarations: DeclarationBlock { - declarations: entry.declarations, - important_declarations: entry.important_declarations - }, - rules: CssRuleList(vec![]), - loc: style_rule.loc.clone() - }) - ]), - loc: style_rule.loc.clone() + rules: CssRuleList(vec![CssRule::Style(StyleRule { + selectors: style_rule.selectors.clone(), + vendor_prefix: VendorPrefix::None, + declarations: DeclarationBlock { + declarations: entry.declarations, + important_declarations: entry.important_declarations, + }, + rules: CssRuleList(vec![]), + loc: style_rule.loc.clone(), + })]), + loc: style_rule.loc.clone(), })); } diff --git a/src/css_modules.rs b/src/css_modules.rs index e9b0d327..bc1d16f6 100644 --- a/src/css_modules.rs +++ b/src/css_modules.rs @@ -1,27 +1,20 @@ -use std::collections::HashMap; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; -use data_encoding::{Specification, Encoding}; -use lazy_static::lazy_static; +use crate::error::PrinterErrorKind; use crate::properties::css_modules::{Composes, ComposesFrom}; -use parcel_selectors::SelectorList; use crate::selector::Selectors; +use data_encoding::{Encoding, Specification}; +use lazy_static::lazy_static; +use parcel_selectors::SelectorList; use serde::Serialize; -use crate::error::PrinterErrorKind; +use std::collections::hash_map::DefaultHasher; +use std::collections::HashMap; +use std::hash::{Hash, Hasher}; #[derive(PartialEq, Debug, Clone, Serialize)] #[serde(tag = "type", rename_all = "lowercase")] pub enum CssModuleReference { - Local { - name: String, - }, - Global { - name: String - }, - Dependency { - name: String, - specifier: String - } + Local { name: String }, + Global { name: String }, + Dependency { name: String, specifier: String }, } #[derive(PartialEq, Debug, Clone, Serialize)] @@ -29,7 +22,7 @@ pub enum CssModuleReference { pub struct CssModuleExport { pub name: String, pub composes: Vec, - pub is_referenced: bool + pub is_referenced: bool, } pub type CssModuleExports = HashMap; @@ -37,24 +30,25 @@ pub type CssModuleExports = HashMap; lazy_static! { static ref ENCODER: Encoding = { let mut spec = Specification::new(); - spec.symbols.push_str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-"); + spec + .symbols + .push_str("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_-"); spec.encoding().unwrap() }; } pub(crate) struct CssModule<'a> { pub hash: &'a str, - pub exports: &'a mut CssModuleExports + pub exports: &'a mut CssModuleExports, } impl<'a> CssModule<'a> { pub fn add_local(&mut self, exported: &str, local: &str) { - self.exports.entry(exported.into()) - .or_insert_with(|| CssModuleExport { - name: get_hashed_name(self.hash, local), - composes: vec![], - is_referenced: false - }); + self.exports.entry(exported.into()).or_insert_with(|| CssModuleExport { + name: get_hashed_name(self.hash, local), + composes: vec![], + is_referenced: false, + }); } pub fn reference(&mut self, name: &str) { @@ -66,25 +60,33 @@ impl<'a> CssModule<'a> { entry.insert(CssModuleExport { name: get_hashed_name(self.hash, name), composes: vec![], - is_referenced: true + is_referenced: true, }); } } } - pub fn handle_composes(&mut self, selectors: &SelectorList, composes: &Composes) -> Result<(), PrinterErrorKind> { + pub fn handle_composes( + &mut self, + selectors: &SelectorList, + composes: &Composes, + ) -> Result<(), PrinterErrorKind> { for sel in &selectors.0 { if sel.len() == 1 { match sel.iter_raw_match_order().next().unwrap() { parcel_selectors::parser::Component::Class(ref id) => { for name in &composes.names { let reference = match &composes.from { - None => CssModuleReference::Local { name: get_hashed_name(self.hash, name.0.as_ref()) }, - Some(ComposesFrom::Global) => CssModuleReference::Global { name: name.0.as_ref().into() }, + None => CssModuleReference::Local { + name: get_hashed_name(self.hash, name.0.as_ref()), + }, + Some(ComposesFrom::Global) => CssModuleReference::Global { + name: name.0.as_ref().into(), + }, Some(ComposesFrom::File(file)) => CssModuleReference::Dependency { name: name.0.to_string(), - specifier: file.to_string() - } + specifier: file.to_string(), + }, }; let export = self.exports.get_mut(&id.0.as_ref().to_owned()).unwrap(); @@ -99,7 +101,7 @@ impl<'a> CssModule<'a> { } // The composes property can only be used within a simple class selector. - return Err(PrinterErrorKind::InvalidComposesSelector) + return Err(PrinterErrorKind::InvalidComposesSelector); } Ok(()) @@ -116,7 +118,7 @@ pub(crate) fn hash(s: &str) -> String { let mut hasher = DefaultHasher::new(); s.hash(&mut hasher); let hash = hasher.finish() as u32; - + let hash = ENCODER.encode(&hash.to_le_bytes()); if matches!(hash.as_bytes()[0], b'0'..=b'9') { format!("_{}", hash) diff --git a/src/declaration.rs b/src/declaration.rs index 3e6dbce3..fc17c210 100644 --- a/src/declaration.rs +++ b/src/declaration.rs @@ -1,64 +1,73 @@ -use cssparser::*; -use crate::properties::Property; +use crate::context::PropertyHandlerContext; +use crate::error::{ParserError, PrinterError}; +use crate::parser::ParserOptions; +use crate::printer::Printer; use crate::properties::box_shadow::BoxShadowHandler; use crate::properties::masking::MaskHandler; -use crate::traits::{PropertyHandler, ToCss}; -use crate::printer::Printer; +use crate::properties::Property; use crate::properties::{ align::AlignHandler, + animation::AnimationHandler, background::BackgroundHandler, + border::BorderHandler, + display::DisplayHandler, flex::FlexHandler, font::FontHandler, + grid::GridHandler, + list::ListStyleHandler, margin_padding::*, outline::OutlineHandler, - border::BorderHandler, - transition::TransitionHandler, - animation::AnimationHandler, - prefix_handler::{PrefixHandler, FallbackHandler}, - display::DisplayHandler, - transform::TransformHandler, - text::TextDecorationHandler, - position::PositionHandler, overflow::OverflowHandler, - list::ListStyleHandler, - grid::GridHandler, + position::PositionHandler, + prefix_handler::{FallbackHandler, PrefixHandler}, size::SizeHandler, + text::TextDecorationHandler, + transform::TransformHandler, + transition::TransitionHandler, }; use crate::targets::Browsers; -use crate::parser::ParserOptions; -use crate::error::{ParserError, PrinterError}; -use crate::context::PropertyHandlerContext; +use crate::traits::{PropertyHandler, ToCss}; +use cssparser::*; #[derive(Debug, PartialEq, Clone)] pub struct DeclarationBlock<'i> { pub important_declarations: Vec>, - pub declarations: Vec> + pub declarations: Vec>, } impl<'i> DeclarationBlock<'i> { - pub fn parse<'t>(input: &mut Parser<'i, 't>, options: &ParserOptions) -> Result>> { + pub fn parse<'t>( + input: &mut Parser<'i, 't>, + options: &ParserOptions, + ) -> Result>> { let mut important_declarations = DeclarationList::new(); let mut declarations = DeclarationList::new(); - let mut parser = DeclarationListParser::new(input, PropertyDeclarationParser { - important_declarations: &mut important_declarations, - declarations: &mut declarations, - options - }); + let mut parser = DeclarationListParser::new( + input, + PropertyDeclarationParser { + important_declarations: &mut important_declarations, + declarations: &mut declarations, + options, + }, + ); while let Some(res) = parser.next() { if let Err((err, _)) = res { - return Err(err) + return Err(err); } } Ok(DeclarationBlock { important_declarations, - declarations + declarations, }) } } impl<'i> ToCss for DeclarationBlock<'i> { - fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> where W: std::fmt::Write { + fn to_css(&self, dest: &mut Printer) -> Result<(), PrinterError> + where + W: std::fmt::Write, + { dest.whitespace()?; dest.write_char('{')?; dest.indent(); @@ -81,7 +90,7 @@ impl<'i> ToCss for DeclarationBlock<'i> { write!(self.declarations, false); write!(self.important_declarations, true); - + dest.dedent(); dest.newline()?; dest.write_char('}') @@ -93,14 +102,14 @@ impl<'i> DeclarationBlock<'i> { &mut self, handler: &mut DeclarationHandler<'i>, important_handler: &mut DeclarationHandler<'i>, - context: &mut PropertyHandlerContext<'i> + context: &mut PropertyHandlerContext<'i>, ) { macro_rules! handle { ($decls: expr, $handler: expr, $important: literal) => { for decl in $decls.iter() { context.is_important = $important; let handled = $handler.handle_property(decl, context); - + if !handled { $handler.decls.push(decl.clone()); } @@ -121,7 +130,7 @@ impl<'i> DeclarationBlock<'i> { struct PropertyDeclarationParser<'a, 'i> { important_declarations: &'a mut Vec>, declarations: &'a mut Vec>, - options: &'a ParserOptions + options: &'a ParserOptions, } /// Parse a declaration within {} block: `color: blue` @@ -134,7 +143,13 @@ impl<'a, 'i> cssparser::DeclarationParser<'i> for PropertyDeclarationParser<'a, name: CowRcStr<'i>, input: &mut cssparser::Parser<'i, 't>, ) -> Result> { - parse_declaration(name, input, &mut self.declarations, &mut self.important_declarations, &self.options) + parse_declaration( + name, + input, + &mut self.declarations, + &mut self.important_declarations, + &self.options, + ) } } @@ -150,13 +165,15 @@ pub(crate) fn parse_declaration<'i, 't>( input: &mut cssparser::Parser<'i, 't>, declarations: &mut DeclarationList<'i>, important_declarations: &mut DeclarationList<'i>, - options: &ParserOptions + options: &ParserOptions, ) -> Result<(), cssparser::ParseError<'i, ParserError<'i>>> { let property = input.parse_until_before(Delimiter::Bang, |input| Property::parse(name, input, options))?; - let important = input.try_parse(|input| { - input.expect_delim('!')?; - input.expect_ident_matching("important") - }).is_ok(); + let important = input + .try_parse(|input| { + input.expect_delim('!')?; + input.expect_ident_matching("important") + }) + .is_ok(); if important { important_declarations.push(property); } else { @@ -193,7 +210,7 @@ pub(crate) struct DeclarationHandler<'i> { mask: MaskHandler<'i>, fallback: FallbackHandler, prefix: PrefixHandler, - decls: DeclarationList<'i> + decls: DeclarationList<'i>, } impl<'i> DeclarationHandler<'i> { @@ -224,36 +241,36 @@ impl<'i> DeclarationHandler<'i> { mask: MaskHandler::default(), fallback: FallbackHandler::new(targets), prefix: PrefixHandler::new(targets), - decls: DeclarationList::new() + decls: DeclarationList::new(), } } pub fn handle_property(&mut self, property: &Property<'i>, context: &mut PropertyHandlerContext<'i>) -> bool { - self.background.handle_property(property, &mut self.decls, context) || - self.border.handle_property(property, &mut self.decls, context) || - self.outline.handle_property(property, &mut self.decls, context) || - self.flex.handle_property(property, &mut self.decls, context) || - self.grid.handle_property(property, &mut self.decls, context) || - self.align.handle_property(property, &mut self.decls, context) || - self.size.handle_property(property, &mut self.decls, context) || - self.margin.handle_property(property, &mut self.decls, context) || - self.padding.handle_property(property, &mut self.decls, context) || - self.scroll_margin.handle_property(property, &mut self.decls, context) || - self.scroll_padding.handle_property(property, &mut self.decls, context) || - self.font.handle_property(property, &mut self.decls, context) || - self.text.handle_property(property, &mut self.decls, context) || - self.list.handle_property(property, &mut self.decls, context) || - self.transition.handle_property(property, &mut self.decls, context) || - self.animation.handle_property(property, &mut self.decls, context) || - self.display.handle_property(property, &mut self.decls, context) || - self.position.handle_property(property, &mut self.decls, context) || - self.inset.handle_property(property, &mut self.decls, context) || - self.overflow.handle_property(property, &mut self.decls, context) || - self.transform.handle_property(property, &mut self.decls, context) || - self.box_shadow.handle_property(property, &mut self.decls, context) || - self.mask.handle_property(property, &mut self.decls, context) || - self.fallback.handle_property(property, &mut self.decls, context) || - self.prefix.handle_property(property, &mut self.decls, context) + self.background.handle_property(property, &mut self.decls, context) + || self.border.handle_property(property, &mut self.decls, context) + || self.outline.handle_property(property, &mut self.decls, context) + || self.flex.handle_property(property, &mut self.decls, context) + || self.grid.handle_property(property, &mut self.decls, context) + || self.align.handle_property(property, &mut self.decls, context) + || self.size.handle_property(property, &mut self.decls, context) + || self.margin.handle_property(property, &mut self.decls, context) + || self.padding.handle_property(property, &mut self.decls, context) + || self.scroll_margin.handle_property(property, &mut self.decls, context) + || self.scroll_padding.handle_property(property, &mut self.decls, context) + || self.font.handle_property(property, &mut self.decls, context) + || self.text.handle_property(property, &mut self.decls, context) + || self.list.handle_property(property, &mut self.decls, context) + || self.transition.handle_property(property, &mut self.decls, context) + || self.animation.handle_property(property, &mut self.decls, context) + || self.display.handle_property(property, &mut self.decls, context) + || self.position.handle_property(property, &mut self.decls, context) + || self.inset.handle_property(property, &mut self.decls, context) + || self.overflow.handle_property(property, &mut self.decls, context) + || self.transform.handle_property(property, &mut self.decls, context) + || self.box_shadow.handle_property(property, &mut self.decls, context) + || self.mask.handle_property(property, &mut self.decls, context) + || self.fallback.handle_property(property, &mut self.decls, context) + || self.prefix.handle_property(property, &mut self.decls, context) } pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i>) { diff --git a/src/dependencies.rs b/src/dependencies.rs index 6526e3cd..2d3e0c05 100644 --- a/src/dependencies.rs +++ b/src/dependencies.rs @@ -1,16 +1,16 @@ +use crate::css_modules::hash; +use crate::printer::Printer; use crate::rules::import::ImportRule; +use crate::traits::ToCss; use crate::values::url::Url; -use serde::Serialize; use cssparser::SourceLocation; -use crate::printer::Printer; -use crate::traits::ToCss; -use crate::css_modules::hash; +use serde::Serialize; #[derive(Serialize)] #[serde(tag = "type", rename_all = "lowercase")] pub enum Dependency { Import(ImportDependency), - Url(UrlDependency) + Url(UrlDependency), } #[derive(Serialize)] @@ -18,7 +18,7 @@ pub struct ImportDependency { pub url: String, pub supports: Option, pub media: Option, - pub loc: SourceRange + pub loc: SourceRange, } impl ImportDependency { @@ -45,7 +45,15 @@ impl ImportDependency { url: rule.url.as_ref().to_owned(), supports, media, - loc: SourceRange::new(filename, SourceLocation { line: rule.loc.line, column: rule.loc.column }, 8, rule.url.len() + 2) // TODO: what about @import url(...)? + loc: SourceRange::new( + filename, + SourceLocation { + line: rule.loc.line, + column: rule.loc.column, + }, + 8, + rule.url.len() + 2, + ), // TODO: what about @import url(...)? } } } @@ -54,7 +62,7 @@ impl ImportDependency { pub struct UrlDependency { pub url: String, pub placeholder: String, - pub loc: SourceRange + pub loc: SourceRange, } impl UrlDependency { @@ -63,7 +71,7 @@ impl UrlDependency { UrlDependency { url: url.url.to_string(), placeholder, - loc: SourceRange::new(filename, url.loc, 4, url.url.len()) + loc: SourceRange::new(filename, url.loc, 4, url.url.len()), } } } @@ -79,7 +87,7 @@ pub struct SourceRange { #[derive(Serialize)] pub struct Location { pub line: u32, - pub column: u32 + pub column: u32, } impl SourceRange { @@ -88,12 +96,12 @@ impl SourceRange { file_path: filename.into(), start: Location { line: loc.line + 1, - column: loc.column + offset + column: loc.column + offset, }, end: Location { line: loc.line + 1, - column: loc.column + offset + (len as u32) - 1 - } + column: loc.column + offset + (len as u32) - 1, + }, } } } diff --git a/src/error.rs b/src/error.rs index 07cc50c5..8a2da0fb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,21 +1,21 @@ -use parcel_selectors::parser::SelectorParseErrorKind; -use cssparser::{ParseError, ParseErrorKind, BasicParseErrorKind}; -use crate::rules::Location; use crate::properties::custom::Token; +use crate::rules::Location; use crate::values::string::CowArcStr; +use cssparser::{BasicParseErrorKind, ParseError, ParseErrorKind}; +use parcel_selectors::parser::SelectorParseErrorKind; use serde::Serialize; #[derive(Debug, PartialEq, Clone)] pub struct Error { pub kind: T, - pub loc: Option + pub loc: Option, } #[derive(Debug, PartialEq, Clone)] pub struct ErrorLocation { pub filename: String, pub line: u32, - pub column: u32 + pub column: u32, } impl ErrorLocation { @@ -23,7 +23,7 @@ impl ErrorLocation { ErrorLocation { filename, line: loc.line, - column: loc.column + column: loc.column, } } } @@ -48,23 +48,20 @@ pub enum ParserError<'i> { InvalidMediaQuery, InvalidNesting, UnexpectedImportRule, - UnexpectedNamespaceRule + UnexpectedNamespaceRule, } impl<'i> Error> { pub fn from(err: ParseError<'i, ParserError<'i>>, filename: String) -> Error> { let kind = match err.kind { - ParseErrorKind::Basic(b) => { - match &b { - BasicParseErrorKind:: - UnexpectedToken(t) => ParserError::UnexpectedToken(t.into()), - BasicParseErrorKind::EndOfInput => ParserError::EndOfInput, - BasicParseErrorKind::AtRuleInvalid(a) => ParserError::AtRuleInvalid(a.into()), - BasicParseErrorKind::AtRuleBodyInvalid => ParserError::AtRuleBodyInvalid, - BasicParseErrorKind::QualifiedRuleInvalid => ParserError::QualifiedRuleInvalid, - } - } - ParseErrorKind::Custom(c) => c + ParseErrorKind::Basic(b) => match &b { + BasicParseErrorKind::UnexpectedToken(t) => ParserError::UnexpectedToken(t.into()), + BasicParseErrorKind::EndOfInput => ParserError::EndOfInput, + BasicParseErrorKind::AtRuleInvalid(a) => ParserError::AtRuleInvalid(a.into()), + BasicParseErrorKind::AtRuleBodyInvalid => ParserError::AtRuleBodyInvalid, + BasicParseErrorKind::QualifiedRuleInvalid => ParserError::QualifiedRuleInvalid, + }, + ParseErrorKind::Custom(c) => c, }; Error { @@ -72,8 +69,8 @@ impl<'i> Error> { loc: Some(ErrorLocation { filename, line: err.location.line, - column: err.location.column - }) + column: err.location.column, + }), } } } @@ -97,9 +94,13 @@ impl<'i> ParserError<'i> { ParserError::InvalidNesting => "Invalid nesting".into(), ParserError::InvalidPageSelector => "Invalid page selector".into(), ParserError::InvalidValue => "Invalid value".into(), - ParserError::UnexpectedImportRule => "@import rules must precede all rules aside from @charset and @layer statements".into(), - ParserError::UnexpectedNamespaceRule => "@namespaces rules must precede all rules aside from @charset, @import, and @layer statements".into(), - ParserError::SelectorError(s) => s.reason() + ParserError::UnexpectedImportRule => { + "@import rules must precede all rules aside from @charset and @layer statements".into() + } + ParserError::UnexpectedNamespaceRule => { + "@namespaces rules must precede all rules aside from @charset, @import, and @layer statements".into() + } + ParserError::SelectorError(s) => s.reason(), } } } @@ -137,30 +138,44 @@ pub enum SelectorError<'i> { impl<'i> From> for SelectorError<'i> { fn from(err: SelectorParseErrorKind<'i>) -> Self { match &err { - SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t) => SelectorError::NoQualifiedNameInAttributeSelector(t.into()), + SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t) => { + SelectorError::NoQualifiedNameInAttributeSelector(t.into()) + } SelectorParseErrorKind::EmptySelector => SelectorError::EmptySelector, SelectorParseErrorKind::DanglingCombinator => SelectorError::DanglingCombinator, SelectorParseErrorKind::NonCompoundSelector => SelectorError::NonCompoundSelector, SelectorParseErrorKind::NonPseudoElementAfterSlotted => SelectorError::NonPseudoElementAfterSlotted, SelectorParseErrorKind::InvalidPseudoElementAfterSlotted => SelectorError::InvalidPseudoElementAfterSlotted, SelectorParseErrorKind::InvalidPseudoElementInsideWhere => SelectorError::InvalidPseudoElementInsideWhere, - SelectorParseErrorKind::InvalidPseudoClassBeforeWebKitScrollbar => SelectorError::InvalidPseudoClassBeforeWebKitScrollbar, - SelectorParseErrorKind::InvalidPseudoClassAfterWebKitScrollbar => SelectorError::InvalidPseudoClassAfterWebKitScrollbar, - SelectorParseErrorKind::InvalidPseudoClassAfterPseudoElement => SelectorError::InvalidPseudoClassAfterPseudoElement, + SelectorParseErrorKind::InvalidPseudoClassBeforeWebKitScrollbar => { + SelectorError::InvalidPseudoClassBeforeWebKitScrollbar + } + SelectorParseErrorKind::InvalidPseudoClassAfterWebKitScrollbar => { + SelectorError::InvalidPseudoClassAfterWebKitScrollbar + } + SelectorParseErrorKind::InvalidPseudoClassAfterPseudoElement => { + SelectorError::InvalidPseudoClassAfterPseudoElement + } SelectorParseErrorKind::InvalidState => SelectorError::InvalidState, SelectorParseErrorKind::MissingNestingSelector => SelectorError::MissingNestingSelector, SelectorParseErrorKind::MissingNestingPrefix => SelectorError::MissingNestingPrefix, - SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(t) => SelectorError::UnexpectedTokenInAttributeSelector(t.into()), + SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(t) => { + SelectorError::UnexpectedTokenInAttributeSelector(t.into()) + } SelectorParseErrorKind::PseudoElementExpectedColon(t) => SelectorError::PseudoElementExpectedColon(t.into()), SelectorParseErrorKind::PseudoElementExpectedIdent(t) => SelectorError::PseudoElementExpectedIdent(t.into()), SelectorParseErrorKind::NoIdentForPseudo(t) => SelectorError::NoIdentForPseudo(t.into()), - SelectorParseErrorKind::UnsupportedPseudoClassOrElement(t) => SelectorError::UnsupportedPseudoClassOrElement(t.into()), + SelectorParseErrorKind::UnsupportedPseudoClassOrElement(t) => { + SelectorError::UnsupportedPseudoClassOrElement(t.into()) + } SelectorParseErrorKind::UnexpectedIdent(t) => SelectorError::UnexpectedIdent(t.into()), SelectorParseErrorKind::ExpectedNamespace(t) => SelectorError::ExpectedNamespace(t.into()), SelectorParseErrorKind::ExpectedBarInAttr(t) => SelectorError::ExpectedBarInAttr(t.into()), SelectorParseErrorKind::BadValueInAttr(t) => SelectorError::BadValueInAttr(t.into()), SelectorParseErrorKind::InvalidQualNameInAttr(t) => SelectorError::InvalidQualNameInAttr(t.into()), - SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t) => SelectorError::ExplicitNamespaceUnexpectedToken(t.into()), + SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t) => { + SelectorError::ExplicitNamespaceUnexpectedToken(t.into()) + } SelectorParseErrorKind::ClassNeedsIdent(t) => SelectorError::ClassNeedsIdent(t.into()), } } @@ -196,7 +211,7 @@ impl<'i> SelectorError<'i> { #[derive(Debug, PartialEq)] pub struct ErrorWithLocation { pub kind: T, - pub loc: Location + pub loc: Location, } pub type MinifyError = ErrorWithLocation; @@ -206,15 +221,21 @@ pub type MinifyError = ErrorWithLocation; pub enum MinifyErrorKind { UnsupportedCustomMediaBooleanLogic { custom_media_loc: Location }, CustomMediaNotDefined { name: String }, - CircularCustomMedia { name: String } + CircularCustomMedia { name: String }, } impl MinifyErrorKind { pub fn reason(&self) -> String { match self { - MinifyErrorKind::UnsupportedCustomMediaBooleanLogic {..} => "Boolean logic with media types in @custom-media rules is not supported by Parcel CSS.".into(), - MinifyErrorKind::CustomMediaNotDefined { name, .. } => format!("Custom media query {} is not defined.", name), - MinifyErrorKind::CircularCustomMedia { name, .. } => format!("Circular custom media query {} detected.", name) + MinifyErrorKind::UnsupportedCustomMediaBooleanLogic { .. } => { + "Boolean logic with media types in @custom-media rules is not supported by Parcel CSS.".into() + } + MinifyErrorKind::CustomMediaNotDefined { name, .. } => { + format!("Custom media query {} is not defined.", name) + } + MinifyErrorKind::CircularCustomMedia { name, .. } => { + format!("Circular custom media query {} detected.", name) + } } } } @@ -227,14 +248,14 @@ pub enum PrinterErrorKind { FmtError, InvalidComposesSelector, InvalidComposesNesting, - AmbiguousUrlInCustomProperty { url: String } + AmbiguousUrlInCustomProperty { url: String }, } impl From for PrinterError { fn from(_: std::fmt::Error) -> PrinterError { PrinterError { kind: PrinterErrorKind::FmtError, - loc: None + loc: None, } } } diff --git a/src/lib.rs b/src/lib.rs index 6f2de477..bf254697 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,37 +1,37 @@ -mod parser; -pub mod rules; -pub mod declaration; -pub mod media_query; -mod selector; -pub mod properties; -pub mod values; -mod printer; -mod traits; -mod macros; -pub mod stylesheet; +pub mod bundler; mod compat; -mod prefixes; -pub mod vendor_prefix; -pub mod targets; +mod context; pub mod css_modules; +pub mod declaration; pub mod dependencies; pub mod error; mod logical; -pub mod bundler; -mod context; +mod macros; +pub mod media_query; +mod parser; +mod prefixes; +mod printer; +pub mod properties; +pub mod rules; +mod selector; +pub mod stylesheet; +pub mod targets; +mod traits; +pub mod values; +pub mod vendor_prefix; #[cfg(test)] mod tests { + use crate::css_modules::{CssModuleExport, CssModuleExports, CssModuleReference}; use crate::dependencies::Dependency; - use crate::error::{Error, MinifyErrorKind, ErrorLocation, ParserError, PrinterErrorKind, SelectorError}; + use crate::error::{Error, ErrorLocation, MinifyErrorKind, ParserError, PrinterErrorKind, SelectorError}; use crate::properties::custom::Token; use crate::rules::CssRule; + use crate::rules::Location; use crate::stylesheet::*; use crate::targets::Browsers; - use crate::rules::Location; use indoc::indoc; - use std::{collections::HashMap}; - use crate::css_modules::{CssModuleExports, CssModuleExport, CssModuleReference}; + use std::collections::HashMap; fn test(source: &str, expected: &str) { let mut stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions::default()).unwrap(); @@ -43,21 +43,45 @@ mod tests { fn minify_test(source: &str, expected: &str) { let mut stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions::default()).unwrap(); stylesheet.minify(MinifyOptions::default()).unwrap(); - let res = stylesheet.to_css(PrinterOptions { minify: true, ..PrinterOptions::default() }).unwrap(); + let res = stylesheet + .to_css(PrinterOptions { + minify: true, + ..PrinterOptions::default() + }) + .unwrap(); assert_eq!(res.code, expected); } fn prefix_test(source: &str, expected: &str, targets: Browsers) { let mut stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions::default()).unwrap(); - stylesheet.minify(MinifyOptions { targets: Some(targets), ..MinifyOptions::default() }).unwrap(); - let res = stylesheet.to_css(PrinterOptions { targets: Some(targets), ..PrinterOptions::default() }).unwrap(); + stylesheet + .minify(MinifyOptions { + targets: Some(targets), + ..MinifyOptions::default() + }) + .unwrap(); + let res = stylesheet + .to_css(PrinterOptions { + targets: Some(targets), + ..PrinterOptions::default() + }) + .unwrap(); assert_eq!(res.code, expected); } fn attr_test(source: &str, expected: &str, minify: bool, targets: Option) { let mut attr = StyleAttribute::parse(source).unwrap(); - attr.minify(MinifyOptions { targets, ..MinifyOptions::default() }); - let res = attr.to_css(PrinterOptions { targets, minify, ..PrinterOptions::default() }).unwrap(); + attr.minify(MinifyOptions { + targets, + ..MinifyOptions::default() + }); + let res = attr + .to_css(PrinterOptions { + targets, + minify, + ..PrinterOptions::default() + }) + .unwrap(); assert_eq!(res.code, expected); } @@ -66,21 +90,55 @@ mod tests { chrome: Some(95 << 16), ..Browsers::default() }); - let mut stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions { nesting: true, ..ParserOptions::default() }).unwrap(); - stylesheet.minify(MinifyOptions { targets, ..MinifyOptions::default() }).unwrap(); - let res = stylesheet.to_css(PrinterOptions { targets, ..PrinterOptions::default() }).unwrap(); + let mut stylesheet = StyleSheet::parse( + "test.css".into(), + &source, + ParserOptions { + nesting: true, + ..ParserOptions::default() + }, + ) + .unwrap(); + stylesheet + .minify(MinifyOptions { + targets, + ..MinifyOptions::default() + }) + .unwrap(); + let res = stylesheet + .to_css(PrinterOptions { + targets, + ..PrinterOptions::default() + }) + .unwrap(); assert_eq!(res.code, expected); } fn nesting_test_no_targets(source: &str, expected: &str) { - let mut stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions { nesting: true, ..ParserOptions::default() }).unwrap(); + let mut stylesheet = StyleSheet::parse( + "test.css".into(), + &source, + ParserOptions { + nesting: true, + ..ParserOptions::default() + }, + ) + .unwrap(); stylesheet.minify(MinifyOptions::default()).unwrap(); let res = stylesheet.to_css(PrinterOptions::default()).unwrap(); assert_eq!(res.code, expected); } fn css_modules_test(source: &str, expected: &str, expected_exports: CssModuleExports) { - let mut stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions { css_modules: true, ..ParserOptions::default() }).unwrap(); + let mut stylesheet = StyleSheet::parse( + "test.css".into(), + &source, + ParserOptions { + css_modules: true, + ..ParserOptions::default() + }, + ) + .unwrap(); stylesheet.minify(MinifyOptions::default()).unwrap(); let res = stylesheet.to_css(PrinterOptions::default()).unwrap(); assert_eq!(res.code, expected); @@ -88,11 +146,24 @@ mod tests { } fn custom_media_test(source: &str, expected: &str) { - let mut stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions { custom_media: true, ..ParserOptions::default() }).unwrap(); - stylesheet.minify(MinifyOptions { - targets: Some(Browsers { chrome: Some(95 << 16), ..Browsers::default() }), - ..MinifyOptions::default() - }).unwrap(); + let mut stylesheet = StyleSheet::parse( + "test.css".into(), + &source, + ParserOptions { + custom_media: true, + ..ParserOptions::default() + }, + ) + .unwrap(); + stylesheet + .minify(MinifyOptions { + targets: Some(Browsers { + chrome: Some(95 << 16), + ..Browsers::default() + }), + ..MinifyOptions::default() + }) + .unwrap(); let res = stylesheet.to_css(PrinterOptions::default()).unwrap(); assert_eq!(res.code, expected); } @@ -101,7 +172,7 @@ mod tests { let res = StyleSheet::parse("test.css".into(), &source, ParserOptions::default()); match res { Ok(_) => unreachable!(), - Err(e) => assert_eq!(e.kind, error) + Err(e) => assert_eq!(e.kind, error), } } @@ -158,500 +229,617 @@ mod tests { #[test] pub fn test_border() { - test(r#" + test( + r#" .foo { border-left: 2px solid red; border-right: 2px solid red; border-bottom: 2px solid red; border-top: 2px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: 2px solid red; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-left-color: red; border-right-color: red; border-bottom-color: red; border-top-color: red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-color: red; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-left-width: thin; border-right-width: thin; border-bottom-width: thin; border-top-width: thin; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-width: thin; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-left-style: dotted; border-right-style: dotted; border-bottom-style: dotted; border-top-style: dotted; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-style: dotted; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-left-width: thin; border-left-style: dotted; border-left-color: red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-left: thin dotted red; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-left-width: thick; border-left: thin dotted red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-left: thin dotted red; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-left-width: thick; border: thin dotted red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: thin dotted red; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border: thin dotted red; border-right-width: thick; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: thin dotted red; border-right-width: thick; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border: thin dotted red; border-right: thick dotted red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: thin dotted red; border-right-width: thick; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border: thin dotted red; border-right-width: thick; border-right-style: solid; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: thin dotted red; border-right: thick solid red; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-top: thin dotted red; border-block-start: thick solid green; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-top: thin dotted red; border-block-start: thick solid green; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border: thin dotted red; border-block-start-width: thick; border-left-width: medium; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: thin dotted red; border-block-start-width: thick; border-left-width: medium; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-block-start: thin dotted red; border-inline-end: thin dotted red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-block-start: thin dotted red; border-inline-end: thin dotted red; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-block-start-width: thin; border-block-start-style: dotted; border-block-start-color: red; border-inline-end: thin dotted red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-block-start: thin dotted red; border-inline-end: thin dotted red; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-block-start: thin dotted red; border-block-end: thin dotted red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-block: thin dotted red; } "# - }); + }, + ); - minify_test(r#" + minify_test( + r#" .foo { border: none; } - "#, indoc! {".foo{border:0}" - }); + "#, + indoc! {".foo{border:0}" + }, + ); minify_test(".foo { border-width: 0 0 1px; }", ".foo{border-width:0 0 1px}"); - test(r#" + test( + r#" .foo { border-block-width: 1px; border-inline-width: 1px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-width: 1px; } "# - }); - test(r#" + }, + ); + test( + r#" .foo { border-block-start-width: 1px; border-block-end-width: 1px; border-inline-start-width: 1px; border-inline-end-width: 1px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-width: 1px; } "# - }); - test(r#" + }, + ); + test( + r#" .foo { border-block-start-width: 1px; border-block-end-width: 1px; border-inline-start-width: 2px; border-inline-end-width: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-block-width: 1px; border-inline-width: 2px; } "# - }); - test(r#" + }, + ); + test( + r#" .foo { border-block-start-width: 1px; border-block-end-width: 1px; border-inline-start-width: 2px; border-inline-end-width: 3px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-block-width: 1px; border-inline-start-width: 2px; border-inline-end-width: 3px; } "# - }); + }, + ); - minify_test(".foo { border-bottom: 1px solid var(--spectrum-global-color-gray-200)}", ".foo{border-bottom:1px solid var(--spectrum-global-color-gray-200)}"); - test(r#" + minify_test( + ".foo { border-bottom: 1px solid var(--spectrum-global-color-gray-200)}", + ".foo{border-bottom:1px solid var(--spectrum-global-color-gray-200)}", + ); + test( + r#" .foo { border-width: 0; border-bottom: var(--test, 1px) solid; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-width: 0; border-bottom: var(--test, 1px) solid; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border: 1px solid black; border-width: 1px 1px 0 0; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: 1px solid #000; border-width: 1px 1px 0 0; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { border-top: 1px solid black; border-bottom: 1px solid black; border-left: 2px solid black; border-right: 2px solid black; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: 1px solid #000; border-width: 1px 2px; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { border-top: 1px solid black; border-bottom: 1px solid black; border-left: 2px solid black; border-right: 1px solid black; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: 1px solid #000; border-left-width: 2px; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { border-top: 1px solid black; border-bottom: 1px solid black; border-left: 1px solid red; border-right: 1px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: 1px solid #000; border-color: #000 red; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { border-block-start: 1px solid black; border-block-end: 1px solid black; border-inline-start: 1px solid red; border-inline-end: 1px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: 1px solid #000; border-inline-color: red; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { border-block-start: 1px solid black; border-block-end: 1px solid black; border-inline-start: 2px solid black; border-inline-end: 2px solid black; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: 1px solid #000; border-inline-width: 2px; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { border-block-start: 1px solid black; border-block-end: 1px solid black; border-inline-start: 2px solid red; border-inline-end: 2px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: 1px solid #000; border-inline: 2px solid red; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { border-block-start: 1px solid black; border-block-end: 1px solid black; border-inline-start: 2px solid red; border-inline-end: 3px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: 1px solid #000; border-inline-start: 2px solid red; border-inline-end: 3px solid red; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { border-block-start: 2px solid black; border-block-end: 1px solid black; border-inline-start: 2px solid red; border-inline-end: 2px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: 2px solid red; border-block-start-color: #000; border-block-end: 1px solid #000; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { border-block-start: 2px solid red; border-block-end: 1px solid red; border-inline-start: 2px solid red; border-inline-end: 2px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: 2px solid red; border-block-end-width: 1px; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { border-block-start: 2px solid red; border-block-end: 2px solid red; border-inline-start: 2px solid red; border-inline-end: 1px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: 2px solid red; border-inline-end-width: 1px; } - "#}); + "#}, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-block: 2px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-top: 2px solid red; border-bottom: 2px solid red; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-block-start: 2px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-top: 2px solid red; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" - .foo { + prefix_test( + r#" + .foo { border-block-end: 2px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-bottom: 2px solid red; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-inline: 2px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-left: 2px solid red; border-right: 2px solid red; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-inline-start: 2px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-left: var(--ltr, 2px solid red); border-right: var(--rtl, 2px solid red); @@ -667,16 +855,20 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-inline-start-width: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-left-width: var(--ltr, 2px); border-right-width: var(--rtl, 2px); @@ -692,16 +884,20 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-inline-end: 2px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-right: var(--ltr, 2px solid red); border-left: var(--rtl, 2px solid red); @@ -717,17 +913,21 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-inline-start: 2px solid red; border-inline-end: 5px solid green; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-left: var(--ltr, 2px solid red) var(--rtl, 5px solid green); border-right: var(--ltr, 5px solid green) var(--rtl, 2px solid red); @@ -743,12 +943,15 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-inline-start: 2px solid red; border-inline-end: 5px solid green; @@ -758,7 +961,8 @@ mod tests { border-inline-start: 1px dotted gray; border-inline-end: 1px solid black; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-left: var(--ltr, 2px solid red) var(--rtl, 5px solid green); border-right: var(--ltr, 5px solid green) var(--rtl, 2px solid red); @@ -779,16 +983,20 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-inline-end: var(--test); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-right: var(--ltr, var(--test)); border-left: var(--rtl, var(--test)); @@ -804,17 +1012,21 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-inline-start: var(--start); border-inline-end: var(--end); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-left: var(--ltr, var(--start)) var(--rtl, var(--end)); border-right: var(--ltr, var(--end)) var(--rtl, var(--start)); @@ -830,49 +1042,113 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - for prop in &["border-inline-start-color", "border-inline-end-color", "border-block-start-color", "border-block-end-color", "border-top-color", "border-bottom-color", "border-left-color", "border-right-color", "border-color", "border-block-color", "border-inline-color"] { - prefix_test(&format!(r#" + for prop in &[ + "border-inline-start-color", + "border-inline-end-color", + "border-block-start-color", + "border-block-end-color", + "border-top-color", + "border-bottom-color", + "border-left-color", + "border-right-color", + "border-color", + "border-block-color", + "border-inline-color", + ] { + prefix_test( + &format!( + r#" .foo {{ {}: lab(40% 56.6 39); }} - "#, prop), &format!(indoc! {r#" + "#, + prop + ), + &format!( + indoc! {r#" .foo {{ {}: #b32323; {}: lab(40% 56.6 39); }} - "#}, prop, prop), Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + prop, prop + ), + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); } - for prop in &["border", "border-inline", "border-block", "border-left", "border-right", "border-top", "border-bottom", "border-block-start", "border-block-end", "border-inline-start", "border-inline-end"] { - prefix_test(&format!(r#" + for prop in &[ + "border", + "border-inline", + "border-block", + "border-left", + "border-right", + "border-top", + "border-bottom", + "border-block-start", + "border-block-end", + "border-inline-start", + "border-inline-end", + ] { + prefix_test( + &format!( + r#" .foo {{ {}: 2px solid lab(40% 56.6 39); }} - "#, prop), &format!(indoc! {r#" + "#, + prop + ), + &format!( + indoc! {r#" .foo {{ {}: 2px solid #b32323; {}: 2px solid lab(40% 56.6 39); }} - "#}, prop, prop), Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + prop, prop + ), + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); } - for prop in &["border", "border-inline", "border-block", "border-left", "border-right", "border-top", "border-bottom", "border-block-start", "border-block-end", "border-inline-start", "border-inline-end"] { - prefix_test(&format!(r#" + for prop in &[ + "border", + "border-inline", + "border-block", + "border-left", + "border-right", + "border-top", + "border-bottom", + "border-block-start", + "border-block-end", + "border-inline-start", + "border-inline-end", + ] { + prefix_test( + &format!( + r#" .foo {{ {}: var(--border-width) solid lab(40% 56.6 39); }} - "#, prop), &format!(indoc! {r#" + "#, + prop + ), + &format!( + indoc! {r#" .foo {{ {}: var(--border-width) solid #b32323; }} @@ -882,17 +1158,23 @@ mod tests { {}: var(--border-width) solid lab(40% 56.6 39); }} }} - "#}, prop, prop), Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + prop, prop + ), + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); } - prefix_test(r#" + prefix_test( + r#" .foo { border-inline-start-color: lab(40% 56.6 39); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-left-color: var(--ltr, #b32323); border-right-color: var(--rtl, #b32323); @@ -914,16 +1196,20 @@ mod tests { --ltr: ; --rtl: initial; } - "#}, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-inline-end-color: lab(40% 56.6 39); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-right-color: var(--ltr, #b32323); border-left-color: var(--rtl, #b32323); @@ -945,17 +1231,21 @@ mod tests { --ltr: ; --rtl: initial; } - "#}, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-inline-start-color: lab(40% 56.6 39); border-inline-end-color: lch(50.998% 135.363 338); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-left-color: var(--ltr, #b32323) var(--rtl, #ee00be); border-right-color: var(--ltr, #ee00be) var(--rtl, #b32323); @@ -977,17 +1267,21 @@ mod tests { --ltr: ; --rtl: initial; } - "#}, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-inline-start-color: lab(40% 56.6 39); border-inline-end-color: lch(50.998% 135.363 338); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-left-color: var(--ltr, #b32323) var(--rtl, #ee00be); border-right-color: var(--ltr, #ee00be) var(--rtl, #b32323); @@ -1016,17 +1310,21 @@ mod tests { --ltr: ; --rtl: initial; } - "#}, Browsers { - chrome: Some(8 << 16), - safari: Some(14 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(8 << 16), + safari: Some(14 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-inline-start: 2px solid lab(40% 56.6 39); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-left: var(--ltr, 2px solid #b32323); border-right: var(--rtl, 2px solid #b32323); @@ -1048,16 +1346,20 @@ mod tests { --ltr: ; --rtl: initial; } - "#}, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-inline-end: 2px solid lab(40% 56.6 39); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-right: var(--ltr, 2px solid #b32323); border-left: var(--rtl, 2px solid #b32323); @@ -1079,16 +1381,20 @@ mod tests { --ltr: ; --rtl: initial; } - "#}, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-inline-end: var(--border-width) solid lab(40% 56.6 39); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-right: var(--ltr, var(--border-width) solid #b32323); border-left: var(--rtl, var(--border-width) solid #b32323); @@ -1110,38 +1416,47 @@ mod tests { --ltr: ; --rtl: initial; } - "#}, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); } #[test] pub fn test_border_image() { - test(r#" + test( + r#" .foo { border-image: url(test.png) 60; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-image: url(test.png) 60; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-image: url(test.png) 60; border-image-source: url(foo.png); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-image: url(foo.png) 60; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-image-source: url(foo.png); border-image-slice: 10 40 10 40 fill; @@ -1149,93 +1464,115 @@ mod tests { border-image-outset: 0; border-image-repeat: round round; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-image: url(foo.png) 10 40 fill / 10px round; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-image: url(foo.png) 60; border-image-source: var(--test); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-image: url(foo.png) 60; border-image-source: var(--test); } "# - }); + }, + ); - test(r#" + test( + r#" .foo { -webkit-border-image: url("test.png") 60; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-image: url(test.png) 60; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { -webkit-border-image: url("test.png") 60; border-image: url("test.png") 60; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-image: url(test.png) 60; border-image: url(test.png) 60; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { -webkit-border-image: url("test.png") 60; border-image-source: url(foo.png); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-image: url(test.png) 60; border-image-source: url(foo.png); } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border: 1px solid red; border-image: url(test.png) 60; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: 1px solid red; border-image: url(test.png) 60; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-image: url(test.png) 60; border: 1px solid red; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border: 1px solid red; } "# - }); + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-image: url("test.png") 60; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-image: url(test.png) 60; -moz-border-image: url(test.png) 60; @@ -1243,34 +1580,42 @@ mod tests { border-image: url(test.png) 60; } "# - }, Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - opera: Some(12 << 16), - ..Browsers::default() - }); - - prefix_test(r#" + }, + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + opera: Some(12 << 16), + ..Browsers::default() + }, + ); + + prefix_test( + r#" .foo { border-image: url(foo.png) 10 40 fill / 10px round; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-image: url(foo.png) 10 40 fill / 10px round; } "# - }, Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - opera: Some(12 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + opera: Some(12 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-image: var(--test) 60; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-image: var(--test) 60; -moz-border-image: var(--test) 60; @@ -1278,35 +1623,43 @@ mod tests { border-image: var(--test) 60; } "# - }, Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - opera: Some(12 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + opera: Some(12 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { -webkit-border-image: url(foo.png) 60; -moz-border-image: url(foo.png) 60; -o-border-image: url(foo.png) 60; border-image: url(foo.png) 60; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-image: url(foo.png) 60; } "# - }, Browsers { - chrome: Some(15 << 16), - ..Browsers::default() - }); + }, + Browsers { + chrome: Some(15 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)) 60; -webkit-border-image: -webkit-linear-gradient(#ff0f0e, #7773ff) 60; @@ -1314,16 +1667,20 @@ mod tests { border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60; } "# - }, Browsers { - chrome: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + chrome: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ff0f0e), to(#7773ff)) 60; -webkit-border-image: -webkit-linear-gradient(#ff0f0e, #7773ff) 60; @@ -1332,17 +1689,21 @@ mod tests { border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60; } "# - }, Browsers { - chrome: Some(8 << 16), - firefox: Some(4 << 16), - ..Browsers::default() - }); + }, + Browsers { + chrome: Some(8 << 16), + firefox: Some(4 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-image: -webkit-linear-gradient(#ff0f0e, #7773ff) 60; border-image: -moz-linear-gradient(#ff0f0e, #7773ff) 60; @@ -1350,33 +1711,41 @@ mod tests { border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 60; } "# - }, Browsers { - chrome: Some(15 << 16), - firefox: Some(15 << 16), - ..Browsers::default() - }); + }, + Browsers { + chrome: Some(15 << 16), + firefox: Some(15 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-image-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-image-source: -webkit-linear-gradient(#ff0f0e, #7773ff); border-image-source: linear-gradient(#ff0f0e, #7773ff); border-image-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)); } "# - }, Browsers { - chrome: Some(15 << 16), - ..Browsers::default() - }); + }, + Browsers { + chrome: Some(15 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) var(--foo); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-image: linear-gradient(#ff0f0e, #7773ff) var(--foo); } @@ -1387,76 +1756,94 @@ mod tests { } } "# - }, Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + }, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); } #[test] pub fn test_border_radius() { - test(r#" + test( + r#" .foo { border-radius: 10px 100px 10px 100px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-radius: 10px 100px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-radius: 10px 100px 10px 100px / 120px 120px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-radius: 10px 100px / 120px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-top-left-radius: 10px 120px; border-top-right-radius: 100px 120px; border-bottom-left-radius: 10px 120px; border-bottom-right-radius: 100px 120px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-radius: 10px 100px / 120px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-radius: 10px 100px 10px 100px / 120px 120px; border-start-start-radius: 10px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-radius: 10px 100px / 120px; border-start-start-radius: 10px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-start-start-radius: 10px; border-radius: 10px 100px 10px 100px / 120px 120px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-radius: 10px 100px / 120px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-top-left-radius: 10px 120px; border-top-right-radius: 100px 120px; @@ -1464,7 +1851,8 @@ mod tests { border-bottom-left-radius: 10px 120px; border-bottom-right-radius: 100px 120px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-top-left-radius: 10px 120px; border-top-right-radius: 100px 120px; @@ -1473,171 +1861,209 @@ mod tests { border-bottom-right-radius: 100px 120px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-radius: 10px; border-top-left-radius: 20px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-radius: 20px 10px 10px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { border-radius: 10px; border-top-left-radius: var(--test); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-radius: 10px; border-top-left-radius: var(--test); } "# - }); + }, + ); - test(r#" + test( + r#" .foo { -webkit-border-radius: 10px 100px 10px 100px; -moz-border-radius: 10px 100px 10px 100px; border-radius: 10px 100px 10px 100px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-radius: 10px 100px; -moz-border-radius: 10px 100px; border-radius: 10px 100px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { -webkit-border-radius: 10px 100px 10px 100px; -moz-border-radius: 20px; border-radius: 30px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-radius: 10px 100px; -moz-border-radius: 20px; border-radius: 30px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { -webkit-border-top-left-radius: 10px; -moz-border-top-left-radius: 10px; border-top-left-radius: 10px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-top-left-radius: 10px; -moz-border-top-left-radius: 10px; border-top-left-radius: 10px; } "# - }); + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-radius: 30px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-radius: 30px; -moz-border-radius: 30px; border-radius: 30px; } "# - }, Browsers { - safari: Some(4 << 16), - firefox: Some(3 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(4 << 16), + firefox: Some(3 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-top-left-radius: 30px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-top-left-radius: 30px; -moz-border-top-left-radius: 30px; border-top-left-radius: 30px; } "# - }, Browsers { - safari: Some(4 << 16), - firefox: Some(3 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(4 << 16), + firefox: Some(3 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { -webkit-border-radius: 30px; -moz-border-radius: 30px; border-radius: 30px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-radius: 30px; } "# - }, Browsers { - safari: Some(14 << 16), - firefox: Some(46 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(14 << 16), + firefox: Some(46 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { -webkit-border-top-left-radius: 30px; -moz-border-top-left-radius: 30px; border-top-left-radius: 30px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-top-left-radius: 30px; } "# - }, Browsers { - safari: Some(14 << 16), - firefox: Some(46 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(14 << 16), + firefox: Some(46 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { -webkit-border-radius: 30px; -moz-border-radius: 30px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-radius: 30px; -moz-border-radius: 30px; } "# - }, Browsers { - safari: Some(14 << 16), - firefox: Some(46 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(14 << 16), + firefox: Some(46 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { -webkit-border-top-left-radius: 30px; -moz-border-top-right-radius: 30px; border-bottom-left-radius: 30px; border-bottom-right-radius: 30px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-top-left-radius: 30px; -moz-border-top-right-radius: 30px; @@ -1645,34 +2071,42 @@ mod tests { border-bottom-right-radius: 30px; } "# - }, Browsers { - safari: Some(14 << 16), - firefox: Some(46 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(14 << 16), + firefox: Some(46 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-radius: var(--test); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-border-radius: var(--test); -moz-border-radius: var(--test); border-radius: var(--test); } "# - }, Browsers { - safari: Some(4 << 16), - firefox: Some(3 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(4 << 16), + firefox: Some(3 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-start-start-radius: 5px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-top-left-radius: var(--ltr, 5px); border-top-right-radius: var(--rtl, 5px); @@ -1688,17 +2122,21 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(12 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(12 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-start-start-radius: 5px; border-start-end-radius: 10px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-top-left-radius: var(--ltr, 5px) var(--rtl, 10px); border-top-right-radius: var(--ltr, 10px) var(--rtl, 5px); @@ -1714,17 +2152,21 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(12 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(12 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-end-start-radius: 5px; border-end-end-radius: 10px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-bottom-left-radius: var(--ltr, 5px) var(--rtl, 10px); border-bottom-right-radius: var(--ltr, 10px) var(--rtl, 5px); @@ -1740,16 +2182,20 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(12 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(12 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-start-start-radius: var(--radius); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-top-left-radius: var(--ltr, var(--radius)); border-top-right-radius: var(--rtl, var(--radius)); @@ -1765,17 +2211,21 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(12 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(12 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { border-start-start-radius: var(--start); border-start-end-radius: var(--end); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { border-top-left-radius: var(--ltr, var(--start)) var(--rtl, var(--end)); border-top-right-radius: var(--ltr, var(--end)) var(--rtl, var(--start)); @@ -1791,62 +2241,76 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(12 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(12 << 16), + ..Browsers::default() + }, + ); } #[test] pub fn test_outline() { - test(r#" + test( + r#" .foo { outline-width: 2px; outline-style: solid; outline-color: blue; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { outline: 2px solid #00f; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { outline: 2px solid blue; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { outline: 2px solid #00f; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { outline: 2px solid red; outline-color: blue; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { outline: 2px solid #00f; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { outline: 2px solid yellow; outline-color: var(--color); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { outline: 2px solid #ff0; outline-color: var(--color); } "# - }); + }, + ); prefix_test( ".foo { outline-color: lab(40% 56.6 39) }", @@ -1859,7 +2323,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -1873,7 +2337,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -1892,39 +2356,46 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); } #[test] pub fn test_margin() { - test(r#" + test( + r#" .foo { margin-left: 10px; margin-right: 10px; margin-top: 20px; margin-bottom: 20px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { margin: 20px 10px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { margin-block-start: 15px; margin-block-end: 15px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { margin-block: 15px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { margin-left: 10px; margin-right: 10px; @@ -1934,7 +2405,8 @@ mod tests { margin-bottom: 20px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { margin-left: 10px; margin-right: 10px; @@ -1943,38 +2415,47 @@ mod tests { margin-bottom: 20px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { margin: 10px; margin-top: 20px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { margin: 20px 10px 10px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { margin: 10px; margin-top: var(--top); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { margin: 10px; margin-top: var(--top); } "# - }); + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { margin-inline-start: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { margin-left: var(--ltr, 2px); margin-right: var(--rtl, 2px); @@ -1990,17 +2471,21 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { margin-inline-start: 2px; margin-inline-end: 4px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { margin-left: var(--ltr, 2px) var(--rtl, 4px); margin-right: var(--ltr, 4px) var(--rtl, 2px); @@ -2016,84 +2501,105 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { margin-inline: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { margin-left: 2px; margin-right: 2px; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { margin-block-start: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { margin-top: 2px; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { margin-block-end: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { margin-bottom: 2px; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); } #[test] pub fn test_padding() { - test(r#" + test( + r#" .foo { padding-left: 10px; padding-right: 10px; padding-top: 20px; padding-bottom: 20px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { padding: 20px 10px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { padding-block-start: 15px; padding-block-end: 15px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { padding-block: 15px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { padding-left: 10px; padding-right: 10px; @@ -2103,7 +2609,8 @@ mod tests { padding-bottom: 20px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { padding-left: 10px; padding-right: 10px; @@ -2112,13 +2619,16 @@ mod tests { padding-bottom: 20px; } "# - }); + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { padding-inline-start: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { padding-left: var(--ltr, 2px); padding-right: var(--rtl, 2px); @@ -2134,17 +2644,21 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { padding-inline-start: 2px; padding-inline-end: 4px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { padding-left: var(--ltr, 2px) var(--rtl, 4px); padding-right: var(--ltr, 4px) var(--rtl, 2px); @@ -2160,151 +2674,186 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { padding-inline: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { padding-left: 2px; padding-right: 2px; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { padding-block-start: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { padding-top: 2px; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { padding-block-end: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { padding-bottom: 2px; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { padding-top: 1px; padding-left: 2px; padding-bottom: 3px; padding-right: 4px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { padding: 1px 4px 3px 2px; } - "#}, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); } #[test] fn test_scroll_padding() { - prefix_test(r#" + prefix_test( + r#" .foo { scroll-padding-inline: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { scroll-padding-inline: 2px; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); } #[test] fn test_size() { - prefix_test(r#" + prefix_test( + r#" .foo { block-size: 25px; inline-size: 25px; min-block-size: 25px; min-inline-size: 25px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { height: 25px; width: 25px; min-height: 25px; min-width: 25px; } - "#}, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { block-size: 25px; inline-size: 25px; min-block-size: 25px; min-inline-size: 25px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { block-size: 25px; inline-size: 25px; min-block-size: 25px; min-inline-size: 25px; } - "#}, Browsers { - safari: Some(14 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(14 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { block-size: var(--size); inline-size: var(--size); min-block-size: var(--size); min-inline-size: var(--size); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { height: var(--size); width: var(--size); min-height: var(--size); min-width: var(--size); } - "#}, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); } #[test] pub fn test_background() { - test(r#" + test( + r#" .foo { background: url(img.png); background-position-x: 20px; @@ -2312,14 +2861,17 @@ mod tests { background-size: 50px 100px; background-repeat: repeat no-repeat; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: url(img.png) 20px 10px / 50px 100px repeat-x; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { background-color: red; background-position: 0% 0%; @@ -2330,14 +2882,17 @@ mod tests { background-attachment: scroll; background-image: none } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: red; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { background-color: gray; background-position: 40% 50%; @@ -2348,14 +2903,17 @@ mod tests { background-attachment: fixed; background-image: url('chess.png'); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: gray url(chess.png) 40% / 10em round fixed border-box; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { background: url(img.png), url(test.jpg) gray; background-position-x: right 20px, 10px; @@ -2363,530 +2921,682 @@ mod tests { background-size: 50px 50px, auto; background-repeat: repeat no-repeat, no-repeat; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: url(img.png) right 20px top 20px / 50px 50px repeat-x, gray url(test.jpg) 10px 15px no-repeat; } "# - }); + }, + ); - minify_test(r#" + minify_test( + r#" .foo { background-position: center center; } - "#, indoc! {".foo{background-position:50%}" - }); + "#, + indoc! {".foo{background-position:50%}" + }, + ); - test(r#" + test( + r#" .foo { background: url(img.png) gray; background-clip: content-box; -webkit-background-clip: text; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: gray url(img.png) padding-box content-box; -webkit-background-clip: text; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { background: url(img.png) gray; -webkit-background-clip: text; background-clip: content-box; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: gray url(img.png); -webkit-background-clip: text; background-clip: content-box; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { background: url(img.png) gray; background-position: var(--pos); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: gray url(img.png); background-position: var(--pos); } "# - }); + }, + ); - minify_test(".foo { background-position: bottom left }", ".foo{background-position:0 100%}"); - minify_test(".foo { background-position: left 10px center }", ".foo{background-position:10px 50%}"); - minify_test(".foo { background-position: right 10px center }", ".foo{background-position:right 10px center}"); - minify_test(".foo { background-position: right 10px top 20px }", ".foo{background-position:right 10px top 20px}"); - minify_test(".foo { background-position: left 10px top 20px }", ".foo{background-position:10px 20px}"); - minify_test(".foo { background-position: left 10px bottom 20px }", ".foo{background-position:left 10px bottom 20px}"); - minify_test(".foo { background-position: left 10px top }", ".foo{background-position:10px 0}"); - minify_test(".foo { background-position: bottom right }", ".foo{background-position:100% 100%}"); + minify_test( + ".foo { background-position: bottom left }", + ".foo{background-position:0 100%}", + ); + minify_test( + ".foo { background-position: left 10px center }", + ".foo{background-position:10px 50%}", + ); + minify_test( + ".foo { background-position: right 10px center }", + ".foo{background-position:right 10px center}", + ); + minify_test( + ".foo { background-position: right 10px top 20px }", + ".foo{background-position:right 10px top 20px}", + ); + minify_test( + ".foo { background-position: left 10px top 20px }", + ".foo{background-position:10px 20px}", + ); + minify_test( + ".foo { background-position: left 10px bottom 20px }", + ".foo{background-position:left 10px bottom 20px}", + ); + minify_test( + ".foo { background-position: left 10px top }", + ".foo{background-position:10px 0}", + ); + minify_test( + ".foo { background-position: bottom right }", + ".foo{background-position:100% 100%}", + ); - minify_test(".foo { background: url('img-sprite.png') no-repeat bottom right }", ".foo{background:url(img-sprite.png) 100% 100% no-repeat}"); + minify_test( + ".foo { background: url('img-sprite.png') no-repeat bottom right }", + ".foo{background:url(img-sprite.png) 100% 100% no-repeat}", + ); minify_test(".foo { background: transparent }", ".foo{background:0 0}"); minify_test(".foo { background: url(\"data:image/svg+xml,%3Csvg width='168' height='24' xmlns='http://www.w3.org/2000/svg'%3E%3C/svg%3E\") }", ".foo{background:url(\"data:image/svg+xml,%3Csvg width='168' height='24' xmlns='http://www.w3.org/2000/svg'%3E%3C/svg%3E\")}"); - - test(r#" + + test( + r#" .foo { background: url(img.png); background-clip: text; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: url(img.png) text; } "# - }); + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { background: url(img.png); background-clip: text; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: url(img.png); -webkit-background-clip: text; background-clip: text; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { background: url(img.png); background-clip: text; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: url(img.png) text; } "# - }, Browsers { - safari: Some(14 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(14 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { background: url(img.png); background-clip: text; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: url(img.png); -webkit-background-clip: text; background-clip: text; } "# - }, Browsers { - safari: Some(14 << 16), - chrome: Some(95 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(14 << 16), + chrome: Some(95 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { background-image: url(img.png); background-clip: text; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background-image: url(img.png); -webkit-background-clip: text; background-clip: text; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { background-image: url(img.png); background-clip: text; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background-image: url(img.png); background-clip: text; } "# - }, Browsers { - safari: Some(14 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(14 << 16), + ..Browsers::default() + }, + ); minify_test(".foo { background: none center }", ".foo{background:50%}"); minify_test(".foo { background: none }", ".foo{background:0 0}"); - - prefix_test(r#" + + prefix_test( + r#" .foo { background: lab(51.5117% 43.3777 -29.0443); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: #af5cae; background: lab(51.5117% 43.3777 -29.0443); } "# - }, Browsers { - chrome: Some(95 << 16), - safari: Some(15 << 16), - ..Browsers::default() - }); + }, + Browsers { + chrome: Some(95 << 16), + safari: Some(15 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { background: lab(51.5117% 43.3777 -29.0443) url(foo.png); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: #af5cae url(foo.png); background: lab(51.5117% 43.3777 -29.0443) url(foo.png); } "# - }, Browsers { - chrome: Some(95 << 16), - safari: Some(15 << 16), - ..Browsers::default() - }); + }, + Browsers { + chrome: Some(95 << 16), + safari: Some(15 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { background: lab(51.5117% 43.3777 -29.0443) linear-gradient(lab(52.2319% 40.1449 59.9171), lab(47.7776% -34.2947 -7.65904)); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: #af5cae linear-gradient(#c65d07, #00807c); background: lab(51.5117% 43.3777 -29.0443) linear-gradient(lab(52.2319% 40.1449 59.9171), lab(47.7776% -34.2947 -7.65904)); } "# - }, Browsers { - chrome: Some(95 << 16), - safari: Some(15 << 16), - ..Browsers::default() - }); + }, + Browsers { + chrome: Some(95 << 16), + safari: Some(15 << 16), + ..Browsers::default() + }, + ); } #[test] pub fn test_flex() { - test(r#" + test( + r#" .foo { flex-direction: column; flex-wrap: wrap; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex-flow: column wrap; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { flex-direction: row; flex-wrap: wrap; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex-flow: wrap; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { flex-direction: row; flex-wrap: nowrap; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex-flow: row; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { flex-direction: column; flex-wrap: nowrap; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex-flow: column; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { flex-grow: 1; flex-shrink: 1; flex-basis: 0%; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex: 1; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { flex-grow: 1; flex-shrink: 0; flex-basis: 0%; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex: 1 0; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { flex-grow: 1; flex-shrink: 0; flex-basis: auto; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex: 1 0 auto; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { flex-grow: 1; flex-shrink: 1; flex-basis: auto; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex: auto; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { flex: 0 0; flex-grow: 1; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex: 1 0; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { flex: 0 0; flex-grow: var(--grow); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex: 0 0; flex-grow: var(--grow); } "# - }); + }, + ); - test(r#" + test( + r#" .foo { align-content: center; justify-content: center; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { place-content: center; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { align-content: first baseline; justify-content: safe right; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { place-content: baseline safe right; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { place-content: first baseline unsafe left; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { place-content: baseline unsafe left; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { place-content: center center; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { place-content: center; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { align-self: center; justify-self: center; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { place-self: center; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { align-self: center; justify-self: unsafe left; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { place-self: center unsafe left; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { align-items: center; justify-items: center; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { place-items: center; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { align-items: center; justify-items: legacy left; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { place-items: center legacy left; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { place-items: center; justify-items: var(--justify); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { place-items: center; justify-items: var(--justify); } "# - }); + }, + ); - test(r#" + test( + r#" .foo { row-gap: 10px; column-gap: 20px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { gap: 10px 20px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { row-gap: 10px; column-gap: 10px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { gap: 10px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { gap: 10px; column-gap: 20px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { gap: 10px 20px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { column-gap: 20px; gap: 10px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { gap: 10px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { row-gap: normal; column-gap: 20px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { gap: normal 20px; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { -webkit-flex-grow: 1; -webkit-flex-shrink: 1; -webkit-flex-basis: auto; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-flex: auto; } "# - }); - test(r#" + }, + ); + test( + r#" .foo { -webkit-flex-grow: 1; -webkit-flex-shrink: 1; @@ -2895,20 +3605,24 @@ mod tests { flex-shrink: 1; flex-basis: auto; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-flex: auto; flex: auto; } "# - }); - prefix_test(r#" + }, + ); + prefix_test( + r#" .foo { -webkit-box-orient: horizontal; -webkit-box-direction: normal; flex-direction: row; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-box-orient: horizontal; -webkit-box-direction: normal; @@ -2916,15 +3630,18 @@ mod tests { flex-direction: row; } "#}, - Browsers { - safari: Some(4 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { flex-direction: row; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-box-orient: horizontal; -moz-box-orient: horizontal; @@ -2935,13 +3652,15 @@ mod tests { flex-direction: row; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -webkit-box-orient: horizontal; -webkit-box-direction: normal; @@ -2951,20 +3670,24 @@ mod tests { -ms-flex-direction: row; flex-direction: row; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex-direction: row; } "#}, - Browsers { - safari: Some(14 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(14 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { flex-wrap: wrap; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-box-lines: multiple; -moz-box-lines: multiple; @@ -2973,13 +3696,15 @@ mod tests { flex-wrap: wrap; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -webkit-box-lines: multiple; -moz-box-lines: multiple; @@ -2987,20 +3712,24 @@ mod tests { -ms-flex-wrap: wrap; flex-wrap: wrap; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex-wrap: wrap; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { flex-flow: row wrap; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-box-orient: horizontal; -moz-box-orient: horizontal; @@ -3011,13 +3740,15 @@ mod tests { flex-flow: wrap; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -webkit-box-orient: horizontal; -moz-box-orient: horizontal; @@ -3027,20 +3758,24 @@ mod tests { -ms-flex-flow: wrap; flex-flow: wrap; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex-flow: wrap; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { flex-grow: 1; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-box-flex: 1; -moz-box-flex: 1; @@ -3049,13 +3784,15 @@ mod tests { flex-grow: 1; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -webkit-box-flex: 1; -moz-box-flex: 1; @@ -3063,84 +3800,100 @@ mod tests { -webkit-flex-grow: 1; flex-grow: 1; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex-grow: 1; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { flex-shrink: 1; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -ms-flex-negative: 1; -webkit-flex-shrink: 1; flex-shrink: 1; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -ms-flex-negative: 1; -webkit-flex-shrink: 1; flex-shrink: 1; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex-shrink: 1; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { flex-basis: 1px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -ms-flex-preferred-size: 1px; -webkit-flex-basis: 1px; flex-basis: 1px; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -ms-flex-preferred-size: 1px; -webkit-flex-basis: 1px; flex-basis: 1px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex-basis: 1px; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { flex: 1; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-box-flex: 1; -moz-box-flex: 1; @@ -3149,13 +3902,15 @@ mod tests { flex: 1; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -webkit-box-flex: 1; -moz-box-flex: 1; @@ -3163,52 +3918,62 @@ mod tests { -ms-flex: 1; flex: 1; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { flex: 1; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { align-content: space-between; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -ms-flex-line-pack: justify; -webkit-align-content: space-between; align-content: space-between; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -ms-flex-line-pack: justify; -webkit-align-content: space-between; align-content: space-between; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { align-content: space-between; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { justify-content: space-between; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-box-pack: justify; -moz-box-pack: justify; @@ -3217,13 +3982,15 @@ mod tests { justify-content: space-between; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -webkit-box-pack: justify; -moz-box-pack: justify; @@ -3231,20 +3998,24 @@ mod tests { -webkit-justify-content: space-between; justify-content: space-between; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { justify-content: space-between; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { place-content: space-between flex-end; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -ms-flex-line-pack: justify; -webkit-box-pack: end; @@ -3256,13 +4027,15 @@ mod tests { justify-content: flex-end; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -ms-flex-line-pack: justify; -webkit-box-pack: end; @@ -3272,79 +4045,95 @@ mod tests { -webkit-justify-content: flex-end; place-content: space-between flex-end; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { place-content: space-between flex-end; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { place-content: space-between flex-end; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { align-content: space-between; justify-content: flex-end; } "#}, - Browsers { - chrome: Some(30 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + chrome: Some(30 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { place-content: space-between flex-end; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { place-content: space-between flex-end; } "#}, - Browsers { - chrome: Some(60 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + chrome: Some(60 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { align-self: flex-end; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -ms-flex-item-align: end; -webkit-align-self: flex-end; align-self: flex-end; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -ms-flex-item-align: end; -webkit-align-self: flex-end; align-self: flex-end; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { align-self: flex-end; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { place-self: center flex-end; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -ms-flex-item-align: center; -webkit-align-self: center; @@ -3352,59 +4141,71 @@ mod tests { justify-self: flex-end; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -ms-flex-item-align: center; -webkit-align-self: center; place-self: center flex-end; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { place-self: center flex-end; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { place-self: center flex-end; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { align-self: center; justify-self: flex-end; } "#}, - Browsers { - chrome: Some(57 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + chrome: Some(57 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { place-self: center flex-end; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { place-self: center flex-end; } "#}, - Browsers { - chrome: Some(59 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + chrome: Some(59 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { align-items: flex-end; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-box-align: end; -moz-box-align: end; @@ -3413,13 +4214,15 @@ mod tests { align-items: flex-end; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -webkit-box-align: end; -moz-box-align: end; @@ -3427,20 +4230,24 @@ mod tests { -webkit-align-items: flex-end; align-items: flex-end; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { align-items: flex-end; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { place-items: flex-end center; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-box-align: end; -moz-box-align: end; @@ -3450,13 +4257,15 @@ mod tests { justify-items: center; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -webkit-box-align: end; -moz-box-align: end; @@ -3464,34 +4273,41 @@ mod tests { -webkit-align-items: flex-end; place-items: flex-end center; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { place-items: flex-end center; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { place-items: flex-end center; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { align-items: flex-end; justify-items: center; } "#}, - Browsers { - safari: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { order: 1; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-box-ordinal-group: 1; -moz-box-ordinal-group: 1; @@ -3500,13 +4316,15 @@ mod tests { order: 1; } "#}, - Browsers { - safari: Some(4 << 16), - firefox: Some(4 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(4 << 16), + firefox: Some(4 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -webkit-box-ordinal-group: 1; -moz-box-ordinal-group: 1; @@ -3514,35 +4332,41 @@ mod tests { -webkit-order: 1; order: 1; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { order: 1; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { -ms-flex: 0 0 8%; flex: 0 0 5%; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -ms-flex: 0 0 8%; flex: 0 0 5%; } "#}, - Browsers { - safari: Some(11 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(11 << 16), + ..Browsers::default() + }, + ); } #[test] fn test_font() { - test(r#" + test( + r#" .foo { font-family: "Helvetica", "Times New Roman", sans-serif; font-size: 12px; @@ -3552,14 +4376,17 @@ mod tests { font-variant-caps: small-caps; line-height: 1.2em; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { font: italic small-caps bold expanded 12px / 1.2em Helvetica, Times New Roman, sans-serif; } "# - }); + }, + ); - minify_test(r#" + minify_test( + r#" .foo { font-family: "Helvetica", "Times New Roman", sans-serif; font-size: 12px; @@ -3569,44 +4396,56 @@ mod tests { font-variant-caps: small-caps; line-height: 1.2em; } - "#, indoc! {".foo{font:italic small-caps 700 125% 12px/1.2em Helvetica,Times New Roman,sans-serif}" - }); + "#, + indoc! {".foo{font:italic small-caps 700 125% 12px/1.2em Helvetica,Times New Roman,sans-serif}" + }, + ); - test(r#" + test( + r#" .foo { font: 12px "Helvetica", "Times New Roman", sans-serif; line-height: 1.2em; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { font: 12px / 1.2em Helvetica, Times New Roman, sans-serif; } "# - }); + }, + ); - test(r#" + test( + r#" .foo { font: 12px "Helvetica", "Times New Roman", sans-serif; line-height: var(--lh); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { font: 12px Helvetica, Times New Roman, sans-serif; line-height: var(--lh); } "# - }); + }, + ); - minify_test(r#" + minify_test( + r#" .foo { font-family: "Helvetica", "Times New Roman", sans-serif; font-size: 12px; font-stretch: expanded; } - "#, indoc! {".foo{font-family:Helvetica,Times New Roman,sans-serif;font-size:12px;font-stretch:125%}" - }); + "#, + indoc! {".foo{font-family:Helvetica,Times New Roman,sans-serif;font-size:12px;font-stretch:125%}" + }, + ); - test(r#" + test( + r#" .foo { font-family: "Helvetica", "Times New Roman", sans-serif; font-size: 12px; @@ -3616,37 +4455,72 @@ mod tests { font-variant-caps: all-small-caps; line-height: 1.2em; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { font: italic bold expanded 12px / 1.2em Helvetica, Times New Roman, sans-serif; font-variant-caps: all-small-caps; } "# - }); + }, + ); - minify_test(".foo { font: normal normal 600 9px/normal Charcoal; }", ".foo{font:600 9px Charcoal}"); - minify_test(".foo { font: normal normal 500 medium/normal Charcoal; }", ".foo{font:500 medium Charcoal}"); - minify_test(".foo { font: normal normal 400 medium Charcoal; }", ".foo{font:400 medium Charcoal}"); - minify_test(".foo { font: normal normal 500 medium/10px Charcoal; }", ".foo{font:500 medium/10px Charcoal}"); - minify_test(".foo { font-family: 'sans-serif'; }", ".foo{font-family:\"sans-serif\"}"); + minify_test( + ".foo { font: normal normal 600 9px/normal Charcoal; }", + ".foo{font:600 9px Charcoal}", + ); + minify_test( + ".foo { font: normal normal 500 medium/normal Charcoal; }", + ".foo{font:500 medium Charcoal}", + ); + minify_test( + ".foo { font: normal normal 400 medium Charcoal; }", + ".foo{font:400 medium Charcoal}", + ); + minify_test( + ".foo { font: normal normal 500 medium/10px Charcoal; }", + ".foo{font:500 medium/10px Charcoal}", + ); + minify_test( + ".foo { font-family: 'sans-serif'; }", + ".foo{font-family:\"sans-serif\"}", + ); minify_test(".foo { font-family: sans-serif; }", ".foo{font-family:sans-serif}"); minify_test(".foo { font-family: 'default'; }", ".foo{font-family:\"default\"}"); minify_test(".foo { font-family: default; }", ".foo{font-family:default}"); minify_test(".foo { font-family: 'inherit'; }", ".foo{font-family:\"inherit\"}"); minify_test(".foo { font-family: inherit; }", ".foo{font-family:inherit}"); minify_test(".foo { font-family: inherit test; }", ".foo{font-family:inherit test}"); - minify_test(".foo { font-family: 'inherit test'; }", ".foo{font-family:inherit test}"); + minify_test( + ".foo { font-family: 'inherit test'; }", + ".foo{font-family:inherit test}", + ); minify_test(".foo { font-family: revert; }", ".foo{font-family:revert}"); minify_test(".foo { font-family: 'revert'; }", ".foo{font-family:\"revert\"}"); minify_test(".foo { font-family: revert-layer; }", ".foo{font-family:revert-layer}"); - minify_test(".foo { font-family: revert-layer, serif; }", ".foo{font-family:revert-layer,serif}"); - minify_test(".foo { font-family: 'revert', sans-serif; }", ".foo{font-family:\"revert\",sans-serif}"); - minify_test(".foo { font-family: 'revert', foo, sans-serif; }", ".foo{font-family:\"revert\",foo,sans-serif}"); + minify_test( + ".foo { font-family: revert-layer, serif; }", + ".foo{font-family:revert-layer,serif}", + ); + minify_test( + ".foo { font-family: 'revert', sans-serif; }", + ".foo{font-family:\"revert\",sans-serif}", + ); + minify_test( + ".foo { font-family: 'revert', foo, sans-serif; }", + ".foo{font-family:\"revert\",foo,sans-serif}", + ); minify_test(".foo { font-family: ''; }", ".foo{font-family:\"\"}"); // font-family in @font-face - minify_test("@font-face { font-family: 'revert'; }", "@font-face{font-family:\"revert\"}"); - minify_test("@font-face { font-family: 'revert-layer'; }", "@font-face{font-family:\"revert-layer\"}"); + minify_test( + "@font-face { font-family: 'revert'; }", + "@font-face{font-family:\"revert\"}", + ); + minify_test( + "@font-face { font-family: 'revert-layer'; }", + "@font-face{font-family:\"revert-layer\"}", + ); } #[test] @@ -3665,7 +4539,10 @@ mod tests { minify_test(".test + .foo {}", ".test+.foo{}"); minify_test(".test ~ .foo {}", ".test~.foo{}"); minify_test(".test .foo {}", ".test .foo{}"); - minify_test(".custom-range::-webkit-slider-thumb:active {}", ".custom-range::-webkit-slider-thumb:active{}"); + minify_test( + ".custom-range::-webkit-slider-thumb:active {}", + ".custom-range::-webkit-slider-thumb:active{}", + ); minify_test(".test:not(.foo, .bar) {}", ".test:not(.foo,.bar){}"); minify_test(".test:is(.foo, .bar) {}", ".test:is(.foo,.bar){}"); minify_test(".test:where(.foo, .bar) {}", ".test:where(.foo,.bar){}"); @@ -3677,25 +4554,80 @@ mod tests { minify_test(".sm\\:text-5xl { font-size: 3rem }", ".sm\\:text-5xl{font-size:3rem}"); minify_test("a:has(> img) {}", "a:has(>img){}"); minify_test("dt:has(+ dt) {}", "dt:has(+dt){}"); - minify_test("section:not(:has(h1, h2, h3, h4, h5, h6)) {}", "section:not(:has(h1,h2,h3,h4,h5,h6)){}"); + minify_test( + "section:not(:has(h1, h2, h3, h4, h5, h6)) {}", + "section:not(:has(h1,h2,h3,h4,h5,h6)){}", + ); minify_test(":has(.sibling ~ .target) {}", ":has(.sibling~.target){}"); minify_test(".x:has(> .a > .b) {}", ".x:has(>.a>.b){}"); minify_test(".x:has(.bar, #foo) {}", ".x:has(.bar,#foo){}"); minify_test(".x:has(span + span) {}", ".x:has(span+span){}"); minify_test("a:has(:visited) {}", "a:has(:visited){}"); - for element in ["-webkit-scrollbar", "-webkit-scrollbar-button", "-webkit-scrollbar-track", "-webkit-scrollbar-track-piece", "-webkit-scrollbar-thumb", "-webkit-scrollbar-corner", "-webkit-resizer"] { - for class in ["horizontal", "vertical", "decrement", "increment", "start", "end", "double-button", "single-button", "no-button", "corner-present", "window-inactive"] { - minify_test(&format!("::{}:{} {{}}", element, class), &format!("::{}:{}{{}}", element, class)); + for element in [ + "-webkit-scrollbar", + "-webkit-scrollbar-button", + "-webkit-scrollbar-track", + "-webkit-scrollbar-track-piece", + "-webkit-scrollbar-thumb", + "-webkit-scrollbar-corner", + "-webkit-resizer", + ] { + for class in [ + "horizontal", + "vertical", + "decrement", + "increment", + "start", + "end", + "double-button", + "single-button", + "no-button", + "corner-present", + "window-inactive", + ] { + minify_test( + &format!("::{}:{} {{}}", element, class), + &format!("::{}:{}{{}}", element, class), + ); } } - for class in ["horizontal", "vertical", "decrement", "increment", "start", "end", "double-button", "single-button", "no-button", "corner-present", "window-inactive"] { - error_test(&format!(":{} {{}}", class), ParserError::SelectorError(SelectorError::InvalidPseudoClassBeforeWebKitScrollbar)); + for class in [ + "horizontal", + "vertical", + "decrement", + "increment", + "start", + "end", + "double-button", + "single-button", + "no-button", + "corner-present", + "window-inactive", + ] { + error_test( + &format!(":{} {{}}", class), + ParserError::SelectorError(SelectorError::InvalidPseudoClassBeforeWebKitScrollbar), + ); } - for element in ["-webkit-scrollbar", "-webkit-scrollbar-button", "-webkit-scrollbar-track", "-webkit-scrollbar-track-piece", "-webkit-scrollbar-thumb", "-webkit-scrollbar-corner", "-webkit-resizer"] { - error_test(&format!("::{}:hover {{}}", element), ParserError::SelectorError(SelectorError::InvalidPseudoClassAfterWebKitScrollbar)); + for element in [ + "-webkit-scrollbar", + "-webkit-scrollbar-button", + "-webkit-scrollbar-track", + "-webkit-scrollbar-track-piece", + "-webkit-scrollbar-thumb", + "-webkit-scrollbar-corner", + "-webkit-resizer", + ] { + error_test( + &format!("::{}:hover {{}}", element), + ParserError::SelectorError(SelectorError::InvalidPseudoClassAfterWebKitScrollbar), + ); } - error_test("a::first-letter:last-child {}", ParserError::SelectorError(SelectorError::InvalidPseudoClassAfterPseudoElement)); + error_test( + "a::first-letter:last-child {}", + ParserError::SelectorError(SelectorError::InvalidPseudoClassAfterPseudoElement), + ); minify_test("a:last-child::first-letter {}", "a:last-child:first-letter{}"); prefix_test( @@ -3707,7 +4639,7 @@ mod tests { Browsers { safari: Some(8 << 16), ..Browsers::default() - } + }, ); prefix_test( ".test:not(.foo, .bar) {}", @@ -3718,13 +4650,14 @@ mod tests { Browsers { safari: Some(11 << 16), ..Browsers::default() - } + }, ); } #[test] fn test_keyframes() { - minify_test(r#" + minify_test( + r#" @keyframes test { from { background: green; @@ -3738,8 +4671,11 @@ mod tests { background: blue } } - "#, "@keyframes test{0%{background:green}50%{background:red}to{background:#00f}}"); - minify_test(r#" + "#, + "@keyframes test{0%{background:green}50%{background:red}to{background:#00f}}", + ); + minify_test( + r#" @keyframes test { from { background: green; @@ -3750,8 +4686,11 @@ mod tests { background: blue } } - "#, "@keyframes test{0%{background:red}to{background:#00f}}"); - minify_test(r#" + "#, + "@keyframes test{0%{background:red}to{background:#00f}}", + ); + minify_test( + r#" @-webkit-keyframes test { from { background: green; @@ -3762,8 +4701,11 @@ mod tests { background: blue } } - "#, "@-webkit-keyframes test{0%{background:red}to{background:#00f}}"); - minify_test(r#" + "#, + "@-webkit-keyframes test{0%{background:red}to{background:#00f}}", + ); + minify_test( + r#" @-moz-keyframes test { from { background: green; @@ -3774,7 +4716,9 @@ mod tests { background: blue } } - "#, "@-moz-keyframes test{0%{background:red}to{background:#00f}}"); + "#, + "@-moz-keyframes test{0%{background:red}to{background:#00f}}", + ); minify_test(r#" @-webkit-keyframes test { from { @@ -3798,7 +4742,8 @@ mod tests { } "#, "@-webkit-keyframes test{0%{background:red}to{background:#00f}}@-moz-keyframes test{0%{background:red}to{background:#00f}}"); - prefix_test(r#" + prefix_test( + r#" @keyframes test { from { background: green; @@ -3807,7 +4752,8 @@ mod tests { background: blue } } - "#, indoc! { r#" + "#, + indoc! { r#" @-webkit-keyframes test { from { background: green; @@ -3837,12 +4783,15 @@ mod tests { background: #00f; } } - "#}, Browsers { - safari: Some(5 << 16), - firefox: Some(6 << 16), - ..Browsers::default() - }); - prefix_test(r#" + "#}, + Browsers { + safari: Some(5 << 16), + firefox: Some(6 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" @-webkit-keyframes test { from { background: green; @@ -3869,7 +4818,8 @@ mod tests { background: blue } } - "#, indoc! { r#" + "#, + indoc! { r#" @keyframes test { from { background: green; @@ -3879,12 +4829,15 @@ mod tests { background: #00f; } } - "#}, Browsers { - safari: Some(10 << 16), - firefox: Some(17 << 16), - ..Browsers::default() - }); - prefix_test(r#" + "#}, + Browsers { + safari: Some(10 << 16), + firefox: Some(17 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" @-webkit-keyframes test1 { from { background: green; @@ -3913,7 +4866,8 @@ mod tests { background: blue } } - "#, indoc! { r#" + "#, + indoc! { r#" @-webkit-keyframes test1 { from { background: green; @@ -3943,12 +4897,15 @@ mod tests { background: #00f; } } - "#}, Browsers { - safari: Some(10 << 16), - firefox: Some(17 << 16), - ..Browsers::default() - }); - prefix_test(r#" + "#}, + Browsers { + safari: Some(10 << 16), + firefox: Some(17 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" @-webkit-keyframes test { from { background: green; @@ -3975,7 +4932,8 @@ mod tests { background: blue } } - "#, indoc! { r#" + "#, + indoc! { r#" @-webkit-keyframes test { from { background: green; @@ -4005,44 +4963,55 @@ mod tests { background: #00f; } } - "#}, Browsers { - safari: Some(10 << 16), - firefox: Some(17 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(10 << 16), + firefox: Some(17 << 16), + ..Browsers::default() + }, + ); } #[test] fn test_important() { - test(r#" + test( + r#" .foo { align-items: center; justify-items: center !important; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { align-items: center; justify-items: center !important; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { justify-items: center !important; align-items: center; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { align-items: center; justify-items: center !important; } - "#}); + "#}, + ); - minify_test(r#" + minify_test( + r#" .foo { font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important; } - "#, ".foo{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace!important}"); + "#, + ".foo{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace!important}", + ); } #[test] @@ -4053,33 +5022,93 @@ mod tests { minify_test(".foo { width: calc(20px + 30px) }", ".foo{width:50px}"); minify_test(".foo { width: calc(20px + 30px + 40px) }", ".foo{width:90px}"); minify_test(".foo { width: calc(100% - 30px) }", ".foo{width:calc(100% - 30px)}"); - minify_test(".foo { width: calc(100% - 30px + 20px) }", ".foo{width:calc(100% - 10px)}"); - minify_test(".foo { width: calc(20px + 100% - 30px) }", ".foo{width:calc(100% - 10px)}"); - minify_test(".foo { width: calc(20px + 100% + 10vw - 30px) }", ".foo{width:calc(100% - 10px + 10vw)}"); - minify_test(".foo { width: calc(20px + 100% - 30px) }", ".foo{width:calc(100% - 10px)}"); - minify_test(".foo { width: calc(2 * (100% - 20px)) }", ".foo{width:calc(200% - 40px)}"); - minify_test(".foo { width: calc((100% - 20px) * 2) }", ".foo{width:calc(200% - 40px)}"); + minify_test( + ".foo { width: calc(100% - 30px + 20px) }", + ".foo{width:calc(100% - 10px)}", + ); + minify_test( + ".foo { width: calc(20px + 100% - 30px) }", + ".foo{width:calc(100% - 10px)}", + ); + minify_test( + ".foo { width: calc(20px + 100% + 10vw - 30px) }", + ".foo{width:calc(100% - 10px + 10vw)}", + ); + minify_test( + ".foo { width: calc(20px + 100% - 30px) }", + ".foo{width:calc(100% - 10px)}", + ); + minify_test( + ".foo { width: calc(2 * (100% - 20px)) }", + ".foo{width:calc(200% - 40px)}", + ); + minify_test( + ".foo { width: calc((100% - 20px) * 2) }", + ".foo{width:calc(200% - 40px)}", + ); minify_test(".foo { width: calc(100% - 20px * 2) }", ".foo{width:calc(100% - 40px)}"); minify_test(".foo { width: calc(1px + 1px) }", ".foo{width:2px}"); minify_test(".foo { width: calc(100vw / 2) }", ".foo{width:50vw}"); minify_test(".foo { width: calc(50px - (20px - 30px)) }", ".foo{width:60px}"); minify_test(".foo { width: calc(100px - (100px - 100%)) }", ".foo{width:100%}"); - minify_test(".foo { width: calc(100px + (100px - 100%)) }", ".foo{width:calc(200px - 100%)}"); - minify_test(".foo { width: calc(1px - (2em + 3%)) }", ".foo{width:calc(1px + -2em - 3%)}"); // TODO: fix sign - minify_test(".foo { width: calc((100vw - 50em) / 2) }", ".foo{width:calc(50vw - 25em)}"); - minify_test(".foo { width: calc(1px - (2em + 4vh + 3%)) }", ".foo{width:calc(1px + -2em - 4vh - 3%)}"); // TODO - minify_test(".foo { width: calc(1px + (2em + (3vh + 4px))) }", ".foo{width:calc(2em + 3vh + 5px)}"); - minify_test(".foo { width: calc(1px - (2em + 4px - 6vh) / 2) }", ".foo{width:calc(-1em - 1px + 3vh)}"); - minify_test(".foo { width: calc(100% - calc(50% + 25px)) }", ".foo{width:calc(50% - 25px)}"); + minify_test( + ".foo { width: calc(100px + (100px - 100%)) }", + ".foo{width:calc(200px - 100%)}", + ); + minify_test( + ".foo { width: calc(1px - (2em + 3%)) }", + ".foo{width:calc(1px + -2em - 3%)}", + ); // TODO: fix sign + minify_test( + ".foo { width: calc((100vw - 50em) / 2) }", + ".foo{width:calc(50vw - 25em)}", + ); + minify_test( + ".foo { width: calc(1px - (2em + 4vh + 3%)) }", + ".foo{width:calc(1px + -2em - 4vh - 3%)}", + ); // TODO + minify_test( + ".foo { width: calc(1px + (2em + (3vh + 4px))) }", + ".foo{width:calc(2em + 3vh + 5px)}", + ); + minify_test( + ".foo { width: calc(1px - (2em + 4px - 6vh) / 2) }", + ".foo{width:calc(-1em - 1px + 3vh)}", + ); + minify_test( + ".foo { width: calc(100% - calc(50% + 25px)) }", + ".foo{width:calc(50% - 25px)}", + ); minify_test(".foo { width: calc(1px/100) }", ".foo{width:.01px}"); - minify_test(".foo { width: calc(100vw / 2 - 6px + 0px) }", ".foo{width:calc(50vw - 6px)}"); + minify_test( + ".foo { width: calc(100vw / 2 - 6px + 0px) }", + ".foo{width:calc(50vw - 6px)}", + ); minify_test(".foo { width: calc(1px + 1) }", ".foo{width:calc(1px + 1)}"); - minify_test(".foo { width: calc( (1em - calc( 10px + 1em)) / 2) }", ".foo{width:-5px}"); - minify_test(".foo { width: calc((100px - 1em) + (-50px + 1em)) }", ".foo{width:50px}"); - minify_test(".foo { width: calc(100% + (2 * 100px) - ((75.37% - 63.5px) - 900px)) }", ".foo{width:calc(24.63% + 1163.5px)}"); - minify_test(".foo { width: calc(((((100% + (2 * 30px) + 63.5px) / 0.7537) - (100vw - 60px)) / 2) + 30px) }", ".foo{width:calc(66.3394% + 141.929px - 50vw)}"); - minify_test(".foo { width: calc(((75.37% - 63.5px) - 900px) + (2 * 100px)) }", ".foo{width:calc(75.37% - 763.5px)}"); - minify_test(".foo { width: calc((900px - (10% - 63.5px)) + (2 * 100px)) }", ".foo{width:calc(1163.5px - 10%)}"); + minify_test( + ".foo { width: calc( (1em - calc( 10px + 1em)) / 2) }", + ".foo{width:-5px}", + ); + minify_test( + ".foo { width: calc((100px - 1em) + (-50px + 1em)) }", + ".foo{width:50px}", + ); + minify_test( + ".foo { width: calc(100% + (2 * 100px) - ((75.37% - 63.5px) - 900px)) }", + ".foo{width:calc(24.63% + 1163.5px)}", + ); + minify_test( + ".foo { width: calc(((((100% + (2 * 30px) + 63.5px) / 0.7537) - (100vw - 60px)) / 2) + 30px) }", + ".foo{width:calc(66.3394% + 141.929px - 50vw)}", + ); + minify_test( + ".foo { width: calc(((75.37% - 63.5px) - 900px) + (2 * 100px)) }", + ".foo{width:calc(75.37% - 763.5px)}", + ); + minify_test( + ".foo { width: calc((900px - (10% - 63.5px)) + (2 * 100px)) }", + ".foo{width:calc(1163.5px - 10%)}", + ); minify_test(".foo { width: calc(500px/0) }", ".foo{width:calc(500px/0)}"); minify_test(".foo { width: calc(500px/2px) }", ".foo{width:calc(500px/2px)}"); minify_test(".foo { width: calc(100% / 3 * 3) }", ".foo{width:100%}"); @@ -4089,46 +5118,139 @@ mod tests { minify_test(".foo { width: calc(200px / +1) }", ".foo{width:200px}"); minify_test(".foo { width: calc(1.1e+1px + 1.1e+1px) }", ".foo{width:22px}"); minify_test(".foo { border-width: calc(1px + 2px) }", ".foo{border-width:3px}"); - minify_test(".foo { border-width: calc(1em + 2px + 2em + 3px) }", ".foo{border-width:calc(3em + 5px)}"); - - minify_test(".foo { border-width: min(1em, 2px) }", ".foo{border-width:min(1em,2px)}"); - minify_test(".foo { border-width: min(1em + 2em, 2px + 2px) }", ".foo{border-width:min(3em,4px)}"); - minify_test(".foo { border-width: min(1em + 2px, 2px + 1em) }", ".foo{border-width:min(1em + 2px,2px + 1em)}"); - minify_test(".foo { border-width: min(1em + 2px + 2px, 2px + 1em + 1px) }", ".foo{border-width:min(1em + 4px,3px + 1em)}"); - minify_test(".foo { border-width: min(2px + 1px, 3px + 4px) }", ".foo{border-width:3px}"); - minify_test(".foo { border-width: min(1px, 1em, 2px, 3in) }", ".foo{border-width:min(1px,1em)}"); - - minify_test(".foo { border-width: max(1em, 2px) }", ".foo{border-width:max(1em,2px)}"); - minify_test(".foo { border-width: max(1em + 2em, 2px + 2px) }", ".foo{border-width:max(3em,4px)}"); - minify_test(".foo { border-width: max(1em + 2px, 2px + 1em) }", ".foo{border-width:max(1em + 2px,2px + 1em)}"); - minify_test(".foo { border-width: max(1em + 2px + 2px, 2px + 1em + 1px) }", ".foo{border-width:max(1em + 4px,3px + 1em)}"); - minify_test(".foo { border-width: max(2px + 1px, 3px + 4px) }", ".foo{border-width:7px}"); - minify_test(".foo { border-width: max(1px, 1em, 2px, 3in) }", ".foo{border-width:max(3in,1em)}"); + minify_test( + ".foo { border-width: calc(1em + 2px + 2em + 3px) }", + ".foo{border-width:calc(3em + 5px)}", + ); + + minify_test( + ".foo { border-width: min(1em, 2px) }", + ".foo{border-width:min(1em,2px)}", + ); + minify_test( + ".foo { border-width: min(1em + 2em, 2px + 2px) }", + ".foo{border-width:min(3em,4px)}", + ); + minify_test( + ".foo { border-width: min(1em + 2px, 2px + 1em) }", + ".foo{border-width:min(1em + 2px,2px + 1em)}", + ); + minify_test( + ".foo { border-width: min(1em + 2px + 2px, 2px + 1em + 1px) }", + ".foo{border-width:min(1em + 4px,3px + 1em)}", + ); + minify_test( + ".foo { border-width: min(2px + 1px, 3px + 4px) }", + ".foo{border-width:3px}", + ); + minify_test( + ".foo { border-width: min(1px, 1em, 2px, 3in) }", + ".foo{border-width:min(1px,1em)}", + ); + + minify_test( + ".foo { border-width: max(1em, 2px) }", + ".foo{border-width:max(1em,2px)}", + ); + minify_test( + ".foo { border-width: max(1em + 2em, 2px + 2px) }", + ".foo{border-width:max(3em,4px)}", + ); + minify_test( + ".foo { border-width: max(1em + 2px, 2px + 1em) }", + ".foo{border-width:max(1em + 2px,2px + 1em)}", + ); + minify_test( + ".foo { border-width: max(1em + 2px + 2px, 2px + 1em + 1px) }", + ".foo{border-width:max(1em + 4px,3px + 1em)}", + ); + minify_test( + ".foo { border-width: max(2px + 1px, 3px + 4px) }", + ".foo{border-width:7px}", + ); + minify_test( + ".foo { border-width: max(1px, 1em, 2px, 3in) }", + ".foo{border-width:max(3in,1em)}", + ); minify_test(".foo { border-width: clamp(1px, 2px, 3px) }", ".foo{border-width:2px}"); minify_test(".foo { border-width: clamp(1px, 10px, 3px) }", ".foo{border-width:3px}"); minify_test(".foo { border-width: clamp(5px, 2px, 10px) }", ".foo{border-width:5px}"); - minify_test(".foo { border-width: clamp(100px, 2px, 10px) }", ".foo{border-width:100px}"); - minify_test(".foo { border-width: clamp(5px + 5px, 5px + 7px, 10px + 20px) }", ".foo{border-width:12px}"); + minify_test( + ".foo { border-width: clamp(100px, 2px, 10px) }", + ".foo{border-width:100px}", + ); + minify_test( + ".foo { border-width: clamp(5px + 5px, 5px + 7px, 10px + 20px) }", + ".foo{border-width:12px}", + ); - minify_test(".foo { border-width: clamp(1em, 2px, 4vh) }", ".foo{border-width:clamp(1em,2px,4vh)}"); - minify_test(".foo { border-width: clamp(1em, 2em, 4vh) }", ".foo{border-width:min(2em,4vh)}"); - minify_test(".foo { border-width: clamp(1em, 2vh, 4vh) }", ".foo{border-width:max(1em,2vh)}"); - minify_test(".foo { border-width: clamp(1px, 1px + 2em, 4px) }", ".foo{border-width:clamp(1px,1px + 2em,4px)}"); + minify_test( + ".foo { border-width: clamp(1em, 2px, 4vh) }", + ".foo{border-width:clamp(1em,2px,4vh)}", + ); + minify_test( + ".foo { border-width: clamp(1em, 2em, 4vh) }", + ".foo{border-width:min(2em,4vh)}", + ); + minify_test( + ".foo { border-width: clamp(1em, 2vh, 4vh) }", + ".foo{border-width:max(1em,2vh)}", + ); + minify_test( + ".foo { border-width: clamp(1px, 1px + 2em, 4px) }", + ".foo{border-width:clamp(1px,1px + 2em,4px)}", + ); minify_test(".foo { border-width: clamp(1px, 2pt, 1in) }", ".foo{border-width:2pt}"); - minify_test(".foo { top: calc(-1 * clamp(1.75rem, 8vw, 4rem)) }", ".foo{top:calc(-1*clamp(1.75rem,8vw,4rem))}"); - minify_test(".foo { top: calc(-1 * min(1.75rem, 8vw, 4rem)) }", ".foo{top:calc(-1*min(1.75rem,8vw))}"); - minify_test(".foo { top: calc(-1 * max(1.75rem, 8vw, 4rem)) }", ".foo{top:calc(-1*max(4rem,8vw))}"); - minify_test(".foo { top: calc(clamp(1.75rem, 8vw, 4rem) * -1) }", ".foo{top:calc(-1*clamp(1.75rem,8vw,4rem))}"); - minify_test(".foo { top: calc(min(1.75rem, 8vw, 4rem) * -1) }", ".foo{top:calc(-1*min(1.75rem,8vw))}"); - minify_test(".foo { top: calc(max(1.75rem, 8vw, 4rem) * -1) }", ".foo{top:calc(-1*max(4rem,8vw))}"); - minify_test(".foo { top: calc(clamp(1.75rem, 8vw, 4rem) / 2) }", ".foo{top:calc(clamp(1.75rem,8vw,4rem)/2)}"); - minify_test(".foo { top: calc(min(1.75rem, 8vw, 4rem) / 2) }", ".foo{top:calc(min(1.75rem,8vw)/2)}"); - minify_test(".foo { top: calc(max(1.75rem, 8vw, 4rem) / 2) }", ".foo{top:calc(max(4rem,8vw)/2)}"); - minify_test(".foo { top: calc(0.5 * clamp(1.75rem, 8vw, 4rem)) }", ".foo{top:calc(clamp(1.75rem,8vw,4rem)/2)}"); - minify_test(".foo { top: calc(1 * clamp(1.75rem, 8vw, 4rem)) }", ".foo{top:calc(clamp(1.75rem,8vw,4rem))}"); - minify_test(".foo { top: calc(2 * clamp(1.75rem, 8vw, 4rem) / 2) }", ".foo{top:calc(clamp(1.75rem,8vw,4rem))}"); + minify_test( + ".foo { top: calc(-1 * clamp(1.75rem, 8vw, 4rem)) }", + ".foo{top:calc(-1*clamp(1.75rem,8vw,4rem))}", + ); + minify_test( + ".foo { top: calc(-1 * min(1.75rem, 8vw, 4rem)) }", + ".foo{top:calc(-1*min(1.75rem,8vw))}", + ); + minify_test( + ".foo { top: calc(-1 * max(1.75rem, 8vw, 4rem)) }", + ".foo{top:calc(-1*max(4rem,8vw))}", + ); + minify_test( + ".foo { top: calc(clamp(1.75rem, 8vw, 4rem) * -1) }", + ".foo{top:calc(-1*clamp(1.75rem,8vw,4rem))}", + ); + minify_test( + ".foo { top: calc(min(1.75rem, 8vw, 4rem) * -1) }", + ".foo{top:calc(-1*min(1.75rem,8vw))}", + ); + minify_test( + ".foo { top: calc(max(1.75rem, 8vw, 4rem) * -1) }", + ".foo{top:calc(-1*max(4rem,8vw))}", + ); + minify_test( + ".foo { top: calc(clamp(1.75rem, 8vw, 4rem) / 2) }", + ".foo{top:calc(clamp(1.75rem,8vw,4rem)/2)}", + ); + minify_test( + ".foo { top: calc(min(1.75rem, 8vw, 4rem) / 2) }", + ".foo{top:calc(min(1.75rem,8vw)/2)}", + ); + minify_test( + ".foo { top: calc(max(1.75rem, 8vw, 4rem) / 2) }", + ".foo{top:calc(max(4rem,8vw)/2)}", + ); + minify_test( + ".foo { top: calc(0.5 * clamp(1.75rem, 8vw, 4rem)) }", + ".foo{top:calc(clamp(1.75rem,8vw,4rem)/2)}", + ); + minify_test( + ".foo { top: calc(1 * clamp(1.75rem, 8vw, 4rem)) }", + ".foo{top:calc(clamp(1.75rem,8vw,4rem))}", + ); + minify_test( + ".foo { top: calc(2 * clamp(1.75rem, 8vw, 4rem) / 2) }", + ".foo{top:calc(clamp(1.75rem,8vw,4rem))}", + ); minify_test(".foo { width: max(0px, 1vw) }", ".foo{width:max(0px,1vw)}"); @@ -4142,7 +5264,7 @@ mod tests { Browsers { safari: Some(12 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -4155,7 +5277,7 @@ mod tests { Browsers { safari: Some(14 << 16), ..Browsers::default() - } + }, ); minify_test(".foo { width: calc(1vh + 2vh) }", ".foo{width:3vh}"); @@ -4168,17 +5290,34 @@ mod tests { minify_test(".foo { width: calc(1cap + 2cap) }", ".foo{width:3cap}"); minify_test(".foo { width: calc(1lh + 2lh) }", ".foo{width:3lh}"); minify_test(".foo { width: calc(1x + 2x) }", ".foo{width:calc(1x + 2x)}"); - } #[test] fn test_box_shadow() { - minify_test(".foo { box-shadow: 64px 64px 12px 40px rgba(0,0,0,0.4) }", ".foo{box-shadow:64px 64px 12px 40px #0006}"); - minify_test(".foo { box-shadow: 12px 12px 0px 8px rgba(0,0,0,0.4) inset }", ".foo{box-shadow:inset 12px 12px 0 8px #0006}"); - minify_test(".foo { box-shadow: inset 12px 12px 0px 8px rgba(0,0,0,0.4) }", ".foo{box-shadow:inset 12px 12px 0 8px #0006}"); - minify_test(".foo { box-shadow: 12px 12px 8px 0px rgba(0,0,0,0.4) }", ".foo{box-shadow:12px 12px 8px #0006}"); - minify_test(".foo { box-shadow: 12px 12px 0px 0px rgba(0,0,0,0.4) }", ".foo{box-shadow:12px 12px #0006}"); - minify_test(".foo { box-shadow: 64px 64px 12px 40px rgba(0,0,0,0.4), 12px 12px 0px 8px rgba(0,0,0,0.4) inset }", ".foo{box-shadow:64px 64px 12px 40px #0006,inset 12px 12px 0 8px #0006}"); + minify_test( + ".foo { box-shadow: 64px 64px 12px 40px rgba(0,0,0,0.4) }", + ".foo{box-shadow:64px 64px 12px 40px #0006}", + ); + minify_test( + ".foo { box-shadow: 12px 12px 0px 8px rgba(0,0,0,0.4) inset }", + ".foo{box-shadow:inset 12px 12px 0 8px #0006}", + ); + minify_test( + ".foo { box-shadow: inset 12px 12px 0px 8px rgba(0,0,0,0.4) }", + ".foo{box-shadow:inset 12px 12px 0 8px #0006}", + ); + minify_test( + ".foo { box-shadow: 12px 12px 8px 0px rgba(0,0,0,0.4) }", + ".foo{box-shadow:12px 12px 8px #0006}", + ); + minify_test( + ".foo { box-shadow: 12px 12px 0px 0px rgba(0,0,0,0.4) }", + ".foo{box-shadow:12px 12px #0006}", + ); + minify_test( + ".foo { box-shadow: 64px 64px 12px 40px rgba(0,0,0,0.4), 12px 12px 0px 8px rgba(0,0,0,0.4) inset }", + ".foo{box-shadow:64px 64px 12px 40px #0006,inset 12px 12px 0 8px #0006}", + ); prefix_test( ".foo { box-shadow: 12px 12px lab(40% 56.6 39) }", @@ -4191,7 +5330,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -4206,7 +5345,7 @@ mod tests { Browsers { chrome: Some(4 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -4221,7 +5360,7 @@ mod tests { Browsers { chrome: Some(4 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -4234,7 +5373,7 @@ mod tests { Browsers { chrome: Some(4 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -4251,7 +5390,7 @@ mod tests { Browsers { chrome: Some(4 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -4268,7 +5407,7 @@ mod tests { Browsers { chrome: Some(95 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -4287,40 +5426,124 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); } #[test] fn test_media() { - minify_test("@media (min-width: 240px) { .foo { color: chartreuse }}", "@media (min-width:240px){.foo{color:#7fff00}}"); - minify_test("@media (width < 240px) { .foo { color: chartreuse }}", "@media (width<240px){.foo{color:#7fff00}}"); - minify_test("@media (width <= 240px) { .foo { color: chartreuse }}", "@media (width<=240px){.foo{color:#7fff00}}"); - minify_test("@media (width > 240px) { .foo { color: chartreuse }}", "@media (width>240px){.foo{color:#7fff00}}"); - minify_test("@media (width >= 240px) { .foo { color: chartreuse }}", "@media (width>=240px){.foo{color:#7fff00}}"); - minify_test("@media (240px < width) { .foo { color: chartreuse }}", "@media (width>240px){.foo{color:#7fff00}}"); - minify_test("@media (240px <= width) { .foo { color: chartreuse }}", "@media (width>=240px){.foo{color:#7fff00}}"); - minify_test("@media (240px > width) { .foo { color: chartreuse }}", "@media (width<240px){.foo{color:#7fff00}}"); - minify_test("@media (240px >= width) { .foo { color: chartreuse }}", "@media (width<=240px){.foo{color:#7fff00}}"); - minify_test("@media (100px < width < 200px) { .foo { color: chartreuse }}", "@media (100px 240px) { .foo { color: chartreuse }}", + "@media (width>240px){.foo{color:#7fff00}}", + ); + minify_test( + "@media (width >= 240px) { .foo { color: chartreuse }}", + "@media (width>=240px){.foo{color:#7fff00}}", + ); + minify_test( + "@media (240px < width) { .foo { color: chartreuse }}", + "@media (width>240px){.foo{color:#7fff00}}", + ); + minify_test( + "@media (240px <= width) { .foo { color: chartreuse }}", + "@media (width>=240px){.foo{color:#7fff00}}", + ); + minify_test( + "@media (240px > width) { .foo { color: chartreuse }}", + "@media (width<240px){.foo{color:#7fff00}}", + ); + minify_test( + "@media (240px >= width) { .foo { color: chartreuse }}", + "@media (width<=240px){.foo{color:#7fff00}}", + ); + minify_test( + "@media (100px < width < 200px) { .foo { color: chartreuse }}", + "@media (100px b) { .test { foo: bar; } } - "#, indoc! { r#" + "#, + indoc! { r#" @supports selector(a > b) { .test { foo: bar; } } - "#}); - test(r#" + "#}, + ); + test( + r#" @supports unknown(test) { .test { foo: bar; } } - "#, indoc! { r#" + "#, + indoc! { r#" @supports unknown(test) { .test { foo: bar; } } - "#}); - test(r#" + "#}, + ); + test( + r#" @supports (unknown) { .test { foo: bar; } } - "#, indoc! { r#" + "#, + indoc! { r#" @supports (unknown) { .test { foo: bar; } } - "#}); - test(r#" + "#}, + ); + test( + r#" @supports (display: grid) and (not (display: inline-grid)) { .test { foo: bar; } } - "#, indoc! { r#" + "#, + indoc! { r#" @supports (display: grid) and (not (display: inline-grid)) { .test { foo: bar; } } - "#}); + "#}, + ); } #[test] fn test_counter_style() { - test(r#" + test( + r#" @counter-style circled-alpha { system: fixed; symbols: Ⓐ Ⓑ Ⓒ; suffix: " "; } - "#, indoc! { r#" + "#, + indoc! { r#" @counter-style circled-alpha { system: fixed; symbols: Ⓐ Ⓑ Ⓒ; suffix: " "; } - "#}); + "#}, + ); } #[test] fn test_namespace() { - minify_test("@namespace url(http://toto.example.org);", "@namespace \"http://toto.example.org\";"); - minify_test("@namespace \"http://toto.example.org\";", "@namespace \"http://toto.example.org\";"); - minify_test("@namespace toto \"http://toto.example.org\";", "@namespace toto \"http://toto.example.org\";"); - minify_test("@namespace toto url(http://toto.example.org);", "@namespace toto \"http://toto.example.org\";"); + minify_test( + "@namespace url(http://toto.example.org);", + "@namespace \"http://toto.example.org\";", + ); + minify_test( + "@namespace \"http://toto.example.org\";", + "@namespace \"http://toto.example.org\";", + ); + minify_test( + "@namespace toto \"http://toto.example.org\";", + "@namespace toto \"http://toto.example.org\";", + ); + minify_test( + "@namespace toto url(http://toto.example.org);", + "@namespace toto \"http://toto.example.org\";", + ); - test(r#" + test( + r#" @namespace "http://example.com/foo"; x { color: red; } - "#, indoc! {r#" + "#, + indoc! {r#" @namespace "http://example.com/foo"; x { color: red; } - "#}); + "#}, + ); - test(r#" + test( + r#" @namespace toto "http://toto.example.org"; toto|x { @@ -6848,7 +8597,8 @@ mod tests { [toto|att=val] { color: blue } - "#, indoc! {r#" + "#, + indoc! {r#" @namespace toto "http://toto.example.org"; toto|x { @@ -6858,9 +8608,11 @@ mod tests { [toto|att="val"] { color: #00f; } - "#}); + "#}, + ); - test(r#" + test( + r#" @namespace "http://example.com/foo"; |x { @@ -6870,7 +8622,8 @@ mod tests { [|att=val] { color: blue } - "#, indoc! {r#" + "#, + indoc! {r#" @namespace "http://example.com/foo"; |x { @@ -6880,9 +8633,11 @@ mod tests { [att="val"] { color: #00f; } - "#}); + "#}, + ); - test(r#" + test( + r#" @namespace "http://example.com/foo"; *|x { @@ -6892,7 +8647,8 @@ mod tests { [*|att=val] { color: blue } - "#, indoc! {r#" + "#, + indoc! {r#" @namespace "http://example.com/foo"; *|x { @@ -6902,9 +8658,13 @@ mod tests { [*|att="val"] { color: #00f; } - "#}); + "#}, + ); - error_test(".foo { color: red } @namespace \"http://example.com/foo\";", ParserError::UnexpectedNamespaceRule); + error_test( + ".foo { color: red } @namespace \"http://example.com/foo\";", + ParserError::UnexpectedNamespaceRule, + ); } #[test] @@ -6913,17 +8673,44 @@ mod tests { minify_test("@import \"foo.css\";", "@import \"foo.css\";"); minify_test("@import url(foo.css) print;", "@import \"foo.css\" print;"); minify_test("@import \"foo.css\" print;", "@import \"foo.css\" print;"); - minify_test("@import \"foo.css\" screen and (orientation: landscape);", "@import \"foo.css\" screen and (orientation:landscape);"); - minify_test("@import url(foo.css) supports(display: flex);", "@import \"foo.css\" supports(display: flex);"); - minify_test("@import url(foo.css) supports(display: flex) print;", "@import \"foo.css\" supports(display: flex) print;"); - minify_test("@import url(foo.css) supports(not (display: flex));", "@import \"foo.css\" supports(not (display: flex));"); - minify_test("@import url(foo.css) supports((display: flex));", "@import \"foo.css\" supports(display: flex);"); + minify_test( + "@import \"foo.css\" screen and (orientation: landscape);", + "@import \"foo.css\" screen and (orientation:landscape);", + ); + minify_test( + "@import url(foo.css) supports(display: flex);", + "@import \"foo.css\" supports(display: flex);", + ); + minify_test( + "@import url(foo.css) supports(display: flex) print;", + "@import \"foo.css\" supports(display: flex) print;", + ); + minify_test( + "@import url(foo.css) supports(not (display: flex));", + "@import \"foo.css\" supports(not (display: flex));", + ); + minify_test( + "@import url(foo.css) supports((display: flex));", + "@import \"foo.css\" supports(display: flex);", + ); minify_test("@charset \"UTF-8\"; @import url(foo.css);", "@import \"foo.css\";"); minify_test("@layer foo; @import url(foo.css);", "@layer foo;@import \"foo.css\";"); - error_test(".foo { color: red } @import url(bar.css);", ParserError::UnexpectedImportRule); - error_test("@namespace \"http://example.com/foo\"; @import url(bar.css);", ParserError::UnexpectedImportRule); - error_test("@media print { .foo { color: red }} @import url(bar.css);", ParserError::UnexpectedImportRule); - error_test("@layer foo; @import url(foo.css); @layer bar; @import url(bar.css)", ParserError::UnexpectedImportRule); + error_test( + ".foo { color: red } @import url(bar.css);", + ParserError::UnexpectedImportRule, + ); + error_test( + "@namespace \"http://example.com/foo\"; @import url(bar.css);", + ParserError::UnexpectedImportRule, + ); + error_test( + "@media print { .foo { color: red }} @import url(bar.css);", + ParserError::UnexpectedImportRule, + ); + error_test( + "@layer foo; @import url(foo.css); @layer bar; @import url(bar.css)", + ParserError::UnexpectedImportRule, + ); } #[test] @@ -6944,7 +8731,7 @@ mod tests { Browsers { chrome: Some(95 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -6960,7 +8747,7 @@ mod tests { safari: Some(5 << 16), firefox: Some(14 << 16), ..Browsers::default() - } + }, ); } @@ -6979,7 +8766,10 @@ mod tests { minify_test(".foo { display: list-item }", ".foo{display:list-item}"); minify_test(".foo { display: block flow list-item }", ".foo{display:list-item}"); minify_test(".foo { display: inline list-item }", ".foo{display:inline list-item}"); - minify_test(".foo { display: inline flow list-item }", ".foo{display:inline list-item}"); + minify_test( + ".foo { display: inline flow list-item }", + ".foo{display:inline list-item}", + ); minify_test(".foo { display: flex }", ".foo{display:flex}"); minify_test(".foo { display: block flex }", ".foo{display:flex}"); minify_test(".foo { display: inline-flex }", ".foo{display:inline-flex}"); @@ -7004,23 +8794,20 @@ mod tests { minify_test(".foo { display: -moz-box }", ".foo{display:-moz-box}"); minify_test( ".foo { display: -webkit-flex; display: -moz-box; display: flex }", - ".foo{display:-webkit-flex;display:-moz-box;display:flex}" + ".foo{display:-webkit-flex;display:-moz-box;display:flex}", ); minify_test( ".foo { display: -webkit-flex; display: flex; display: -moz-box }", - ".foo{display:-webkit-flex;display:flex;display:-moz-box}" - ); - minify_test( - ".foo { display: flex; display: grid }", - ".foo{display:grid}" + ".foo{display:-webkit-flex;display:flex;display:-moz-box}", ); + minify_test(".foo { display: flex; display: grid }", ".foo{display:grid}"); minify_test( ".foo { display: -webkit-inline-flex; display: -moz-inline-box; display: inline-flex }", - ".foo{display:-webkit-inline-flex;display:-moz-inline-box;display:inline-flex}" + ".foo{display:-webkit-inline-flex;display:-moz-inline-box;display:inline-flex}", ); minify_test( ".foo { display: flex; display: var(--grid); }", - ".foo{display:flex;display:var(--grid)}" + ".foo{display:flex;display:var(--grid)}", ); prefix_test( ".foo{ display: flex }", @@ -7038,7 +8825,7 @@ mod tests { firefox: Some(14 << 16), ie: Some(10 << 16), ..Browsers::default() - } + }, ); prefix_test( ".foo{ display: flex; display: -webkit-box; }", @@ -7052,7 +8839,7 @@ mod tests { firefox: Some(14 << 16), ie: Some(10 << 16), ..Browsers::default() - } + }, ); prefix_test( ".foo{ display: -webkit-box; display: flex; }", @@ -7070,9 +8857,10 @@ mod tests { firefox: Some(14 << 16), ie: Some(10 << 16), ..Browsers::default() - } + }, ); - prefix_test(r#" + prefix_test( + r#" .foo { display: -webkit-box; display: -moz-box; @@ -7089,9 +8877,10 @@ mod tests { Browsers { safari: Some(14 << 16), ..Browsers::default() - } + }, ); - prefix_test(r#" + prefix_test( + r#" .foo { display: -webkit-box; display: flex; @@ -7110,7 +8899,7 @@ mod tests { Browsers { safari: Some(14 << 16), ..Browsers::default() - } + }, ); prefix_test( ".foo{ display: inline-flex }", @@ -7128,9 +8917,10 @@ mod tests { firefox: Some(14 << 16), ie: Some(10 << 16), ..Browsers::default() - } + }, ); - prefix_test(r#" + prefix_test( + r#" .foo { display: -webkit-inline-box; display: -moz-inline-box; @@ -7147,7 +8937,7 @@ mod tests { Browsers { safari: Some(14 << 16), ..Browsers::default() - } + }, ); } @@ -7166,11 +8956,26 @@ mod tests { minify_test(".foo { text-transform: capitalize }", ".foo{text-transform:capitalize}"); minify_test(".foo { text-transform: none }", ".foo{text-transform:none}"); minify_test(".foo { text-transform: full-width }", ".foo{text-transform:full-width}"); - minify_test(".foo { text-transform: full-size-kana }", ".foo{text-transform:full-size-kana}"); - minify_test(".foo { text-transform: uppercase full-width }", ".foo{text-transform:uppercase full-width}"); - minify_test(".foo { text-transform: full-width uppercase }", ".foo{text-transform:uppercase full-width}"); - minify_test(".foo { text-transform: uppercase full-width full-size-kana }", ".foo{text-transform:uppercase full-width full-size-kana}"); - minify_test(".foo { text-transform: full-width uppercase full-size-kana }", ".foo{text-transform:uppercase full-width full-size-kana}"); + minify_test( + ".foo { text-transform: full-size-kana }", + ".foo{text-transform:full-size-kana}", + ); + minify_test( + ".foo { text-transform: uppercase full-width }", + ".foo{text-transform:uppercase full-width}", + ); + minify_test( + ".foo { text-transform: full-width uppercase }", + ".foo{text-transform:uppercase full-width}", + ); + minify_test( + ".foo { text-transform: uppercase full-width full-size-kana }", + ".foo{text-transform:uppercase full-width full-size-kana}", + ); + minify_test( + ".foo { text-transform: full-width uppercase full-size-kana }", + ".foo{text-transform:uppercase full-width full-size-kana}", + ); } #[test] @@ -7204,7 +9009,7 @@ mod tests { firefox: Some(50 << 16), opera: Some(12 << 16), ..Browsers::default() - } + }, ); prefix_test( r#" @@ -7224,7 +9029,7 @@ mod tests { firefox: Some(94 << 16), opera: Some(30 << 16), ..Browsers::default() - } + }, ); } @@ -7276,7 +9081,7 @@ mod tests { firefox: Some(40 << 16), ie: Some(10 << 16), ..Browsers::default() - } + }, ); prefix_test( r#" @@ -7299,7 +9104,7 @@ mod tests { firefox: Some(88 << 16), edge: Some(79 << 16), ..Browsers::default() - } + }, ); prefix_test( r#" @@ -7320,7 +9125,7 @@ mod tests { firefox: Some(88 << 16), edge: Some(79 << 16), ..Browsers::default() - } + }, ); } @@ -7331,11 +9136,13 @@ mod tests { minify_test(".foo { text-align: END }", ".foo{text-align:end}"); minify_test(".foo { text-align: left }", ".foo{text-align:left}"); - prefix_test(r#" + prefix_test( + r#" .foo { text-align: start; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-align: var(--ltr, left) var(--rtl, right); } @@ -7350,16 +9157,20 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(2 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(2 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-align: end; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-align: var(--ltr, right) var(--rtl, left); } @@ -7374,24 +9185,30 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(2 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(2 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-align: start; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-align: start; } "# - }, Browsers { - safari: Some(14 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(14 << 16), + ..Browsers::default() + }, + ); } #[test] @@ -7409,7 +9226,7 @@ mod tests { Browsers { firefox: Some(40 << 16), ..Browsers::default() - } + }, ); prefix_test( r#" @@ -7426,7 +9243,7 @@ mod tests { Browsers { firefox: Some(88 << 16), ..Browsers::default() - } + }, ); } @@ -7454,214 +9271,333 @@ mod tests { minify_test(".foo { text-indent: 10% }", ".foo{text-indent:10%}"); minify_test(".foo { text-indent: 3em hanging }", ".foo{text-indent:3em hanging}"); minify_test(".foo { text-indent: 3em each-line }", ".foo{text-indent:3em each-line}"); - minify_test(".foo { text-indent: 3em hanging each-line }", ".foo{text-indent:3em hanging each-line}"); - minify_test(".foo { text-indent: 3em each-line hanging }", ".foo{text-indent:3em hanging each-line}"); - minify_test(".foo { text-indent: each-line 3em hanging }", ".foo{text-indent:3em hanging each-line}"); - minify_test(".foo { text-indent: each-line hanging 3em }", ".foo{text-indent:3em hanging each-line}"); - } - - #[test] - fn test_text_decoration() { - minify_test(".foo { text-decoration-line: none }", ".foo{text-decoration-line:none}"); - minify_test(".foo { text-decoration-line: underline }", ".foo{text-decoration-line:underline}"); - minify_test(".foo { text-decoration-line: overline }", ".foo{text-decoration-line:overline}"); - minify_test(".foo { text-decoration-line: line-through }", ".foo{text-decoration-line:line-through}"); - minify_test(".foo { text-decoration-line: blink }", ".foo{text-decoration-line:blink}"); - minify_test(".foo { text-decoration-line: underline overline }", ".foo{text-decoration-line:underline overline}"); - minify_test(".foo { text-decoration-line: overline underline }", ".foo{text-decoration-line:underline overline}"); - minify_test(".foo { text-decoration-line: overline line-through underline }", ".foo{text-decoration-line:underline overline line-through}"); - minify_test(".foo { text-decoration-line: spelling-error }", ".foo{text-decoration-line:spelling-error}"); - minify_test(".foo { text-decoration-line: grammar-error }", ".foo{text-decoration-line:grammar-error}"); - minify_test(".foo { -webkit-text-decoration-line: overline underline }", ".foo{-webkit-text-decoration-line:underline overline}"); - minify_test(".foo { -moz-text-decoration-line: overline underline }", ".foo{-moz-text-decoration-line:underline overline}"); - - minify_test(".foo { text-decoration-style: solid }", ".foo{text-decoration-style:solid}"); - minify_test(".foo { text-decoration-style: dotted }", ".foo{text-decoration-style:dotted}"); - minify_test(".foo { -webkit-text-decoration-style: solid }", ".foo{-webkit-text-decoration-style:solid}"); - - minify_test(".foo { text-decoration-color: yellow }", ".foo{text-decoration-color:#ff0}"); - minify_test(".foo { -webkit-text-decoration-color: yellow }", ".foo{-webkit-text-decoration-color:#ff0}"); - - minify_test(".foo { text-decoration: none }", ".foo{text-decoration:none}"); - minify_test(".foo { text-decoration: underline dotted }", ".foo{text-decoration:underline dotted}"); - minify_test(".foo { text-decoration: underline dotted yellow }", ".foo{text-decoration:underline dotted #ff0}"); - minify_test(".foo { text-decoration: yellow dotted underline }", ".foo{text-decoration:underline dotted #ff0}"); - minify_test(".foo { text-decoration: underline overline dotted yellow }", ".foo{text-decoration:underline overline dotted #ff0}"); - minify_test(".foo { -webkit-text-decoration: yellow dotted underline }", ".foo{-webkit-text-decoration:underline dotted #ff0}"); - minify_test(".foo { -moz-text-decoration: yellow dotted underline }", ".foo{-moz-text-decoration:underline dotted #ff0}"); + minify_test( + ".foo { text-indent: 3em hanging each-line }", + ".foo{text-indent:3em hanging each-line}", + ); + minify_test( + ".foo { text-indent: 3em each-line hanging }", + ".foo{text-indent:3em hanging each-line}", + ); + minify_test( + ".foo { text-indent: each-line 3em hanging }", + ".foo{text-indent:3em hanging each-line}", + ); + minify_test( + ".foo { text-indent: each-line hanging 3em }", + ".foo{text-indent:3em hanging each-line}", + ); + } + + #[test] + fn test_text_decoration() { + minify_test(".foo { text-decoration-line: none }", ".foo{text-decoration-line:none}"); + minify_test( + ".foo { text-decoration-line: underline }", + ".foo{text-decoration-line:underline}", + ); + minify_test( + ".foo { text-decoration-line: overline }", + ".foo{text-decoration-line:overline}", + ); + minify_test( + ".foo { text-decoration-line: line-through }", + ".foo{text-decoration-line:line-through}", + ); + minify_test( + ".foo { text-decoration-line: blink }", + ".foo{text-decoration-line:blink}", + ); + minify_test( + ".foo { text-decoration-line: underline overline }", + ".foo{text-decoration-line:underline overline}", + ); + minify_test( + ".foo { text-decoration-line: overline underline }", + ".foo{text-decoration-line:underline overline}", + ); + minify_test( + ".foo { text-decoration-line: overline line-through underline }", + ".foo{text-decoration-line:underline overline line-through}", + ); + minify_test( + ".foo { text-decoration-line: spelling-error }", + ".foo{text-decoration-line:spelling-error}", + ); + minify_test( + ".foo { text-decoration-line: grammar-error }", + ".foo{text-decoration-line:grammar-error}", + ); + minify_test( + ".foo { -webkit-text-decoration-line: overline underline }", + ".foo{-webkit-text-decoration-line:underline overline}", + ); + minify_test( + ".foo { -moz-text-decoration-line: overline underline }", + ".foo{-moz-text-decoration-line:underline overline}", + ); + + minify_test( + ".foo { text-decoration-style: solid }", + ".foo{text-decoration-style:solid}", + ); + minify_test( + ".foo { text-decoration-style: dotted }", + ".foo{text-decoration-style:dotted}", + ); + minify_test( + ".foo { -webkit-text-decoration-style: solid }", + ".foo{-webkit-text-decoration-style:solid}", + ); + + minify_test( + ".foo { text-decoration-color: yellow }", + ".foo{text-decoration-color:#ff0}", + ); + minify_test( + ".foo { -webkit-text-decoration-color: yellow }", + ".foo{-webkit-text-decoration-color:#ff0}", + ); + + minify_test(".foo { text-decoration: none }", ".foo{text-decoration:none}"); + minify_test( + ".foo { text-decoration: underline dotted }", + ".foo{text-decoration:underline dotted}", + ); + minify_test( + ".foo { text-decoration: underline dotted yellow }", + ".foo{text-decoration:underline dotted #ff0}", + ); + minify_test( + ".foo { text-decoration: yellow dotted underline }", + ".foo{text-decoration:underline dotted #ff0}", + ); + minify_test( + ".foo { text-decoration: underline overline dotted yellow }", + ".foo{text-decoration:underline overline dotted #ff0}", + ); + minify_test( + ".foo { -webkit-text-decoration: yellow dotted underline }", + ".foo{-webkit-text-decoration:underline dotted #ff0}", + ); + minify_test( + ".foo { -moz-text-decoration: yellow dotted underline }", + ".foo{-moz-text-decoration:underline dotted #ff0}", + ); - test(r#" + test( + r#" .foo { text-decoration-line: underline; text-decoration-style: dotted; text-decoration-color: yellow; text-decoration-thickness: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-decoration: underline 2px dotted #ff0; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { text-decoration: underline; text-decoration-style: dotted; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-decoration: underline dotted; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { text-decoration: underline; text-decoration-style: var(--style); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-decoration: underline; text-decoration-style: var(--style); } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { -webkit-text-decoration: underline; -webkit-text-decoration-style: dotted; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-text-decoration: underline dotted; } - "#}); + "#}, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-decoration: underline dotted; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-text-decoration: underline dotted; -moz-text-decoration: underline dotted; text-decoration: underline dotted; } "#}, - Browsers { - safari: Some(8 << 16), - firefox: Some(30 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(8 << 16), + firefox: Some(30 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-decoration-line: underline; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-text-decoration-line: underline; -moz-text-decoration-line: underline; text-decoration-line: underline; } "#}, - Browsers { - safari: Some(8 << 16), - firefox: Some(30 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(8 << 16), + firefox: Some(30 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-decoration-style: dotted; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-text-decoration-style: dotted; -moz-text-decoration-style: dotted; text-decoration-style: dotted; } "#}, - Browsers { - safari: Some(8 << 16), - firefox: Some(30 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(8 << 16), + firefox: Some(30 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-decoration-color: yellow; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-text-decoration-color: #ff0; -moz-text-decoration-color: #ff0; text-decoration-color: #ff0; } "#}, - Browsers { - safari: Some(8 << 16), - firefox: Some(30 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(8 << 16), + firefox: Some(30 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-decoration: underline; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-decoration: underline; } "#}, - Browsers { - safari: Some(8 << 16), - firefox: Some(30 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(8 << 16), + firefox: Some(30 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { -webkit-text-decoration: underline dotted; -moz-text-decoration: underline dotted; text-decoration: underline dotted; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-decoration: underline dotted; } "#}, - Browsers { - safari: Some(14 << 16), - firefox: Some(45 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(14 << 16), + firefox: Some(45 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-decoration: var(--test); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-text-decoration: var(--test); -moz-text-decoration: var(--test); text-decoration: var(--test); } "#}, - Browsers { - safari: Some(8 << 16), - firefox: Some(30 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(8 << 16), + firefox: Some(30 << 16), + ..Browsers::default() + }, + ); - minify_test(".foo { text-decoration-skip-ink: all }", ".foo{text-decoration-skip-ink:all}"); - minify_test(".foo { -webkit-text-decoration-skip-ink: all }", ".foo{-webkit-text-decoration-skip-ink:all}"); + minify_test( + ".foo { text-decoration-skip-ink: all }", + ".foo{text-decoration-skip-ink:all}", + ); + minify_test( + ".foo { -webkit-text-decoration-skip-ink: all }", + ".foo{-webkit-text-decoration-skip-ink:all}", + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-decoration: lch(50.998% 135.363 338) underline; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-text-decoration: underline #ee00be; -moz-text-decoration: underline #ee00be; @@ -7671,17 +9607,20 @@ mod tests { text-decoration: underline lch(50.998% 135.363 338); } "#}, - Browsers { - safari: Some(8 << 16), - firefox: Some(30 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(8 << 16), + firefox: Some(30 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-decoration-color: lch(50.998% 135.363 338); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-text-decoration-color: #ee00be; -moz-text-decoration-color: #ee00be; @@ -7691,17 +9630,20 @@ mod tests { text-decoration-color: lch(50.998% 135.363 338); } "#}, - Browsers { - safari: Some(8 << 16), - firefox: Some(30 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(8 << 16), + firefox: Some(30 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-decoration: lch(50.998% 135.363 338) var(--style); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-decoration: #ee00be var(--style); } @@ -7712,158 +9654,218 @@ mod tests { } } "#}, - Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); } #[test] fn test_text_emphasis() { minify_test(".foo { text-emphasis-style: none }", ".foo{text-emphasis-style:none}"); - minify_test(".foo { text-emphasis-style: filled }", ".foo{text-emphasis-style:filled}"); + minify_test( + ".foo { text-emphasis-style: filled }", + ".foo{text-emphasis-style:filled}", + ); minify_test(".foo { text-emphasis-style: open }", ".foo{text-emphasis-style:open}"); minify_test(".foo { text-emphasis-style: dot }", ".foo{text-emphasis-style:dot}"); - minify_test(".foo { text-emphasis-style: filled dot }", ".foo{text-emphasis-style:dot}"); - minify_test(".foo { text-emphasis-style: dot filled }", ".foo{text-emphasis-style:dot}"); - minify_test(".foo { text-emphasis-style: open dot }", ".foo{text-emphasis-style:open dot}"); - minify_test(".foo { text-emphasis-style: dot open }", ".foo{text-emphasis-style:open dot}"); + minify_test( + ".foo { text-emphasis-style: filled dot }", + ".foo{text-emphasis-style:dot}", + ); + minify_test( + ".foo { text-emphasis-style: dot filled }", + ".foo{text-emphasis-style:dot}", + ); + minify_test( + ".foo { text-emphasis-style: open dot }", + ".foo{text-emphasis-style:open dot}", + ); + minify_test( + ".foo { text-emphasis-style: dot open }", + ".foo{text-emphasis-style:open dot}", + ); minify_test(".foo { text-emphasis-style: \"x\" }", ".foo{text-emphasis-style:\"x\"}"); - + minify_test(".foo { text-emphasis-color: yellow }", ".foo{text-emphasis-color:#ff0}"); minify_test(".foo { text-emphasis: none }", ".foo{text-emphasis:none}"); minify_test(".foo { text-emphasis: filled }", ".foo{text-emphasis:filled}"); - minify_test(".foo { text-emphasis: filled yellow }", ".foo{text-emphasis:filled #ff0}"); - minify_test(".foo { text-emphasis: dot filled yellow }", ".foo{text-emphasis:dot #ff0}"); + minify_test( + ".foo { text-emphasis: filled yellow }", + ".foo{text-emphasis:filled #ff0}", + ); + minify_test( + ".foo { text-emphasis: dot filled yellow }", + ".foo{text-emphasis:dot #ff0}", + ); - test(r#" + test( + r#" .foo { text-emphasis-style: filled; text-emphasis-color: yellow; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-emphasis: filled #ff0; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { text-emphasis: filled red; text-emphasis-color: yellow; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-emphasis: filled #ff0; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { text-emphasis: filled yellow; text-emphasis-color: var(--color); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-emphasis: filled #ff0; text-emphasis-color: var(--color); } - "#}); + "#}, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-emphasis-style: filled; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-text-emphasis-style: filled; text-emphasis-style: filled; } "#}, - Browsers { - safari: Some(10 << 16), - chrome: Some(30 << 16), - firefox: Some(45 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(10 << 16), + chrome: Some(30 << 16), + firefox: Some(45 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { -webkit-text-emphasis-style: filled; text-emphasis-style: filled; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-emphasis-style: filled; } "#}, - Browsers { - safari: Some(10 << 16), - firefox: Some(45 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(10 << 16), + firefox: Some(45 << 16), + ..Browsers::default() + }, + ); - minify_test(".foo { text-emphasis-position: over }", ".foo{text-emphasis-position:over}"); - minify_test(".foo { text-emphasis-position: under }", ".foo{text-emphasis-position:under}"); - minify_test(".foo { text-emphasis-position: over right }", ".foo{text-emphasis-position:over}"); - minify_test(".foo { text-emphasis-position: over left }", ".foo{text-emphasis-position:over left}"); + minify_test( + ".foo { text-emphasis-position: over }", + ".foo{text-emphasis-position:over}", + ); + minify_test( + ".foo { text-emphasis-position: under }", + ".foo{text-emphasis-position:under}", + ); + minify_test( + ".foo { text-emphasis-position: over right }", + ".foo{text-emphasis-position:over}", + ); + minify_test( + ".foo { text-emphasis-position: over left }", + ".foo{text-emphasis-position:over left}", + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-emphasis-position: over; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-text-emphasis-position: over; text-emphasis-position: over; } "#}, - Browsers { - safari: Some(10 << 16), - chrome: Some(30 << 16), - firefox: Some(45 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(10 << 16), + chrome: Some(30 << 16), + firefox: Some(45 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" - .foo { + prefix_test( + r#" + .foo { text-emphasis-position: over left; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-emphasis-position: over left; } "#}, - Browsers { - safari: Some(10 << 16), - chrome: Some(30 << 16), - firefox: Some(45 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(10 << 16), + chrome: Some(30 << 16), + firefox: Some(45 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-emphasis-position: var(--test); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-text-emphasis-position: var(--test); text-emphasis-position: var(--test); } "#}, - Browsers { - safari: Some(10 << 16), - chrome: Some(30 << 16), - firefox: Some(45 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(10 << 16), + chrome: Some(30 << 16), + firefox: Some(45 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-emphasis: filled lch(50.998% 135.363 338); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-text-emphasis: filled #ee00be; text-emphasis: filled #ee00be; @@ -7871,17 +9873,20 @@ mod tests { text-emphasis: filled lch(50.998% 135.363 338); } "#}, - Browsers { - chrome: Some(25 << 16), - firefox: Some(48 << 16), - ..Browsers::default() - }); + Browsers { + chrome: Some(25 << 16), + firefox: Some(48 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-emphasis-color: lch(50.998% 135.363 338); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-text-emphasis-color: #ee00be; text-emphasis-color: #ee00be; @@ -7889,17 +9894,20 @@ mod tests { text-emphasis-color: lch(50.998% 135.363 338); } "#}, - Browsers { - chrome: Some(25 << 16), - firefox: Some(48 << 16), - ..Browsers::default() - }); + Browsers { + chrome: Some(25 << 16), + firefox: Some(48 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-emphasis: lch(50.998% 135.363 338) var(--style); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-emphasis: #ee00be var(--style); } @@ -7910,20 +9918,36 @@ mod tests { } } "#}, - Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); } #[test] fn test_text_shadow() { - minify_test(".foo { text-shadow: 1px 1px 2px yellow; }", ".foo{text-shadow:1px 1px 2px #ff0}"); - minify_test(".foo { text-shadow: 1px 1px 2px 3px yellow; }", ".foo{text-shadow:1px 1px 2px 3px #ff0}"); - minify_test(".foo { text-shadow: 1px 1px 0 yellow; }", ".foo{text-shadow:1px 1px #ff0}"); - minify_test(".foo { text-shadow: 1px 1px yellow; }", ".foo{text-shadow:1px 1px #ff0}"); - minify_test(".foo { text-shadow: 1px 1px yellow, 2px 3px red; }", ".foo{text-shadow:1px 1px #ff0,2px 3px red}"); - + minify_test( + ".foo { text-shadow: 1px 1px 2px yellow; }", + ".foo{text-shadow:1px 1px 2px #ff0}", + ); + minify_test( + ".foo { text-shadow: 1px 1px 2px 3px yellow; }", + ".foo{text-shadow:1px 1px 2px 3px #ff0}", + ); + minify_test( + ".foo { text-shadow: 1px 1px 0 yellow; }", + ".foo{text-shadow:1px 1px #ff0}", + ); + minify_test( + ".foo { text-shadow: 1px 1px yellow; }", + ".foo{text-shadow:1px 1px #ff0}", + ); + minify_test( + ".foo { text-shadow: 1px 1px yellow, 2px 3px red; }", + ".foo{text-shadow:1px 1px #ff0,2px 3px red}", + ); + prefix_test( ".foo { text-shadow: 12px 12px lab(40% 56.6 39) }", indoc! { r#" @@ -7935,7 +9959,7 @@ mod tests { Browsers { chrome: Some(4 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -7951,7 +9975,7 @@ mod tests { chrome: Some(90 << 16), safari: Some(14 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -7965,7 +9989,7 @@ mod tests { Browsers { chrome: Some(4 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -7984,133 +10008,161 @@ mod tests { Browsers { chrome: Some(4 << 16), ..Browsers::default() - } + }, ); } #[test] fn test_position() { - test(r#" + test( + r#" .foo { position: relative; position: absolute; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { position: absolute; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { position: -webkit-sticky; position: sticky; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { position: -webkit-sticky; position: sticky; } - "#}); + "#}, + ); - prefix_test(r#" + prefix_test( + r#" .foo { position: sticky; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { position: -webkit-sticky; position: sticky; } "#}, - Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { position: -webkit-sticky; position: sticky; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { position: sticky; } "#}, - Browsers { - safari: Some(13 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(13 << 16), + ..Browsers::default() + }, + ); - test(r#" + test( + r#" .foo { top: 0; left: 0; bottom: 0; right: 0; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { inset: 0; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { top: 2px; left: 4px; bottom: 2px; right: 4px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { inset: 2px 4px; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { top: 1px; left: 2px; bottom: 3px; right: 4px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { inset: 1px 4px 3px 2px; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { inset-block-start: 2px; inset-block-end: 2px; inset-inline-start: 4px; inset-inline-end: 4px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { inset-block: 2px; inset-inline: 4px; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { inset-block-start: 2px; inset-block-end: 3px; inset-inline-start: 4px; inset-inline-end: 5px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { inset-block: 2px 3px; inset-inline: 4px 5px; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { inset-block-start: 2px; inset-block-end: 3px; @@ -8118,18 +10170,22 @@ mod tests { inset-inline-start: 4px; inset-inline-end: 5px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { inset: 4px; inset-inline: 4px 5px; } - "#}); + "#}, + ); - prefix_test(r#" + prefix_test( + r#" .foo { inset-inline-start: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { left: var(--ltr, 2px); right: var(--rtl, 2px); @@ -8145,17 +10201,21 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { inset-inline-start: 2px; inset-inline-end: 4px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { left: var(--ltr, 2px) var(--rtl, 4px); right: var(--ltr, 4px) var(--rtl, 2px); @@ -8171,72 +10231,90 @@ mod tests { --rtl: initial; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { inset-inline: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { left: 2px; right: 2px; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { inset-block-start: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { top: 2px; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { inset-block-end: 2px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { bottom: 2px; } "# - }, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + }, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { top: 1px; left: 2px; bottom: 3px; right: 4px; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { top: 1px; bottom: 3px; left: 2px; right: 4px; } - "#}, Browsers { - safari: Some(8 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }, + ); } #[test] @@ -8245,120 +10323,150 @@ mod tests { minify_test(".foo { overflow: hidden hidden }", ".foo{overflow:hidden}"); minify_test(".foo { overflow: hidden auto }", ".foo{overflow:hidden auto}"); - test(r#" + test( + r#" .foo { overflow-x: hidden; overflow-y: auto; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { overflow: hidden auto; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { overflow: hidden; overflow-y: auto; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { overflow: hidden auto; } - "#}); - test(r#" + "#}, + ); + test( + r#" .foo { overflow: hidden; overflow-y: var(--y); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { overflow: hidden; overflow-y: var(--y); } - "#}); - prefix_test(r#" + "#}, + ); + prefix_test( + r#" .foo { overflow: hidden auto; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { overflow-x: hidden; overflow-y: auto; } "#}, - Browsers { - chrome: Some(67 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + chrome: Some(67 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { overflow: hidden hidden; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { overflow: hidden; } "#}, - Browsers { - chrome: Some(67 << 16), - ..Browsers::default() - }); - prefix_test(r#" + Browsers { + chrome: Some(67 << 16), + ..Browsers::default() + }, + ); + prefix_test( + r#" .foo { overflow: hidden auto; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { overflow: hidden auto; } "#}, - Browsers { - chrome: Some(68 << 16), - ..Browsers::default() - }); + Browsers { + chrome: Some(68 << 16), + ..Browsers::default() + }, + ); minify_test(".foo { text-overflow: ellipsis }", ".foo{text-overflow:ellipsis}"); - prefix_test(r#" + prefix_test( + r#" .foo { text-overflow: ellipsis; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -o-text-overflow: ellipsis; text-overflow: ellipsis; } "#}, - Browsers { - safari: Some(4 << 16), - opera: Some(10 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(4 << 16), + opera: Some(10 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { -o-text-overflow: ellipsis; text-overflow: ellipsis; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { text-overflow: ellipsis; } "#}, - Browsers { - safari: Some(4 << 16), - opera: Some(14 << 16), - ..Browsers::default() - }); - } + Browsers { + safari: Some(4 << 16), + opera: Some(14 << 16), + ..Browsers::default() + }, + ); + } #[test] fn test_ui() { minify_test(".foo { resize: both }", ".foo{resize:both}"); minify_test(".foo { resize: Horizontal }", ".foo{resize:horizontal}"); minify_test(".foo { cursor: ew-resize }", ".foo{cursor:ew-resize}"); - minify_test(".foo { cursor: url(\"test.cur\"), ew-resize }", ".foo{cursor:url(test.cur),ew-resize}"); - minify_test(".foo { cursor: url(\"test.cur\"), url(\"foo.cur\"), ew-resize }", ".foo{cursor:url(test.cur),url(foo.cur),ew-resize}"); + minify_test( + ".foo { cursor: url(\"test.cur\"), ew-resize }", + ".foo{cursor:url(test.cur),ew-resize}", + ); + minify_test( + ".foo { cursor: url(\"test.cur\"), url(\"foo.cur\"), ew-resize }", + ".foo{cursor:url(test.cur),url(foo.cur),ew-resize}", + ); minify_test(".foo { caret-color: auto }", ".foo{caret-color:auto}"); minify_test(".foo { caret-color: yellow }", ".foo{caret-color:#ff0}"); minify_test(".foo { caret-shape: block }", ".foo{caret-shape:block}"); @@ -8375,13 +10483,18 @@ mod tests { minify_test(".foo { accent-color: auto }", ".foo{accent-color:auto}"); minify_test(".foo { accent-color: yellow }", ".foo{accent-color:#ff0}"); minify_test(".foo { appearance: None }", ".foo{appearance:none}"); - minify_test(".foo { -webkit-appearance: textfield }", ".foo{-webkit-appearance:textfield}"); + minify_test( + ".foo { -webkit-appearance: textfield }", + ".foo{-webkit-appearance:textfield}", + ); - prefix_test(r#" + prefix_test( + r#" .foo { user-select: none; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-user-select: none; -moz-user-select: none; @@ -8389,59 +10502,68 @@ mod tests { user-select: none; } "#}, - Browsers { - safari: Some(8 << 16), - opera: Some(5 << 16), - firefox: Some(10 << 16), - ie: Some(10 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(8 << 16), + opera: Some(5 << 16), + firefox: Some(10 << 16), + ie: Some(10 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-user-select: none; user-select: none; } "#}, - Browsers { - safari: Some(8 << 16), - opera: Some(80 << 16), - firefox: Some(80 << 16), - edge: Some(80 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(8 << 16), + opera: Some(80 << 16), + firefox: Some(80 << 16), + edge: Some(80 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { user-select: none; } "#}, - Browsers { - opera: Some(80 << 16), - firefox: Some(80 << 16), - edge: Some(80 << 16), - ..Browsers::default() - }); + Browsers { + opera: Some(80 << 16), + firefox: Some(80 << 16), + edge: Some(80 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { appearance: none; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-appearance: none; -moz-appearance: none; @@ -8449,53 +10571,60 @@ mod tests { appearance: none; } "#}, - Browsers { - safari: Some(8 << 16), - chrome: Some(80 << 16), - firefox: Some(10 << 16), - ie: Some(11 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(8 << 16), + chrome: Some(80 << 16), + firefox: Some(10 << 16), + ie: Some(11 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { -webkit-appearance: none; -moz-appearance: none; -ms-appearance: none; appearance: none; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { -webkit-appearance: none; appearance: none; } "#}, - Browsers { - safari: Some(15 << 16), - chrome: Some(85 << 16), - firefox: Some(80 << 16), - edge: Some(85 << 16), - ..Browsers::default() - }); + Browsers { + safari: Some(15 << 16), + chrome: Some(85 << 16), + firefox: Some(80 << 16), + edge: Some(85 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { -webkit-appearance: none; -moz-appearance: none; -ms-appearance: none; appearance: none; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { appearance: none; } "#}, - Browsers { - chrome: Some(85 << 16), - firefox: Some(80 << 16), - edge: Some(85 << 16), - ..Browsers::default() - }); + Browsers { + chrome: Some(85 << 16), + firefox: Some(80 << 16), + edge: Some(85 << 16), + ..Browsers::default() + }, + ); prefix_test( ".foo { caret-color: lch(50.998% 135.363 338) }", @@ -8510,7 +10639,7 @@ mod tests { chrome: Some(90 << 16), safari: Some(14 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8526,7 +10655,7 @@ mod tests { chrome: Some(90 << 16), safari: Some(14 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8545,7 +10674,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); } @@ -8553,48 +10682,78 @@ mod tests { fn test_list() { minify_test(".foo { list-style-type: disc; }", ".foo{list-style-type:disc}"); minify_test(".foo { list-style-type: \"★\"; }", ".foo{list-style-type:\"★\"}"); - minify_test(".foo { list-style-type: symbols(cyclic '○' '●'); }", ".foo{list-style-type:symbols(cyclic \"○\" \"●\")}"); - minify_test(".foo { list-style-type: symbols('○' '●'); }", ".foo{list-style-type:symbols(\"○\" \"●\")}"); - minify_test(".foo { list-style-type: symbols(symbolic '○' '●'); }", ".foo{list-style-type:symbols(\"○\" \"●\")}"); - minify_test(".foo { list-style-type: symbols(symbolic url('ellipse.png')); }", ".foo{list-style-type:symbols(url(ellipse.png))}"); - minify_test(".foo { list-style-image: url('ellipse.png'); }", ".foo{list-style-image:url(ellipse.png)}"); - minify_test(".foo { list-style-position: outside; }", ".foo{list-style-position:outside}"); - minify_test(".foo { list-style: \"★\" url(ellipse.png) outside; }", ".foo{list-style:\"★\" url(ellipse.png)}"); + minify_test( + ".foo { list-style-type: symbols(cyclic '○' '●'); }", + ".foo{list-style-type:symbols(cyclic \"○\" \"●\")}", + ); + minify_test( + ".foo { list-style-type: symbols('○' '●'); }", + ".foo{list-style-type:symbols(\"○\" \"●\")}", + ); + minify_test( + ".foo { list-style-type: symbols(symbolic '○' '●'); }", + ".foo{list-style-type:symbols(\"○\" \"●\")}", + ); + minify_test( + ".foo { list-style-type: symbols(symbolic url('ellipse.png')); }", + ".foo{list-style-type:symbols(url(ellipse.png))}", + ); + minify_test( + ".foo { list-style-image: url('ellipse.png'); }", + ".foo{list-style-image:url(ellipse.png)}", + ); + minify_test( + ".foo { list-style-position: outside; }", + ".foo{list-style-position:outside}", + ); + minify_test( + ".foo { list-style: \"★\" url(ellipse.png) outside; }", + ".foo{list-style:\"★\" url(ellipse.png)}", + ); - test(r#" + test( + r#" .foo { list-style-type: disc; list-style-image: url(ellipse.png); list-style-position: outside; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { list-style: url(ellipse.png); } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { list-style: \"★\" url(ellipse.png) outside; list-style-image: none; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { list-style: \"★\"; } - "#}); + "#}, + ); - test(r#" + test( + r#" .foo { list-style: \"★\" url(ellipse.png) outside; list-style-image: var(--img); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { list-style: \"★\" url(ellipse.png); list-style-image: var(--img); } - "#}); + "#}, + ); prefix_test( ".foo { list-style-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) }", @@ -8609,7 +10768,7 @@ mod tests { Browsers { chrome: Some(8 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8623,7 +10782,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8642,72 +10801,93 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); } #[test] fn test_image_set() { - minify_test(".foo { background: image-set(\"foo.png\" 2x, url(bar.png) 1x) }", ".foo{background:image-set(\"foo.png\" 2x,\"bar.png\")}"); - minify_test(".foo { background: image-set('foo.webp' type('webp'), url(foo.jpg)) }", ".foo{background:image-set(\"foo.webp\" type(\"webp\"),\"foo.jpg\")}"); - minify_test(".foo { background: -webkit-image-set(url(\"foo.png\") 2x, url(bar.png) 1x) }", ".foo{background:-webkit-image-set(url(foo.png) 2x,url(bar.png))}"); - - test(r#" + minify_test( + ".foo { background: image-set(\"foo.png\" 2x, url(bar.png) 1x) }", + ".foo{background:image-set(\"foo.png\" 2x,\"bar.png\")}", + ); + minify_test( + ".foo { background: image-set('foo.webp' type('webp'), url(foo.jpg)) }", + ".foo{background:image-set(\"foo.webp\" type(\"webp\"),\"foo.jpg\")}", + ); + minify_test( + ".foo { background: -webkit-image-set(url(\"foo.png\") 2x, url(bar.png) 1x) }", + ".foo{background:-webkit-image-set(url(foo.png) 2x,url(bar.png))}", + ); + + test( + r#" .foo { background: -webkit-image-set(url("foo.png") 2x, url(bar.png) 1x); background: image-set(url("foo.png") 2x, url(bar.png) 1x); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: -webkit-image-set(url(foo.png) 2x, url(bar.png)); background: image-set("foo.png" 2x, "bar.png"); } - "#}); + "#}, + ); - prefix_test(r#" + prefix_test( + r#" .foo { background: image-set(url("foo.png") 2x, url(bar.png) 1x); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: -webkit-image-set(url(foo.png) 2x, url(bar.png)); background: image-set("foo.png" 2x, "bar.png"); } "#}, - Browsers { - chrome: Some(85 << 16), - firefox: Some(80 << 16), - ..Browsers::default() - }); + Browsers { + chrome: Some(85 << 16), + firefox: Some(80 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { background: -webkit-image-set(url("foo.png") 2x, url(bar.png) 1x); background: image-set(url("foo.png") 2x, url(bar.png) 1x); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: image-set("foo.png" 2x, "bar.png"); } "#}, - Browsers { - firefox: Some(80 << 16), - ..Browsers::default() - }); + Browsers { + firefox: Some(80 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { background: -webkit-image-set(url("foo.png") 2x, url(bar.png) 1x); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { background: -webkit-image-set(url(foo.png) 2x, url(bar.png)); } "#}, - Browsers { - chrome: Some(95 << 16), - ..Browsers::default() - }); + Browsers { + chrome: Some(95 << 16), + ..Browsers::default() + }, + ); } #[test] @@ -8738,35 +10918,101 @@ mod tests { minify_test(".foo { color: hwb(194 50% 0%) }", ".foo{color:#80e1ff}"); minify_test(".foo { color: hwb(194 50% 50%) }", ".foo{color:gray}"); // minify_test(".foo { color: ActiveText }", ".foo{color:ActiveTet}"); - minify_test(".foo { color: lab(29.2345% 39.3825 20.0664); }", ".foo{color:lab(29.2345% 39.3825 20.0664)}"); - minify_test(".foo { color: lab(29.2345% 39.3825 20.0664 / 100%); }", ".foo{color:lab(29.2345% 39.3825 20.0664)}"); - minify_test(".foo { color: lab(29.2345% 39.3825 20.0664 / 50%); }", ".foo{color:lab(29.2345% 39.3825 20.0664/.5)}"); - minify_test(".foo { color: lch(29.2345% 44.2 27); }", ".foo{color:lch(29.2345% 44.2 27)}"); - minify_test(".foo { color: lch(29.2345% 44.2 45deg); }", ".foo{color:lch(29.2345% 44.2 45)}"); - minify_test(".foo { color: lch(29.2345% 44.2 .5turn); }", ".foo{color:lch(29.2345% 44.2 180)}"); - minify_test(".foo { color: lch(29.2345% 44.2 27 / 100%); }", ".foo{color:lch(29.2345% 44.2 27)}"); - minify_test(".foo { color: lch(29.2345% 44.2 27 / 50%); }", ".foo{color:lch(29.2345% 44.2 27/.5)}"); - minify_test(".foo { color: oklab(40.101% 0.1147 0.0453); }", ".foo{color:oklab(40.101% .1147 .0453)}"); - minify_test(".foo { color: oklch(40.101% 0.12332 21.555); }", ".foo{color:oklch(40.101% .12332 21.555)}"); - minify_test(".foo { color: oklch(40.101% 0.12332 .5turn); }", ".foo{color:oklch(40.101% .12332 180)}"); - minify_test(".foo { color: color(display-p3 1 0.5 0); }", ".foo{color:color(display-p3 1 .5)}"); - minify_test(".foo { color: color(display-p3 100% 50% 0%); }", ".foo{color:color(display-p3 1 .5)}"); - minify_test(".foo { color: color(xyz-d50 0.2005 0.14089 0.4472); }", ".foo{color:color(xyz-d50 .2005 .14089 .4472)}"); - minify_test(".foo { color: color(xyz-d50 20.05% 14.089% 44.72%); }", ".foo{color:color(xyz-d50 .2005 .14089 .4472)}"); - minify_test(".foo { color: color(xyz-d65 0.2005 0.14089 0.4472); }", ".foo{color:color(xyz .2005 .14089 .4472)}"); - minify_test(".foo { color: color(xyz-d65 20.05% 14.089% 44.72%); }", ".foo{color:color(xyz .2005 .14089 .4472)}"); - minify_test(".foo { color: color(xyz 0.2005 0.14089 0.4472); }", ".foo{color:color(xyz .2005 .14089 .4472)}"); - minify_test(".foo { color: color(xyz 20.05% 14.089% 44.72%); }", ".foo{color:color(xyz .2005 .14089 .4472)}"); + minify_test( + ".foo { color: lab(29.2345% 39.3825 20.0664); }", + ".foo{color:lab(29.2345% 39.3825 20.0664)}", + ); + minify_test( + ".foo { color: lab(29.2345% 39.3825 20.0664 / 100%); }", + ".foo{color:lab(29.2345% 39.3825 20.0664)}", + ); + minify_test( + ".foo { color: lab(29.2345% 39.3825 20.0664 / 50%); }", + ".foo{color:lab(29.2345% 39.3825 20.0664/.5)}", + ); + minify_test( + ".foo { color: lch(29.2345% 44.2 27); }", + ".foo{color:lch(29.2345% 44.2 27)}", + ); + minify_test( + ".foo { color: lch(29.2345% 44.2 45deg); }", + ".foo{color:lch(29.2345% 44.2 45)}", + ); + minify_test( + ".foo { color: lch(29.2345% 44.2 .5turn); }", + ".foo{color:lch(29.2345% 44.2 180)}", + ); + minify_test( + ".foo { color: lch(29.2345% 44.2 27 / 100%); }", + ".foo{color:lch(29.2345% 44.2 27)}", + ); + minify_test( + ".foo { color: lch(29.2345% 44.2 27 / 50%); }", + ".foo{color:lch(29.2345% 44.2 27/.5)}", + ); + minify_test( + ".foo { color: oklab(40.101% 0.1147 0.0453); }", + ".foo{color:oklab(40.101% .1147 .0453)}", + ); + minify_test( + ".foo { color: oklch(40.101% 0.12332 21.555); }", + ".foo{color:oklch(40.101% .12332 21.555)}", + ); + minify_test( + ".foo { color: oklch(40.101% 0.12332 .5turn); }", + ".foo{color:oklch(40.101% .12332 180)}", + ); + minify_test( + ".foo { color: color(display-p3 1 0.5 0); }", + ".foo{color:color(display-p3 1 .5)}", + ); + minify_test( + ".foo { color: color(display-p3 100% 50% 0%); }", + ".foo{color:color(display-p3 1 .5)}", + ); + minify_test( + ".foo { color: color(xyz-d50 0.2005 0.14089 0.4472); }", + ".foo{color:color(xyz-d50 .2005 .14089 .4472)}", + ); + minify_test( + ".foo { color: color(xyz-d50 20.05% 14.089% 44.72%); }", + ".foo{color:color(xyz-d50 .2005 .14089 .4472)}", + ); + minify_test( + ".foo { color: color(xyz-d65 0.2005 0.14089 0.4472); }", + ".foo{color:color(xyz .2005 .14089 .4472)}", + ); + minify_test( + ".foo { color: color(xyz-d65 20.05% 14.089% 44.72%); }", + ".foo{color:color(xyz .2005 .14089 .4472)}", + ); + minify_test( + ".foo { color: color(xyz 0.2005 0.14089 0.4472); }", + ".foo{color:color(xyz .2005 .14089 .4472)}", + ); + minify_test( + ".foo { color: color(xyz 20.05% 14.089% 44.72%); }", + ".foo{color:color(xyz .2005 .14089 .4472)}", + ); minify_test(".foo { color: color(xyz 0.2005 0 0); }", ".foo{color:color(xyz .2005)}"); minify_test(".foo { color: color(xyz 0 0 0); }", ".foo{color:color(xyz)}"); minify_test(".foo { color: color(xyz 0 1 0); }", ".foo{color:color(xyz 0 1)}"); minify_test(".foo { color: color(xyz 0 1); }", ".foo{color:color(xyz 0 1)}"); minify_test(".foo { color: color(xyz 1); }", ".foo{color:color(xyz 1)}"); minify_test(".foo { color: color(xyz); }", ".foo{color:color(xyz)}"); - minify_test(".foo { color: color(xyz 0 1 0 / 20%); }", ".foo{color:color(xyz 0 1/.2)}"); + minify_test( + ".foo { color: color(xyz 0 1 0 / 20%); }", + ".foo{color:color(xyz 0 1/.2)}", + ); minify_test(".foo { color: color(xyz / 20%); }", ".foo{color:color(xyz/.2)}"); - minify_test(".foo { color: color(display-p3 100% 50% 0 / 20%); }", ".foo{color:color(display-p3 1 .5/.2)}"); - minify_test(".foo { color: color(display-p3 100% / 20%); }", ".foo{color:color(display-p3 1/.2)}"); + minify_test( + ".foo { color: color(display-p3 100% 50% 0 / 20%); }", + ".foo{color:color(display-p3 1 .5/.2)}", + ); + minify_test( + ".foo { color: color(display-p3 100% / 20%); }", + ".foo{color:color(display-p3 1/.2)}", + ); minify_test(".foo { color: hsl(none none none) }", ".foo{color:#000}"); minify_test(".foo { color: hwb(none none none) }", ".foo{color:red}"); minify_test(".foo { color: rgb(none none none) }", ".foo{color:#000}"); @@ -8781,7 +11027,7 @@ mod tests { Browsers { chrome: Some(95 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8794,7 +11040,7 @@ mod tests { Browsers { ie: Some(11 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8807,7 +11053,7 @@ mod tests { Browsers { ie: Some(11 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8822,7 +11068,7 @@ mod tests { safari: Some(10 << 16), ios_saf: Some(9 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8837,7 +11083,7 @@ mod tests { safari: Some(10 << 16), ios_saf: Some(10 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8851,7 +11097,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8865,7 +11111,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8879,7 +11125,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8893,7 +11139,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8906,7 +11152,7 @@ mod tests { Browsers { safari: Some(15 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8921,7 +11167,7 @@ mod tests { chrome: Some(90 << 16), safari: Some(15 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8937,7 +11183,7 @@ mod tests { chrome: Some(90 << 16), safari: Some(14 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8953,7 +11199,7 @@ mod tests { chrome: Some(90 << 16), safari: Some(14 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8968,7 +11214,7 @@ mod tests { chrome: Some(90 << 16), safari: Some(15 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8982,7 +11228,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -8996,7 +11242,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -9011,7 +11257,7 @@ mod tests { chrome: Some(90 << 16), safari: Some(14 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -9024,7 +11270,7 @@ mod tests { Browsers { safari: Some(14 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -9039,7 +11285,7 @@ mod tests { chrome: Some(90 << 16), safari: Some(15 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -9053,7 +11299,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -9067,7 +11313,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -9080,7 +11326,7 @@ mod tests { Browsers { safari: Some(15 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -9094,7 +11340,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -9108,7 +11354,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -9122,7 +11368,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -9136,7 +11382,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -9152,7 +11398,7 @@ mod tests { chrome: Some(90 << 16), safari: Some(14 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -9168,7 +11414,7 @@ mod tests { chrome: Some(90 << 16), safari: Some(14 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -9187,62 +11433,173 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); } #[test] fn test_color_mix() { - minify_test(".foo { color: color-mix(in lab, purple 50%, plum 50%); }", ".foo{color:lab(51.5117% 43.3777 -29.0443)}"); - minify_test(".foo { color: color-mix(in lch, peru 40%, palegoldenrod); }", ".foo{color:lch(79.7255% 40.4542 84.7634)}"); - minify_test(".foo { color: color-mix(in lch, teal 65%, olive); }", ".foo{color:lch(49.4431% 40.4806 162.546)}"); - minify_test(".foo { color: color-mix(in lch, white, black); }", ".foo{color:lch(50% 0 none)}"); - minify_test(".foo { color: color-mix(in xyz, rgb(82.02% 30.21% 35.02%) 75.23%, rgb(5.64% 55.94% 85.31%)); }", ".foo{color:color(xyz .287458 .208776 .260566)}"); - minify_test(".foo { color: color-mix(in lch, white, blue); }", ".foo{color:lch(64.7842% 65.6007 301.364)}"); - minify_test(".foo { color: color-mix(in oklch, white, blue); }", ".foo{color:oklch(72.6007% .156607 264.052)}"); - minify_test(".foo { color: color-mix(in srgb, white, blue); }", ".foo{color:#8080ff}"); - minify_test(".foo { color: color-mix(in lch, blue, white); }", ".foo{color:lch(64.7842% 65.6007 301.364)}"); - minify_test(".foo { color: color-mix(in oklch, blue, white); }", ".foo{color:oklch(72.6007% .156607 264.052)}"); - minify_test(".foo { color: color-mix(in srgb, blue, white); }", ".foo{color:#8080ff}"); + minify_test( + ".foo { color: color-mix(in lab, purple 50%, plum 50%); }", + ".foo{color:lab(51.5117% 43.3777 -29.0443)}", + ); + minify_test( + ".foo { color: color-mix(in lch, peru 40%, palegoldenrod); }", + ".foo{color:lch(79.7255% 40.4542 84.7634)}", + ); + minify_test( + ".foo { color: color-mix(in lch, teal 65%, olive); }", + ".foo{color:lch(49.4431% 40.4806 162.546)}", + ); + minify_test( + ".foo { color: color-mix(in lch, white, black); }", + ".foo{color:lch(50% 0 none)}", + ); + minify_test( + ".foo { color: color-mix(in xyz, rgb(82.02% 30.21% 35.02%) 75.23%, rgb(5.64% 55.94% 85.31%)); }", + ".foo{color:color(xyz .287458 .208776 .260566)}", + ); + minify_test( + ".foo { color: color-mix(in lch, white, blue); }", + ".foo{color:lch(64.7842% 65.6007 301.364)}", + ); + minify_test( + ".foo { color: color-mix(in oklch, white, blue); }", + ".foo{color:oklch(72.6007% .156607 264.052)}", + ); + minify_test( + ".foo { color: color-mix(in srgb, white, blue); }", + ".foo{color:#8080ff}", + ); + minify_test( + ".foo { color: color-mix(in lch, blue, white); }", + ".foo{color:lch(64.7842% 65.6007 301.364)}", + ); + minify_test( + ".foo { color: color-mix(in oklch, blue, white); }", + ".foo{color:oklch(72.6007% .156607 264.052)}", + ); + minify_test( + ".foo { color: color-mix(in srgb, blue, white); }", + ".foo{color:#8080ff}", + ); // minify_test(".foo { color: color-mix(in hsl, color(display-p3 0 1 0) 80%, yellow); }", ".foo{color:hsl(108 100% 49.9184%) }"); - minify_test(".foo { color: color-mix(in hsl, hsl(120 100% 49.898%) 80%, yellow); }", ".foo{color:#33fe00}"); - minify_test(".foo { color: color-mix(in srgb, rgb(100% 0% 0% / 0.7) 25%, rgb(0% 100% 0% / 0.2)); }", ".foo{color:#89760053}"); - minify_test(".foo { color: color-mix(in srgb, rgb(100% 0% 0% / 0.7) 20%, rgb(0% 100% 0% / 0.2) 60%); }", ".foo{color:#89760042}"); - minify_test(".foo { color: color-mix(in lch, color(display-p3 0 1 none), color(display-p3 0 0 1)); }", ".foo{color:lch(58.8143% 141.732 218.684)}"); - minify_test(".foo { color: color-mix(in srgb, rgb(128 128 none), rgb(none none 128)); }", ".foo{color:gray}"); - minify_test(".foo { color: color-mix(in srgb, rgb(50% 50% none), rgb(none none 50%)); }", ".foo{color:gray}"); - minify_test(".foo { color: color-mix(in srgb, rgb(none 50% none), rgb(50% none 50%)); }", ".foo{color:gray}"); - minify_test(".foo { --color: color-mix(in lch, teal 65%, olive); }", ".foo{--color:lch(49.4431% 40.4806 162.546)}"); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120 100% 49.898%) 80%, yellow); }", + ".foo{color:#33fe00}", + ); + minify_test( + ".foo { color: color-mix(in srgb, rgb(100% 0% 0% / 0.7) 25%, rgb(0% 100% 0% / 0.2)); }", + ".foo{color:#89760053}", + ); + minify_test( + ".foo { color: color-mix(in srgb, rgb(100% 0% 0% / 0.7) 20%, rgb(0% 100% 0% / 0.2) 60%); }", + ".foo{color:#89760042}", + ); + minify_test( + ".foo { color: color-mix(in lch, color(display-p3 0 1 none), color(display-p3 0 0 1)); }", + ".foo{color:lch(58.8143% 141.732 218.684)}", + ); + minify_test( + ".foo { color: color-mix(in srgb, rgb(128 128 none), rgb(none none 128)); }", + ".foo{color:gray}", + ); + minify_test( + ".foo { color: color-mix(in srgb, rgb(50% 50% none), rgb(none none 50%)); }", + ".foo{color:gray}", + ); + minify_test( + ".foo { color: color-mix(in srgb, rgb(none 50% none), rgb(50% none 50%)); }", + ".foo{color:gray}", + ); + minify_test( + ".foo { --color: color-mix(in lch, teal 65%, olive); }", + ".foo{--color:lch(49.4431% 40.4806 162.546)}", + ); // regex for converting web platform tests: // test_computed_value\(.*?, `(.*?)`, `(.*?)`\); // minify_test(".foo { color: $1 }", ".foo{color:$2}"); // https://github.com/web-platform-tests/wpt/blob/f8c76b11cff66a7adc87264a18e39353cb5a60c9/css/css-color/parsing/color-mix-computed.html - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%)) }", ".foo{color:#545c3d}"); - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40%)) }", ".foo{color:#706a43}"); - minify_test(".foo { color: color-mix(in hsl, 25% hsl(120deg 10% 20%), hsl(30deg 30% 40%)) }", ".foo{color:#706a43}"); - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20%), 25% hsl(30deg 30% 40%)) }", ".foo{color:#3d4936}"); - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%) 25%) }", ".foo{color:#3d4936}"); - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40%) 75%) }", ".foo{color:#706a43}"); - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 30%, hsl(30deg 30% 40%) 90%) }", ".foo{color:#706a43}"); // Scale down > 100% sum. - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 12.5%, hsl(30deg 30% 40%) 37.5%) }", ".foo{color:#706a4380}"); // Scale up < 100% sum, causes alpha multiplication. - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 0%, hsl(30deg 30% 40%)) }", ".foo{color:#856647}"); - - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4), hsl(30deg 30% 40% / .8)) }", ".foo{color:#5f694199}"); - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40% / .8)) }", ".foo{color:#6c6742d9}"); - minify_test(".foo { color: color-mix(in hsl, 25% hsl(120deg 10% 20% / .4), hsl(30deg 30% 40% / .8)) }", ".foo{color:#797245b3}"); - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4), 25% hsl(30deg 30% 40% / .8)) }", ".foo{color:#44543b80}"); - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4), hsl(30deg 30% 40% / .8) 25%) }", ".foo{color:#44543b80}"); - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4) 25%, hsl(30deg 30% 40% / .8) 75%) }", ".foo{color:#797245b3}"); - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4) 30%, hsl(30deg 30% 40% / .8) 90%) }", ".foo{color:#797245b3}"); // Scale down > 100% sum. - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4) 12.5%, hsl(30deg 30% 40% / .8) 37.5%) }", ".foo{color:#79724559}"); // Scale up < 100% sum, causes alpha multiplication. - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4) 0%, hsl(30deg 30% 40% / .8)) }", ".foo{color:#856647cc}"); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%)) }", + ".foo{color:#545c3d}", + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40%)) }", + ".foo{color:#706a43}", + ); + minify_test( + ".foo { color: color-mix(in hsl, 25% hsl(120deg 10% 20%), hsl(30deg 30% 40%)) }", + ".foo{color:#706a43}", + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20%), 25% hsl(30deg 30% 40%)) }", + ".foo{color:#3d4936}", + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20%), hsl(30deg 30% 40%) 25%) }", + ".foo{color:#3d4936}", + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40%) 75%) }", + ".foo{color:#706a43}", + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 30%, hsl(30deg 30% 40%) 90%) }", + ".foo{color:#706a43}", + ); // Scale down > 100% sum. + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 12.5%, hsl(30deg 30% 40%) 37.5%) }", + ".foo{color:#706a4380}", + ); // Scale up < 100% sum, causes alpha multiplication. + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 0%, hsl(30deg 30% 40%)) }", + ".foo{color:#856647}", + ); + + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4), hsl(30deg 30% 40% / .8)) }", + ".foo{color:#5f694199}", + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20%) 25%, hsl(30deg 30% 40% / .8)) }", + ".foo{color:#6c6742d9}", + ); + minify_test( + ".foo { color: color-mix(in hsl, 25% hsl(120deg 10% 20% / .4), hsl(30deg 30% 40% / .8)) }", + ".foo{color:#797245b3}", + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4), 25% hsl(30deg 30% 40% / .8)) }", + ".foo{color:#44543b80}", + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4), hsl(30deg 30% 40% / .8) 25%) }", + ".foo{color:#44543b80}", + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4) 25%, hsl(30deg 30% 40% / .8) 75%) }", + ".foo{color:#797245b3}", + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4) 30%, hsl(30deg 30% 40% / .8) 90%) }", + ".foo{color:#797245b3}", + ); // Scale down > 100% sum. + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4) 12.5%, hsl(30deg 30% 40% / .8) 37.5%) }", + ".foo{color:#79724559}", + ); // Scale up < 100% sum, causes alpha multiplication. + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 10% 20% / .4) 0%, hsl(30deg 30% 40% / .8)) }", + ".foo{color:#856647cc}", + ); fn canonicalize(s: &str) -> String { use crate::traits::{Parse, ToCss}; use crate::values::color::CssColor; - use cssparser::{ParserInput, Parser}; + use cssparser::{Parser, ParserInput}; let mut input = ParserInput::new(s); let mut parser = Parser::new(&mut input); @@ -9254,118 +11611,352 @@ mod tests { // test_computed_value\(.*?, `(.*?)`, canonicalize\(`(.*?)`\)\); // minify_test(".foo { color: $1 }", &canonicalize("$2")); - minify_test(".foo { color: color-mix(in hsl, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }", &canonicalize("hsl(50deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }", &canonicalize("hsl(50deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }", &canonicalize("hsl(10deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }", &canonicalize("hsl(10deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }", &canonicalize("hsl(350deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl, hsl(320deg 50% 50%), hsl(20deg 50% 50%)) }", &canonicalize("hsl(350deg 50% 50%)")); - - minify_test(".foo { color: color-mix(in hsl shorter hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }", &canonicalize("hsl(50deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl shorter hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }", &canonicalize("hsl(50deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl shorter hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }", &canonicalize("hsl(10deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl shorter hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }", &canonicalize("hsl(10deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl shorter hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }", &canonicalize("hsl(350deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl shorter hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%)) }", &canonicalize("hsl(350deg 50% 50%)")); - - minify_test(".foo { color: color-mix(in hsl longer hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }", &canonicalize("hsl(230deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl longer hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }", &canonicalize("hsl(230deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl longer hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }", &canonicalize("hsl(190deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl longer hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }", &canonicalize("hsl(190deg 50% 50%)")); + minify_test( + ".foo { color: color-mix(in hsl, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }", + &canonicalize("hsl(50deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }", + &canonicalize("hsl(50deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }", + &canonicalize("hsl(10deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }", + &canonicalize("hsl(10deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }", + &canonicalize("hsl(350deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(320deg 50% 50%), hsl(20deg 50% 50%)) }", + &canonicalize("hsl(350deg 50% 50%)"), + ); + + minify_test( + ".foo { color: color-mix(in hsl shorter hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }", + &canonicalize("hsl(50deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl shorter hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }", + &canonicalize("hsl(50deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl shorter hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }", + &canonicalize("hsl(10deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl shorter hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }", + &canonicalize("hsl(10deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl shorter hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }", + &canonicalize("hsl(350deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl shorter hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%)) }", + &canonicalize("hsl(350deg 50% 50%)"), + ); + + minify_test( + ".foo { color: color-mix(in hsl longer hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }", + &canonicalize("hsl(230deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl longer hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }", + &canonicalize("hsl(230deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl longer hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }", + &canonicalize("hsl(190deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl longer hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }", + &canonicalize("hsl(190deg 50% 50%)"), + ); // minify_test(".foo { color: color-mix(in hsl longer hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }", &canonicalize("hsl(170deg 50% 50%)")); // minify_test(".foo { color: color-mix(in hsl longer hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%)) }", &canonicalize("hsl(170deg 50% 50%)")); - - minify_test(".foo { color: color-mix(in hsl increasing hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }", &canonicalize("hsl(50deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl increasing hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }", &canonicalize("hsl(230deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl increasing hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }", &canonicalize("hsl(190deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl increasing hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }", &canonicalize("hsl(10deg 50% 50%)")); + + minify_test( + ".foo { color: color-mix(in hsl increasing hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }", + &canonicalize("hsl(50deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl increasing hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }", + &canonicalize("hsl(230deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl increasing hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }", + &canonicalize("hsl(190deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl increasing hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }", + &canonicalize("hsl(10deg 50% 50%)"), + ); // minify_test(".foo { color: color-mix(in hsl increasing hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }", &canonicalize("hsl(170deg 50% 50%)")); // minify_test(".foo { color: color-mix(in hsl increasing hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%)) }", &canonicalize("hsl(350deg 50% 50%)")); - - minify_test(".foo { color: color-mix(in hsl decreasing hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }", &canonicalize("hsl(230deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl decreasing hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }", &canonicalize("hsl(50deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl decreasing hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }", &canonicalize("hsl(10deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl decreasing hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }", &canonicalize("hsl(190deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl decreasing hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }", &canonicalize("hsl(350deg 50% 50%)")); + + minify_test( + ".foo { color: color-mix(in hsl decreasing hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }", + &canonicalize("hsl(230deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl decreasing hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }", + &canonicalize("hsl(50deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl decreasing hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }", + &canonicalize("hsl(10deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl decreasing hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }", + &canonicalize("hsl(190deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl decreasing hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }", + &canonicalize("hsl(350deg 50% 50%)"), + ); // minify_test(".foo { color: color-mix(in hsl decreasing hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%)) }", &canonicalize("hsl(170deg 50% 50%)")); - - minify_test(".foo { color: color-mix(in hsl specified hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }", &canonicalize("hsl(50deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl specified hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }", &canonicalize("hsl(50deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl specified hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }", &canonicalize("hsl(190deg 50% 50%)")); - minify_test(".foo { color: color-mix(in hsl specified hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }", &canonicalize("hsl(190deg 50% 50%)")); + + minify_test( + ".foo { color: color-mix(in hsl specified hue, hsl(40deg 50% 50%), hsl(60deg 50% 50%)) }", + &canonicalize("hsl(50deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl specified hue, hsl(60deg 50% 50%), hsl(40deg 50% 50%)) }", + &canonicalize("hsl(50deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl specified hue, hsl(50deg 50% 50%), hsl(330deg 50% 50%)) }", + &canonicalize("hsl(190deg 50% 50%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl specified hue, hsl(330deg 50% 50%), hsl(50deg 50% 50%)) }", + &canonicalize("hsl(190deg 50% 50%)"), + ); // minify_test(".foo { color: color-mix(in hsl specified hue, hsl(20deg 50% 50%), hsl(320deg 50% 50%)) }", &canonicalize("hsl(170deg 50% 50%)")); // minify_test(".foo { color: color-mix(in hsl specified hue, hsl(320deg 50% 50%), hsl(20deg 50% 50%)) }", &canonicalize("hsl(170deg 50% 50%)")); - - minify_test(".foo { color: color-mix(in hsl, hsl(none none none), hsl(none none none)) }", &canonicalize("hsl(none none none)")); - minify_test(".foo { color: color-mix(in hsl, hsl(none none none), hsl(30deg 40% 80%)) }", &canonicalize("hsl(30deg 40% 80%)")); - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 20% 40%), hsl(none none none)) }", &canonicalize("hsl(120deg 20% 40%)")); - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 20% none), hsl(30deg 40% 60%)) }", &canonicalize("hsl(75deg 30% 60%)")); - minify_test(".foo { color: color-mix(in hsl, hsl(120deg 20% 40%), hsl(30deg 20% none)) }", &canonicalize("hsl(75deg 20% 40%)")); - minify_test(".foo { color: color-mix(in hsl, hsl(none 20% 40%), hsl(30deg none 80%)) }", &canonicalize("hsl(30deg 20% 60%)")); - - minify_test(".foo { color: color-mix(in hsl, color(display-p3 0 1 0) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(0, 249, 66)")); // Naive clip based mapping would give rgb(0, 255, 0). - minify_test(".foo { color: color-mix(in hsl, lab(100% 104.3 -50.9) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(255, 255, 255)")); // Naive clip based mapping would give rgb(255, 150, 255). - minify_test(".foo { color: color-mix(in hsl, lab(0% 104.3 -50.9) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(42, 0, 34)")); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black, - minify_test(".foo { color: color-mix(in hsl, lch(100% 116 334) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(255, 255, 255)")); // Naive clip based mapping would give rgb(255, 150, 255). - minify_test(".foo { color: color-mix(in hsl, lch(0% 116 334) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(42, 0, 34)")); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black, - minify_test(".foo { color: color-mix(in hsl, oklab(100% 0.365 -0.16) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(255, 255, 255)")); // Naive clip based mapping would give rgb(255, 92, 255). - minify_test(".foo { color: color-mix(in hsl, oklab(0% 0.365 -0.16) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(0, 0, 0)")); // Naive clip based mapping would give rgb(19, 0, 24). - minify_test(".foo { color: color-mix(in hsl, oklch(100% 0.399 336.3) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(255, 255, 255)")); // Naive clip based mapping would give rgb(255, 91, 255). - minify_test(".foo { color: color-mix(in hsl, oklch(0% 0.399 336.3) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(0, 0, 0)")); // Naive clip based mapping would give rgb(20, 0, 24). - - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%)) }", &canonicalize("rgb(147, 179, 52)")); - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 25%, hwb(30deg 30% 40%)) }", &canonicalize("rgb(166, 153, 64)")); - minify_test(".foo { color: color-mix(in hwb, 25% hwb(120deg 10% 20%), hwb(30deg 30% 40%)) }", &canonicalize("rgb(166, 153, 64)")); - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20%), 25% hwb(30deg 30% 40%)) }", &canonicalize("rgb(96, 191, 39)")); - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%) 25%) }", &canonicalize("rgb(96, 191, 39)")); - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 25%, hwb(30deg 30% 40%) 75%) }", &canonicalize("rgb(166, 153, 64)")); - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 30%, hwb(30deg 30% 40%) 90%) }", &canonicalize("rgb(166, 153, 64)")); // Scale down > 100% sum. - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 12.5%, hwb(30deg 30% 40%) 37.5%) }", &canonicalize("rgba(166, 153, 64, 0.5)")); // Scale up < 100% sum, causes alpha multiplication. - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 0%, hwb(30deg 30% 40%)) }", &canonicalize("rgb(153, 115, 77)")); - - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4), hwb(30deg 30% 40% / .8)) }", &canonicalize("rgba(143, 170, 60, 0.6)")); - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 25%, hwb(30deg 30% 40% / .8)) }", &canonicalize("rgba(160, 149, 70, 0.7)")); - minify_test(".foo { color: color-mix(in hwb, 25% hwb(120deg 10% 20% / .4), hwb(30deg 30% 40% / .8)) }", &canonicalize("rgba(160, 149, 70, 0.7)")); - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20%), 25% hwb(30deg 30% 40% / .8)) }", &canonicalize("rgba(95, 193, 37, 0.95)")); - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4), hwb(30deg 30% 40% / .8) 25%) }", &canonicalize("rgba(98, 184, 46, 0.5)")); - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 25%, hwb(30deg 30% 40% / .8) 75%) }", &canonicalize("rgba(160, 149, 70, 0.7)")); - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 30%, hwb(30deg 30% 40% / .8) 90%) }", &canonicalize("rgba(160, 149, 70, 0.7)")); // Scale down > 100% sum. - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 12.5%, hwb(30deg 30% 40% / .8) 37.5%) }", &canonicalize("rgba(160, 149, 70, 0.35)")); // Scale up < 100% sum, causes alpha multiplication. - minify_test(".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 0%, hwb(30deg 30% 40% / .8)) }", &canonicalize("rgba(153, 115, 77, 0.8)")); - + + minify_test( + ".foo { color: color-mix(in hsl, hsl(none none none), hsl(none none none)) }", + &canonicalize("hsl(none none none)"), + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(none none none), hsl(30deg 40% 80%)) }", + &canonicalize("hsl(30deg 40% 80%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 20% 40%), hsl(none none none)) }", + &canonicalize("hsl(120deg 20% 40%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 20% none), hsl(30deg 40% 60%)) }", + &canonicalize("hsl(75deg 30% 60%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(120deg 20% 40%), hsl(30deg 20% none)) }", + &canonicalize("hsl(75deg 20% 40%)"), + ); + minify_test( + ".foo { color: color-mix(in hsl, hsl(none 20% 40%), hsl(30deg none 80%)) }", + &canonicalize("hsl(30deg 20% 60%)"), + ); + + minify_test( + ".foo { color: color-mix(in hsl, color(display-p3 0 1 0) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(0, 249, 66)"), + ); // Naive clip based mapping would give rgb(0, 255, 0). + minify_test( + ".foo { color: color-mix(in hsl, lab(100% 104.3 -50.9) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(255, 255, 255)"), + ); // Naive clip based mapping would give rgb(255, 150, 255). + minify_test( + ".foo { color: color-mix(in hsl, lab(0% 104.3 -50.9) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(42, 0, 34)"), + ); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black, + minify_test( + ".foo { color: color-mix(in hsl, lch(100% 116 334) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(255, 255, 255)"), + ); // Naive clip based mapping would give rgb(255, 150, 255). + minify_test( + ".foo { color: color-mix(in hsl, lch(0% 116 334) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(42, 0, 34)"), + ); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black, + minify_test( + ".foo { color: color-mix(in hsl, oklab(100% 0.365 -0.16) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(255, 255, 255)"), + ); // Naive clip based mapping would give rgb(255, 92, 255). + minify_test( + ".foo { color: color-mix(in hsl, oklab(0% 0.365 -0.16) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(0, 0, 0)"), + ); // Naive clip based mapping would give rgb(19, 0, 24). + minify_test( + ".foo { color: color-mix(in hsl, oklch(100% 0.399 336.3) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(255, 255, 255)"), + ); // Naive clip based mapping would give rgb(255, 91, 255). + minify_test( + ".foo { color: color-mix(in hsl, oklch(0% 0.399 336.3) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(0, 0, 0)"), + ); // Naive clip based mapping would give rgb(20, 0, 24). + + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%)) }", + &canonicalize("rgb(147, 179, 52)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 25%, hwb(30deg 30% 40%)) }", + &canonicalize("rgb(166, 153, 64)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, 25% hwb(120deg 10% 20%), hwb(30deg 30% 40%)) }", + &canonicalize("rgb(166, 153, 64)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20%), 25% hwb(30deg 30% 40%)) }", + &canonicalize("rgb(96, 191, 39)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20%), hwb(30deg 30% 40%) 25%) }", + &canonicalize("rgb(96, 191, 39)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 25%, hwb(30deg 30% 40%) 75%) }", + &canonicalize("rgb(166, 153, 64)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 30%, hwb(30deg 30% 40%) 90%) }", + &canonicalize("rgb(166, 153, 64)"), + ); // Scale down > 100% sum. + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 12.5%, hwb(30deg 30% 40%) 37.5%) }", + &canonicalize("rgba(166, 153, 64, 0.5)"), + ); // Scale up < 100% sum, causes alpha multiplication. + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20%) 0%, hwb(30deg 30% 40%)) }", + &canonicalize("rgb(153, 115, 77)"), + ); + + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4), hwb(30deg 30% 40% / .8)) }", + &canonicalize("rgba(143, 170, 60, 0.6)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 25%, hwb(30deg 30% 40% / .8)) }", + &canonicalize("rgba(160, 149, 70, 0.7)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, 25% hwb(120deg 10% 20% / .4), hwb(30deg 30% 40% / .8)) }", + &canonicalize("rgba(160, 149, 70, 0.7)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20%), 25% hwb(30deg 30% 40% / .8)) }", + &canonicalize("rgba(95, 193, 37, 0.95)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4), hwb(30deg 30% 40% / .8) 25%) }", + &canonicalize("rgba(98, 184, 46, 0.5)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 25%, hwb(30deg 30% 40% / .8) 75%) }", + &canonicalize("rgba(160, 149, 70, 0.7)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 30%, hwb(30deg 30% 40% / .8) 90%) }", + &canonicalize("rgba(160, 149, 70, 0.7)"), + ); // Scale down > 100% sum. + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 12.5%, hwb(30deg 30% 40% / .8) 37.5%) }", + &canonicalize("rgba(160, 149, 70, 0.35)"), + ); // Scale up < 100% sum, causes alpha multiplication. + minify_test( + ".foo { color: color-mix(in hwb, hwb(120deg 10% 20% / .4) 0%, hwb(30deg 30% 40% / .8)) }", + &canonicalize("rgba(153, 115, 77, 0.8)"), + ); + // minify_test(".foo { color: color-mix(in hwb, hwb(40deg 30% 40%), hwb(60deg 30% 40%)) }", &canonicalize("hwb(50deg 30% 40%)")); // minify_test(".foo { color: color-mix(in hwb, hwb(60deg 30% 40%), hwb(40deg 30% 40%)) }", &canonicalize("hwb(50deg 30% 40%)")); - minify_test(".foo { color: color-mix(in hwb, hwb(50deg 30% 40%), hwb(330deg 30% 40%)) }", &canonicalize("hwb(10deg 30% 40%)")); - minify_test(".foo { color: color-mix(in hwb, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }", &canonicalize("hwb(10deg 30% 40%)")); - minify_test(".foo { color: color-mix(in hwb, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }", &canonicalize("hwb(350deg 30% 40%)")); - minify_test(".foo { color: color-mix(in hwb, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }", &canonicalize("hwb(350deg 30% 40%)")); + minify_test( + ".foo { color: color-mix(in hwb, hwb(50deg 30% 40%), hwb(330deg 30% 40%)) }", + &canonicalize("hwb(10deg 30% 40%)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }", + &canonicalize("hwb(10deg 30% 40%)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }", + &canonicalize("hwb(350deg 30% 40%)"), + ); + minify_test( + ".foo { color: color-mix(in hwb, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }", + &canonicalize("hwb(350deg 30% 40%)"), + ); // minify_test(".foo { color: color-mix(in hwb shorter hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%)) }", &canonicalize("hwb(50deg 30% 40%)")); // minify_test(".foo { color: color-mix(in hwb shorter hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%)) }", &canonicalize("hwb(50deg 30% 40%)")); - minify_test(".foo { color: color-mix(in hwb shorter hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%)) }", &canonicalize("hwb(10deg 30% 40%)")); - minify_test(".foo { color: color-mix(in hwb shorter hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }", &canonicalize("hwb(10deg 30% 40%)")); - minify_test(".foo { color: color-mix(in hwb shorter hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }", &canonicalize("hwb(350deg 30% 40%)")); - minify_test(".foo { color: color-mix(in hwb shorter hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }", &canonicalize("hwb(350deg 30% 40%)")); + minify_test( + ".foo { color: color-mix(in hwb shorter hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%)) }", + &canonicalize("hwb(10deg 30% 40%)"), + ); + minify_test( + ".foo { color: color-mix(in hwb shorter hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }", + &canonicalize("hwb(10deg 30% 40%)"), + ); + minify_test( + ".foo { color: color-mix(in hwb shorter hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }", + &canonicalize("hwb(350deg 30% 40%)"), + ); + minify_test( + ".foo { color: color-mix(in hwb shorter hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }", + &canonicalize("hwb(350deg 30% 40%)"), + ); - minify_test(".foo { color: color-mix(in hwb longer hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%)) }", &canonicalize("hwb(230deg 30% 40%)")); - minify_test(".foo { color: color-mix(in hwb longer hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%)) }", &canonicalize("hwb(230deg 30% 40%)")); + minify_test( + ".foo { color: color-mix(in hwb longer hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%)) }", + &canonicalize("hwb(230deg 30% 40%)"), + ); + minify_test( + ".foo { color: color-mix(in hwb longer hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%)) }", + &canonicalize("hwb(230deg 30% 40%)"), + ); // minify_test(".foo { color: color-mix(in hwb longer hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%)) }", &canonicalize("hwb(190deg 30% 40%)")); // minify_test(".foo { color: color-mix(in hwb longer hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }", &canonicalize("hwb(190deg 30% 40%)")); // minify_test(".foo { color: color-mix(in hwb longer hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }", &canonicalize("hwb(170deg 30% 40%)")); // minify_test(".foo { color: color-mix(in hwb longer hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }", &canonicalize("hwb(170deg 30% 40%)")); - + // minify_test(".foo { color: color-mix(in hwb increasing hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%)) }", &canonicalize("hwb(50deg 30% 40%)")); - minify_test(".foo { color: color-mix(in hwb increasing hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%)) }", &canonicalize("hwb(230deg 30% 40%)")); + minify_test( + ".foo { color: color-mix(in hwb increasing hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%)) }", + &canonicalize("hwb(230deg 30% 40%)"), + ); // minify_test(".foo { color: color-mix(in hwb increasing hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%)) }", &canonicalize("hwb(190deg 30% 40%)")); - minify_test(".foo { color: color-mix(in hwb increasing hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }", &canonicalize("hwb(10deg 30% 40%)")); + minify_test( + ".foo { color: color-mix(in hwb increasing hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }", + &canonicalize("hwb(10deg 30% 40%)"), + ); // minify_test(".foo { color: color-mix(in hwb increasing hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }", &canonicalize("hwb(170deg 30% 40%)")); - minify_test(".foo { color: color-mix(in hwb increasing hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }", &canonicalize("hwb(350deg 30% 40%)")); + minify_test( + ".foo { color: color-mix(in hwb increasing hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }", + &canonicalize("hwb(350deg 30% 40%)"), + ); - minify_test(".foo { color: color-mix(in hwb decreasing hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%)) }", &canonicalize("hwb(230deg 30% 40%)")); + minify_test( + ".foo { color: color-mix(in hwb decreasing hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%)) }", + &canonicalize("hwb(230deg 30% 40%)"), + ); // minify_test(".foo { color: color-mix(in hwb decreasing hue, hwb(60deg 30% 40%), hwb(40deg 30% 40%)) }", &canonicalize("hwb(50deg 30% 40%)")); - minify_test(".foo { color: color-mix(in hwb decreasing hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%)) }", &canonicalize("hwb(10deg 30% 40%)")); + minify_test( + ".foo { color: color-mix(in hwb decreasing hue, hwb(50deg 30% 40%), hwb(330deg 30% 40%)) }", + &canonicalize("hwb(10deg 30% 40%)"), + ); // minify_test(".foo { color: color-mix(in hwb decreasing hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }", &canonicalize("hwb(190deg 30% 40%)")); - minify_test(".foo { color: color-mix(in hwb decreasing hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }", &canonicalize("hwb(350deg 30% 40%)")); + minify_test( + ".foo { color: color-mix(in hwb decreasing hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }", + &canonicalize("hwb(350deg 30% 40%)"), + ); // minify_test(".foo { color: color-mix(in hwb decreasing hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }", &canonicalize("hwb(170deg 30% 40%)")); // minify_test(".foo { color: color-mix(in hwb specified hue, hwb(40deg 30% 40%), hwb(60deg 30% 40%)) }", &canonicalize("hwb(50deg 30% 40%)")); @@ -9374,262 +11965,1120 @@ mod tests { // minify_test(".foo { color: color-mix(in hwb specified hue, hwb(330deg 30% 40%), hwb(50deg 30% 40%)) }", &canonicalize("hwb(190deg 30% 40%)")); // minify_test(".foo { color: color-mix(in hwb specified hue, hwb(20deg 30% 40%), hwb(320deg 30% 40%)) }", &canonicalize("hwb(170deg 30% 40%)")); // minify_test(".foo { color: color-mix(in hwb specified hue, hwb(320deg 30% 40%), hwb(20deg 30% 40%)) }", &canonicalize("hwb(170deg 30% 40%)")); - - minify_test(".foo { color: color-mix(in hwb, color(display-p3 0 1 0) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(0, 249, 66)")); // Naive clip based mapping would give rgb(0, 255, 0). - minify_test(".foo { color: color-mix(in hwb, lab(100% 104.3 -50.9) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(255, 255, 255)")); // Naive clip based mapping would give rgb(255, 150, 255). - minify_test(".foo { color: color-mix(in hwb, lab(0% 104.3 -50.9) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(42, 0, 34)")); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black, - minify_test(".foo { color: color-mix(in hwb, lch(100% 116 334) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(255, 255, 255)")); // Naive clip based mapping would give rgb(255, 150, 255). - minify_test(".foo { color: color-mix(in hwb, lch(0% 116 334) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(42, 0, 34)")); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black, - minify_test(".foo { color: color-mix(in hwb, oklab(100% 0.365 -0.16) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(255, 255, 255)")); // Naive clip based mapping would give rgb(255, 92, 255). - minify_test(".foo { color: color-mix(in hwb, oklab(0% 0.365 -0.16) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(0, 0, 0)")); // Naive clip based mapping would give rgb(19, 0, 24). - minify_test(".foo { color: color-mix(in hwb, oklch(100% 0.399 336.3) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(255, 255, 255)")); // Naive clip based mapping would give rgb(255, 91, 255). - minify_test(".foo { color: color-mix(in hwb, oklch(0% 0.399 336.3) 100%, rgb(0, 0, 0) 0%) }", &canonicalize("rgb(0, 0, 0)")); // Naive clip based mapping would give rgb(20, 0, 24). + + minify_test( + ".foo { color: color-mix(in hwb, color(display-p3 0 1 0) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(0, 249, 66)"), + ); // Naive clip based mapping would give rgb(0, 255, 0). + minify_test( + ".foo { color: color-mix(in hwb, lab(100% 104.3 -50.9) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(255, 255, 255)"), + ); // Naive clip based mapping would give rgb(255, 150, 255). + minify_test( + ".foo { color: color-mix(in hwb, lab(0% 104.3 -50.9) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(42, 0, 34)"), + ); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black, + minify_test( + ".foo { color: color-mix(in hwb, lch(100% 116 334) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(255, 255, 255)"), + ); // Naive clip based mapping would give rgb(255, 150, 255). + minify_test( + ".foo { color: color-mix(in hwb, lch(0% 116 334) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(42, 0, 34)"), + ); // Naive clip based mapping would give rgb(90, 0, 76). NOTE: 0% lightness in Lab/LCH does not automatically correspond with sRGB black, + minify_test( + ".foo { color: color-mix(in hwb, oklab(100% 0.365 -0.16) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(255, 255, 255)"), + ); // Naive clip based mapping would give rgb(255, 92, 255). + minify_test( + ".foo { color: color-mix(in hwb, oklab(0% 0.365 -0.16) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(0, 0, 0)"), + ); // Naive clip based mapping would give rgb(19, 0, 24). + minify_test( + ".foo { color: color-mix(in hwb, oklch(100% 0.399 336.3) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(255, 255, 255)"), + ); // Naive clip based mapping would give rgb(255, 91, 255). + minify_test( + ".foo { color: color-mix(in hwb, oklch(0% 0.399 336.3) 100%, rgb(0, 0, 0) 0%) }", + &canonicalize("rgb(0, 0, 0)"), + ); // Naive clip based mapping would give rgb(20, 0, 24). for color_space in &["lch", "oklch"] { // regex for converting web platform tests: // test_computed_value\(.*?, `color-mix\(in \$\{colorSpace\}(.*?), (.*?)\$\{colorSpace\}(.*?) \$\{colorSpace\}(.*?)`, `\$\{colorSpace\}(.*?)`\); // minify_test(&format!(".foo {{ color: color-mix(in {0}$1, $2{0}$3 {0}$4 }}", color_space), &format!(".foo{{color:{}$5}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), {0}(50% 60 70deg)) }}", color_space), &format!(".foo{{color:{}(30% 40 50)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 25%, {0}(50% 60 70deg)) }}", color_space), &format!(".foo{{color:{}(40% 50 60)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, 25% {0}(10% 20 30deg), {0}(50% 60 70deg)) }}", color_space), &format!(".foo{{color:{}(40% 50 60)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), 25% {0}(50% 60 70deg)) }}", color_space), &format!(".foo{{color:{}(20% 30 40)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), {0}(50% 60 70deg) 25%) }}", color_space), &format!(".foo{{color:{}(20% 30 40)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 25%, {0}(50% 60 70deg) 75%) }}", color_space), &format!(".foo{{color:{}(40% 50 60)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 30%, {0}(50% 60 70deg) 90%) }}", color_space), &format!(".foo{{color:{}(40% 50 60)}}", color_space)); // Scale down > 100% sum. - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 12.5%, {0}(50% 60 70deg) 37.5%) }}", color_space), &format!(".foo{{color:{}(40% 50 60/.5)}}", color_space)); // Scale up < 100% sum, causes alpha multiplication. - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 0%, {0}(50% 60 70deg)) }}", color_space), &format!(".foo{{color:{}(50% 60 70)}}", color_space)); - - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4), {0}(50% 60 70deg / .8)) }}", color_space), &format!(".foo{{color:{}(36.6667% 46.6667 50/.6)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 25%, {0}(50% 60 70deg / .8)) }}", color_space), &format!(".foo{{color:{}(44.2857% 54.2857 60/.7)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, 25% {0}(10% 20 30deg / .4), {0}(50% 60 70deg / .8)) }}", color_space), &format!(".foo{{color:{}(44.2857% 54.2857 60/.7)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4), 25% {0}(50% 60 70deg / .8)) }}", color_space), &format!(".foo{{color:{}(26% 36 40/.5)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4), {0}(50% 60 70deg / .8) 25%) }}", color_space), &format!(".foo{{color:{}(26% 36 40/.5)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 25%, {0}(50% 60 70deg / .8) 75%) }}", color_space), &format!(".foo{{color:{}(44.2857% 54.2857 60/.7)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 30%, {0}(50% 60 70deg / .8) 90%) }}", color_space), &format!(".foo{{color:{}(44.2857% 54.2857 60/.7)}}", color_space)); // Scale down > 100% sum. - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 12.5%, {0}(50% 60 70deg / .8) 37.5%) }}", color_space), &format!(".foo{{color:{}(44.2857% 54.2857 60/.35)}}", color_space)); // Scale up < 100% sum, causes alpha multiplication. - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 0%, {0}(50% 60 70deg / .8)) }}", color_space), &format!(".foo{{color:{}(50% 60 70/.8)}}", color_space)); - - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 50)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 50)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 10)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 10)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 350)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 350)}}", color_space)); - - minify_test(&format!(".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 50)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 50)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 10)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 10)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 350)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 350)}}", color_space)); - - minify_test(&format!(".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 230)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 230)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 190)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 190)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 170)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 170)}}", color_space)); - - minify_test(&format!(".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 50)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 230)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 190)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 10)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 170)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 350)}}", color_space)); - - minify_test(&format!(".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 230)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 50)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 10)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 190)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 350)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 170)}}", color_space)); - - minify_test(&format!(".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 50)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 50)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 190)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 190)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 170)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}", color_space), &format!(".foo{{color:{}(100% 0 170)}}", color_space)); - - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(none none none), {0}(none none none)) }}", color_space), &format!(".foo{{color:{}(none none none)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(none none none), {0}(50% 60 70deg)) }}", color_space), &format!(".foo{{color:{}(50% 60 70)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), {0}(none none none)) }}", color_space), &format!(".foo{{color:{}(10% 20 30)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 none), {0}(50% 60 70deg)) }}", color_space), &format!(".foo{{color:{}(30% 40 70)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), {0}(50% 60 none)) }}", color_space), &format!(".foo{{color:{}(30% 40 30)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(none 20 30deg), {0}(50% none 70deg)) }}", color_space), &format!(".foo{{color:{}(50% 20 50)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / none), {0}(50% 60 70deg)) }}", color_space), &format!(".foo{{color:{}(30% 40 50)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / none), {0}(50% 60 70deg / 0.5)) }}", color_space), &format!(".foo{{color:{}(30% 40 50/.5)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / none), {0}(50% 60 70deg / none)) }}", color_space), &format!(".foo{{color:{}(30% 40 50/none)}}", color_space)); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), {0}(50% 60 70deg)) }}", + color_space + ), + &format!(".foo{{color:{}(30% 40 50)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 25%, {0}(50% 60 70deg)) }}", + color_space + ), + &format!(".foo{{color:{}(40% 50 60)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, 25% {0}(10% 20 30deg), {0}(50% 60 70deg)) }}", + color_space + ), + &format!(".foo{{color:{}(40% 50 60)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), 25% {0}(50% 60 70deg)) }}", + color_space + ), + &format!(".foo{{color:{}(20% 30 40)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), {0}(50% 60 70deg) 25%) }}", + color_space + ), + &format!(".foo{{color:{}(20% 30 40)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 25%, {0}(50% 60 70deg) 75%) }}", + color_space + ), + &format!(".foo{{color:{}(40% 50 60)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 30%, {0}(50% 60 70deg) 90%) }}", + color_space + ), + &format!(".foo{{color:{}(40% 50 60)}}", color_space), + ); // Scale down > 100% sum. + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 12.5%, {0}(50% 60 70deg) 37.5%) }}", + color_space + ), + &format!(".foo{{color:{}(40% 50 60/.5)}}", color_space), + ); // Scale up < 100% sum, causes alpha multiplication. + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg) 0%, {0}(50% 60 70deg)) }}", + color_space + ), + &format!(".foo{{color:{}(50% 60 70)}}", color_space), + ); + + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4), {0}(50% 60 70deg / .8)) }}", + color_space + ), + &format!(".foo{{color:{}(36.6667% 46.6667 50/.6)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 25%, {0}(50% 60 70deg / .8)) }}", + color_space + ), + &format!(".foo{{color:{}(44.2857% 54.2857 60/.7)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, 25% {0}(10% 20 30deg / .4), {0}(50% 60 70deg / .8)) }}", + color_space + ), + &format!(".foo{{color:{}(44.2857% 54.2857 60/.7)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4), 25% {0}(50% 60 70deg / .8)) }}", + color_space + ), + &format!(".foo{{color:{}(26% 36 40/.5)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4), {0}(50% 60 70deg / .8) 25%) }}", + color_space + ), + &format!(".foo{{color:{}(26% 36 40/.5)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 25%, {0}(50% 60 70deg / .8) 75%) }}", + color_space + ), + &format!(".foo{{color:{}(44.2857% 54.2857 60/.7)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 30%, {0}(50% 60 70deg / .8) 90%) }}", + color_space + ), + &format!(".foo{{color:{}(44.2857% 54.2857 60/.7)}}", color_space), + ); // Scale down > 100% sum. + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 12.5%, {0}(50% 60 70deg / .8) 37.5%) }}", + color_space + ), + &format!(".foo{{color:{}(44.2857% 54.2857 60/.35)}}", color_space), + ); // Scale up < 100% sum, causes alpha multiplication. + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / .4) 0%, {0}(50% 60 70deg / .8)) }}", + color_space + ), + &format!(".foo{{color:{}(50% 60 70/.8)}}", color_space), + ); + + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 50)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 50)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 10)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 10)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 350)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 350)}}", color_space), + ); + + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 50)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 50)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 10)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 10)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 350)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} shorter hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 350)}}", color_space), + ); + + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 230)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 230)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 190)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 190)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 170)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} longer hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 170)}}", color_space), + ); + + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 50)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 230)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 190)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 10)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 170)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} increasing hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 350)}}", color_space), + ); + + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 230)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 50)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 10)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 190)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 350)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} decreasing hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 170)}}", color_space), + ); + + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 40deg), {0}(100% 0 60deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 50)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 60deg), {0}(100% 0 40deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 50)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 50deg), {0}(100% 0 330deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 190)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 330deg), {0}(100% 0 50deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 190)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 20deg), {0}(100% 0 320deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 170)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0} specified hue, {0}(100% 0 320deg), {0}(100% 0 20deg)) }}", + color_space + ), + &format!(".foo{{color:{}(100% 0 170)}}", color_space), + ); + + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(none none none), {0}(none none none)) }}", + color_space + ), + &format!(".foo{{color:{}(none none none)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(none none none), {0}(50% 60 70deg)) }}", + color_space + ), + &format!(".foo{{color:{}(50% 60 70)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), {0}(none none none)) }}", + color_space + ), + &format!(".foo{{color:{}(10% 20 30)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 none), {0}(50% 60 70deg)) }}", + color_space + ), + &format!(".foo{{color:{}(30% 40 70)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg), {0}(50% 60 none)) }}", + color_space + ), + &format!(".foo{{color:{}(30% 40 30)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(none 20 30deg), {0}(50% none 70deg)) }}", + color_space + ), + &format!(".foo{{color:{}(50% 20 50)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / none), {0}(50% 60 70deg)) }}", + color_space + ), + &format!(".foo{{color:{}(30% 40 50)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / none), {0}(50% 60 70deg / 0.5)) }}", + color_space + ), + &format!(".foo{{color:{}(30% 40 50/.5)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30deg / none), {0}(50% 60 70deg / none)) }}", + color_space + ), + &format!(".foo{{color:{}(30% 40 50/none)}}", color_space), + ); } for color_space in ["lab", "oklab"] { - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30), {0}(50% 60 70)) }}", color_space), &format!(".foo{{color:{}(30% 40 50)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 25%, {0}(50% 60 70)) }}", color_space), &format!(".foo{{color:{}(40% 50 60)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, 25% {0}(10% 20 30), {0}(50% 60 70)) }}", color_space), &format!(".foo{{color:{}(40% 50 60)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30), 25% {0}(50% 60 70)) }}", color_space), &format!(".foo{{color:{}(20% 30 40)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30), {0}(50% 60 70) 25%) }}", color_space), &format!(".foo{{color:{}(20% 30 40)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 25%, {0}(50% 60 70) 75%) }}", color_space), &format!(".foo{{color:{}(40% 50 60)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 30%, {0}(50% 60 70) 90%) }}", color_space), &format!(".foo{{color:{}(40% 50 60)}}", color_space)); // Scale down > 100% sum. - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 12.5%, {0}(50% 60 70) 37.5%) }}", color_space), &format!(".foo{{color:{}(40% 50 60/.5)}}", color_space)); // Scale up < 100% sum, causes alpha multiplication. - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 0%, {0}(50% 60 70)) }}", color_space), &format!(".foo{{color:{}(50% 60 70)}}", color_space)); - - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4), {0}(50% 60 70 / .8)) }}", color_space), &format!(".foo{{color:{}(36.6667% 46.6667 56.6667/.6)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 25%, {0}(50% 60 70 / .8)) }}", color_space), &format!(".foo{{color:{}(44.2857% 54.2857 64.2857/.7)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, 25% {0}(10% 20 30 / .4), {0}(50% 60 70 / .8)) }}", color_space), &format!(".foo{{color:{}(44.2857% 54.2857 64.2857/.7)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4), 25% {0}(50% 60 70 / .8)) }}", color_space), &format!(".foo{{color:{}(26% 36 46/.5)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4), {0}(50% 60 70 / .8) 25%) }}", color_space), &format!(".foo{{color:{}(26% 36 46/.5)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 25%, {0}(50% 60 70 / .8) 75%) }}", color_space), &format!(".foo{{color:{}(44.2857% 54.2857 64.2857/.7)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 30%, {0}(50% 60 70 / .8) 90%) }}", color_space), &format!(".foo{{color:{}(44.2857% 54.2857 64.2857/.7)}}", color_space)); // Scale down > 100% sum. - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 12.5%, {0}(50% 60 70 / .8) 37.5%) }}", color_space), &format!(".foo{{color:{}(44.2857% 54.2857 64.2857/.35)}}", color_space)); // Scale up < 100% sum, causes alpha multiplication. - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 0%, {0}(50% 60 70 / .8)) }}", color_space), &format!(".foo{{color:{}(50% 60 70/.8)}}", color_space)); - - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(none none none), {0}(none none none)) }}", color_space), &format!(".foo{{color:{}(none none none)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(none none none), {0}(50% 60 70)) }}", color_space), &format!(".foo{{color:{}(50% 60 70)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30), {0}(none none none)) }}", color_space), &format!(".foo{{color:{}(10% 20 30)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 none), {0}(50% 60 70)) }}", color_space), &format!(".foo{{color:{}(30% 40 70)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30), {0}(50% 60 none)) }}", color_space), &format!(".foo{{color:{}(30% 40 30)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(none 20 30), {0}(50% none 70)) }}", color_space), &format!(".foo{{color:{}(50% 20 50)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / none), {0}(50% 60 70)) }}", color_space), &format!(".foo{{color:{}(30% 40 50)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / none), {0}(50% 60 70 / 0.5)) }}", color_space), &format!(".foo{{color:{}(30% 40 50/.5)}}", color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / none), {0}(50% 60 70 / none)) }}", color_space), &format!(".foo{{color:{}(30% 40 50/none)}}", color_space)); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30), {0}(50% 60 70)) }}", + color_space + ), + &format!(".foo{{color:{}(30% 40 50)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 25%, {0}(50% 60 70)) }}", + color_space + ), + &format!(".foo{{color:{}(40% 50 60)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, 25% {0}(10% 20 30), {0}(50% 60 70)) }}", + color_space + ), + &format!(".foo{{color:{}(40% 50 60)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30), 25% {0}(50% 60 70)) }}", + color_space + ), + &format!(".foo{{color:{}(20% 30 40)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30), {0}(50% 60 70) 25%) }}", + color_space + ), + &format!(".foo{{color:{}(20% 30 40)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 25%, {0}(50% 60 70) 75%) }}", + color_space + ), + &format!(".foo{{color:{}(40% 50 60)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 30%, {0}(50% 60 70) 90%) }}", + color_space + ), + &format!(".foo{{color:{}(40% 50 60)}}", color_space), + ); // Scale down > 100% sum. + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 12.5%, {0}(50% 60 70) 37.5%) }}", + color_space + ), + &format!(".foo{{color:{}(40% 50 60/.5)}}", color_space), + ); // Scale up < 100% sum, causes alpha multiplication. + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30) 0%, {0}(50% 60 70)) }}", + color_space + ), + &format!(".foo{{color:{}(50% 60 70)}}", color_space), + ); + + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4), {0}(50% 60 70 / .8)) }}", + color_space + ), + &format!(".foo{{color:{}(36.6667% 46.6667 56.6667/.6)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 25%, {0}(50% 60 70 / .8)) }}", + color_space + ), + &format!(".foo{{color:{}(44.2857% 54.2857 64.2857/.7)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, 25% {0}(10% 20 30 / .4), {0}(50% 60 70 / .8)) }}", + color_space + ), + &format!(".foo{{color:{}(44.2857% 54.2857 64.2857/.7)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4), 25% {0}(50% 60 70 / .8)) }}", + color_space + ), + &format!(".foo{{color:{}(26% 36 46/.5)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4), {0}(50% 60 70 / .8) 25%) }}", + color_space + ), + &format!(".foo{{color:{}(26% 36 46/.5)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 25%, {0}(50% 60 70 / .8) 75%) }}", + color_space + ), + &format!(".foo{{color:{}(44.2857% 54.2857 64.2857/.7)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 30%, {0}(50% 60 70 / .8) 90%) }}", + color_space + ), + &format!(".foo{{color:{}(44.2857% 54.2857 64.2857/.7)}}", color_space), + ); // Scale down > 100% sum. + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 12.5%, {0}(50% 60 70 / .8) 37.5%) }}", + color_space + ), + &format!(".foo{{color:{}(44.2857% 54.2857 64.2857/.35)}}", color_space), + ); // Scale up < 100% sum, causes alpha multiplication. + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / .4) 0%, {0}(50% 60 70 / .8)) }}", + color_space + ), + &format!(".foo{{color:{}(50% 60 70/.8)}}", color_space), + ); + + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(none none none), {0}(none none none)) }}", + color_space + ), + &format!(".foo{{color:{}(none none none)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(none none none), {0}(50% 60 70)) }}", + color_space + ), + &format!(".foo{{color:{}(50% 60 70)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30), {0}(none none none)) }}", + color_space + ), + &format!(".foo{{color:{}(10% 20 30)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 none), {0}(50% 60 70)) }}", + color_space + ), + &format!(".foo{{color:{}(30% 40 70)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30), {0}(50% 60 none)) }}", + color_space + ), + &format!(".foo{{color:{}(30% 40 30)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(none 20 30), {0}(50% none 70)) }}", + color_space + ), + &format!(".foo{{color:{}(50% 20 50)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / none), {0}(50% 60 70)) }}", + color_space + ), + &format!(".foo{{color:{}(30% 40 50)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / none), {0}(50% 60 70 / 0.5)) }}", + color_space + ), + &format!(".foo{{color:{}(30% 40 50/.5)}}", color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, {0}(10% 20 30 / none), {0}(50% 60 70 / none)) }}", + color_space + ), + &format!(".foo{{color:{}(30% 40 50/none)}}", color_space), + ); } - for color_space in [/*"srgb", */"srgb-linear", "xyz", "xyz-d50", "xyz-d65"] { + for color_space in [/*"srgb", */ "srgb-linear", "xyz", "xyz-d50", "xyz-d65"] { // regex for converting web platform tests: // test_computed_value\(.*?, `color-mix\(in \$\{colorSpace\}(.*?), (.*?)color\(\$\{colorSpace\}(.*?) color\(\$\{colorSpace\}(.*?)`, `color\(\$\{resultColorSpace\}(.*?)`\); // minify_test(&format!(".foo {{ color: color-mix(in {0}$1, $2color({0}$3 color({0}$4 }}", color_space), &format!(".foo{{color:color({}$5}}", result_color_space)); let result_color_space = if color_space == "xyz-d65" { "xyz" } else { color_space }; - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), color({0} .5 .6 .7)) }}", color_space), &format!(".foo{{color:color({} .3 .4 .5)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 25%, color({0} .5 .6 .7)) }}", color_space), &format!(".foo{{color:color({} .4 .5 .6)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, 25% color({0} .1 .2 .3), color({0} .5 .6 .7)) }}", color_space), &format!(".foo{{color:color({} .4 .5 .6)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), color({0} .5 .6 .7) 25%) }}", color_space), &format!(".foo{{color:color({} .2 .3 .4)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), 25% color({0} .5 .6 .7)) }}", color_space), &format!(".foo{{color:color({} .2 .3 .4)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 25%, color({0} .5 .6 .7) 75%) }}", color_space), &format!(".foo{{color:color({} .4 .5 .6)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 30%, color({0} .5 .6 .7) 90%) }}", color_space), &format!(".foo{{color:color({} .4 .5 .6)}}", result_color_space)); // Scale down > 100% sum. - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 12.5%, color({0} .5 .6 .7) 37.5%) }}", color_space), &format!(".foo{{color:color({} .4 .5 .6/.5)}}", result_color_space)); // Scale up < 100% sum, causes alpha multiplication. - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 0%, color({0} .5 .6 .7)) }}", color_space), &format!(".foo{{color:color({} .5 .6 .7)}}", result_color_space)); - - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .5), color({0} .5 .6 .7 / .8)) }}", color_space), &format!(".foo{{color:color({} .346154 .446154 .546154/.65)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 25%, color({0} .5 .6 .7 / .8)) }}", color_space), &format!(".foo{{color:color({} .442857 .542857 .642857/.7)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, 25% color({0} .1 .2 .3 / .4), color({0} .5 .6 .7 / .8)) }}", color_space), &format!(".foo{{color:color({} .442857 .542857 .642857/.7)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4), color({0} .5 .6 .7 / .8) 25%) }}", color_space), &format!(".foo{{color:color({} .26 .36 .46/.5)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4), 25% color({0} .5 .6 .7 / .8)) }}", color_space), &format!(".foo{{color:color({} .26 .36 .46/.5)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 25%, color({0} .5 .6 .7 / .8) 75%) }}", color_space), &format!(".foo{{color:color({} .442857 .542857 .642857/.7)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 30%, color({0} .5 .6 .7 / .8) 90%) }}", color_space), &format!(".foo{{color:color({} .442857 .542857 .642857/.7)}}", result_color_space)); // Scale down > 100% sum. - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 12.5%, color({0} .5 .6 .7 / .8) 37.5%) }}", color_space), &format!(".foo{{color:color({} .442857 .542857 .642857/.35)}}", result_color_space)); // Scale up < 100% sum, causes alpha multiplication. - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 0%, color({0} .5 .6 .7 / .8)) }}", color_space), &format!(".foo{{color:color({} .5 .6 .7/.8)}}", result_color_space)); - - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} 2 3 4 / 5), color({0} 4 6 8 / 10)) }}", color_space), &format!(".foo{{color:color({} 3 4.5 6)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} -2 -3 -4), color({0} -4 -6 -8)) }}", color_space), &format!(".foo{{color:color({} -3 -4.5 -6)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} -2 -3 -4 / -5), color({0} -4 -6 -8 / -10)) }}", color_space), &format!(".foo{{color:color({}/0)}}", result_color_space)); - - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} none none none), color({0} none none none)) }}", color_space), &format!(".foo{{color:color({} none none none)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} none none none), color({0} .5 .6 .7)) }}", color_space), &format!(".foo{{color:color({} .5 .6 .7)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), color({0} none none none)) }}", color_space), &format!(".foo{{color:color({} .1 .2 .3)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 none), color({0} .5 .6 .7)) }}", color_space), &format!(".foo{{color:color({} .3 .4 .7)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), color({0} .5 .6 none)) }}", color_space), &format!(".foo{{color:color({} .3 .4 .3)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} none .2 .3), color({0} .5 none .7)) }}", color_space), &format!(".foo{{color:color({} .5 .2 .5)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / none), color({0} .5 .6 .7)) }}", color_space), &format!(".foo{{color:color({} .3 .4 .5)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / none), color({0} .5 .6 .7 / 0.5)) }}", color_space), &format!(".foo{{color:color({} .3 .4 .5/.5)}}", result_color_space)); - minify_test(&format!(".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / none), color({0} .5 .6 .7 / none)) }}", color_space), &format!(".foo{{color:color({} .3 .4 .5/none)}}", result_color_space)); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), color({0} .5 .6 .7)) }}", + color_space + ), + &format!(".foo{{color:color({} .3 .4 .5)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 25%, color({0} .5 .6 .7)) }}", + color_space + ), + &format!(".foo{{color:color({} .4 .5 .6)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, 25% color({0} .1 .2 .3), color({0} .5 .6 .7)) }}", + color_space + ), + &format!(".foo{{color:color({} .4 .5 .6)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), color({0} .5 .6 .7) 25%) }}", + color_space + ), + &format!(".foo{{color:color({} .2 .3 .4)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), 25% color({0} .5 .6 .7)) }}", + color_space + ), + &format!(".foo{{color:color({} .2 .3 .4)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 25%, color({0} .5 .6 .7) 75%) }}", + color_space + ), + &format!(".foo{{color:color({} .4 .5 .6)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 30%, color({0} .5 .6 .7) 90%) }}", + color_space + ), + &format!(".foo{{color:color({} .4 .5 .6)}}", result_color_space), + ); // Scale down > 100% sum. + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 12.5%, color({0} .5 .6 .7) 37.5%) }}", + color_space + ), + &format!(".foo{{color:color({} .4 .5 .6/.5)}}", result_color_space), + ); // Scale up < 100% sum, causes alpha multiplication. + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3) 0%, color({0} .5 .6 .7)) }}", + color_space + ), + &format!(".foo{{color:color({} .5 .6 .7)}}", result_color_space), + ); + + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .5), color({0} .5 .6 .7 / .8)) }}", + color_space + ), + &format!( + ".foo{{color:color({} .346154 .446154 .546154/.65)}}", + result_color_space + ), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 25%, color({0} .5 .6 .7 / .8)) }}", + color_space + ), + &format!(".foo{{color:color({} .442857 .542857 .642857/.7)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, 25% color({0} .1 .2 .3 / .4), color({0} .5 .6 .7 / .8)) }}", + color_space + ), + &format!(".foo{{color:color({} .442857 .542857 .642857/.7)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4), color({0} .5 .6 .7 / .8) 25%) }}", + color_space + ), + &format!(".foo{{color:color({} .26 .36 .46/.5)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4), 25% color({0} .5 .6 .7 / .8)) }}", + color_space + ), + &format!(".foo{{color:color({} .26 .36 .46/.5)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 25%, color({0} .5 .6 .7 / .8) 75%) }}", + color_space + ), + &format!(".foo{{color:color({} .442857 .542857 .642857/.7)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 30%, color({0} .5 .6 .7 / .8) 90%) }}", + color_space + ), + &format!(".foo{{color:color({} .442857 .542857 .642857/.7)}}", result_color_space), + ); // Scale down > 100% sum. + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 12.5%, color({0} .5 .6 .7 / .8) 37.5%) }}", + color_space + ), + &format!( + ".foo{{color:color({} .442857 .542857 .642857/.35)}}", + result_color_space + ), + ); // Scale up < 100% sum, causes alpha multiplication. + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / .4) 0%, color({0} .5 .6 .7 / .8)) }}", + color_space + ), + &format!(".foo{{color:color({} .5 .6 .7/.8)}}", result_color_space), + ); + + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} 2 3 4 / 5), color({0} 4 6 8 / 10)) }}", + color_space + ), + &format!(".foo{{color:color({} 3 4.5 6)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} -2 -3 -4), color({0} -4 -6 -8)) }}", + color_space + ), + &format!(".foo{{color:color({} -3 -4.5 -6)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} -2 -3 -4 / -5), color({0} -4 -6 -8 / -10)) }}", + color_space + ), + &format!(".foo{{color:color({}/0)}}", result_color_space), + ); + + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} none none none), color({0} none none none)) }}", + color_space + ), + &format!(".foo{{color:color({} none none none)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} none none none), color({0} .5 .6 .7)) }}", + color_space + ), + &format!(".foo{{color:color({} .5 .6 .7)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), color({0} none none none)) }}", + color_space + ), + &format!(".foo{{color:color({} .1 .2 .3)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 none), color({0} .5 .6 .7)) }}", + color_space + ), + &format!(".foo{{color:color({} .3 .4 .7)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3), color({0} .5 .6 none)) }}", + color_space + ), + &format!(".foo{{color:color({} .3 .4 .3)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} none .2 .3), color({0} .5 none .7)) }}", + color_space + ), + &format!(".foo{{color:color({} .5 .2 .5)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / none), color({0} .5 .6 .7)) }}", + color_space + ), + &format!(".foo{{color:color({} .3 .4 .5)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / none), color({0} .5 .6 .7 / 0.5)) }}", + color_space + ), + &format!(".foo{{color:color({} .3 .4 .5/.5)}}", result_color_space), + ); + minify_test( + &format!( + ".foo {{ color: color-mix(in {0}, color({0} .1 .2 .3 / none), color({0} .5 .6 .7 / none)) }}", + color_space + ), + &format!(".foo{{color:color({} .3 .4 .5/none)}}", result_color_space), + ); } } #[cfg(feature = "grid")] #[test] fn test_grid() { - minify_test(".foo { grid-template-columns: [first nav-start] 150px [main-start] 1fr [last]; }", ".foo{grid-template-columns:[first nav-start]150px[main-start]1fr[last]}"); - minify_test(".foo { grid-template-columns: 150px 1fr; }", ".foo{grid-template-columns:150px 1fr}"); - minify_test(".foo { grid-template-columns: repeat(4, 1fr); }", ".foo{grid-template-columns:repeat(4,1fr)}"); - minify_test(".foo { grid-template-columns: repeat(2, [e] 40px); }", ".foo{grid-template-columns:repeat(2,[e]40px)}"); - minify_test(".foo { grid-template-columns: repeat(4, [col-start] 250px [col-end]); }", ".foo{grid-template-columns:repeat(4,[col-start]250px[col-end])}"); - minify_test(".foo { grid-template-columns: repeat(4, [col-start] 60% [col-end]); }", ".foo{grid-template-columns:repeat(4,[col-start]60%[col-end])}"); - minify_test(".foo { grid-template-columns: repeat(4, [col-start] 1fr [col-end]); }", ".foo{grid-template-columns:repeat(4,[col-start]1fr[col-end])}"); - minify_test(".foo { grid-template-columns: repeat(4, [col-start] min-content [col-end]); }", ".foo{grid-template-columns:repeat(4,[col-start]min-content[col-end])}"); - minify_test(".foo { grid-template-columns: repeat(4, [col-start] max-content [col-end]); }", ".foo{grid-template-columns:repeat(4,[col-start]max-content[col-end])}"); - minify_test(".foo { grid-template-columns: repeat(4, [col-start] auto [col-end]); }", ".foo{grid-template-columns:repeat(4,[col-start]auto[col-end])}"); - minify_test(".foo { grid-template-columns: repeat(4, [col-start] minmax(100px, 1fr) [col-end]); }", ".foo{grid-template-columns:repeat(4,[col-start]minmax(100px,1fr)[col-end])}"); - minify_test(".foo { grid-template-columns: repeat(4, [col-start] fit-content(200px) [col-end]); }", ".foo{grid-template-columns:repeat(4,[col-start]fit-content(200px)[col-end])}"); - minify_test(".foo { grid-template-columns: repeat(4, 10px [col-start] 30% [col-middle] auto [col-end]); }", ".foo{grid-template-columns:repeat(4,10px[col-start]30%[col-middle]auto[col-end])}"); - minify_test(".foo { grid-template-columns: repeat(5, auto); }", ".foo{grid-template-columns:repeat(5,auto)}"); - minify_test(".foo { grid-template-columns: repeat(auto-fill, 250px); }", ".foo{grid-template-columns:repeat(auto-fill,250px)}"); - minify_test(".foo { grid-template-columns: repeat(auto-fit, 250px); }", ".foo{grid-template-columns:repeat(auto-fit,250px)}"); - minify_test(".foo { grid-template-columns: repeat(auto-fill, [col-start] 250px [col-end]); }", ".foo{grid-template-columns:repeat(auto-fill,[col-start]250px[col-end])}"); - minify_test(".foo { grid-template-columns: repeat(auto-fill, [col-start] minmax(100px, 1fr) [col-end]); }", ".foo{grid-template-columns:repeat(auto-fill,[col-start]minmax(100px,1fr)[col-end])}"); - minify_test(".foo { grid-template-columns: minmax(min-content, 1fr); }", ".foo{grid-template-columns:minmax(min-content,1fr)}"); - minify_test(".foo { grid-template-columns: 200px repeat(auto-fill, 100px) 300px; }", ".foo{grid-template-columns:200px repeat(auto-fill,100px) 300px}"); + minify_test( + ".foo { grid-template-columns: [first nav-start] 150px [main-start] 1fr [last]; }", + ".foo{grid-template-columns:[first nav-start]150px[main-start]1fr[last]}", + ); + minify_test( + ".foo { grid-template-columns: 150px 1fr; }", + ".foo{grid-template-columns:150px 1fr}", + ); + minify_test( + ".foo { grid-template-columns: repeat(4, 1fr); }", + ".foo{grid-template-columns:repeat(4,1fr)}", + ); + minify_test( + ".foo { grid-template-columns: repeat(2, [e] 40px); }", + ".foo{grid-template-columns:repeat(2,[e]40px)}", + ); + minify_test( + ".foo { grid-template-columns: repeat(4, [col-start] 250px [col-end]); }", + ".foo{grid-template-columns:repeat(4,[col-start]250px[col-end])}", + ); + minify_test( + ".foo { grid-template-columns: repeat(4, [col-start] 60% [col-end]); }", + ".foo{grid-template-columns:repeat(4,[col-start]60%[col-end])}", + ); + minify_test( + ".foo { grid-template-columns: repeat(4, [col-start] 1fr [col-end]); }", + ".foo{grid-template-columns:repeat(4,[col-start]1fr[col-end])}", + ); + minify_test( + ".foo { grid-template-columns: repeat(4, [col-start] min-content [col-end]); }", + ".foo{grid-template-columns:repeat(4,[col-start]min-content[col-end])}", + ); + minify_test( + ".foo { grid-template-columns: repeat(4, [col-start] max-content [col-end]); }", + ".foo{grid-template-columns:repeat(4,[col-start]max-content[col-end])}", + ); + minify_test( + ".foo { grid-template-columns: repeat(4, [col-start] auto [col-end]); }", + ".foo{grid-template-columns:repeat(4,[col-start]auto[col-end])}", + ); + minify_test( + ".foo { grid-template-columns: repeat(4, [col-start] minmax(100px, 1fr) [col-end]); }", + ".foo{grid-template-columns:repeat(4,[col-start]minmax(100px,1fr)[col-end])}", + ); + minify_test( + ".foo { grid-template-columns: repeat(4, [col-start] fit-content(200px) [col-end]); }", + ".foo{grid-template-columns:repeat(4,[col-start]fit-content(200px)[col-end])}", + ); + minify_test( + ".foo { grid-template-columns: repeat(4, 10px [col-start] 30% [col-middle] auto [col-end]); }", + ".foo{grid-template-columns:repeat(4,10px[col-start]30%[col-middle]auto[col-end])}", + ); + minify_test( + ".foo { grid-template-columns: repeat(5, auto); }", + ".foo{grid-template-columns:repeat(5,auto)}", + ); + minify_test( + ".foo { grid-template-columns: repeat(auto-fill, 250px); }", + ".foo{grid-template-columns:repeat(auto-fill,250px)}", + ); + minify_test( + ".foo { grid-template-columns: repeat(auto-fit, 250px); }", + ".foo{grid-template-columns:repeat(auto-fit,250px)}", + ); + minify_test( + ".foo { grid-template-columns: repeat(auto-fill, [col-start] 250px [col-end]); }", + ".foo{grid-template-columns:repeat(auto-fill,[col-start]250px[col-end])}", + ); + minify_test( + ".foo { grid-template-columns: repeat(auto-fill, [col-start] minmax(100px, 1fr) [col-end]); }", + ".foo{grid-template-columns:repeat(auto-fill,[col-start]minmax(100px,1fr)[col-end])}", + ); + minify_test( + ".foo { grid-template-columns: minmax(min-content, 1fr); }", + ".foo{grid-template-columns:minmax(min-content,1fr)}", + ); + minify_test( + ".foo { grid-template-columns: 200px repeat(auto-fill, 100px) 300px; }", + ".foo{grid-template-columns:200px repeat(auto-fill,100px) 300px}", + ); minify_test(".foo { grid-template-columns: [linename1 linename2] 100px repeat(auto-fit, [linename1] 300px) [linename3]; }", ".foo{grid-template-columns:[linename1 linename2]100px repeat(auto-fit,[linename1]300px)[linename3]}"); - minify_test(".foo { grid-template-rows: [linename1 linename2] 100px repeat(auto-fit, [linename1] 300px) [linename3]; }", ".foo{grid-template-rows:[linename1 linename2]100px repeat(auto-fit,[linename1]300px)[linename3]}"); + minify_test( + ".foo { grid-template-rows: [linename1 linename2] 100px repeat(auto-fit, [linename1] 300px) [linename3]; }", + ".foo{grid-template-rows:[linename1 linename2]100px repeat(auto-fit,[linename1]300px)[linename3]}", + ); minify_test(".foo { grid-auto-rows: auto; }", ".foo{grid-auto-rows:auto}"); minify_test(".foo { grid-auto-rows: 1fr; }", ".foo{grid-auto-rows:1fr}"); minify_test(".foo { grid-auto-rows: 100px; }", ".foo{grid-auto-rows:100px}"); - minify_test(".foo { grid-auto-rows: min-content; }", ".foo{grid-auto-rows:min-content}"); - minify_test(".foo { grid-auto-rows: max-content; }", ".foo{grid-auto-rows:max-content}"); - minify_test(".foo { grid-auto-rows: minmax(100px,auto); }", ".foo{grid-auto-rows:minmax(100px,auto)}"); - minify_test(".foo { grid-auto-rows: fit-content(20%); }", ".foo{grid-auto-rows:fit-content(20%)}"); - minify_test(".foo { grid-auto-rows: 100px minmax(100px, auto) 10% 0.5fr fit-content(400px); }", ".foo{grid-auto-rows:100px minmax(100px,auto) 10% .5fr fit-content(400px)}"); - minify_test(".foo { grid-auto-columns: 100px minmax(100px, auto) 10% 0.5fr fit-content(400px); }", ".foo{grid-auto-columns:100px minmax(100px,auto) 10% .5fr fit-content(400px)}"); - - minify_test(r#" + minify_test( + ".foo { grid-auto-rows: min-content; }", + ".foo{grid-auto-rows:min-content}", + ); + minify_test( + ".foo { grid-auto-rows: max-content; }", + ".foo{grid-auto-rows:max-content}", + ); + minify_test( + ".foo { grid-auto-rows: minmax(100px,auto); }", + ".foo{grid-auto-rows:minmax(100px,auto)}", + ); + minify_test( + ".foo { grid-auto-rows: fit-content(20%); }", + ".foo{grid-auto-rows:fit-content(20%)}", + ); + minify_test( + ".foo { grid-auto-rows: 100px minmax(100px, auto) 10% 0.5fr fit-content(400px); }", + ".foo{grid-auto-rows:100px minmax(100px,auto) 10% .5fr fit-content(400px)}", + ); + minify_test( + ".foo { grid-auto-columns: 100px minmax(100px, auto) 10% 0.5fr fit-content(400px); }", + ".foo{grid-auto-columns:100px minmax(100px,auto) 10% .5fr fit-content(400px)}", + ); + + minify_test( + r#" .foo { grid-template-areas: "head head" "nav main" "foot ...."; } - "#, ".foo{grid-template-areas:\"head head\"\"nav main\"\"foot.\"}"); - minify_test(r#" + "#, + ".foo{grid-template-areas:\"head head\"\"nav main\"\"foot.\"}", + ); + minify_test( + r#" .foo { grid-template-areas: "head head" "nav main" ".... foot"; } - "#, ".foo{grid-template-areas:\"head head\"\"nav main\"\".foot\"}"); - minify_test(r#" + "#, + ".foo{grid-template-areas:\"head head\"\"nav main\"\".foot\"}", + ); + minify_test( + r#" .foo { grid-template-areas: "head head" "nav main" ".... ...."; } - "#, ".foo{grid-template-areas:\"head head\"\"nav main\"\". .\"}"); + "#, + ".foo{grid-template-areas:\"head head\"\"nav main\"\". .\"}", + ); - test(r#" + test( + r#" .foo { grid-template-areas: "head head" "nav main" "foot ...."; } - "#, indoc! { r#" + "#, + indoc! { r#" .foo { grid-template-areas: "head head" "nav main" "foot ."; } - "#}); + "#}, + ); - minify_test(r#" + minify_test( + r#" .foo { grid-template: [header-top] "a a a" [header-bottom] [main-top] "b b b" 1fr [main-bottom]; } - "#, ".foo{grid-template:[header-top]\"a a a\"[header-bottom main-top]\"b b b\"1fr[main-bottom]}"); - minify_test(r#" + "#, + ".foo{grid-template:[header-top]\"a a a\"[header-bottom main-top]\"b b b\"1fr[main-bottom]}", + ); + minify_test( + r#" .foo { grid-template: "head head" "nav main" 1fr "foot ...."; } - "#, ".foo{grid-template:\"head head\"\"nav main\"1fr\"foot.\"}"); - minify_test(r#" + "#, + ".foo{grid-template:\"head head\"\"nav main\"1fr\"foot.\"}", + ); + minify_test( + r#" .foo { grid-template: [header-top] "a a a" [header-bottom] [main-top] "b b b" 1fr [main-bottom] / auto 1fr auto; } - "#, ".foo{grid-template:[header-top]\"a a a\"[header-bottom main-top]\"b b b\"1fr[main-bottom]/auto 1fr auto}"); + "#, + ".foo{grid-template:[header-top]\"a a a\"[header-bottom main-top]\"b b b\"1fr[main-bottom]/auto 1fr auto}", + ); - minify_test(".foo { grid-template: auto 1fr / auto 1fr auto; }", ".foo{grid-template:auto 1fr/auto 1fr auto}"); + minify_test( + ".foo { grid-template: auto 1fr / auto 1fr auto; }", + ".foo{grid-template:auto 1fr/auto 1fr auto}", + ); minify_test( ".foo { grid-template: [linename1 linename2] 100px repeat(auto-fit, [linename1] 300px) [linename3] / [linename1 linename2] 100px repeat(auto-fit, [linename1] 300px) [linename3]; }", ".foo{grid-template:[linename1 linename2]100px repeat(auto-fit,[linename1]300px)[linename3]/[linename1 linename2]100px repeat(auto-fit,[linename1]300px)[linename3]}" @@ -9637,68 +13086,128 @@ mod tests { test( ".foo{grid-template:[header-top]\"a a a\"[header-bottom main-top]\"b b b\"1fr[main-bottom]/auto 1fr auto}", - indoc!{r#" + indoc! {r#" .foo { grid-template: [header-top] "a a a" [header-bottom] [main-top] "b b b" 1fr [main-bottom] / auto 1fr auto; } - "#} + "#}, ); test( ".foo{grid-template:[header-top]\"a a a\"[main-top]\"b b b\"1fr/auto 1fr auto}", - indoc!{r#" + indoc! {r#" .foo { grid-template: [header-top] "a a a" [main-top] "b b b" 1fr / auto 1fr auto; } - "#} + "#}, ); minify_test(".foo { grid-auto-flow: row }", ".foo{grid-auto-flow:row}"); minify_test(".foo { grid-auto-flow: column }", ".foo{grid-auto-flow:column}"); minify_test(".foo { grid-auto-flow: row dense }", ".foo{grid-auto-flow:dense}"); minify_test(".foo { grid-auto-flow: dense row }", ".foo{grid-auto-flow:dense}"); - minify_test(".foo { grid-auto-flow: column dense }", ".foo{grid-auto-flow:column dense}"); - minify_test(".foo { grid-auto-flow: dense column }", ".foo{grid-auto-flow:column dense}"); + minify_test( + ".foo { grid-auto-flow: column dense }", + ".foo{grid-auto-flow:column dense}", + ); + minify_test( + ".foo { grid-auto-flow: dense column }", + ".foo{grid-auto-flow:column dense}", + ); minify_test(".foo { grid: none }", ".foo{grid:none}"); minify_test(".foo { grid: \"a\" 100px \"b\" 1fr }", ".foo{grid:\"a\"100px\"b\"1fr}"); - minify_test(".foo { grid: [linename1] \"a\" 100px [linename2] }", ".foo{grid:[linename1]\"a\"100px[linename2]}"); - minify_test(".foo { grid: \"a\" 200px \"b\" min-content }", ".foo{grid:\"a\"200px\"b\"min-content}"); - minify_test(".foo { grid: \"a\" minmax(100px, max-content) \"b\" 20% }", ".foo{grid:\"a\"minmax(100px,max-content)\"b\"20%}"); + minify_test( + ".foo { grid: [linename1] \"a\" 100px [linename2] }", + ".foo{grid:[linename1]\"a\"100px[linename2]}", + ); + minify_test( + ".foo { grid: \"a\" 200px \"b\" min-content }", + ".foo{grid:\"a\"200px\"b\"min-content}", + ); + minify_test( + ".foo { grid: \"a\" minmax(100px, max-content) \"b\" 20% }", + ".foo{grid:\"a\"minmax(100px,max-content)\"b\"20%}", + ); minify_test(".foo { grid: 100px / 200px }", ".foo{grid:100px/200px}"); - minify_test(".foo { grid: minmax(400px, min-content) / repeat(auto-fill, 50px) }", ".foo{grid:minmax(400px,min-content)/repeat(auto-fill,50px)}"); - + minify_test( + ".foo { grid: minmax(400px, min-content) / repeat(auto-fill, 50px) }", + ".foo{grid:minmax(400px,min-content)/repeat(auto-fill,50px)}", + ); + minify_test(".foo { grid: 200px / auto-flow }", ".foo{grid:200px/auto-flow}"); minify_test(".foo { grid: 30% / auto-flow dense }", ".foo{grid:30%/auto-flow dense}"); minify_test(".foo { grid: 30% / dense auto-flow }", ".foo{grid:30%/auto-flow dense}"); - minify_test(".foo { grid: repeat(3, [line1 line2 line3] 200px) / auto-flow 300px }", ".foo{grid:repeat(3,[line1 line2 line3]200px)/auto-flow 300px}"); - minify_test(".foo { grid: [line1] minmax(20em, max-content) / auto-flow dense 40% }", ".foo{grid:[line1]minmax(20em,max-content)/auto-flow dense 40%}"); + minify_test( + ".foo { grid: repeat(3, [line1 line2 line3] 200px) / auto-flow 300px }", + ".foo{grid:repeat(3,[line1 line2 line3]200px)/auto-flow 300px}", + ); + minify_test( + ".foo { grid: [line1] minmax(20em, max-content) / auto-flow dense 40% }", + ".foo{grid:[line1]minmax(20em,max-content)/auto-flow dense 40%}", + ); minify_test(".foo { grid: none / auto-flow 1fr }", ".foo{grid:none/auto-flow 1fr}"); minify_test(".foo { grid: auto-flow / 200px }", ".foo{grid:none/200px}"); minify_test(".foo { grid: auto-flow dense / 30% }", ".foo{grid:auto-flow dense/30%}"); minify_test(".foo { grid: dense auto-flow / 30% }", ".foo{grid:auto-flow dense/30%}"); - minify_test(".foo { grid: auto-flow 300px / repeat(3, [line1 line2 line3] 200px) }", ".foo{grid:auto-flow 300px/repeat(3,[line1 line2 line3]200px)}"); - minify_test(".foo { grid: auto-flow dense 40% / [line1] minmax(20em, max-content) }", ".foo{grid:auto-flow dense 40%/[line1]minmax(20em,max-content)}"); + minify_test( + ".foo { grid: auto-flow 300px / repeat(3, [line1 line2 line3] 200px) }", + ".foo{grid:auto-flow 300px/repeat(3,[line1 line2 line3]200px)}", + ); + minify_test( + ".foo { grid: auto-flow dense 40% / [line1] minmax(20em, max-content) }", + ".foo{grid:auto-flow dense 40%/[line1]minmax(20em,max-content)}", + ); minify_test(".foo { grid-row-start: auto }", ".foo{grid-row-start:auto}"); minify_test(".foo { grid-row-start: some-area }", ".foo{grid-row-start:some-area}"); minify_test(".foo { grid-row-start: 2 }", ".foo{grid-row-start:2}"); - minify_test(".foo { grid-row-start: 2 some-line }", ".foo{grid-row-start:2 some-line}"); - minify_test(".foo { grid-row-start: some-line 2 }", ".foo{grid-row-start:2 some-line}"); + minify_test( + ".foo { grid-row-start: 2 some-line }", + ".foo{grid-row-start:2 some-line}", + ); + minify_test( + ".foo { grid-row-start: some-line 2 }", + ".foo{grid-row-start:2 some-line}", + ); minify_test(".foo { grid-row-start: span 3 }", ".foo{grid-row-start:span 3}"); - minify_test(".foo { grid-row-start: span some-line }", ".foo{grid-row-start:span some-line}"); - minify_test(".foo { grid-row-start: span some-line 1 }", ".foo{grid-row-start:span some-line}"); - minify_test(".foo { grid-row-start: span 1 some-line }", ".foo{grid-row-start:span some-line}"); - minify_test(".foo { grid-row-start: span 5 some-line }", ".foo{grid-row-start:span 5 some-line}"); - minify_test(".foo { grid-row-start: span some-line 5 }", ".foo{grid-row-start:span 5 some-line}"); + minify_test( + ".foo { grid-row-start: span some-line }", + ".foo{grid-row-start:span some-line}", + ); + minify_test( + ".foo { grid-row-start: span some-line 1 }", + ".foo{grid-row-start:span some-line}", + ); + minify_test( + ".foo { grid-row-start: span 1 some-line }", + ".foo{grid-row-start:span some-line}", + ); + minify_test( + ".foo { grid-row-start: span 5 some-line }", + ".foo{grid-row-start:span 5 some-line}", + ); + minify_test( + ".foo { grid-row-start: span some-line 5 }", + ".foo{grid-row-start:span 5 some-line}", + ); - minify_test(".foo { grid-row-end: span 1 some-line }", ".foo{grid-row-end:span some-line}"); - minify_test(".foo { grid-column-start: span 1 some-line }", ".foo{grid-column-start:span some-line}"); - minify_test(".foo { grid-column-end: span 1 some-line }", ".foo{grid-column-end:span some-line}"); + minify_test( + ".foo { grid-row-end: span 1 some-line }", + ".foo{grid-row-end:span some-line}", + ); + minify_test( + ".foo { grid-column-start: span 1 some-line }", + ".foo{grid-column-start:span some-line}", + ); + minify_test( + ".foo { grid-column-end: span 1 some-line }", + ".foo{grid-column-end:span some-line}", + ); minify_test(".foo { grid-row: 1 }", ".foo{grid-row:1}"); minify_test(".foo { grid-row: 1 / auto }", ".foo{grid-row:1}"); @@ -9706,8 +13215,14 @@ mod tests { minify_test(".foo { grid-row: 1 / 3 }", ".foo{grid-row:1/3}"); minify_test(".foo { grid-row: 1 / span 2 }", ".foo{grid-row:1/span 2}"); minify_test(".foo { grid-row: main-start }", ".foo{grid-row:main-start}"); - minify_test(".foo { grid-row: main-start / main-end }", ".foo{grid-row:main-start/main-end}"); - minify_test(".foo { grid-row: main-start / main-start }", ".foo{grid-row:main-start}"); + minify_test( + ".foo { grid-row: main-start / main-end }", + ".foo{grid-row:main-start/main-end}", + ); + minify_test( + ".foo { grid-row: main-start / main-start }", + ".foo{grid-row:main-start}", + ); minify_test(".foo { grid-column: 1 / auto }", ".foo{grid-column:1}"); minify_test(".foo { grid-area: a }", ".foo{grid-area:a}"); @@ -9729,11 +13244,11 @@ mod tests { grid-template-areas: none; } "#, - indoc!{r#" + indoc! {r#" .foo { grid-template: auto 1fr / auto 1fr auto; } - "#} + "#}, ); test( @@ -9745,13 +13260,13 @@ mod tests { grid-template-columns: auto 1fr auto; } "#, - indoc!{r#" + indoc! {r#" .foo { grid-template: [header-top] "a a a" [header-bottom] [main-top] "b b b" 1fr [main-bottom] / auto 1fr auto; } - "#} + "#}, ); test( @@ -9763,14 +13278,14 @@ mod tests { grid-template-rows: auto 1fr; } "#, - indoc!{r#" + indoc! {r#" .foo { grid-template-rows: auto 1fr; grid-template-columns: repeat(3, 1fr); grid-template-areas: "a a a" "b b b"; } - "#} + "#}, ); test( @@ -9782,14 +13297,14 @@ mod tests { grid-template-rows: repeat(2, 1fr); } "#, - indoc!{r#" + indoc! {r#" .foo { grid-template-rows: repeat(2, 1fr); grid-template-columns: auto 1fr auto; grid-template-areas: "a a a" "b b b"; } - "#} + "#}, ); test( @@ -9801,13 +13316,13 @@ mod tests { grid-template-columns: 10px 1fr 1fr 10px; } "#, - indoc!{r#" + indoc! {r#" .foo { grid-template: ". a a ." ". b b ." 1fr / 10px 1fr 1fr 10px; } - "#} + "#}, ); test( @@ -9818,11 +13333,11 @@ mod tests { grid-template-rows: repeat(2, 1fr); } "#, - indoc!{r#" + indoc! {r#" .foo { grid-template: repeat(2, 1fr) / auto 1fr auto; } - "#} + "#}, ); test( @@ -9833,11 +13348,11 @@ mod tests { grid-template-rows: none; } "#, - indoc!{r#" + indoc! {r#" .foo { grid-template: none; } - "#} + "#}, ); test( @@ -9852,13 +13367,13 @@ mod tests { grid-auto-columns: auto; } "#, - indoc!{r#" + indoc! {r#" .foo { grid: [header-top] "a a a" [header-bottom] [main-top] "b b b" 1fr [main-bottom] / auto 1fr auto; } - "#} + "#}, ); test( @@ -9872,11 +13387,11 @@ mod tests { grid-auto-columns: auto; } "#, - indoc!{r#" + indoc! {r#" .foo { grid: repeat(2, 1fr) / auto 1fr auto; } - "#} + "#}, ); test( @@ -9890,11 +13405,11 @@ mod tests { grid-auto-columns: auto; } "#, - indoc!{r#" + indoc! {r#" .foo { grid: none; } - "#} + "#}, ); test( @@ -9909,7 +13424,7 @@ mod tests { grid-auto-columns: 1fr; } "#, - indoc!{r#" + indoc! {r#" .foo { grid-template: [header-top] "a a a" [header-bottom] [main-top] "b b b" 1fr [main-bottom] @@ -9918,7 +13433,7 @@ mod tests { grid-auto-columns: 1fr; grid-auto-flow: column; } - "#} + "#}, ); test( @@ -9932,11 +13447,11 @@ mod tests { grid-auto-columns: auto; } "#, - indoc!{r#" + indoc! {r#" .foo { grid: auto 1fr / auto 1fr auto; } - "#} + "#}, ); test( @@ -9950,14 +13465,14 @@ mod tests { grid-auto-columns: 1fr; } "#, - indoc!{r#" + indoc! {r#" .foo { grid-template: auto 1fr / auto 1fr auto; grid-auto-rows: 1fr; grid-auto-columns: 1fr; grid-auto-flow: column; } - "#} + "#}, ); test( @@ -9971,14 +13486,14 @@ mod tests { grid-auto-columns: 1fr; } "#, - indoc!{r#" + indoc! {r#" .foo { grid-template: none / auto 1fr auto; grid-auto-rows: 1fr; grid-auto-columns: 1fr; grid-auto-flow: column; } - "#} + "#}, ); test( @@ -9992,11 +13507,11 @@ mod tests { grid-auto-columns: auto; } "#, - indoc!{r#" + indoc! {r#" .foo { grid: auto-flow 1fr / auto 1fr auto; } - "#} + "#}, ); test( @@ -10010,11 +13525,11 @@ mod tests { grid-auto-columns: auto; } "#, - indoc!{r#" + indoc! {r#" .foo { grid: auto-flow dense 1fr / auto 1fr auto; } - "#} + "#}, ); test( @@ -10028,11 +13543,11 @@ mod tests { grid-auto-columns: 1fr; } "#, - indoc!{r#" + indoc! {r#" .foo { grid: auto 1fr auto / auto-flow 1fr; } - "#} + "#}, ); test( @@ -10046,11 +13561,11 @@ mod tests { grid-auto-columns: 1fr; } "#, - indoc!{r#" + indoc! {r#" .foo { grid: auto 1fr auto / auto-flow dense 1fr; } - "#} + "#}, ); test( @@ -10064,14 +13579,14 @@ mod tests { grid-auto-columns: 1fr; } "#, - indoc!{r#" + indoc! {r#" .foo { grid-template: auto 1fr auto / none; grid-auto-flow: var(--auto-flow); grid-auto-rows: auto; grid-auto-columns: 1fr; } - "#} + "#}, ); test( @@ -10081,11 +13596,11 @@ mod tests { grid-template-rows: 1fr 1fr 1fr; } "#, - indoc!{r#" + indoc! {r#" .foo { grid: 1fr 1fr 1fr / auto-flow dense 1fr; } - "#} + "#}, ); test( @@ -10097,11 +13612,11 @@ mod tests { grid-column-end: a; } "#, - indoc!{r#" + indoc! {r#" .foo { grid-area: a; } - "#} + "#}, ); test( @@ -10113,11 +13628,11 @@ mod tests { grid-column-end: 4; } "#, - indoc!{r#" + indoc! {r#" .foo { grid-area: 1 / 3 / 2 / 4; } - "#} + "#}, ); test( @@ -10127,11 +13642,11 @@ mod tests { grid-row-end: a; } "#, - indoc!{r#" + indoc! {r#" .foo { grid-row: a; } - "#} + "#}, ); test( @@ -10141,32 +13656,44 @@ mod tests { grid-column-end: a; } "#, - indoc!{r#" + indoc! {r#" .foo { grid-column: a; } - "#} + "#}, ); } #[test] fn test_moz_document() { - minify_test(r#" + minify_test( + r#" @-moz-document url-prefix() { h1 { color: yellow; } } - "#, "@-moz-document url-prefix(){h1{color:#ff0}}"); - minify_test(r#" + "#, + "@-moz-document url-prefix(){h1{color:#ff0}}", + ); + minify_test( + r#" @-moz-document url-prefix("") { h1 { color: yellow; } } - "#, "@-moz-document url-prefix(){h1{color:#ff0}}"); - error_test("@-moz-document url-prefix(foo) {}", ParserError::UnexpectedToken(crate::properties::custom::Token::Ident("foo".into()))); - error_test("@-moz-document url-prefix(\"foo\") {}", ParserError::UnexpectedToken(crate::properties::custom::Token::QuotedString("foo".into()))); + "#, + "@-moz-document url-prefix(){h1{color:#ff0}}", + ); + error_test( + "@-moz-document url-prefix(foo) {}", + ParserError::UnexpectedToken(crate::properties::custom::Token::Ident("foo".into())), + ); + error_test( + "@-moz-document url-prefix(\"foo\") {}", + ParserError::UnexpectedToken(crate::properties::custom::Token::QuotedString("foo".into())), + ); } #[test] @@ -10180,20 +13707,40 @@ mod tests { minify_test(".foo { --test:foo; }", ".foo{--test:foo}"); minify_test(".foo { --test:foo ; }", ".foo{--test:foo}"); minify_test(".foo { --test: var(--foo, 20px); }", ".foo{--test:var(--foo,20px)}"); - minify_test(".foo { transition: var(--foo, 20px),\nvar(--bar, 40px); }", ".foo{transition:var(--foo,20px),var(--bar,40px)}"); - minify_test(".foo { background: var(--color) var(--image); }", ".foo{background:var(--color)var(--image)}"); - minify_test(".foo { height: calc(var(--spectrum-global-dimension-size-300) / 2);", ".foo{height:calc(var(--spectrum-global-dimension-size-300)/2)}"); - minify_test(".foo { color: var(--color, rgb(255, 255, 0)); }", ".foo{color:var(--color,#ff0)}"); - minify_test(".foo { color: var(--color, #ffff00); }", ".foo{color:var(--color,#ff0)}"); - minify_test(".foo { color: var(--color, rgb(var(--red), var(--green), 0)); }", ".foo{color:var(--color,rgb(var(--red),var(--green),0))}"); + minify_test( + ".foo { transition: var(--foo, 20px),\nvar(--bar, 40px); }", + ".foo{transition:var(--foo,20px),var(--bar,40px)}", + ); + minify_test( + ".foo { background: var(--color) var(--image); }", + ".foo{background:var(--color)var(--image)}", + ); + minify_test( + ".foo { height: calc(var(--spectrum-global-dimension-size-300) / 2);", + ".foo{height:calc(var(--spectrum-global-dimension-size-300)/2)}", + ); + minify_test( + ".foo { color: var(--color, rgb(255, 255, 0)); }", + ".foo{color:var(--color,#ff0)}", + ); + minify_test( + ".foo { color: var(--color, #ffff00); }", + ".foo{color:var(--color,#ff0)}", + ); + minify_test( + ".foo { color: var(--color, rgb(var(--red), var(--green), 0)); }", + ".foo{color:var(--color,rgb(var(--red),var(--green),0))}", + ); minify_test(".foo { --test: .5s; }", ".foo{--test:.5s}"); minify_test(".foo { --theme-sizes-1\\/12: 2 }", ".foo{--theme-sizes-1\\/12:2}"); - - prefix_test(r#" + + prefix_test( + r#" .foo { --custom: lab(40% 56.6 39); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --custom: #b32323; } @@ -10203,16 +13750,20 @@ mod tests { --custom: lab(40% 56.6 39); } } - "#}, Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { --custom: lab(40% 56.6 39) !important; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --custom: #b32323 !important; } @@ -10222,16 +13773,20 @@ mod tests { --custom: lab(40% 56.6 39) !important; } } - "#}, Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { --custom: lab(40% 56.6 39); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --custom: #b32323; } @@ -10247,17 +13802,21 @@ mod tests { --custom: lab(40% 56.6 39); } } - "#}, Browsers { - chrome: Some(90 << 16), - safari: Some(14 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + safari: Some(14 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { --custom: lab(40% 56.6 39); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --custom: color(display-p3 .643308 .192455 .167712); } @@ -10267,42 +13826,54 @@ mod tests { --custom: lab(40% 56.6 39); } } - "#}, Browsers { - safari: Some(14 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(14 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { --custom: lab(40% 56.6 39); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --custom: lab(40% 56.6 39); } - "#}, Browsers { - safari: Some(15 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(15 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { --custom: oklab(59.686% 0.1009 0.1192); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --custom: lab(52.2319% 40.1449 59.9171); } - "#}, Browsers { - safari: Some(15 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(15 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { --custom: oklab(59.686% 0.1009 0.1192); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --custom: color(display-p3 .724144 .386777 .148795); } @@ -10312,16 +13883,20 @@ mod tests { --custom: lab(52.2319% 40.1449 59.9171); } } - "#}, Browsers { - safari: Some(14 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(14 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { --custom: oklab(59.686% 0.1009 0.1192); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --custom: #c65d07; } @@ -10337,18 +13912,22 @@ mod tests { --custom: lab(52.2319% 40.1449 59.9171); } } - "#}, Browsers { - safari: Some(14 << 16), - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(14 << 16), + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { --foo: oklab(59.686% 0.1009 0.1192); --bar: lab(40% 56.6 39); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --foo: #c65d07; --bar: #b32323; @@ -10367,17 +13946,21 @@ mod tests { --bar: lab(40% 56.6 39); } } - "#}, Browsers { - safari: Some(14 << 16), - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(14 << 16), + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { --foo: color(display-p3 0 1 0); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --foo: #00f942; } @@ -10387,30 +13970,38 @@ mod tests { --foo: color(display-p3 0 1 0); } } - "#}, Browsers { - safari: Some(14 << 16), - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(14 << 16), + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { --foo: color(display-p3 0 1 0); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --foo: color(display-p3 0 1 0); } - "#}, Browsers { - safari: Some(14 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(14 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { --foo: color(display-p3 0 1 0); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --foo: #00f942; } @@ -10420,17 +14011,21 @@ mod tests { --foo: color(display-p3 0 1 0); } } - "#}, Browsers { - safari: Some(15 << 16), - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + safari: Some(15 << 16), + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { --foo: color(display-p3 0 1 0); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --foo: #00f942; } @@ -10440,12 +14035,15 @@ mod tests { --foo: color(display-p3 0 1 0); } } - "#}, Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { text-decoration: underline; } @@ -10453,7 +14051,8 @@ mod tests { .foo { --custom: lab(40% 56.6 39); } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --custom: #b32323; text-decoration: underline; @@ -10464,12 +14063,15 @@ mod tests { --custom: lab(40% 56.6 39); } } - "#}, Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" .foo { --custom: lab(40% 56.6 39); } @@ -10477,7 +14079,8 @@ mod tests { .foo { text-decoration: underline; } - "#, indoc! {r#" + "#, + indoc! {r#" .foo { --custom: #b32323; } @@ -10491,12 +14094,15 @@ mod tests { .foo { text-decoration: underline; } - "#}, Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" @keyframes foo { from { --custom: lab(40% 56.6 39); @@ -10506,7 +14112,8 @@ mod tests { --custom: lch(50.998% 135.363 338); } } - "#, indoc! {r#" + "#, + indoc! {r#" @keyframes foo { from { --custom: #b32323; @@ -10528,12 +14135,15 @@ mod tests { } } } - "#}, Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" @keyframes foo { from { --custom: lab(40% 56.6 39); @@ -10543,7 +14153,8 @@ mod tests { --custom: lch(50.998% 135.363 338); } } - "#, indoc! {r#" + "#, + indoc! {r#" @keyframes foo { from { --custom: #b32323; @@ -10577,13 +14188,16 @@ mod tests { } } } - "#}, Browsers { - chrome: Some(90 << 16), - safari: Some(14 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + safari: Some(14 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" @keyframes foo { from { --custom: #ff0; @@ -10595,7 +14209,8 @@ mod tests { opacity: 1; } } - "#, indoc! {r#" + "#, + indoc! {r#" @keyframes foo { from { --custom: #ff0; @@ -10621,18 +14236,22 @@ mod tests { } } } - "#}, Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); - prefix_test(r#" + prefix_test( + r#" @keyframes foo { from { text-decoration: var(--foo) lab(29.2345% 39.3825 20.0664); } } - "#, indoc! {r#" + "#, + indoc! {r#" @keyframes foo { from { text-decoration: var(--foo) #7d2329; @@ -10646,15 +14265,18 @@ mod tests { } } } - "#}, Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); } #[test] fn test_charset() { - test(r#" + test( + r#" @charset "UTF-8"; .foo { @@ -10666,7 +14288,8 @@ mod tests { .bar { color: yellow; } - "#, indoc! { r#" + "#, + indoc! { r#" .foo { color: red; } @@ -10674,17 +14297,50 @@ mod tests { .bar { color: #ff0; } - "#}) + "#}, + ) } #[test] fn test_style_attr() { attr_test("color: yellow; flex: 1 1 auto", "color: #ff0; flex: auto", false, None); attr_test("color: yellow; flex: 1 1 auto", "color:#ff0;flex:auto", true, None); - attr_test("border-inline-start: 2px solid red", "border-inline-start: 2px solid red", false, Some(Browsers { safari: Some(12 << 16), ..Browsers::default() })); - attr_test("color: lab(40% 56.6 39);", "color:#b32323;color:lab(40% 56.6 39)", true, Some(Browsers { safari: Some(8 << 16), ..Browsers::default() })); - attr_test("--foo: lab(40% 56.6 39);", "--foo:#b32323", true, Some(Browsers { safari: Some(8 << 16), ..Browsers::default() })); - attr_test("text-decoration: var(--foo) lab(40% 56.6 39);", "text-decoration:var(--foo)#b32323", true, Some(Browsers { chrome: Some(90 << 16), ..Browsers::default() })); + attr_test( + "border-inline-start: 2px solid red", + "border-inline-start: 2px solid red", + false, + Some(Browsers { + safari: Some(12 << 16), + ..Browsers::default() + }), + ); + attr_test( + "color: lab(40% 56.6 39);", + "color:#b32323;color:lab(40% 56.6 39)", + true, + Some(Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }), + ); + attr_test( + "--foo: lab(40% 56.6 39);", + "--foo:#b32323", + true, + Some(Browsers { + safari: Some(8 << 16), + ..Browsers::default() + }), + ); + attr_test( + "text-decoration: var(--foo) lab(40% 56.6 39);", + "text-decoration:var(--foo)#b32323", + true, + Some(Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }), + ); } #[test] @@ -10696,7 +14352,7 @@ mod tests { & > .bar { color: red; } } "#, - indoc!{r#" + indoc! {r#" .foo { color: #00f; } @@ -10704,7 +14360,7 @@ mod tests { .foo > .bar { color: red; } - "#} + "#}, ); nesting_test( @@ -10714,7 +14370,7 @@ mod tests { &.bar { color: red; } } "#, - indoc!{r#" + indoc! {r#" .foo { color: #00f; } @@ -10722,7 +14378,7 @@ mod tests { .foo.bar { color: red; } - "#} + "#}, ); nesting_test( @@ -10732,7 +14388,7 @@ mod tests { & + .baz, &.qux { color: red; } } "#, - indoc!{r#" + indoc! {r#" .foo, .bar { color: #00f; } @@ -10740,7 +14396,7 @@ mod tests { :is(.foo, .bar) + .baz, :is(.foo, .bar).qux { color: red; } - "#} + "#}, ); nesting_test( @@ -10750,7 +14406,7 @@ mod tests { & .bar & .baz & .qux { color: red; } } "#, - indoc!{r#" + indoc! {r#" .foo { color: #00f; } @@ -10758,7 +14414,7 @@ mod tests { .foo .bar .foo .baz .foo .qux { color: red; } - "#} + "#}, ); nesting_test( @@ -10768,7 +14424,7 @@ mod tests { & { padding: 2ch; } } "#, - indoc!{r#" + indoc! {r#" .foo { color: #00f; } @@ -10776,7 +14432,7 @@ mod tests { .foo { padding: 2ch; } - "#} + "#}, ); nesting_test( @@ -10786,7 +14442,7 @@ mod tests { && { padding: 2ch; } } "#, - indoc!{r#" + indoc! {r#" .foo { color: #00f; } @@ -10794,7 +14450,7 @@ mod tests { .foo.foo { padding: 2ch; } - "#} + "#}, ); nesting_test( @@ -10803,11 +14459,11 @@ mod tests { &:hover > .baz { color: red; } } "#, - indoc!{r#" + indoc! {r#" :is(.error, .invalid):hover > .baz { color: red; } - "#} + "#}, ); nesting_test( @@ -10816,11 +14472,11 @@ mod tests { &:is(.bar, &.baz) { color: red; } } "#, - indoc!{r#" + indoc! {r#" .foo:is(.bar, .foo.baz) { color: red; } - "#} + "#}, ); nesting_test( @@ -10837,7 +14493,7 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" figure { margin: 0; } @@ -10849,7 +14505,7 @@ mod tests { figure > figcaption > p { font-size: .9rem; } - "#} + "#}, ); nesting_test( @@ -10861,7 +14517,7 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" .foo { color: red; } @@ -10869,7 +14525,7 @@ mod tests { .foo > .bar { color: #00f; } - "#} + "#}, ); nesting_test( @@ -10881,7 +14537,7 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" .foo { color: red; } @@ -10889,7 +14545,7 @@ mod tests { .parent .foo { color: #00f; } - "#} + "#}, ); nesting_test( @@ -10901,7 +14557,7 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" .foo { color: red; } @@ -10909,7 +14565,7 @@ mod tests { :not(.foo) { color: #00f; } - "#} + "#}, ); nesting_test( @@ -10924,7 +14580,7 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" .foo { color: #00f; } @@ -10936,7 +14592,7 @@ mod tests { .bar .foo.baz { color: green; } - "#} + "#}, ); nesting_test( @@ -10949,7 +14605,7 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" .foo { display: grid; } @@ -10959,7 +14615,7 @@ mod tests { grid-auto-flow: column; } } - "#} + "#}, ); nesting_test( @@ -10976,7 +14632,7 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" .foo { display: grid; } @@ -10992,7 +14648,7 @@ mod tests { } } } - "#} + "#}, ); nesting_test( @@ -11005,7 +14661,7 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" .foo { display: grid; } @@ -11015,7 +14671,7 @@ mod tests { grid-auto-flow: column; } } - "#} + "#}, ); nesting_test( @@ -11045,7 +14701,7 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" @namespace "http://example.com/foo"; @namespace toto "http://toto.example.org"; @@ -11068,7 +14724,7 @@ mod tests { toto|x.foo { color: red; } - "#} + "#}, ); nesting_test( @@ -11079,11 +14735,11 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" article.foo > figure { color: red; } - "#} + "#}, ); nesting_test( @@ -11121,7 +14777,7 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" @namespace "http://example.com/foo"; @namespace toto "http://toto.example.org"; @@ -11144,7 +14800,7 @@ mod tests { .foo:is(toto|x) { color: red; } - "#} + "#}, ); nesting_test( @@ -11155,11 +14811,11 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" div.bar { background: green; } - "#} + "#}, ); nesting_test( @@ -11170,11 +14826,11 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" span:is(div > .foo) { background: green; } - "#} + "#}, ); nesting_test( @@ -11185,11 +14841,11 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" .foo h1 { background: green; } - "#} + "#}, ); nesting_test( @@ -11204,7 +14860,7 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" :not(.foo) { color: red; } @@ -11212,7 +14868,7 @@ mod tests { .foo h1 { background: green; } - "#} + "#}, ); nesting_test( @@ -11227,7 +14883,7 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" .foo h1 { background: green; } @@ -11235,7 +14891,7 @@ mod tests { :not(.foo) { color: red; } - "#} + "#}, ); nesting_test( @@ -11246,11 +14902,11 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" h1:is(.foo .bar) { background: green; } - "#} + "#}, ); nesting_test( @@ -11261,11 +14917,11 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" h1:is(.foo .bar) { background: green; } - "#} + "#}, ); nesting_test( @@ -11276,11 +14932,11 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" h1.foo.bar { background: green; } - "#} + "#}, ); nesting_test( @@ -11291,11 +14947,11 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" h1:is(.foo .bar) .baz { background: green; } - "#} + "#}, ); nesting_test( @@ -11306,11 +14962,11 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" h1 .baz:is(.foo .bar) { background: green; } - "#} + "#}, ); nesting_test( @@ -11321,11 +14977,11 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" .foo .bar.baz { background: green; } - "#} + "#}, ); nesting_test( @@ -11336,11 +14992,11 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" .baz:is(.foo .bar) { background: green; } - "#} + "#}, ); nesting_test( @@ -11351,11 +15007,11 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" .baz .foo .bar { background: green; } - "#} + "#}, ); nesting_test_no_targets( @@ -11370,7 +15026,7 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" .foo { color: #00f; @@ -11382,7 +15038,7 @@ mod tests { } } } - "#} + "#}, ); nesting_test_no_targets( @@ -11398,7 +15054,7 @@ mod tests { } } "#, - indoc!{r#" + indoc! {r#" .foo { color: #00f; @@ -11410,7 +15066,7 @@ mod tests { color: purple; } } - "#} + "#}, ); nesting_test_no_targets( @@ -11419,19 +15075,20 @@ mod tests { &:hover > .baz { color: red; } } "#, - indoc!{r#" + indoc! {r#" .error, .invalid { &:hover > .baz { color: red; } } - "#} + "#}, ); } #[test] fn test_css_modules() { - css_modules_test(r#" + css_modules_test( + r#" .foo { color: red; } @@ -11465,7 +15122,8 @@ mod tests { from { opacity: 0 } to { opacity: 1 } } - "#, indoc!{r#" + "#, + indoc! {r#" .EgL3uq_foo { color: red; } @@ -11509,16 +15167,19 @@ mod tests { opacity: 1; } } - "#}, map! { - "foo" => "EgL3uq_foo", - "id" => "EgL3uq_id", - "test" => "EgL3uq_test" referenced: true, - "circles" => "EgL3uq_circles" referenced: true, - "fade" => "EgL3uq_fade" - }); + "#}, + map! { + "foo" => "EgL3uq_foo", + "id" => "EgL3uq_id", + "test" => "EgL3uq_test" referenced: true, + "circles" => "EgL3uq_circles" referenced: true, + "fade" => "EgL3uq_fade" + }, + ); #[cfg(feature = "grid")] - css_modules_test(r#" + css_modules_test( + r#" body { grid: [header-top] "a a a" [header-bottom] [main-top] "b b b" 1fr [main-bottom] @@ -11532,7 +15193,8 @@ mod tests { main { grid-row: main-top / main-bottom; } - "#, indoc!{r#" + "#, + indoc! {r#" body { grid: [EgL3uq_header-top] "EgL3uq_a EgL3uq_a EgL3uq_a" [EgL3uq_header-bottom] [EgL3uq_main-top] "EgL3uq_b EgL3uq_b EgL3uq_b" 1fr [EgL3uq_main-bottom] @@ -11546,14 +15208,16 @@ mod tests { main { grid-row: EgL3uq_main-top / EgL3uq_main-bottom; } - "#}, map! { - "header-top" => "EgL3uq_header-top", - "header-bottom" => "EgL3uq_header-bottom", - "main-top" => "EgL3uq_main-top", - "main-bottom" => "EgL3uq_main-bottom", - "a" => "EgL3uq_a", - "b" => "EgL3uq_b" - }); + "#}, + map! { + "header-top" => "EgL3uq_header-top", + "header-bottom" => "EgL3uq_header-bottom", + "main-top" => "EgL3uq_main-top", + "main-bottom" => "EgL3uq_main-bottom", + "a" => "EgL3uq_a", + "b" => "EgL3uq_b" + }, + ); #[cfg(feature = "grid")] css_modules_test( @@ -11570,7 +15234,7 @@ mod tests { grid-column-start: foo-start; } "#, - indoc!{r#" + indoc! {r#" .EgL3uq_grid { grid-template-areas: "EgL3uq_foo"; } @@ -11588,20 +15252,25 @@ mod tests { "foo-start" => "EgL3uq_foo-start", "grid" => "EgL3uq_grid", "bar" => "EgL3uq_bar" - } + }, ); - css_modules_test(r#" + css_modules_test( + r#" test { transition-property: opacity; } - "#, indoc!{r#" + "#, + indoc! {r#" test { transition-property: opacity; } - "#}, map! {}); + "#}, + map! {}, + ); - css_modules_test(r#" + css_modules_test( + r#" :global(.foo) { color: red; } @@ -11613,7 +15282,8 @@ mod tests { .bar :global(.baz) { color: purple; } - "#, indoc!{r#" + "#, + indoc! {r#" .foo { color: red; } @@ -11625,17 +15295,18 @@ mod tests { .EgL3uq_bar .baz { color: purple; } - "#}, map! { - "bar" => "EgL3uq_bar" - }); - + "#}, + map! { + "bar" => "EgL3uq_bar" + }, + ); // :global(:local(.hi)) { // color: green; // } - - css_modules_test(r#" + css_modules_test( + r#" .test { composes: foo; background: white; @@ -11644,7 +15315,8 @@ mod tests { .foo { color: red; } - "#, indoc!{r#" + "#, + indoc! {r#" .EgL3uq_test { background: #fff; } @@ -11652,12 +15324,15 @@ mod tests { .EgL3uq_foo { color: red; } - "#}, map! { - "test" => "EgL3uq_test" "EgL3uq_foo", - "foo" => "EgL3uq_foo" - }); + "#}, + map! { + "test" => "EgL3uq_test" "EgL3uq_foo", + "foo" => "EgL3uq_foo" + }, + ); - css_modules_test(r#" + css_modules_test( + r#" .a, .b { composes: foo; background: white; @@ -11666,7 +15341,8 @@ mod tests { .foo { color: red; } - "#, indoc!{r#" + "#, + indoc! {r#" .EgL3uq_a, .EgL3uq_b { background: #fff; } @@ -11674,13 +15350,16 @@ mod tests { .EgL3uq_foo { color: red; } - "#}, map! { - "a" => "EgL3uq_a" "EgL3uq_foo", - "b" => "EgL3uq_b" "EgL3uq_foo", - "foo" => "EgL3uq_foo" - }); + "#}, + map! { + "a" => "EgL3uq_a" "EgL3uq_foo", + "b" => "EgL3uq_b" "EgL3uq_foo", + "foo" => "EgL3uq_foo" + }, + ); - css_modules_test(r#" + css_modules_test( + r#" .test { composes: foo bar; background: white; @@ -11693,7 +15372,8 @@ mod tests { .bar { color: yellow; } - "#, indoc!{r#" + "#, + indoc! {r#" .EgL3uq_test { background: #fff; } @@ -11705,65 +15385,84 @@ mod tests { .EgL3uq_bar { color: #ff0; } - "#}, map! { - "test" => "EgL3uq_test" "EgL3uq_foo" "EgL3uq_bar", - "foo" => "EgL3uq_foo", - "bar" => "EgL3uq_bar" - }); + "#}, + map! { + "test" => "EgL3uq_test" "EgL3uq_foo" "EgL3uq_bar", + "foo" => "EgL3uq_foo", + "bar" => "EgL3uq_bar" + }, + ); - css_modules_test(r#" + css_modules_test( + r#" .test { composes: foo from global; background: white; } - "#, indoc!{r#" + "#, + indoc! {r#" .EgL3uq_test { background: #fff; } - "#}, map! { - "test" => "EgL3uq_test" "foo" global: true - }); + "#}, + map! { + "test" => "EgL3uq_test" "foo" global: true + }, + ); - css_modules_test(r#" + css_modules_test( + r#" .test { composes: foo bar from global; background: white; } - "#, indoc!{r#" + "#, + indoc! {r#" .EgL3uq_test { background: #fff; } - "#}, map! { - "test" => "EgL3uq_test" "foo" global: true "bar" global: true - }); + "#}, + map! { + "test" => "EgL3uq_test" "foo" global: true "bar" global: true + }, + ); - css_modules_test(r#" + css_modules_test( + r#" .test { composes: foo from "foo.css"; background: white; } - "#, indoc!{r#" + "#, + indoc! {r#" .EgL3uq_test { background: #fff; } - "#}, map! { - "test" => "EgL3uq_test" "foo" from "foo.css" - }); + "#}, + map! { + "test" => "EgL3uq_test" "foo" from "foo.css" + }, + ); - css_modules_test(r#" + css_modules_test( + r#" .test { composes: foo bar from "foo.css"; background: white; } - "#, indoc!{r#" + "#, + indoc! {r#" .EgL3uq_test { background: #fff; } - "#}, map! { - "test" => "EgL3uq_test" "foo" from "foo.css" "bar" from "foo.css" - }); + "#}, + map! { + "test" => "EgL3uq_test" "foo" from "foo.css" "bar" from "foo.css" + }, + ); - css_modules_test(r#" + css_modules_test( + r#" .test { composes: foo; composes: foo from "foo.css"; @@ -11774,7 +15473,8 @@ mod tests { .foo { color: red; } - "#, indoc!{r#" + "#, + indoc! {r#" .EgL3uq_test { background: #fff; } @@ -11782,10 +15482,12 @@ mod tests { .EgL3uq_foo { color: red; } - "#}, map! { - "test" => "EgL3uq_test" "EgL3uq_foo" "foo" from "foo.css" "bar" from "bar.css", - "foo" => "EgL3uq_foo" - }); + "#}, + map! { + "test" => "EgL3uq_test" "EgL3uq_foo" "foo" from "foo.css" "bar" from "bar.css", + "foo" => "EgL3uq_foo" + }, + ); } #[test] @@ -11819,15 +15521,17 @@ mod tests { "#}; let stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions::default()).unwrap(); - let res = stylesheet.to_css(PrinterOptions { - pseudo_classes: Some(PseudoClasses { - hover: Some("is-hovered"), - active: Some("is-active"), - focus_visible: Some("focus-visible"), - ..PseudoClasses::default() - }), - ..PrinterOptions::default() - }).unwrap(); + let res = stylesheet + .to_css(PrinterOptions { + pseudo_classes: Some(PseudoClasses { + hover: Some("is-hovered"), + active: Some("is-active"), + focus_visible: Some("focus-visible"), + ..PseudoClasses::default() + }), + ..PrinterOptions::default() + }) + .unwrap(); assert_eq!(res.code, expected); let source = r#" @@ -11842,14 +15546,24 @@ mod tests { } "#}; - let stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions { css_modules: true, ..ParserOptions::default() }).unwrap(); - let res = stylesheet.to_css(PrinterOptions { - pseudo_classes: Some(PseudoClasses { - hover: Some("is-hovered"), - ..PseudoClasses::default() - }), - ..PrinterOptions::default() - }).unwrap(); + let stylesheet = StyleSheet::parse( + "test.css".into(), + &source, + ParserOptions { + css_modules: true, + ..ParserOptions::default() + }, + ) + .unwrap(); + let res = stylesheet + .to_css(PrinterOptions { + pseudo_classes: Some(PseudoClasses { + hover: Some("is-hovered"), + ..PseudoClasses::default() + }), + ..PrinterOptions::default() + }) + .unwrap(); assert_eq!(res.code, expected); } @@ -11899,7 +15613,7 @@ mod tests { } "#; - let expected = indoc!{r#" + let expected = indoc! {r#" .foo { color: red; } @@ -11920,10 +15634,15 @@ mod tests { "#}; let mut stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions::default()).unwrap(); - stylesheet.minify(MinifyOptions { - unused_symbols: vec!["bar", "other_id", "fade", "circles"].iter().map(|s| String::from(*s)).collect(), - ..MinifyOptions::default() - }).unwrap(); + stylesheet + .minify(MinifyOptions { + unused_symbols: vec!["bar", "other_id", "fade", "circles"] + .iter() + .map(|s| String::from(*s)) + .collect(), + ..MinifyOptions::default() + }) + .unwrap(); let res = stylesheet.to_css(PrinterOptions::default()).unwrap(); assert_eq!(res.code, expected); @@ -11937,17 +15656,27 @@ mod tests { } "#; - let expected = indoc!{r#" + let expected = indoc! {r#" .foo { color: red; } "#}; - let mut stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions { nesting: true, ..ParserOptions::default() }).unwrap(); - stylesheet.minify(MinifyOptions { - unused_symbols: vec!["bar"].iter().map(|s| String::from(*s)).collect(), - ..MinifyOptions::default() - }).unwrap(); + let mut stylesheet = StyleSheet::parse( + "test.css".into(), + &source, + ParserOptions { + nesting: true, + ..ParserOptions::default() + }, + ) + .unwrap(); + stylesheet + .minify(MinifyOptions { + unused_symbols: vec!["bar"].iter().map(|s| String::from(*s)).collect(), + ..MinifyOptions::default() + }) + .unwrap(); let res = stylesheet.to_css(PrinterOptions::default()).unwrap(); assert_eq!(res.code, expected); @@ -11981,21 +15710,36 @@ mod tests { } "#; - let expected = indoc!{r#" + let expected = indoc! {r#" :not(.foo) { color: green; } "#}; - let mut stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions { nesting: true, ..ParserOptions::default() }).unwrap(); - stylesheet.minify(MinifyOptions { - unused_symbols: vec!["foo", "x"].iter().map(|s| String::from(*s)).collect(), - ..MinifyOptions::default() - }).unwrap(); - let res = stylesheet.to_css(PrinterOptions { - targets: Some(Browsers { chrome: Some(95 << 16), ..Browsers::default() }), - ..PrinterOptions::default() - }).unwrap(); + let mut stylesheet = StyleSheet::parse( + "test.css".into(), + &source, + ParserOptions { + nesting: true, + ..ParserOptions::default() + }, + ) + .unwrap(); + stylesheet + .minify(MinifyOptions { + unused_symbols: vec!["foo", "x"].iter().map(|s| String::from(*s)).collect(), + ..MinifyOptions::default() + }) + .unwrap(); + let res = stylesheet + .to_css(PrinterOptions { + targets: Some(Browsers { + chrome: Some(95 << 16), + ..Browsers::default() + }), + ..PrinterOptions::default() + }) + .unwrap(); assert_eq!(res.code, expected); } @@ -12022,47 +15766,149 @@ mod tests { minify_test(".foo { stroke-dasharray: 4 1 2; }", ".foo{stroke-dasharray:4 1 2}"); minify_test(".foo { stroke-dasharray: 4,1,2; }", ".foo{stroke-dasharray:4 1 2}"); minify_test(".foo { stroke-dasharray: 4, 1, 2; }", ".foo{stroke-dasharray:4 1 2}"); - minify_test(".foo { stroke-dasharray: 4px, 1px, 2px; }", ".foo{stroke-dasharray:4 1 2}"); + minify_test( + ".foo { stroke-dasharray: 4px, 1px, 2px; }", + ".foo{stroke-dasharray:4 1 2}", + ); minify_test(".foo { mask: url('foo.svg'); }", ".foo{mask:url(foo.svg)}"); - minify_test(".foo { mask: url(masks.svg#star) luminance }", ".foo{mask:url(masks.svg#star) luminance}"); - minify_test(".foo { mask: url(masks.svg#star) 40px 20px }", ".foo{mask:url(masks.svg#star) 40px 20px}"); - minify_test(".foo { mask: url(masks.svg#star) 0 0 / 50px 50px }", ".foo{mask:url(masks.svg#star) 0 0/50px 50px}"); - minify_test(".foo { mask: url(masks.svg#star) repeat-x }", ".foo{mask:url(masks.svg#star) repeat-x}"); - minify_test(".foo { mask: url(masks.svg#star) stroke-box }", ".foo{mask:url(masks.svg#star) stroke-box}"); - minify_test(".foo { mask: url(masks.svg#star) stroke-box stroke-box }", ".foo{mask:url(masks.svg#star) stroke-box}"); - minify_test(".foo { mask: url(masks.svg#star) border-box }", ".foo{mask:url(masks.svg#star)}"); - minify_test(".foo { mask: url(masks.svg#star) left / 16px repeat-y, url(masks.svg#circle) right / 16px repeat-y }", ".foo{mask:url(masks.svg#star) 0/16px repeat-y,url(masks.svg#circle) 100%/16px repeat-y}"); - - minify_test(".foo { mask-border: url('border-mask.png') 25; }", ".foo{mask-border:url(border-mask.png) 25}"); - minify_test(".foo { mask-border: url('border-mask.png') 25 / 35px / 12px space alpha; }", ".foo{mask-border:url(border-mask.png) 25/35px/12px space}"); - minify_test(".foo { mask-border: url('border-mask.png') 25 / 35px / 12px space luminance; }", ".foo{mask-border:url(border-mask.png) 25/35px/12px space luminance}"); - minify_test(".foo { mask-border: url('border-mask.png') luminance 25 / 35px / 12px space; }", ".foo{mask-border:url(border-mask.png) 25/35px/12px space luminance}"); - - minify_test(".foo { clip-path: url('clip.svg#star'); }", ".foo{clip-path:url(clip.svg#star)}"); + minify_test( + ".foo { mask: url(masks.svg#star) luminance }", + ".foo{mask:url(masks.svg#star) luminance}", + ); + minify_test( + ".foo { mask: url(masks.svg#star) 40px 20px }", + ".foo{mask:url(masks.svg#star) 40px 20px}", + ); + minify_test( + ".foo { mask: url(masks.svg#star) 0 0 / 50px 50px }", + ".foo{mask:url(masks.svg#star) 0 0/50px 50px}", + ); + minify_test( + ".foo { mask: url(masks.svg#star) repeat-x }", + ".foo{mask:url(masks.svg#star) repeat-x}", + ); + minify_test( + ".foo { mask: url(masks.svg#star) stroke-box }", + ".foo{mask:url(masks.svg#star) stroke-box}", + ); + minify_test( + ".foo { mask: url(masks.svg#star) stroke-box stroke-box }", + ".foo{mask:url(masks.svg#star) stroke-box}", + ); + minify_test( + ".foo { mask: url(masks.svg#star) border-box }", + ".foo{mask:url(masks.svg#star)}", + ); + minify_test( + ".foo { mask: url(masks.svg#star) left / 16px repeat-y, url(masks.svg#circle) right / 16px repeat-y }", + ".foo{mask:url(masks.svg#star) 0/16px repeat-y,url(masks.svg#circle) 100%/16px repeat-y}", + ); + + minify_test( + ".foo { mask-border: url('border-mask.png') 25; }", + ".foo{mask-border:url(border-mask.png) 25}", + ); + minify_test( + ".foo { mask-border: url('border-mask.png') 25 / 35px / 12px space alpha; }", + ".foo{mask-border:url(border-mask.png) 25/35px/12px space}", + ); + minify_test( + ".foo { mask-border: url('border-mask.png') 25 / 35px / 12px space luminance; }", + ".foo{mask-border:url(border-mask.png) 25/35px/12px space luminance}", + ); + minify_test( + ".foo { mask-border: url('border-mask.png') luminance 25 / 35px / 12px space; }", + ".foo{mask-border:url(border-mask.png) 25/35px/12px space luminance}", + ); + + minify_test( + ".foo { clip-path: url('clip.svg#star'); }", + ".foo{clip-path:url(clip.svg#star)}", + ); minify_test(".foo { clip-path: margin-box; }", ".foo{clip-path:margin-box}"); - minify_test(".foo { clip-path: inset(100px 50px); }", ".foo{clip-path:inset(100px 50px)}"); - minify_test(".foo { clip-path: inset(100px 50px round 5px); }", ".foo{clip-path:inset(100px 50px round 5px)}"); - minify_test(".foo { clip-path: inset(100px 50px round 5px 5px 5px 5px); }", ".foo{clip-path:inset(100px 50px round 5px)}"); + minify_test( + ".foo { clip-path: inset(100px 50px); }", + ".foo{clip-path:inset(100px 50px)}", + ); + minify_test( + ".foo { clip-path: inset(100px 50px round 5px); }", + ".foo{clip-path:inset(100px 50px round 5px)}", + ); + minify_test( + ".foo { clip-path: inset(100px 50px round 5px 5px 5px 5px); }", + ".foo{clip-path:inset(100px 50px round 5px)}", + ); minify_test(".foo { clip-path: circle(50px); }", ".foo{clip-path:circle(50px)}"); - minify_test(".foo { clip-path: circle(50px at center center); }", ".foo{clip-path:circle(50px)}"); - minify_test(".foo { clip-path: circle(50px at 50% 50%); }", ".foo{clip-path:circle(50px)}"); - minify_test(".foo { clip-path: circle(50px at 0 100px); }", ".foo{clip-path:circle(50px at 0 100px)}"); - minify_test(".foo { clip-path: circle(closest-side at 0 100px); }", ".foo{clip-path:circle(at 0 100px)}"); - minify_test(".foo { clip-path: circle(farthest-side at 0 100px); }", ".foo{clip-path:circle(farthest-side at 0 100px)}"); - minify_test(".foo { clip-path: circle(closest-side at 50% 50%); }", ".foo{clip-path:circle()}"); - minify_test(".foo { clip-path: ellipse(50px 60px at 0 10% 20%); }", ".foo{clip-path:ellipse(50px 60px at 0 10% 20%)}"); - minify_test(".foo { clip-path: ellipse(50px 60px at center center); }", ".foo{clip-path:ellipse(50px 60px)}"); - minify_test(".foo { clip-path: ellipse(closest-side closest-side at 50% 50%); }", ".foo{clip-path:ellipse()}"); - minify_test(".foo { clip-path: ellipse(closest-side closest-side at 10% 20%); }", ".foo{clip-path:ellipse(at 10% 20%)}"); - minify_test(".foo { clip-path: ellipse(farthest-side closest-side at 10% 20%); }", ".foo{clip-path:ellipse(farthest-side closest-side at 10% 20%)}"); - minify_test(".foo { clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); }", ".foo{clip-path:polygon(50% 0%,100% 50%,50% 100%,0% 50%)}"); - minify_test(".foo { clip-path: polygon(nonzero, 50% 0%, 100% 50%, 50% 100%, 0% 50%); }", ".foo{clip-path:polygon(50% 0%,100% 50%,50% 100%,0% 50%)}"); - minify_test(".foo { clip-path: polygon(evenodd, 50% 0%, 100% 50%, 50% 100%, 0% 50%); }", ".foo{clip-path:polygon(evenodd,50% 0%,100% 50%,50% 100%,0% 50%)}"); - minify_test(".foo { clip-path: padding-box circle(50px at 0 100px); }", ".foo{clip-path:circle(50px at 0 100px) padding-box}"); - minify_test(".foo { clip-path: circle(50px at 0 100px) padding-box; }", ".foo{clip-path:circle(50px at 0 100px) padding-box}"); - minify_test(".foo { clip-path: circle(50px at 0 100px) border-box; }", ".foo{clip-path:circle(50px at 0 100px)}"); - + minify_test( + ".foo { clip-path: circle(50px at center center); }", + ".foo{clip-path:circle(50px)}", + ); + minify_test( + ".foo { clip-path: circle(50px at 50% 50%); }", + ".foo{clip-path:circle(50px)}", + ); + minify_test( + ".foo { clip-path: circle(50px at 0 100px); }", + ".foo{clip-path:circle(50px at 0 100px)}", + ); + minify_test( + ".foo { clip-path: circle(closest-side at 0 100px); }", + ".foo{clip-path:circle(at 0 100px)}", + ); + minify_test( + ".foo { clip-path: circle(farthest-side at 0 100px); }", + ".foo{clip-path:circle(farthest-side at 0 100px)}", + ); + minify_test( + ".foo { clip-path: circle(closest-side at 50% 50%); }", + ".foo{clip-path:circle()}", + ); + minify_test( + ".foo { clip-path: ellipse(50px 60px at 0 10% 20%); }", + ".foo{clip-path:ellipse(50px 60px at 0 10% 20%)}", + ); + minify_test( + ".foo { clip-path: ellipse(50px 60px at center center); }", + ".foo{clip-path:ellipse(50px 60px)}", + ); + minify_test( + ".foo { clip-path: ellipse(closest-side closest-side at 50% 50%); }", + ".foo{clip-path:ellipse()}", + ); + minify_test( + ".foo { clip-path: ellipse(closest-side closest-side at 10% 20%); }", + ".foo{clip-path:ellipse(at 10% 20%)}", + ); + minify_test( + ".foo { clip-path: ellipse(farthest-side closest-side at 10% 20%); }", + ".foo{clip-path:ellipse(farthest-side closest-side at 10% 20%)}", + ); + minify_test( + ".foo { clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); }", + ".foo{clip-path:polygon(50% 0%,100% 50%,50% 100%,0% 50%)}", + ); + minify_test( + ".foo { clip-path: polygon(nonzero, 50% 0%, 100% 50%, 50% 100%, 0% 50%); }", + ".foo{clip-path:polygon(50% 0%,100% 50%,50% 100%,0% 50%)}", + ); + minify_test( + ".foo { clip-path: polygon(evenodd, 50% 0%, 100% 50%, 50% 100%, 0% 50%); }", + ".foo{clip-path:polygon(evenodd,50% 0%,100% 50%,50% 100%,0% 50%)}", + ); + minify_test( + ".foo { clip-path: padding-box circle(50px at 0 100px); }", + ".foo{clip-path:circle(50px at 0 100px) padding-box}", + ); + minify_test( + ".foo { clip-path: circle(50px at 0 100px) padding-box; }", + ".foo{clip-path:circle(50px at 0 100px) padding-box}", + ); + minify_test( + ".foo { clip-path: circle(50px at 0 100px) border-box; }", + ".foo{clip-path:circle(50px at 0 100px)}", + ); + prefix_test( ".foo { clip-path: circle(50px); }", indoc! { r#" @@ -12074,7 +15920,7 @@ mod tests { Browsers { chrome: Some(30 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12087,7 +15933,7 @@ mod tests { Browsers { chrome: Some(80 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12101,7 +15947,7 @@ mod tests { Browsers { safari: Some(8 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12114,7 +15960,7 @@ mod tests { Browsers { safari: Some(14 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12130,7 +15976,7 @@ mod tests { chrome: Some(90 << 16), safari: Some(14 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12146,7 +15992,7 @@ mod tests { chrome: Some(90 << 16), safari: Some(14 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12162,7 +16008,7 @@ mod tests { chrome: Some(90 << 16), safari: Some(14 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12181,7 +16027,7 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12197,7 +16043,7 @@ mod tests { Browsers { chrome: Some(8 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12215,7 +16061,7 @@ mod tests { Browsers { chrome: Some(8 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12229,7 +16075,7 @@ mod tests { Browsers { chrome: Some(8 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12250,33 +16096,37 @@ mod tests { Browsers { chrome: Some(90 << 16), ..Browsers::default() - } + }, ); prefix_test( - ".foo { mask: url(masks.svg#star) luminance }", + ".foo { mask: url(masks.svg#star) luminance }", indoc! { r#" .foo { -webkit-mask: url(masks.svg#star); -webkit-mask-source-type: luminance; mask: url(masks.svg#star) luminance; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( - ".foo { mask-image: url(masks.svg#star) }", + ".foo { mask-image: url(masks.svg#star) }", indoc! { r#" .foo { -webkit-mask-image: url(masks.svg#star); mask-image: url(masks.svg#star); } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" @@ -12290,7 +16140,7 @@ mod tests { mask-composite: subtract; mask-mode: luminance; } - "#, + "#, indoc! { r#" .foo { -webkit-mask: url(masks.svg#star) 25% 75% / cover no-repeat content-box padding-box; @@ -12298,10 +16148,12 @@ mod tests { -webkit-mask-source-type: luminance; mask: url(masks.svg#star) 25% 75% / cover no-repeat content-box padding-box subtract luminance; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" @@ -12315,7 +16167,7 @@ mod tests { mask-composite: subtract; mask-mode: luminance; } - "#, + "#, indoc! { r#" .foo { -webkit-mask: linear-gradient(#ff0f0e, #7773ff) 25% 75% / cover no-repeat content-box padding-box; @@ -12327,65 +16179,73 @@ mod tests { -webkit-mask-source-type: luminance; mask: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 25% 75% / cover no-repeat content-box padding-box subtract luminance; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" .foo { mask-composite: subtract; } - "#, + "#, indoc! { r#" .foo { -webkit-mask-composite: source-out; mask-composite: subtract; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" .foo { mask-mode: luminance; } - "#, + "#, indoc! { r#" .foo { -webkit-mask-source-type: luminance; mask-mode: luminance; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" .foo { mask-border: url('border-mask.png') 25 / 35px / 12px space luminance; } - "#, + "#, indoc! { r#" .foo { -webkit-mask-box-image: url(border-mask.png) 25 / 35px / 12px space; mask-border: url(border-mask.png) 25 / 35px / 12px space luminance; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" .foo { mask-border: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 25 / 35px / 12px space luminance; } - "#, + "#, indoc! { r#" .foo { -webkit-mask-box-image: linear-gradient(#ff0f0e, #7773ff) 25 / 35px / 12px space; @@ -12393,17 +16253,19 @@ mod tests { -webkit-mask-box-image: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 25 / 35px / 12px space; mask-border: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) 25 / 35px / 12px space luminance; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" .foo { mask-border-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)); } - "#, + "#, indoc! { r#" .foo { -webkit-mask-box-image-source: linear-gradient(#ff0f0e, #7773ff); @@ -12411,10 +16273,12 @@ mod tests { -webkit-mask-box-image-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)); mask-border-source: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)); } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" @@ -12432,10 +16296,12 @@ mod tests { -webkit-mask-box-image: url(foo.png) 10 40 / 10px round; mask-border: url(foo.png) 10 40 / 10px round luminance; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" @@ -12451,10 +16317,12 @@ mod tests { .foo { -webkit-mask-box-image: url(foo.png) 10 40 / 10px round; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" @@ -12467,10 +16335,12 @@ mod tests { -webkit-mask-box-image-slice: 10 40; mask-border-slice: 10 40; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" @@ -12483,17 +16353,19 @@ mod tests { -webkit-mask-box-image-slice: var(--foo); mask-border-slice: var(--foo); } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" .foo { mask-border: linear-gradient(lch(56.208% 136.76 46.312), lch(51% 135.366 301.364)) var(--foo); } - "#, + "#, indoc! { r#" .foo { -webkit-mask-box-image: linear-gradient(#ff0f0e, #7773ff) var(--foo); @@ -12506,97 +16378,118 @@ mod tests { mask-border: linear-gradient(lab(56.208% 94.4644 98.8928), lab(51% 70.4544 -115.586)) var(--foo); } } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" .foo { transition: mask 200ms; } - "#, + "#, indoc! { r#" .foo { transition: -webkit-mask .2s, mask .2s; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" .foo { transition: mask-border 200ms; } - "#, + "#, indoc! { r#" .foo { transition: -webkit-mask-box-image .2s, mask-border .2s; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" .foo { transition-property: mask; } - "#, + "#, indoc! { r#" .foo { transition-property: -webkit-mask, mask; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" .foo { transition-property: mask-border; } - "#, + "#, indoc! { r#" .foo { transition-property: -webkit-mask-box-image, mask-border; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); prefix_test( r#" .foo { transition-property: mask-composite, mask-mode; } - "#, + "#, indoc! { r#" .foo { transition-property: -webkit-mask-composite, mask-composite, -webkit-mask-source-type, mask-mode; } - "#},Browsers { - chrome: Some(90 << 16), - ..Browsers::default() - }); + "#}, + Browsers { + chrome: Some(90 << 16), + ..Browsers::default() + }, + ); } #[test] fn test_filter() { - minify_test(".foo { filter: url('filters.svg#filter-id'); }", ".foo{filter:url(filters.svg#filter-id)}"); + minify_test( + ".foo { filter: url('filters.svg#filter-id'); }", + ".foo{filter:url(filters.svg#filter-id)}", + ); minify_test(".foo { filter: blur(5px); }", ".foo{filter:blur(5px)}"); minify_test(".foo { filter: blur(0px); }", ".foo{filter:blur()}"); minify_test(".foo { filter: brightness(10%); }", ".foo{filter:brightness(10%)}"); minify_test(".foo { filter: brightness(100%); }", ".foo{filter:brightness()}"); - minify_test(".foo { filter: drop-shadow(16px 16px 20px yellow); }", ".foo{filter:drop-shadow(16px 16px 20px #ff0)}"); - minify_test(".foo { filter: contrast(175%) brightness(3%); }", ".foo{filter:contrast(175%)brightness(3%)}"); - + minify_test( + ".foo { filter: drop-shadow(16px 16px 20px yellow); }", + ".foo{filter:drop-shadow(16px 16px 20px #ff0)}", + ); + minify_test( + ".foo { filter: contrast(175%) brightness(3%); }", + ".foo{filter:contrast(175%)brightness(3%)}", + ); + prefix_test( ".foo { filter: blur(5px) }", indoc! { r#" @@ -12608,7 +16501,7 @@ mod tests { Browsers { chrome: Some(20 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12621,7 +16514,7 @@ mod tests { Browsers { chrome: Some(80 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12634,7 +16527,7 @@ mod tests { Browsers { chrome: Some(80 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12648,7 +16541,7 @@ mod tests { Browsers { safari: Some(15 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12662,7 +16555,7 @@ mod tests { Browsers { chrome: Some(20 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12677,7 +16570,7 @@ mod tests { Browsers { chrome: Some(20 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12691,7 +16584,7 @@ mod tests { Browsers { chrome: Some(4 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12705,7 +16598,7 @@ mod tests { Browsers { chrome: Some(4 << 16), ..Browsers::default() - } + }, ); prefix_test( @@ -12724,20 +16617,26 @@ mod tests { Browsers { chrome: Some(4 << 16), ..Browsers::default() - } + }, ); } #[test] fn test_viewport() { - minify_test(r#" + minify_test( + r#" @viewport { width: 100vw; - }"#, "@viewport{width:100vw}"); - minify_test(r#" + }"#, + "@viewport{width:100vw}", + ); + minify_test( + r#" @-ms-viewport { width: device-width; - }"#, "@-ms-viewport{width:device-width}"); + }"#, + "@-ms-viewport{width:device-width}", + ); } #[test] @@ -12758,7 +16657,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -12777,7 +16676,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -12797,7 +16696,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -12816,7 +16715,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -12835,7 +16734,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -12854,7 +16753,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -12873,7 +16772,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -12892,7 +16791,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -12911,7 +16810,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -12930,7 +16829,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -12949,7 +16848,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -12969,7 +16868,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -12989,7 +16888,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -13010,7 +16909,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -13029,7 +16928,7 @@ mod tests { color: green; } } - "#} + "#}, ); custom_media_test( @@ -13043,7 +16942,7 @@ mod tests { } } "#, - "\n" + "\n", ); custom_media_test( @@ -13056,7 +16955,7 @@ mod tests { } } "#, - "\n" + "\n", ); custom_media_test( @@ -13075,13 +16974,24 @@ mod tests { color: green; } } - "#} + "#}, ); fn custom_media_error_test(source: &str, err: Error) { - let mut stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions { custom_media: true, ..ParserOptions::default() }).unwrap(); + let mut stylesheet = StyleSheet::parse( + "test.css".into(), + &source, + ParserOptions { + custom_media: true, + ..ParserOptions::default() + }, + ) + .unwrap(); let res = stylesheet.minify(MinifyOptions { - targets: Some(Browsers { chrome: Some(95 << 16), ..Browsers::default() }), + targets: Some(Browsers { + chrome: Some(95 << 16), + ..Browsers::default() + }), ..MinifyOptions::default() }); assert_eq!(res, Err(err)) @@ -13102,15 +17012,15 @@ mod tests { custom_media_loc: Location { source_index: 0, line: 1, - column: 7 - } + column: 7, + }, }, loc: Some(ErrorLocation { filename: "test.css".into(), line: 3, - column: 7 + column: 7, }), - } + }, ); custom_media_error_test( @@ -13128,15 +17038,15 @@ mod tests { custom_media_loc: Location { source_index: 0, line: 1, - column: 7 - } + column: 7, + }, }, loc: Some(ErrorLocation { filename: "test.css".into(), line: 3, - column: 7 + column: 7, }), - } + }, ); custom_media_error_test( @@ -13151,15 +17061,15 @@ mod tests { custom_media_loc: Location { source_index: 0, line: 2, - column: 7 - } + column: 7, + }, }, loc: Some(ErrorLocation { filename: "test.css".into(), line: 4, - column: 7 + column: 7, }), - } + }, ); custom_media_error_test( @@ -13174,15 +17084,15 @@ mod tests { custom_media_loc: Location { source_index: 0, line: 2, - column: 7 - } + column: 7, + }, }, loc: Some(ErrorLocation { filename: "test.css".into(), line: 4, - column: 7 + column: 7, }), - } + }, ); custom_media_error_test( @@ -13197,15 +17107,15 @@ mod tests { custom_media_loc: Location { source_index: 0, line: 1, - column: 7 - } + column: 7, + }, }, loc: Some(ErrorLocation { filename: "test.css".into(), line: 4, - column: 7 + column: 7, }), - } + }, ); custom_media_error_test( @@ -13224,15 +17134,15 @@ mod tests { custom_media_loc: Location { source_index: 0, line: 2, - column: 7 - } + column: 7, + }, }, loc: Some(ErrorLocation { filename: "test.css".into(), line: 4, - column: 7 + column: 7, }), - } + }, ); custom_media_error_test( @@ -13250,15 +17160,15 @@ mod tests { custom_media_loc: Location { source_index: 0, line: 1, - column: 7 - } + column: 7, + }, }, loc: Some(ErrorLocation { filename: "test.css".into(), line: 3, - column: 7 + column: 7, }), - } + }, ); custom_media_error_test( @@ -13276,9 +17186,9 @@ mod tests { loc: Some(ErrorLocation { filename: "test.css".into(), line: 1, - column: 7 + column: 7, }), - } + }, ); custom_media_error_test( @@ -13299,9 +17209,9 @@ mod tests { loc: Some(ErrorLocation { filename: "test.css".into(), line: 4, - column: 7 + column: 7, }), - } + }, ); } @@ -13310,11 +17220,13 @@ mod tests { fn dep_test(source: &str, expected: &str, deps: Vec<(&str, &str)>) { let mut stylesheet = StyleSheet::parse("test.css".into(), &source, ParserOptions::default()).unwrap(); stylesheet.minify(MinifyOptions::default()).unwrap(); - let res = stylesheet.to_css(PrinterOptions { - analyze_dependencies: true, - minify: true, - ..PrinterOptions::default() - }).unwrap(); + let res = stylesheet + .to_css(PrinterOptions { + analyze_dependencies: true, + minify: true, + ..PrinterOptions::default() + }) + .unwrap(); assert_eq!(res.code, expected); let dependencies = res.dependencies.unwrap(); assert_eq!(dependencies.len(), deps.len()); @@ -13322,9 +17234,9 @@ mod tests { match &dependencies[i] { Dependency::Url(dep) => { assert_eq!(dep.url, url); - assert_eq!(dep.placeholder, placeholder); + assert_eq!(dep.placeholder, placeholder); } - _ => unreachable!() + _ => unreachable!(), } } } @@ -13337,100 +17249,85 @@ mod tests { }); match res { Err(e) => assert_eq!(e.kind, error), - _ => unreachable!() + _ => unreachable!(), } } dep_test( ".foo { background: image-set('./img12x.png', './img21x.png' 2x)}", ".foo{background:image-set(\"hXFI8W\",\"_5TkpBa\" 2x)}", - vec![ - ("./img12x.png", "hXFI8W"), - ("./img21x.png", "_5TkpBa") - ] + vec![("./img12x.png", "hXFI8W"), ("./img21x.png", "_5TkpBa")], ); dep_test( ".foo { background: image-set(url(./img12x.png), url('./img21x.png') 2x)}", ".foo{background:image-set(\"hXFI8W\",\"_5TkpBa\" 2x)}", - vec![ - ("./img12x.png", "hXFI8W"), - ("./img21x.png", "_5TkpBa") - ] + vec![("./img12x.png", "hXFI8W"), ("./img21x.png", "_5TkpBa")], ); dep_test( ".foo { --test: url(/foo.png) }", ".foo{--test:url(\"lDnnrG\")}", - vec![ - ("/foo.png", "lDnnrG"), - ] + vec![("/foo.png", "lDnnrG")], ); dep_test( ".foo { --test: url(\"/foo.png\") }", ".foo{--test:url(\"lDnnrG\")}", - vec![ - ("/foo.png", "lDnnrG"), - ] + vec![("/foo.png", "lDnnrG")], ); dep_test( ".foo { --test: url(\"http://example.com/foo.png\") }", ".foo{--test:url(\"_3X1zSW\")}", - vec![ - ("http://example.com/foo.png", "_3X1zSW"), - ] + vec![("http://example.com/foo.png", "_3X1zSW")], ); dep_test( ".foo { --test: url(\"data:image/svg+xml;utf8,\") }", ".foo{--test:url(\"-vl-rG\")}", - vec![ - ("data:image/svg+xml;utf8,", "-vl-rG"), - ] + vec![("data:image/svg+xml;utf8,", "-vl-rG")], ); dep_test( ".foo { background: url(\"foo.png\") var(--test) }", ".foo{background:url(\"Vwkwkq\") var(--test)}", - vec![ - ("foo.png", "Vwkwkq"), - ] + vec![("foo.png", "Vwkwkq")], ); dep_error_test( ".foo { --test: url(\"foo.png\") }", - PrinterErrorKind::AmbiguousUrlInCustomProperty { url: "foo.png".into() } + PrinterErrorKind::AmbiguousUrlInCustomProperty { url: "foo.png".into() }, ); dep_error_test( ".foo { --test: url(foo.png) }", - PrinterErrorKind::AmbiguousUrlInCustomProperty { url: "foo.png".into() } + PrinterErrorKind::AmbiguousUrlInCustomProperty { url: "foo.png".into() }, ); dep_error_test( ".foo { --test: url(./foo.png) }", - PrinterErrorKind::AmbiguousUrlInCustomProperty { url: "./foo.png".into() } + PrinterErrorKind::AmbiguousUrlInCustomProperty { + url: "./foo.png".into(), + }, ); dep_test( ".foo { behavior: url(#foo) }", ".foo{behavior:url(\"Zn9-2q\")}", - vec![ - ("#foo", "Zn9-2q"), - ] + vec![("#foo", "Zn9-2q")], ); } #[test] fn test_api() { - let stylesheet = StyleSheet::parse("test.css".into(), ".foo:hover { color: red }", ParserOptions::default()).unwrap(); + let stylesheet = + StyleSheet::parse("test.css".into(), ".foo:hover { color: red }", ParserOptions::default()).unwrap(); match &stylesheet.rules.0[0] { CssRule::Style(s) => { assert_eq!(&s.selectors.to_string(), ".foo:hover"); - }, - _ => unreachable!() + } + _ => unreachable!(), } } @@ -13441,20 +17338,26 @@ mod tests { minify_test("@layer foo.bar;", "@layer foo.bar;"); minify_test("@layer foo.bar, baz;", "@layer foo.bar,baz;"); - minify_test(r#" + minify_test( + r#" @layer foo { .bar { color: red; } } - "#, "@layer foo{.bar{color:red}}"); - minify_test(r#" + "#, + "@layer foo{.bar{color:red}}", + ); + minify_test( + r#" @layer foo.bar { .bar { color: red; } } - "#, "@layer foo.bar{.bar{color:red}}"); + "#, + "@layer foo.bar{.bar{color:red}}", + ); minify_test(r#" @layer base { p { max-width: 70ch; } @@ -13470,95 +17373,139 @@ mod tests { } } "#, "@layer base{p{max-width:70ch}}@layer framework{@layer base{p{margin-block:.75em}}@layer theme{p{color:#222}}}"); - minify_test(r#" + minify_test( + r#" @layer { .bar { color: red; } } - "#, "@layer{.bar{color:red}}"); + "#, + "@layer{.bar{color:red}}", + ); error_test("@layer;", ParserError::UnexpectedToken(Token::Semicolon)); error_test("@layer foo, bar {};", ParserError::AtRuleBodyInvalid); minify_test("@import 'test.css' layer;", "@import \"test.css\" layer;"); minify_test("@import 'test.css' layer(foo);", "@import \"test.css\" layer(foo);"); - minify_test("@import 'test.css' layer(foo.bar);", "@import \"test.css\" layer(foo.bar);"); - error_test("@import 'test.css' layer(foo, bar) {};", ParserError::UnexpectedToken(Token::Comma)); + minify_test( + "@import 'test.css' layer(foo.bar);", + "@import \"test.css\" layer(foo.bar);", + ); + error_test( + "@import 'test.css' layer(foo, bar) {};", + ParserError::UnexpectedToken(Token::Comma), + ); } #[test] fn test_property() { - minify_test(r#" + minify_test( + r#" @property --property-name { syntax: ''; inherits: false; initial-value: yellow; } - "#, "@property --property-name{syntax:\"\";inherits:false;initial-value:#ff0}"); + "#, + "@property --property-name{syntax:\"\";inherits:false;initial-value:#ff0}", + ); - minify_test(r#" + minify_test( + r#" @property --property-name { syntax: ''; inherits: true; initial-value: 25px; } - "#, "@property --property-name{syntax:\"\";inherits:true;initial-value:25px}"); - - error_test(r#" + "#, + "@property --property-name{syntax:\"\";inherits:true;initial-value:25px}", + ); + + error_test( + r#" @property --property-name { syntax: ''; inherits: false; initial-value: 25px; } - "#, ParserError::UnexpectedToken(crate::properties::custom::Token::Dimension { has_sign: false, value: 25.0, int_value: Some(25), unit: "px".into() })); + "#, + ParserError::UnexpectedToken(crate::properties::custom::Token::Dimension { + has_sign: false, + value: 25.0, + int_value: Some(25), + unit: "px".into(), + }), + ); - error_test(r#" + error_test( + r#" @property --property-name { syntax: ''; inherits: false; initial-value: var(--some-value); } - "#, ParserError::UnexpectedToken(crate::properties::custom::Token::Function("var".into()))); + "#, + ParserError::UnexpectedToken(crate::properties::custom::Token::Function("var".into())), + ); - error_test(r#" + error_test( + r#" @property --property-name { syntax: ''; inherits: false; } - "#, ParserError::AtRuleBodyInvalid); + "#, + ParserError::AtRuleBodyInvalid, + ); - minify_test(r#" + minify_test( + r#" @property --property-name { syntax: '*'; inherits: false; } - "#, "@property --property-name{syntax:\"*\";inherits:false}"); + "#, + "@property --property-name{syntax:\"*\";inherits:false}", + ); - error_test(r#" + error_test( + r#" @property --property-name { syntax: '*'; } - "#, ParserError::AtRuleBodyInvalid); + "#, + ParserError::AtRuleBodyInvalid, + ); - error_test(r#" + error_test( + r#" @property --property-name { inherits: false; } - "#, ParserError::AtRuleBodyInvalid); + "#, + ParserError::AtRuleBodyInvalid, + ); - error_test(r#" + error_test( + r#" @property property-name { syntax: '*'; inherits: false; } - "#, ParserError::UnexpectedToken(crate::properties::custom::Token::Ident("property-name".into()))); + "#, + ParserError::UnexpectedToken(crate::properties::custom::Token::Ident("property-name".into())), + ); - minify_test(r#" + minify_test( + r#" @property --property-name { syntax: 'custom | '; inherits: false; initial-value: yellow; } - "#, "@property --property-name{syntax:\"custom|\";inherits:false;initial-value:#ff0}"); + "#, + "@property --property-name{syntax:\"custom|\";inherits:false;initial-value:#ff0}", + ); minify_test(r#" @property --property-name { @@ -13568,65 +17515,86 @@ mod tests { } "#, "@property --property-name{syntax:\"\";inherits:false;initial-value:matrix(2,0,0,2,300,500)}"); - minify_test(r#" + minify_test( + r#" @property --property-name { syntax: '