pub mod go;
pub mod java;
pub mod javascript;
pub mod python;
pub mod rust_lang;
use rma_common::Language;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ResolvedImport {
pub local_name: String,
pub source_file: PathBuf,
pub exported_name: String,
pub kind: ImportKind,
pub specifier: String,
pub line: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ImportKind {
Default,
Named,
Namespace,
CommonJS,
Use,
GoImport,
JavaImport,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Export {
pub name: String,
pub is_default: bool,
pub node_id: usize,
pub line: usize,
pub kind: ExportKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ExportKind {
Function,
Class,
Variable,
Type,
Module,
Unknown,
}
#[derive(Debug, Clone, Default)]
pub struct FileImports {
pub imports: Vec<ResolvedImport>,
pub exports: Vec<Export>,
pub unresolved: Vec<UnresolvedImport>,
}
#[derive(Debug, Clone)]
pub struct UnresolvedImport {
pub specifier: String,
pub local_name: String,
pub line: usize,
pub reason: UnresolvedReason,
}
#[derive(Debug, Clone)]
pub enum UnresolvedReason {
ExternalPackage,
FileNotFound,
Ambiguous(Vec<PathBuf>),
Unsupported,
}
pub fn extract_file_imports(
tree: &tree_sitter::Tree,
source: &[u8],
file_path: &Path,
language: Language,
project_root: &Path,
) -> FileImports {
match language {
Language::JavaScript | Language::TypeScript => {
javascript::extract_imports(tree, source, file_path, project_root)
}
Language::Python => python::extract_imports(tree, source, file_path, project_root),
Language::Rust => rust_lang::extract_imports(tree, source, file_path, project_root),
Language::Go => go::extract_imports(tree, source, file_path, project_root),
Language::Java => java::extract_imports(tree, source, file_path, project_root),
_ => FileImports::default(),
}
}
pub fn resolve_relative_import(
specifier: &str,
from_file: &Path,
_project_root: &Path,
extensions: &[&str],
) -> Option<PathBuf> {
if !specifier.starts_with("./") && !specifier.starts_with("../") {
return None;
}
let from_dir = from_file.parent()?;
let base_path = from_dir.join(specifier);
if base_path.exists() && base_path.is_file() {
return Some(base_path.canonicalize().unwrap_or(base_path));
}
for ext in extensions {
let with_ext = base_path.with_extension(ext.trim_start_matches('.'));
if with_ext.exists() && with_ext.is_file() {
return Some(with_ext.canonicalize().unwrap_or(with_ext));
}
}
if base_path.is_dir() {
for ext in extensions {
let index = base_path.join(format!("index.{}", ext.trim_start_matches('.')));
if index.exists() {
return Some(index.canonicalize().unwrap_or(index));
}
}
}
None
}
pub fn is_external_package(specifier: &str) -> bool {
if specifier.starts_with("./") || specifier.starts_with("../") {
return false;
}
if specifier.starts_with('/') {
return false;
}
if specifier.starts_with('@') {
let parts: Vec<&str> = specifier.splitn(2, '/').collect();
if parts.len() == 2 && !parts[0].contains('/') && parts[0].len() > 1 {
return true;
}
return false;
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_external_package() {
assert!(is_external_package("express"));
assert!(is_external_package("lodash"));
assert!(is_external_package("@types/node"));
assert!(is_external_package("@org/package"));
assert!(!is_external_package("./utils"));
assert!(!is_external_package("../lib/helper"));
assert!(!is_external_package("/absolute/path"));
}
#[test]
fn test_import_kind_equality() {
assert_eq!(ImportKind::Default, ImportKind::Default);
assert_ne!(ImportKind::Default, ImportKind::Named);
}
}