diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..1b2bc9ee --- /dev/null +++ b/src/error.rs @@ -0,0 +1,121 @@ +//! Custom error types for diagnostics +//! Includes re-exported error types from dependencies + +mod utils; + +use std::{error::Error, fmt::Display}; + +use cssparser::{BasicParseErrorKind, ParseErrorKind, Token}; +use selectors::parser::SelectorParseErrorKind; + +/// Error type that is returned when calling `Selector::parse` +#[derive(Debug, Clone)] +pub enum SelectorErrorKind<'a> { + /// A `Token` was not expected + UnexpectedToken(Token<'a>), + + /// End-Of-Line was unexpected + EndOfLine, + + /// `@` rule is invalid + InvalidAtRule(String), + + /// The body of an `@` rule is invalid + InvalidAtRuleBody, + + /// The qualified rule is invalid + QualRuleInvalid, + + /// Expected a `::` for a pseudoelement + ExpectedColonOnPseudoElement(Token<'a>), + + /// Expected an identity for a pseudoelement + ExpectedIdentityOnPseudoElement(Token<'a>), + + /// A `SelectorParseErrorKind` error that isn't really supposed to happen did + UnexpectedSelectorParseError(SelectorParseErrorKind<'a>), +} + +impl<'a> From>> for SelectorErrorKind<'a> { + fn from(original: cssparser::ParseError<'a, SelectorParseErrorKind<'a>>) -> Self { + // NOTE: This could be improved, but I dont + // exactly know how + match original.kind { + ParseErrorKind::Basic(err) => SelectorErrorKind::from(err), + ParseErrorKind::Custom(err) => SelectorErrorKind::from(err), + } + } +} + +impl<'a> From> for SelectorErrorKind<'a> { + fn from(err: BasicParseErrorKind<'a>) -> Self { + match err { + BasicParseErrorKind::UnexpectedToken(token) => Self::UnexpectedToken(token), + BasicParseErrorKind::EndOfInput => Self::EndOfLine, + BasicParseErrorKind::AtRuleInvalid(rule) => { + Self::InvalidAtRule(rule.clone().to_string()) + } + BasicParseErrorKind::AtRuleBodyInvalid => Self::InvalidAtRuleBody, + BasicParseErrorKind::QualifiedRuleInvalid => Self::QualRuleInvalid, + } + } +} + +impl<'a> From> for SelectorErrorKind<'a> { + fn from(err: SelectorParseErrorKind<'a>) -> Self { + match err { + SelectorParseErrorKind::PseudoElementExpectedColon(token) => { + Self::ExpectedColonOnPseudoElement(token) + } + SelectorParseErrorKind::PseudoElementExpectedIdent(token) => { + Self::ExpectedIdentityOnPseudoElement(token) + } + other => Self::UnexpectedSelectorParseError(other), + } + } +} + +impl<'a> Display for SelectorErrorKind<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::UnexpectedToken(token) => { + format!("Token {:?} was not expected", utils::render_token(token)) + } + Self::EndOfLine => "Unexpected EOL".to_string(), + Self::InvalidAtRule(rule) => format!("Invalid @-rule {:?}", rule), + Self::InvalidAtRuleBody => "The body of an @-rule was invalid".to_string(), + Self::QualRuleInvalid => "The qualified name was invalid".to_string(), + Self::ExpectedColonOnPseudoElement(token) => format!( + "Expected a ':' token for pseudoelement, got {:?} instead", + utils::render_token(token) + ), + Self::ExpectedIdentityOnPseudoElement(token) => format!( + "Expected identity for pseudoelement, got {:?} instead", + utils::render_token(token) + ), + Self::UnexpectedSelectorParseError(err) => format!( + "Unexpected error occurred. Please report this to the developer\n{:#?}", + err + ), + } + ) + } +} + +impl<'a> Error for SelectorErrorKind<'a> { + fn description(&self) -> &str { + match self { + Self::UnexpectedToken(_) => "Token was not expected", + Self::EndOfLine => "Unexpected EOL", + Self::InvalidAtRule(_) => "Invalid @-rule", + Self::InvalidAtRuleBody => "The body of an @-rule was invalid", + Self::QualRuleInvalid => "The qualified name was invalid", + Self::ExpectedColonOnPseudoElement(_) => "Missing colon character on pseudoelement", + Self::ExpectedIdentityOnPseudoElement(_) => "Missing pseudoelement identity", + Self::UnexpectedSelectorParseError(_) => "Unexpected error", + } + } +} diff --git a/src/error/utils.rs b/src/error/utils.rs new file mode 100644 index 00000000..a1bcb0e7 --- /dev/null +++ b/src/error/utils.rs @@ -0,0 +1,91 @@ +use cssparser::Token; + +pub(crate) fn render_token(token: &Token<'_>) -> String { + // THIS TOOK FOREVER TO IMPLEMENT + + match token { + // TODO: Give these guys some better names + Token::Ident(ident) => format!("{}", ident.clone()), + Token::AtKeyword(value) => format!("@{}", value.clone()), + Token::Hash(name) | Token::IDHash(name) => format!("#{}", name.clone()), + Token::QuotedString(value) => format!("\"{}\"", value.clone()), + Token::Number { + has_sign: signed, + value: num, + int_value: _, + } + | Token::Percentage { + has_sign: signed, + unit_value: num, + int_value: _, + } => render_number(*signed, *num, token), + Token::Dimension { + has_sign: signed, + value: num, + int_value: _, + unit, + } => format!("{}{}", render_int(*signed, *num), unit), + Token::WhiteSpace(_) => String::from(" "), + Token::Comment(comment) => format!("/* {} */", comment), + Token::Function(name) => format!("{}()", name.clone()), + Token::BadString(string) => format!("", string.clone()), + Token::BadUrl(url) => format!("", url.clone()), + // Single-character token + sc_token => render_single_char_token(sc_token), + } +} + +fn render_single_char_token(token: &Token) -> String { + String::from(match token { + Token::Colon => ":", + Token::Semicolon => ";", + Token::Comma => ",", + Token::IncludeMatch => "~=", + Token::DashMatch => "|=", + Token::PrefixMatch => "^=", + Token::SuffixMatch => "$=", + Token::SubstringMatch => "*=", + Token::CDO => "", + Token::ParenthesisBlock => "<(", + Token::SquareBracketBlock => "<[", + Token::CurlyBracketBlock => "<{", + Token::CloseParenthesis => "<)", + Token::CloseSquareBracket => "<]", + Token::CloseCurlyBracket => "<}", + other => panic!( + "Token {:?} is not supposed to match as a single-character token!", + other + ), + }) +} + +fn render_number(signed: bool, num: f32, token: &Token) -> String { + let num = render_int(signed, num); + + match token { + Token::Number { .. } => num, + Token::Percentage { .. } => format!("{}%", num), + _ => panic!("render_number is not supposed to be called on a non-numerical token"), + } +} + +fn render_int(signed: bool, num: f32) -> String { + if signed { + render_int_signed(num) + } else { + render_int_unsigned(num) + } +} + +fn render_int_signed(num: f32) -> String { + if num > 0.0 { + format!("+{}", num) + } else { + format!("-{}", num) + } +} + +fn render_int_unsigned(num: f32) -> String { + format!("{}", num) +} diff --git a/src/lib.rs b/src/lib.rs index e5a48655..4f19459f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -146,6 +146,7 @@ pub use crate::node::Node; pub use crate::selector::Selector; pub mod element_ref; +pub mod error; pub mod html; pub mod node; pub mod selector; diff --git a/src/selector.rs b/src/selector.rs index d7b8b10b..2e1a14e7 100644 --- a/src/selector.rs +++ b/src/selector.rs @@ -9,6 +9,7 @@ use html5ever::{LocalName, Namespace}; use selectors::parser::SelectorParseErrorKind; use selectors::{matching, parser, visitor}; +use crate::error::SelectorErrorKind; use crate::ElementRef; /// Wrapper around CSS selectors. @@ -23,12 +24,13 @@ pub struct Selector { impl Selector { /// Parses a CSS selector group. - pub fn parse( - selectors: &'_ str, - ) -> Result>> { + pub fn parse(selectors: &'_ str) -> Result { let mut parser_input = cssparser::ParserInput::new(selectors); let mut parser = cssparser::Parser::new(&mut parser_input); - parser::SelectorList::parse(&Parser, &mut parser).map(|list| Selector { selectors: list.0 }) + + parser::SelectorList::parse(&Parser, &mut parser) + .map(|list| Selector { selectors: list.0 }) + .map_err(SelectorErrorKind::from) } /// Returns true if the element matches this selector. @@ -140,7 +142,7 @@ impl cssparser::ToCss for PseudoElement { } impl<'i> TryFrom<&'i str> for Selector { - type Error = cssparser::ParseError<'i, SelectorParseErrorKind<'i>>; + type Error = SelectorErrorKind<'i>; fn try_from(s: &'i str) -> Result { Selector::parse(s)