Skip to main content

objects/object/
semantic_change.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Semantic change descriptions.
3
4use std::path::PathBuf;
5
6use serde::{Deserialize, Serialize};
7
8/// What kind of modification was made to a file.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
10pub enum ModificationKind {
11    /// Real logic/behaviour change.
12    Logic,
13    /// Only whitespace or indentation changed.
14    WhitespaceOnly,
15    /// Only import/use statements changed.
16    ImportsOnly,
17    /// Only comments changed.
18    CommentsOnly,
19    /// Formatting pass (tokens identical, layout differs).
20    FormattingOnly,
21    /// Mix of logic and non-logic changes.
22    Mixed,
23}
24
25/// How important is this change for review.
26#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
27pub enum ChangeImportance {
28    /// Reviewer can safely skip.
29    Noise,
30    /// Low priority — imports, comments, renames.
31    Low,
32    /// Medium priority — mixed changes, signature tweaks.
33    Medium,
34    /// High priority — logic changes, new/deleted functions.
35    High,
36}
37
38/// A semantic change description.
39#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
40pub enum SemanticChange {
41    /// A new file was added.
42    FileAdded { path: PathBuf },
43    /// A file was deleted.
44    FileDeleted { path: PathBuf },
45    /// A file was modified.
46    FileModified {
47        path: PathBuf,
48        #[serde(default)]
49        classification: Option<ModificationKind>,
50        #[serde(default)]
51        importance: Option<ChangeImportance>,
52        /// Confidence in the classification (0.0–1.0). AST-backed = high, token-fallback = lower.
53        #[serde(default)]
54        confidence: Option<f64>,
55    },
56    /// A file was renamed.
57    FileRenamed { from: PathBuf, to: PathBuf },
58    /// A function was added without enough evidence to call it an extraction.
59    FunctionAdded {
60        file: PathBuf,
61        name: String,
62        #[serde(default)]
63        importance: Option<ChangeImportance>,
64    },
65    /// A function was extracted from an existing function.
66    FunctionExtracted {
67        file: PathBuf,
68        name: String,
69        #[serde(default)]
70        source_file: Option<PathBuf>,
71        #[serde(default)]
72        source_name: Option<String>,
73        #[serde(default)]
74        importance: Option<ChangeImportance>,
75    },
76    /// A function was deleted.
77    FunctionDeleted {
78        file: PathBuf,
79        name: String,
80        #[serde(default)]
81        importance: Option<ChangeImportance>,
82    },
83    /// A function was renamed.
84    FunctionRenamed {
85        file: PathBuf,
86        old_name: String,
87        new_name: String,
88        #[serde(default)]
89        importance: Option<ChangeImportance>,
90    },
91    /// A function body changed without a signature/name change.
92    FunctionModified {
93        file: PathBuf,
94        name: String,
95        #[serde(default)]
96        importance: Option<ChangeImportance>,
97    },
98    /// A function moved within the same file without changing its body.
99    FunctionMoved {
100        file: PathBuf,
101        name: String,
102        old_start_line: usize,
103        new_start_line: usize,
104        #[serde(default)]
105        importance: Option<ChangeImportance>,
106    },
107    /// A function signature changed.
108    SignatureChanged {
109        file: PathBuf,
110        name: String,
111        old_signature: String,
112        new_signature: String,
113        #[serde(default)]
114        importance: Option<ChangeImportance>,
115    },
116    /// A dependency was added.
117    DependencyAdded { name: String, version: String },
118    /// A dependency was removed.
119    DependencyRemoved { name: String },
120    /// Custom semantic change.
121    Custom {
122        change_type: String,
123        data: serde_json::Value,
124    },
125}
126
127impl SemanticChange {
128    /// Get a short description.
129    pub fn description(&self) -> String {
130        match self {
131            SemanticChange::FileAdded { path } => format!("add {}", path.display()),
132            SemanticChange::FileDeleted { path } => format!("delete {}", path.display()),
133            SemanticChange::FileModified {
134                path,
135                classification,
136                ..
137            } => {
138                if let Some(kind) = classification {
139                    format!("modify {} ({:?})", path.display(), kind)
140                } else {
141                    format!("modify {}", path.display())
142                }
143            }
144            SemanticChange::FileRenamed { from, to } => {
145                format!("rename {} -> {}", from.display(), to.display())
146            }
147            SemanticChange::FunctionAdded { file, name, .. } => {
148                format!("add function {} in {}", name, file.display())
149            }
150            SemanticChange::FunctionExtracted {
151                file,
152                name,
153                source_file,
154                source_name,
155                ..
156            } => {
157                if let Some(source_name) = source_name {
158                    let source_file = source_file.as_ref().unwrap_or(file);
159                    format!(
160                        "extract function {} from {} in {}",
161                        name,
162                        source_name,
163                        source_file.display()
164                    )
165                } else {
166                    format!("extract function {} in {}", name, file.display())
167                }
168            }
169            SemanticChange::FunctionDeleted { file, name, .. } => {
170                format!("delete function {} in {}", name, file.display())
171            }
172            SemanticChange::FunctionRenamed {
173                file,
174                old_name,
175                new_name,
176                ..
177            } => {
178                format!("rename {} -> {} in {}", old_name, new_name, file.display())
179            }
180            SemanticChange::FunctionModified { file, name, .. } => {
181                format!("modify function {} in {}", name, file.display())
182            }
183            SemanticChange::FunctionMoved {
184                file,
185                name,
186                old_start_line,
187                new_start_line,
188                ..
189            } => {
190                format!(
191                    "move function {} in {} ({} -> {})",
192                    name,
193                    file.display(),
194                    old_start_line + 1,
195                    new_start_line + 1
196                )
197            }
198            SemanticChange::SignatureChanged { file, name, .. } => {
199                format!("change signature of {} in {}", name, file.display())
200            }
201            SemanticChange::DependencyAdded { name, version } => {
202                format!("add dependency {}@{}", name, version)
203            }
204            SemanticChange::DependencyRemoved { name } => {
205                format!("remove dependency {}", name)
206            }
207            SemanticChange::Custom { change_type, .. } => {
208                format!("custom: {}", change_type)
209            }
210        }
211    }
212}