use std::io::Read;
use std::path::Path;
const GENERATED_JS_MARKER_SCAN_BYTES: usize = 64 * 1024;
const GENERATED_JS_ANALYSIS_READ_BYTES: u64 = 256 * 1024;
pub(super) const MINIFIED_JS_MIN_BYTES: usize = 128 * 1024;
const MINIFIED_JS_LONG_LINE_BYTES: usize = 20 * 1024;
const MINIFIED_JS_MAX_LINES: usize = 20;
const MINIFIED_JS_AVG_LINE_BYTES: usize = 2 * 1024;
const GENERATED_JS_MARKERS: &[&str] = &[
"generated by",
"do not edit",
"@generated",
"auto-generated",
"automatically generated",
];
pub(super) fn is_generated_js_bundle(path: &Path) -> bool {
if !is_js_family_file(path) {
return false;
}
let Ok(metadata) = path.metadata() else {
return false;
};
let Ok(bytes) = read_file_prefix(path, GENERATED_JS_ANALYSIS_READ_BYTES) else {
return false;
};
if contains_generated_js_marker(&bytes) {
return true;
}
if metadata.len() < MINIFIED_JS_MIN_BYTES as u64 {
return false;
};
looks_minified_js_bundle(&bytes)
}
fn read_file_prefix(path: &Path, max_bytes: u64) -> std::io::Result<Vec<u8>> {
let mut file = std::fs::File::open(path)?;
let mut bytes = Vec::with_capacity(max_bytes.min(usize::MAX as u64) as usize);
file.by_ref().take(max_bytes).read_to_end(&mut bytes)?;
Ok(bytes)
}
fn is_js_family_file(path: &Path) -> bool {
path.extension()
.and_then(|ext| ext.to_str())
.map(|ext| {
matches!(
ext.to_ascii_lowercase().as_str(),
"js" | "jsx" | "cjs" | "mjs"
)
})
.unwrap_or(false)
}
fn contains_generated_js_marker(bytes: &[u8]) -> bool {
let scan_len = bytes.len().min(GENERATED_JS_MARKER_SCAN_BYTES);
let scan = String::from_utf8_lossy(&bytes[..scan_len]).to_ascii_lowercase();
GENERATED_JS_MARKERS
.iter()
.any(|marker| scan.contains(marker))
}
fn looks_minified_js_bundle(bytes: &[u8]) -> bool {
if bytes.len() < MINIFIED_JS_MIN_BYTES {
return false;
}
let mut line_count = 0usize;
let mut total_line_bytes = 0usize;
let mut longest_line_bytes = 0usize;
for line in bytes.split(|byte| *byte == b'\n') {
let line_len = line.len();
if line_len == 0 {
continue;
}
line_count += 1;
total_line_bytes += line_len;
longest_line_bytes = longest_line_bytes.max(line_len);
}
if line_count == 0 {
return false;
}
longest_line_bytes >= MINIFIED_JS_LONG_LINE_BYTES
|| (line_count <= MINIFIED_JS_MAX_LINES
&& total_line_bytes / line_count >= MINIFIED_JS_AVG_LINE_BYTES)
}