1use serde::Deserialize;
2use std::path::{Path, PathBuf};
3
4#[derive(Debug, Clone, Deserialize)]
21#[serde(rename_all = "camelCase")]
22#[derive(Default)]
23pub struct Settings {
24 #[serde(default)]
25 pub inlay_hints: InlayHintsSettings,
26 #[serde(default)]
27 pub lint: LintSettings,
28 #[serde(default)]
29 pub file_operations: FileOperationsSettings,
30}
31
32#[derive(Debug, Clone, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct InlayHintsSettings {
36 #[serde(default = "default_true")]
38 pub parameters: bool,
39 #[serde(default = "default_true")]
42 pub gas_estimates: bool,
43}
44
45impl Default for InlayHintsSettings {
46 fn default() -> Self {
47 Self {
48 parameters: true,
49 gas_estimates: true,
50 }
51 }
52}
53
54#[derive(Debug, Clone, Deserialize)]
56#[serde(rename_all = "camelCase")]
57pub struct LintSettings {
58 #[serde(default = "default_true")]
60 pub enabled: bool,
61 #[serde(default)]
65 pub severity: Vec<String>,
66 #[serde(default)]
70 pub only: Vec<String>,
71 #[serde(default)]
74 pub exclude: Vec<String>,
75}
76
77impl Default for LintSettings {
78 fn default() -> Self {
79 Self {
80 enabled: true,
81 severity: Vec::new(),
82 only: Vec::new(),
83 exclude: Vec::new(),
84 }
85 }
86}
87
88#[derive(Debug, Clone, Deserialize)]
90#[serde(rename_all = "camelCase")]
91pub struct FileOperationsSettings {
92 #[serde(default = "default_true")]
94 pub template_on_create: bool,
95 #[serde(default = "default_true")]
97 pub update_imports_on_rename: bool,
98 #[serde(default = "default_true")]
100 pub update_imports_on_delete: bool,
101}
102
103impl Default for FileOperationsSettings {
104 fn default() -> Self {
105 Self {
106 template_on_create: true,
107 update_imports_on_rename: true,
108 update_imports_on_delete: true,
109 }
110 }
111}
112
113fn default_true() -> bool {
114 true
115}
116
117pub fn parse_settings(value: &serde_json::Value) -> Settings {
125 if let Some(inner) = value.get("solidity-language-server")
127 && let Ok(s) = serde_json::from_value::<Settings>(inner.clone())
128 {
129 return s;
130 }
131 serde_json::from_value::<Settings>(value.clone()).unwrap_or_default()
133}
134
135#[derive(Debug, Clone)]
140pub struct FoundryConfig {
141 pub root: PathBuf,
143 pub solc_version: Option<String>,
146 pub remappings: Vec<String>,
149 pub via_ir: bool,
152 pub optimizer: bool,
154 pub optimizer_runs: u64,
157 pub evm_version: Option<String>,
161 pub ignored_error_codes: Vec<u64>,
163 pub sources_dir: String,
165 pub libs: Vec<String>,
168}
169
170impl Default for FoundryConfig {
171 fn default() -> Self {
172 Self {
173 root: PathBuf::new(),
174 solc_version: None,
175 remappings: Vec::new(),
176 via_ir: false,
177 optimizer: false,
178 optimizer_runs: 200,
179 evm_version: None,
180 ignored_error_codes: Vec::new(),
181 sources_dir: "src".to_string(),
182 libs: vec!["lib".to_string()],
183 }
184 }
185}
186
187pub fn load_foundry_config(file_path: &Path) -> FoundryConfig {
194 let toml_path = match find_foundry_toml(file_path) {
195 Some(p) => p,
196 None => {
197 let start = if file_path.is_file() {
198 file_path.parent().unwrap_or(file_path)
199 } else {
200 file_path
201 };
202 let root = find_git_root(start).unwrap_or_else(|| start.to_path_buf());
203 return FoundryConfig {
204 root,
205 ..Default::default()
206 };
207 }
208 };
209 load_foundry_config_from_toml(&toml_path)
210}
211
212pub fn load_foundry_config_from_toml(toml_path: &Path) -> FoundryConfig {
214 let root = toml_path.parent().unwrap_or(Path::new("")).to_path_buf();
215
216 let content = match std::fs::read_to_string(toml_path) {
217 Ok(c) => c,
218 Err(_) => {
219 return FoundryConfig {
220 root,
221 ..Default::default()
222 };
223 }
224 };
225
226 let table: toml::Table = match content.parse() {
227 Ok(t) => t,
228 Err(_) => {
229 return FoundryConfig {
230 root,
231 ..Default::default()
232 };
233 }
234 };
235
236 let profile_name = std::env::var("FOUNDRY_PROFILE").unwrap_or_else(|_| "default".to_string());
237
238 let profile = table
239 .get("profile")
240 .and_then(|p| p.as_table())
241 .and_then(|p| p.get(&profile_name))
242 .and_then(|p| p.as_table());
243
244 let profile = match profile {
245 Some(p) => p,
246 None => {
247 return FoundryConfig {
248 root,
249 ..Default::default()
250 };
251 }
252 };
253
254 let solc_version = profile
256 .get("solc")
257 .or_else(|| profile.get("solc_version"))
258 .and_then(|v| v.as_str())
259 .map(|s| s.to_string());
260
261 let remappings = profile
263 .get("remappings")
264 .and_then(|v| v.as_array())
265 .map(|arr| {
266 arr.iter()
267 .filter_map(|v| v.as_str())
268 .map(|s| s.to_string())
269 .collect()
270 })
271 .unwrap_or_default();
272
273 let via_ir = profile
275 .get("via_ir")
276 .and_then(|v| v.as_bool())
277 .unwrap_or(false);
278
279 let optimizer = profile
281 .get("optimizer")
282 .and_then(|v| v.as_bool())
283 .unwrap_or(false);
284
285 let optimizer_runs = profile
287 .get("optimizer_runs")
288 .and_then(|v| v.as_integer())
289 .map(|v| v as u64)
290 .unwrap_or(200);
291
292 let evm_version = profile
294 .get("evm_version")
295 .and_then(|v| v.as_str())
296 .map(|s| s.to_string());
297
298 let sources_dir = profile
300 .get("src")
301 .and_then(|v| v.as_str())
302 .map(|s| s.to_string())
303 .unwrap_or_else(|| "src".to_string());
304
305 let libs = profile
307 .get("libs")
308 .and_then(|v| v.as_array())
309 .map(|arr| {
310 arr.iter()
311 .filter_map(|v| v.as_str())
312 .map(|s| s.to_string())
313 .collect()
314 })
315 .unwrap_or_else(|| vec!["lib".to_string()]);
316
317 let ignored_error_codes = profile
319 .get("ignored_error_codes")
320 .and_then(|v| v.as_array())
321 .map(|arr| {
322 arr.iter()
323 .filter_map(|v| v.as_integer())
324 .map(|v| v as u64)
325 .collect()
326 })
327 .unwrap_or_default();
328
329 FoundryConfig {
330 root,
331 solc_version,
332 remappings,
333 via_ir,
334 optimizer,
335 optimizer_runs,
336 evm_version,
337 ignored_error_codes,
338 sources_dir,
339 libs,
340 }
341}
342
343#[derive(Debug, Clone)]
345pub struct LintConfig {
346 pub root: PathBuf,
348 pub lint_on_build: bool,
350 pub ignore_patterns: Vec<glob::Pattern>,
352}
353
354impl Default for LintConfig {
355 fn default() -> Self {
356 Self {
357 root: PathBuf::new(),
358 lint_on_build: true,
359 ignore_patterns: Vec::new(),
360 }
361 }
362}
363
364impl LintConfig {
365 pub fn should_lint(&self, file_path: &Path) -> bool {
371 if !self.lint_on_build {
372 return false;
373 }
374
375 if self.ignore_patterns.is_empty() {
376 return true;
377 }
378
379 let relative = file_path.strip_prefix(&self.root).unwrap_or(file_path);
382
383 let rel_str = relative.to_string_lossy();
384
385 for pattern in &self.ignore_patterns {
386 if pattern.matches(&rel_str) {
387 return false;
388 }
389 }
390
391 true
392 }
393}
394
395fn find_git_root(start: &Path) -> Option<PathBuf> {
400 let start = if start.is_file() {
401 start.parent()?
402 } else {
403 start
404 };
405 start
406 .ancestors()
407 .find(|p| p.join(".git").exists())
408 .map(Path::to_path_buf)
409}
410
411pub fn find_foundry_toml(start: &Path) -> Option<PathBuf> {
416 let start_dir = if start.is_file() {
417 start.parent()?
418 } else {
419 start
420 };
421
422 let boundary = find_git_root(start_dir);
423
424 start_dir
425 .ancestors()
426 .take_while(|p| {
428 if let Some(boundary) = &boundary {
429 p.starts_with(boundary)
430 } else {
431 true
432 }
433 })
434 .find(|p| p.join("foundry.toml").is_file())
435 .map(|p| p.join("foundry.toml"))
436}
437
438pub fn load_lint_config(file_path: &Path) -> LintConfig {
442 let toml_path = match find_foundry_toml(file_path) {
443 Some(p) => p,
444 None => return LintConfig::default(),
445 };
446
447 let root = toml_path.parent().unwrap_or(Path::new("")).to_path_buf();
448
449 let content = match std::fs::read_to_string(&toml_path) {
450 Ok(c) => c,
451 Err(_) => {
452 return LintConfig {
453 root,
454 ..Default::default()
455 };
456 }
457 };
458
459 let table: toml::Table = match content.parse() {
460 Ok(t) => t,
461 Err(_) => {
462 return LintConfig {
463 root,
464 ..Default::default()
465 };
466 }
467 };
468
469 let profile_name = std::env::var("FOUNDRY_PROFILE").unwrap_or_else(|_| "default".to_string());
471
472 let lint_table = table
474 .get("profile")
475 .and_then(|p| p.as_table())
476 .and_then(|p| p.get(&profile_name))
477 .and_then(|p| p.as_table())
478 .and_then(|p| p.get("lint"))
479 .and_then(|l| l.as_table());
480
481 let lint_table = match lint_table {
482 Some(t) => t,
483 None => {
484 return LintConfig {
485 root,
486 ..Default::default()
487 };
488 }
489 };
490
491 let lint_on_build = lint_table
493 .get("lint_on_build")
494 .and_then(|v| v.as_bool())
495 .unwrap_or(true);
496
497 let ignore_patterns = lint_table
499 .get("ignore")
500 .and_then(|v| v.as_array())
501 .map(|arr| {
502 arr.iter()
503 .filter_map(|v| v.as_str())
504 .filter_map(|s| glob::Pattern::new(s).ok())
505 .collect()
506 })
507 .unwrap_or_default();
508
509 LintConfig {
510 root,
511 lint_on_build,
512 ignore_patterns,
513 }
514}
515
516pub fn load_lint_config_from_toml(toml_path: &Path) -> LintConfig {
519 let root = toml_path.parent().unwrap_or(Path::new("")).to_path_buf();
520
521 let content = match std::fs::read_to_string(toml_path) {
522 Ok(c) => c,
523 Err(_) => {
524 return LintConfig {
525 root,
526 ..Default::default()
527 };
528 }
529 };
530
531 let table: toml::Table = match content.parse() {
532 Ok(t) => t,
533 Err(_) => {
534 return LintConfig {
535 root,
536 ..Default::default()
537 };
538 }
539 };
540
541 let profile_name = std::env::var("FOUNDRY_PROFILE").unwrap_or_else(|_| "default".to_string());
542
543 let lint_table = table
544 .get("profile")
545 .and_then(|p| p.as_table())
546 .and_then(|p| p.get(&profile_name))
547 .and_then(|p| p.as_table())
548 .and_then(|p| p.get("lint"))
549 .and_then(|l| l.as_table());
550
551 let lint_table = match lint_table {
552 Some(t) => t,
553 None => {
554 return LintConfig {
555 root,
556 ..Default::default()
557 };
558 }
559 };
560
561 let lint_on_build = lint_table
562 .get("lint_on_build")
563 .and_then(|v| v.as_bool())
564 .unwrap_or(true);
565
566 let ignore_patterns = lint_table
567 .get("ignore")
568 .and_then(|v| v.as_array())
569 .map(|arr| {
570 arr.iter()
571 .filter_map(|v| v.as_str())
572 .filter_map(|s| glob::Pattern::new(s).ok())
573 .collect()
574 })
575 .unwrap_or_default();
576
577 LintConfig {
578 root,
579 lint_on_build,
580 ignore_patterns,
581 }
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587 use std::fs;
588
589 #[test]
590 fn test_default_config_lints_everything() {
591 let config = LintConfig::default();
592 assert!(config.should_lint(Path::new("test/MyTest.sol")));
593 assert!(config.should_lint(Path::new("src/Token.sol")));
594 }
595
596 #[test]
597 fn test_lint_on_build_false_skips_all() {
598 let config = LintConfig {
599 lint_on_build: false,
600 ..Default::default()
601 };
602 assert!(!config.should_lint(Path::new("src/Token.sol")));
603 }
604
605 #[test]
606 fn test_ignore_pattern_matches() {
607 let config = LintConfig {
608 root: PathBuf::from("/project"),
609 lint_on_build: true,
610 ignore_patterns: vec![glob::Pattern::new("test/**/*").unwrap()],
611 };
612 assert!(!config.should_lint(Path::new("/project/test/MyTest.sol")));
613 assert!(config.should_lint(Path::new("/project/src/Token.sol")));
614 }
615
616 #[test]
617 fn test_multiple_ignore_patterns() {
618 let config = LintConfig {
619 root: PathBuf::from("/project"),
620 lint_on_build: true,
621 ignore_patterns: vec![
622 glob::Pattern::new("test/**/*").unwrap(),
623 glob::Pattern::new("script/**/*").unwrap(),
624 ],
625 };
626 assert!(!config.should_lint(Path::new("/project/test/MyTest.sol")));
627 assert!(!config.should_lint(Path::new("/project/script/Deploy.sol")));
628 assert!(config.should_lint(Path::new("/project/src/Token.sol")));
629 }
630
631 #[test]
632 fn test_load_lint_config_from_toml() {
633 let dir = tempfile::tempdir().unwrap();
634 let toml_path = dir.path().join("foundry.toml");
635 fs::write(
636 &toml_path,
637 r#"
638[profile.default.lint]
639ignore = ["test/**/*"]
640lint_on_build = true
641"#,
642 )
643 .unwrap();
644
645 let config = load_lint_config_from_toml(&toml_path);
646 assert!(config.lint_on_build);
647 assert_eq!(config.ignore_patterns.len(), 1);
648 assert!(!config.should_lint(&dir.path().join("test/MyTest.sol")));
649 assert!(config.should_lint(&dir.path().join("src/Token.sol")));
650 }
651
652 #[test]
653 fn test_load_lint_config_lint_on_build_false() {
654 let dir = tempfile::tempdir().unwrap();
655 let toml_path = dir.path().join("foundry.toml");
656 fs::write(
657 &toml_path,
658 r#"
659[profile.default.lint]
660lint_on_build = false
661"#,
662 )
663 .unwrap();
664
665 let config = load_lint_config_from_toml(&toml_path);
666 assert!(!config.lint_on_build);
667 assert!(!config.should_lint(&dir.path().join("src/Token.sol")));
668 }
669
670 #[test]
671 fn test_load_lint_config_no_lint_section() {
672 let dir = tempfile::tempdir().unwrap();
673 let toml_path = dir.path().join("foundry.toml");
674 fs::write(
675 &toml_path,
676 r#"
677[profile.default]
678src = "src"
679"#,
680 )
681 .unwrap();
682
683 let config = load_lint_config_from_toml(&toml_path);
684 assert!(config.lint_on_build);
685 assert!(config.ignore_patterns.is_empty());
686 }
687
688 #[test]
689 fn test_find_foundry_toml() {
690 let dir = tempfile::tempdir().unwrap();
691 let toml_path = dir.path().join("foundry.toml");
692 fs::write(&toml_path, "[profile.default]").unwrap();
693
694 let nested = dir.path().join("src");
696 fs::create_dir_all(&nested).unwrap();
697
698 let found = find_foundry_toml(&nested);
699 assert_eq!(found, Some(toml_path));
700 }
701
702 #[test]
703 fn test_load_lint_config_walks_ancestors() {
704 let dir = tempfile::tempdir().unwrap();
705 let toml_path = dir.path().join("foundry.toml");
706 fs::write(
707 &toml_path,
708 r#"
709[profile.default.lint]
710ignore = ["test/**/*"]
711"#,
712 )
713 .unwrap();
714
715 let nested_file = dir.path().join("src/Token.sol");
716 fs::create_dir_all(dir.path().join("src")).unwrap();
717 fs::write(&nested_file, "// solidity").unwrap();
718
719 let config = load_lint_config(&nested_file);
720 assert_eq!(config.root, dir.path());
721 assert_eq!(config.ignore_patterns.len(), 1);
722 }
723
724 #[test]
725 fn test_find_git_root() {
726 let dir = tempfile::tempdir().unwrap();
727 fs::create_dir_all(dir.path().join(".git")).unwrap();
729 let nested = dir.path().join("sub/deep");
730 fs::create_dir_all(&nested).unwrap();
731
732 let root = find_git_root(&nested);
733 assert_eq!(root, Some(dir.path().to_path_buf()));
734 }
735
736 #[test]
737 fn test_find_foundry_toml_stops_at_git_boundary() {
738 let dir = tempfile::tempdir().unwrap();
746
747 fs::write(dir.path().join("foundry.toml"), "[profile.default]").unwrap();
749
750 let repo = dir.path().join("repo");
752 fs::create_dir_all(repo.join(".git")).unwrap();
753 fs::create_dir_all(repo.join("sub")).unwrap();
754
755 let found = find_foundry_toml(&repo.join("sub"));
756 assert_eq!(found, None);
758 }
759
760 #[test]
761 fn test_find_foundry_toml_within_git_boundary() {
762 let dir = tempfile::tempdir().unwrap();
770 let repo = dir.path().join("repo");
771 fs::create_dir_all(repo.join(".git")).unwrap();
772 fs::create_dir_all(repo.join("src")).unwrap();
773 let toml_path = repo.join("foundry.toml");
774 fs::write(&toml_path, "[profile.default]").unwrap();
775
776 let found = find_foundry_toml(&repo.join("src"));
777 assert_eq!(found, Some(toml_path));
778 }
779
780 #[test]
781 fn test_find_foundry_toml_no_git_repo_still_walks_up() {
782 let dir = tempfile::tempdir().unwrap();
785 let toml_path = dir.path().join("foundry.toml");
786 fs::write(&toml_path, "[profile.default]").unwrap();
787
788 let nested = dir.path().join("a/b/c");
789 fs::create_dir_all(&nested).unwrap();
790
791 let found = find_foundry_toml(&nested);
792 assert_eq!(found, Some(toml_path));
793 }
794
795 #[test]
798 fn test_load_foundry_config_compiler_settings() {
799 let dir = tempfile::tempdir().unwrap();
800 let toml_path = dir.path().join("foundry.toml");
801 fs::write(
802 &toml_path,
803 r#"
804[profile.default]
805src = "src"
806solc = '0.8.33'
807optimizer = true
808optimizer_runs = 9999999
809via_ir = true
810evm_version = 'osaka'
811ignored_error_codes = [2394, 6321, 3860, 5574, 2424, 8429, 4591]
812"#,
813 )
814 .unwrap();
815
816 let config = load_foundry_config_from_toml(&toml_path);
817 assert_eq!(config.solc_version, Some("0.8.33".to_string()));
818 assert!(config.optimizer);
819 assert_eq!(config.optimizer_runs, 9999999);
820 assert!(config.via_ir);
821 assert_eq!(config.evm_version, Some("osaka".to_string()));
822 assert_eq!(
823 config.ignored_error_codes,
824 vec![2394, 6321, 3860, 5574, 2424, 8429, 4591]
825 );
826 }
827
828 #[test]
829 fn test_load_foundry_config_defaults_when_absent() {
830 let dir = tempfile::tempdir().unwrap();
831 let toml_path = dir.path().join("foundry.toml");
832 fs::write(
833 &toml_path,
834 r#"
835[profile.default]
836src = "src"
837"#,
838 )
839 .unwrap();
840
841 let config = load_foundry_config_from_toml(&toml_path);
842 assert_eq!(config.solc_version, None);
843 assert!(!config.optimizer);
844 assert_eq!(config.optimizer_runs, 200);
845 assert!(!config.via_ir);
846 assert_eq!(config.evm_version, None);
847 assert!(config.ignored_error_codes.is_empty());
848 assert_eq!(config.libs, vec!["lib".to_string()]);
849 }
850
851 #[test]
852 fn test_load_foundry_config_partial_settings() {
853 let dir = tempfile::tempdir().unwrap();
854 let toml_path = dir.path().join("foundry.toml");
855 fs::write(
856 &toml_path,
857 r#"
858[profile.default]
859via_ir = true
860evm_version = "cancun"
861"#,
862 )
863 .unwrap();
864
865 let config = load_foundry_config_from_toml(&toml_path);
866 assert!(config.via_ir);
867 assert!(!config.optimizer); assert_eq!(config.optimizer_runs, 200); assert_eq!(config.evm_version, Some("cancun".to_string()));
870 assert!(config.ignored_error_codes.is_empty());
871 }
872
873 #[test]
874 fn test_load_foundry_config_libs() {
875 let dir = tempfile::tempdir().unwrap();
876 let toml_path = dir.path().join("foundry.toml");
877 fs::write(
878 &toml_path,
879 r#"
880[profile.default]
881libs = ["lib", "node_modules", "dependencies"]
882"#,
883 )
884 .unwrap();
885
886 let config = load_foundry_config_from_toml(&toml_path);
887 assert_eq!(
888 config.libs,
889 vec![
890 "lib".to_string(),
891 "node_modules".to_string(),
892 "dependencies".to_string()
893 ]
894 );
895 }
896
897 #[test]
898 fn test_load_foundry_config_libs_defaults_when_absent() {
899 let dir = tempfile::tempdir().unwrap();
900 let toml_path = dir.path().join("foundry.toml");
901 fs::write(
902 &toml_path,
903 r#"
904[profile.default]
905src = "src"
906"#,
907 )
908 .unwrap();
909
910 let config = load_foundry_config_from_toml(&toml_path);
911 assert_eq!(config.libs, vec!["lib".to_string()]);
912 }
913
914 #[test]
917 fn test_parse_settings_defaults() {
918 let value = serde_json::json!({});
919 let s = parse_settings(&value);
920 assert!(s.inlay_hints.parameters);
921 assert!(s.inlay_hints.gas_estimates);
922 assert!(s.lint.enabled);
923 assert!(s.file_operations.template_on_create);
924 assert!(s.file_operations.update_imports_on_rename);
925 assert!(s.file_operations.update_imports_on_delete);
926 assert!(s.lint.severity.is_empty());
927 assert!(s.lint.only.is_empty());
928 assert!(s.lint.exclude.is_empty());
929 }
930
931 #[test]
932 fn test_parse_settings_wrapped() {
933 let value = serde_json::json!({
934 "solidity-language-server": {
935 "inlayHints": { "parameters": false, "gasEstimates": false },
936 "lint": {
937 "enabled": true,
938 "severity": ["high", "med"],
939 "only": ["incorrect-shift"],
940 "exclude": ["pascal-case-struct", "mixed-case-variable"]
941 },
942 "fileOperations": {
943 "templateOnCreate": false,
944 "updateImportsOnRename": false,
945 "updateImportsOnDelete": false
946 },
947 }
948 });
949 let s = parse_settings(&value);
950 assert!(!s.inlay_hints.parameters);
951 assert!(!s.inlay_hints.gas_estimates);
952 assert!(s.lint.enabled);
953 assert!(!s.file_operations.template_on_create);
954 assert!(!s.file_operations.update_imports_on_rename);
955 assert!(!s.file_operations.update_imports_on_delete);
956 assert_eq!(s.lint.severity, vec!["high", "med"]);
957 assert_eq!(s.lint.only, vec!["incorrect-shift"]);
958 assert_eq!(
959 s.lint.exclude,
960 vec!["pascal-case-struct", "mixed-case-variable"]
961 );
962 }
963
964 #[test]
965 fn test_parse_settings_direct() {
966 let value = serde_json::json!({
967 "inlayHints": { "parameters": false },
968 "lint": { "enabled": false },
969 "fileOperations": {
970 "templateOnCreate": false,
971 "updateImportsOnRename": false,
972 "updateImportsOnDelete": false
973 }
974 });
975 let s = parse_settings(&value);
976 assert!(!s.inlay_hints.parameters);
977 assert!(!s.lint.enabled);
978 assert!(!s.file_operations.template_on_create);
979 assert!(!s.file_operations.update_imports_on_rename);
980 assert!(!s.file_operations.update_imports_on_delete);
981 }
982
983 #[test]
984 fn test_parse_settings_partial() {
985 let value = serde_json::json!({
986 "solidity-language-server": {
987 "lint": { "exclude": ["unused-import"] }
988 }
989 });
990 let s = parse_settings(&value);
991 assert!(s.inlay_hints.parameters);
993 assert!(s.inlay_hints.gas_estimates);
994 assert!(s.lint.enabled);
996 assert!(s.file_operations.template_on_create);
997 assert!(s.file_operations.update_imports_on_rename);
998 assert!(s.file_operations.update_imports_on_delete);
999 assert!(s.lint.severity.is_empty());
1000 assert!(s.lint.only.is_empty());
1001 assert_eq!(s.lint.exclude, vec!["unused-import"]);
1002 }
1003
1004 #[test]
1005 fn test_parse_settings_empty_wrapped() {
1006 let value = serde_json::json!({
1007 "solidity-language-server": {}
1008 });
1009 let s = parse_settings(&value);
1010 assert!(s.inlay_hints.parameters);
1011 assert!(s.inlay_hints.gas_estimates);
1012 assert!(s.lint.enabled);
1013 assert!(s.file_operations.template_on_create);
1014 assert!(s.file_operations.update_imports_on_rename);
1015 assert!(s.file_operations.update_imports_on_delete);
1016 assert!(s.lint.severity.is_empty());
1017 assert!(s.lint.only.is_empty());
1018 assert!(s.lint.exclude.is_empty());
1019 }
1020
1021 #[test]
1022 fn test_parse_settings_severity_only() {
1023 let value = serde_json::json!({
1024 "solidity-language-server": {
1025 "lint": {
1026 "severity": ["high", "gas"],
1027 "only": ["incorrect-shift", "asm-keccak256"]
1028 }
1029 }
1030 });
1031 let s = parse_settings(&value);
1032 assert_eq!(s.lint.severity, vec!["high", "gas"]);
1033 assert_eq!(s.lint.only, vec!["incorrect-shift", "asm-keccak256"]);
1034 assert!(s.lint.exclude.is_empty());
1035 }
1036}