1use serde_json::{json, Value};
16
17pub fn generate_settings(solc_version: Option<&str>, remappings: &[String]) -> String {
22 let mut settings = serde_json::Map::new();
23
24 settings.insert("solidity.validation.disable".into(), json!(false));
26 if let Some(version) = solc_version.filter(|v| !v.is_empty()) {
27 settings.insert(
28 "solidity.validation.requiredCompilerVersion".into(),
29 json!(version),
30 );
31 }
32 settings.insert("solidity.linter".into(), json!("solhint"));
33 if !remappings.is_empty() {
34 let remaps: Vec<Value> = remappings.iter().map(|r| json!(r)).collect();
35 settings.insert("solidity.remappings".into(), Value::Array(remaps));
36 }
37
38 settings.insert(
40 "emeraldwalk.runonsave".into(),
41 json!({
42 "commands": [
43 {
44 "match": "\\.sol$",
45 "isAsync": true,
46 "cmd": "forge-guard audit --offline"
47 }
48 ]
49 }),
50 );
51
52 serde_json::to_string_pretty(&Value::Object(settings)).expect("settings serialize") + "\n"
53}
54
55pub fn generate_tasks() -> String {
57 let file_line_pattern = json!({
66 "regexp": "^\\s*📄 (.+):(\\d+)$",
67 "file": 1,
68 "line": 2
69 });
70
71 let matcher = |severity: &str, severity_regex: &str| {
72 json!({
73 "owner": "forge-guard",
74 "severity": severity,
75 "fileLocation": ["relative", "${workspaceFolder}"],
76 "pattern": [
77 {
78 "regexp": severity_regex,
79 "message": 2
80 },
81 file_line_pattern.clone()
82 ]
83 })
84 };
85
86 let tasks = json!({
87 "version": "2.0.0",
88 "tasks": [
89 {
90 "label": "forge-guard: audit",
91 "type": "shell",
92 "command": "forge-guard",
93 "args": ["audit", "--offline"],
94 "group": { "kind": "build", "isDefault": true },
95 "presentation": {
96 "reveal": "always",
97 "panel": "dedicated",
98 "clear": true
99 },
100 "problemMatcher": [
101 matcher("error", "^\\s*\\[(CRITICAL|HIGH)\\] (.+)$"),
102 matcher("warning", "^\\s*\\[(MEDIUM)\\] (.+)$"),
103 matcher("info", "^\\s*\\[(LOW|INFO)\\] (.+)$")
104 ]
105 }
106 ]
107 });
108
109 serde_json::to_string_pretty(&tasks).expect("tasks serialize") + "\n"
110}
111
112pub fn generate_extensions() -> String {
114 let extensions = json!({
115 "recommendations": [
116 "juanblanco.solidity",
117 "emeraldwalk.runonsave",
118 "tamasfe.even-better-toml"
119 ]
120 });
121 serde_json::to_string_pretty(&extensions).expect("extensions serialize") + "\n"
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_settings_contains_validation_rules() {
130 let settings = generate_settings(
131 Some("0.8.23"),
132 &["@openzeppelin/=lib/openzeppelin-contracts/".into()],
133 );
134 let parsed: Value = serde_json::from_str(&settings).unwrap();
135 assert_eq!(parsed["solidity.validation.disable"], json!(false));
136 assert_eq!(
137 parsed["solidity.validation.requiredCompilerVersion"],
138 json!("0.8.23")
139 );
140 assert_eq!(parsed["solidity.linter"], json!("solhint"));
141 assert_eq!(
142 parsed["solidity.remappings"][0],
143 json!("@openzeppelin/=lib/openzeppelin-contracts/")
144 );
145 }
146
147 #[test]
148 fn test_settings_omits_unknown_compiler_version() {
149 let settings = generate_settings(None, &[]);
150 let parsed: Value = serde_json::from_str(&settings).unwrap();
151 assert!(parsed
152 .get("solidity.validation.requiredCompilerVersion")
153 .is_none());
154 assert!(parsed.get("solidity.remappings").is_none());
155 }
156
157 #[test]
158 fn test_settings_has_audit_on_save() {
159 let settings = generate_settings(None, &[]);
160 let parsed: Value = serde_json::from_str(&settings).unwrap();
161 let run_on_save = &parsed["emeraldwalk.runonsave"];
162 assert_eq!(run_on_save["commands"][0]["match"], json!("\\.sol$"));
163 assert_eq!(
164 run_on_save["commands"][0]["cmd"],
165 json!("forge-guard audit --offline")
166 );
167 }
168
169 #[test]
170 fn test_tasks_has_audit_task_with_matchers() {
171 let tasks = generate_tasks();
172 let parsed: Value = serde_json::from_str(&tasks).unwrap();
173 let task = &parsed["tasks"][0];
174 assert_eq!(task["label"], json!("forge-guard: audit"));
175 assert_eq!(task["command"], json!("forge-guard"));
176 assert_eq!(task["group"]["isDefault"], json!(true));
177
178 let matchers = task["problemMatcher"].as_array().unwrap();
179 assert_eq!(matchers.len(), 3);
180 assert_eq!(matchers[0]["severity"], json!("error"));
181 assert_eq!(matchers[1]["severity"], json!("warning"));
182 assert_eq!(matchers[2]["severity"], json!("info"));
183 let re = matchers[0]["pattern"][0]["regexp"].as_str().unwrap();
185 assert!(re.contains("CRITICAL") && re.contains("HIGH"));
186 for m in matchers {
188 let file_pattern = &m["pattern"][1];
189 assert_eq!(file_pattern["file"], json!(1));
190 assert_eq!(file_pattern["line"], json!(2));
191 }
192 }
193
194 #[test]
195 fn test_extensions_recommendations() {
196 let extensions = generate_extensions();
197 let parsed: Value = serde_json::from_str(&extensions).unwrap();
198 let recs = parsed["recommendations"].as_array().unwrap();
199 assert!(recs.iter().any(|r| r == &json!("juanblanco.solidity")));
200 assert!(recs.iter().any(|r| r == &json!("emeraldwalk.runonsave")));
201 }
202}