use serde_json::{json, Value};
pub fn generate_settings(solc_version: Option<&str>, remappings: &[String]) -> String {
let mut settings = serde_json::Map::new();
settings.insert("solidity.validation.disable".into(), json!(false));
if let Some(version) = solc_version.filter(|v| !v.is_empty()) {
settings.insert(
"solidity.validation.requiredCompilerVersion".into(),
json!(version),
);
}
settings.insert("solidity.linter".into(), json!("solhint"));
if !remappings.is_empty() {
let remaps: Vec<Value> = remappings.iter().map(|r| json!(r)).collect();
settings.insert("solidity.remappings".into(), Value::Array(remaps));
}
settings.insert(
"emeraldwalk.runonsave".into(),
json!({
"commands": [
{
"match": "\\.sol$",
"isAsync": true,
"cmd": "forge-guard audit --offline"
}
]
}),
);
serde_json::to_string_pretty(&Value::Object(settings)).expect("settings serialize") + "\n"
}
pub fn generate_tasks() -> String {
let file_line_pattern = json!({
"regexp": "^\\s*📄 (.+):(\\d+)$",
"file": 1,
"line": 2
});
let matcher = |severity: &str, severity_regex: &str| {
json!({
"owner": "forge-guard",
"severity": severity,
"fileLocation": ["relative", "${workspaceFolder}"],
"pattern": [
{
"regexp": severity_regex,
"message": 2
},
file_line_pattern.clone()
]
})
};
let tasks = json!({
"version": "2.0.0",
"tasks": [
{
"label": "forge-guard: audit",
"type": "shell",
"command": "forge-guard",
"args": ["audit", "--offline"],
"group": { "kind": "build", "isDefault": true },
"presentation": {
"reveal": "always",
"panel": "dedicated",
"clear": true
},
"problemMatcher": [
matcher("error", "^\\s*\\[(CRITICAL|HIGH)\\] (.+)$"),
matcher("warning", "^\\s*\\[(MEDIUM)\\] (.+)$"),
matcher("info", "^\\s*\\[(LOW|INFO)\\] (.+)$")
]
}
]
});
serde_json::to_string_pretty(&tasks).expect("tasks serialize") + "\n"
}
pub fn generate_extensions() -> String {
let extensions = json!({
"recommendations": [
"juanblanco.solidity",
"emeraldwalk.runonsave",
"tamasfe.even-better-toml"
]
});
serde_json::to_string_pretty(&extensions).expect("extensions serialize") + "\n"
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_settings_contains_validation_rules() {
let settings = generate_settings(
Some("0.8.23"),
&["@openzeppelin/=lib/openzeppelin-contracts/".into()],
);
let parsed: Value = serde_json::from_str(&settings).unwrap();
assert_eq!(parsed["solidity.validation.disable"], json!(false));
assert_eq!(
parsed["solidity.validation.requiredCompilerVersion"],
json!("0.8.23")
);
assert_eq!(parsed["solidity.linter"], json!("solhint"));
assert_eq!(
parsed["solidity.remappings"][0],
json!("@openzeppelin/=lib/openzeppelin-contracts/")
);
}
#[test]
fn test_settings_omits_unknown_compiler_version() {
let settings = generate_settings(None, &[]);
let parsed: Value = serde_json::from_str(&settings).unwrap();
assert!(parsed
.get("solidity.validation.requiredCompilerVersion")
.is_none());
assert!(parsed.get("solidity.remappings").is_none());
}
#[test]
fn test_settings_has_audit_on_save() {
let settings = generate_settings(None, &[]);
let parsed: Value = serde_json::from_str(&settings).unwrap();
let run_on_save = &parsed["emeraldwalk.runonsave"];
assert_eq!(run_on_save["commands"][0]["match"], json!("\\.sol$"));
assert_eq!(
run_on_save["commands"][0]["cmd"],
json!("forge-guard audit --offline")
);
}
#[test]
fn test_tasks_has_audit_task_with_matchers() {
let tasks = generate_tasks();
let parsed: Value = serde_json::from_str(&tasks).unwrap();
let task = &parsed["tasks"][0];
assert_eq!(task["label"], json!("forge-guard: audit"));
assert_eq!(task["command"], json!("forge-guard"));
assert_eq!(task["group"]["isDefault"], json!(true));
let matchers = task["problemMatcher"].as_array().unwrap();
assert_eq!(matchers.len(), 3);
assert_eq!(matchers[0]["severity"], json!("error"));
assert_eq!(matchers[1]["severity"], json!("warning"));
assert_eq!(matchers[2]["severity"], json!("info"));
let re = matchers[0]["pattern"][0]["regexp"].as_str().unwrap();
assert!(re.contains("CRITICAL") && re.contains("HIGH"));
for m in matchers {
let file_pattern = &m["pattern"][1];
assert_eq!(file_pattern["file"], json!(1));
assert_eq!(file_pattern["line"], json!(2));
}
}
#[test]
fn test_extensions_recommendations() {
let extensions = generate_extensions();
let parsed: Value = serde_json::from_str(&extensions).unwrap();
let recs = parsed["recommendations"].as_array().unwrap();
assert!(recs.iter().any(|r| r == &json!("juanblanco.solidity")));
assert!(recs.iter().any(|r| r == &json!("emeraldwalk.runonsave")));
}
}