This commit is contained in:
405
src/models/mod.rs
Normal file
405
src/models/mod.rs
Normal 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,
|
||||
}
|
||||
Reference in New Issue
Block a user