fix(core): tighten word boundary to reject digits and underscores in level detection (closes #28)
This commit is contained in:
@@ -100,13 +100,21 @@ fn detect_level_from_text(line: &str) -> Option<LogLevel> {
|
||||
)
|
||||
}
|
||||
|
||||
// ─── is_ident_char ─────────────────────────────────────────────────────────
|
||||
/// Whether a byte looks like an ASCII identifier continuation character
|
||||
/// (letter / digit / underscore). Log-level keywords must NOT be adjacent to
|
||||
/// such characters to count as a valid word boundary.
|
||||
fn is_ident_char(b: u8) -> bool {
|
||||
b.is_ascii_alphanumeric() || b == b'_'
|
||||
}
|
||||
|
||||
// ─── is_word_boundary ───────────────────────────────────────────────────────
|
||||
/// Check that the match at `start..start+len` is surrounded by non-alphabetic
|
||||
/// Check that the match at `start..start+len` is surrounded by non-identifier
|
||||
/// characters (or the string edge).
|
||||
fn is_word_boundary(text: &str, start: usize, len: usize) -> bool {
|
||||
let before_ok = start == 0 || !text.as_bytes()[start - 1].is_ascii_alphabetic();
|
||||
let before_ok = start == 0 || !is_ident_char(text.as_bytes()[start - 1]);
|
||||
let after_idx = start + len;
|
||||
let after_ok = after_idx >= text.len() || !text.as_bytes()[after_idx].is_ascii_alphabetic();
|
||||
let after_ok = after_idx >= text.len() || !is_ident_char(text.as_bytes()[after_idx]);
|
||||
before_ok && after_ok
|
||||
}
|
||||
|
||||
@@ -209,4 +217,36 @@ mod tests {
|
||||
let line = format!("{prefix} ERROR something");
|
||||
assert_eq!(detect_level(&line), Some(LogLevel::Error));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boundary_rejects_trailing_digits() {
|
||||
assert_eq!(detect_level("ERROR123"), None);
|
||||
assert_eq!(detect_level("WARN2: bad"), None);
|
||||
assert_eq!(detect_level("ERR2"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boundary_rejects_underscore() {
|
||||
assert_eq!(detect_level("INFO_foo"), None);
|
||||
assert_eq!(detect_level("DBG_value=5"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boundary_rejects_leading_digits_and_underscore() {
|
||||
assert_eq!(detect_level("123ERROR: fail"), None);
|
||||
assert_eq!(detect_level("foo_ERROR: fail"), None);
|
||||
assert_eq!(detect_level("1WRN"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boundary_accepts_valid_suffixes() {
|
||||
assert_eq!(detect_level("ERROR: fail"), Some(LogLevel::Error));
|
||||
assert_eq!(detect_level("[ERROR] fail"), Some(LogLevel::Error));
|
||||
assert_eq!(detect_level("ERROR fail"), Some(LogLevel::Error));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boundary_camel_case_regression() {
|
||||
assert_eq!(detect_level("errorLevel"), None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user