Skip to main content

lightningcss/
error.rs

1//! Error types.
2
3use crate::properties::custom::Token;
4use crate::rules::Location;
5use crate::values::string::CowArcStr;
6use cssparser::{BasicParseErrorKind, ParseError, ParseErrorKind};
7use parcel_selectors::parser::SelectorParseErrorKind;
8#[cfg(any(feature = "serde", feature = "nodejs"))]
9use serde::Serialize;
10#[cfg(feature = "into_owned")]
11use static_self::IntoOwned;
12use std::fmt;
13
14/// An error with a source location.
15#[derive(Debug, PartialEq, Clone)]
16#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(serde::Serialize))]
17#[cfg_attr(any(feature = "serde"), derive(serde::Deserialize))]
18#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
19#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
20pub struct Error<T> {
21  /// The type of error that occurred.
22  pub kind: T,
23  /// The location where the error occurred.
24  pub loc: Option<ErrorLocation>,
25}
26
27impl<T: fmt::Display> fmt::Display for Error<T> {
28  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29    self.kind.fmt(f)?;
30    if let Some(loc) = &self.loc {
31      write!(f, " at {}", loc)?;
32    }
33    Ok(())
34  }
35}
36
37impl<T: fmt::Display + fmt::Debug> std::error::Error for Error<T> {}
38
39/// A line and column location within a source file.
40#[derive(Debug, PartialEq, Clone)]
41#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(serde::Serialize))]
42#[cfg_attr(any(feature = "serde"), derive(serde::Deserialize))]
43#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
44#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
45pub struct ErrorLocation {
46  /// The filename in which the error occurred.
47  pub filename: String,
48  /// The line number, starting from 0.
49  pub line: u32,
50  /// The column number, starting from 1.
51  pub column: u32,
52}
53
54impl ErrorLocation {
55  /// Create a new error location from a source location and filename.
56  pub fn new(loc: Location, filename: String) -> Self {
57    ErrorLocation {
58      filename,
59      line: loc.line,
60      column: loc.column,
61    }
62  }
63}
64
65impl fmt::Display for ErrorLocation {
66  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67    write!(f, "{}:{}:{}", self.filename, self.line, self.column)
68  }
69}
70
71/// A parser error.
72#[derive(Debug, PartialEq, Clone)]
73#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
74#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize))]
75#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(tag = "type", content = "value"))]
76pub enum ParserError<'i> {
77  /// An at rule body was invalid.
78  AtRuleBodyInvalid,
79  /// An at rule prelude was invalid
80  AtRulePreludeInvalid,
81  /// An unknown or unsupported at rule was encountered.
82  AtRuleInvalid(CowArcStr<'i>),
83  /// Unexpectedly encountered the end of input data.
84  EndOfInput,
85  /// A declaration was invalid.
86  InvalidDeclaration,
87  /// A media query was invalid.
88  InvalidMediaQuery,
89  /// The brackets in a condition cannot be empty.
90  EmptyBracketInCondition,
91  /// Invalid CSS nesting.
92  InvalidNesting,
93  /// The @nest rule is deprecated.
94  DeprecatedNestRule,
95  /// The @value rule (of CSS modules) is deprecated.
96  DeprecatedCssModulesValueRule,
97  /// An invalid selector in an `@page` rule.
98  InvalidPageSelector,
99  /// An invalid value was encountered.
100  InvalidValue,
101  /// Invalid qualified rule.
102  QualifiedRuleInvalid,
103  /// A selector was invalid.
104  SelectorError(SelectorError<'i>),
105  /// An `@import` rule was encountered after any rule besides `@charset` or `@layer`.
106  UnexpectedImportRule,
107  /// A `@namespace` rule was encountered after any rules besides `@charset`, `@import`, or `@layer`.
108  UnexpectedNamespaceRule,
109  /// An unexpected token was encountered.
110  UnexpectedToken(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>),
111  /// Maximum nesting depth was reached.
112  MaximumNestingDepth,
113}
114
115impl<'i> fmt::Display for ParserError<'i> {
116  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117    use ParserError::*;
118    match self {
119      AtRuleBodyInvalid => write!(f, "Invalid @ rule body"),
120      AtRulePreludeInvalid => write!(f, "Invalid @ rule prelude"),
121      AtRuleInvalid(name) => write!(f, "Unknown at rule: @{}", name),
122      EndOfInput => write!(f, "Unexpected end of input"),
123      InvalidDeclaration => write!(f, "Invalid declaration"),
124      InvalidMediaQuery => write!(f, "Invalid media query"),
125      EmptyBracketInCondition => write!(f, "The brackets cannot be empty"),
126      InvalidNesting => write!(f, "Invalid nesting"),
127      DeprecatedNestRule => write!(f, "The @nest rule is deprecated"),
128      DeprecatedCssModulesValueRule => write!(f, "The @value rule is deprecated"),
129      InvalidPageSelector => write!(f, "Invalid page selector"),
130      InvalidValue => write!(f, "Invalid value"),
131      QualifiedRuleInvalid => write!(f, "Invalid qualified rule"),
132      SelectorError(s) => s.fmt(f),
133      UnexpectedImportRule => write!(
134        f,
135        "@import rules must precede all rules aside from @charset and @layer statements"
136      ),
137      UnexpectedNamespaceRule => write!(
138        f,
139        "@namespaces rules must precede all rules aside from @charset, @import, and @layer statements"
140      ),
141      UnexpectedToken(token) => write!(f, "Unexpected token {:?}", token),
142      MaximumNestingDepth => write!(f, "Overflowed the maximum nesting depth"),
143    }
144  }
145}
146
147impl<'i> Error<ParserError<'i>> {
148  /// Creates an error from a cssparser error.
149  pub fn from(err: ParseError<'i, ParserError<'i>>, filename: String) -> Error<ParserError<'i>> {
150    let kind = match err.kind {
151      ParseErrorKind::Basic(b) => match &b {
152        BasicParseErrorKind::UnexpectedToken(t) => ParserError::UnexpectedToken(t.into()),
153        BasicParseErrorKind::EndOfInput => ParserError::EndOfInput,
154        BasicParseErrorKind::AtRuleInvalid(a) => ParserError::AtRuleInvalid(a.into()),
155        BasicParseErrorKind::AtRuleBodyInvalid => ParserError::AtRuleBodyInvalid,
156        BasicParseErrorKind::QualifiedRuleInvalid => ParserError::QualifiedRuleInvalid,
157      },
158      ParseErrorKind::Custom(c) => c,
159    };
160
161    Error {
162      kind,
163      loc: Some(ErrorLocation {
164        filename,
165        line: err.location.line,
166        column: err.location.column,
167      }),
168    }
169  }
170
171  /// Consumes the value and returns an owned clone.
172  #[cfg(feature = "into_owned")]
173  pub fn into_owned<'x>(self) -> Error<ParserError<'static>> {
174    Error {
175      kind: self.kind.into_owned(),
176      loc: self.loc,
177    }
178  }
179}
180
181impl<'i> From<SelectorParseErrorKind<'i>> for ParserError<'i> {
182  fn from(err: SelectorParseErrorKind<'i>) -> ParserError<'i> {
183    ParserError::SelectorError(err.into())
184  }
185}
186
187impl<'i> ParserError<'i> {
188  #[deprecated(note = "use `ParserError::to_string()` or `fmt::Display` instead")]
189  #[allow(missing_docs)]
190  pub fn reason(&self) -> String {
191    self.to_string()
192  }
193}
194
195/// A selector parsing error.
196#[derive(Debug, PartialEq, Clone)]
197#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
198#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize))]
199#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(tag = "type", content = "value"))]
200pub enum SelectorError<'i> {
201  /// An unexpected token was found in an attribute selector.
202  BadValueInAttr(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>),
203  /// An unexpected token was found in a class selector.
204  ClassNeedsIdent(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>),
205  /// A dangling combinator was found.
206  DanglingCombinator,
207  /// An empty selector.
208  EmptySelector,
209  /// A `|` was expected in an attribute selector.
210  ExpectedBarInAttr(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>),
211  /// A namespace was expected.
212  ExpectedNamespace(CowArcStr<'i>),
213  /// An unexpected token was encountered in a namespace.
214  ExplicitNamespaceUnexpectedToken(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>),
215  /// An invalid pseudo class was encountered after a pseudo element.
216  InvalidPseudoClassAfterPseudoElement,
217  /// An invalid pseudo class was encountered after a `-webkit-scrollbar` pseudo element.
218  InvalidPseudoClassAfterWebKitScrollbar,
219  /// A `-webkit-scrollbar` state was encountered before a `-webkit-scrollbar` pseudo element.
220  InvalidPseudoClassBeforeWebKitScrollbar,
221  /// Invalid qualified name in attribute selector.
222  InvalidQualNameInAttr(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>),
223  /// The current token is not allowed in this state.
224  InvalidState,
225  /// The selector is required to have the `&` nesting selector at the start.
226  MissingNestingPrefix,
227  /// The selector is missing a `&` nesting selector.
228  MissingNestingSelector,
229  /// No qualified name in attribute selector.
230  NoQualifiedNameInAttributeSelector(
231    #[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>,
232  ),
233  /// An Invalid token was encountered in a pseudo element.
234  PseudoElementExpectedIdent(#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>),
235  /// An unexpected identifier was encountered.
236  UnexpectedIdent(CowArcStr<'i>),
237  /// An unexpected token was encountered inside an attribute selector.
238  UnexpectedTokenInAttributeSelector(
239    #[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>,
240  ),
241
242  /// An unsupported pseudo class was encountered.
243  UnsupportedPseudoClass(CowArcStr<'i>),
244
245  /// An unsupported pseudo element was encountered.
246  UnsupportedPseudoElement(CowArcStr<'i>),
247
248  /// Ambiguous CSS module class.
249  AmbiguousCssModuleClass(CowArcStr<'i>),
250
251  /// An unexpected token was encountered after a pseudo element.
252  UnexpectedSelectorAfterPseudoElement(
253    #[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(skip))] Token<'i>,
254  ),
255}
256
257impl<'i> fmt::Display for SelectorError<'i> {
258  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259    use SelectorError::*;
260    match self {
261      InvalidState => write!(f, "Invalid state"),
262      BadValueInAttr(token) => write!(f, "Invalid value in attribute selector: {:?}", token),
263      ClassNeedsIdent(token) => write!(f, "Expected identifier in class selector, got {:?}", token),
264      DanglingCombinator => write!(f, "Invalid dangling combinator in selector"),
265      EmptySelector => write!(f, "Invalid empty selector"),
266      ExpectedBarInAttr(name) => write!(f, "Expected | in attribute, got {:?}", name),
267      ExpectedNamespace(name) => write!(f, "Expected namespace: {}", name),
268      ExplicitNamespaceUnexpectedToken(token) => write!(f, "Unexpected token in namespace selector: {:?}", token),
269      InvalidPseudoClassAfterPseudoElement => write!(f, "Invalid pseudo class after pseudo element, only user action pseudo classes (e.g. :hover, :active) are allowed"),
270      InvalidPseudoClassAfterWebKitScrollbar => write!(f, "Invalid pseudo class after ::-webkit-scrollbar pseudo element"),
271      InvalidPseudoClassBeforeWebKitScrollbar => write!(f, "Pseudo class must be prefixed by a ::-webkit-scrollbar pseudo element"),
272      InvalidQualNameInAttr(token) => write!(f, "Invalid qualified name in attribute selector: {:?}", token),
273      MissingNestingPrefix => write!(f, "A nested rule must start with a nesting selector (&) as prefix of each selector, or start with @nest"),
274      MissingNestingSelector => write!(f, "A nesting selector (&) is required in each selector of a @nest rule"),
275      NoQualifiedNameInAttributeSelector(token) => write!(f, "No qualified name in attribute selector: {:?}.", token),
276      PseudoElementExpectedIdent(token) => write!(f, "Invalid token in pseudo element: {:?}", token),
277      UnexpectedIdent(name) => write!(f, "Unexpected identifier: {}", name),
278      UnexpectedTokenInAttributeSelector(token) => write!(f, "Unexpected token in attribute selector: {:?}", token),
279      UnsupportedPseudoClass(name) =>write!(f, "'{name}' is not recognized as a valid pseudo-class. Did you mean '::{name}' (pseudo-element) or is this a typo?"),
280      UnsupportedPseudoElement(name) => write!(f, "'{name}' is not recognized as a valid pseudo-element. Did you mean ':{name}' (pseudo-class) or is this a typo?"),
281      AmbiguousCssModuleClass(_) => write!(f, "Ambiguous CSS module class not supported"),
282      UnexpectedSelectorAfterPseudoElement(token) => {
283        write!(
284          f,
285          "Pseudo-elements like '::before' or '::after' can't be followed by selectors like '{token:?}'"
286        )
287      },
288    }
289  }
290}
291
292impl<'i> From<SelectorParseErrorKind<'i>> for SelectorError<'i> {
293  fn from(err: SelectorParseErrorKind<'i>) -> Self {
294    match &err {
295      SelectorParseErrorKind::NoQualifiedNameInAttributeSelector(t) => {
296        SelectorError::NoQualifiedNameInAttributeSelector(t.into())
297      }
298      SelectorParseErrorKind::EmptySelector => SelectorError::EmptySelector,
299      SelectorParseErrorKind::DanglingCombinator => SelectorError::DanglingCombinator,
300      SelectorParseErrorKind::InvalidPseudoClassBeforeWebKitScrollbar => {
301        SelectorError::InvalidPseudoClassBeforeWebKitScrollbar
302      }
303      SelectorParseErrorKind::InvalidPseudoClassAfterWebKitScrollbar => {
304        SelectorError::InvalidPseudoClassAfterWebKitScrollbar
305      }
306      SelectorParseErrorKind::InvalidPseudoClassAfterPseudoElement => {
307        SelectorError::InvalidPseudoClassAfterPseudoElement
308      }
309      SelectorParseErrorKind::InvalidState => SelectorError::InvalidState,
310      SelectorParseErrorKind::MissingNestingSelector => SelectorError::MissingNestingSelector,
311      SelectorParseErrorKind::MissingNestingPrefix => SelectorError::MissingNestingPrefix,
312      SelectorParseErrorKind::UnexpectedTokenInAttributeSelector(t) => {
313        SelectorError::UnexpectedTokenInAttributeSelector(t.into())
314      }
315      SelectorParseErrorKind::PseudoElementExpectedIdent(t) => SelectorError::PseudoElementExpectedIdent(t.into()),
316      SelectorParseErrorKind::UnsupportedPseudoClass(t) => SelectorError::UnsupportedPseudoClass(t.into()),
317      SelectorParseErrorKind::UnsupportedPseudoElement(t) => SelectorError::UnsupportedPseudoElement(t.into()),
318      SelectorParseErrorKind::UnexpectedIdent(t) => SelectorError::UnexpectedIdent(t.into()),
319      SelectorParseErrorKind::ExpectedNamespace(t) => SelectorError::ExpectedNamespace(t.into()),
320      SelectorParseErrorKind::ExpectedBarInAttr(t) => SelectorError::ExpectedBarInAttr(t.into()),
321      SelectorParseErrorKind::BadValueInAttr(t) => SelectorError::BadValueInAttr(t.into()),
322      SelectorParseErrorKind::InvalidQualNameInAttr(t) => SelectorError::InvalidQualNameInAttr(t.into()),
323      SelectorParseErrorKind::ExplicitNamespaceUnexpectedToken(t) => {
324        SelectorError::ExplicitNamespaceUnexpectedToken(t.into())
325      }
326      SelectorParseErrorKind::ClassNeedsIdent(t) => SelectorError::ClassNeedsIdent(t.into()),
327      SelectorParseErrorKind::AmbiguousCssModuleClass(name) => SelectorError::AmbiguousCssModuleClass(name.into()),
328      SelectorParseErrorKind::UnexpectedSelectorAfterPseudoElement(t) => {
329        SelectorError::UnexpectedSelectorAfterPseudoElement(t.into())
330      }
331    }
332  }
333}
334
335#[derive(Debug, PartialEq)]
336pub(crate) struct ErrorWithLocation<T> {
337  pub kind: T,
338  pub loc: Location,
339}
340
341impl<T: fmt::Display> fmt::Display for ErrorWithLocation<T> {
342  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343    self.kind.fmt(f)
344  }
345}
346
347impl<T: fmt::Display + fmt::Debug> std::error::Error for ErrorWithLocation<T> {}
348
349pub(crate) type MinifyError = ErrorWithLocation<MinifyErrorKind>;
350
351/// A transformation error.
352#[derive(Debug, PartialEq)]
353#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize))]
354#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(tag = "type"))]
355pub enum MinifyErrorKind {
356  /// A circular `@custom-media` rule was detected.
357  CircularCustomMedia {
358    /// The name of the `@custom-media` rule that was referenced circularly.
359    name: String,
360  },
361  /// Attempted to reference a custom media rule that doesn't exist.
362  CustomMediaNotDefined {
363    /// The name of the `@custom-media` rule that was not defined.
364    name: String,
365  },
366  /// Boolean logic with media types in @custom-media rules is not supported.
367  UnsupportedCustomMediaBooleanLogic {
368    /// The source location of the `@custom-media` rule with unsupported boolean logic.
369    custom_media_loc: Location,
370  },
371  /// A CSS module selector did not contain at least one class or id selector.
372  ImpureCSSModuleSelector,
373}
374
375impl fmt::Display for MinifyErrorKind {
376  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
377    use MinifyErrorKind::*;
378    match self {
379      CircularCustomMedia { name } => write!(f, "Circular custom media query {} detected", name),
380      CustomMediaNotDefined { name } => write!(f, "Custom media query {} is not defined", name),
381      UnsupportedCustomMediaBooleanLogic { .. } => write!(
382        f,
383        "Boolean logic with media types in @custom-media rules is not supported by Lightning CSS"
384      ),
385      ImpureCSSModuleSelector => write!(
386        f,
387        "A selector in CSS modules should contain at least one class or ID selector"
388      ),
389    }
390  }
391}
392
393impl MinifyErrorKind {
394  #[deprecated(note = "use `MinifyErrorKind::to_string()` or `fmt::Display` instead")]
395  #[allow(missing_docs)]
396  pub fn reason(&self) -> String {
397    self.to_string()
398  }
399}
400
401/// A printer error.
402pub type PrinterError = Error<PrinterErrorKind>;
403
404/// A printer error type.
405#[derive(Debug, PartialEq)]
406#[cfg_attr(any(feature = "serde", feature = "nodejs"), derive(Serialize))]
407#[cfg_attr(any(feature = "serde", feature = "nodejs"), serde(tag = "type"))]
408pub enum PrinterErrorKind {
409  /// An ambiguous relative `url()` was encountered in a custom property declaration.
410  AmbiguousUrlInCustomProperty {
411    /// The ambiguous URL.
412    url: String,
413  },
414  /// A [std::fmt::Error](std::fmt::Error) was encountered in the underlying destination.
415  FmtError,
416  /// The CSS modules `composes` property cannot be used within nested rules.
417  InvalidComposesNesting,
418  /// The CSS modules `composes` property can only be used with a simple class selector.
419  InvalidComposesSelector,
420  /// The CSS modules pattern must end with `[local]` for use in CSS grid.
421  InvalidCssModulesPatternInGrid,
422}
423
424impl From<fmt::Error> for PrinterError {
425  fn from(_: fmt::Error) -> PrinterError {
426    PrinterError {
427      kind: PrinterErrorKind::FmtError,
428      loc: None,
429    }
430  }
431}
432
433impl fmt::Display for PrinterErrorKind {
434  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
435    use PrinterErrorKind::*;
436    match self {
437      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),
438      FmtError => write!(f, "Printer error"),
439      InvalidComposesNesting => write!(f, "The `composes` property cannot be used within nested rules"),
440      InvalidComposesSelector => write!(f, "The `composes` property cannot be used with a simple class selector"),
441      InvalidCssModulesPatternInGrid => write!(f, "The CSS modules `pattern` config must end with `[local]` for use in CSS grid line names."),
442    }
443  }
444}
445
446impl PrinterErrorKind {
447  #[deprecated(note = "use `PrinterErrorKind::to_string()` or `fmt::Display` instead")]
448  #[allow(missing_docs)]
449  pub fn reason(&self) -> String {
450    self.to_string()
451  }
452}