From 645ef6a1b66a9a05f97883535f162cab4d0483f5 Mon Sep 17 00:00:00 2001 From: Paul Colomiets Date: Sat, 8 Jan 2022 03:35:24 +0200 Subject: [PATCH] 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 --- src/handlers/json.rs | 56 ++++++++++++++++++++++++++++++++------------ tests/test_json.rs | 35 ++++++++++++++++++++++----- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/handlers/json.rs b/src/handlers/json.rs index 7508db0..eb47abc 100644 --- a/src/handlers/json.rs +++ b/src/handlers/json.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::fmt::{self, Write}; use crate::{protocol::Diagnostic, ReportHandler, Severity}; @@ -23,20 +23,34 @@ impl Default for JSONReportHandler { } } -fn escape(input: &str) -> String { - input - .chars() - .map(|c| match c { - '"' => "\\\\\"".to_string(), - '\'' => "\\\\'".to_string(), - '\r' => "\\\\r".to_string(), - '\n' => "\\\\n".to_string(), - '\t' => "\\\\t".to_string(), - '\u{08}' => "\\\\b".to_string(), - '\u{0c}' => "\\\\f".to_string(), - c => format!("{}", c), - }) - .collect() +struct Escape<'a>(&'a str); + +impl fmt::Display for Escape<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for c in self.0.chars() { + let escape = match c { + '\\' => Some(r"\\"), + '"' => Some(r#"\""#), + '\'' => Some(r"\'"), + '\r' => Some(r"\r"), + '\n' => Some(r"\n"), + '\t' => Some(r"\t"), + '\u{08}' => Some(r"\b"), + '\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 { @@ -93,7 +107,13 @@ impl JSONReportHandler { } if let Some(relateds) = diagnostic.related() { write!(f, r#""related": ["#)?; + let mut add_comma = false; for related in relateds { + if add_comma { + write!(f, ",")?; + } else { + add_comma = true; + } self.render_report(f, related)?; } write!(f, "]")?; @@ -127,3 +147,9 @@ impl ReportHandler for JSONReportHandler { 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"); +} diff --git a/tests/test_json.rs b/tests/test_json.rs index c56217c..f7ad3a6 100644 --- a/tests/test_json.rs +++ b/tests/test_json.rs @@ -497,7 +497,7 @@ mod json_report_handler { println!("Error: {}", out); let expected: String = r#" { - "message": "wtf?!\\nit broke :(", + "message": "wtf?!\nit broke :(", "code": "oops::my::bad", "severity": "error", "help": "try doing it better next time?", @@ -737,11 +737,18 @@ mod json_report_handler { let err = MyBad { src: NamedSource::new("bad_file.rs", src.clone()), highlight: (9, 4).into(), - related: vec![MyBad { - src: NamedSource::new("bad_file2.rs", src), - highlight: (0, 6).into(), - related: vec![], - }], + related: vec![ + MyBad { + src: NamedSource::new("bad_file2.rs", src.clone()), + 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()); println!("Error: {}", out); @@ -777,6 +784,22 @@ mod json_report_handler { } ], "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()