Initial upload with CI/CD workflow
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-02-05 14:40:58 +00:00
parent 5c53f88a08
commit 4da3117fc5

405
src/models/mod.rs Normal file
View File

@@ -0,0 +1,405 @@
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FileLocation {
pub path: PathBuf,
pub line: usize,
pub column: usize,
pub end_line: Option<usize>,
pub end_column: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Priority {
Critical,
High,
Medium,
Low,
}
impl Priority {
pub fn from_keyword(keyword: &str) -> Self {
match keyword.to_uppercase().as_str() {
"FIXME" | "BUG" | "ERROR" => Priority::Critical,
"XXX" | "URGENT" | "CRITICAL" => Priority::High,
"TODO" | "TEMP" | "PERF" => Priority::Medium,
"HACK" | "NOTE" | "REFACTOR" | "XXX" => Priority::Low,
_ => Priority::Medium,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Priority::Critical => "Critical",
Priority::High => "High",
Priority::Medium => "Medium",
Priority::Low => "Low",
}
}
}
impl PartialOrd for Priority {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Priority {
fn cmp(&self, other: &Self) -> Ordering {
fn rank(p: &Priority) -> u8 {
match p {
Priority::Critical => 4,
Priority::High => 3,
Priority::Medium => 2,
Priority::Low => 1,
}
}
rank(self).cmp(&rank(other))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum CommentType {
SingleLine,
MultiLine,
DocBlock,
Shebang,
Comment,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TechDebtItem {
pub id: String,
pub keyword: String,
pub comment_type: CommentType,
pub content: String,
pub location: FileLocation,
pub priority: Priority,
pub complexity_score: u8,
pub context_before: Option<String>,
pub context_after: Option<String>,
pub metadata: ItemMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ItemMetadata {
pub language: String,
pub is_question: bool,
pub has_exclamation: bool,
pub word_count: usize,
pub created_at: Option<String>,
pub author: Option<String>,
}
impl TechDebtItem {
pub fn new(
keyword: String,
content: String,
location: FileLocation,
language: String,
comment_type: CommentType,
) -> Self {
let complexity_score = calculate_complexity(&content);
let is_question = content.contains('?');
let has_exclamation = content.contains('!');
let word_count = content.split_whitespace().count();
let priority = Priority::from_keyword(&keyword);
let id = format!(
"{:?}-{:?}-{}",
location.path,
location.line,
keyword
);
Self {
id,
keyword,
comment_type,
content,
location,
priority,
complexity_score,
context_before: None,
context_after: None,
metadata: ItemMetadata {
language,
is_question,
has_exclamation,
word_count,
created_at: None,
author: None,
},
}
}
}
fn calculate_complexity(content: &str) -> u8 {
let mut score = 1;
let question_count = content.matches('?').count();
let exclamation_count = content.matches('!').count();
let word_count = content.split_whitespace().count();
let uppercase_count = content.chars().filter(|c| c.is_uppercase()).count();
score += (question_count * 2) as u8;
score += exclamation_count as u8;
score += (word_count / 10) as u8;
score += (uppercase_count / 5) as u8;
if content.len() > 200 {
score += 2;
}
if content.len() > 500 {
score += 3;
}
if content.contains("HACK") || content.contains("WORKAROUND") {
score += 2;
}
std::cmp::min(score, 10)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnalysisSummary {
pub total_items: usize,
pub by_priority: ByPriority,
pub by_language: ByLanguage,
pub complexity_distribution: ComplexityDistribution,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ByPriority {
pub critical: usize,
pub high: usize,
pub medium: usize,
pub low: usize,
}
impl ByPriority {
pub fn from_items(items: &[TechDebtItem]) -> Self {
let mut critical = 0;
let mut high = 0;
let mut medium = 0;
let mut low = 0;
for item in items {
match item.priority {
Priority::Critical => critical += 1,
Priority::High => high += 1,
Priority::Medium => medium += 1,
Priority::Low => low += 1,
}
}
Self {
critical,
high,
medium,
low,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ByLanguage {
pub items: Vec<LanguageCount>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LanguageCount {
pub language: String,
pub count: usize,
}
impl ByLanguage {
pub fn from_items(items: &[TechDebtItem]) -> Self {
let mut counts: std::collections::HashMap<String, usize> =
std::collections::HashMap::new();
for item in items {
*counts
.entry(item.metadata.language.clone())
.or_insert(0) += 1;
}
let mut items_vec: Vec<LanguageCount> = counts
.into_iter()
.map(|(lang, count)| LanguageCount { lang, count })
.collect();
items_vec.sort_by(|a, b| b.count.cmp(&a.count));
Self { items: items_vec }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplexityDistribution {
pub low: usize,
pub medium: usize,
pub high: usize,
pub critical: usize,
}
impl ComplexityDistribution {
pub fn from_items(items: &[TechDebtItem]) -> Self {
let mut low = 0;
let mut medium = 0;
let mut high = 0;
let mut critical = 0;
for item in items {
match item.complexity_score {
1..=3 => low += 1,
4..=6 => medium += 1,
7..=8 => high += 1,
9..=10 => critical += 1,
_ => {}
}
}
Self {
low,
medium,
high,
critical,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub patterns: Vec<PatternConfig>,
pub languages: Vec<String>,
pub ignore: Vec<String>,
pub extensions: Vec<String>,
pub complexity: ComplexityConfig,
pub export: ExportConfig,
}
impl Default for Config {
fn default() -> Self {
Self {
patterns: vec![
PatternConfig {
keyword: "FIXME".to_string(),
priority: Priority::Critical,
regex: false,
},
PatternConfig {
keyword: "TODO".to_string(),
priority: Priority::Medium,
regex: false,
},
PatternConfig {
keyword: "HACK".to_string(),
priority: Priority::Low,
regex: false,
},
PatternConfig {
keyword: "BUG".to_string(),
priority: Priority::Critical,
regex: false,
},
PatternConfig {
keyword: "XXX".to_string(),
priority: Priority::High,
regex: false,
},
PatternConfig {
keyword: "NOTE".to_string(),
priority: Priority::Low,
regex: false,
},
],
languages: vec![
"javascript".to_string(),
"typescript".to_string(),
"python".to_string(),
"rust".to_string(),
"go".to_string(),
"java".to_string(),
"c".to_string(),
"cpp".to_string(),
"ruby".to_string(),
],
ignore: vec![
"node_modules/**".to_string(),
"target/**".to_string(),
".git/**".to_string(),
"vendor/**".to_string(),
"dist/**".to_string(),
"build/**".to_string(),
],
extensions: vec![
".js".to_string(),
".ts".to_string(),
".jsx".to_string(),
".tsx".to_string(),
".py".to_string(),
".rs".to_string(),
".go".to_string(),
".java".to_string(),
".c".to_string(),
".cpp".to_string(),
".h".to_string(),
".hpp".to_string(),
".rb".to_string(),
".md".to_string(),
],
complexity: ComplexityConfig::default(),
export: ExportConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatternConfig {
pub keyword: String,
pub priority: Priority,
pub regex: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplexityConfig {
pub enabled: bool,
pub max_comment_length: usize,
pub question_weight: u8,
pub exclamation_weight: u8,
}
impl Default for ComplexityConfig {
fn default() -> Self {
Self {
enabled: true,
max_comment_length: 500,
question_weight: 2,
exclamation_weight: 1,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportConfig {
pub include_metadata: bool,
pub include_context: bool,
}
impl Default for ExportConfig {
fn default() -> Self {
Self {
include_metadata: true,
include_context: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GroupBy {
pub field: String,
}