use log::warn;
use miette::Result;
use oxc::{
allocator::Allocator,
ast::{ast::Program, Visit},
parser::{Parser, ParserReturn},
span::SourceType,
};
use std::sync::Arc;
use super::{
error::ParserFailedDiagnostic,
visit::{ApiKey, ApiKeyVisitor},
};
use crate::Config;
#[derive(Debug, Default)]
pub struct ApiKeyExtractor {
config: Arc<Config>,
}
impl ApiKeyExtractor {
pub fn new(config: Arc<Config>) -> Self {
Self { config }
}
pub fn extract_api_keys<'s, 'a: 's>(
&'s self,
allocator: &'a Allocator,
source_code: &'a str,
) -> Result<Vec<ApiKey>> {
let program = Self::parse(allocator, source_code)?;
let mut visitor = ApiKeyVisitor::new(&self.config);
visitor.visit_program(&program);
Ok(visitor.into_inner())
}
fn parse<'a>(allocator: &'a Allocator, source_code: &'a str) -> Result<Program<'a>> {
let ret: ParserReturn<'a> =
Parser::new(allocator, source_code, SourceType::default()).parse();
match (ret.panicked, ret.errors) {
(true, errors) if errors.is_empty() => Err(ParserFailedDiagnostic::default().into()),
(true, mut errors) if errors.len() == 1 => Err(errors.remove(0).into()),
(true, errors) => Err(ParserFailedDiagnostic::from_iter(errors).into()),
(false, errors) => {
if !errors.is_empty() {
warn!("Parsing completed with {} recoverable errors", errors.len());
for error in errors {
warn!("{error:?}");
}
}
Ok(ret.program)
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_aws_access_key_id_value() {
let alloc = Allocator::default();
let extractor = ApiKeyExtractor::default();
const SOURCES: [&str; 1] = [r#"const x = "AKIAXXXXXXXXXXXXXXXX";"#];
for src in SOURCES {
let keys = extractor.extract_api_keys(&alloc, src).unwrap();
assert_eq!(keys.len(), 1);
}
}
}