cargo_sonar/
codeclimate.rs1use crate::Issue as _;
2
3#[derive(Debug, serde::Serialize, serde::Deserialize)]
4#[serde(rename_all = "lowercase")]
5#[allow(dead_code)]
7#[non_exhaustive]
8pub enum Severity {
9 Blocker,
10 Critical,
11 Major,
12 Minor,
13 Info,
14}
15
16impl From<crate::Severity> for Severity {
17 #[inline]
18 fn from(severity: crate::Severity) -> Self {
19 match severity {
20 crate::Severity::Blocker => Self::Blocker,
21 crate::Severity::Critical => Self::Critical,
22 crate::Severity::Major => Self::Major,
23 crate::Severity::Minor => Self::Minor,
24 crate::Severity::Info => Self::Info,
25 }
26 }
27}
28
29#[derive(Debug, serde::Serialize, serde::Deserialize)]
30#[serde(rename_all = "lowercase")]
31#[allow(dead_code)]
33#[non_exhaustive]
34pub enum Category {
35 #[serde(rename = "Bug Risk")] BugRisk,
37 Clarity,
38 Compatibility,
39 Complexity,
40 Duplication,
41 Performance,
42 Security,
43 Style,
44}
45
46impl From<crate::Category> for Category {
47 #[inline]
48 fn from(category: crate::Category) -> Self {
49 match category {
50 crate::Category::Bug => Self::BugRisk,
51 crate::Category::Complexity => Self::Complexity,
52 crate::Category::Duplication => Self::Duplication,
53 crate::Category::Performance => Self::Performance,
54 crate::Category::Security => Self::Security,
55 crate::Category::Style => Self::Style,
56 }
57 }
58}
59
60#[derive(Debug, serde::Serialize, serde::Deserialize)]
61#[serde(rename_all = "lowercase")]
62pub struct LineColumn {
63 pub line: usize,
64 pub column: usize,
65}
66
67#[derive(Debug, serde::Serialize, serde::Deserialize)]
68#[serde(rename_all = "lowercase")]
69#[non_exhaustive]
70pub enum Position {
71 Lines { begin: usize, end: usize },
72 Positions { begin: LineColumn, end: LineColumn },
73}
74
75#[derive(Debug, serde::Serialize, serde::Deserialize)]
76#[serde(rename_all = "camelCase")]
77pub struct Location {
78 pub path: String,
79 #[serde(flatten)]
80 pub position: Position,
81}
82
83impl From<crate::Location> for Location {
84 #[inline]
85 fn from(location: crate::Location) -> Self {
86 Self {
87 path: location.path.to_string_lossy().to_string(),
88 position: Position::Positions {
89 begin: LineColumn {
90 line: location.range.start.line,
91 column: location.range.start.column,
92 },
93 end: LineColumn {
94 line: location.range.end.line,
95 column: location.range.end.column,
96 },
97 },
98 }
99 }
100}
101
102#[derive(Debug, serde::Serialize, serde::Deserialize)]
103#[serde(rename_all = "snake_case")]
104pub struct Issue {
105 pub description: String,
106 pub check_name: String,
107 pub fingerprint: String,
108 pub severity: Severity,
109 pub location: Location,
110}
111
112#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
113pub struct Issues(Vec<Issue>);
114
115impl Issues {
116 #[must_use]
118 #[inline]
119 pub fn len(&self) -> usize {
120 self.0.len()
121 }
122
123 #[must_use]
125 #[inline]
126 pub fn is_empty(&self) -> bool {
127 self.0.is_empty()
128 }
129}
130
131impl FromIterator<Issue> for Issues {
132 #[inline]
133 fn from_iter<T: IntoIterator<Item = Issue>>(issues: T) -> Self {
134 Self(issues.into_iter().collect())
135 }
136}
137
138#[derive(Debug)]
139pub struct CodeClimate;
140
141impl<'c> crate::FromIssues<'c> for CodeClimate {
142 type Report = Issues;
143
144 fn from_issues(issues: impl IntoIterator<Item = crate::IssueType<'c>>) -> Self::Report {
145 issues
146 .into_iter()
147 .filter_map(|issue| {
148 issue.location().map(|location| Issue {
149 description: location.message.clone(),
150 check_name: issue.issue_uid(),
151 fingerprint: format!("{:x}", issue.fingerprint()),
152 severity: Severity::from(issue.severity()),
153 location: Location::from(location),
154 })
155 })
156 .collect()
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::{CodeClimate, Position, Severity};
163 use crate::{test::Issue, FromIssues as _, IssueType};
164 use test_log::test;
165
166 #[test]
167 fn simple_issue() {
168 let issues = CodeClimate::from_issues(vec![IssueType::Test(Issue)]);
169 let issue = &issues.0.first().unwrap();
170 assert_eq!(issue.check_name, "fake::fake_issue");
171 assert!(matches!(issue.severity, Severity::Info));
172 assert_eq!(issue.fingerprint, "1d623b89683f9ce4e074de1676d12416");
173 assert_eq!(issue.description, "this issue is bad!");
174 assert_eq!(issue.location.path, "Cargo.lock");
175 let Position::Positions { ref begin, ref end } = issue.location.position else {
176 panic!("not matching a Position::Positions variant");
177 };
178 assert_eq!(begin.line, 1);
179 assert_eq!(end.line, 2);
180 assert_eq!(begin.column, 1);
181 assert_eq!(end.column, 42);
182 }
183}