Unnamed repository; edit this file 'description' to name the repository.
| -rw-r--r-- | crates/rust-analyzer/src/config.rs | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 5b6215c41f..3ba0e47f14 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -2908,6 +2908,7 @@ enum SnippetScopeDef { #[derive(Serialize, Deserialize, Debug, Clone, Default)] #[serde(default)] +#[serde(try_from = "SnippetDefRepr")] pub(crate) struct SnippetDef { #[serde(with = "single_or_array")] #[serde(skip_serializing_if = "Vec::is_empty")] @@ -2931,6 +2932,46 @@ pub(crate) struct SnippetDef { scope: SnippetScopeDef, } +/// Plain deserialization target for [`SnippetDef`]. Both the client JSON +/// config and `rust-analyzer.toml` configs deserialize a `SnippetDef` per +/// map entry, so validating the field combination here (via `TryFrom`) +/// covers both config sources instead of only one. +#[derive(Deserialize, Default)] +#[serde(default)] +struct SnippetDefRepr { + #[serde(with = "single_or_array")] + prefix: Vec<String>, + #[serde(with = "single_or_array")] + postfix: Vec<String>, + #[serde(with = "single_or_array")] + body: Vec<String>, + #[serde(with = "single_or_array")] + requires: Vec<String>, + description: Option<String>, + scope: SnippetScopeDef, +} + +impl TryFrom<SnippetDefRepr> for SnippetDef { + type Error = String; + + fn try_from(repr: SnippetDefRepr) -> Result<Self, Self::Error> { + if repr.scope == SnippetScopeDef::Item && !repr.postfix.is_empty() { + return Err( + "'postfix' is not supported together with '\"scope\": \"item\"'; postfix snippets are not supported in item scope" + .to_owned(), + ); + } + Ok(SnippetDef { + prefix: repr.prefix, + postfix: repr.postfix, + body: repr.body, + requires: repr.requires, + description: repr.description, + scope: repr.scope, + }) + } +} + mod single_or_array { use serde::{Deserialize, Serialize}; @@ -4428,4 +4469,30 @@ mod tests { == Some(Utf8PathBuf::from("other_folder")) )); } + #[test] + fn postfix_snippet_item_scope_is_invalid() { + let mut config = + Config::new(AbsPathBuf::assert(project_root()), Default::default(), vec![], None); + let mut change = ConfigChange::default(); + change.change_client_config(serde_json::json!({ + "completion":{ + "snippets": { + "custom":{ + "foo": { + "postfix": "foo", + "body": "foo", + "scope": "item" + } + } + } + } + })); + let errors; + (config, errors, _) = config.apply_change(change); + assert!(!errors.0.is_empty(), "expected a config error for postfix+item scope"); + assert!( + config.snippets.iter().all(|s| s.postfix_triggers.iter().all(|t| &**t != "foo")), + "invalid snippet should not have been registered" + ); + } } |