decomment
Remove comments from source text. One function, thirteen languages, no dependencies.
The scanner is a single pass over the bytes with an explicit state for code, line comment, block comment, each string form, and regex literal. A comment marker inside a string, a template, or a regex literal is not a comment, so it is left alone. The output is always a byte subsequence of the input.
[]
= "0.2.0"
Use
use ;
let source = "let url = \"http://example.com\"; // not the url\n";
let result = strip;
assert_eq!;
assert_eq!;
strip cannot fail and cannot panic. Every byte sequence has a defined stripped form, so
the return value carries the output and a diagnostic rather than a Result.
Languages
| Language | Line | Block | Nests | String forms |
|---|---|---|---|---|
C |
// |
/* */ |
no | " ' with \ escapes |
Cpp |
// |
/* */ |
no | C forms plus R"delim( ... )delim" |
CSharp |
// |
/* */ |
no | C forms plus @"..." where "" is an escaped quote |
Css |
none | /* */ |
no | " ' with \ escapes |
Go |
// |
/* */ |
no | " ' plus raw backtick strings |
Html |
none | <!-- --> |
no | none |
Java |
// |
/* */ |
no | C forms plus text blocks """ |
JavaScript |
// |
/* */ |
no | " ', templates with ${}, regex literals |
Python |
# |
none | n/a | ' " ''' """ with quote prefixes |
Rust |
// |
/* */ |
yes | ", r#"..."#, b", br#", char with the lifetime rule |
Sql |
-- |
/* */ |
no | ' and " where a doubled quote is an escaped quote |
Swift |
// |
/* */ |
yes | ", """, #"..."#, ##"..."## |
TypeScript |
// |
/* */ |
no | identical to JavaScript |
TypeScript is a deliberate alias of JavaScript. A test asserts the two produce identical
output on every vector in the suite.
Block comment nesting is a per-language fact rather than a setting.
use ;
let source = "/* a /* b */ c */ x";
assert_eq!;
assert_eq!;
Options
Mode picks what to remove. All takes line and block comments, Line and Block take one
form each, and First takes the first comment the other settings would remove and copies the
rest of the input through unchanged.
Line and Block still run the full scanner, and neither scans the text of the other form.
A // inside a block comment is not a line comment.
use ;
let mut options = new;
options.mode = Line;
let source = "/* see http://example.com */ x";
assert_eq!;
preserve_newlines writes back the line terminators that sat inside removed text, byte for
byte, so CRLF stays CRLF.
use ;
let mut options = new;
options.preserve_newlines = true;
assert_eq!;
keep_bang_comments keeps a comment whose body starts with !. The body starts at the byte
after the opening marker, so //!, /*!, <!--!, --!, and #! qualify and /// does not.
That keeps minifier license banners and keeps a shebang line.
use ;
let mut options = new;
options.keep_bang_comments = true;
let source = "#!/usr/bin/env python3\nx = 1 # note\n";
assert_eq!;
Every option composes with every other option. No flag turns another off.
Truncated input
A chunker cuts files at arbitrary boundaries, so a chunk that ends inside a comment is routine. An unterminated block comment is deleted to end of input, which is what a compiler does with one, and the deletion is reported.
use ;
let result = strip;
assert_eq!;
let open = result.unterminated.unwrap;
assert_eq!;
assert_eq!;
Unterminated implements core::error::Error, so a caller that treats a truncated chunk as a
failure can use ? after ok_or.
Which string forms survive a line terminator is a per-language fact, and it decides what one stray quote costs.
A " or ' string closes at the line terminator in C, C++, C#, CSS, Go, Java, JavaScript,
Python, Swift, and TypeScript. Scanning continues in code state, so a stray quote there cannot
disable comment removal for the rest of a file, and nothing is reported, because the construct
is not open where the scan ends.
Every multi-line form stays open instead, because that is what its grammar says. A stray Rust
", Go backtick, SQL ' or ", triple quote, raw r#", R"d(, #", or verbatim @" runs
to end of input. Everything after it is string data, comment removal stops there, and the case
is reported as StringLiteral.
use ;
let source = "a \"stray\n// gone\n";
assert_eq!;
let source = "a \"stray\n// kept\n";
let result = strip;
assert_eq!;
assert_eq!;
Limits
- Comments inside an embedded HTML
<script>or<style>element are left alone. Extract the element and call again withJavaScriptorCss. - Telling a regex literal from a division needs the preceding token.
)and}are treated as ending an expression, soif (x) /re/.test(y)and a regex right after a block close are read as division. Both fail in the direction that keeps code. - Shell, Ruby, PHP, Perl, YAML, and Lua are not in the table. Heredocs, string interpolation, and embedded sub-languages need more than a byte scanner, and a half lexer for them would damage heredoc bodies.
- Stripping is not idempotent. Removing a block comment can put a
/next to a*that was not next to it before.
use ;
let once = strip.output;
assert_eq!;
assert_eq!;
Requirements
#![no_std] with extern crate alloc. An allocator is required, because the output is a
String. No cargo features, no dependencies, and one allocation per call.
MSRV is 1.81.0, the release that stabilized core::error::Error. A CI job builds the library
and runs the doctests on that exact release, so the floor is tested and not asserted. The full
test suite runs on stable.
License
Licensed under either of Apache License, Version 2.0 or the MIT license at your option.
Unless you state otherwise, any contribution you intentionally submit for inclusion in this crate, as defined in the Apache-2.0 license, is dual licensed as above, with no additional terms or conditions.