sem_core/parser/
differ.rs1use rayon::prelude::*;
2use serde::Serialize;
3
4use crate::git::types::FileChange;
5use crate::model::change::{ChangeType, SemanticChange};
6use crate::model::identity::match_entities;
7use crate::parser::registry::ParserRegistry;
8use std::collections::HashSet;
9
10#[derive(Debug, Clone, Serialize)]
11#[serde(rename_all = "camelCase")]
12pub struct DiffResult {
13 pub changes: Vec<SemanticChange>,
14 pub file_count: usize,
15 pub added_count: usize,
16 pub modified_count: usize,
17 pub deleted_count: usize,
18 pub moved_count: usize,
19 pub renamed_count: usize,
20}
21
22pub fn compute_semantic_diff(
23 file_changes: &[FileChange],
24 registry: &ParserRegistry,
25 commit_sha: Option<&str>,
26 author: Option<&str>,
27) -> DiffResult {
28 let per_file_changes: Vec<(String, Vec<SemanticChange>)> = file_changes
30 .par_iter()
31 .filter_map(|file| {
32 let plugin = registry.get_plugin(&file.file_path)?;
33
34 let before_entities = if let Some(ref content) = file.before_content {
35 let before_path = file.old_file_path.as_deref().unwrap_or(&file.file_path);
36 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
37 plugin.extract_entities(content, before_path)
38 })) {
39 Ok(entities) => entities,
40 Err(_) => Vec::new(),
41 }
42 } else {
43 Vec::new()
44 };
45
46 let after_entities = if let Some(ref content) = file.after_content {
47 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
48 plugin.extract_entities(content, &file.file_path)
49 })) {
50 Ok(entities) => entities,
51 Err(_) => Vec::new(),
52 }
53 } else {
54 Vec::new()
55 };
56
57 let sim_fn = |a: &crate::model::entity::SemanticEntity,
58 b: &crate::model::entity::SemanticEntity|
59 -> f64 { plugin.compute_similarity(a, b) };
60
61 let result = match_entities(
62 &before_entities,
63 &after_entities,
64 &file.file_path,
65 Some(&sim_fn),
66 commit_sha,
67 author,
68 );
69
70 if result.changes.is_empty() {
71 None
72 } else {
73 Some((file.file_path.clone(), result.changes))
74 }
75 })
76 .collect();
77
78 let mut all_changes: Vec<SemanticChange> = Vec::new();
79 let mut files_with_changes: HashSet<String> = HashSet::new();
80 for (file_path, changes) in per_file_changes {
81 files_with_changes.insert(file_path);
82 all_changes.extend(changes);
83 }
84
85 let mut added_count = 0;
87 let mut modified_count = 0;
88 let mut deleted_count = 0;
89 let mut moved_count = 0;
90 let mut renamed_count = 0;
91
92 for c in &all_changes {
93 match c.change_type {
94 ChangeType::Added => added_count += 1,
95 ChangeType::Modified => modified_count += 1,
96 ChangeType::Deleted => deleted_count += 1,
97 ChangeType::Moved => moved_count += 1,
98 ChangeType::Renamed => renamed_count += 1,
99 }
100 }
101
102 DiffResult {
103 changes: all_changes,
104 file_count: files_with_changes.len(),
105 added_count,
106 modified_count,
107 deleted_count,
108 moved_count,
109 renamed_count,
110 }
111}