diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0d3c0229..a7be6aec 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,12 +2,12 @@ name: CI on: push: - branches: [master] + branches: [main] pull_request: workflow_dispatch: merge_group: types: [checks_requested] - + jobs: linux-ci: name: Linux @@ -22,6 +22,7 @@ jobs: features: - - --features dummy_match_byte + - --features malloc_size_of include: - toolchain: nightly features: --features bench @@ -36,7 +37,10 @@ jobs: profile: minimal toolchain: ${{ matrix.toolchain }} override: true - components: ${{ matrix.toolchain == 'nightly' && 'miri,rust-src' || '' }} + components: rustfmt, clippy, ${{ matrix.toolchain == 'nightly' && 'miri,rust-src' || '' }} + + - name: Cargo format & lint + run: cargo fmt --check && cargo clippy -- -Dwarnings - name: Cargo build run: cargo build ${{ matrix.features }} diff --git a/Cargo.toml b/Cargo.toml index ab2341d8..6604f20e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cssparser" -version = "0.33.0" -authors = [ "Simon Sapin " ] +version = "0.35.0" +authors = ["Simon Sapin "] description = "Rust implementation of CSS Syntax Level 3" documentation = "https://docs.rs/cssparser/" @@ -15,18 +15,23 @@ rust-version = "1.63" exclude = ["src/css-parsing-tests/**", "src/big-data-url.css"] [dev-dependencies] -serde_json = "1.0" +serde_json = "1.0.25" difference = "2.0" encoding_rs = "0.8" [dependencies] -cssparser-macros = {path = "./macros", version = "0.6.1"} +cssparser-macros = { path = "./macros", version = "0.6.1" } dtoa-short = "0.3" itoa = "1.0" -phf = {version = ">=0.8,<=0.11", features = ["macros"]} -serde = {version = "1.0", optional = true} +phf = { version = "0.11.2", features = ["macros"] } +serde = { version = "1.0", features = ["derive"], optional = true } +malloc_size_of = { version = "0.1", default-features = false, optional = true } smallvec = "1.0" +[profile.profiling] +inherits = "release" +debug = true + [features] bench = [] dummy_match_byte = [] diff --git a/README.md b/README.md index 84d47d9e..d9ca4ada 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ rust-cssparser [![Build Status](https://github.com/servo/rust-cssparser/actions/workflows/main.yml/badge.svg)](https://github.com/servo/rust-cssparser/actions) -[Documentation](https://docs.rs/cssparser/) +[Documentation](https://docs.rs/cssparser) Rust implementation of [CSS Syntax Module Level 3](https://drafts.csswg.org/css-syntax/) @@ -53,5 +53,5 @@ Parsing CSS involves a series of steps: It does however provide some helper functions to parse [CSS colors](src/color.rs) and [An+B](src/nth.rs) (the argument to `:nth-child()` and related selectors. - See [Servo’s `style` crate](https://github.com/servo/servo/tree/master/components/style) + See [Servo’s `style` crate](https://github.com/servo/stylo/tree/main/style) for an example of a parser based on rust-cssparser. diff --git a/color/Cargo.toml b/color/Cargo.toml index d65ee3ed..5dd6aac7 100644 --- a/color/Cargo.toml +++ b/color/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cssparser-color" -version = "0.1.0" +version = "0.3.0" authors = ["Emilio Cobos Álvarez "] description = "Color implementation based on cssparser" documentation = "https://docs.rs/cssparser-color/" @@ -12,9 +12,13 @@ edition = "2021" path = "lib.rs" [dependencies] -cssparser = { version = "0.33", path = ".." } +cssparser = { path = "..", version = "0.35" } +serde = { version = "1.0", features = ["derive"], optional = true } + +[features] +serde = ["cssparser/serde", "dep:serde"] [dev-dependencies] -serde_json = "1.0" +serde_json = "1.0.25" difference = "2.0" encoding_rs = "0.8" diff --git a/color/lib.rs b/color/lib.rs index 1c52e38d..5b116c23 100644 --- a/color/lib.rs +++ b/color/lib.rs @@ -16,17 +16,15 @@ use cssparser::color::{ PredefinedColorSpace, OPAQUE, }; use cssparser::{match_ignore_ascii_case, CowRcStr, ParseError, Parser, ToCss, Token}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::f32::consts::PI; use std::fmt; -use std::str::FromStr; /// Return the named color with the given name. /// /// Matching is case-insensitive in the ASCII range. /// CSS escaping (if relevant) should be resolved before calling this function. /// (For example, the value of an `Ident` token is fine.) +#[allow(clippy::result_unit_err)] #[inline] pub fn parse_color_keyword(ident: &str) -> Result where @@ -55,10 +53,8 @@ where let token = input.next()?; match *token { Token::Hash(ref value) | Token::IDHash(ref value) => { - parse_hash_color(value.as_bytes()).map(|(r, g, b, a)| { - P::Output::from_rgba(r, g, b, a) - }) - }, + parse_hash_color(value.as_bytes()).map(|(r, g, b, a)| P::Output::from_rgba(r, g, b, a)) + } Token::Ident(ref value) => parse_color_keyword(value), Token::Function(ref name) => { let name = name.clone(); @@ -405,13 +401,7 @@ fn parse_color_with_color_space<'i, 't, P>( where P: ColorParser<'i>, { - let color_space = { - let location = arguments.current_source_location(); - - let ident = arguments.expect_ident()?; - PredefinedColorSpace::from_str(ident) - .map_err(|_| location.new_unexpected_token_error(Token::Ident(ident.clone())))? - }; + let color_space = PredefinedColorSpace::parse(arguments)?; let (c1, c2, c3, alpha) = parse_components( color_parser, @@ -506,6 +496,7 @@ fn normalize_hue(hue: f32) -> f32 { } /// A color with red, green, blue, and alpha components, in a byte each. +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[derive(Clone, Copy, PartialEq, Debug)] pub struct RgbaLegacy { /// The red component. @@ -544,27 +535,6 @@ impl RgbaLegacy { } } -#[cfg(feature = "serde")] -impl Serialize for RgbaLegacy { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - (self.red, self.green, self.blue, self.alpha).serialize(serializer) - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for RgbaLegacy { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let (r, g, b, a) = Deserialize::deserialize(deserializer)?; - Ok(RgbaLegacy::new(r, g, b, a)) - } -} - impl ToCss for RgbaLegacy { fn to_css(&self, dest: &mut W) -> fmt::Result where @@ -588,6 +558,7 @@ impl ToCss for RgbaLegacy { /// Color specified by hue, saturation and lightness components. #[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Hsl { /// The hue component. pub hue: Option, @@ -632,29 +603,9 @@ impl ToCss for Hsl { } } -#[cfg(feature = "serde")] -impl Serialize for Hsl { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - (self.hue, self.saturation, self.lightness, self.alpha).serialize(serializer) - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for Hsl { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let (lightness, a, b, alpha) = Deserialize::deserialize(deserializer)?; - Ok(Self::new(lightness, a, b, alpha)) - } -} - /// Color specified by hue, whiteness and blackness components. #[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Hwb { /// The hue component. pub hue: Option, @@ -699,32 +650,12 @@ impl ToCss for Hwb { } } -#[cfg(feature = "serde")] -impl Serialize for Hwb { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - (self.hue, self.whiteness, self.blackness, self.alpha).serialize(serializer) - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for Hwb { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let (lightness, whiteness, blackness, alpha) = Deserialize::deserialize(deserializer)?; - Ok(Self::new(lightness, whiteness, blackness, alpha)) - } -} - // NOTE: LAB and OKLAB is not declared inside the [impl_lab_like] macro, // because it causes cbindgen to ignore them. /// Color specified by lightness, a- and b-axis components. #[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Lab { /// The lightness component. pub lightness: Option, @@ -738,6 +669,7 @@ pub struct Lab { /// Color specified by lightness, a- and b-axis components. #[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Oklab { /// The lightness component. pub lightness: Option, @@ -768,27 +700,6 @@ macro_rules! impl_lab_like { } } - #[cfg(feature = "serde")] - impl Serialize for $cls { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - (self.lightness, self.a, self.b, self.alpha).serialize(serializer) - } - } - - #[cfg(feature = "serde")] - impl<'de> Deserialize<'de> for $cls { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let (lightness, a, b, alpha) = Deserialize::deserialize(deserializer)?; - Ok(Self::new(lightness, a, b, alpha)) - } - } - impl ToCss for $cls { fn to_css(&self, dest: &mut W) -> fmt::Result where @@ -816,6 +727,7 @@ impl_lab_like!(Oklab, "oklab"); /// Color specified by lightness, chroma and hue components. #[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Lch { /// The lightness component. pub lightness: Option, @@ -829,6 +741,7 @@ pub struct Lch { /// Color specified by lightness, chroma and hue components. #[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Oklch { /// The lightness component. pub lightness: Option, @@ -859,27 +772,6 @@ macro_rules! impl_lch_like { } } - #[cfg(feature = "serde")] - impl Serialize for $cls { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - (self.lightness, self.chroma, self.hue, self.alpha).serialize(serializer) - } - } - - #[cfg(feature = "serde")] - impl<'de> Deserialize<'de> for $cls { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let (lightness, chroma, hue, alpha) = Deserialize::deserialize(deserializer)?; - Ok(Self::new(lightness, chroma, hue, alpha)) - } - } - impl ToCss for $cls { fn to_css(&self, dest: &mut W) -> fmt::Result where @@ -905,6 +797,7 @@ impl_lch_like!(Oklch, "oklch"); /// A color specified by the color() function. /// #[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct ColorFunction { /// The color space for this color. pub color_space: PredefinedColorSpace, @@ -966,6 +859,8 @@ impl ToCss for ColorFunction { /// /// #[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(tag = "type"))] pub enum Color { /// The 'currentcolor' keyword. CurrentColor, diff --git a/color/tests.rs b/color/tests.rs index babb076b..d8d8e5d4 100644 --- a/color/tests.rs +++ b/color/tests.rs @@ -3,25 +3,21 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use super::*; -use crate::{ColorParser, PredefinedColorSpace, Color, RgbaLegacy}; -use cssparser::{Parser, ParserInput}; -use serde_json::{self, json, Value}; +use cssparser::ParserInput; +use serde_json::{json, Value}; fn almost_equals(a: &Value, b: &Value) -> bool { match (a, b) { - (&Value::Number(ref a), &Value::Number(ref b)) => { + (Value::Number(a), Value::Number(b)) => { let a = a.as_f64().unwrap(); let b = b.as_f64().unwrap(); (a - b).abs() <= a.abs() * 1e-6 } (&Value::Bool(a), &Value::Bool(b)) => a == b, - (&Value::String(ref a), &Value::String(ref b)) => a == b, - (&Value::Array(ref a), &Value::Array(ref b)) => { - a.len() == b.len() - && a.iter() - .zip(b.iter()) - .all(|(ref a, ref b)| almost_equals(*a, *b)) + (Value::String(a), Value::String(b)) => a == b, + (Value::Array(a), Value::Array(b)) => { + a.len() == b.len() && a.iter().zip(b.iter()).all(|(a, b)| almost_equals(a, b)) } (&Value::Object(_), &Value::Object(_)) => panic!("Not implemented"), (&Value::Null, &Value::Null) => true, @@ -43,8 +39,7 @@ fn assert_json_eq(results: Value, expected: Value, message: &str) { } } - -fn run_raw_json_tests ()>(json_data: &str, run: F) { +fn run_raw_json_tests(json_data: &str, run: F) { let items = match serde_json::from_str(json_data) { Ok(Value::Array(items)) => items, other => panic!("Invalid JSON: {:?}", other), @@ -92,11 +87,14 @@ fn color3() { #[cfg_attr(all(miri, feature = "skip_long_tests"), ignore)] #[test] fn color3_hsl() { - run_color_tests(include_str!("../src/css-parsing-tests/color3_hsl.json"), |c| { - c.ok() - .map(|v| v.to_css_string().to_json()) - .unwrap_or(Value::Null) - }) + run_color_tests( + include_str!("../src/css-parsing-tests/color3_hsl.json"), + |c| { + c.ok() + .map(|v| v.to_css_string().to_json()) + .unwrap_or(Value::Null) + }, + ) } /// color3_keywords.json is different: R, G and B are in 0..255 rather than 0..1 @@ -115,11 +113,14 @@ fn color3_keywords() { #[cfg_attr(all(miri, feature = "skip_long_tests"), ignore)] #[test] fn color4_hwb() { - run_color_tests(include_str!("../src/css-parsing-tests/color4_hwb.json"), |c| { - c.ok() - .map(|v| v.to_css_string().to_json()) - .unwrap_or(Value::Null) - }) + run_color_tests( + include_str!("../src/css-parsing-tests/color4_hwb.json"), + |c| { + c.ok() + .map(|v| v.to_css_string().to_json()) + .unwrap_or(Value::Null) + }, + ) } #[cfg_attr(all(miri, feature = "skip_long_tests"), ignore)] @@ -214,7 +215,7 @@ impl ToJson for Color { Color::Oklab(ref c) => json!([c.lightness, c.a, c.b, c.alpha]), Color::Oklch(ref c) => json!([c.lightness, c.chroma, c.hue, c.alpha]), Color::ColorFunction(ref c) => { - json!([c.color_space.as_str(), c.c1, c.c2, c.c3, c.alpha]) + json!([c.color_space.to_css_string(), c.c1, c.c2, c.c3, c.alpha]) } } } @@ -355,7 +356,7 @@ fn generic_parser() { ]; for (input, expected) in TESTS { - let mut input = ParserInput::new(*input); + let mut input = ParserInput::new(input); let mut input = Parser::new(&mut input); let actual: OutputType = parse_color_with(&TestColorParser, &mut input).unwrap(); diff --git a/macros/Cargo.toml b/macros/Cargo.toml index 1c225bf2..494705f9 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -13,5 +13,5 @@ path = "lib.rs" proc-macro = true [dependencies] -quote = "1" +quote = "1.0.29" syn = {version = "2", features = ["full", "extra-traits"]} diff --git a/macros/lib.rs b/macros/lib.rs index 4354cc8e..8b40bd4c 100644 --- a/macros/lib.rs +++ b/macros/lib.rs @@ -48,9 +48,7 @@ fn get_byte_from_lit(lit: &syn::Lit) -> u8 { fn get_byte_from_expr_lit(expr: &syn::Expr) -> u8 { match *expr { - syn::Expr::Lit(syn::ExprLit { ref lit, .. }) => { - get_byte_from_lit(lit) - } + syn::Expr::Lit(syn::ExprLit { ref lit, .. }) => get_byte_from_lit(lit), _ => unreachable!(), } } @@ -63,15 +61,17 @@ fn parse_pat_to_table<'a>( table: &mut [u8; 256], ) { match pat { - &syn::Pat::Lit(syn::PatLit { ref lit, .. }) => { + syn::Pat::Lit(syn::PatLit { ref lit, .. }) => { let value = get_byte_from_lit(lit); if table[value as usize] == 0 { table[value as usize] = case_id; } } - &syn::Pat::Range(syn::PatRange { ref start, ref end, .. }) => { - let lo = get_byte_from_expr_lit(&start.as_ref().unwrap()); - let hi = get_byte_from_expr_lit(&end.as_ref().unwrap()); + syn::Pat::Range(syn::PatRange { + ref start, ref end, .. + }) => { + let lo = get_byte_from_expr_lit(start.as_ref().unwrap()); + let hi = get_byte_from_expr_lit(end.as_ref().unwrap()); for value in lo..hi { if table[value as usize] == 0 { table[value as usize] = case_id; @@ -81,14 +81,14 @@ fn parse_pat_to_table<'a>( table[hi as usize] = case_id; } } - &syn::Pat::Wild(_) => { + syn::Pat::Wild(_) => { for byte in table.iter_mut() { if *byte == 0 { *byte = case_id; } } } - &syn::Pat::Ident(syn::PatIdent { ref ident, .. }) => { + syn::Pat::Ident(syn::PatIdent { ref ident, .. }) => { assert_eq!(*wildcard, None); *wildcard = Some(ident); for byte in table.iter_mut() { @@ -97,7 +97,7 @@ fn parse_pat_to_table<'a>( } } } - &syn::Pat::Or(syn::PatOr { ref cases, .. }) => { + syn::Pat::Or(syn::PatOr { ref cases, .. }) => { for case in cases { parse_pat_to_table(case, case_id, wildcard, table); } diff --git a/src/color.rs b/src/color.rs index d5f9a5c0..978936e0 100644 --- a/src/color.rs +++ b/src/color.rs @@ -14,9 +14,8 @@ /// The opaque alpha value of 1.0. pub const OPAQUE: f32 = 1.0; -use crate::ToCss; +use crate::{BasicParseError, Parser, ToCss, Token}; use std::fmt; -use std::str::FromStr; /// Clamp a 0..1 number to a 0..255 range to u8. /// @@ -76,7 +75,9 @@ pub fn serialize_color_alpha( /// A Predefined color space specified in: /// -#[derive(Clone, Copy, PartialEq, Debug)] +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(tag = "type"))] pub enum PredefinedColorSpace { /// Srgb, @@ -97,36 +98,21 @@ pub enum PredefinedColorSpace { } impl PredefinedColorSpace { - /// Returns the string value of the predefined color space. - pub fn as_str(&self) -> &str { - match self { - PredefinedColorSpace::Srgb => "srgb", - PredefinedColorSpace::SrgbLinear => "srgb-linear", - PredefinedColorSpace::DisplayP3 => "display-p3", - PredefinedColorSpace::A98Rgb => "a98-rgb", - PredefinedColorSpace::ProphotoRgb => "prophoto-rgb", - PredefinedColorSpace::Rec2020 => "rec2020", - PredefinedColorSpace::XyzD50 => "xyz-d50", - PredefinedColorSpace::XyzD65 => "xyz-d65", - } - } -} - -impl FromStr for PredefinedColorSpace { - type Err = (); + /// Parse a PredefinedColorSpace from the given input. + pub fn parse<'i>(input: &mut Parser<'i, '_>) -> Result> { + let location = input.current_source_location(); - fn from_str(s: &str) -> Result { - Ok(match_ignore_ascii_case! { s, - "srgb" => PredefinedColorSpace::Srgb, - "srgb-linear" => PredefinedColorSpace::SrgbLinear, - "display-p3" => PredefinedColorSpace::DisplayP3, - "a98-rgb" => PredefinedColorSpace::A98Rgb, - "prophoto-rgb" => PredefinedColorSpace::ProphotoRgb, - "rec2020" => PredefinedColorSpace::Rec2020, - "xyz-d50" => PredefinedColorSpace::XyzD50, - "xyz" | "xyz-d65" => PredefinedColorSpace::XyzD65, - - _ => return Err(()), + let ident = input.expect_ident()?; + Ok(match_ignore_ascii_case! { ident, + "srgb" => Self::Srgb, + "srgb-linear" => Self::SrgbLinear, + "display-p3" => Self::DisplayP3, + "a98-rgb" => Self::A98Rgb, + "prophoto-rgb" => Self::ProphotoRgb, + "rec2020" => Self::Rec2020, + "xyz-d50" => Self::XyzD50, + "xyz" | "xyz-d65" => Self::XyzD65, + _ => return Err(location.new_basic_unexpected_token_error(Token::Ident(ident.clone()))), }) } } @@ -136,11 +122,21 @@ impl ToCss for PredefinedColorSpace { where W: fmt::Write, { - dest.write_str(self.as_str()) + dest.write_str(match self { + Self::Srgb => "srgb", + Self::SrgbLinear => "srgb-linear", + Self::DisplayP3 => "display-p3", + Self::A98Rgb => "a98-rgb", + Self::ProphotoRgb => "prophoto-rgb", + Self::Rec2020 => "rec2020", + Self::XyzD50 => "xyz-d50", + Self::XyzD65 => "xyz-d65", + }) } } /// Parse a color hash, without the leading '#' character. +#[allow(clippy::result_unit_err)] #[inline] pub fn parse_hash_color(value: &[u8]) -> Result<(u8, u8, u8, f32), ()> { Ok(match value.len() { @@ -328,6 +324,7 @@ ascii_case_insensitive_phf_map! { /// Returns the named color with the given name. /// +#[allow(clippy::result_unit_err)] #[inline] pub fn parse_named_color(ident: &str) -> Result<(u8, u8, u8), ()> { named_colors::get(ident).copied().ok_or(()) diff --git a/src/cow_rc_str.rs b/src/cow_rc_str.rs index ecf14a0a..03631f47 100644 --- a/src/cow_rc_str.rs +++ b/src/cow_rc_str.rs @@ -4,7 +4,7 @@ use std::borrow::{Borrow, Cow}; use std::rc::Rc; -use std::{cmp, fmt, hash, marker, mem, ops, slice, str, ptr}; +use std::{cmp, fmt, hash, marker, mem, ops, ptr, slice, str}; /// A string that is either shared (heap-allocated and reference-counted) or borrowed. /// @@ -23,9 +23,9 @@ pub struct CowRcStr<'a> { phantom: marker::PhantomData>>, } -fn _static_assert_same_size<'a>() { +fn _static_assert_same_size() { // "Instantiate" the generic function without calling it. - let _ = mem::transmute::, Option>>; + let _ = mem::transmute::, Option>>; } impl<'a> From> for CowRcStr<'a> { @@ -51,7 +51,7 @@ impl<'a> From<&'a str> for CowRcStr<'a> { } } -impl<'a> From for CowRcStr<'a> { +impl From for CowRcStr<'_> { #[inline] fn from(s: String) -> Self { CowRcStr::from_rc(Rc::new(s)) @@ -84,7 +84,7 @@ impl<'a> CowRcStr<'a> { } } -impl<'a> Clone for CowRcStr<'a> { +impl Clone for CowRcStr<'_> { #[inline] fn clone(&self) -> Self { match self.unpack() { @@ -99,7 +99,7 @@ impl<'a> Clone for CowRcStr<'a> { } } -impl<'a> Drop for CowRcStr<'a> { +impl Drop for CowRcStr<'_> { #[inline] fn drop(&mut self) { if let Err(ptr) = self.unpack() { @@ -108,7 +108,7 @@ impl<'a> Drop for CowRcStr<'a> { } } -impl<'a> ops::Deref for CowRcStr<'a> { +impl ops::Deref for CowRcStr<'_> { type Target = str; #[inline] @@ -119,65 +119,65 @@ impl<'a> ops::Deref for CowRcStr<'a> { // Boilerplate / trivial impls below. -impl<'a> AsRef for CowRcStr<'a> { +impl AsRef for CowRcStr<'_> { #[inline] fn as_ref(&self) -> &str { self } } -impl<'a> Borrow for CowRcStr<'a> { +impl Borrow for CowRcStr<'_> { #[inline] fn borrow(&self) -> &str { self } } -impl<'a> Default for CowRcStr<'a> { +impl Default for CowRcStr<'_> { #[inline] fn default() -> Self { Self::from("") } } -impl<'a> hash::Hash for CowRcStr<'a> { +impl hash::Hash for CowRcStr<'_> { #[inline] fn hash(&self, hasher: &mut H) { str::hash(self, hasher) } } -impl<'a, T: AsRef> PartialEq for CowRcStr<'a> { +impl> PartialEq for CowRcStr<'_> { #[inline] fn eq(&self, other: &T) -> bool { str::eq(self, other.as_ref()) } } -impl<'a, T: AsRef> PartialOrd for CowRcStr<'a> { +impl> PartialOrd for CowRcStr<'_> { #[inline] fn partial_cmp(&self, other: &T) -> Option { str::partial_cmp(self, other.as_ref()) } } -impl<'a> Eq for CowRcStr<'a> {} +impl Eq for CowRcStr<'_> {} -impl<'a> Ord for CowRcStr<'a> { +impl Ord for CowRcStr<'_> { #[inline] fn cmp(&self, other: &Self) -> cmp::Ordering { str::cmp(self, other) } } -impl<'a> fmt::Display for CowRcStr<'a> { +impl fmt::Display for CowRcStr<'_> { #[inline] fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { str::fmt(self, formatter) } } -impl<'a> fmt::Debug for CowRcStr<'a> { +impl fmt::Debug for CowRcStr<'_> { #[inline] fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { str::fmt(self, formatter) diff --git a/src/css-parsing-tests/urange.json b/src/css-parsing-tests/urange.json index 857d1d62..4dcb529c 100644 --- a/src/css-parsing-tests/urange.json +++ b/src/css-parsing-tests/urange.json @@ -84,6 +84,11 @@ null, null, null +], + +"U+26F9200D2640, U+10000-26F9200D2640", [ + null, + null ] ] diff --git a/src/from_bytes.rs b/src/from_bytes.rs index 78a56d3e..7d9d2c76 100644 --- a/src/from_bytes.rs +++ b/src/from_bytes.rs @@ -24,9 +24,9 @@ pub trait EncodingSupport { /// /// * `css_bytes`: A byte string. /// * `protocol_encoding`: The encoding label, if any, defined by HTTP or equivalent protocol. -/// (e.g. via the `charset` parameter of the `Content-Type` header.) +/// (e.g. via the `charset` parameter of the `Content-Type` header.) /// * `environment_encoding`: An optional `Encoding` object for the [environment encoding] -/// (https://drafts.csswg.org/css-syntax/#environment-encoding), if any. +/// (https://drafts.csswg.org/css-syntax/#environment-encoding), if any. /// /// Returns the encoding to use. pub fn stylesheet_encoding( diff --git a/src/macros.rs b/src/macros.rs index fc4b77a1..67d83658 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -182,7 +182,7 @@ pub fn _cssparser_internal_to_lowercase<'a>( let input_bytes = unsafe { &*(input.as_bytes() as *const [u8] as *const [MaybeUninit]) }; - buffer.copy_from_slice(&*input_bytes); + buffer.copy_from_slice(input_bytes); // Same as above re layout, plus these bytes have been initialized: let buffer = unsafe { &mut *(buffer as *mut [MaybeUninit] as *mut [u8]) }; @@ -195,7 +195,7 @@ pub fn _cssparser_internal_to_lowercase<'a>( } Some( - match input.bytes().position(|byte| matches!(byte, b'A'..=b'Z')) { + match input.bytes().position(|byte| byte.is_ascii_uppercase()) { Some(first_uppercase) => make_ascii_lowercase(buffer, input, first_uppercase), // common case: input is already lower-case None => input, diff --git a/src/nth.rs b/src/nth.rs index 518de4d9..4fe5a6bc 100644 --- a/src/nth.rs +++ b/src/nth.rs @@ -7,8 +7,8 @@ use super::{BasicParseError, Parser, ParserInput, Token}; /// Parse the *An+B* notation, as found in the `:nth-child()` selector. /// The input is typically the arguments of a function, /// in which case the caller needs to check if the arguments’ parser is exhausted. -/// Return `Ok((A, B))`, or `Err(())` for a syntax error. -pub fn parse_nth<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(i32, i32), BasicParseError<'i>> { +/// Return `Ok((A, B))`, or an `Err(..)` for a syntax error. +pub fn parse_nth<'i>(input: &mut Parser<'i, '_>) -> Result<(i32, i32), BasicParseError<'i>> { match *input.next()? { Token::Number { int_value: Some(b), .. @@ -22,7 +22,7 @@ pub fn parse_nth<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(i32, i32), Basic unit, "n" => Ok(parse_b(input, a)?), "n-" => Ok(parse_signless_b(input, a, -1)?), - _ => match parse_n_dash_digits(&*unit) { + _ => match parse_n_dash_digits(unit) { Ok(b) => Ok((a, b)), Err(()) => { let unit = unit.clone(); @@ -40,8 +40,8 @@ pub fn parse_nth<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(i32, i32), Basic "n-" => Ok(parse_signless_b(input, 1, -1)?), "-n-" => Ok(parse_signless_b(input, -1, -1)?), _ => { - let (slice, a) = if value.starts_with("-") { - (&value[1..], -1) + let (slice, a) = if let Some(stripped) = value.strip_prefix('-') { + (stripped, -1) } else { (&**value, 1) }; @@ -81,7 +81,7 @@ pub fn parse_nth<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(i32, i32), Basic } } -fn parse_b<'i, 't>(input: &mut Parser<'i, 't>, a: i32) -> Result<(i32, i32), BasicParseError<'i>> { +fn parse_b<'i>(input: &mut Parser<'i, '_>, a: i32) -> Result<(i32, i32), BasicParseError<'i>> { let start = input.state(); match input.next() { Ok(&Token::Delim('+')) => parse_signless_b(input, a, 1), @@ -98,8 +98,8 @@ fn parse_b<'i, 't>(input: &mut Parser<'i, 't>, a: i32) -> Result<(i32, i32), Bas } } -fn parse_signless_b<'i, 't>( - input: &mut Parser<'i, 't>, +fn parse_signless_b<'i>( + input: &mut Parser<'i, '_>, a: i32, b_sign: i32, ) -> Result<(i32, i32), BasicParseError<'i>> { @@ -118,7 +118,7 @@ fn parse_n_dash_digits(string: &str) -> Result { let bytes = string.as_bytes(); if bytes.len() >= 3 && bytes[..2].eq_ignore_ascii_case(b"n-") - && bytes[2..].iter().all(|&c| matches!(c, b'0'..=b'9')) + && bytes[2..].iter().all(|&c| c.is_ascii_digit()) { Ok(parse_number_saturate(&string[1..]).unwrap()) // Include the minus sign } else { diff --git a/src/parser.rs b/src/parser.rs index dd7777a2..aabeea6a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -53,7 +53,7 @@ impl ParserState { /// /// Would need to scan the whole {} block to find a semicolon, only for parsing getting restarted /// as a qualified rule later. -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ParseUntilErrorBehavior { /// Consume until we see the relevant delimiter or the end of the stream. Consume, @@ -76,7 +76,7 @@ pub enum BasicParseErrorKind<'i> { QualifiedRuleInvalid, } -impl<'i> fmt::Display for BasicParseErrorKind<'i> { +impl fmt::Display for BasicParseErrorKind<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { BasicParseErrorKind::UnexpectedToken(token) => { @@ -116,18 +116,30 @@ impl<'i, T> From> for ParseError<'i, T> { impl SourceLocation { /// Create a new BasicParseError at this location for an unexpected token #[inline] - pub fn new_basic_unexpected_token_error<'i>(self, token: Token<'i>) -> BasicParseError<'i> { + pub fn new_basic_unexpected_token_error(self, token: Token<'_>) -> BasicParseError<'_> { + self.new_basic_error(BasicParseErrorKind::UnexpectedToken(token)) + } + + /// Create a new BasicParseError at this location + #[inline] + pub fn new_basic_error(self, kind: BasicParseErrorKind<'_>) -> BasicParseError<'_> { BasicParseError { - kind: BasicParseErrorKind::UnexpectedToken(token), + kind, location: self, } } /// Create a new ParseError at this location for an unexpected token #[inline] - pub fn new_unexpected_token_error<'i, E>(self, token: Token<'i>) -> ParseError<'i, E> { + pub fn new_unexpected_token_error(self, token: Token<'_>) -> ParseError<'_, E> { + self.new_error(BasicParseErrorKind::UnexpectedToken(token)) + } + + /// Create a new basic ParseError at the current location + #[inline] + pub fn new_error(self, kind: BasicParseErrorKind<'_>) -> ParseError<'_, E> { ParseError { - kind: ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(token)), + kind: ParseErrorKind::Basic(kind), location: self, } } @@ -164,7 +176,7 @@ impl<'i, T> ParseErrorKind<'i, T> { } } -impl<'i, E: fmt::Display> fmt::Display for ParseErrorKind<'i, E> { +impl fmt::Display for ParseErrorKind<'_, E> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ParseErrorKind::Basic(ref basic) => basic.fmt(f), @@ -206,13 +218,13 @@ impl<'i, T> ParseError<'i, T> { } } -impl<'i, E: fmt::Display> fmt::Display for ParseError<'i, E> { +impl fmt::Display for ParseError<'_, E> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.kind.fmt(f) } } -impl<'i, E: fmt::Display + fmt::Debug> std::error::Error for ParseError<'i, E> {} +impl std::error::Error for ParseError<'_, E> {} /// The owned input for a parser. pub struct ParserInput<'i> { @@ -348,10 +360,8 @@ impl Delimiters { table }; - match byte { - None => Delimiter::None, - Some(b) => TABLE[b as usize], - } + assert_eq!(TABLE[0], Delimiter::None); + TABLE[byte.unwrap_or(0) as usize] } } @@ -450,19 +460,13 @@ impl<'i: 't, 't> Parser<'i, 't> { /// Create a new BasicParseError at the current location #[inline] pub fn new_basic_error(&self, kind: BasicParseErrorKind<'i>) -> BasicParseError<'i> { - BasicParseError { - kind, - location: self.current_source_location(), - } + self.current_source_location().new_basic_error(kind) } /// Create a new basic ParseError at the current location #[inline] pub fn new_error(&self, kind: BasicParseErrorKind<'i>) -> ParseError<'i, E> { - ParseError { - kind: ParseErrorKind::Basic(kind), - location: self.current_source_location(), - } + self.current_source_location().new_error(kind) } /// Create a new custom BasicParseError at the current location @@ -606,6 +610,7 @@ impl<'i: 't, 't> Parser<'i, 't> { /// See the `Parser::parse_nested_block` method to parse the content of functions or blocks. /// /// This only returns a closing token when it is unmatched (and therefore an error). + #[allow(clippy::should_implement_trait)] pub fn next(&mut self) -> Result<&Token<'i>, BasicParseError<'i>> { self.skip_whitespace(); self.next_including_whitespace_and_comments() @@ -652,9 +657,8 @@ impl<'i: 't, 't> Parser<'i, 't> { let token = if using_cached_token { let cached_token = self.input.cached_token.as_ref().unwrap(); self.input.tokenizer.reset(&cached_token.end_state); - match cached_token.token { - Token::Function(ref name) => self.input.tokenizer.see_function(name), - _ => {} + if let Token::Function(ref name) = cached_token.token { + self.input.tokenizer.see_function(name) } &cached_token.token } else { @@ -678,7 +682,7 @@ impl<'i: 't, 't> Parser<'i, 't> { } /// Have the given closure parse something, then check the the input is exhausted. - /// The result is overridden to `Err(())` if some input remains. + /// The result is overridden to an `Err(..)` if some input remains. /// /// This can help tell e.g. `color: green;` from `color: green 4px;` #[inline] @@ -699,7 +703,7 @@ impl<'i: 't, 't> Parser<'i, 't> { /// /// Successful results are accumulated in a vector. /// - /// This method returns `Err(())` the first time that a closure call does, + /// This method returns an`Err(..)` the first time that a closure call does, /// or if a closure call leaves some input before the next comma or the end /// of the input. #[inline] @@ -748,7 +752,7 @@ impl<'i: 't, 't> Parser<'i, 't> { match self.parse_until_before(Delimiter::Comma, &mut parse_one) { Ok(v) => values.push(v), Err(e) if !ignore_errors => return Err(e), - Err(_) => {}, + Err(_) => {} } match self.next() { Err(_) => return Ok(values), @@ -768,7 +772,7 @@ impl<'i: 't, 't> Parser<'i, 't> { /// The given closure is called with a "delimited" parser /// that stops at the end of the block or function (at the matching closing token). /// - /// The result is overridden to `Err(())` if the closure leaves some input before that point. + /// The result is overridden to an `Err(..)` if the closure leaves some input before that point. #[inline] pub fn parse_nested_block(&mut self, parse: F) -> Result> where @@ -784,7 +788,7 @@ impl<'i: 't, 't> Parser<'i, 't> { /// that stops before the first character at this block/function nesting level /// that matches the given set of delimiters, or at the end of the input. /// - /// The result is overridden to `Err(())` if the closure leaves some input before that point. + /// The result is overridden to an `Err(..)` if the closure leaves some input before that point. #[inline] pub fn parse_until_before( &mut self, @@ -835,7 +839,7 @@ impl<'i: 't, 't> Parser<'i, 't> { /// expect_ident, but clone the CowRcStr #[inline] pub fn expect_ident_cloned(&mut self) -> Result, BasicParseError<'i>> { - self.expect_ident().map(|s| s.clone()) + self.expect_ident().cloned() } /// Parse a whose unescaped value is an ASCII-insensitive match for the given value. @@ -860,7 +864,7 @@ impl<'i: 't, 't> Parser<'i, 't> { /// expect_string, but clone the CowRcStr #[inline] pub fn expect_string_cloned(&mut self) -> Result, BasicParseError<'i>> { - self.expect_string().map(|s| s.clone()) + self.expect_string().cloned() } /// Parse either a or a , and return the unescaped value. @@ -879,7 +883,7 @@ impl<'i: 't, 't> Parser<'i, 't> { Token::UnquotedUrl(ref value) => Ok(value.clone()), Token::Function(ref name) if name.eq_ignore_ascii_case("url") => { self.parse_nested_block(|input| { - input.expect_string().map_err(Into::into).map(|s| s.clone()) + input.expect_string().map_err(Into::into).cloned() }) .map_err(ParseError::<()>::basic) } @@ -894,7 +898,7 @@ impl<'i: 't, 't> Parser<'i, 't> { Token::QuotedString(ref value) => Ok(value.clone()), Token::Function(ref name) if name.eq_ignore_ascii_case("url") => { self.parse_nested_block(|input| { - input.expect_string().map_err(Into::into).map(|s| s.clone()) + input.expect_string().map_err(Into::into).cloned() }) .map_err(ParseError::<()>::basic) } diff --git a/src/rules_and_declarations.rs b/src/rules_and_declarations.rs index fb33a7d0..bdaef077 100644 --- a/src/rules_and_declarations.rs +++ b/src/rules_and_declarations.rs @@ -4,9 +4,7 @@ // https://drafts.csswg.org/css-syntax/#parsing -use super::{ - BasicParseError, BasicParseErrorKind, Delimiter, Delimiters, ParseError, Parser, Token, -}; +use super::{BasicParseError, BasicParseErrorKind, Delimiter, ParseError, Parser, Token}; use crate::cow_rc_str::CowRcStr; use crate::parser::{parse_nested_block, parse_until_after, ParseUntilErrorBehavior, ParserState}; @@ -14,7 +12,7 @@ use crate::parser::{parse_nested_block, parse_until_after, ParseUntilErrorBehavi /// /// Typical usage is `input.try_parse(parse_important).is_ok()` /// at the end of a `DeclarationParser::parse_value` implementation. -pub fn parse_important<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), BasicParseError<'i>> { +pub fn parse_important<'i>(input: &mut Parser<'i, '_>) -> Result<(), BasicParseError<'i>> { input.expect_delim('!')?; input.expect_ident_matching("important") } @@ -34,7 +32,7 @@ pub trait DeclarationParser<'i> { /// /// Return the finished representation for the declaration /// as returned by `DeclarationListParser::next`, - /// or `Err(())` to ignore the entire declaration as invalid. + /// or an `Err(..)` to ignore the entire declaration as invalid. /// /// Declaration name matching should be case-insensitive in the ASCII range. /// This can be done with `std::ascii::Ascii::eq_ignore_ascii_case`, @@ -51,6 +49,7 @@ pub trait DeclarationParser<'i> { &mut self, name: CowRcStr<'i>, input: &mut Parser<'i, 't>, + _declaration_start: &ParserState, ) -> Result> { Err(input.new_error(BasicParseErrorKind::UnexpectedToken(Token::Ident(name)))) } @@ -78,7 +77,7 @@ pub trait AtRuleParser<'i> { /// Parse the prelude of an at-rule with the given `name`. /// /// Return the representation of the prelude and the type of at-rule, - /// or `Err(())` to ignore the entire at-rule as invalid. + /// or an `Err(..)` to ignore the entire at-rule as invalid. /// /// The prelude is the part after the at-keyword /// and before the `;` semicolon or `{ /* ... */ }` block. @@ -106,6 +105,7 @@ pub trait AtRuleParser<'i> { /// This is only called when `parse_prelude` returned `WithoutBlock`, and /// either the `;` semicolon indeed follows the prelude, or parser is at /// the end of the input. + #[allow(clippy::result_unit_err)] fn rule_without_block( &mut self, prelude: Self::Prelude, @@ -122,7 +122,7 @@ pub trait AtRuleParser<'i> { /// /// Return the finished representation of the at-rule /// as returned by `RuleListParser::next` or `DeclarationListParser::next`, - /// or `Err(())` to ignore the entire at-rule as invalid. + /// or an `Err(..)` to ignore the entire at-rule as invalid. /// /// This is only called when `parse_prelude` returned `WithBlock`, and a block /// was indeed found following the prelude. @@ -161,7 +161,7 @@ pub trait QualifiedRuleParser<'i> { /// Parse the prelude of a qualified rule. For style rules, this is as Selector list. /// /// Return the representation of the prelude, - /// or `Err(())` to ignore the entire at-rule as invalid. + /// or an `Err(..)` to ignore the entire at-rule as invalid. /// /// The prelude is the part before the `{ /* ... */ }` block. /// @@ -180,7 +180,7 @@ pub trait QualifiedRuleParser<'i> { /// /// Return the finished representation of the qualified rule /// as returned by `RuleListParser::next`, - /// or `Err(())` to ignore the entire at-rule as invalid. + /// or an `Err(..)` to ignore the entire at-rule as invalid. fn parse_block<'t>( &mut self, prelude: Self::Prelude, @@ -242,7 +242,7 @@ impl<'i, 't, 'a, P, I, E> RuleBodyParser<'i, 't, 'a, P, I, E> { } /// https://drafts.csswg.org/css-syntax/#consume-a-blocks-contents -impl<'i, 't, 'a, I, P, E: 'i> Iterator for RuleBodyParser<'i, 't, 'a, P, I, E> +impl<'i, I, P, E: 'i> Iterator for RuleBodyParser<'i, '_, '_, P, I, E> where P: RuleBodyItemParser<'i, I, E>, { @@ -253,10 +253,10 @@ where self.input.skip_whitespace(); let start = self.input.state(); match self.input.next_including_whitespace_and_comments().ok()? { - Token::CloseCurlyBracket | - Token::WhiteSpace(..) | - Token::Semicolon | - Token::Comment(..) => continue, + Token::CloseCurlyBracket + | Token::WhiteSpace(..) + | Token::Semicolon + | Token::Comment(..) => continue, Token::AtKeyword(ref name) => { let name = name.clone(); return Some(parse_at_rule(&start, name, self.input, &mut *self.parser)); @@ -280,7 +280,7 @@ where error_behavior, |input| { input.expect_colon()?; - parser.parse_value(name, input) + parser.parse_value(name, input, &start) }, ) }; @@ -292,9 +292,9 @@ where &start, self.input, &mut *self.parser, - Delimiter::Semicolon | Delimiter::CurlyBracketBlock, + /* nested = */ true, ) { - return Some(Ok(qual)) + return Some(Ok(qual)); } } @@ -303,12 +303,8 @@ where token => { let result = if self.parser.parse_qualified() { self.input.reset(&start); - let delimiters = if self.parser.parse_declarations() { - Delimiter::Semicolon | Delimiter::CurlyBracketBlock - } else { - Delimiter::CurlyBracketBlock - }; - parse_qualified_rule(&start, self.input, &mut *self.parser, delimiters) + let nested = self.parser.parse_declarations(); + parse_qualified_rule(&start, self.input, &mut *self.parser, nested) } else { let token = token.clone(); self.input.parse_until_after(Delimiter::Semicolon, |_| { @@ -353,8 +349,8 @@ where } } -/// `RuleListParser` is an iterator that yields `Ok(_)` for a rule or `Err(())` for an invalid one. -impl<'i, 't, 'a, R, P, E: 'i> Iterator for StyleSheetParser<'i, 't, 'a, P> +/// `RuleListParser` is an iterator that yields `Ok(_)` for a rule or an `Err(..)` for an invalid one. +impl<'i, R, P, E: 'i> Iterator for StyleSheetParser<'i, '_, '_, P> where P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E> + AtRuleParser<'i, AtRule = R, Error = E>, @@ -367,7 +363,7 @@ where let start = self.input.state(); let at_keyword = match self.input.next_byte()? { b'@' => match self.input.next_including_whitespace_and_comments() { - Ok(&Token::AtKeyword(ref name)) => Some(name.clone()), + Ok(Token::AtKeyword(name)) => Some(name.clone()), _ => { self.input.reset(&start); None @@ -397,7 +393,7 @@ where &start, self.input, &mut *self.parser, - Delimiter::CurlyBracketBlock, + /* nested = */ false, ); return Some(result.map_err(|e| (e, self.input.slice_from(start.position())))); } @@ -413,12 +409,13 @@ pub fn parse_one_declaration<'i, 't, P, E>( where P: DeclarationParser<'i, Error = E>, { + let start = input.state(); let start_position = input.position(); input .parse_entirely(|input| { let name = input.expect_ident()?.clone(); input.expect_colon()?; - parser.parse_value(name, input) + parser.parse_value(name, input, &start) }) .map_err(|e| (e, input.slice_from(start_position))) } @@ -450,7 +447,7 @@ where if let Some(name) = at_keyword { parse_at_rule(&start, name, input, parser).map_err(|e| e.0) } else { - parse_qualified_rule(&start, input, parser, Delimiter::CurlyBracketBlock) + parse_qualified_rule(&start, input, parser, /* nested = */ false) } }) } @@ -490,18 +487,54 @@ where } } +// If the first two non- values of rule’s prelude are an whose +// value starts with "--" followed by a , then... +fn looks_like_a_custom_property(input: &mut Parser) -> bool { + let ident = match input.expect_ident() { + Ok(i) => i, + Err(..) => return false, + }; + ident.starts_with("--") && input.expect_colon().is_ok() +} + +// https://drafts.csswg.org/css-syntax/#consume-a-qualified-rule fn parse_qualified_rule<'i, 't, P, E>( start: &ParserState, input: &mut Parser<'i, 't>, parser: &mut P, - delimiters: Delimiters, + nested: bool, ) -> Result<

