{"record":{"id":"f25ce6d9c9ba81d6","repo":"rust-lang/rust-analyzer","slug":"replacement-contains-undefined-placeholders","errorCode":null,"errorMessage":"Replacement contains undefined placeholders: {}","messagePattern":"Replacement contains undefined placeholders: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ide-ssr/src/parsing.rs","lineNumber":252,"sourceCode":"    let mut defined_placeholders = FxHashSet::default();\n    for p in &rule.pattern.tokens {\n        if let PatternElement::Placeholder(placeholder) = p {\n            defined_placeholders.insert(&placeholder.ident);\n        }\n    }\n    let mut undefined = Vec::new();\n    for p in &rule.template.tokens {\n        if let PatternElement::Placeholder(placeholder) = p {\n            if !defined_placeholders.contains(&placeholder.ident) {\n                undefined.push(placeholder.ident.to_string());\n            }\n            if !placeholder.constraints.is_empty() {\n                bail!(\"Replacement placeholders cannot have constraints\");\n            }\n        }\n    }\n    if !undefined.is_empty() {\n        bail!(\"Replacement contains undefined placeholders: {}\", undefined.join(\", \"));\n    }\n    Ok(())\n}\n\nfn tokenize(source: &str) -> Result<Vec<Token>, SsrError> {\n    let lexed = parser::LexedStr::new(parser::Edition::CURRENT, source);\n    if let Some((_, first_error)) = lexed.errors().next() {\n        bail!(\"Failed to parse pattern: {}\", first_error);\n    }\n    let mut tokens: Vec<Token> = Vec::new();\n    for i in 0..lexed.len() {\n        tokens.push(Token { kind: lexed.kind(i), text: lexed.text(i).into() });\n    }\n    Ok(tokens)\n}\n\nfn parse_placeholder(tokens: &mut std::vec::IntoIter<Token>) -> Result<Placeholder, SsrError> {\n    let mut name = None;","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/rust-lang/rust-analyzer/blob/e8f7e90aa3e7b26aa9a000200f606c1078da99ec/crates/ide-ssr/src/parsing.rs#L234-L270","documentation":"Every placeholder used in the replacement template must have been defined (bound) in the search pattern; otherwise SSR would not know what text to substitute. `validate_rule` collects replacement placeholders not present in the search pattern's `defined_placeholders` and bails listing them.","triggerScenarios":"`SsrPattern::from_str` with a rule whose replacement references an undeclared placeholder, e.g. `foo($x) ==>> bar($x, $y)` where `$y` never appears in the search pattern.","commonSituations":"Typos in placeholder names (case mismatch `$Arg` vs `$arg`); hand-editing the replacement and adding a new placeholder without updating the search; generating rules programmatically where template and pattern come from different sources.","solutions":["Add the missing placeholder to the search pattern (e.g. `foo($x, $y) ==>> bar($x, $y)`).","Remove the undefined placeholder from the replacement template.","Check for case/typo mismatches between the two sides of `==>>`; names must match exactly.","If the replacement needs constant text, write it literally instead of as a `$placeholder`."],"exampleFix":"// before\n\"foo($x) ==>> bar($x, $y)\"          // $y undefined\n// after\n\"foo($x, $y) ==>> bar($x, $y)\"","handlingStrategy":"validation","validationCode":"fn defined_placeholders(rule: &str) -> (Vec<String>, Vec<String>) {\n    let (search, repl) = rule.split_once(\"==>>\").unwrap_or((rule, \"\"));\n    let grab = |s: &str| {\n        s.split(|c: char| !c.is_alphanumeric() && c != '_')\n            .filter_map(|w| w.strip_prefix('$').map(String::from))\n            .collect::<Vec<_>>()\n    };\n    (grab(search), grab(repl))\n}\nlet (defined, used) = defined_placeholders(rule);\nlet missing: Vec<_> = used.iter().filter(|u| !defined.contains(u)).collect();\nanyhow::ensure!(missing.is_empty(), \"undefined replacement placeholders: {:?}\", missing);","typeGuard":"fn all_replacement_placeholders_defined(search: &str, replacement: &str) -> bool {\n    let defined: std::collections::HashSet<_> =\n        search.split(|c: char| !c.is_alphanumeric() && c != '_')\n            .filter_map(|w| w.strip_prefix('$')).collect();\n    replacement.split(|c: char| !c.is_alphanumeric() && c != '_')\n        .filter_map(|w| w.strip_prefix('$'))\n        .all(|p| defined.contains(p))\n}","tryCatchPattern":"match SsrPattern::from_str(rule) {\n    Ok(p) => p,\n    Err(e) if e.to_string().starts_with(\"Replacement contains undefined placeholders\") => {\n        eprintln!(\"{} — add these placeholders to the search pattern or remove them\", e);\n        return Ok(());\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Extract the placeholder set from both sides of ==>> and diff them before submitting the rule","Watch for case-sensitivity typos in placeholder names ($arg vs $Arg)","When generating rules programmatically, derive the template from captured names, never free-form text","Write literal replacement text directly instead of inventing $placeholders for constants"],"tags":["rust","ide-ssr","placeholder","undefined-variable","rule-validation"],"backgroundTag":"undefined-placeholder-in-replacement","analyzedSha":"e8f7e90aa3e7b26aa9a000200f606c1078da99ec","analyzedAt":"2026-09-03T21:08:06.959Z","contentChangedAt":"2026-09-03T21:08:06.959Z","schemaVersion":2},"datasetVersion":"2026-09-11T07:07:21.782Z"}