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