>::QualifiedRule, ParseError<'i, E>> where P: QualifiedRuleParser<'i, Error = E>, { - let prelude = input.parse_until_before(delimiters, |input| parser.parse_prelude(input)); + input.skip_whitespace(); + let prelude = { + let state = input.state(); + if looks_like_a_custom_property(input) { + // If nested is true, consume the remnants of a bad declaration from input, with + // nested set to true, and return nothing. + // If nested is false, consume a block from input, and return nothing. + let delimiters = if nested { + Delimiter::Semicolon + } else { + Delimiter::CurlyBracketBlock + }; + let _: Result<(), ParseError<()>> = input.parse_until_after(delimiters, |_| Ok(())); + return Err(state + .source_location() + .new_error(BasicParseErrorKind::QualifiedRuleInvalid)); + } + let delimiters = if nested { + Delimiter::Semicolon | Delimiter::CurlyBracketBlock + } else { + Delimiter::CurlyBracketBlock + }; + input.reset(&state); + input.parse_until_before(delimiters, |input| parser.parse_prelude(input)) + }; + input.expect_curly_bracket_block()?; // Do this here so that we consume the `{` even if the prelude is `Err`. let prelude = prelude?; - parse_nested_block(input, |input| parser.parse_block(prelude, &start, input)) + parse_nested_block(input, |input| parser.parse_block(prelude, start, input)) } diff --git a/src/serializer.rs b/src/serializer.rs index 19f01456..6696a622 100644 --- a/src/serializer.rs +++ b/src/serializer.rs @@ -3,8 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::match_byte; -use dtoa_short::{self, Notation}; -use itoa; +use dtoa_short::Notation; use std::fmt::{self, Write}; use std::str; @@ -49,24 +48,23 @@ where dtoa_short::write(dest, value)? }; - if int_value.is_none() && value.fract() == 0. { - if !notation.decimal_point && !notation.scientific { - dest.write_str(".0")?; - } + if int_value.is_none() && value.fract() == 0. && !notation.decimal_point && !notation.scientific + { + dest.write_str(".0")?; } Ok(()) } -impl<'a> ToCss for Token<'a> { +impl ToCss for Token<'_> { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, { match *self { - Token::Ident(ref value) => serialize_identifier(&**value, dest)?, + Token::Ident(ref value) => serialize_identifier(value, dest)?, Token::AtKeyword(ref value) => { dest.write_str("@")?; - serialize_identifier(&**value, dest)?; + serialize_identifier(value, dest)?; } Token::Hash(ref value) => { dest.write_str("#")?; @@ -74,12 +72,12 @@ impl<'a> ToCss for Token<'a> { } Token::IDHash(ref value) => { dest.write_str("#")?; - serialize_identifier(&**value, dest)?; + serialize_identifier(value, dest)?; } - Token::QuotedString(ref value) => serialize_string(&**value, dest)?, + Token::QuotedString(ref value) => serialize_string(value, dest)?, Token::UnquotedUrl(ref value) => { dest.write_str("url(")?; - serialize_unquoted_url(&**value, dest)?; + serialize_unquoted_url(value, dest)?; dest.write_str(")")?; } Token::Delim(value) => dest.write_char(value)?, @@ -134,7 +132,7 @@ impl<'a> ToCss for Token<'a> { Token::CDC => dest.write_str("-->")?, Token::Function(ref name) => { - serialize_identifier(&**name, dest)?; + serialize_identifier(name, dest)?; dest.write_str("(")?; } Token::ParenthesisBlock => dest.write_str("(")?, @@ -167,7 +165,7 @@ fn hex_escape(ascii_byte: u8, dest: &mut W) -> fmt::Result where W: fmt::Write, { - static HEX_DIGITS: &'static [u8; 16] = b"0123456789abcdef"; + static HEX_DIGITS: &[u8; 16] = b"0123456789abcdef"; let b3; let b4; let bytes = if ascii_byte > 0x0F { @@ -179,7 +177,7 @@ where b3 = [b'\\', HEX_DIGITS[ascii_byte as usize], b' ']; &b3[..] }; - dest.write_str(unsafe { str::from_utf8_unchecked(&bytes) }) + dest.write_str(unsafe { str::from_utf8_unchecked(bytes) }) } fn char_escape(ascii_byte: u8, dest: &mut W) -> fmt::Result @@ -199,9 +197,9 @@ where return Ok(()); } - if value.starts_with("--") { + if let Some(value) = value.strip_prefix("--") { dest.write_str("--")?; - serialize_name(&value[2..], dest) + serialize_name(value, dest) } else if value == "-" { dest.write_str("\\-") } else { @@ -240,7 +238,7 @@ where dest.write_str(&value[chunk_start..i])?; if let Some(escaped) = escaped { dest.write_str(escaped)?; - } else if (b >= b'\x01' && b <= b'\x1F') || b == b'\x7F' { + } else if (b'\x01'..=b'\x1F').contains(&b) || b == b'\x7F' { hex_escape(b, dest)?; } else { char_escape(b, dest)?; @@ -313,7 +311,7 @@ where } } -impl<'a, W> fmt::Write for CssStringWriter<'a, W> +impl fmt::Write for CssStringWriter<'_, W> where W: fmt::Write, { @@ -340,7 +338,7 @@ where macro_rules! impl_tocss_for_int { ($T: ty) => { - impl<'a> ToCss for $T { + impl ToCss for $T { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, @@ -363,7 +361,7 @@ impl_tocss_for_int!(u64); macro_rules! impl_tocss_for_float { ($T: ty) => { - impl<'a> ToCss for $T { + impl ToCss for $T { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write, @@ -378,19 +376,113 @@ impl_tocss_for_float!(f32); impl_tocss_for_float!(f64); /// A category of token. See the `needs_separator_when_before` method. -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub struct TokenSerializationType(TokenSerializationTypeVariants); +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] +pub enum TokenSerializationType { + /// No token serialization type. + #[default] + Nothing, + + /// The [``](https://drafts.csswg.org/css-syntax/#whitespace-token-diagram) + /// type. + WhiteSpace, + + /// The [``](https://drafts.csswg.org/css-syntax/#at-keyword-token-diagram) + /// type, the "[``](https://drafts.csswg.org/css-syntax/#hash-token-diagram) with + /// the type flag set to 'unrestricted'" type, or the + /// "[``](https://drafts.csswg.org/css-syntax/#hash-token-diagram) with the type + /// flag set to 'id'" type. + AtKeywordOrHash, + + /// The [``](https://drafts.csswg.org/css-syntax/#number-token-diagram) type. + Number, + + /// The [``](https://drafts.csswg.org/css-syntax/#dimension-token-diagram) + /// type. + Dimension, + + /// The [``](https://drafts.csswg.org/css-syntax/#percentage-token-diagram) + /// type. + Percentage, + + /// The [``](https://drafts.csswg.org/css-syntax/#url-token-diagram) or + /// `` type. + UrlOrBadUrl, + + /// The [``](https://drafts.csswg.org/css-syntax/#function-token-diagram) type. + Function, + + /// The [``](https://drafts.csswg.org/css-syntax/#ident-token-diagram) type. + Ident, + + /// The `-->` [``](https://drafts.csswg.org/css-syntax/#CDC-token-diagram) type. + CDC, + + /// The `|=` + /// [``](https://drafts.csswg.org/css-syntax/#dash-match-token-diagram) type. + DashMatch, + + /// The `*=` + /// [``](https://drafts.csswg.org/css-syntax/#substring-match-token-diagram) + /// type. + SubstringMatch, + + /// The `<(-token>` type. + OpenParen, + + /// The `#` `` type. + DelimHash, + + /// The `@` `` type. + DelimAt, + + /// The `.` or `+` `` type. + DelimDotOrPlus, + + /// The `-` `` type. + DelimMinus, + + /// The `?` `` type. + DelimQuestion, + + /// The `$`, `^`, or `~` `` type. + DelimAssorted, + + /// The `=` `` type. + DelimEquals, + + /// The `|` `` type. + DelimBar, + + /// The `/` `` type. + DelimSlash, + + /// The `*` `` type. + DelimAsterisk, + + /// The `%` `` type. + DelimPercent, + + /// A type indicating any other token. + Other, +} + +#[cfg(feature = "malloc_size_of")] +malloc_size_of::malloc_size_of_is_0!(TokenSerializationType); impl TokenSerializationType { /// Return a value that represents the absence of a token, e.g. before the start of the input. + #[deprecated( + since = "0.32.1", + note = "use TokenSerializationType::Nothing or TokenSerializationType::default() instead" + )] pub fn nothing() -> TokenSerializationType { - TokenSerializationType(TokenSerializationTypeVariants::Nothing) + Default::default() } - /// If this value is `TokenSerializationType::nothing()`, set it to the given value instead. + /// If this value is `TokenSerializationType::Nothing`, set it to the given value instead. pub fn set_if_nothing(&mut self, new_value: TokenSerializationType) { - if self.0 == TokenSerializationTypeVariants::Nothing { - self.0 = new_value.0 + if matches!(self, TokenSerializationType::Nothing) { + *self = new_value } } @@ -404,10 +496,10 @@ impl TokenSerializationType { /// See https://github.com/w3c/csswg-drafts/issues/4088 for the /// `DelimPercent` bits. pub fn needs_separator_when_before(self, other: TokenSerializationType) -> bool { - use self::TokenSerializationTypeVariants::*; - match self.0 { + use self::TokenSerializationType::*; + match self { Ident => matches!( - other.0, + other, Ident | Function | UrlOrBadUrl @@ -419,15 +511,15 @@ impl TokenSerializationType { | OpenParen ), AtKeywordOrHash | Dimension => matches!( - other.0, + other, Ident | Function | UrlOrBadUrl | DelimMinus | Number | Percentage | Dimension | CDC ), DelimHash | DelimMinus => matches!( - other.0, + other, Ident | Function | UrlOrBadUrl | DelimMinus | Number | Percentage | Dimension ), Number => matches!( - other.0, + other, Ident | Function | UrlOrBadUrl @@ -437,11 +529,11 @@ impl TokenSerializationType { | DelimPercent | Dimension ), - DelimAt => matches!(other.0, Ident | Function | UrlOrBadUrl | DelimMinus), - DelimDotOrPlus => matches!(other.0, Number | Percentage | Dimension), - DelimAssorted | DelimAsterisk => matches!(other.0, DelimEquals), - DelimBar => matches!(other.0, DelimEquals | DelimBar | DashMatch), - DelimSlash => matches!(other.0, DelimAsterisk | SubstringMatch), + DelimAt => matches!(other, Ident | Function | UrlOrBadUrl | DelimMinus), + DelimDotOrPlus => matches!(other, Number | Percentage | Dimension), + DelimAssorted | DelimAsterisk => matches!(other, DelimEquals), + DelimBar => matches!(other, DelimEquals | DelimBar | DashMatch), + DelimSlash => matches!(other, DelimAsterisk | SubstringMatch), Nothing | WhiteSpace | Percentage | UrlOrBadUrl | Function | CDC | OpenParen | DashMatch | SubstringMatch | DelimQuestion | DelimEquals | DelimPercent | Other => { false @@ -450,43 +542,14 @@ impl TokenSerializationType { } } -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -enum TokenSerializationTypeVariants { - Nothing, - WhiteSpace, - AtKeywordOrHash, - Number, - Dimension, - Percentage, - UrlOrBadUrl, - Function, - Ident, - CDC, - DashMatch, - SubstringMatch, - OpenParen, // '(' - DelimHash, // '#' - DelimAt, // '@' - DelimDotOrPlus, // '.', '+' - DelimMinus, // '-' - DelimQuestion, // '?' - DelimAssorted, // '$', '^', '~' - DelimEquals, // '=' - DelimBar, // '|' - DelimSlash, // '/' - DelimAsterisk, // '*' - DelimPercent, // '%' - Other, // anything else -} - -impl<'a> Token<'a> { +impl Token<'_> { /// Categorize a token into a type that determines when `/**/` needs to be inserted /// between two tokens when serialized next to each other without whitespace in between. /// /// See the `TokenSerializationType::needs_separator_when_before` method. pub fn serialization_type(&self) -> TokenSerializationType { - use self::TokenSerializationTypeVariants::*; - TokenSerializationType(match *self { + use self::TokenSerializationType::*; + match self { Token::Ident(_) => Ident, Token::AtKeyword(_) | Token::Hash(_) | Token::IDHash(_) => AtKeywordOrHash, Token::UnquotedUrl(_) | Token::BadUrl(_) => UrlOrBadUrl, @@ -526,6 +589,6 @@ impl<'a> Token<'a> { | Token::IncludeMatch | Token::PrefixMatch | Token::SuffixMatch => Other, - }) + } } } diff --git a/src/tests.rs b/src/tests.rs index d4bc5f51..3c122f0d 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -5,8 +5,7 @@ #[cfg(feature = "bench")] extern crate test; -use encoding_rs; -use serde_json::{self, json, Map, Value}; +use serde_json::{json, Map, Value}; #[cfg(feature = "bench")] use self::test::Bencher; @@ -25,25 +24,23 @@ macro_rules! JArray { } fn almost_equals(a: &Value, b: &Value) -> bool { - match (a, b) { - (&Value::Number(ref a), &Value::Number(ref b)) => { + let var_name = match (a, b) { + (Value::Number(a), Value::Number(b)) => { let a = a.as_f64().unwrap(); let b = b.as_f64().unwrap(); (a - b).abs() <= a.abs() * 1e-6 } (&Value::Bool(a), &Value::Bool(b)) => a == b, - (&Value::String(ref a), &Value::String(ref b)) => a == b, - (&Value::Array(ref a), &Value::Array(ref b)) => { - a.len() == b.len() - && a.iter() - .zip(b.iter()) - .all(|(ref a, ref b)| almost_equals(*a, *b)) + (Value::String(a), Value::String(b)) => a == b, + (Value::Array(a), Value::Array(b)) => { + a.len() == b.len() && a.iter().zip(b.iter()).all(|(a, b)| almost_equals(a, b)) } (&Value::Object(_), &Value::Object(_)) => panic!("Not implemented"), (&Value::Null, &Value::Null) => true, _ => false, - } + }; + var_name } fn normalize(json: &mut Value) { @@ -77,7 +74,7 @@ fn assert_json_eq(results: Value, mut expected: Value, message: &str) { } } -fn run_raw_json_tests ()>(json_data: &str, run: F) { +fn run_raw_json_tests(json_data: &str, run: F) { let items = match serde_json::from_str(json_data) { Ok(Value::Array(items)) => items, other => panic!("Invalid JSON: {:?}", other), @@ -242,7 +239,7 @@ fn stylesheet_from_bytes() { fn get_string<'a>(map: &'a Map, key: &str) -> Option<&'a str> { match map.get(key) { - Some(&Value::String(ref s)) => Some(s), + Some(Value::String(s)) => Some(s), Some(&Value::Null) => None, None => None, _ => panic!("Unexpected JSON"), @@ -393,7 +390,7 @@ fn unicode_range() { if input.is_exhausted() { Ok(result) } else { - while let Ok(_) = input.next() {} + while input.next().is_ok() {} Ok(None) } }); @@ -433,11 +430,9 @@ fn serializer(preserve_comments: bool) { preserve_comments: bool, ) { while let Ok(token) = if preserve_comments { - input - .next_including_whitespace_and_comments() - .map(|t| t.clone()) + input.next_including_whitespace_and_comments().cloned() } else { - input.next_including_whitespace().map(|t| t.clone()) + input.next_including_whitespace().cloned() } { let token_type = token.serialization_type(); if !preserve_comments && previous_token.needs_separator_when_before(token_type) @@ -466,7 +461,7 @@ fn serializer(preserve_comments: bool) { } let mut serialized = String::new(); write_to( - TokenSerializationType::nothing(), + TokenSerializationType::Nothing, input, &mut serialized, preserve_comments, @@ -593,8 +588,6 @@ fn line_numbers() { #[test] fn overflow() { - use std::iter::repeat; - let css = r" 2147483646 2147483647 @@ -619,7 +612,7 @@ fn overflow() { -3.402824e+38 " - .replace("{309 zeros}", &repeat('0').take(309).collect::()); + .replace("{309 zeros}", &"0".repeat(309)); let mut input = ParserInput::new(&css); let mut input = Parser::new(&mut input); @@ -637,15 +630,13 @@ fn overflow() { assert_eq!(input.expect_integer(), Ok(-2147483648)); assert_eq!(input.expect_integer(), Ok(-2147483648)); - assert_eq!(input.expect_number(), Ok(3.30282347e+38)); + assert_eq!(input.expect_number(), Ok(3.302_823_5e38)); assert_eq!(input.expect_number(), Ok(f32::MAX)); assert_eq!(input.expect_number(), Ok(f32::INFINITY)); - assert!(f32::MAX != f32::INFINITY); - assert_eq!(input.expect_number(), Ok(-3.30282347e+38)); + assert_eq!(input.expect_number(), Ok(-3.302_823_5e38)); assert_eq!(input.expect_number(), Ok(f32::MIN)); assert_eq!(input.expect_number(), Ok(f32::NEG_INFINITY)); - assert!(f32::MIN != f32::NEG_INFINITY); } #[test] @@ -782,9 +773,9 @@ where } } -impl<'a> ToJson for CowRcStr<'a> { +impl ToJson for CowRcStr<'_> { fn to_json(&self) -> Value { - let s: &str = &*self; + let s: &str = self; s.to_json() } } @@ -847,7 +838,7 @@ fn no_stack_overflow_multiple_nested_blocks() { } let mut input = ParserInput::new(&input); let mut input = Parser::new(&mut input); - while let Ok(..) = input.next() {} + while input.next().is_ok() {} } impl<'i> DeclarationParser<'i> for JsonParser { @@ -858,23 +849,22 @@ impl<'i> DeclarationParser<'i> for JsonParser { &mut self, name: CowRcStr<'i>, input: &mut Parser<'i, 't>, + _declaration_start: &ParserState, ) -> Result> { let mut value = vec![]; let mut important = false; loop { let start = input.state(); - if let Ok(mut token) = input.next_including_whitespace().map(|t| t.clone()) { + if let Ok(mut token) = input.next_including_whitespace().cloned() { // Hack to deal with css-parsing-tests assuming that // `!important` in the middle of a declaration value is OK. // This can never happen per spec // (even CSS Variables forbid top-level `!`) if token == Token::Delim('!') { input.reset(&start); - if parse_important(input).is_ok() { - if input.is_exhausted() { - important = true; - break; - } + if parse_important(input).is_ok() && input.is_exhausted() { + important = true; + break; } input.reset(&start); token = input.next_including_whitespace().unwrap().clone(); @@ -905,7 +895,7 @@ impl<'i> AtRuleParser<'i> for JsonParser { ]; match_ignore_ascii_case! { &*name, "charset" => { - Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone()).into())) + Err(input.new_error(BasicParseErrorKind::AtRuleInvalid(name.clone()))) }, _ => Ok(prelude), } @@ -957,7 +947,7 @@ impl<'i> QualifiedRuleParser<'i> for JsonParser { } } -impl<'i> RuleBodyItemParser<'i, Value, ()> for JsonParser { +impl RuleBodyItemParser<'_, Value, ()> for JsonParser { fn parse_qualified(&self) -> bool { true } @@ -968,7 +958,7 @@ impl<'i> RuleBodyItemParser<'i, Value, ()> for JsonParser { fn component_values_to_json(input: &mut Parser) -> Vec { let mut values = vec![]; - while let Ok(token) = input.next_including_whitespace().map(|t| t.clone()) { + while let Ok(token) = input.next_including_whitespace().cloned() { values.push(one_component_value_to_json(token, input)); } values @@ -978,9 +968,9 @@ fn one_component_value_to_json(token: Token, input: &mut Parser) -> Value { fn numeric(value: f32, int_value: Option, has_sign: bool) -> Vec { vec![ Token::Number { - value: value, - int_value: int_value, - has_sign: has_sign, + value, + int_value, + has_sign, } .to_css_string() .to_json(), @@ -1137,7 +1127,7 @@ fn parse_until_before_stops_at_delimiter_or_end_of_input() { let ox = ix.next(); let oy = iy.next(); assert_eq!(ox, oy); - if let Err(_) = ox { + if ox.is_err() { break; } } @@ -1223,7 +1213,7 @@ fn parse_sourcemapping_comments() { for test in tests { let mut input = ParserInput::new(test.0); let mut parser = Parser::new(&mut input); - while let Ok(_) = parser.next_including_whitespace() {} + while parser.next_including_whitespace().is_ok() {} assert_eq!(parser.current_source_map_url(), test.1); } } @@ -1247,7 +1237,7 @@ fn parse_sourceurl_comments() { for test in tests { let mut input = ParserInput::new(test.0); let mut parser = Parser::new(&mut input); - while let Ok(_) = parser.next_including_whitespace() {} + while parser.next_including_whitespace().is_ok() {} assert_eq!(parser.current_source_url(), test.1); } } @@ -1321,7 +1311,8 @@ fn utf16_columns() { break; } Err(_) => { - assert!(false); + // should this be an explicit panic instead? + unreachable!(); } Ok(_) => {} }; diff --git a/src/tokenizer.rs b/src/tokenizer.rs index a3b70063..1c85e10a 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -190,7 +190,7 @@ pub enum Token<'a> { CloseCurlyBracket, } -impl<'a> Token<'a> { +impl Token<'_> { /// Return whether this token represents a parse error. /// /// `BadUrl` and `BadString` are tokenizer-level parse errors. @@ -255,10 +255,10 @@ impl<'a> Tokenizer<'a> { #[inline] pub fn see_function(&mut self, name: &str) { - if self.var_or_env_functions == SeenStatus::LookingForThem { - if name.eq_ignore_ascii_case("var") || name.eq_ignore_ascii_case("env") { - self.var_or_env_functions = SeenStatus::SeenAtLeastOne; - } + if self.var_or_env_functions == SeenStatus::LookingForThem + && (name.eq_ignore_ascii_case("var") || name.eq_ignore_ascii_case("env")) + { + self.var_or_env_functions = SeenStatus::SeenAtLeastOne; } } @@ -322,11 +322,13 @@ impl<'a> Tokenizer<'a> { pub fn current_source_line(&self) -> &'a str { let current = self.position(); - let start = self.slice(SourcePosition(0)..current) - .rfind(|c| matches!(c, '\r' | '\n' | '\x0C')) + let start = self + .slice(SourcePosition(0)..current) + .rfind(['\r', '\n', '\x0C']) .map_or(0, |start| start + 1); - let end = self.slice(current..SourcePosition(self.input.len())) - .find(|c| matches!(c, '\r' | '\n' | '\x0C')) + let end = self + .slice(current..SourcePosition(self.input.len())) + .find(['\r', '\n', '\x0C']) .map_or(self.input.len(), |end| current.0 + end); self.slice(SourcePosition(start)..SourcePosition(end)) } @@ -424,7 +426,10 @@ impl<'a> Tokenizer<'a> { #[inline] fn next_char(&self) -> char { - unsafe { self.input.get_unchecked(self.position().0..) }.chars().next().unwrap() + unsafe { self.input.get_unchecked(self.position().0..) } + .chars() + .next() + .unwrap() } // Given that a newline has been seen, advance over the newline @@ -528,6 +533,9 @@ impl<'a> Tokenizer<'a> { #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] pub struct SourcePosition(pub(crate) usize); +#[cfg(feature = "malloc_size_of")] +malloc_size_of::malloc_size_of_is_0!(SourcePosition); + impl SourcePosition { /// Returns the current byte index in the original input. #[inline] @@ -537,7 +545,7 @@ impl SourcePosition { } /// The line and column number for a given position within the input. -#[derive(PartialEq, Eq, Debug, Clone, Copy)] +#[derive(PartialEq, Eq, Debug, Clone, Copy, Default)] pub struct SourceLocation { /// The line number, starting at 0 for the first line. pub line: u32, @@ -547,6 +555,9 @@ pub struct SourceLocation { pub column: u32, } +#[cfg(feature = "malloc_size_of")] +malloc_size_of::malloc_size_of_is_0!(SourceLocation); + fn next_token<'a>(tokenizer: &mut Tokenizer<'a>) -> Result, ()> { if tokenizer.is_eof() { return Err(()); @@ -561,11 +572,11 @@ fn next_token<'a>(tokenizer: &mut Tokenizer<'a>) -> Result, ()> { b'#' => { tokenizer.advance(1); if is_ident_start(tokenizer) { IDHash(consume_name(tokenizer)) } - else if !tokenizer.is_eof() && match tokenizer.next_byte_unchecked() { + else if !tokenizer.is_eof() && + matches!(tokenizer.next_byte_unchecked(), b'0'..=b'9' | b'-') { // Any other valid case here already resulted in IDHash. - b'0'..=b'9' | b'-' => true, - _ => false, - } { Hash(consume_name(tokenizer)) } + Hash(consume_name(tokenizer)) + } else { Delim('#') } }, b'$' => { @@ -582,11 +593,11 @@ fn next_token<'a>(tokenizer: &mut Tokenizer<'a>) -> Result, ()> { b'+' => { if ( tokenizer.has_at_least(1) - && matches!(tokenizer.byte_at(1), b'0'..=b'9') + && tokenizer.byte_at(1).is_ascii_digit() ) || ( tokenizer.has_at_least(2) && tokenizer.byte_at(1) == b'.' - && matches!(tokenizer.byte_at(2), b'0'..=b'9') + && tokenizer.byte_at(2).is_ascii_digit() ) { consume_numeric(tokenizer) } else { @@ -598,11 +609,11 @@ fn next_token<'a>(tokenizer: &mut Tokenizer<'a>) -> Result, ()> { b'-' => { if ( tokenizer.has_at_least(1) - && matches!(tokenizer.byte_at(1), b'0'..=b'9') + && tokenizer.byte_at(1).is_ascii_digit() ) || ( tokenizer.has_at_least(2) && tokenizer.byte_at(1) == b'.' - && matches!(tokenizer.byte_at(2), b'0'..=b'9') + && tokenizer.byte_at(2).is_ascii_digit() ) { consume_numeric(tokenizer) } else if tokenizer.starts_with(b"-->") { @@ -617,8 +628,7 @@ fn next_token<'a>(tokenizer: &mut Tokenizer<'a>) -> Result, ()> { }, b'.' => { if tokenizer.has_at_least(1) - && matches!(tokenizer.byte_at(1), b'0'..=b'9' - ) { + && tokenizer.byte_at(1).is_ascii_digit() { consume_numeric(tokenizer) } else { tokenizer.advance(1); @@ -716,9 +726,7 @@ fn check_for_source_map<'a>(tokenizer: &mut Tokenizer<'a>, contents: &'a str) { // If there is a source map directive, extract the URL. if contents.starts_with(directive) || contents.starts_with(directive_old) { let contents = &contents[directive.len()..]; - tokenizer.source_map_url = contents - .split(|c| c == ' ' || c == '\t' || c == '\x0C' || c == '\r' || c == '\n') - .next() + tokenizer.source_map_url = contents.split([' ', '\t', '\x0C', '\r', '\n']).next(); } let directive = "# sourceURL="; @@ -727,9 +735,7 @@ fn check_for_source_map<'a>(tokenizer: &mut Tokenizer<'a>, contents: &'a str) { // If there is a source map directive, extract the URL. if contents.starts_with(directive) || contents.starts_with(directive_old) { let contents = &contents[directive.len()..]; - tokenizer.source_url = contents - .split(|c| c == ' ' || c == '\t' || c == '\x0C' || c == '\r' || c == '\n') - .next() + tokenizer.source_url = contents.split([' ', '\t', '\x0C', '\r', '\n']).next() } } @@ -1001,7 +1007,7 @@ fn byte_to_hex_digit(b: u8) -> Option { } fn byte_to_decimal_digit(b: u8) -> Option { - if b >= b'0' && b <= b'9' { + if b.is_ascii_digit() { Some((b - b'0') as u32) } else { None @@ -1038,7 +1044,7 @@ fn consume_numeric<'a>(tokenizer: &mut Tokenizer<'a>) -> Token<'a> { let mut fractional_part: f64 = 0.; if tokenizer.has_at_least(1) && tokenizer.next_byte_unchecked() == b'.' - && matches!(tokenizer.byte_at(1), b'0'..=b'9') + && tokenizer.byte_at(1).is_ascii_digit() { is_integer = false; tokenizer.advance(1); // Consume '.' @@ -1055,32 +1061,32 @@ fn consume_numeric<'a>(tokenizer: &mut Tokenizer<'a>) -> Token<'a> { let mut value = sign * (integral_part + fractional_part); - if tokenizer.has_at_least(1) && matches!(tokenizer.next_byte_unchecked(), b'e' | b'E') { - if matches!(tokenizer.byte_at(1), b'0'..=b'9') + if tokenizer.has_at_least(1) + && matches!(tokenizer.next_byte_unchecked(), b'e' | b'E') + && (tokenizer.byte_at(1).is_ascii_digit() || (tokenizer.has_at_least(2) && matches!(tokenizer.byte_at(1), b'+' | b'-') - && matches!(tokenizer.byte_at(2), b'0'..=b'9')) - { - is_integer = false; + && tokenizer.byte_at(2).is_ascii_digit())) + { + is_integer = false; + tokenizer.advance(1); + let (has_sign, sign) = match tokenizer.next_byte_unchecked() { + b'-' => (true, -1.), + b'+' => (true, 1.), + _ => (false, 1.), + }; + if has_sign { tokenizer.advance(1); - let (has_sign, sign) = match tokenizer.next_byte_unchecked() { - b'-' => (true, -1.), - b'+' => (true, 1.), - _ => (false, 1.), - }; - if has_sign { - tokenizer.advance(1); - } - let mut exponent: f64 = 0.; - while let Some(digit) = byte_to_decimal_digit(tokenizer.next_byte_unchecked()) { - exponent = exponent * 10. + digit as f64; - tokenizer.advance(1); - if tokenizer.is_eof() { - break; - } + } + let mut exponent: f64 = 0.; + while let Some(digit) = byte_to_decimal_digit(tokenizer.next_byte_unchecked()) { + exponent = exponent * 10. + digit as f64; + tokenizer.advance(1); + if tokenizer.is_eof() { + break; } - value *= f64::powf(10., sign * exponent); } + value *= f64::powf(10., sign * exponent); } let int_value = if is_integer { @@ -1339,7 +1345,7 @@ fn consume_unquoted_url<'a>(tokenizer: &mut Tokenizer<'a>) -> Result, } // (value, number of digits up to 6) -fn consume_hex_digits<'a>(tokenizer: &mut Tokenizer<'a>) -> (u32, u32) { +fn consume_hex_digits(tokenizer: &mut Tokenizer<'_>) -> (u32, u32) { let mut value = 0; let mut digits = 0; while digits < 6 && !tokenizer.is_eof() { diff --git a/src/unicode_range.rs b/src/unicode_range.rs index b0a2017c..a4130ef0 100644 --- a/src/unicode_range.rs +++ b/src/unicode_range.rs @@ -24,7 +24,7 @@ pub struct UnicodeRange { impl UnicodeRange { /// https://drafts.csswg.org/css-syntax/#urange-syntax - pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { + pub fn parse<'i>(input: &mut Parser<'i, '_>) -> Result> { // = // u '+' '?'* | // u '?'* | @@ -57,7 +57,7 @@ impl UnicodeRange { } } -fn parse_tokens<'i, 't>(input: &mut Parser<'i, 't>) -> Result<(), BasicParseError<'i>> { +fn parse_tokens<'i>(input: &mut Parser<'i, '_>) -> Result<(), BasicParseError<'i>> { match input.next_including_whitespace()?.clone() { Token::Delim('+') => { match *input.next_including_whitespace()? { @@ -104,7 +104,7 @@ fn parse_concatenated(text: &[u8]) -> Result { Some((&b'+', text)) => text, _ => return Err(()), }; - let (first_hex_value, hex_digit_count) = consume_hex(&mut text); + let (first_hex_value, hex_digit_count) = consume_hex(&mut text, 6)?; let question_marks = consume_question_marks(&mut text); let consumed = hex_digit_count + question_marks; if consumed == 0 || consumed > 6 { @@ -123,33 +123,35 @@ fn parse_concatenated(text: &[u8]) -> Result { start: first_hex_value, end: first_hex_value, }); - } else { - if let Some((&b'-', mut text)) = text.split_first() { - let (second_hex_value, hex_digit_count) = consume_hex(&mut text); - if hex_digit_count > 0 && hex_digit_count <= 6 && text.is_empty() { - return Ok(UnicodeRange { - start: first_hex_value, - end: second_hex_value, - }); - } + } else if let Some((&b'-', mut text)) = text.split_first() { + let (second_hex_value, hex_digit_count) = consume_hex(&mut text, 6)?; + if hex_digit_count > 0 && hex_digit_count <= 6 && text.is_empty() { + return Ok(UnicodeRange { + start: first_hex_value, + end: second_hex_value, + }); } } Err(()) } -fn consume_hex(text: &mut &[u8]) -> (u32, usize) { +// Consume hex digits, but return an error if more than digit_limit are found. +fn consume_hex(text: &mut &[u8], digit_limit: usize) -> Result<(u32, usize), ()> { let mut value = 0; let mut digits = 0; while let Some((&byte, rest)) = text.split_first() { if let Some(digit_value) = (byte as char).to_digit(16) { + if digits == digit_limit { + return Err(()); + } value = value * 0x10 + digit_value; digits += 1; - *text = rest + *text = rest; } else { break; } } - (value, digits) + Ok((value, digits)) } fn consume_question_marks(text: &mut &[u8]) -> usize {