Skip to main content

cpp_linter/cli/
structs.rs

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/// An enum to describe `--lines-changed-only` CLI option's behavior.
13#[derive(PartialEq, Clone, Debug, Default)]
14pub enum LinesChangedOnly {
15    /// All lines are scanned
16    #[default]
17    Off,
18    /// Only lines in the diff are scanned
19    Diff,
20    /// Only lines in the diff with additions are scanned.
21    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    /// Get a list possible value variants for display in `--help` output.
37    fn value_variants<'a>() -> &'a [Self] {
38        &[
39            LinesChangedOnly::Off,
40            LinesChangedOnly::Diff,
41            LinesChangedOnly::On,
42        ]
43    }
44
45    /// Get a display value (for `--help` output) of the enum variant.
46    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    /// Parse a string into a [`LinesChangedOnly`] enum variant.
66    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/// An enum to describe `--thread-comments` CLI option's behavior.
101#[derive(PartialEq, Clone, Debug)]
102pub enum ThreadComments {
103    /// Always post a new comment and delete any outdated ones.
104    On,
105    /// Do not post thread comments.
106    Off,
107    /// Only update existing thread comments.
108    /// If none exist, then post a new one.
109    Update,
110}
111
112#[cfg(feature = "bin")]
113impl ValueEnum for ThreadComments {
114    /// Get a list possible value variants for display in `--help` output.
115    fn value_variants<'a>() -> &'a [Self] {
116        &[Self::On, Self::Off, Self::Update]
117    }
118
119    /// Get a display value (for `--help` output) of the enum variant.
120    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    /// Parse a string into a [`ThreadComments`] enum variant.
141    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/// A data structure to contain CLI options that relate to
166/// clang-tidy or clang-format arguments.
167#[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    /// Construct a [`ClangParams`] instance from a [`Cli`] instance.
186    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
222/// A struct to contain CLI options that relate to
223/// [`RestClient.post_feedback()`](fn@crate::rest_api::RestClient.post_feedback()).
224pub 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    /// Construct a [`FeedbackInput`] instance from a [`Cli`] instance.
238    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    /// Construct a [`FeedbackInput`] instance with default values.
254    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}