swc-project/swc · error · swc_css_parser::error::Error

Expected url or function

Error message

Expected url or function

What it means

Thrown by Parse<DocumentPreludeMatchingFunction> while parsing the prelude of @document. Every item in the comma-separated URL-matching list must be either a url() token or another function — the legacy Firefox matching functions url-prefix(), domain(), regexp(), or src(). If the current token is neither, ErrorKind::Expected("url or function") is returned for that position. @document is legacy Firefox-only syntax, so this prelude is stricter than most at-rules.

Source

Thrown at crates/swc_css_parser/src/parser/at_rules/mod.rs:1311

        match cur!(self) {
            tok!("url") => Ok(DocumentPreludeMatchingFunction::Url(self.parse()?)),
            Token::Function {
                value: function_name,
                ..
            } => {
                if matches_eq_ignore_ascii_case!(function_name, "url", "src") {
                    Ok(DocumentPreludeMatchingFunction::Url(self.parse()?))
                } else {
                    // TODO improve me
                    let function = self.parse()?;

                    Ok(DocumentPreludeMatchingFunction::Function(function))
                }
            }
            _ => {
                let span = self.input.cur_span();

                Err(Error::new(span, ErrorKind::Expected("url or function")))
            }
        }
    }
}

impl<I> Parse<MediaQueryList> for Parser<I>
where
    I: ParserInput,
{
    fn parse(&mut self) -> PResult<MediaQueryList> {
        let query: MediaQuery = self.parse()?;
        let mut queries = vec![query];

        // TODO error recovery
        // To parse a <media-query-list> production, parse a comma-separated list of
        // component values, then parse each entry in the returned list as a
        // <media-query>. Its value is the list of <media-query>s so produced.
        loop {

View on GitHub (pinned to 5176682b65)

Solutions

  1. Wrap plain URLs: @document url(https://example.com) { }
  2. Use the matching functions for the other forms: url-prefix("https://"), domain("example.com"), regexp("https:.*")
  3. Drop the @document rule entirely — it is not supported by modern engines and keeps the parse from succeeding
  4. If you gate imports by URL, move the decision into the bundler instead of CSS

Example fix

/* before */
@document "https://example.com/" {
  h1 { color: red; }
}

/* after */
@document url-prefix("https://example.com/") {
  h1 { color: red; }
}
Defensive patterns

Strategy: try-catch

Validate before calling

fn document_prelude_ok(css: &str) -> bool {
    let prelude = css.split_once('{').map_or(css, |(p, _)| p);
    for item in prelude.split(',') {
        let t = item.trim();
        if t.is_empty() {
            return false;
        }
        let head = t.split('(').next().unwrap_or("").trim();
        let is_fn = t.contains('(') && t.ends_with(')');
        let is_url_tok = head.eq_ignore_ascii_case("url") && !t.starts_with('"');
        if !(is_fn || is_url_tok) {
            return false;
        }
    }
    true
}

Type guard

fn is_document_prelude_error(e: &swc_css_parser::error::Error) -> bool {
    matches!(e.kind(), swc_css_parser::error::ErrorKind::Expected(m) if *m == "url or function")
}

Try / catch

match parse_file::<Stylesheet>(&fm, None, config, &mut errors) {
    Ok(sheet) => handle(sheet),
    Err(err) if is_document_prelude_error(&err) => {
        // legacy Firefox-only rule: safest recovery is to drop @document blocks at generation time
        log_and_strip_at_rule(css, *err.into_inner().0, "@document");
    }
    Err(err) => return Err(err.into()),
}

Prevention

When it happens

Trigger: '@document "https://example.com" { }' (a string instead of url(...)), '@document example.com { }' (bare ident), '@document #frag { }', or a comma item that is a number/dimension/string rather than a function token.

Common situations: Porting old Firefox user-stylesheets or UA CSS forward, generated CSS where a URL was interpolated without the url() wrapper, and code that assumes other engines parse @document (they do not, so the whole sheet fails in swc-based tooling).

Related errors


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/6bab1cdaac6f5ccb. Report an issue: GitHub.