Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #22937 from Joshuaisikah/warn-postfix-snippet-item-scope
Report a config error for postfix snippets with item scope
Chayim Refael Friedman 11 days ago
parent a9c0ce4 · parent fbe2a73 · commit 126e557
-rw-r--r--crates/rust-analyzer/src/config.rs67
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"
+ );
+ }
}