const SPECIFIER_KEYWORDS: &[&[u8]] = &[b"from", b"import"];
const SPECIFIER_CALLEES: &[&[u8]] = &[b"import", b"require"];
fn is_identifier_byte(byte: u8) -> bool {
return byte.is_ascii_alphanumeric() || byte == b'_' || byte == b'$';
}
fn skip_whitespace_backwards(text: &[u8], end: usize) -> usize {
return text[..end]
.iter()
.rposition(|byte: &u8| return !matches!(byte, b' ' | b'\t' | b'\r' | b'\n'))
.map_or(0, |index: usize| return index + 1);
}
fn ends_with_keyword(text: &[u8], end: usize, keywords: &[&[u8]]) -> bool {
return keywords.iter().any(|keyword: &&[u8]| {
let Some(start) = end.checked_sub(keyword.len()) else {
return false;
};
let before: Option<&u8> = start
.checked_sub(1)
.and_then(|index: usize| return text.get(index));
return text[start..end] == **keyword
&& !before.is_some_and(|byte: &u8| return is_identifier_byte(*byte));
});
}
fn is_specifier_position(text: &[u8], quote_index: usize) -> bool {
let token_end: usize = skip_whitespace_backwards(text, quote_index);
if ends_with_keyword(text, token_end, SPECIFIER_KEYWORDS) {
return true;
}
let Some(paren) = token_end.checked_sub(1) else {
return false;
};
return text[paren] == b'('
&& ends_with_keyword(
text,
skip_whitespace_backwards(text, paren),
SPECIFIER_CALLEES,
);
}
pub fn imports_package(source_text: &str, package_name: &str) -> bool {
let text: &[u8] = source_text.as_bytes();
return source_text
.match_indices(package_name)
.any(|(index, _): (usize, &str)| {
let Some(quote_index) = index.checked_sub(1) else {
return false;
};
let quote: u8 = text[quote_index];
let following: Option<&u8> = text.get(index + package_name.len());
return matches!(quote, b'\'' | b'"' | b'`')
&& (following == Some("e) || following == Some(&b'/'))
&& is_specifier_position(text, quote_index);
});
}
#[cfg(test)]
#[path = "dependent_version_imports_tests.rs"]
mod tests;