1use std::{fmt::Display, path::PathBuf};
2
3#[cfg(feature = "bin")]
4use clap::{ValueEnum, builder::PossibleValue};
5
6#[cfg(feature = "bin")]
7use super::Cli;
8use crate::clang_tools::clang_tidy::CompilationUnit;
9
10use git_bot_feedback::FileFilter;
11
12#[derive(PartialEq, Clone, Debug, Default)]
14pub enum LinesChangedOnly {
15 #[default]
17 Off,
18 Diff,
20 On,
22}
23
24impl From<LinesChangedOnly> for git_bot_feedback::LinesChangedOnly {
25 fn from(val: LinesChangedOnly) -> Self {
26 match val {
27 LinesChangedOnly::Off => git_bot_feedback::LinesChangedOnly::Off,
28 LinesChangedOnly::Diff => git_bot_feedback::LinesChangedOnly::Diff,
29 LinesChangedOnly::On => git_bot_feedback::LinesChangedOnly::On,
30 }
31 }
32}
33
34#[cfg(feature = "bin")]
35impl ValueEnum for LinesChangedOnly {
36 fn value_variants<'a>() -> &'a [Self] {
38 &[
39 LinesChangedOnly::Off,
40 LinesChangedOnly::Diff,
41 LinesChangedOnly::On,
42 ]
43 }
44
45 fn to_possible_value(&self) -> Option<PossibleValue> {
47 match self {
48 LinesChangedOnly::Off => Some(
49 PossibleValue::new("false")
50 .help("All lines in a file are analyzed.")
51 .aliases(["off", "0"]),
52 ),
53 LinesChangedOnly::Diff => Some(PossibleValue::new("diff").help(
54 "All lines in the diff are analyzed \
55 (including unchanged lines but not subtractions).",
56 )),
57 LinesChangedOnly::On => Some(
58 PossibleValue::new("true")
59 .help("Only lines in the diff that contain additions are analyzed.")
60 .aliases(["on", "1"]),
61 ),
62 }
63 }
64
65 fn from_str(val: &str, ignore_case: bool) -> Result<LinesChangedOnly, String> {
67 let val = if ignore_case {
68 val.to_lowercase()
69 } else {
70 val.to_string()
71 };
72 match val.as_str() {
73 "true" | "on" | "1" => Ok(LinesChangedOnly::On),
74 "diff" => Ok(LinesChangedOnly::Diff),
75 _ => Ok(LinesChangedOnly::Off),
76 }
77 }
78}
79
80impl LinesChangedOnly {
81 pub fn is_change_valid(&self, added_lines: bool, diff_chunks: bool) -> bool {
82 match self {
83 LinesChangedOnly::Off => true,
84 LinesChangedOnly::Diff => diff_chunks,
85 LinesChangedOnly::On => added_lines,
86 }
87 }
88}
89
90impl Display for LinesChangedOnly {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 match self {
93 LinesChangedOnly::Off => write!(f, "false"),
94 LinesChangedOnly::Diff => write!(f, "diff"),
95 LinesChangedOnly::On => write!(f, "true"),
96 }
97 }
98}
99
100#[derive(PartialEq, Clone, Debug)]
102pub enum ThreadComments {
103 On,
105 Off,
107 Update,
110}
111
112#[cfg(feature = "bin")]
113impl ValueEnum for ThreadComments {
114 fn value_variants<'a>() -> &'a [Self] {
116 &[Self::On, Self::Off, Self::Update]
117 }
118
119 fn to_possible_value(&self) -> Option<PossibleValue> {
121 match self {
122 ThreadComments::On => Some(
123 PossibleValue::new("true")
124 .help("Always post a new comment and delete any outdated ones.")
125 .aliases(["on", "1"]),
126 ),
127 ThreadComments::Off => Some(
128 PossibleValue::new("false")
129 .help("Do not post thread comments.")
130 .aliases(["off", "0"]),
131 ),
132 ThreadComments::Update => {
133 Some(PossibleValue::new("update").help(
134 "Only update existing thread comments. If none exist, then post a new one.",
135 ))
136 }
137 }
138 }
139
140 fn from_str(val: &str, ignore_case: bool) -> Result<ThreadComments, String> {
142 let val = if ignore_case {
143 val.to_lowercase()
144 } else {
145 val.to_string()
146 };
147 match val.as_str() {
148 "true" | "on" | "1" => Ok(ThreadComments::On),
149 "update" => Ok(ThreadComments::Update),
150 _ => Ok(ThreadComments::Off),
151 }
152 }
153}
154
155impl Display for ThreadComments {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 match self {
158 ThreadComments::On => write!(f, "true"),
159 ThreadComments::Off => write!(f, "false"),
160 ThreadComments::Update => write!(f, "update"),
161 }
162 }
163}
164
165#[derive(Debug, Clone, Default)]
168pub struct ClangParams {
169 pub tidy_checks: String,
170 pub lines_changed_only: LinesChangedOnly,
171 pub database: Option<PathBuf>,
172 pub extra_args: Vec<String>,
173 pub database_json: Option<Vec<CompilationUnit>>,
174 pub style: String,
175 pub clang_tidy_command: Option<PathBuf>,
176 pub clang_format_command: Option<PathBuf>,
177 pub tidy_filter: Option<FileFilter>,
178 pub format_filter: Option<FileFilter>,
179 pub tidy_review: bool,
180 pub format_review: bool,
181}
182
183#[cfg(feature = "bin")]
184impl From<&Cli> for ClangParams {
185 fn from(args: &Cli) -> Self {
187 let extensions: Vec<&str> = args
188 .source_options
189 .extensions
190 .iter()
191 .map(|ext| ext.as_str())
192 .collect();
193 let tidy_filter = args.tidy_options.ignore_tidy.as_ref().map(|ignore_tidy| {
194 let ignore_tidy: Vec<&str> = ignore_tidy.iter().map(|s| s.as_str()).collect();
195 FileFilter::new(&ignore_tidy, &extensions.clone(), Some("clang-tidy"))
196 });
197 let format_filter = args
198 .format_options
199 .ignore_format
200 .as_ref()
201 .map(|ignore_format| {
202 let ignore_format: Vec<&str> = ignore_format.iter().map(|s| s.as_str()).collect();
203 FileFilter::new(&ignore_format, &extensions, Some("clang-format"))
204 });
205 ClangParams {
206 tidy_checks: args.tidy_options.tidy_checks.clone(),
207 lines_changed_only: args.source_options.lines_changed_only.clone(),
208 database: args.tidy_options.database.clone(),
209 extra_args: args.tidy_options.extra_arg.clone(),
210 database_json: None,
211 style: args.format_options.style.clone(),
212 clang_tidy_command: None,
213 clang_format_command: None,
214 tidy_filter,
215 format_filter,
216 tidy_review: args.feedback_options.tidy_review,
217 format_review: args.feedback_options.format_review,
218 }
219 }
220}
221
222pub struct FeedbackInput {
225 pub thread_comments: ThreadComments,
226 pub no_lgtm: bool,
227 pub step_summary: bool,
228 pub file_annotations: bool,
229 pub style: String,
230 pub tidy_review: bool,
231 pub format_review: bool,
232 pub passive_reviews: bool,
233}
234
235#[cfg(feature = "bin")]
236impl From<&Cli> for FeedbackInput {
237 fn from(args: &Cli) -> Self {
239 FeedbackInput {
240 style: args.format_options.style.clone(),
241 no_lgtm: args.feedback_options.no_lgtm,
242 step_summary: args.feedback_options.step_summary,
243 thread_comments: args.feedback_options.thread_comments.clone(),
244 file_annotations: args.feedback_options.file_annotations,
245 tidy_review: args.feedback_options.tidy_review,
246 format_review: args.feedback_options.format_review,
247 passive_reviews: args.feedback_options.passive_reviews,
248 }
249 }
250}
251
252impl Default for FeedbackInput {
253 fn default() -> Self {
255 FeedbackInput {
256 thread_comments: ThreadComments::Off,
257 no_lgtm: true,
258 step_summary: false,
259 file_annotations: true,
260 style: "llvm".to_string(),
261 tidy_review: false,
262 format_review: false,
263 passive_reviews: false,
264 }
265 }
266}
267
268#[cfg(all(test, feature = "bin"))]
269mod test {
270 #![allow(clippy::unwrap_used)]
271
272 use clap::{Parser, ValueEnum};
273
274 use super::{Cli, LinesChangedOnly, ThreadComments};
275
276 #[test]
277 fn parse_positional() {
278 let cli = Cli::parse_from(["cpp-linter", "file1.c", "file2.h"]);
279 let not_ignored = cli.not_ignored.expect("failed to parse positional args");
280 assert!(!not_ignored.is_empty());
281 assert!(not_ignored.contains(&String::from("file1.c")));
282 assert!(not_ignored.contains(&String::from("file2.h")));
283 }
284
285 #[test]
286 fn display_lines_changed_only_enum() {
287 let input = "Diff";
288 assert_eq!(
289 LinesChangedOnly::from_str(input, true).unwrap(),
290 LinesChangedOnly::Diff
291 );
292 assert_eq!(format!("{}", LinesChangedOnly::Diff), input.to_lowercase());
293
294 assert_eq!(
295 LinesChangedOnly::from_str(input, false).unwrap(),
296 LinesChangedOnly::Off
297 );
298 }
299
300 #[test]
301 fn display_thread_comments_enum() {
302 let input = "Update";
303 assert_eq!(
304 ThreadComments::from_str(input, true).unwrap(),
305 ThreadComments::Update
306 );
307 assert_eq!(format!("{}", ThreadComments::Update), input.to_lowercase());
308 assert_eq!(
309 ThreadComments::from_str(input, false).unwrap(),
310 ThreadComments::Off
311 );
312 }
313}