debtmap 0.21.1

Code complexity and technical debt analyzer
Documentation
use std::path::{Component, Path, PathBuf};

use tree_sitter::Node;

use crate::analyzers::solidity::parser::node_text;
use crate::core::ast::SolidityAst;
use crate::core::{Dependency, DependencyKind};

pub(super) fn import_dependencies(node: Node, ast: &SolidityAst) -> Vec<Dependency> {
    let mut paths = Vec::new();
    collect_import_paths(node, ast, &mut paths);
    paths
        .into_iter()
        .map(|name| resolve_import_name(&name, &ast.path))
        .map(|name| Dependency {
            name,
            kind: DependencyKind::Import,
        })
        .collect()
}

pub(super) fn inheritance_name(node: Node, ast: &SolidityAst) -> Option<String> {
    node.child_by_field_name("name")
        .map(|name| node_text(&name, &ast.source).to_string())
        .or_else(|| {
            node_text(&node, &ast.source)
                .split_whitespace()
                .last()
                .map(str::to_string)
        })
}

pub(super) fn sort_dependencies(dependencies: &mut Vec<Dependency>) {
    dependencies.sort_by(|a, b| {
        a.name
            .cmp(&b.name)
            .then_with(|| format!("{:?}", a.kind).cmp(&format!("{:?}", b.kind)))
    });
    dependencies.dedup_by(|a, b| a.name == b.name && a.kind == b.kind);
}

fn collect_import_paths(node: Node, ast: &SolidityAst, paths: &mut Vec<String>) {
    if matches!(node.kind(), "import_path" | "string") {
        let text = node_text(&node, &ast.source).trim_matches('"').to_string();
        if !text.is_empty() {
            paths.push(text);
        }
    }
    let mut cursor = node.walk();
    for child in node.children(&mut cursor) {
        collect_import_paths(child, ast, paths);
    }
}

fn resolve_import_name(import: &str, file_path: &Path) -> String {
    if !import.starts_with('.') {
        return import.to_string();
    }
    file_path
        .parent()
        .map(|parent| normalize_path(parent.join(import)))
        .unwrap_or_else(|| PathBuf::from(import))
        .to_string_lossy()
        .replace(std::path::MAIN_SEPARATOR, "/")
}

fn normalize_path(path: PathBuf) -> PathBuf {
    path.components()
        .fold(PathBuf::new(), |mut normalized, part| {
            match part {
                Component::ParentDir => {
                    normalized.pop();
                }
                Component::CurDir => {}
                _ => normalized.push(part.as_os_str()),
            }
            normalized
        })
}