//! Error types. use crate::properties::custom::Token; use crate::rules::Location; use crate::values::string::CowArcStr; use cssparser::{BasicParseErrorKind, ParseError, ParseErrorKind}; use parcel_selectors::parser::SelectorParseErrorKind; #[cfg(any(feature = "serde", feature = "nodejs"))] use serde::Serialize; #[cfg(feature = "into_owned")] use static_self::IntoOwned; use std::fmt; /// An error with a source location. #[derive(Debug, PartialEq, Clone)] #[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(serde::Serialize))] #[cfg_attr(any(feature = "serde"), derive(serde::Deserialize))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub struct Error { /// The type of error that occurred. pub kind: T, /// The location where the error occurred. pub loc: Option, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.kind.fmt(f)?; if let Some(loc) = &self.loc { write!(f, " at {}", loc)?; } Ok(()) } } impl std::error::Error for Error {} /// A line and column location within a source file. #[derive(Debug, PartialEq, Clone)] #[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(serde::Serialize))] #[cfg_attr(any(feature = "serde"), derive(serde::Deserialize))] #[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))] pub struct ErrorLocation { /// The filename in which the error occurred. pub filename: String, /// The line number, starting from 0. pub line: u32, /// The column number, starting from 1. pub column: u32, } impl ErrorLocation { /// Create a new error location from a source location and filename. pub fn new(loc: Location, filename: String) -> Self { ErrorLocation { filename, line: loc.line, column: loc.column, } } } impl fmt::Display for ErrorLocation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}:{}", self.filename, self.line, self.column) } } /// A parser error. #[derive(Debug, PartialEq, Clone)] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize))] #[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(tag = "type", content = "value"))] pub enum ParserError<'i> { /// An at rule body was invalid. AtRuleBodyInvalid, /// An at rule prelude was invalid AtRulePreludeInvalid, /// An unknown or unsupported at rule was encountered. AtRuleInvalid(CowArcStr<'i>), /// Unexpectedly encountered the end of input data. EndOfInput, /// A declaration was invalid. InvalidDeclaration, /// A media query was invalid. InvalidMediaQuery, /// Invalid CSS nesting. InvalidNesting, /// The @nest rule is deprecated. DeprecatedNestRule, /// An invalid selector in an `@page` rule. InvalidPageSelector, /// An invalid value was encountered. InvalidValue, /// Invalid qualified rule. QualifiedRuleInvalid, /// A selector was invalid. SelectorError(SelectorError<'i>), /// An `@import` rule was encountered after any rule besides `@charset` or `@layer`. UnexpectedImportRule, /// A `@namespace` rule was encountered after any rules besides `@charset`, `@import`, or `@layer`. UnexpectedNamespaceRule, /// An unexpected token was encountered. UnexpectedToken(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>), /// Maximum nesting depth was reached. MaximumNestingDepth, } impl<'i> fmt::Display for ParserError<'i> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use ParserError::*; match self { AtRuleBodyInvalid => write!(f, "Invalid @ rule body"), AtRulePreludeInvalid => write!(f, "Invalid @ rule prelude"), AtRuleInvalid(name) => write!(f, "Unknown at rule: @{}", name), EndOfInput => write!(f, "Unexpected end of input"), InvalidDeclaration => write!(f, "Invalid declaration"), InvalidMediaQuery => write!(f, "Invalid media query"), InvalidNesting => write!(f, "Invalid nesting"), DeprecatedNestRule => write!(f, "The @nest rule is deprecated"), InvalidPageSelector => write!(f, "Invalid page selector"), InvalidValue => write!(f, "Invalid value"), QualifiedRuleInvalid => write!(f, "Invalid qualified rule"), SelectorError(s) => s.fmt(f), UnexpectedImportRule => write!( f, "@import rules must precede all rules aside from @charset and @layer statements" ), UnexpectedNamespaceRule => write!( f, "@namespaces rules must precede all rules aside from @charset, @import, and @layer statements" ), UnexpectedToken(token) => write!(f, "Unexpected token {:?}", token), MaximumNestingDepth => write!(f, "Overflowed the maximum nesting depth"), } } } impl<'i> Error> { /// Creates an error from a cssparser 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, }; Error { kind, loc: Some(ErrorLocation { filename, line: err.location.line, column: err.location.column, }), } } /// Consumes the value and returns an owned clone. #[cfg(feature = "into_owned")] pub fn into_owned<'x>(self) -> Error> { Error { kind: self.kind.into_owned(), loc: self.loc, } } } impl<'i> From> for ParserError<'i> { fn from(err: SelectorParseErrorKind<'i>) -> ParserError<'i> { ParserError::SelectorError(err.into()) } } impl<'i> ParserError<'i> { #[deprecated(note = "use `ParserError::to_string()` or `fmt::Display` instead")] #[allow(missing_docs)] pub fn reason(&self) -> String { self.to_string() } } /// A selector parsing error. #[derive(Debug, PartialEq, Clone)] #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))] #[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize))] #[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(tag = "type", content = "value"))] pub enum SelectorError<'i> { /// An unexpected token was found in an attribute selector. BadValueInAttr(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>), /// An unexpected token was found in a class selector. ClassNeedsIdent(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>), /// A dangling combinator was found. DanglingCombinator, /// An empty selector. EmptySelector, /// A `|` was expected in an attribute selector. ExpectedBarInAttr(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>), /// A namespace was expected. ExpectedNamespace(CowArcStr<'i>), /// An unexpected token was encountered in a namespace. ExplicitNamespaceUnexpectedToken(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>), /// An invalid pseudo class was encountered after a pseudo element. InvalidPseudoClassAfterPseudoElement, /// An invalid pseudo class was encountered after a `-webkit-scrollbar` pseudo element. InvalidPseudoClassAfterWebKitScrollbar, /// A `-webkit-scrollbar` state was encountered before a `-webkit-scrollbar` pseudo element. InvalidPseudoClassBeforeWebKitScrollbar, /// Invalid qualified name in attribute selector. InvalidQualNameInAttr(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>), /// The current token is not allowed in this state. InvalidState, /// The selector is required to have the `&` nesting selector at the start. MissingNestingPrefix, /// The selector is missing a `&` nesting selector. MissingNestingSelector, /// No qualified name in attribute selector. NoQualifiedNameInAttributeSelector( #[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>, ), /// An Invalid token was encountered in a pseudo element. PseudoElementExpectedIdent(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>), /// An unexpected identifier was encountered. UnexpectedIdent(CowArcStr<'i>), /// An unexpected token was encountered inside an attribute selector. UnexpectedTokenInAttributeSelector( #[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>, ), /// An unsupported pseudo class or pseudo element was encountered. UnsupportedPseudoClassOrElement(CowArcStr<'i>), } impl<'i> fmt::Display for SelectorError<'i> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use SelectorError::*; match self { InvalidState => write!(f, "Invalid state"), BadValueInAttr(token) => write!(f, "Invalid value in attribute selector: {:?}", token), ClassNeedsIdent(token) => write!(f, "Expected identifier in class selector, got {:?}", token), DanglingCombinator => write!(f, "Invalid dangling combinator in selector"), EmptySelector => write!(f, "Invalid empty selector"), ExpectedBarInAttr(name) => write!(f, "Expected | in attribute, got {:?}", name), ExpectedNamespace(name) => write!(f, "Expected namespace: {}", name), ExplicitNamespaceUnexpectedToken(token) => write!(f, "Unexpected token in namespace selector: {:?}", token), InvalidPseudoClassAfterPseudoElement => write!(f, "Invalid pseudo class after pseudo element, only user action pseudo classes (e.g. :hover, :active) are allowed"), InvalidPseudoClassAfterWebKitScrollbar => write!(f, "Invalid pseudo class after ::-webkit-scrollbar pseudo element"), InvalidPseudoClassBeforeWebKitScrollbar => write!(f, "Pseudo class must be prefixed by a ::-webkit-scrollbar pseudo element"), InvalidQualNameInAttr(token) => write!(f, "Invalid qualified name in attribute selector: {:?}", token), MissingNestingPrefix => write!(f, "A nested rule must start with a nesting selector (&) as prefix of each selector, or start with @nest"), MissingNestingSelector => write!(f, "A nesting selector (&) is required in each selector of a @nest rule"), NoQualifiedNameInAttributeSelector(token) => write!(f, "No qualified name in attribute selector: {:?}.", token), PseudoElementExpectedIdent(token) => write!(f, "Invalid token in pseudo element: {:?}", token), UnexpectedIdent(name) => write!(f, "Unexpected identifier: {}", name), UnexpectedTokenInAttributeSelector(token) => write!(f, "Unexpected token in attribute selector: {:?}", token), UnsupportedPseudoClassOrElement(name) => write!(f, "Unsupported pseudo class or element: {}", name), } } } impl<'i> From> for SelectorError<'i> { fn from(err: SelectorParseErrorKind<'i>) -> Self { match &err { SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t) => { SelectorError::NoQualifiedNameInAttributeSelector(t.into()) } SelectorParseErrorKind::EmptySelector => SelectorError::EmptySelector, SelectorParseErrorKind::DanglingCombinator => SelectorError::DanglingCombinator, 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::PseudoElementExpectedIdent(t) => SelectorError::PseudoElementExpectedIdent(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::ClassNeedsIdent(t) => SelectorError::ClassNeedsIdent(t.into()), } } } #[derive(Debug, PartialEq)] pub(crate) struct ErrorWithLocation { pub kind: T, pub loc: Location, } impl fmt::Display for ErrorWithLocation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.kind.fmt(f) } } impl std::error::Error for ErrorWithLocation {} pub(crate) type MinifyError = ErrorWithLocation; /// A transformation error. #[derive(Debug, PartialEq)] #[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize))] #[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(tag = "type"))] pub enum MinifyErrorKind { /// A circular `@custom-media` rule was detected. CircularCustomMedia { /// The name of the `@custom-media` rule that was referenced circularly. name: String, }, /// Attempted to reference a custom media rule that doesn't exist. CustomMediaNotDefined { /// The name of the `@custom-media` rule that was not defined. name: String, }, /// Boolean logic with media types in @custom-media rules is not supported. UnsupportedCustomMediaBooleanLogic { /// The source location of the `@custom-media` rule with unsupported boolean logic. custom_media_loc: Location, }, } impl fmt::Display for MinifyErrorKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use MinifyErrorKind::*; match self { CircularCustomMedia { name } => write!(f, "Circular custom media query {} detected", name), CustomMediaNotDefined { name } => write!(f, "Custom media query {} is not defined", name), UnsupportedCustomMediaBooleanLogic { .. } => write!( f, "Boolean logic with media types in @custom-media rules is not supported by Lightning CSS" ), } } } impl MinifyErrorKind { #[deprecated(note = "use `MinifyErrorKind::to_string()` or `fmt::Display` instead")] #[allow(missing_docs)] pub fn reason(&self) -> String { self.to_string() } } /// A printer error. pub type PrinterError = Error; /// A printer error type. #[derive(Debug, PartialEq)] #[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize))] #[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(tag = "type"))] pub enum PrinterErrorKind { /// An ambiguous relative `url()` was encountered in a custom property declaration. AmbiguousUrlInCustomProperty { /// The ambiguous URL. url: String, }, /// A [std::fmt::Error](std::fmt::Error) was encountered in the underlying destination. FmtError, /// The CSS modules `composes` property cannot be used within nested rules. InvalidComposesNesting, /// The CSS modules `composes` property cannot be used with a simple class selector. InvalidComposesSelector, /// The CSS modules pattern must end with `[local]` for use in CSS grid. InvalidCssModulesPatternInGrid, } impl From for PrinterError { fn from(_: fmt::Error) -> PrinterError { PrinterError { kind: PrinterErrorKind::FmtError, loc: None, } } } impl fmt::Display for PrinterErrorKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use PrinterErrorKind::*; match self { AmbiguousUrlInCustomProperty { url } => write!(f, "Ambiguous url('{}') in custom property. Relative paths are resolved from the location the var() is used, not where the custom property is defined. Use an absolute URL instead", url), FmtError => write!(f, "Printer error"), InvalidComposesNesting => write!(f, "The `composes` property cannot be used within nested rules"), InvalidComposesSelector => write!(f, "The `composes` property cannot be used with a simple class selector"), InvalidCssModulesPatternInGrid => write!(f, "The CSS modules `pattern` config must end with `[local]` for use in CSS grid line names."), } } } impl PrinterErrorKind { #[deprecated(note = "use `PrinterErrorKind::to_string()` or `fmt::Display` instead")] #[allow(missing_docs)] pub fn reason(&self) -> String { self.to_string() } }