use std::fs;
use crate::types::FileAnalysis;
pub(super) fn is_svelte_component_api(file_path: &str, export_name: &str) -> bool {
let is_svelte_file = file_path.ends_with(".svelte") || file_path.ends_with(".svelte.ts");
if !is_svelte_file {
return false;
}
const COMPONENT_API_METHODS: &[&str] = &[
"show",
"hide",
"open",
"close",
"toggle",
"dismiss",
"focus",
"blur",
"select",
"selectAll",
"clear",
"reset",
"validate",
"submit",
"getText",
"setText",
"getValue",
"setValue",
"getContent",
"setContent",
"insertText",
"replaceText",
"scrollTo",
"scrollToTop",
"scrollToBottom",
"scrollIntoView",
"play",
"pause",
"stop",
"restart",
"animate",
"enable",
"disable",
"activate",
"deactivate",
"expand",
"collapse",
"init",
"destroy",
"refresh",
"update",
"reload",
"imports",
"exports",
"getters",
"state",
"values",
];
if COMPONENT_API_METHODS.contains(&export_name) {
return true;
}
const API_PREFIXES: &[&str] = &[
"scroll",
"get",
"set",
"on",
"handle",
"apply",
"is",
"has",
"can",
"should",
"do",
"trigger",
"emit",
"fire",
"dispatch",
"notify",
"load",
"fetch",
"save",
"delete",
"add",
"remove",
"insert",
"append",
"prepend",
"move",
"swap",
"sort",
"filter",
"find",
"search",
"check",
"verify",
"compute",
"calculate",
"render",
"draw",
"create",
"update",
"edit",
"reset",
"clear",
"refresh",
"submit",
"show",
"hide",
"open",
"close",
"toggle",
"select",
"click",
"press",
"validate",
"sanitize",
"normalize",
"format",
"parse",
"serialize",
"deserialize",
];
for prefix in API_PREFIXES {
if export_name.starts_with(prefix)
&& export_name.len() > prefix.len()
&& export_name
.chars()
.nth(prefix.len())
.is_some_and(|c| c.is_uppercase())
{
return true;
}
}
false
}
pub(super) fn is_rust_const_table(analysis: &FileAnalysis) -> bool {
if analysis.language != "rs" {
return false;
}
let const_exports: Vec<_> = analysis
.exports
.iter()
.filter(|e| e.kind == "const")
.collect();
if const_exports.len() < 8 {
return false;
}
let shouting: usize = const_exports
.iter()
.filter(|e| {
let name = e.name.as_str();
!name.is_empty()
&& name
.chars()
.all(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || c == '_')
})
.count();
let non_const_exports = analysis.exports.len().saturating_sub(const_exports.len());
shouting * 4 >= const_exports.len() * 3 && non_const_exports <= 2
}
pub(super) fn is_python_library(root: &std::path::Path) -> bool {
root.join("setup.py").exists()
|| root.join("pyproject.toml").exists()
|| root.join("setup.cfg").exists()
|| root.join("Lib").is_dir()
}
pub(super) fn is_in_python_all(analysis: &FileAnalysis, export_name: &str) -> bool {
analysis
.exports
.iter()
.any(|e| e.name == export_name && e.kind == "__all__")
}
pub(super) fn is_python_stdlib_export(analysis: &FileAnalysis, export_name: &str) -> bool {
if !analysis.path.contains("/Lib/") && !analysis.path.starts_with("Lib/") {
return false;
}
if is_in_python_all(analysis, export_name) {
return true;
}
if !export_name.starts_with('_') {
if export_name
.chars()
.all(|c| c.is_uppercase() || c.is_ascii_digit() || c == '_')
{
return true;
}
let has_explicit_all = analysis.exports.iter().any(|e| e.kind == "__all__");
if !has_explicit_all {
return true;
}
}
false
}
pub(super) fn is_python_dunder_method(export_name: &str) -> bool {
export_name.starts_with("__") && export_name.ends_with("__")
}
pub(super) fn rust_has_known_derives(path: &str, keywords: &[&str]) -> bool {
let Ok(content) = fs::read_to_string(path) else {
return false;
};
let lower = content.to_lowercase();
lower.contains("derive(") && keywords.iter().any(|kw| lower.contains(kw))
}
pub(super) fn crate_import_matches_file(
import_raw_path: &str,
export_file_path: &str,
symbol_name: &str,
) -> bool {
if !import_raw_path.starts_with("crate::")
&& !import_raw_path.starts_with("super::")
&& !import_raw_path.starts_with("self::")
{
return false;
}
let export_normalized = export_file_path.replace('\\', "/");
let import_segments: Vec<&str> = import_raw_path
.split("::")
.filter(|s| *s != "crate" && *s != "super" && *s != "self" && *s != symbol_name)
.collect();
if import_segments.is_empty() {
return false;
}
let module_path = import_segments.join("/");
if export_normalized.contains(&format!("{}.rs", module_path))
|| export_normalized.contains(&format!("{}/mod.rs", module_path))
|| export_normalized.contains(&format!("{}/lib.rs", module_path))
{
return true;
}
if let Some(last_segment) = import_segments.last() {
let file_stem = export_normalized
.rsplit('/')
.next()
.unwrap_or("")
.trim_end_matches(".rs");
if file_stem == *last_segment {
return true;
}
}
if import_raw_path.starts_with("super::") && !import_segments.is_empty() {
if let Some(first_segment) = import_segments.first() {
let file_name = export_normalized.rsplit('/').next().unwrap_or("");
if file_name == format!("{}.rs", first_segment) {
return true;
}
}
}
let file_stem = export_normalized
.rsplit('/')
.next()
.unwrap_or("")
.trim_end_matches(".rs")
.trim_end_matches("/mod");
let symbol_pattern = format!(r"\b{}\b", regex::escape(symbol_name));
let module_pattern = format!(r"\b{}\b", regex::escape(file_stem));
if let (Ok(sym_re), Ok(mod_re)) = (
regex::Regex::new(&symbol_pattern),
regex::Regex::new(&module_pattern),
) && sym_re.is_match(import_raw_path)
&& mod_re.is_match(import_raw_path)
{
return true;
}
false
}