rust_analyzer_mcp/
settings.rs1use anyhow::{bail, Result};
8use serde_json::{json, Value};
9
10#[derive(Clone, Debug, Default, PartialEq, Eq)]
12pub enum Features {
13 #[default]
15 Default,
16 All,
18 Named(Vec<String>),
21}
22
23#[derive(Clone, Debug, Default, PartialEq, Eq)]
25pub struct Settings {
26 features: Features,
27 no_default_features: bool,
28 overrides: Vec<(String, Value)>,
30}
31
32impl Settings {
33 pub fn enable_all_features(&mut self) -> Result<()> {
35 if self.features != Features::Default {
36 bail!("--all-features cannot be combined with --features");
37 }
38
39 self.features = Features::All;
40 Ok(())
41 }
42
43 pub fn enable_features(&mut self, list: &str) -> Result<()> {
45 let named: Vec<String> = list
46 .split([',', ' ', '\t'])
47 .filter(|feature| !feature.is_empty())
48 .map(str::to_string)
49 .collect();
50 if named.is_empty() {
51 bail!("--features needs at least one feature name");
52 }
53
54 match &mut self.features {
55 Features::All => bail!("--features cannot be combined with --all-features"),
58 Features::Named(features) => features.extend(named),
59 features @ Features::Default => *features = Features::Named(named),
60 }
61 Ok(())
62 }
63
64 pub fn disable_default_features(&mut self) -> Result<()> {
66 if self.features == Features::All {
67 bail!("--no-default-features cannot be combined with --all-features");
68 }
69
70 self.no_default_features = true;
71 Ok(())
72 }
73
74 pub fn set(&mut self, assignment: &str) -> Result<()> {
80 let Some((key, value)) = assignment.split_once('=') else {
81 bail!("--config needs a KEY=VALUE, such as check.command=clippy");
82 };
83 if key.is_empty() || key.split('.').any(str::is_empty) {
84 bail!("'{key}' is not a setting name");
85 }
86
87 let value = serde_json::from_str(value).unwrap_or_else(|_| json!(value));
88 self.overrides.push((key.to_string(), value));
89 Ok(())
90 }
91
92 pub fn to_json(&self) -> Value {
94 let mut settings = json!({
95 "cargo": {
96 "buildScripts": {
97 "enable": true
98 }
99 },
100 "checkOnSave": true,
101 "diagnostics": {
102 "enable": true,
103 "experimental": {
104 "enable": true
105 }
106 },
107 "procMacro": {
108 "enable": true
109 }
110 });
111
112 match &self.features {
115 Features::Default => {}
116 Features::All => settings["cargo"]["features"] = json!("all"),
117 Features::Named(features) => settings["cargo"]["features"] = json!(features),
118 }
119 if self.no_default_features {
120 settings["cargo"]["noDefaultFeatures"] = json!(true);
121 }
122
123 for (key, value) in &self.overrides {
124 set_at(&mut settings, key, value.clone());
125 }
126
127 settings
128 }
129}
130
131fn set_at(settings: &mut Value, key: &str, value: Value) {
133 let mut at = settings;
134 let mut names = key.split('.').peekable();
135
136 while let Some(name) = names.next() {
137 if names.peek().is_none() {
138 at[name] = value;
139 return;
140 }
141
142 if !at[name].is_object() {
145 at[name] = json!({});
146 }
147 at = &mut at[name];
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn the_defaults_leave_features_to_the_manifest() {
157 let settings = Settings::default().to_json();
158
159 assert_eq!(settings["cargo"]["features"], Value::Null);
160 assert_eq!(settings["cargo"]["noDefaultFeatures"], Value::Null);
161 }
162
163 #[test]
164 fn all_features_is_the_word_rust_analyzer_knows() {
165 let mut settings = Settings::default();
166 settings.enable_all_features().unwrap();
167
168 assert_eq!(settings.to_json()["cargo"]["features"], json!("all"));
171 }
172
173 #[test]
174 fn features_are_named_one_by_one() {
175 let mut settings = Settings::default();
176 settings.enable_features("serde,tokio").unwrap();
177 settings.enable_features("other/thing extra").unwrap();
178
179 assert_eq!(
180 settings.to_json()["cargo"]["features"],
181 json!(["serde", "tokio", "other/thing", "extra"])
182 );
183 }
184
185 #[test]
186 fn features_that_name_nothing_are_refused() {
187 assert!(Settings::default().enable_features("").is_err());
188 assert!(Settings::default().enable_features(" , ").is_err());
189 }
190
191 #[test]
192 fn all_features_cannot_be_narrowed() {
193 let mut settings = Settings::default();
195 settings.enable_all_features().unwrap();
196
197 assert!(settings.enable_features("serde").is_err());
198 assert!(settings.disable_default_features().is_err());
199
200 let mut settings = Settings::default();
201 settings.enable_features("serde").unwrap();
202 assert!(settings.enable_all_features().is_err());
203 }
204
205 #[test]
206 fn default_features_can_be_left_out() {
207 let mut settings = Settings::default();
208 settings.disable_default_features().unwrap();
209 settings.enable_features("serde").unwrap();
210
211 let settings = settings.to_json();
212 assert_eq!(settings["cargo"]["noDefaultFeatures"], json!(true));
213 assert_eq!(settings["cargo"]["features"], json!(["serde"]));
214 }
215
216 #[test]
217 fn any_setting_can_be_named_outright() {
218 let mut settings = Settings::default();
219 settings.set("check.command=clippy").unwrap();
220 settings
221 .set("cargo.target=x86_64-unknown-linux-gnu")
222 .unwrap();
223 settings.set("check.extraArgs=[\"--tests\"]").unwrap();
224 settings.set("procMacro.enable=false").unwrap();
225
226 let settings = settings.to_json();
227 assert_eq!(settings["check"]["command"], json!("clippy"));
228 assert_eq!(
229 settings["cargo"]["target"],
230 json!("x86_64-unknown-linux-gnu")
231 );
232 assert_eq!(settings["check"]["extraArgs"], json!(["--tests"]));
233 assert_eq!(settings["procMacro"]["enable"], json!(false));
235 }
236
237 #[test]
238 fn an_override_replaces_what_stands_in_its_way() {
239 let mut settings = Settings::default();
240 settings.set("checkOnSave.enable=true").unwrap();
241
242 assert_eq!(settings.to_json()["checkOnSave"], json!({ "enable": true }));
245 }
246
247 #[test]
248 fn settings_that_name_nothing_are_refused() {
249 assert!(Settings::default().set("check.command").is_err());
250 assert!(Settings::default().set("=clippy").is_err());
251 assert!(Settings::default().set("check..command=clippy").is_err());
252 }
253}