sem-core 0.11.1

Entity-level semantic diff engine. Extracts functions, classes, and methods from 28 languages via tree-sitter and diffs at the entity level.
Documentation
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SemanticEntity {
    pub id: String,
    pub file_path: String,
    pub entity_type: String,
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub parent_id: Option<String>,
    pub content: String,
    pub content_hash: String,
    /// AST-based hash that strips comments and normalizes whitespace.
    /// Two entities with the same structural_hash are logically identical
    /// even if formatting/comments differ. Inspired by Unison's content-addressed model.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub structural_hash: Option<String>,
    pub start_line: usize,
    pub end_line: usize,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub metadata: Option<HashMap<String, String>>,
}

pub fn build_entity_id(
    file_path: &str,
    entity_type: &str,
    name: &str,
    parent_id: Option<&str>,
) -> String {
    match parent_id {
        Some(pid) => format!("{pid}::{name}"),
        None => format!("{file_path}::{entity_type}::{name}"),
    }
}

/// Build an entity ID with a line-number disambiguator for overloads.
pub fn build_entity_id_disambiguated(
    file_path: &str,
    entity_type: &str,
    name: &str,
    parent_id: Option<&str>,
    line: usize,
) -> String {
    let base = build_entity_id(file_path, entity_type, name, parent_id);
    format!("{base}@L{line}")
}

/// Build an entity ID with a line-number and same-line ordinal disambiguator.
pub fn build_entity_id_disambiguated_with_ordinal(
    file_path: &str,
    entity_type: &str,
    name: &str,
    parent_id: Option<&str>,
    line: usize,
    ordinal: usize,
) -> String {
    let base = build_entity_id_disambiguated(file_path, entity_type, name, parent_id, line);
    format!("{base}#{ordinal}")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_build_entity_id_no_parent() {
        assert_eq!(
            build_entity_id("src/main.ts", "function", "hello", None),
            "src/main.ts::function::hello"
        );
    }

    #[test]
    fn test_build_entity_id_with_parent() {
        let id = build_entity_id(
            "src/main.ts",
            "method",
            "greet",
            Some("src/main.ts::class::MyClass"),
        );
        assert_eq!(id, "src/main.ts::class::MyClass::greet");
    }
}