use crate::Issue as _;
#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "lowercase")]
#[allow(dead_code)]
pub enum Severity {
Blocker,
Critical,
Major,
Minor,
Info,
}
impl From<crate::Severity> for Severity {
fn from(severity: crate::Severity) -> Self {
match severity {
crate::Severity::Blocker => Self::Blocker,
crate::Severity::Critical => Self::Critical,
crate::Severity::Major => Self::Major,
crate::Severity::Minor => Self::Minor,
crate::Severity::Info => Self::Info,
}
}
}
#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "lowercase")]
#[allow(dead_code)]
pub enum Category {
#[serde(rename = "Bug Risk")] BugRisk,
Clarity,
Compatibility,
Complexity,
Duplication,
Performance,
Security,
Style,
}
impl From<crate::Category> for Category {
fn from(category: crate::Category) -> Self {
match category {
crate::Category::Bug => Self::BugRisk,
crate::Category::Complexity => Self::Complexity,
crate::Category::Duplication => Self::Duplication,
crate::Category::Performance => Self::Performance,
crate::Category::Security => Self::Security,
crate::Category::Style => Self::Style,
}
}
}
#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "lowercase")]
pub struct LineColumn {
pub line: usize,
pub column: usize,
}
#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Position {
Lines { begin: usize, end: usize },
Positions { begin: LineColumn, end: LineColumn },
}
#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Location {
pub path: String,
#[serde(flatten)]
pub position: Position,
}
impl From<crate::Location> for Location {
fn from(location: crate::Location) -> Self {
Self {
path: location.path.to_string_lossy().to_string(),
position: Position::Positions {
begin: LineColumn {
line: location.range.start.line,
column: location.range.start.column,
},
end: LineColumn {
line: location.range.end.line,
column: location.range.end.column,
},
},
}
}
}
#[derive(Debug, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub struct Issue {
pub description: String,
pub check_name: String,
pub fingerprint: String,
pub severity: Severity,
pub location: Location,
}
#[derive(Debug, Default, serde::Serialize)]
pub struct Issues(Vec<Issue>);
impl Issues {
#[must_use]
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.0.len()
}
}
impl FromIterator<Issue> for Issues {
fn from_iter<T: IntoIterator<Item = Issue>>(issues: T) -> Self {
Self(issues.into_iter().collect())
}
}
#[derive(Debug)]
pub struct CodeClimate;
impl<'c> crate::FromIssues<'c> for CodeClimate {
type Report = Issues;
fn from_issues(issues: impl IntoIterator<Item = crate::IssueType<'c>>) -> Self::Report {
issues
.into_iter()
.filter_map(|issue| {
issue.location().map(|location| Issue {
description: location.message.clone(),
check_name: issue.issue_uid(),
fingerprint: format!("{:x}", issue.fingerprint()),
severity: Severity::from(issue.severity()),
location: Location::from(location),
})
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::{CodeClimate, Position, Severity};
use crate::{test::Issue, FromIssues as _, IssueType};
use test_log::test;
#[test]
fn simple_issue() {
let issues = CodeClimate::from_issues(vec![IssueType::Test(Issue)]);
let issue = &issues.0[0];
assert_eq!(issue.check_name, "fake::fake_issue");
assert!(matches!(issue.severity, Severity::Info));
assert_eq!(issue.fingerprint, "1d623b89683f9ce4e074de1676d12416");
assert_eq!(issue.description, "this issue is bad!");
assert_eq!(issue.location.path, "Cargo.lock");
let Position::Positions { begin, end } = &issue.location.position else {
panic!("not matching a Position::Positions variant");
};
assert_eq!(begin.line, 1);
assert_eq!(end.line, 2);
assert_eq!(begin.column, 1);
assert_eq!(end.column, 42);
}
}