use std::{fmt::Display, path::PathBuf};
#[cfg(feature = "bin")]
use clap::{ValueEnum, builder::PossibleValue};
#[cfg(feature = "bin")]
use super::Cli;
use crate::clang_tools::clang_tidy::CompilationUnit;
use git_bot_feedback::FileFilter;
#[derive(PartialEq, Clone, Debug, Default)]
pub enum LinesChangedOnly {
#[default]
Off,
Diff,
On,
}
impl From<LinesChangedOnly> for git_bot_feedback::LinesChangedOnly {
fn from(val: LinesChangedOnly) -> Self {
match val {
LinesChangedOnly::Off => git_bot_feedback::LinesChangedOnly::Off,
LinesChangedOnly::Diff => git_bot_feedback::LinesChangedOnly::Diff,
LinesChangedOnly::On => git_bot_feedback::LinesChangedOnly::On,
}
}
}
#[cfg(feature = "bin")]
impl ValueEnum for LinesChangedOnly {
fn value_variants<'a>() -> &'a [Self] {
&[
LinesChangedOnly::Off,
LinesChangedOnly::Diff,
LinesChangedOnly::On,
]
}
fn to_possible_value(&self) -> Option<PossibleValue> {
match self {
LinesChangedOnly::Off => Some(
PossibleValue::new("false")
.help("All lines in a file are analyzed.")
.aliases(["off", "0"]),
),
LinesChangedOnly::Diff => Some(PossibleValue::new("diff").help(
"All lines in the diff are analyzed \
(including unchanged lines but not subtractions).",
)),
LinesChangedOnly::On => Some(
PossibleValue::new("true")
.help("Only lines in the diff that contain additions are analyzed.")
.aliases(["on", "1"]),
),
}
}
fn from_str(val: &str, ignore_case: bool) -> Result<LinesChangedOnly, String> {
let val = if ignore_case {
val.to_lowercase()
} else {
val.to_string()
};
match val.as_str() {
"true" | "on" | "1" => Ok(LinesChangedOnly::On),
"diff" => Ok(LinesChangedOnly::Diff),
_ => Ok(LinesChangedOnly::Off),
}
}
}
impl LinesChangedOnly {
pub fn is_change_valid(&self, added_lines: bool, diff_chunks: bool) -> bool {
match self {
LinesChangedOnly::Off => true,
LinesChangedOnly::Diff => diff_chunks,
LinesChangedOnly::On => added_lines,
}
}
}
impl Display for LinesChangedOnly {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LinesChangedOnly::Off => write!(f, "false"),
LinesChangedOnly::Diff => write!(f, "diff"),
LinesChangedOnly::On => write!(f, "true"),
}
}
}
#[derive(PartialEq, Clone, Debug)]
pub enum ThreadComments {
On,
Off,
Update,
}
#[cfg(feature = "bin")]
impl ValueEnum for ThreadComments {
fn value_variants<'a>() -> &'a [Self] {
&[Self::On, Self::Off, Self::Update]
}
fn to_possible_value(&self) -> Option<PossibleValue> {
match self {
ThreadComments::On => Some(
PossibleValue::new("true")
.help("Always post a new comment and delete any outdated ones.")
.aliases(["on", "1"]),
),
ThreadComments::Off => Some(
PossibleValue::new("false")
.help("Do not post thread comments.")
.aliases(["off", "0"]),
),
ThreadComments::Update => {
Some(PossibleValue::new("update").help(
"Only update existing thread comments. If none exist, then post a new one.",
))
}
}
}
fn from_str(val: &str, ignore_case: bool) -> Result<ThreadComments, String> {
let val = if ignore_case {
val.to_lowercase()
} else {
val.to_string()
};
match val.as_str() {
"true" | "on" | "1" => Ok(ThreadComments::On),
"update" => Ok(ThreadComments::Update),
_ => Ok(ThreadComments::Off),
}
}
}
impl Display for ThreadComments {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ThreadComments::On => write!(f, "true"),
ThreadComments::Off => write!(f, "false"),
ThreadComments::Update => write!(f, "update"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ClangParams {
pub tidy_checks: String,
pub lines_changed_only: LinesChangedOnly,
pub database: Option<PathBuf>,
pub extra_args: Vec<String>,
pub database_json: Option<Vec<CompilationUnit>>,
pub style: String,
pub clang_tidy_command: Option<PathBuf>,
pub clang_format_command: Option<PathBuf>,
pub tidy_filter: Option<FileFilter>,
pub format_filter: Option<FileFilter>,
pub tidy_review: bool,
pub format_review: bool,
}
#[cfg(feature = "bin")]
impl From<&Cli> for ClangParams {
fn from(args: &Cli) -> Self {
let extensions: Vec<&str> = args
.source_options
.extensions
.iter()
.map(|ext| ext.as_str())
.collect();
let tidy_filter = args.tidy_options.ignore_tidy.as_ref().map(|ignore_tidy| {
let ignore_tidy: Vec<&str> = ignore_tidy.iter().map(|s| s.as_str()).collect();
FileFilter::new(&ignore_tidy, &extensions.clone(), Some("clang-tidy"))
});
let format_filter = args
.format_options
.ignore_format
.as_ref()
.map(|ignore_format| {
let ignore_format: Vec<&str> = ignore_format.iter().map(|s| s.as_str()).collect();
FileFilter::new(&ignore_format, &extensions, Some("clang-format"))
});
ClangParams {
tidy_checks: args.tidy_options.tidy_checks.clone(),
lines_changed_only: args.source_options.lines_changed_only.clone(),
database: args.tidy_options.database.clone(),
extra_args: args.tidy_options.extra_arg.clone(),
database_json: None,
style: args.format_options.style.clone(),
clang_tidy_command: None,
clang_format_command: None,
tidy_filter,
format_filter,
tidy_review: args.feedback_options.tidy_review,
format_review: args.feedback_options.format_review,
}
}
}
pub struct FeedbackInput {
pub thread_comments: ThreadComments,
pub no_lgtm: bool,
pub step_summary: bool,
pub file_annotations: bool,
pub style: String,
pub tidy_review: bool,
pub format_review: bool,
pub passive_reviews: bool,
}
#[cfg(feature = "bin")]
impl From<&Cli> for FeedbackInput {
fn from(args: &Cli) -> Self {
FeedbackInput {
style: args.format_options.style.clone(),
no_lgtm: args.feedback_options.no_lgtm,
step_summary: args.feedback_options.step_summary,
thread_comments: args.feedback_options.thread_comments.clone(),
file_annotations: args.feedback_options.file_annotations,
tidy_review: args.feedback_options.tidy_review,
format_review: args.feedback_options.format_review,
passive_reviews: args.feedback_options.passive_reviews,
}
}
}
impl Default for FeedbackInput {
fn default() -> Self {
FeedbackInput {
thread_comments: ThreadComments::Off,
no_lgtm: true,
step_summary: false,
file_annotations: true,
style: "llvm".to_string(),
tidy_review: false,
format_review: false,
passive_reviews: false,
}
}
}
#[cfg(all(test, feature = "bin"))]
mod test {
#![allow(clippy::unwrap_used)]
use clap::{Parser, ValueEnum};
use super::{Cli, LinesChangedOnly, ThreadComments};
#[test]
fn parse_positional() {
let cli = Cli::parse_from(["cpp-linter", "file1.c", "file2.h"]);
let not_ignored = cli.not_ignored.expect("failed to parse positional args");
assert!(!not_ignored.is_empty());
assert!(not_ignored.contains(&String::from("file1.c")));
assert!(not_ignored.contains(&String::from("file2.h")));
}
#[test]
fn display_lines_changed_only_enum() {
let input = "Diff";
assert_eq!(
LinesChangedOnly::from_str(input, true).unwrap(),
LinesChangedOnly::Diff
);
assert_eq!(format!("{}", LinesChangedOnly::Diff), input.to_lowercase());
assert_eq!(
LinesChangedOnly::from_str(input, false).unwrap(),
LinesChangedOnly::Off
);
}
#[test]
fn display_thread_comments_enum() {
let input = "Update";
assert_eq!(
ThreadComments::from_str(input, true).unwrap(),
ThreadComments::Update
);
assert_eq!(format!("{}", ThreadComments::Update), input.to_lowercase());
assert_eq!(
ThreadComments::from_str(input, false).unwrap(),
ThreadComments::Off
);
}
}