const HEADER_SCAN_BYTES: usize = 8 * 1024;
pub(in crate::code) fn is_generated_file(path: &str, bytes: &[u8]) -> bool {
path_has_generated_signal(path) || header_has_generated_signal(bytes)
}
pub(in crate::code) fn path_has_generated_signal(path: &str) -> bool {
let normalized = normalized_path(path);
let file_name = normalized.rsplit('/').next().unwrap_or(&normalized);
generated_file_name(file_name) || generated_path_segments(&normalized)
}
fn generated_file_name(file_name: &str) -> bool {
file_name.ends_with(".pb.go")
|| file_name.ends_with(".pulsar.go")
|| file_name.ends_with(".generated.ts")
|| file_name.ends_with(".generated.tsx")
|| file_name.ends_with(".generated.js")
|| file_name.ends_with(".auto.ts")
|| file_name.ends_with(".auto.tsx")
|| file_name.ends_with(".auto.js")
|| file_name.ends_with(".min.js")
|| file_name.ends_with(".min.css")
}
fn generated_path_segments(path: &str) -> bool {
let segments = path
.split('/')
.filter(|segment| !segment.is_empty())
.collect::<Vec<_>>();
segments
.first()
.is_some_and(|segment| matches!(*segment, "dist" | "build"))
|| segments
.windows(2)
.any(|window| window == ["target", "generated"])
}
fn header_has_generated_signal(bytes: &[u8]) -> bool {
let header = &bytes[..bytes.len().min(HEADER_SCAN_BYTES)];
if header.is_empty() || header_contains_nul(header) {
return false;
}
let header = String::from_utf8_lossy(header).to_ascii_lowercase();
[
"code generated by",
"auto-generated",
"autogenerated",
"@generated",
"this file was generated",
"openapi generator",
"swagger codegen",
]
.iter()
.any(|marker| header.contains(marker))
}
fn header_contains_nul(bytes: &[u8]) -> bool {
bytes.contains(&0)
}
fn normalized_path(path: &str) -> String {
path.replace('\\', "/").to_ascii_lowercase()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn detects_generated_path_patterns() {
for path in [
"proto/user.pb.go",
"src/service.pulsar.go",
"web/api.generated.ts",
"web/client.auto.ts",
"public/app.min.js",
"assets/main.min.css",
"dist/client/index.ts",
"build/openapi/client.rs",
"target/generated/schema.rs",
] {
assert!(path_has_generated_signal(path), "{path}");
}
}
#[test]
fn detects_header_markers_for_swagger_and_openapi_clients() {
for content in [
"// Code generated by protoc-gen-go. DO NOT EDIT.\npackage api\n",
"/* auto-generated swagger client */\nexport class UsersApi {}\n",
"// @generated by relay test fixture\n",
"// OpenAPI Generator client\nexport const request = () => {}\n",
] {
assert!(is_generated_file("src/client.ts", content.as_bytes()));
}
}
#[test]
fn does_not_treat_size_alone_as_generated() {
let large_text = vec![b'a'; 1024 * 1024 + 1];
assert!(!is_generated_file("src/huge.ts", &large_text));
}
#[test]
fn keeps_nested_build_and_dist_source_paths_handwritten() {
for path in ["src/build/config.rs", "internal/dist/reader.go"] {
assert!(!path_has_generated_signal(path), "{path}");
}
for path in ["build/openapi/client.rs", "dist/client/index.ts"] {
assert!(path_has_generated_signal(path), "{path}");
}
}
}