use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum IndentStyle {
Tabs,
Spaces2,
Spaces4,
Spaces8,
Mixed,
#[default]
Unknown,
}
impl IndentStyle {
pub fn display(self) -> &'static str {
match self {
Self::Tabs => "Tabs",
Self::Spaces2 => "2-Space",
Self::Spaces4 => "4-Space",
Self::Spaces8 => "8-Space",
Self::Mixed => "Mixed",
Self::Unknown => "\u{2014}",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StyleSignal {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StyleGuideScore {
pub name: String,
pub description: String,
pub score_pct: u8,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct StyleAnalysis {
pub language_family: String,
pub indent_style: IndentStyle,
pub tab_indented_lines: u32,
pub space2_indented_lines: u32,
pub space4_indented_lines: u32,
pub lines_over_80: u32,
pub lines_over_100: u32,
pub lines_over_120: u32,
pub max_line_length: u32,
pub total_lines: u32,
pub signals: Vec<StyleSignal>,
pub guide_scores: Vec<StyleGuideScore>,
pub dominant_guide: String,
pub dominant_score_pct: u8,
}
pub fn scan_indent(line: &str, tabs: &mut u32, sp2: &mut u32, sp4: &mut u32) {
let first = match line.chars().next() {
Some(c) => c,
None => return,
};
if first == '\t' {
*tabs += 1;
return;
}
if first != ' ' {
return;
}
let leading = line.bytes().take_while(|&b| b == b' ').count();
if leading == 0 {
return;
}
if leading % 4 == 0 {
*sp4 += 1;
} else if leading % 2 == 0 {
*sp2 += 1;
}
}
pub fn classify_indent(tabs: u32, sp2: u32, sp4: u32) -> IndentStyle {
let total = tabs + sp2 + sp4;
if total == 0 {
return IndentStyle::Unknown;
}
let tab_pct = tabs as f32 / total as f32;
let s2_pct = sp2 as f32 / total as f32;
let s4_pct = sp4 as f32 / total as f32;
if tab_pct >= 0.60 {
return IndentStyle::Tabs;
}
if s4_pct >= 0.60 {
return IndentStyle::Spaces4;
}
if s2_pct >= 0.60 {
return IndentStyle::Spaces2;
}
if sp4 > sp2 * 2 && sp4 > tabs {
return IndentStyle::Spaces4;
}
if sp2 > sp4 && sp2 > tabs {
return IndentStyle::Spaces2;
}
IndentStyle::Mixed
}
pub fn weighted_score(features: &[(f32, f32)]) -> u8 {
let s: f32 = features.iter().map(|(w, v)| w * v).sum();
(s * 100.0).round().clamp(0.0, 100.0) as u8
}
pub fn score_indent_2(s: IndentStyle) -> f32 {
match s {
IndentStyle::Spaces2 => 1.0,
IndentStyle::Mixed => 0.35,
_ => 0.05,
}
}
pub fn score_indent_4(s: IndentStyle) -> f32 {
match s {
IndentStyle::Spaces4 => 1.0,
IndentStyle::Mixed => 0.35,
_ => 0.05,
}
}
pub fn score_indent_tabs(s: IndentStyle) -> f32 {
match s {
IndentStyle::Tabs => 1.0,
IndentStyle::Mixed => 0.20,
_ => 0.05,
}
}
pub fn score_line80(over: u32, total: u32) -> f32 {
if total == 0 {
return 1.0;
}
let p = over as f32 / total as f32;
if p < 0.02 {
1.00
} else if p < 0.08 {
0.75
} else if p < 0.20 {
0.45
} else {
0.10
}
}
pub fn score_line88(over88: u32, total: u32) -> f32 {
score_line_n(over88, total)
}
pub fn score_line100(over100: u32, total: u32) -> f32 {
score_line_n(over100, total)
}
pub fn score_line120(over120: u32, total: u32) -> f32 {
score_line_n(over120, total)
}
fn score_line_n(over: u32, total: u32) -> f32 {
if total == 0 {
return 1.0;
}
let p = over as f32 / total as f32;
if p < 0.03 {
1.00
} else if p < 0.10 {
0.75
} else if p < 0.25 {
0.45
} else {
0.10
}
}
pub fn count_over(lines: &[&str], limit: usize) -> u32 {
lines.iter().filter(|l| l.len() > limit).count() as u32
}