rust-lang/rust-analyzer · error · SsrError

Placeholders should either be $name or ${{name:constraints}}

Error message

Placeholders should either be $name or ${{name:constraints}}

What it means

SSR placeholders must match $name or ${name:constraints}; if the token after '$' is neither a valid identifier nor an opening '{', parse_placeholder bails with this message. The parse then fails and the whole pattern is rejected.

Source

Thrown at crates/ide-ssr/src/parsing.rs:297

                    tokens.next().ok_or_else(|| SsrError::new("Unexpected end of placeholder"))?;
                if token.kind == SyntaxKind::IDENT {
                    name = Some(token.text);
                }
                loop {
                    let token = tokens
                        .next()
                        .ok_or_else(|| SsrError::new("Placeholder is missing closing brace '}'"))?;
                    match token.kind {
                        T![:] => {
                            constraints.push(parse_constraint(tokens)?);
                        }
                        T!['}'] => break,
                        _ => bail!("Unexpected token while parsing placeholder: '{}'", token.text),
                    }
                }
            }
            _ => {
                bail!("Placeholders should either be $name or ${{name:constraints}}");
            }
        }
    }
    let name = name.ok_or_else(|| SsrError::new("Placeholder ($) with no name"))?;
    Ok(Placeholder::new(name, constraints))
}

fn parse_constraint(tokens: &mut std::vec::IntoIter<Token>) -> Result<Constraint, SsrError> {
    let constraint_type = tokens
        .next()
        .ok_or_else(|| SsrError::new("Found end of placeholder while looking for a constraint"))?
        .text
        .to_string();
    match constraint_type.as_str() {
        "kind" => {
            expect_token(tokens, "(")?;
            let t = tokens.next().ok_or_else(|| {
                SsrError::new("Unexpected end of constraint while looking for kind")

View on GitHub (pinned to e8f7e90aa3)

Solutions

  1. Use a bare identifier after $: $name
  2. Or use the braced form with constraints: ${name:kind(literal)}
  3. Remove stray characters between '$' and the placeholder name

Example fix

// before
let pattern = "foo($1)";
// after
let pattern = "foo($arg)";
Defensive patterns

Strategy: validation

Validate before calling

fn has_wellformed_placeholders(p: &str) -> bool {
    let bytes = p.as_bytes();
    let mut i = 0;
    while i < bytes.len() {
        if bytes[i] == b'$' {
            match bytes.get(i + 1) {
                Some(&b) if b.is_ascii_alphabetic() || b == b'_' => {}
                Some(&b'{') => {}
                _ => return false, // $ not followed by ident or '{'
            }
        }
        i += 1;
    }
    true
}

Try / catch

match parse_pattern(pattern) {
    Ok(p) => use_pattern(p),
    Err(e) if e.message.contains("Placeholders should either be") => {
        eprintln!("Fix placeholder: must be $name or ${{name:constraints}}");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Writing patterns like 'foo($' (dollar at end), '$ kind(literal)', or '$-x' where the character after '$' is not an identifier or '{'.

Common situations: Typing $ followed by a digit or punctuation in the IDE structural search box; scripting pattern generation that concatenates '$' with a non-identifier; confusing SSR placeholders with regex groups.

Related errors


AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03). Data as JSON: /api/errors/04063b53e3fb4cec. Report an issue: GitHub.