Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An Error Type for Selector::parse #95

Merged
merged 17 commits into from Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
121 changes: 121 additions & 0 deletions 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<cssparser::ParseError<'a, SelectorParseErrorKind<'a>>> 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),
}
cfvescovo marked this conversation as resolved.
Show resolved Hide resolved
}
}

impl<'a> From<BasicParseErrorKind<'a>> 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<SelectorParseErrorKind<'a>> 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",
}
}
}
91 changes: 91 additions & 0 deletions 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.clone()),
Kiwifuit marked this conversation as resolved.
Show resolved Hide resolved
Token::Function(name) => format!("{}()", name.clone()),
Token::BadString(string) => format!("<Bad String {:?}>", string.clone()),
Token::BadUrl(url) => format!("<Bad URL {:?}>", 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::CDC => "-->",
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)
}
1 change: 1 addition & 0 deletions src/lib.rs
Expand Up @@ -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;
Expand Down
12 changes: 7 additions & 5 deletions src/selector.rs
Expand Up @@ -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.
Expand All @@ -23,12 +24,13 @@ pub struct Selector {
impl Selector {
/// Parses a CSS selector group.

pub fn parse(
selectors: &'_ str,
) -> Result<Self, cssparser::ParseError<'_, SelectorParseErrorKind<'_>>> {
pub fn parse(selectors: &'_ str) -> Result<Self, SelectorErrorKind> {
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.
Expand Down Expand Up @@ -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<Self, Self::Error> {
Selector::parse(s)
Expand Down