1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4
5use crate::{SourceFile, SourceModel};
6
7#[derive(
9 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema, Default,
10)]
11#[serde(rename_all = "lowercase")]
12pub enum Severity {
13 #[default]
14 Hint,
15 Warning,
16 Error,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
21#[serde(rename_all = "snake_case")]
22pub enum SmellCategory {
23 #[default]
24 Bloaters,
25 OoAbusers,
26 ChangePreventers,
27 Dispensables,
28 Couplers,
29 Security,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
34pub struct Location {
35 pub path: PathBuf,
36 pub start_line: usize,
37 #[serde(default, skip_serializing_if = "crate::is_zero_usize")]
39 pub start_col: usize,
40 pub end_line: usize,
41 #[serde(default, skip_serializing_if = "crate::is_zero_usize")]
43 pub end_col: usize,
44 pub name: Option<String>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
49pub struct Finding {
50 pub smell_name: String,
51 pub category: SmellCategory,
52 pub severity: Severity,
53 pub location: Location,
54 pub message: String,
55 pub suggested_refactorings: Vec<String>,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub actual_value: Option<f64>,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub threshold: Option<f64>,
62 #[serde(skip_serializing_if = "Option::is_none")]
66 pub risk_score: Option<f64>,
67}
68
69pub struct AnalysisContext<'a> {
71 pub file: &'a SourceFile,
72 pub model: &'a SourceModel,
73 pub tree: Option<&'a tree_sitter::Tree>,
74 pub ts_language: Option<&'a tree_sitter::Language>,
75}
76
77pub trait Plugin: Send + Sync {
79 fn name(&self) -> &str;
81
82 fn version(&self) -> &str {
84 env!("CARGO_PKG_VERSION")
85 }
86
87 fn description(&self) -> &str {
89 ""
90 }
91
92 fn authors(&self) -> Vec<String> {
94 vec![env!("CARGO_PKG_AUTHORS").to_string()]
95 }
96
97 fn smells(&self) -> Vec<String> {
101 Vec::new()
102 }
103
104 fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding>;
106}
107
108pub fn func_location(path: &std::path::Path, f: &crate::FunctionInfo) -> Location {
110 Location {
111 path: path.into(),
112 start_line: f.start_line,
113 start_col: f.name_col,
114 end_line: f.start_line,
115 end_col: f.name_end_col,
116 name: Some(f.name.clone()),
117 }
118}
119
120pub fn class_location(path: &std::path::Path, c: &crate::ClassInfo) -> Location {
122 Location {
123 path: path.into(),
124 start_line: c.start_line,
125 start_col: c.name_col,
126 end_line: c.start_line,
127 end_col: c.name_end_col,
128 name: Some(c.name.clone()),
129 }
130}