use crate::models::{ImportRelation, Symbol};
use super::UNPARSED_IMPORT_PREFIX;
use super::context::{
ExternalCallTarget, ExternalRootBinding, ExtractedImports, ImportBindings,
ImportResolutionContext, LocalCallBinding,
};
use super::predicates::rust_external_roots;
mod go_rust;
mod java_csharp;
mod lua;
mod objc;
mod php_kotlin;
mod python_js;
mod rest;
mod scala;
mod shell;
use go_rust::{parse_go_import_statement, parse_rust_import_statement};
use java_csharp::{
csharp_global_qualifier_parts, parse_csharp_import_statement, parse_java_import_statement,
};
use lua::parse_lua_import_statement;
pub(crate) use lua::resolve_lua_require_member_callee;
use objc::parse_objc_import_statement;
pub(crate) use objc::resolve_objc_local_callee;
use php_kotlin::{
parse_kotlin_import_statement, parse_php_import_statement, php_local_symbol_exists,
};
use python_js::{parse_js_import_statement, parse_python_import_statement};
use rest::{
parse_dart_import_statement, parse_elixir_import_statement, parse_ruby_import_statement,
parse_swift_import_statement,
};
use scala::parse_scala_import_statement;
use shell::parse_shell_import_statement;
pub(crate) use shell::resolve_shell_local_callee;
pub(crate) fn parse_import_statement(
language: &str,
text: &str,
rel_path: &str,
import_context: &ImportResolutionContext,
extracted: &mut ExtractedImports,
) -> anyhow::Result<()> {
match language {
"python" => parse_python_import_statement(text, rel_path, import_context, extracted)?,
"javascript" | "typescript" => {
parse_js_import_statement(text, rel_path, import_context, extracted)?
}
"go" => parse_go_import_statement(text, rel_path, import_context, extracted)?,
"rust" => parse_rust_import_statement(text, rel_path, import_context, extracted),
"java" => parse_java_import_statement(text, rel_path, import_context, extracted),
"csharp" => parse_csharp_import_statement(text, rel_path, import_context, extracted),
"php" => parse_php_import_statement(text, rel_path, import_context, extracted),
"kotlin" => parse_kotlin_import_statement(text, rel_path, import_context, extracted),
"scala" => parse_scala_import_statement(text, rel_path, import_context, extracted),
"lua" => parse_lua_import_statement(text, rel_path, import_context, extracted),
"objc" => parse_objc_import_statement(text, rel_path, import_context, extracted),
"swift" => parse_swift_import_statement(text, rel_path, import_context, extracted),
"ruby" => parse_ruby_import_statement(text, rel_path, import_context, extracted),
"dart" => parse_dart_import_statement(text, rel_path, import_context, extracted),
"elixir" => parse_elixir_import_statement(text, rel_path, import_context, extracted),
"bash" => parse_shell_import_statement(text, rel_path, extracted),
_ => push_unparsed_import(rel_path, text, extracted)?,
}
Ok(())
}
pub(super) fn push_unparsed_import(
rel_path: &str,
text: &str,
extracted: &mut ExtractedImports,
) -> anyhow::Result<()> {
let text = text.trim();
if text.is_empty() {
anyhow::bail!("unparsed import fallback for `{rel_path}` was empty");
}
if text.lines().count() != 1 {
anyhow::bail!("unparsed import fallback for `{rel_path}` must be a single line");
}
log::debug!("recording unparsed import fallback in {rel_path}: {text}");
extracted.imports.push(ImportRelation {
file_path: rel_path.to_string(),
module_name: format!("{UNPARSED_IMPORT_PREFIX}{text}"),
});
Ok(())
}
pub(crate) fn seed_import_bindings(
language: &str,
import_context: &ImportResolutionContext,
bindings: &mut ImportBindings,
) {
match language {
"rust" => {
for root in rust_external_roots(import_context) {
bindings.external_roots.insert(
root.clone(),
ExternalRootBinding {
module: root,
module_from_qualifier: true,
},
);
}
}
"elixir" => {
for (root, module) in &import_context.elixir_external_roots {
if import_context.elixir_local_module_roots.contains(root) {
continue;
}
let module = import_context
.elixir_external_root_module(root)
.unwrap_or(module);
bindings.external_roots.insert(
root.clone(),
ExternalRootBinding {
module: module.to_string(),
module_from_qualifier: true,
},
);
}
for (root, module) in &import_context.elixir_external_root_overrides {
if import_context.elixir_external_roots.contains_key(root)
|| import_context.elixir_local_module_roots.contains(root)
{
continue;
}
bindings.external_roots.insert(
root.clone(),
ExternalRootBinding {
module: module.clone(),
module_from_qualifier: true,
},
);
}
}
_ => {}
}
}
pub(crate) fn resolve_external_callee(
import_context: &ImportResolutionContext,
import_bindings: &ImportBindings,
symbols: &[Symbol],
callee_name: &str,
root_alias: Option<&str>,
qualifier_path: Option<&str>,
is_bare_call: bool,
) -> Option<ExternalCallTarget> {
if is_bare_call {
if symbols.iter().any(|symbol| symbol.name == callee_name) {
return None;
}
if let Some(binding) = import_bindings.bare.get(callee_name) {
return Some(ExternalCallTarget {
module: binding.module.clone(),
callee_name: binding.callee_name.clone(),
});
}
if import_bindings.bare_wildcard_modules.len() == 1 {
return Some(ExternalCallTarget {
module: import_bindings.bare_wildcard_modules[0].clone(),
callee_name: callee_name.to_string(),
});
}
if import_bindings.bare_wildcard_modules.len() > 1 {
log::debug!(
"skipping ambiguous bare call `{callee_name}` with {} wildcard imports",
import_bindings.bare_wildcard_modules.len()
);
}
return None;
}
let root_alias = root_alias?;
if symbols.iter().any(|symbol| symbol.name == root_alias) {
return None;
}
if let Some(module) = import_bindings.member.get(root_alias) {
return Some(ExternalCallTarget {
module: module.clone(),
callee_name: callee_name.to_string(),
});
}
let qualifier_path = qualifier_path?;
if let Some(module) = qualifier_path.strip_prefix('\\') {
if module.is_empty() {
return None;
}
let local_symbol = format!("{module}\\{callee_name}");
if php_local_symbol_exists(import_context, module)
|| php_local_symbol_exists(import_context, &local_symbol)
{
return None;
}
return Some(ExternalCallTarget {
module: module.to_string(),
callee_name: callee_name.to_string(),
});
}
let (root_alias, qualifier_path) = csharp_global_qualifier_parts(root_alias, qualifier_path)
.unwrap_or((root_alias, qualifier_path));
let root_binding = import_bindings.external_roots.get(root_alias)?;
let module = if root_binding.module_from_qualifier {
qualifier_path.to_string()
} else {
root_binding.module.clone()
};
Some(ExternalCallTarget {
module,
callee_name: callee_name.to_string(),
})
}
pub(crate) fn resolve_local_callee(
import_bindings: &ImportBindings,
symbols: &[Symbol],
callee_name: &str,
is_bare_call: bool,
) -> Option<LocalCallBinding> {
if !is_bare_call {
return None;
}
if symbols.iter().any(|symbol| symbol.name == callee_name) {
return None;
}
import_bindings.local_bare.get(callee_name).cloned()
}
pub(crate) fn resolve_local_member_callee(
import_bindings: &ImportBindings,
symbols: &[Symbol],
callee_name: &str,
root_alias: Option<&str>,
is_member_call: bool,
) -> Option<LocalCallBinding> {
if !is_member_call {
return None;
}
let root_alias = root_alias?;
if symbols.iter().any(|symbol| symbol.name == root_alias) {
return None;
}
let candidate_files = import_bindings.local_member.get(root_alias)?.clone();
Some(LocalCallBinding::named(
candidate_files,
callee_name.to_string(),
))
}
pub(crate) fn resolve_ruby_local_member_callee(
import_context: &ImportResolutionContext,
symbols: &[Symbol],
callee_name: &str,
root_alias: Option<&str>,
is_member_call: bool,
) -> Option<LocalCallBinding> {
if !is_member_call {
return None;
}
let root_alias = root_alias?;
if symbols.iter().any(|symbol| symbol.name == root_alias) {
return None;
}
let candidate_files = import_context.ruby_constant_files(root_alias);
if candidate_files.is_empty() {
return None;
}
let target_name = if callee_name == "new" {
root_alias.to_string()
} else {
callee_name.to_string()
};
Some(LocalCallBinding::named(candidate_files, target_name))
}
pub(crate) fn resolve_php_local_member_callee(
import_context: &ImportResolutionContext,
callee_name: &str,
qualifier_path: Option<&str>,
is_member_call: bool,
) -> Option<LocalCallBinding> {
if !is_member_call {
return None;
}
let class_path = qualifier_path?.trim_start_matches('\\');
if !class_path.contains('\\') {
return None;
}
let candidate_files = import_context.php_candidate_files(class_path);
if candidate_files.is_empty() {
return None;
}
Some(LocalCallBinding::named(
candidate_files,
callee_name.to_string(),
))
}
pub(crate) fn resolve_swift_local_callee(
import_context: &ImportResolutionContext,
rel_path: &str,
callee_name: &str,
is_bare_call: bool,
) -> Option<LocalCallBinding> {
if !is_bare_call {
return None;
}
let candidate_files = import_context.swift_module_candidate_files(rel_path);
if candidate_files.is_empty() {
return None;
}
Some(LocalCallBinding::named(
candidate_files,
callee_name.to_string(),
))
}
pub(crate) fn resolve_dart_local_callee(
import_bindings: &ImportBindings,
symbols: &[Symbol],
callee_name: &str,
is_bare_call: bool,
) -> Option<LocalCallBinding> {
if !is_bare_call {
return None;
}
if import_bindings.dart_local_import_files.is_empty() {
return None;
}
if symbols.iter().any(|symbol| symbol.name == callee_name) {
return None;
}
let mut candidate_files = import_bindings.dart_local_import_files.clone();
candidate_files.sort();
candidate_files.dedup();
Some(LocalCallBinding::named(
candidate_files,
callee_name.to_string(),
))
}
pub(crate) fn resolve_elixir_local_callee(
import_context: &ImportResolutionContext,
import_bindings: &ImportBindings,
symbols: &[Symbol],
callee_name: &str,
qualifier_path: Option<&str>,
is_bare_call: bool,
is_member_call: bool,
) -> Option<LocalCallBinding> {
if is_member_call {
let candidate_files = import_context.elixir_module_files(qualifier_path?);
if candidate_files.is_empty() {
return None;
}
return Some(LocalCallBinding::named(
candidate_files,
callee_name.to_string(),
));
}
if !is_bare_call {
return None;
}
if import_bindings.elixir_local_import_files.is_empty() {
return None;
}
if symbols.iter().any(|symbol| symbol.name == callee_name) {
return None;
}
let mut candidate_files = import_bindings.elixir_local_import_files.clone();
candidate_files.sort();
candidate_files.dedup();
Some(LocalCallBinding::named(
candidate_files,
callee_name.to_string(),
))
}
pub(crate) fn resolve_rust_local_qualified_callee(
import_context: &ImportResolutionContext,
rel_path: &str,
callee_name: &str,
qualifier_path: Option<&str>,
is_member_call: bool,
) -> Option<LocalCallBinding> {
if !is_member_call {
return None;
}
let qualifier_path = qualifier_path?;
import_context.rust_qualified_candidate(rel_path, qualifier_path, callee_name)
}
pub(crate) fn resolve_csharp_local_member_callee(
import_context: &ImportResolutionContext,
import_bindings: &ImportBindings,
symbols: &[Symbol],
callee_name: &str,
root_alias: Option<&str>,
qualifier_path: Option<&str>,
is_member_call: bool,
) -> Option<LocalCallBinding> {
if !is_member_call {
return None;
}
let qualifier_path = qualifier_path?;
let root_alias = root_alias?;
if symbols.iter().any(|symbol| symbol.name == root_alias) {
return None;
}
let candidate_files = if qualifier_path.contains('.') {
import_context.csharp_type_files(qualifier_path)
} else {
let mut files: Vec<String> = import_bindings
.csharp_local_namespaces
.iter()
.flat_map(|namespace| {
import_context.csharp_type_files(&format!("{namespace}.{qualifier_path}"))
})
.collect();
files.sort();
files.dedup();
files
};
if candidate_files.is_empty() {
return None;
}
Some(LocalCallBinding::named(
candidate_files,
callee_name.to_string(),
))
}