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