use std::collections::HashSet;
const ERROR_TOKENS: &[&str] = &["catch", "except"];
const AUTH_STRONG: &[&str] = &["oauth", "jwt", "rbac", "passport", "authentic", "authoriz"];
const AUTH_TOKENS: &[&str] = &[
"auth",
"login",
"logout",
"signin",
"signout",
"permission",
"permissions",
"session",
"sessions",
"role",
"roles",
];
pub(super) fn line_has_error_handling(line: &str) -> bool {
let tokens = tokenize(line);
ERROR_TOKENS.iter().any(|needle| tokens.contains(*needle))
}
pub(super) fn line_has_auth(line: &str) -> bool {
let lower = line.to_ascii_lowercase();
if AUTH_STRONG.iter().any(|needle| lower.contains(needle)) {
return true;
}
let tokens = tokenize(line);
AUTH_TOKENS.iter().any(|needle| tokens.contains(*needle))
}
fn tokenize(text: &str) -> HashSet<String> {
let mut tokens = HashSet::new();
let mut current = String::new();
for ch in text.chars() {
if !ch.is_ascii_alphanumeric() {
flush(&mut current, &mut tokens);
continue;
}
if ch.is_ascii_uppercase()
&& current
.chars()
.last()
.is_some_and(|last| last.is_ascii_lowercase() || last.is_ascii_digit())
{
flush(&mut current, &mut tokens);
}
current.push(ch.to_ascii_lowercase());
}
flush(&mut current, &mut tokens);
tokens
}
fn flush(current: &mut String, tokens: &mut HashSet<String>) {
if !current.is_empty() {
tokens.insert(std::mem::take(current));
}
}
#[cfg(test)]
mod tests {
use super::{line_has_auth, line_has_error_handling};
#[test]
fn error_handling_matches_real_catch_not_lookalikes() {
assert!(line_has_error_handling("} catch (e) {"));
assert!(line_has_error_handling(" except ValueError:"));
assert!(!line_has_error_handling("const catchy = makeCatcher();"));
assert!(!line_has_error_handling("// nothing to see here"));
}
#[test]
fn auth_matches_real_tokens_and_prefixes_not_author() {
assert!(line_has_auth("if (!isAuthenticated(req)) return 401;"));
assert!(line_has_auth("requireAuth(req, res);"));
assert!(line_has_auth("checkPermission(user, 'admin');"));
assert!(line_has_auth("const token = verifyJWT(header);"));
assert!(!line_has_auth("const author = post.author;"));
assert!(!line_has_auth("import { Authority } from './authority';"));
}
}