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::{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}
286
287impl SelectorWarningKind {
288    /// Get all warnings for this selector.
289    pub fn from_selector(selector: &Selector<SelectorImpl>) -> Vec<Self> {
290        let mut result = vec![];
291        if UnconstrainedRelativeSelectorVisitor::has_warning(selector, 0, false) {
292            result.push(SelectorWarningKind::UnconstraintedRelativeSelector);
293        }
294        result
295    }
296}
297
298/// Per-compound state for finding unconstrained relative selectors.
299struct PerCompoundState {
300    /// Is there a relative selector in this compound?
301    relative_selector_found: bool,
302    /// Is this compound constrained in any way?
303    constrained: bool,
304    /// Nested below, or inside relative selector?
305    in_relative_selector: bool,
306}
307
308impl PerCompoundState {
309    fn new(in_relative_selector: bool) -> Self {
310        Self {
311            relative_selector_found: false,
312            constrained: false,
313            in_relative_selector,
314        }
315    }
316}
317
318/// Visitor to check if there's any unconstrained relative selector.
319struct UnconstrainedRelativeSelectorVisitor {
320    compound_state: PerCompoundState,
321}
322
323impl UnconstrainedRelativeSelectorVisitor {
324    fn new(in_relative_selector: bool) -> Self {
325        Self {
326            compound_state: PerCompoundState::new(in_relative_selector),
327        }
328    }
329
330    fn has_warning(
331        selector: &Selector<SelectorImpl>,
332        offset: usize,
333        in_relative_selector: bool,
334    ) -> bool {
335        let relative_selector = matches!(
336            selector.iter_raw_parse_order_from(0).next().unwrap(),
337            Component::RelativeSelectorAnchor
338        );
339        debug_assert!(
340            !relative_selector || offset == 0,
341            "Checking relative selector from non-rightmost?"
342        );
343        let mut visitor = Self::new(in_relative_selector);
344        let mut iter = if relative_selector {
345            selector.iter_skip_relative_selector_anchor()
346        } else {
347            selector.iter_from(offset)
348        };
349        loop {
350            visitor.compound_state = PerCompoundState::new(in_relative_selector);
351
352            for s in &mut iter {
353                s.visit(&mut visitor);
354            }
355
356            if (visitor.compound_state.relative_selector_found
357                || visitor.compound_state.in_relative_selector)
358                && !visitor.compound_state.constrained
359            {
360                return true;
361            }
362
363            if iter.next_sequence().is_none() {
364                break;
365            }
366        }
367        false
368    }
369}
370
371impl SelectorVisitor for UnconstrainedRelativeSelectorVisitor {
372    type Impl = SelectorImpl;
373
374    fn visit_simple_selector(&mut self, c: &Component<Self::Impl>) -> bool {
375        match c {
376            // Deferred to visit_selector_list
377            Component::Is(..)
378            | Component::Where(..)
379            | Component::Negation(..)
380            | Component::Has(..) => (),
381            Component::ExplicitUniversalType => (),
382            _ => self.compound_state.constrained |= true,
383        };
384        true
385    }
386
387    fn visit_selector_list(
388        &mut self,
389        _list_kind: SelectorListKind,
390        list: &[Selector<Self::Impl>],
391    ) -> bool {
392        let mut all_constrained = true;
393        for s in list {
394            let mut offset = 0;
395            // First, check the rightmost compound for constraint at this level.
396            if !self.compound_state.in_relative_selector {
397                let mut nested = Self::new(false);
398                let mut iter = s.iter();
399                loop {
400                    for c in &mut iter {
401                        c.visit(&mut nested);
402                        offset += 1;
403                    }
404
405                    let c = iter.next_sequence();
406                    offset += 1;
407                    if c.map_or(true, |c| !c.is_pseudo_element()) {
408                        break;
409                    }
410                }
411                // Every single selector in the list must be constrained.
412                all_constrained &= nested.compound_state.constrained;
413            }
414
415            if offset >= s.len() {
416                continue;
417            }
418
419            // Then, recurse in to check at the deeper level.
420            if Self::has_warning(s, offset, self.compound_state.in_relative_selector) {
421                self.compound_state.constrained = false;
422                if !self.compound_state.in_relative_selector {
423                    self.compound_state.relative_selector_found = true;
424                }
425                return false;
426            }
427        }
428        self.compound_state.constrained |= all_constrained;
429        true
430    }
431
432    fn visit_relative_selector_list(&mut self, list: &[RelativeSelector<Self::Impl>]) -> bool {
433        debug_assert!(
434            !self.compound_state.in_relative_selector,
435            "Nested relative selector"
436        );
437        self.compound_state.relative_selector_found = true;
438
439        for rs in list {
440            // If the inside is unconstrained, we are unconstrained no matter what.
441            if Self::has_warning(&rs.selector, 0, true) {
442                self.compound_state.constrained = false;
443                return false;
444            }
445        }
446        true
447    }
448}