use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use crate::{FunctionInfo, SourceFile, SourceModel, TypeRef};
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema, Default,
)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
#[default]
Hint,
Warning,
Error,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum SmellCategory {
#[default]
Bloaters,
OoAbusers,
ChangePreventers,
Dispensables,
Couplers,
Security,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
pub struct Location {
pub path: PathBuf,
pub start_line: usize,
#[serde(default, skip_serializing_if = "crate::is_zero_usize")]
pub start_col: usize,
pub end_line: usize,
#[serde(default, skip_serializing_if = "crate::is_zero_usize")]
pub end_col: usize,
pub name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
pub struct Finding {
pub smell_name: String,
pub category: SmellCategory,
pub severity: Severity,
pub location: Location,
pub message: String,
pub suggested_refactorings: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub actual_value: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub threshold: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub risk_score: Option<f64>,
}
pub trait ProjectQuery: Send + Sync {
fn is_called_externally(&self, name: &str, exclude_path: &Path) -> bool;
fn callers_of(&self, name: &str) -> Vec<PathBuf>;
fn cross_file_call_counts(&self) -> Vec<((PathBuf, PathBuf), u32)>;
fn function_home(&self, name: &str) -> Option<PathBuf>;
fn function_by_name(&self, name: &str) -> Option<(PathBuf, FunctionInfo)>;
fn class_home(&self, name: &str) -> Option<PathBuf>;
fn model_by_path(&self, path: &Path) -> Option<SourceModel>;
fn is_project_type(&self, name: &str) -> bool;
fn is_third_party(&self, type_ref: &TypeRef) -> bool;
fn workspace_crate_names(&self) -> Vec<String>;
fn is_test_path(&self, path: &Path) -> bool;
fn file_count(&self) -> usize;
}
pub trait ProjectQueryBulk: ProjectQuery {
fn iter_models(&self) -> Box<dyn Iterator<Item = (&Path, &SourceModel)> + '_>;
}
pub struct AnalysisContext<'a> {
pub file: &'a SourceFile,
pub model: &'a SourceModel,
pub tree: Option<&'a tree_sitter::Tree>,
pub ts_language: Option<&'a tree_sitter::Language>,
pub project: Option<&'a std::sync::Arc<dyn ProjectQuery>>,
}
pub trait Plugin: Send + Sync {
fn name(&self) -> &str;
fn version(&self) -> &str {
env!("CARGO_PKG_VERSION")
}
fn description(&self) -> &str {
""
}
fn authors(&self) -> Vec<String> {
vec![env!("CARGO_PKG_AUTHORS").to_string()]
}
fn smells(&self) -> Vec<String> {
Vec::new()
}
fn analyze(&self, ctx: &AnalysisContext) -> Vec<Finding>;
}
pub fn func_location(path: &std::path::Path, f: &crate::FunctionInfo) -> Location {
Location {
path: path.into(),
start_line: f.start_line,
start_col: f.name_col,
end_line: f.start_line,
end_col: f.name_end_col,
name: Some(f.name.clone()),
}
}
pub fn class_location(path: &std::path::Path, c: &crate::ClassInfo) -> Location {
Location {
path: path.into(),
start_line: c.start_line,
start_col: c.name_col,
end_line: c.start_line,
end_col: c.name_end_col,
name: Some(c.name.clone()),
}
}