use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum EntityType {
NamedType,
TraitBound,
ModulePath,
ErrorVariant,
TestRelation,
DocConcept,
Annotation,
LiteralString,
TypeAlias,
ConstantSymbol,
ExternalCrate,
ConceptCluster,
NaturalLanguagePhrase,
}
impl EntityType {
pub fn as_str(&self) -> &'static str {
match self {
Self::NamedType => "NamedType",
Self::TraitBound => "TraitBound",
Self::ModulePath => "ModulePath",
Self::ErrorVariant => "ErrorVariant",
Self::TestRelation => "TestRelation",
Self::DocConcept => "DocConcept",
Self::Annotation => "Annotation",
Self::LiteralString => "LiteralString",
Self::TypeAlias => "TypeAlias",
Self::ConstantSymbol => "ConstantSymbol",
Self::ExternalCrate => "ExternalCrate",
Self::ConceptCluster => "ConceptCluster",
Self::NaturalLanguagePhrase => "NaturalLanguagePhrase",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum EdgeKind {
CallsFunction,
CalledByFunction,
Implements,
UsesType,
Derives,
ModuleContains,
ReExports,
RaisesError,
Configures,
TestedBy,
TestUsesFixture,
CoOccursInTest,
Documents,
ReferencesConcept,
Aliases,
ErrorDescribes,
}
impl EdgeKind {
pub fn score_multiplier(&self) -> f32 {
match self {
EdgeKind::Implements => 0.85,
EdgeKind::UsesType => 0.75,
EdgeKind::TestedBy => 0.80,
EdgeKind::Documents => 0.65,
EdgeKind::ReferencesConcept => 0.60,
_ => 0.70,
}
}
}
pub mod tables {
pub const ENTITIES: &str = "entities";
pub const ENTITY_EDGES: &str = "entity_edges";
pub const CHUNK_ENTITIES: &str = "chunk_entities";
pub const ENTITY_CHUNKS: &str = "entity_chunks";
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RawEntity {
pub id: String,
pub entity_type: EntityType,
pub text: String,
pub span: (usize, usize),
pub file: String,
pub line: usize,
}
impl RawEntity {
pub fn new(
entity_type: EntityType,
text: String,
span: (usize, usize),
file: &str,
line: usize,
) -> Self {
let mut h = Sha256::new();
h.update(entity_type.as_str().as_bytes());
h.update(b"\0");
h.update(text.as_bytes());
h.update(b"\0");
h.update(file.as_bytes());
let id = format!("{:x}", h.finalize());
Self {
id,
entity_type,
text,
span,
file: file.to_string(),
line,
}
}
}
pub fn fact_hash_str(s: &str) -> String {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
s.hash(&mut h);
format!("{:08x}", h.finish())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_entity_id_is_stable() {
let a = RawEntity::new(EntityType::NamedType, "Foo".into(), (0, 3), "src/x.rs", 1);
let b = RawEntity::new(
EntityType::NamedType,
"Foo".into(),
(10, 13),
"src/x.rs",
99,
);
assert_eq!(a.id, b.id);
}
#[test]
fn raw_entity_id_changes_with_type() {
let a = RawEntity::new(EntityType::NamedType, "Foo".into(), (0, 3), "src/x.rs", 1);
let b = RawEntity::new(EntityType::ModulePath, "Foo".into(), (0, 3), "src/x.rs", 1);
assert_ne!(a.id, b.id);
}
#[test]
fn edge_kind_score_multiplier_known_values() {
assert!((EdgeKind::Implements.score_multiplier() - 0.85).abs() < 1e-6);
assert!((EdgeKind::UsesType.score_multiplier() - 0.75).abs() < 1e-6);
assert!((EdgeKind::TestedBy.score_multiplier() - 0.80).abs() < 1e-6);
assert!((EdgeKind::Documents.score_multiplier() - 0.65).abs() < 1e-6);
assert!((EdgeKind::ReferencesConcept.score_multiplier() - 0.60).abs() < 1e-6);
assert!((EdgeKind::CallsFunction.score_multiplier() - 0.70).abs() < 1e-6);
}
#[test]
fn fact_hash_str_is_deterministic() {
let a = fact_hash_str("rust-analyzer cargo crate/Foo#");
let b = fact_hash_str("rust-analyzer cargo crate/Foo#");
assert_eq!(a, b);
assert!(a.len() >= 8 && a.len() <= 16);
assert!(a.chars().all(|c| c.is_ascii_hexdigit()));
}
#[test]
fn entity_type_as_str_round_trip() {
let variants = [
EntityType::NamedType,
EntityType::TraitBound,
EntityType::ModulePath,
EntityType::ErrorVariant,
EntityType::TestRelation,
EntityType::DocConcept,
EntityType::Annotation,
EntityType::LiteralString,
EntityType::TypeAlias,
EntityType::ConstantSymbol,
EntityType::ExternalCrate,
EntityType::ConceptCluster,
EntityType::NaturalLanguagePhrase,
];
for v in variants {
assert!(!v.as_str().is_empty());
}
}
}