jsslint_core/
json_output.rs1use crate::catalogue;
22use crate::report::{CategorySummary, ComplianceReport, Violation};
23use serde_json::{json, Map, Value};
24
25pub fn to_payload(report: &ComplianceReport) -> Value {
27 let mut sorted = report.violations.clone();
28 crate::report::sort_violations(&mut sorted);
29
30 let violations: Vec<Value> = sorted.iter().map(violation_value).collect();
31 let categories: Vec<Value> = report.categories.iter().map(category_value).collect();
32 let skipped_rules: Vec<Value> = report
33 .skipped_rules
34 .iter()
35 .map(|s| json!({"rule_id": s.rule_id, "reason": s.reason}))
36 .collect();
37
38 json!({
39 "tool_version": report.tool_version,
40 "journal_id": report.journal_id,
41 "compliance_percentage": report.compliance_percentage,
42 "categories": categories,
43 "violations": violations,
44 "skipped_rules": skipped_rules,
45 })
46}
47
48fn violation_value(v: &Violation) -> Value {
49 let meta = catalogue::lookup(&v.rule_id);
50 let guide_section = meta.map(|m| m.guide_section).unwrap_or("");
51 let guide_url = meta.and_then(|m| m.guide_url);
52 let confidence = meta.map(|m| m.confidence).unwrap_or("high");
53
54 json!({
55 "file": v.file,
56 "line": v.line,
57 "column": v.column,
58 "rule_id": v.rule_id,
59 "severity": v.severity.as_str(),
60 "message": v.message,
61 "suggestion": v.suggestion,
62 "fix": Value::Null,
63 "guide_section": guide_section,
64 "guide_url": guide_url,
65 "confidence": confidence,
66 })
67}
68
69fn category_value(c: &CategorySummary) -> Value {
70 json!({
71 "category_id": c.category_id,
72 "title": c.title,
73 "status": c.status.as_str(),
74 "rules_applied": c.rules_applied,
75 "rules_passed": c.rules_passed,
76 })
77}
78
79pub fn render(report: &ComplianceReport) -> String {
81 let payload = to_payload(report);
82 let mut out = String::new();
83 write_value(&payload, 0, &mut out);
84 out.push('\n');
85 out
86}
87
88pub(crate) fn write_value(value: &Value, indent: usize, out: &mut String) {
93 match value {
94 Value::Null => out.push_str("null"),
95 Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
96 Value::Number(n) => out.push_str(&n.to_string()),
97 Value::String(s) => write_py_string(s, out),
98 Value::Array(items) => write_array(items, indent, out),
99 Value::Object(map) => write_object(map, indent, out),
100 }
101}
102
103fn write_array(items: &[Value], indent: usize, out: &mut String) {
104 if items.is_empty() {
105 out.push_str("[]");
106 return;
107 }
108 out.push('[');
109 let inner_indent = indent + 2;
110 for (i, item) in items.iter().enumerate() {
111 out.push('\n');
112 out.push_str(&" ".repeat(inner_indent));
113 write_value(item, inner_indent, out);
114 if i + 1 < items.len() {
115 out.push(',');
116 }
117 }
118 out.push('\n');
119 out.push_str(&" ".repeat(indent));
120 out.push(']');
121}
122
123fn write_object(map: &Map<String, Value>, indent: usize, out: &mut String) {
124 if map.is_empty() {
125 out.push_str("{}");
126 return;
127 }
128 let mut keys: Vec<&String> = map.keys().collect();
132 keys.sort();
133
134 out.push('{');
135 let inner_indent = indent + 2;
136 for (i, key) in keys.iter().enumerate() {
137 out.push('\n');
138 out.push_str(&" ".repeat(inner_indent));
139 write_py_string(key, out);
140 out.push_str(": ");
141 write_value(&map[*key], inner_indent, out);
142 if i + 1 < keys.len() {
143 out.push(',');
144 }
145 }
146 out.push('\n');
147 out.push_str(&" ".repeat(indent));
148 out.push('}');
149}
150
151fn write_py_string(s: &str, out: &mut String) {
159 out.push('"');
160 for c in s.chars() {
161 match c {
162 '"' => out.push_str("\\\""),
163 '\\' => out.push_str("\\\\"),
164 '\n' => out.push_str("\\n"),
165 '\r' => out.push_str("\\r"),
166 '\t' => out.push_str("\\t"),
167 '\x08' => out.push_str("\\b"),
168 '\x0c' => out.push_str("\\f"),
169 c if (' '..='~').contains(&c) => out.push(c),
170 c => {
171 let cp = c as u32;
172 if cp <= 0xFFFF {
173 out.push_str(&format!("\\u{cp:04x}"));
174 } else {
175 let cp = cp - 0x10000;
176 let high = 0xD800 + (cp >> 10);
177 let low = 0xDC00 + (cp & 0x3FF);
178 out.push_str(&format!("\\u{high:04x}\\u{low:04x}"));
179 }
180 }
181 }
182 }
183 out.push('"');
184}