fix(json): proper escapes for JSON strings (#101)

Includes two fixes:
1. Things like `\n` are not doubly-escaped any more
2. The backslash `\` itself in the source is escaped
This commit is contained in:
Paul Colomiets 2022-01-08 03:35:24 +02:00 committed by GitHub
parent 52e5ec8064
commit 645ef6a1b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 70 additions and 21 deletions

View File

@ -1,4 +1,4 @@
use std::fmt; use std::fmt::{self, Write};
use crate::{protocol::Diagnostic, ReportHandler, Severity}; use crate::{protocol::Diagnostic, ReportHandler, Severity};
@ -23,20 +23,34 @@ impl Default for JSONReportHandler {
} }
} }
fn escape(input: &str) -> String { struct Escape<'a>(&'a str);
input
.chars() impl fmt::Display for Escape<'_> {
.map(|c| match c { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
'"' => "\\\\\"".to_string(), for c in self.0.chars() {
'\'' => "\\\\'".to_string(), let escape = match c {
'\r' => "\\\\r".to_string(), '\\' => Some(r"\\"),
'\n' => "\\\\n".to_string(), '"' => Some(r#"\""#),
'\t' => "\\\\t".to_string(), '\'' => Some(r"\'"),
'\u{08}' => "\\\\b".to_string(), '\r' => Some(r"\r"),
'\u{0c}' => "\\\\f".to_string(), '\n' => Some(r"\n"),
c => format!("{}", c), '\t' => Some(r"\t"),
}) '\u{08}' => Some(r"\b"),
.collect() '\u{0c}' => Some(r"\f"),
_ => None,
};
if let Some(escape) = escape {
f.write_str(escape)?;
} else {
f.write_char(c)?;
}
}
Ok(())
}
}
fn escape(input: &'_ str) -> Escape<'_> {
Escape(input)
} }
impl JSONReportHandler { impl JSONReportHandler {
@ -93,7 +107,13 @@ impl JSONReportHandler {
} }
if let Some(relateds) = diagnostic.related() { if let Some(relateds) = diagnostic.related() {
write!(f, r#""related": ["#)?; write!(f, r#""related": ["#)?;
let mut add_comma = false;
for related in relateds { for related in relateds {
if add_comma {
write!(f, ",")?;
} else {
add_comma = true;
}
self.render_report(f, related)?; self.render_report(f, related)?;
} }
write!(f, "]")?; write!(f, "]")?;
@ -127,3 +147,9 @@ impl ReportHandler for JSONReportHandler {
self.render_report(f, diagnostic) self.render_report(f, diagnostic)
} }
} }
#[test]
fn test_escape() {
assert_eq!(escape("a\nb").to_string(), r"a\nb");
assert_eq!(escape("C:\\Miette").to_string(), r"C:\\Miette");
}

View File

@ -497,7 +497,7 @@ mod json_report_handler {
println!("Error: {}", out); println!("Error: {}", out);
let expected: String = r#" let expected: String = r#"
{ {
"message": "wtf?!\\nit broke :(", "message": "wtf?!\nit broke :(",
"code": "oops::my::bad", "code": "oops::my::bad",
"severity": "error", "severity": "error",
"help": "try doing it better next time?", "help": "try doing it better next time?",
@ -737,11 +737,18 @@ mod json_report_handler {
let err = MyBad { let err = MyBad {
src: NamedSource::new("bad_file.rs", src.clone()), src: NamedSource::new("bad_file.rs", src.clone()),
highlight: (9, 4).into(), highlight: (9, 4).into(),
related: vec![MyBad { related: vec![
src: NamedSource::new("bad_file2.rs", src), MyBad {
highlight: (0, 6).into(), src: NamedSource::new("bad_file2.rs", src.clone()),
related: vec![], highlight: (0, 6).into(),
}], related: vec![],
},
MyBad {
src: NamedSource::new("bad_file3.rs", src),
highlight: (0, 6).into(),
related: vec![],
},
],
}; };
let out = fmt_report(err.into()); let out = fmt_report(err.into());
println!("Error: {}", out); println!("Error: {}", out);
@ -777,6 +784,22 @@ mod json_report_handler {
} }
], ],
"related": [] "related": []
},{
"message": "oops!",
"code": "oops::my::bad",
"severity": "error",
"help": "try doing it better next time?",
"filename": "bad_file3.rs",
"labels": [
{
"label": "this bit here",
"span": {
"offset": 0,
"length": 6
}
}
],
"related": []
}] }]
}"# }"#
.lines() .lines()