use crate::types::ContextFilter;
pub fn classify_line(line: &str) -> LineContext {
let trimmed = line.trim_start();
if is_comment_line(trimmed) {
return LineContext::Comment;
}
if trimmed.starts_with("*") || trimmed.starts_with("*/") {
return LineContext::Comment;
}
if has_code_tokens(line) {
return LineContext::Code;
}
if has_string_literal(line) {
return LineContext::String;
}
LineContext::Code
}
pub fn should_include(context: LineContext, filter: Option<&ContextFilter>) -> bool {
match filter {
None => true,
Some(ContextFilter::Code) => context == LineContext::Code,
Some(ContextFilter::Comments) => context == LineContext::Comment,
Some(ContextFilter::Strings) => context == LineContext::String,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LineContext {
Code,
Comment,
String,
}
fn is_comment_line(trimmed: &str) -> bool {
trimmed.starts_with("//")
|| trimmed.starts_with('#')
|| trimmed.starts_with("/*")
|| trimmed.starts_with("*")
}
fn has_code_tokens(line: &str) -> bool {
let code_indicators = [
"fn ",
"def ",
"function ",
"class ",
"struct ",
"impl ",
"let ",
"const ",
"var ",
"mut ",
"return",
"if ",
"else ",
"for ",
"while ",
"loop ",
"match ",
"use ",
"mod ",
"pub ",
"priv ",
"import ",
"from ",
"using ",
"println!",
"print!",
"assert!",
"assert_eq!",
"(",
")",
"{",
"}",
"[",
"]",
";",
"=>",
"->",
"::",
];
let lower = line.to_lowercase();
code_indicators.iter().any(|&token| lower.contains(token))
}
fn has_string_literal(line: &str) -> bool {
if let Some(comment_pos) = line.find("//") {
let before_comment = &line[..comment_pos];
has_unescaped_quote(before_comment)
} else {
has_unescaped_quote(line)
}
}
fn has_unescaped_quote(s: &str) -> bool {
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'"' || bytes[i] == b'\'' {
return true;
}
i += 1;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_classify_comment() {
assert_eq!(classify_line("// This is a comment"), LineContext::Comment);
assert_eq!(classify_line(" // Indented comment"), LineContext::Comment);
assert_eq!(classify_line("# Python comment"), LineContext::Comment);
assert_eq!(
classify_line("/* block comment start"),
LineContext::Comment
);
assert_eq!(classify_line(" * continuation"), LineContext::Comment);
}
#[test]
fn test_classify_string() {
assert_eq!(classify_line(r#""hello world""#), LineContext::String);
assert_eq!(classify_line("'a'"), LineContext::String);
}
#[test]
fn test_classify_code_with_strings() {
assert_eq!(classify_line(r#"println!("hello");"#), LineContext::Code);
assert_eq!(classify_line("let x = 'a';"), LineContext::Code);
}
#[test]
fn test_classify_code() {
assert_eq!(classify_line("fn main() {"), LineContext::Code);
assert_eq!(classify_line("let x = 42;"), LineContext::Code);
assert_eq!(classify_line("}"), LineContext::Code);
}
#[test]
fn test_should_include_filter() {
assert!(should_include(LineContext::Code, None));
assert!(should_include(
LineContext::Code,
Some(&ContextFilter::Code)
));
assert!(!should_include(
LineContext::Comment,
Some(&ContextFilter::Code)
));
assert!(should_include(
LineContext::Comment,
Some(&ContextFilter::Comments)
));
assert!(!should_include(
LineContext::Code,
Some(&ContextFilter::Comments)
));
assert!(should_include(
LineContext::String,
Some(&ContextFilter::Strings)
));
}
#[test]
fn test_string_in_comment_not_counted() {
assert_eq!(
classify_line(r#"// println!("hello")"#),
LineContext::Comment
);
}
}