1use std::borrow::Cow;
20
21pub fn blank_comments(src: &str) -> Result<Cow<'_, str>, CommentError> {
28 let bytes = src.as_bytes();
29 let mut spans: Vec<(usize, usize)> = Vec::new();
30 let mut i = 0;
31 while i < bytes.len() {
32 match bytes[i] {
33 b'"' => i = skip_string(bytes, i),
34 b'/' if bytes.get(i + 1) == Some(&b'/') => {
35 let start = i;
36 while i < bytes.len() && bytes[i] != b'\n' {
37 i += 1;
38 }
39 spans.push((start, i));
40 }
41 b'/' if bytes.get(i + 1) == Some(&b'*') => {
42 let start = i;
43 i += 2;
44 loop {
45 if i + 1 >= bytes.len() {
46 return Err(CommentError {
47 line: line_of(bytes, start),
48 });
49 }
50 if bytes[i] == b'*' && bytes[i + 1] == b'/' {
51 i += 2;
52 break;
53 }
54 i += 1;
55 }
56 spans.push((start, i));
57 }
58 _ => i += 1,
59 }
60 }
61 if spans.is_empty() {
62 return Ok(Cow::Borrowed(src));
63 }
64 let mut out = src.as_bytes().to_vec();
65 for (start, end) in spans {
66 for b in &mut out[start..end] {
67 if *b != b'\n' && *b != b'\r' {
70 *b = b' ';
71 }
72 }
73 }
74 Ok(Cow::Owned(
77 String::from_utf8(out).expect("blanking preserves UTF-8"),
78 ))
79}
80
81#[derive(Debug, Clone, Copy, thiserror::Error)]
85#[error("unterminated block comment opened on line {line}")]
86pub struct CommentError {
87 pub line: usize,
88}
89
90fn skip_string(bytes: &[u8], open: usize) -> usize {
94 let mut i = open + 1;
95 while i < bytes.len() {
96 match bytes[i] {
97 b'\\' => i += 2,
100 b'"' => return i + 1,
101 _ => i += 1,
102 }
103 }
104 i
105}
106
107fn line_of(bytes: &[u8], offset: usize) -> usize {
108 1 + bytes[..offset].iter().filter(|&&b| b == b'\n').count()
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 fn blank_to(src: &str, want_json: &str) -> String {
119 let out = blank_comments(src).expect("no comment error").into_owned();
120 assert_eq!(src.len(), out.len(), "byte length changed");
121 let newlines = |s: &str| s.bytes().filter(|&b| b == b'\n').count();
122 assert_eq!(newlines(src), newlines(&out), "newline count changed");
123 let got: serde_json::Value = serde_json::from_str(&out).expect("blanked text parses");
124 let want: serde_json::Value = serde_json::from_str(want_json).expect("expectation parses");
125 assert_eq!(got, want);
126 out
127 }
128
129 #[test]
130 fn line_comments_go_but_their_lines_stay() {
131 let out = blank_to("{\n // the base coat\n \"a\": 1\n}", r#"{"a": 1}"#);
132 assert!(!out.contains("base coat"));
133 assert_eq!(out.lines().nth(1).unwrap().trim(), "");
134 }
135
136 #[test]
137 fn block_comments_go_inline_and_across_lines() {
138 blank_to("[1, /* two */ 3]", "[1, 3]");
139 let out = blank_to(
140 "{\n/* why\n this\n width */\n\"a\": 1\n}",
141 r#"{"a": 1}"#,
142 );
143 assert!(!out.contains("width"));
144 }
145
146 #[test]
147 fn comment_openers_inside_strings_are_text() {
148 let src = r#"{"url": "https://example.com/{z}/{x}/{y}.pbf", "b": "/* not a comment */"}"#;
150 assert_eq!(blank_comments(src).unwrap(), src);
151 }
152
153 #[test]
154 fn escapes_do_not_end_a_string_early() {
155 let src = r#"{"a": "say \"hi\" // here", "b": 1}"#;
158 assert_eq!(blank_comments(src).unwrap(), src);
159 let out = blank_to("{\"a\": \"back\\\\\" // gone\n}", r#"{"a": "back\\"}"#);
162 assert!(!out.contains("gone"));
163 }
164
165 #[test]
166 fn a_comment_may_end_the_file() {
167 blank_to("{} // done", "{}");
168 blank_to("{} /* done */", "{}");
169 }
170
171 #[test]
172 fn non_ascii_comment_text_survives_blanking() {
173 let out = blank_to("{\n // 幅は 1.2 px\n \"a\": 1\n}", r#"{"a": 1}"#);
174 assert!(!out.contains('幅'));
175 }
176
177 #[test]
178 fn crlf_line_endings_are_preserved() {
179 let out = blank_to("{\r\n // note\r\n \"a\": 1\r\n}", r#"{"a": 1}"#);
180 assert_eq!(out.matches("\r\n").count(), 3);
181 }
182
183 #[test]
184 fn a_document_without_comments_is_not_copied() {
185 let src = r#"{"a": 1}"#;
186 assert!(matches!(blank_comments(src).unwrap(), Cow::Borrowed(_)));
187 }
188
189 #[test]
190 fn unterminated_block_comment_names_its_line() {
191 let err = blank_comments("{\n\"a\": 1\n/* and then nothing").unwrap_err();
192 assert_eq!(err.line, 3);
193 }
194}