Skip to content

Commit 793c310

Browse files
authored
refactor: use stricter linting rules (#353)
- mandates no panicking code (eg. `unwrap()`, `expect()`, `panic!()`, etc) used in production; tests are allowed to panic. - ensures all public API has a doc comment. Any public API missing a doc comment now has a one. - collapsed single-file submodules into a normal submodule (eg. `common_fs/mod.rs` becomes just `common_fs.rs`) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added REST client helpers for debug checks and log-group start/end. * **Bug Fixes / Improvements** * Safer comment generation with fallible error propagation. * Filtered out invalid zero line numbers when computing added-line ranges. * Tests adjusted to allow certain test-only unwrap/expect usage. * **Documentation** * Expanded Rustdoc comments across CLI, error types, and tooling modules. * **Refactor** * Hardened crate linting and made the git-related module internal. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 4d49e6b commit 793c310

13 files changed

Lines changed: 261 additions & 42 deletions

File tree

cpp-linter/src/clang_tools/clang_format.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ use log::Level;
1616
use super::{CACHE_DIR, MakeSuggestions};
1717
use crate::{cli::ClangParams, common_fs::FileObj, error::ClangCaptureError};
1818

19+
/// A struct to hold clang-format advice for a single file.
1920
#[derive(Debug, Clone, PartialEq, Eq, Default)]
2021
pub struct FormatAdvice {
2122
/// A list of line ranges that clang-format wants to replace.
2223
pub replacements: Vec<RangeInclusive<u32>>,
2324

25+
/// A path to a cached file containing the full contents of the file after applying clang-format fixes.
2426
pub patched: PathBuf,
2527
}
2628

cpp-linter/src/clang_tools/clang_tidy.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ pub struct TidyNotification {
7070
}
7171

7272
impl TidyNotification {
73+
/// Get a markdown-formatted link to the clang-tidy documentation page for [`Self::diagnostic`].
7374
pub fn diagnostic_link(&self) -> String {
7475
if self.diagnostic.starts_with("clang-diagnostic-") {
7576
// clang-diagnostic-* diagnostics are compiler diagnostics and don't have
@@ -101,6 +102,8 @@ impl TidyNotification {
101102
pub struct TidyAdvice {
102103
/// A list of notifications parsed from clang-tidy stdout.
103104
pub notes: Vec<TidyNotification>,
105+
106+
/// A buffer to hold the contents of the file after applying clang-tidy fixes.
104107
pub patched: Option<Vec<u8>>,
105108
}
106109

@@ -377,7 +380,7 @@ pub fn run_clang_tidy(
377380

378381
#[cfg(test)]
379382
mod test {
380-
#![allow(clippy::unwrap_used)]
383+
#![allow(clippy::unwrap_used, clippy::expect_used)]
381384

382385
use std::{
383386
env,

cpp-linter/src/clang_tools/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#![deny(clippy::unwrap_used)]
21
//! This module holds the functionality related to running clang-format and/or
32
//! clang-tidy.
43
@@ -28,6 +27,7 @@ use clang_format::run_clang_format;
2827
pub mod clang_tidy;
2928
use clang_tidy::run_clang_tidy;
3029

30+
/// The directory name to use for caching clang-tidy and clang-format results.
3131
pub const CACHE_DIR: &str = ".cpp-linter-cache";
3232

3333
/// This creates a task to run clang-tidy and clang-format on a single file.
@@ -95,7 +95,7 @@ pub struct ClangVersions {
9595
pub tidy_version: Option<Version>,
9696
}
9797

98-
/// Runs clang-tidy and/or clang-format and returns the parsed output from each.
98+
/// Runs clang-tidy and/or clang-format and returns the version used for each.
9999
///
100100
/// If `tidy_checks` is `"-*"` then clang-tidy is not executed.
101101
/// If `style` is a blank string (`""`), then clang-format is not executed.
@@ -228,6 +228,7 @@ pub struct ReviewComments {
228228
}
229229

230230
impl ReviewComments {
231+
/// Get a markdown-formatted string that summarizes the given [`ReviewComment`]s.
231232
pub fn summarize(
232233
&self,
233234
clang_versions: &ClangVersions,
@@ -290,6 +291,7 @@ impl ReviewComments {
290291
body
291292
}
292293

294+
/// Check if a given comment's [`Suggestion`] is already contained within the existing [`Self::comments`].
293295
pub fn is_comment_in_suggestions(&mut self, comment: &Suggestion) -> bool {
294296
for s in &mut self.comments {
295297
if s.path == comment.path
@@ -305,6 +307,8 @@ impl ReviewComments {
305307
}
306308
}
307309

310+
/// A helper function to create a [`Diff`] and its associated [`InternedInput`] from
311+
/// a `patched` buffer and the `original_content`` of the file.
308312
pub fn make_patch<'buffer>(
309313
patched: &'buffer str,
310314
original_content: &'buffer str,

cpp-linter/src/cli/mod.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
#![deny(clippy::unwrap_used)]
2-
31
//! This module holds the Command Line Interface design.
2+
43
use std::path::PathBuf;
54
#[cfg(feature = "bin")]
65
use std::str::FromStr;
@@ -22,7 +21,9 @@ pub use structs::{ClangParams, FeedbackInput, LinesChangedOnly, ThreadComments};
2221
#[cfg(feature = "bin")]
2322
#[derive(Debug, Clone, PartialEq, Eq, ValueEnum)]
2423
pub enum Verbosity {
24+
/// Enables the [`log::Level::Info`] log level and above.
2525
Info,
26+
/// Enables the [`log::Level::Debug`] log level and above.
2627
Debug,
2728
}
2829

@@ -39,18 +40,23 @@ impl Verbosity {
3940
#[derive(Debug, Clone, Parser)]
4041
#[command(author, about)]
4142
pub struct Cli {
43+
/// The CLI's general options, such as `--version` and `--verbosity`.
4244
#[command(flatten)]
4345
pub general_options: GeneralOptions,
4446

47+
/// The CLI's source options, such as `--extensions` and `--ignore`.
4548
#[command(flatten)]
4649
pub source_options: SourceOptions,
4750

51+
/// The CLI's clang-format options, such as `--style` and `--ignore-format`.
4852
#[command(flatten)]
4953
pub format_options: FormatOptions,
5054

55+
/// The CLI's clang-tidy options, such as `--tidy-checks` and `--database`.
5156
#[command(flatten)]
5257
pub tidy_options: TidyOptions,
5358

59+
/// The CLI's feedback options, such as `--thread-comments` and `--no-lgtm`.
5460
#[command(flatten)]
5561
pub feedback_options: FeedbackOptions,
5662

@@ -67,6 +73,9 @@ pub struct Cli {
6773
)]
6874
pub not_ignored: Option<Vec<String>>,
6975

76+
/// A subcommand to run instead of the default action of cpp-linter.
77+
///
78+
/// This is currently only used for the `version` subcommand, which prints the version of cpp-linter and exits.
7079
#[command(subcommand)]
7180
pub commands: Option<CliCommand>,
7281
}

cpp-linter/src/cli/structs.rs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ impl ValueEnum for LinesChangedOnly {
7878
}
7979

8080
impl LinesChangedOnly {
81+
/// Is the instance valid for under the given conditions/flags?
8182
pub fn is_change_valid(&self, added_lines: bool, diff_chunks: bool) -> bool {
8283
match self {
8384
LinesChangedOnly::Off => true,
@@ -164,20 +165,68 @@ impl Display for ThreadComments {
164165

165166
/// A data structure to contain CLI options that relate to
166167
/// clang-tidy or clang-format arguments.
168+
///
169+
/// This struct is designed to be a thread-safe vehicle for common clang arguments and configurations.
167170
#[derive(Debug, Clone, Default)]
168171
pub struct ClangParams {
172+
/// The clang-tidy checks to run.
173+
///
174+
/// Format of this string follows the `-checks` argument of clang-tidy.
169175
pub tidy_checks: String,
176+
177+
/// Focus on changed lines or entire files.
170178
pub lines_changed_only: LinesChangedOnly,
179+
180+
/// An optional path to a compilation database, used for clang-tidy.
171181
pub database: Option<PathBuf>,
182+
183+
/// Extra arguments to pass to clang-tidy.
184+
///
185+
/// Format of these strings follows the `-extra-arg` argument of clang-tidy.
172186
pub extra_args: Vec<String>,
187+
188+
/// An optional list of compilation units, used for clang-tidy.
189+
///
190+
/// This can be set to None initially, but it will be populated by
191+
/// [`capture_clang_tools_output()`](crate::clang_tools::capture_clang_tools_output),
192+
/// if the [`Self::database`] is given [`Some`] valid value (and the
193+
/// compile_commands.json file is parsed successfully).
173194
pub database_json: Option<Vec<CompilationUnit>>,
195+
196+
/// The clang-format style to use.
197+
///
198+
/// Format of this string follows the `-style` argument of clang-format.
174199
pub style: String,
200+
201+
/// An optional path to the clang-tidy executable.
202+
///
203+
/// If [`Self::tidy_checks`] is not `-*`, then this will be populated by
204+
/// [`capture_clang_tools_output()`](crate::clang_tools::capture_clang_tools_output),
205+
/// regardless if this is given [`Some`] value.
175206
pub clang_tidy_command: Option<PathBuf>,
207+
208+
/// An optional path to the clang-format executable.
209+
///
210+
/// If [`Self::style`] is not an empty string, then this will be populated by
211+
/// [`capture_clang_tools_output()`](crate::clang_tools::capture_clang_tools_output),
212+
/// regardless if this is given [`Some`] value.
176213
pub clang_format_command: Option<PathBuf>,
214+
215+
/// An optional [`FileFilter`] to exclude files only from clang-tidy analysis.
177216
pub tidy_filter: Option<FileFilter>,
217+
218+
/// An optional [`FileFilter`] to exclude files only from clang-format analysis.
178219
pub format_filter: Option<FileFilter>,
220+
221+
/// Assert this if preparing a PR review with clang-tidy advice.
179222
pub tidy_review: bool,
223+
224+
/// Assert this if preparing a PR review with clang-format advice.
180225
pub format_review: bool,
226+
227+
/// The root of the repository, used to locate relative file paths in processing.
228+
///
229+
/// A project-specific cache folder is created in this path.
181230
pub repo_root: PathBuf,
182231
}
183232

@@ -237,14 +286,33 @@ impl From<&Cli> for ClangParams {
237286
/// A struct to contain CLI options that relate to
238287
/// [`RestClient.post_feedback()`](fn@crate::rest_api::RestClient.post_feedback()).
239288
pub struct FeedbackInput {
289+
/// How thread comments are created or updated.
240290
pub thread_comments: ThreadComments,
291+
292+
/// Whether to omit a "LGTM" type message.
241293
pub no_lgtm: bool,
294+
295+
/// Whether to post a step summary comment.
242296
pub step_summary: bool,
297+
298+
/// Whether to post file annotations.
243299
pub file_annotations: bool,
300+
301+
/// The clang-format style to show in file annotations.
244302
pub style: String,
303+
304+
/// Whether to post a PR review with clang-tidy suggestions/notes.
245305
pub tidy_review: bool,
306+
307+
/// Whether to post a PR review with clang-format suggestions.
246308
pub format_review: bool,
309+
310+
/// Should PR reviews be commentary?
311+
///
312+
/// If false, reviews will approve or request changes.
247313
pub passive_reviews: bool,
314+
315+
/// The root of the repository, used to locate relative file paths in processing.
248316
pub repo_root: PathBuf,
249317
}
250318

@@ -285,7 +353,7 @@ impl Default for FeedbackInput {
285353

286354
#[cfg(all(test, feature = "bin"))]
287355
mod test {
288-
#![allow(clippy::unwrap_used)]
356+
#![allow(clippy::unwrap_used, clippy::expect_used)]
289357

290358
use clap::{Parser, ValueEnum};
291359

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::{
44
fmt::Debug,
55
fs,
6+
num::NonZeroU32,
67
ops::RangeInclusive,
78
path::{Path, PathBuf},
89
};
@@ -61,10 +62,15 @@ impl FileObj {
6162
added_lines: Vec<u32>,
6263
diff_chunks: Vec<RangeInclusive<u32>>,
6364
) -> Self {
65+
// filter out any line numbers that are 0 since line numbers are always 1-indexed in diffs
66+
let added_lines: Vec<NonZeroU32> = added_lines
67+
.into_iter()
68+
.filter_map(NonZeroU32::new)
69+
.collect();
6470
let added_ranges = FileObj::consolidate_numbers_to_ranges(&added_lines);
6571
FileObj {
6672
name,
67-
added_lines,
73+
added_lines: added_lines.into_iter().map(|v| v.get()).collect(),
6874
added_ranges,
6975
diff_chunks,
7076
format_advice: None,
@@ -75,23 +81,38 @@ impl FileObj {
7581
/// A helper function to consolidate a [Vec<u32>] of line numbers into a
7682
/// [Vec<RangeInclusive<u32>>] in which each range describes the beginning and
7783
/// ending of a group of consecutive line numbers.
78-
fn consolidate_numbers_to_ranges(lines: &[u32]) -> Vec<RangeInclusive<u32>> {
79-
let mut range_start = None;
84+
fn consolidate_numbers_to_ranges(lines: &[NonZeroU32]) -> Vec<RangeInclusive<u32>> {
8085
let mut ranges: Vec<RangeInclusive<u32>> = Vec::new();
81-
for (index, number) in lines.iter().enumerate() {
82-
if index == 0 {
83-
range_start = Some(*number);
84-
} else if number - 1 != lines[index - 1] {
85-
ranges.push(RangeInclusive::new(range_start.unwrap(), lines[index - 1]));
86-
range_start = Some(*number);
86+
let mut line_iter = lines.iter().enumerate();
87+
let mut range_start = match line_iter.next() {
88+
Some((_, number)) => number.get(),
89+
None => return ranges, // return empty vector if no lines
90+
};
91+
// lines.len() cannot be 0 at this point
92+
let last_index = lines.len() - 1;
93+
if last_index == 0 {
94+
// Single element case: push range and return
95+
ranges.push(RangeInclusive::new(range_start, range_start));
96+
return ranges;
97+
}
98+
for (index, number) in line_iter {
99+
// use let chain to avoid repeated lookup of lines[index - 1].
100+
// should always yield some value since we entered the for loop at index 1.
101+
if let Some(prev_line) = lines.get(index - 1)
102+
&& number.get() - 1 != prev_line.get()
103+
{
104+
ranges.push(RangeInclusive::new(range_start, prev_line.get()));
105+
range_start = number.get();
87106
}
88-
if index == lines.len() - 1 {
89-
ranges.push(RangeInclusive::new(range_start.unwrap(), *number));
107+
if index == last_index {
108+
ranges.push(RangeInclusive::new(range_start, number.get()));
90109
}
91110
}
92111
ranges
93112
}
94113

114+
/// Get the list of line ranges to consider based on the given
115+
/// [`LinesChangedOnly`] configuration.
95116
pub fn get_ranges(&self, lines_changed_only: &LinesChangedOnly) -> Vec<RangeInclusive<u32>> {
96117
match lines_changed_only {
97118
LinesChangedOnly::Diff => self.diff_chunks.to_vec(),
@@ -265,6 +286,14 @@ mod test {
265286
assert_eq!(ranges, vec![4..=5, 9..=9]);
266287
}
267288

289+
#[test]
290+
fn get_ranges_single_added_line() {
291+
let added_lines = vec![5];
292+
let file_obj = FileObj::from(PathBuf::from("tests/demo/demo.cpp"), added_lines, vec![]);
293+
let ranges = file_obj.get_ranges(&LinesChangedOnly::On);
294+
assert_eq!(ranges, vec![5..=5]);
295+
}
296+
268297
#[test]
269298
fn line_not_in_diff() {
270299
let file_obj = FileObj::new(PathBuf::from("tests/demo/demo.cpp"));

0 commit comments

Comments
 (0)