style/
error_reporting.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Types used to report parsing errors.
6
7#![deny(missing_docs)]
8
9use crate::selector_parser::SelectorImpl;
10use crate::stylesheets::UrlExtraData;
11use cssparser::{BasicParseErrorKind, ParseErrorKind, SourceLocation, Token};
12use selectors::parser::{Combinator, Component, RelativeSelector, Selector};
13use selectors::visitor::{SelectorListKind, SelectorVisitor};
14use selectors::SelectorList;
15use std::fmt;
16use style_traits::ParseError;
17
18/// Errors that can be encountered while parsing CSS.
19#[derive(Debug)]
20pub enum ContextualParseError<'a> {
21    /// A property declaration was not recognized.
22    UnsupportedPropertyDeclaration(&'a str, ParseError<'a>, &'a [SelectorList<SelectorImpl>]),
23    /// A property descriptor was not recognized.
24    UnsupportedPropertyDescriptor(&'a str, ParseError<'a>),
25    /// A font face descriptor was not recognized.
26    UnsupportedFontFaceDescriptor(&'a str, ParseError<'a>),
27    /// A font feature values descriptor was not recognized.
28    UnsupportedFontFeatureValuesDescriptor(&'a str, ParseError<'a>),
29    /// A font palette values descriptor was not recognized.
30    UnsupportedFontPaletteValuesDescriptor(&'a str, ParseError<'a>),
31    /// A keyframe rule was not valid.
32    InvalidKeyframeRule(&'a str, ParseError<'a>),
33    /// A font feature values rule was not valid.
34    InvalidFontFeatureValuesRule(&'a str, ParseError<'a>),
35    /// A rule was invalid for some reason.
36    InvalidRule(&'a str, ParseError<'a>),
37    /// A rule was not recognized.
38    UnsupportedRule(&'a str, ParseError<'a>),
39    /// A viewport descriptor declaration was not recognized.
40    UnsupportedViewportDescriptorDeclaration(&'a str, ParseError<'a>),
41    /// A counter style descriptor declaration was not recognized.
42    UnsupportedCounterStyleDescriptorDeclaration(&'a str, ParseError<'a>),
43    /// A counter style rule had no symbols.
44    InvalidCounterStyleWithoutSymbols(String),
45    /// A counter style rule had less than two symbols.
46    InvalidCounterStyleNotEnoughSymbols(String),
47    /// A counter style rule did not have additive-symbols.
48    InvalidCounterStyleWithoutAdditiveSymbols,
49    /// A counter style rule had extends with symbols.
50    InvalidCounterStyleExtendsWithSymbols,
51    /// A counter style rule had extends with additive-symbols.
52    InvalidCounterStyleExtendsWithAdditiveSymbols,
53    /// A media rule was invalid for some reason.
54    InvalidMediaRule(&'a str, ParseError<'a>),
55    /// A value was not recognized.
56    UnsupportedValue(&'a str, ParseError<'a>),
57    /// A never-matching `:host` selector was found.
58    NeverMatchingHostSelector(String),
59    /// A view-transition declaration was not recognized.
60    UnsupportedViewTransitionDescriptor(&'a str, ParseError<'a>),
61}
62
63impl<'a> fmt::Display for ContextualParseError<'a> {
64    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65        fn token_to_str(t: &Token, f: &mut fmt::Formatter) -> fmt::Result {
66            match *t {
67                Token::Ident(ref i) => write!(f, "identifier {}", i),
68                Token::AtKeyword(ref kw) => write!(f, "keyword @{}", kw),
69                Token::Hash(ref h) => write!(f, "hash #{}", h),
70                Token::IDHash(ref h) => write!(f, "id selector #{}", h),
71                Token::QuotedString(ref s) => write!(f, "quoted string \"{}\"", s),
72                Token::UnquotedUrl(ref u) => write!(f, "url {}", u),
73                Token::Delim(ref d) => write!(f, "delimiter {}", d),
74                Token::Number {
75                    int_value: Some(i), ..
76                } => write!(f, "number {}", i),
77                Token::Number { value, .. } => write!(f, "number {}", value),
78                Token::Percentage {
79                    int_value: Some(i), ..
80                } => write!(f, "percentage {}", i),
81                Token::Percentage { unit_value, .. } => {
82                    write!(f, "percentage {}", unit_value * 100.)
83                },
84                Token::Dimension {
85                    value, ref unit, ..
86                } => write!(f, "dimension {}{}", value, unit),
87                Token::WhiteSpace(_) => write!(f, "whitespace"),
88                Token::Comment(_) => write!(f, "comment"),
89                Token::Colon => write!(f, "colon (:)"),
90                Token::Semicolon => write!(f, "semicolon (;)"),
91                Token::Comma => write!(f, "comma (,)"),
92                Token::IncludeMatch => write!(f, "include match (~=)"),
93                Token::DashMatch => write!(f, "dash match (|=)"),
94                Token::PrefixMatch => write!(f, "prefix match (^=)"),
95                Token::SuffixMatch => write!(f, "suffix match ($=)"),
96                Token::SubstringMatch => write!(f, "substring match (*=)"),
97                Token::CDO => write!(f, "CDO (<!--)"),
98                Token::CDC => write!(f, "CDC (-->)"),
99                Token::Function(ref name) => write!(f, "function {}", name),
100                Token::ParenthesisBlock => write!(f, "parenthesis ("),
101                Token::SquareBracketBlock => write!(f, "square bracket ["),
102                Token::CurlyBracketBlock => write!(f, "curly bracket {{"),
103                Token::BadUrl(ref _u) => write!(f, "bad url parse error"),
104                Token::BadString(ref _s) => write!(f, "bad string parse error"),
105                Token::CloseParenthesis => write!(f, "unmatched close parenthesis"),
106                Token::CloseSquareBracket => write!(f, "unmatched close square bracket"),
107                Token::CloseCurlyBracket => write!(f, "unmatched close curly bracket"),
108            }
109        }
110
111        fn parse_error_to_str(err: &ParseError, f: &mut fmt::Formatter) -> fmt::Result {
112            match err.kind {
113                ParseErrorKind::Basic(BasicParseErrorKind::UnexpectedToken(ref t)) => {
114                    write!(f, "found unexpected ")?;
115                    token_to_str(t, f)
116                },
117                ParseErrorKind::Basic(BasicParseErrorKind::EndOfInput) => {
118                    write!(f, "unexpected end of input")
119                },
120                ParseErrorKind::Basic(BasicParseErrorKind::AtRuleInvalid(ref i)) => {
121                    write!(f, "@ rule invalid: {}", i)
122                },
123                ParseErrorKind::Basic(BasicParseErrorKind::AtRuleBodyInvalid) => {
124                    write!(f, "@ rule invalid")
125                },
126                ParseErrorKind::Basic(BasicParseErrorKind::QualifiedRuleInvalid) => {
127                    write!(f, "qualified rule invalid")
128                },
129                ParseErrorKind::Custom(ref err) => write!(f, "{:?}", err),
130            }
131        }
132
133        match *self {
134            ContextualParseError::UnsupportedPropertyDeclaration(decl, ref err, _selectors) => {
135                write!(f, "Unsupported property declaration: '{}', ", decl)?;
136                parse_error_to_str(err, f)
137            },
138            ContextualParseError::UnsupportedPropertyDescriptor(decl, ref err) => {
139                write!(
140                    f,
141                    "Unsupported @property descriptor declaration: '{}', ",
142                    decl
143                )?;
144                parse_error_to_str(err, f)
145            },
146            ContextualParseError::UnsupportedFontFaceDescriptor(decl, ref err) => {
147                write!(
148                    f,
149                    "Unsupported @font-face descriptor declaration: '{}', ",
150                    decl
151                )?;
152                parse_error_to_str(err, f)
153            },
154            ContextualParseError::UnsupportedFontFeatureValuesDescriptor(decl, ref err) => {
155                write!(
156                    f,
157                    "Unsupported @font-feature-values descriptor declaration: '{}', ",
158                    decl
159                )?;
160                parse_error_to_str(err, f)
161            },
162            ContextualParseError::UnsupportedFontPaletteValuesDescriptor(decl, ref err) => {
163                write!(
164                    f,
165                    "Unsupported @font-palette-values descriptor declaration: '{}', ",
166                    decl
167                )?;
168                parse_error_to_str(err, f)
169            },
170            ContextualParseError::InvalidKeyframeRule(rule, ref err) => {
171                write!(f, "Invalid keyframe rule: '{}', ", rule)?;
172                parse_error_to_str(err, f)
173            },
174            ContextualParseError::InvalidFontFeatureValuesRule(rule, ref err) => {
175                write!(f, "Invalid font feature value rule: '{}', ", rule)?;
176                parse_error_to_str(err, f)
177            },
178            ContextualParseError::InvalidRule(rule, ref err) => {
179                write!(f, "Invalid rule: '{}', ", rule)?;
180                parse_error_to_str(err, f)
181            },
182            ContextualParseError::UnsupportedRule(rule, ref err) => {
183                write!(f, "Unsupported rule: '{}', ", rule)?;
184                parse_error_to_str(err, f)
185            },
186            ContextualParseError::UnsupportedViewportDescriptorDeclaration(decl, ref err) => {
187                write!(
188                    f,
189                    "Unsupported @viewport descriptor declaration: '{}', ",
190                    decl
191                )?;
192                parse_error_to_str(err, f)
193            },
194            ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(decl, ref err) => {
195                write!(
196                    f,
197                    "Unsupported @counter-style descriptor declaration: '{}', ",
198                    decl
199                )?;
200                parse_error_to_str(err, f)
201            },
202            ContextualParseError::InvalidCounterStyleWithoutSymbols(ref system) => write!(
203                f,
204                "Invalid @counter-style rule: 'system: {}' without 'symbols'",
205                system
206            ),
207            ContextualParseError::InvalidCounterStyleNotEnoughSymbols(ref system) => write!(
208                f,
209                "Invalid @counter-style rule: 'system: {}' less than two 'symbols'",
210                system
211            ),
212            ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols => write!(
213                f,
214                "Invalid @counter-style rule: 'system: additive' without 'additive-symbols'"
215            ),
216            ContextualParseError::InvalidCounterStyleExtendsWithSymbols => write!(
217                f,
218                "Invalid @counter-style rule: 'system: extends …' with 'symbols'"
219            ),
220            ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols => write!(
221                f,
222                "Invalid @counter-style rule: 'system: extends …' with 'additive-symbols'"
223            ),
224            ContextualParseError::InvalidMediaRule(media_rule, ref err) => {
225                write!(f, "Invalid media rule: {}, ", media_rule)?;
226                parse_error_to_str(err, f)
227            },
228            ContextualParseError::UnsupportedValue(_value, ref err) => parse_error_to_str(err, f),
229            ContextualParseError::NeverMatchingHostSelector(ref selector) => {
230                write!(f, ":host selector is not featureless: {}", selector)
231            },
232            ContextualParseError::UnsupportedViewTransitionDescriptor(decl, ref err) => {
233                write!(
234                    f,
235                    "Unsupported @view-transition descriptor declaration: '{}', ",
236                    decl
237                )?;
238                parse_error_to_str(err, f)
239            },
240        }
241    }
242}
243
244/// A generic trait for an error reporter.
245pub trait ParseErrorReporter {
246    /// Called when the style engine detects an error.
247    ///
248    /// Returns the current input being parsed, the source location it was
249    /// reported from, and a message.
250    fn report_error(
251        &self,
252        url: &UrlExtraData,
253        location: SourceLocation,
254        error: ContextualParseError,
255    );
256}
257
258/// An error reporter that uses [the `log` crate](https://github.com/rust-lang-nursery/log)
259/// at `info` level.
260///
261/// This logging is silent by default, and can be enabled with a `RUST_LOG=style=info`
262/// environment variable.
263/// (See [`env_logger`](https://rust-lang-nursery.github.io/log/env_logger/).)
264#[cfg(feature = "servo")]
265pub struct RustLogReporter;
266
267#[cfg(feature = "servo")]
268impl ParseErrorReporter for RustLogReporter {
269    fn report_error(
270        &self,
271        url: &UrlExtraData,
272        location: SourceLocation,
273        error: ContextualParseError,
274    ) {
275        if log_enabled!(log::Level::Info) {
276            info!(
277                "Url:\t{}\n{}:{} {}",
278                url.as_str(),
279                location.line,
280                location.column,
281                error
282            )
283        }
284    }
285}
286
287/// Any warning a selector may generate.
288/// TODO(dshin): Bug 1860634 - Merge with never matching host selector warning, which is part of the rule parser.
289#[repr(u8)]
290#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
291pub enum SelectorWarningKind {
292    /// Relative Selector with not enough constraint, either outside or inside the selector. e.g. `*:has(.a)`, `.a:has(*)`.
293    /// May cause expensive invalidations for every element inserted and/or removed.
294    UnconstraintedRelativeSelector,
295    /// `:scope` can have 3 meanings, but in all cases, the relationship is defined strictly by an ancestor-descendant
296    /// relationship. This means that any presence of sibling selectors to its right would make it never match.
297    SiblingCombinatorAfterScopeSelector,
298}
299
300impl SelectorWarningKind {
301    /// Get all warnings for this selector.
302    pub fn from_selector(selector: &Selector<SelectorImpl>) -> Vec<Self> {
303        let mut result = vec![];
304        if UnconstrainedRelativeSelectorVisitor::has_warning(selector, 0, false) {
305            result.push(SelectorWarningKind::UnconstraintedRelativeSelector);
306        }
307        if SiblingCombinatorAfterScopeSelectorVisitor::has_warning(selector) {
308            result.push(SelectorWarningKind::SiblingCombinatorAfterScopeSelector);
309        }
310        result
311    }
312}
313
314/// Per-compound state for finding unconstrained relative selectors.
315struct PerCompoundState {
316    /// Is there a relative selector in this compound?
317    relative_selector_found: bool,
318    /// Is this compound constrained in any way?
319    constrained: bool,
320    /// Nested below, or inside relative selector?
321    in_relative_selector: bool,
322}
323
324impl PerCompoundState {
325    fn new(in_relative_selector: bool) -> Self {
326        Self {
327            relative_selector_found: false,
328            constrained: false,
329            in_relative_selector,
330        }
331    }
332}
333
334/// Visitor to check if there's any unconstrained relative selector.
335struct UnconstrainedRelativeSelectorVisitor {
336    compound_state: PerCompoundState,
337}
338
339impl UnconstrainedRelativeSelectorVisitor {
340    fn new(in_relative_selector: bool) -> Self {
341        Self {
342            compound_state: PerCompoundState::new(in_relative_selector),
343        }
344    }
345
346    fn has_warning(
347        selector: &Selector<SelectorImpl>,
348        offset: usize,
349        in_relative_selector: bool,
350    ) -> bool {
351        let relative_selector = matches!(
352            selector.iter_raw_parse_order_from(0).next().unwrap(),
353            Component::RelativeSelectorAnchor
354        );
355        debug_assert!(
356            !relative_selector || offset == 0,
357            "Checking relative selector from non-rightmost?"
358        );
359        let mut visitor = Self::new(in_relative_selector);
360        let mut iter = if relative_selector {
361            selector.iter_skip_relative_selector_anchor()
362        } else {
363            selector.iter_from(offset)
364        };
365        loop {
366            visitor.compound_state = PerCompoundState::new(in_relative_selector);
367
368            for s in &mut iter {
369                s.visit(&mut visitor);
370            }
371
372            if (visitor.compound_state.relative_selector_found
373                || visitor.compound_state.in_relative_selector)
374                && !visitor.compound_state.constrained
375            {
376                return true;
377            }
378
379            if iter.next_sequence().is_none() {
380                break;
381            }
382        }
383        false
384    }
385}
386
387impl SelectorVisitor for UnconstrainedRelativeSelectorVisitor {
388    type Impl = SelectorImpl;
389
390    fn visit_simple_selector(&mut self, c: &Component<Self::Impl>) -> bool {
391        match c {
392            // Deferred to visit_selector_list
393            Component::Is(..)
394            | Component::Where(..)
395            | Component::Negation(..)
396            | Component::Has(..) => (),
397            Component::ExplicitUniversalType => (),
398            _ => self.compound_state.constrained |= true,
399        };
400        true
401    }
402
403    fn visit_selector_list(
404        &mut self,
405        _list_kind: SelectorListKind,
406        list: &[Selector<Self::Impl>],
407    ) -> bool {
408        let mut all_constrained = true;
409        for s in list {
410            let mut offset = 0;
411            // First, check the rightmost compound for constraint at this level.
412            if !self.compound_state.in_relative_selector {
413                let mut nested = Self::new(false);
414                let mut iter = s.iter();
415                loop {
416                    for c in &mut iter {
417                        c.visit(&mut nested);
418                        offset += 1;
419                    }
420
421                    let c = iter.next_sequence();
422                    offset += 1;
423                    if c.map_or(true, |c| !c.is_pseudo_element()) {
424                        break;
425                    }
426                }
427                // Every single selector in the list must be constrained.
428                all_constrained &= nested.compound_state.constrained;
429            }
430
431            if offset >= s.len() {
432                continue;
433            }
434
435            // Then, recurse in to check at the deeper level.
436            if Self::has_warning(s, offset, self.compound_state.in_relative_selector) {
437                self.compound_state.constrained = false;
438                if !self.compound_state.in_relative_selector {
439                    self.compound_state.relative_selector_found = true;
440                }
441                return false;
442            }
443        }
444        self.compound_state.constrained |= all_constrained;
445        true
446    }
447
448    fn visit_relative_selector_list(&mut self, list: &[RelativeSelector<Self::Impl>]) -> bool {
449        debug_assert!(
450            !self.compound_state.in_relative_selector,
451            "Nested relative selector"
452        );
453        self.compound_state.relative_selector_found = true;
454
455        for rs in list {
456            // If the inside is unconstrained, we are unconstrained no matter what.
457            if Self::has_warning(&rs.selector, 0, true) {
458                self.compound_state.constrained = false;
459                return false;
460            }
461        }
462        true
463    }
464}
465
466struct SiblingCombinatorAfterScopeSelectorVisitor {
467    right_combinator_is_sibling: bool,
468    found: bool,
469}
470
471impl SiblingCombinatorAfterScopeSelectorVisitor {
472    fn new(right_combinator_is_sibling: bool) -> Self {
473        Self {
474            right_combinator_is_sibling,
475            found: false,
476        }
477    }
478    fn has_warning(selector: &Selector<SelectorImpl>) -> bool {
479        if !selector.has_scope_selector() {
480            return false;
481        }
482        let visitor = SiblingCombinatorAfterScopeSelectorVisitor::new(false);
483        visitor.find_never_matching_scope_selector(selector)
484    }
485
486    fn find_never_matching_scope_selector(mut self, selector: &Selector<SelectorImpl>) -> bool {
487        selector.visit(&mut self);
488        self.found
489    }
490}
491
492impl SelectorVisitor for SiblingCombinatorAfterScopeSelectorVisitor {
493    type Impl = SelectorImpl;
494
495    fn visit_simple_selector(&mut self, c: &Component<Self::Impl>) -> bool {
496        if !matches!(c, Component::Scope | Component::ImplicitScope) {
497            return true;
498        }
499        // e.g. `:scope ~ .a` will never match.
500        if self.right_combinator_is_sibling {
501            self.found = true;
502        }
503        true
504    }
505
506    fn visit_selector_list(
507        &mut self,
508        _list_kind: SelectorListKind,
509        list: &[Selector<Self::Impl>],
510    ) -> bool {
511        for s in list {
512            let list_visitor = Self::new(self.right_combinator_is_sibling);
513            self.found |= list_visitor.find_never_matching_scope_selector(s);
514        }
515        true
516    }
517
518    fn visit_complex_selector(&mut self, combinator_to_right: Option<Combinator>) -> bool {
519        if let Some(c) = combinator_to_right {
520            // Subject compounds' state is determined by the outer visitor. e.g: When there's `:is(.a .b) ~ .c`,
521            // the inner visitor is assumed to be constructed with right_combinator_is_sibling == true.
522            self.right_combinator_is_sibling = c.is_sibling();
523        }
524        true
525    }
526
527    // It's harder to discern if use of :scope <sibling-combinator> is invalid - at least for now, defer.
528}