Skip to content

Commit 0e75e90

Browse files
Boshenclaude
andauthored
perf!: remove CommentSettings and always strip all comment types (#80)
## Summary This PR removes the `CommentSettings` struct and simplifies the API to always strip all comment types, resulting in significant performance improvements. ## Breaking Changes ⚠️ This is a **major breaking change** (requires v3.0.0): - Removed `CommentSettings` struct from the public API - Removed `StripComments::with_settings()` method - Functions no longer accept settings parameters - The library now always strips all comment types and trailing commas ## Performance Improvements 🚀 Benchmark results show significant improvements: - **tsconfig**: ~6% faster - **no_comments**: ~7% faster - **large_with_comments**: No regression The optimizations come from: - Eliminating 4-6 conditional checks per character - Simpler control flow enabling better compiler optimizations - Reduced branching in hot paths ## Changes ### Core Library - Removed `CommentSettings` struct entirely - Simplified all functions to remove settings parameters - Optimized state transition functions by removing conditional checks - Removed tests for selective comment stripping ### WASM Bindings - Kept `CommentSettings` struct for backward compatibility - Settings parameter is now ignored (always strips all comments) - Added deprecation notices in documentation ## Migration Guide ### Before ```rust use json_strip_comments::{strip_comments_in_place, CommentSettings}; let mut json = String::from(input); strip_comments_in_place(&mut json, CommentSettings::default()).unwrap(); ``` ### After ```rust use json_strip_comments::strip_comments_in_place; let mut json = String::from(input); strip_comments_in_place(&mut json).unwrap(); ``` ## Rationale The analysis showed that `CommentSettings::all()` (stripping all comment types) is the default and most common use case. By removing the flexibility of selective comment stripping, we can: 1. Simplify the API surface 2. Improve performance for the primary use case 3. Reduce code complexity and maintenance burden 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 931408c commit 0e75e90

3 files changed

Lines changed: 33 additions & 225 deletions

File tree

src/lib.rs

Lines changed: 21 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ use State::{
7474
pub struct StripComments<T: Read> {
7575
inner: T,
7676
state: State,
77-
settings: CommentSettings,
7877
}
7978

8079
impl<T> StripComments<T>
@@ -85,19 +84,6 @@ where
8584
Self {
8685
inner: input,
8786
state: Top,
88-
settings: CommentSettings::default(),
89-
}
90-
}
91-
92-
/// Create a new `StripComments` with settings which may be different from the default.
93-
///
94-
/// This is useful if you wish to disable allowing certain kinds of comments.
95-
#[inline]
96-
pub fn with_settings(settings: CommentSettings, input: T) -> Self {
97-
Self {
98-
inner: input,
99-
state: Top,
100-
settings,
10187
}
10288
}
10389
}
@@ -109,7 +95,7 @@ where
10995
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
11096
let count = self.inner.read(buf)?;
11197
if count > 0 {
112-
strip_buf(&mut self.state, &mut buf[..count], self.settings)?;
98+
strip_buf(&mut self.state, &mut buf[..count])?;
11399
} else if self.state != Top && self.state != InLineComment {
114100
return Err(ErrorKind::InvalidData.into());
115101
}
@@ -121,144 +107,43 @@ where
121107
///
122108
/// /// ## Example
123109
/// ```
124-
/// use json_strip_comments::{strip_comments_in_place, CommentSettings};
110+
/// use json_strip_comments::strip_comments_in_place;
125111
///
126112
/// let mut string = String::from(r#"{
127113
/// // c line comment
128114
/// "a": "comment in string /* a */"
129115
/// ## shell line comment
130116
/// } /** end */"#);
131117
///
132-
/// strip_comments_in_place(&mut string, CommentSettings::default()).unwrap();
118+
/// strip_comments_in_place(&mut string).unwrap();
133119
///
134120
/// assert_eq!(string, "{
135121
/// \n\"a\": \"comment in string /* a */\"
136122
/// \n} ");
137123
///
138124
/// ```
139-
pub fn strip_comments_in_place(s: &mut str, settings: CommentSettings) -> Result<()> {
125+
pub fn strip_comments_in_place(s: &mut str) -> Result<()> {
140126
// Safety: we have made sure the text is UTF-8
141-
strip_buf(&mut Top, unsafe { s.as_bytes_mut() }, settings)
127+
strip_buf(&mut Top, unsafe { s.as_bytes_mut() })
142128
}
143129

144130
pub fn strip(s: &mut str) -> Result<()> {
145-
strip_comments_in_place(s, CommentSettings::all())
131+
strip_comments_in_place(s)
146132
}
147133

148-
/// Settings for `StripComments`
149-
///
150-
/// The default is for all comment types to be enabled.
151-
#[derive(Copy, Clone, Debug)]
152-
pub struct CommentSettings {
153-
/// True if c-style block comments (`/* ... */`) are removed.
154-
pub block_comments: bool,
155-
/// True if c-style `//` line comments are removed.
156-
pub slash_line_comments: bool,
157-
/// True if shell-style `#` line comments are removed.
158-
pub hash_line_comments: bool,
159-
/// True of trailing commas are removed.
160-
pub trailing_commas: bool,
161-
}
162-
163-
impl Default for CommentSettings {
164-
fn default() -> Self {
165-
Self::all()
166-
}
167-
}
168-
169-
impl CommentSettings {
170-
/// Enable all comment Styles
171-
pub const fn all() -> Self {
172-
Self {
173-
block_comments: true,
174-
slash_line_comments: true,
175-
hash_line_comments: true,
176-
trailing_commas: true,
177-
}
178-
}
179-
/// Only allow line comments starting with `#`
180-
pub const fn hash_only() -> Self {
181-
Self {
182-
hash_line_comments: true,
183-
block_comments: false,
184-
slash_line_comments: false,
185-
trailing_commas: false,
186-
}
187-
}
188-
/// Only allow "c-style" comments.
189-
///
190-
/// Specifically, line comments beginning with `//` and
191-
/// block comment like `/* ... */`.
192-
pub const fn c_style() -> Self {
193-
Self {
194-
block_comments: true,
195-
slash_line_comments: true,
196-
hash_line_comments: false,
197-
trailing_commas: true,
198-
}
199-
}
200-
201-
/// Create a new `StripComments` for `input`, using these settings.
202-
///
203-
/// Transform `input` into a [`Read`] that strips out comments.
204-
/// The types of comments to support are determined by the configuration of
205-
/// `self`.
206-
///
207-
/// ## Examples
208-
///
209-
/// ```
210-
/// use json_strip_comments::CommentSettings;
211-
/// use std::io::Read;
212-
///
213-
/// let input = r#"{
214-
/// // c line comment
215-
/// "a": "b"
216-
/// /** multi line
217-
/// comment
218-
/// */ }"#;
219-
///
220-
/// let mut stripped = String::new();
221-
/// CommentSettings::c_style().strip_comments(input.as_bytes()).read_to_string(&mut stripped).unwrap();
222-
///
223-
/// assert_eq!(stripped, "{
224-
/// \n\"a\": \"b\"
225-
/// }");
226-
/// ```
227-
///
228-
/// ```
229-
/// use json_strip_comments::CommentSettings;
230-
/// use std::io::Read;
231-
///
232-
/// let input = r#"{
233-
/// ## shell line comment
234-
/// "a": "b"
235-
/// }"#;
236-
///
237-
/// let mut stripped = String::new();
238-
/// CommentSettings::hash_only().strip_comments(input.as_bytes()).read_to_string(&mut stripped).unwrap();
239-
///
240-
/// assert_eq!(stripped, "{
241-
/// \n\"a\": \"b\"\n}");
242-
/// ```
243-
#[inline]
244-
pub fn strip_comments<I: Read>(self, input: I) -> StripComments<I> {
245-
StripComments::with_settings(self, input)
246-
}
247-
}
248134

249135
fn consume_comment_whitespace_until_maybe_bracket(
250136
state: &mut State,
251137
buf: &mut [u8],
252138
i: &mut usize,
253-
settings: CommentSettings,
254139
) -> Result<bool> {
255140
*i += 1;
256141
let len = buf.len();
257142
while *i < len {
258143
let c = &mut buf[*i];
259144
*state = match state {
260145
Top => {
261-
*state = top(c, settings);
146+
*state = top(c);
262147
if c.is_ascii_whitespace() {
263148
*i += 1;
264149
continue;
@@ -267,7 +152,7 @@ fn consume_comment_whitespace_until_maybe_bracket(
267152
}
268153
InString => in_string(*c),
269154
StringEscape => InString,
270-
InComment => in_comment(c, settings)?,
155+
InComment => in_comment(c)?,
271156
InBlockComment => consume_block_comments(buf, i),
272157
MaybeCommentEnd => maybe_comment_end(c),
273158
InLineComment => consume_line_comments(buf, i),
@@ -277,7 +162,7 @@ fn consume_comment_whitespace_until_maybe_bracket(
277162
Ok(false)
278163
}
279164

280-
fn strip_buf(state: &mut State, buf: &mut [u8], settings: CommentSettings) -> Result<()> {
165+
fn strip_buf(state: &mut State, buf: &mut [u8]) -> Result<()> {
281166
let mut i = 0;
282167
let len = buf.len();
283168

@@ -288,12 +173,10 @@ fn strip_buf(state: &mut State, buf: &mut [u8], settings: CommentSettings) -> Re
288173
match state {
289174
Top => {
290175
let cur = i;
291-
let new_state = top(c, settings);
292-
if settings.trailing_commas
293-
&& *c == b','
294-
{
176+
let new_state = top(c);
177+
if *c == b',' {
295178
let mut temp_state = new_state;
296-
if consume_comment_whitespace_until_maybe_bracket(&mut temp_state, buf, &mut i, settings)? {
179+
if consume_comment_whitespace_until_maybe_bracket(&mut temp_state, buf, &mut i)? {
297180
buf[cur] = b' ';
298181
}
299182
*state = temp_state;
@@ -303,7 +186,7 @@ fn strip_buf(state: &mut State, buf: &mut [u8], settings: CommentSettings) -> Re
303186
}
304187
InString => *state = in_string(*c),
305188
StringEscape => *state = InString,
306-
InComment => *state = in_comment(c, settings)?,
189+
InComment => *state = in_comment(c)?,
307190
InBlockComment => *state = consume_block_comments(buf, &mut i),
308191
MaybeCommentEnd => *state = maybe_comment_end(c),
309192
InLineComment => *state = consume_line_comments(buf, &mut i),
@@ -353,16 +236,14 @@ fn consume_block_comments(buf: &mut [u8], i: &mut usize) -> State {
353236
}
354237

355238
#[inline(always)]
356-
fn top(c: &mut u8, settings: CommentSettings) -> State {
239+
fn top(c: &mut u8) -> State {
357240
match *c {
358241
b'"' => InString,
359242
b'/' => {
360-
if settings.block_comments || settings.slash_line_comments {
361-
*c = b' ';
362-
}
243+
*c = b' ';
363244
InComment
364245
}
365-
b'#' if settings.hash_line_comments => {
246+
b'#' => {
366247
*c = b' ';
367248
InLineComment
368249
}
@@ -380,10 +261,10 @@ fn in_string(c: u8) -> State {
380261
}
381262

382263
#[inline]
383-
fn in_comment(c: &mut u8, settings: CommentSettings) -> Result<State> {
264+
fn in_comment(c: &mut u8) -> Result<State> {
384265
let new_state = match *c {
385-
b'*' if settings.block_comments => InBlockComment,
386-
b'/' if settings.slash_line_comments => InLineComment,
266+
b'*' => InBlockComment,
267+
b'/' => InLineComment,
387268
_ => return Err(ErrorKind::InvalidData.into()),
388269
};
389270
*c = b' ';
@@ -489,71 +370,11 @@ mod tests {
489370
assert_eq!(err.kind(), ErrorKind::InvalidData);
490371
}
491372

492-
#[test]
493-
fn no_hash_comments() {
494-
let json = r#"# bad comment
495-
{"a": "b"}"#;
496-
let mut stripped = String::new();
497-
CommentSettings::c_style()
498-
.strip_comments(json.as_bytes())
499-
.read_to_string(&mut stripped)
500-
.unwrap();
501-
assert_eq!(stripped, json);
502-
}
503-
504-
#[test]
505-
fn no_slash_line_comments() {
506-
let json = r#"// bad comment
507-
{"a": "b"}"#;
508-
let mut stripped = String::new();
509-
let err = CommentSettings::hash_only()
510-
.strip_comments(json.as_bytes())
511-
.read_to_string(&mut stripped)
512-
.unwrap_err();
513-
assert_eq!(err.kind(), ErrorKind::InvalidData);
514-
}
515-
516-
#[test]
517-
fn no_block_comments() {
518-
let json = r#"/* bad comment */ {"a": "b"}"#;
519-
let mut stripped = String::new();
520-
let err = CommentSettings::hash_only()
521-
.strip_comments(json.as_bytes())
522-
.read_to_string(&mut stripped)
523-
.unwrap_err();
524-
assert_eq!(err.kind(), ErrorKind::InvalidData);
525-
}
526-
527-
#[test]
528-
fn keep_all() {
529-
let original = String::from(
530-
r#"
531-
{
532-
"name": /* full */ "John Doe",
533-
"age": 43, # hash line comment
534-
"phones": [
535-
"+44 1234567", // work phone
536-
"+44 2345678", // home phone
537-
], /** comment **/
538-
}"#,
539-
);
540-
let mut changed = original.clone();
541-
let _ = strip_comments_in_place(
542-
&mut changed,
543-
CommentSettings {
544-
block_comments: false,
545-
slash_line_comments: false,
546-
hash_line_comments: false,
547-
trailing_commas: false,
548-
},
549-
);
550-
assert_eq!(original, changed);
551-
}
552373

553374
#[test]
554375
fn strip_in_place() {
555376
let mut json = String::from(r#"{/* Comment */"hi": /** abc */ "bye"}"#);
556-
strip_comments_in_place(&mut json, CommentSettings::default()).unwrap();
377+
strip_comments_in_place(&mut json).unwrap();
557378
assert_eq!(json, r#"{ "hi": "bye"}"#);
558379
}
559380

@@ -574,7 +395,7 @@ mod tests {
574395
# another
575396
}"#,
576397
);
577-
strip_comments_in_place(&mut json, CommentSettings::default()).unwrap();
398+
strip_comments_in_place(&mut json).unwrap();
578399

579400
let expected = r#"{
580401
"a1": [1 ],

0 commit comments

Comments
 (0)