Skip to content

Commit 4c4abbb

Browse files
authored
style: start phasing out .unwrap() calls (#242)
Any `.unwrap()` will cause a panic from the call site. This replaces some calls to `.unwrap()` with a proper error message that can be better understood by users. Most of these calls sites are guarded by if conditions, so these new error messages should never be seen. But, just in case there's is some unforeseen bad behavior, these error messages should provide a better user experience. This doesn't cover the all of cpp-linter/**.rs sources. I've started migrating some of the REST API and git diff-ing behavior to [2bndy5/git-bot-feedback]. In doing so, the `.unwrap()` calls were replaced with custom exceptions. So, migrating to [2bndy5/git-bot-feedback] should take care of most other `.unwrap()` calls. [2bndy5/git-bot-feedback]: https://github.com/2bndy5/git-bot-feedback
1 parent 184e32e commit 4c4abbb

File tree

9 files changed

+128
-97
lines changed

9 files changed

+128
-97
lines changed

cpp-linter/clippy.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
allow-unwrap-in-tests = true

cpp-linter/src/clang_tools/clang_format.rs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{
77
sync::{Arc, Mutex, MutexGuard},
88
};
99

10-
use anyhow::{Context, Result};
10+
use anyhow::{Context, Result, anyhow};
1111
use log::Level;
1212
use serde::Deserialize;
1313

@@ -54,11 +54,12 @@ pub struct Replacement {
5454

5555
/// Get a string that summarizes the given `--style`
5656
pub fn summarize_style(style: &str) -> String {
57-
if ["google", "chromium", "microsoft", "mozilla", "webkit"].contains(&style) {
57+
let mut char_iter = style.chars();
58+
if ["google", "chromium", "microsoft", "mozilla", "webkit"].contains(&style)
59+
&& let Some(first_char) = char_iter.next()
60+
{
5861
// capitalize the first letter
59-
let mut char_iter = style.chars();
60-
let first_char = char_iter.next().unwrap();
61-
first_char.to_uppercase().collect::<String>() + char_iter.as_str()
62+
first_char.to_ascii_uppercase().to_string() + char_iter.as_str()
6263
} else if style == "llvm" || style == "gnu" {
6364
style.to_ascii_uppercase()
6465
} else {
@@ -67,25 +68,31 @@ pub fn summarize_style(style: &str) -> String {
6768
}
6869

6970
/// Get a total count of clang-format advice from the given list of [FileObj]s.
70-
pub fn tally_format_advice(files: &[Arc<Mutex<FileObj>>]) -> u64 {
71+
pub fn tally_format_advice(files: &[Arc<Mutex<FileObj>>]) -> Result<u64> {
7172
let mut total = 0;
7273
for file in files {
73-
let file = file.lock().unwrap();
74+
let file = file
75+
.lock()
76+
.map_err(|_| anyhow!("Failed to acquire lock on mutex for a source file"))?;
7477
if let Some(advice) = &file.format_advice
7578
&& !advice.replacements.is_empty()
7679
{
7780
total += 1;
7881
}
7982
}
80-
total
83+
Ok(total)
8184
}
8285

8386
/// Run clang-format for a specific `file`, then parse and return it's XML output.
8487
pub fn run_clang_format(
8588
file: &mut MutexGuard<FileObj>,
8689
clang_params: &ClangParams,
8790
) -> Result<Vec<(log::Level, String)>> {
88-
let mut cmd = Command::new(clang_params.clang_format_command.as_ref().unwrap());
91+
let cmd_path = clang_params
92+
.clang_format_command
93+
.as_ref()
94+
.ok_or(anyhow!("clang-format path unknown"))?;
95+
let mut cmd = Command::new(cmd_path);
8996
let mut logs = vec![];
9097
cmd.args(["--style", &clang_params.style]);
9198
let ranges = file.get_ranges(&clang_params.lines_changed_only);
@@ -101,12 +108,7 @@ pub fn run_clang_format(
101108
Level::Info,
102109
format!(
103110
"Getting format fixes with \"{} {}\"",
104-
clang_params
105-
.clang_format_command
106-
.as_ref()
107-
.unwrap()
108-
.to_str()
109-
.unwrap_or_default(),
111+
cmd.get_program().to_string_lossy(),
110112
cmd.get_args()
111113
.map(|a| a.to_string_lossy())
112114
.collect::<Vec<_>>()

cpp-linter/src/clang_tools/clang_tidy.rs

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::{
1010
};
1111

1212
// non-std crates
13-
use anyhow::{Context, Result};
13+
use anyhow::{Context, Result, anyhow};
1414
use regex::Regex;
1515
use serde::Deserialize;
1616

@@ -148,14 +148,18 @@ fn parse_tidy_output(
148148
tidy_stdout: &[u8],
149149
database_json: &Option<Vec<CompilationUnit>>,
150150
) -> Result<TidyAdvice> {
151-
let note_header = Regex::new(NOTE_HEADER).unwrap();
152-
let fixed_note =
153-
Regex::new(r"^.+:(\d+):\d+:\snote: FIX-IT applied suggested code changes$").unwrap();
151+
let note_header = Regex::new(NOTE_HEADER)
152+
.with_context(|| "Failed to compile RegExp pattern for note header")?;
153+
let fixed_note = Regex::new(r"^.+:(\d+):\d+:\snote: FIX-IT applied suggested code changes$")
154+
.with_context(|| "Failed to compile RegExp pattern for fixed note")?;
154155
let mut found_fix = false;
155156
let mut notification = None;
156157
let mut result = Vec::new();
157-
let cur_dir = current_dir().unwrap();
158-
for line in String::from_utf8(tidy_stdout.to_vec()).unwrap().lines() {
158+
let cur_dir = current_dir().with_context(|| "Failed to access current working directory")?;
159+
for line in String::from_utf8(tidy_stdout.to_vec())
160+
.with_context(|| "Failed to convert clang-tidy stdout to UTF-8 string")?
161+
.lines()
162+
{
159163
if let Some(captured) = note_header.captures(line) {
160164
// First check that the diagnostic name is a actual diagnostic name.
161165
// Sometimes clang-tidy uses square brackets to enclose additional context
@@ -197,14 +201,12 @@ fn parse_tidy_output(
197201
filename = normalize_path(&PathBuf::from_iter([&cur_dir, &filename]));
198202
}
199203
debug_assert!(filename.is_absolute());
200-
if filename.is_absolute() && filename.starts_with(&cur_dir) {
204+
if filename.is_absolute()
205+
&& let Ok(file_n) = filename.strip_prefix(&cur_dir)
206+
{
201207
// if this filename can't be made into a relative path, then it is
202208
// likely not a member of the project's sources (ie /usr/include/stdio.h)
203-
filename = filename
204-
.strip_prefix(&cur_dir)
205-
// we already checked above that filename.starts_with(current_directory)
206-
.unwrap()
207-
.to_path_buf();
209+
filename = file_n.to_path_buf();
208210
}
209211

210212
notification = Some(TidyNotification {
@@ -247,10 +249,12 @@ fn parse_tidy_output(
247249
}
248250

249251
/// Get a total count of clang-tidy advice from the given list of [FileObj]s.
250-
pub fn tally_tidy_advice(files: &[Arc<Mutex<FileObj>>]) -> u64 {
252+
pub fn tally_tidy_advice(files: &[Arc<Mutex<FileObj>>]) -> Result<u64> {
251253
let mut total = 0;
252254
for file in files {
253-
let file = file.lock().unwrap();
255+
let file = file
256+
.lock()
257+
.map_err(|_| anyhow!("Failed to acquire lock on mutex for a source file"))?;
254258
if let Some(advice) = &file.tidy_advice {
255259
for tidy_note in &advice.notes {
256260
let file_path = PathBuf::from(&tidy_note.filename);
@@ -260,15 +264,19 @@ pub fn tally_tidy_advice(files: &[Arc<Mutex<FileObj>>]) -> u64 {
260264
}
261265
}
262266
}
263-
total
267+
Ok(total)
264268
}
265269

266270
/// Run clang-tidy, then parse and return it's output.
267271
pub fn run_clang_tidy(
268272
file: &mut MutexGuard<FileObj>,
269273
clang_params: &ClangParams,
270274
) -> Result<Vec<(log::Level, std::string::String)>> {
271-
let mut cmd = Command::new(clang_params.clang_tidy_command.as_ref().unwrap());
275+
let cmd_path = clang_params
276+
.clang_tidy_command
277+
.as_ref()
278+
.ok_or(anyhow!("clang-tidy command not located"))?;
279+
let mut cmd = Command::new(cmd_path);
272280
let mut logs = vec![];
273281
if !clang_params.tidy_checks.is_empty() {
274282
cmd.args(["-checks", &clang_params.tidy_checks]);
@@ -318,7 +326,12 @@ pub fn run_clang_tidy(
318326
.join(" ")
319327
),
320328
));
321-
let output = cmd.output().unwrap();
329+
let output = cmd.output().with_context(|| {
330+
format!(
331+
"Failed to execute clang-tidy on file: {}",
332+
file_name.clone()
333+
)
334+
})?;
322335
logs.push((
323336
log::Level::Debug,
324337
format!(
@@ -339,7 +352,9 @@ pub fn run_clang_tidy(
339352
&output.stdout,
340353
&clang_params.database_json,
341354
)?);
342-
if clang_params.tidy_review {
355+
if clang_params.tidy_review
356+
&& let Some(original_content) = &original_content
357+
{
343358
if let Some(tidy_advice) = &mut file.tidy_advice {
344359
// cache file changes in a buffer and restore the original contents for further analysis
345360
tidy_advice.patched =
@@ -348,7 +363,7 @@ pub fn run_clang_tidy(
348363
})?);
349364
}
350365
// original_content is guaranteed to be Some() value at this point
351-
fs::write(&file_name, original_content.unwrap())
366+
fs::write(&file_name, original_content)
352367
.with_context(|| format!("Failed to restore file's original content: {file_name}"))?;
353368
}
354369
Ok(logs)

cpp-linter/src/clang_tools/mod.rs

Lines changed: 60 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#![deny(clippy::unwrap_used)]
12
//! This module holds the functionality related to running clang-format and/or
23
//! clang-tidy.
34
@@ -60,10 +61,12 @@ impl ClangTool {
6061
pub fn get_exe_path(&self, version: &RequestedVersion) -> Result<PathBuf> {
6162
let name = self.as_str();
6263
match version {
63-
RequestedVersion::Path(path_buf) => {
64-
which_in(name, Some(path_buf), current_dir().unwrap())
65-
.map_err(|_| anyhow!("Could not find {self} by path"))
66-
}
64+
RequestedVersion::Path(path_buf) => which_in(
65+
name,
66+
Some(path_buf),
67+
current_dir().with_context(|| "Failed to access current working directory.")?,
68+
)
69+
.map_err(|_| anyhow!("Could not find {self} by path")),
6770
// Thus, we should use whatever is installed and added to $PATH.
6871
RequestedVersion::SystemDefault | RequestedVersion::NoValue => {
6972
which(name).map_err(|_| anyhow!("Could not find clang tool by name"))
@@ -118,12 +121,17 @@ impl ClangTool {
118121
fn capture_version(clang_tool: &PathBuf) -> Result<String> {
119122
let output = Command::new(clang_tool).arg("--version").output()?;
120123
let stdout = String::from_utf8_lossy(&output.stdout);
121-
let version_pattern = Regex::new(r"(?i)version[^\d]*([\d.]+)").unwrap();
124+
let version_pattern = Regex::new(r"(?i)version[^\d]*([\d.]+)")
125+
.with_context(|| "Failed to allocate RegExp pattern (for clang version parsing) ")?;
122126
let captures = version_pattern.captures(&stdout).ok_or(anyhow!(
123127
"Failed to find version number in `{} --version` output",
124128
clang_tool.to_string_lossy()
125129
))?;
126-
Ok(captures.get(1).unwrap().as_str().to_string())
130+
Ok(captures
131+
.get(1)
132+
.ok_or(anyhow!("Failed to get version capture group"))?
133+
.as_str()
134+
.to_string())
127135
}
128136
}
129137

@@ -434,53 +442,54 @@ pub trait MakeSuggestions {
434442
})?;
435443
hunks_in_patch += 1;
436444
let hunk_range = file_obj.is_hunk_in_diff(&hunk);
437-
if hunk_range.is_none() {
438-
continue;
439-
}
440-
let (start_line, end_line) = hunk_range.unwrap();
441-
let mut suggestion = String::new();
442-
let suggestion_help = self.get_suggestion_help(start_line, end_line);
443-
let mut removed = vec![];
444-
for line_index in 0..line_count {
445-
let diff_line = patch
446-
.line_in_hunk(hunk_id, line_index)
447-
.with_context(|| format!("Failed to get line {line_index} in a hunk {hunk_id} of patch for {file_name}"))?;
448-
let line = String::from_utf8(diff_line.content().to_owned())
449-
.with_context(|| format!("Failed to convert line {line_index} buffer to string in hunk {hunk_id} of patch for {file_name}"))?;
450-
if ['+', ' '].contains(&diff_line.origin()) {
451-
suggestion.push_str(line.as_str());
452-
} else {
453-
removed.push(
454-
diff_line
455-
.old_lineno()
456-
.expect("Removed line should have a line number"),
457-
);
445+
match hunk_range {
446+
None => continue,
447+
Some((start_line, end_line)) => {
448+
let mut suggestion = String::new();
449+
let suggestion_help = self.get_suggestion_help(start_line, end_line);
450+
let mut removed = vec![];
451+
for line_index in 0..line_count {
452+
let diff_line = patch
453+
.line_in_hunk(hunk_id, line_index)
454+
.with_context(|| format!("Failed to get line {line_index} in a hunk {hunk_id} of patch for {file_name}"))?;
455+
let line = String::from_utf8(diff_line.content().to_owned())
456+
.with_context(|| format!("Failed to convert line {line_index} buffer to string in hunk {hunk_id} of patch for {file_name}"))?;
457+
if ['+', ' '].contains(&diff_line.origin()) {
458+
suggestion.push_str(line.as_str());
459+
} else {
460+
removed.push(
461+
diff_line
462+
.old_lineno()
463+
.expect("Removed line should have a line number"),
464+
);
465+
}
466+
}
467+
if suggestion.is_empty() && !removed.is_empty() {
468+
suggestion.push_str(
469+
format!(
470+
"Please remove the line(s)\n- {}",
471+
removed
472+
.iter()
473+
.map(|l| l.to_string())
474+
.collect::<Vec<String>>()
475+
.join("\n- ")
476+
)
477+
.as_str(),
478+
)
479+
} else {
480+
suggestion = format!("```suggestion\n{suggestion}```");
481+
}
482+
let comment = Suggestion {
483+
line_start: start_line,
484+
line_end: end_line,
485+
suggestion: format!("{suggestion_help}\n{suggestion}"),
486+
path: file_name.clone(),
487+
};
488+
if !review_comments.is_comment_in_suggestions(&comment) {
489+
review_comments.comments.push(comment);
490+
}
458491
}
459492
}
460-
if suggestion.is_empty() && !removed.is_empty() {
461-
suggestion.push_str(
462-
format!(
463-
"Please remove the line(s)\n- {}",
464-
removed
465-
.iter()
466-
.map(|l| l.to_string())
467-
.collect::<Vec<String>>()
468-
.join("\n- ")
469-
)
470-
.as_str(),
471-
)
472-
} else {
473-
suggestion = format!("```suggestion\n{suggestion}```");
474-
}
475-
let comment = Suggestion {
476-
line_start: start_line,
477-
line_end: end_line,
478-
suggestion: format!("{suggestion_help}\n{suggestion}"),
479-
path: file_name.clone(),
480-
};
481-
if !review_comments.is_comment_in_suggestions(&comment) {
482-
review_comments.comments.push(comment);
483-
}
484493
}
485494
review_comments.tool_total[is_tidy_tool] =
486495
Some(review_comments.tool_total[is_tidy_tool].unwrap_or_default() + hunks_in_patch);

cpp-linter/src/cli/mod.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![deny(clippy::unwrap_used)]
2+
13
//! This module holds the Command Line Interface design.
24
use std::{path::PathBuf, str::FromStr};
35

@@ -437,17 +439,17 @@ pub struct FeedbackOptions {
437439
/// the value will be split at spaces.
438440
pub fn convert_extra_arg_val(args: &[String]) -> Vec<String> {
439441
let mut val = args.iter();
440-
if val.len() == 1 {
442+
if args.len() == 1
443+
&& let Some(v) = val.next()
444+
{
441445
// specified once; split and return result
442-
val.next()
443-
.unwrap()
444-
.trim_matches('\'')
446+
v.trim_matches('\'')
445447
.trim_matches('"')
446448
.split(' ')
447449
.map(|i| i.to_string())
448450
.collect()
449451
} else {
450-
// specified multiple times; just return
452+
// specified multiple times; just return a clone of the values
451453
val.map(|i| i.to_string()).collect()
452454
}
453455
}

0 commit comments

Comments
 (0)