Skip to content

Commit 17fb8a6

Browse files
committed
fix: propagate errors
This follows idiomatic rust [error handling] by using the anyhow library. [error handling]: https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html [anyhow library]: https://crates.io/crates/anyhow
1 parent 4f6d221 commit 17fb8a6

File tree

21 files changed

+644
-603
lines changed

21 files changed

+644
-603
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cpp-linter/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ license.workspace = true
1414
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1515

1616
[dependencies]
17+
anyhow = "1.0.89"
1718
chrono = "0.4.38"
1819
clap = "4.5.17"
1920
fast-glob = "0.4.0"

cpp-linter/src/clang_tools/clang_format.rs

+24-21
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{
66
sync::{Arc, Mutex, MutexGuard},
77
};
88

9+
use anyhow::{Context, Result};
910
use log::Level;
1011
// non-std crates
1112
use serde::Deserialize;
@@ -109,15 +110,16 @@ pub fn tally_format_advice(files: &[Arc<Mutex<FileObj>>]) -> u64 {
109110
pub fn run_clang_format(
110111
file: &mut MutexGuard<FileObj>,
111112
clang_params: &ClangParams,
112-
) -> Vec<(log::Level, String)> {
113+
) -> Result<Vec<(log::Level, String)>> {
113114
let mut cmd = Command::new(clang_params.clang_format_command.as_ref().unwrap());
114115
let mut logs = vec![];
115116
cmd.args(["--style", &clang_params.style]);
116117
let ranges = file.get_ranges(&clang_params.lines_changed_only);
117118
for range in &ranges {
118119
cmd.arg(format!("--lines={}:{}", range.start(), range.end()));
119120
}
120-
cmd.arg(file.name.to_string_lossy().as_ref());
121+
let file_name = file.name.to_string_lossy().to_string();
122+
cmd.arg(file.name.to_path_buf().as_os_str());
121123
let mut patched = None;
122124
if clang_params.format_review {
123125
logs.push((
@@ -129,7 +131,7 @@ pub fn run_clang_format(
129131
.as_ref()
130132
.unwrap()
131133
.to_str()
132-
.unwrap(),
134+
.unwrap_or_default(),
133135
cmd.get_args()
134136
.map(|a| a.to_str().unwrap())
135137
.collect::<Vec<&str>>()
@@ -138,7 +140,7 @@ pub fn run_clang_format(
138140
));
139141
patched = Some(
140142
cmd.output()
141-
.expect("Failed to get fixes from clang-format")
143+
.with_context(|| format!("Failed to get fixes from clang-format: {file_name}"))?
142144
.stdout,
143145
);
144146
}
@@ -147,33 +149,34 @@ pub fn run_clang_format(
147149
log::Level::Info,
148150
format!(
149151
"Running \"{} {}\"",
150-
clang_params
151-
.clang_format_command
152-
.as_ref()
153-
.unwrap()
154-
.to_str()
155-
.unwrap(),
152+
cmd.get_program().to_string_lossy(),
156153
cmd.get_args()
157154
.map(|x| x.to_str().unwrap())
158155
.collect::<Vec<&str>>()
159156
.join(" ")
160157
),
161158
));
162-
let output = cmd.output().unwrap();
159+
let output = cmd
160+
.output()
161+
.with_context(|| format!("Failed to get replacements from clang-format: {file_name}"))?;
163162
if !output.stderr.is_empty() || !output.status.success() {
164-
logs.push((
165-
log::Level::Debug,
166-
format!(
167-
"clang-format raised the follow errors:\n{}",
168-
String::from_utf8(output.stderr).unwrap()
169-
),
170-
));
163+
if let Ok(stderr) = String::from_utf8(output.stderr) {
164+
logs.push((
165+
log::Level::Debug,
166+
format!("clang-format raised the follow errors:\n{}", stderr),
167+
));
168+
} else {
169+
logs.push((
170+
log::Level::Error,
171+
"stderr from clang-format was not UTF-8 encoded".to_string(),
172+
));
173+
}
171174
}
172175
if output.stdout.is_empty() {
173-
return logs;
176+
return Ok(logs);
174177
}
175178
let xml = String::from_utf8(output.stdout)
176-
.unwrap()
179+
.with_context(|| format!("stdout from clang-format was not UTF-8 encoded: {file_name}"))?
177180
.lines()
178181
.collect::<Vec<&str>>()
179182
.join("");
@@ -208,7 +211,7 @@ pub fn run_clang_format(
208211
format_advice.replacements = filtered_replacements;
209212
}
210213
file.format_advice = Some(format_advice);
211-
logs
214+
Ok(logs)
212215
}
213216

214217
#[cfg(test)]

cpp-linter/src/clang_tools/clang_tidy.rs

+42-40
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::{
99
sync::{Arc, Mutex, MutexGuard},
1010
};
1111

12+
use anyhow::{Context, Result};
1213
// non-std crates
1314
use regex::Regex;
1415
use serde::Deserialize;
@@ -132,7 +133,7 @@ impl MakeSuggestions for TidyAdvice {
132133
fn parse_tidy_output(
133134
tidy_stdout: &[u8],
134135
database_json: &Option<Vec<CompilationUnit>>,
135-
) -> Option<TidyAdvice> {
136+
) -> Result<Option<TidyAdvice>> {
136137
let note_header = Regex::new(r"^(.+):(\d+):(\d+):\s(\w+):(.*)\[([a-zA-Z\d\-\.]+)\]$").unwrap();
137138
let fixed_note =
138139
Regex::new(r"^.+:(\d+):\d+:\snote: FIX-IT applied suggested code changes$").unwrap();
@@ -178,14 +179,15 @@ fn parse_tidy_output(
178179
// likely not a member of the project's sources (ie /usr/include/stdio.h)
179180
filename = filename
180181
.strip_prefix(&cur_dir)
181-
.expect("cannot determine filename by relative path.")
182+
// we already checked above that filename.starts_with(current_directory)
183+
.unwrap()
182184
.to_path_buf();
183185
}
184186

185187
notification = Some(TidyNotification {
186188
filename: filename.to_string_lossy().to_string().replace('\\', "/"),
187-
line: captured[2].parse::<u32>().unwrap(),
188-
cols: captured[3].parse::<u32>().unwrap(),
189+
line: captured[2].parse()?,
190+
cols: captured[3].parse()?,
189191
severity: String::from(&captured[4]),
190192
rationale: String::from(&captured[5]).trim().to_string(),
191193
diagnostic: String::from(&captured[6]),
@@ -195,9 +197,7 @@ fn parse_tidy_output(
195197
// begin capturing subsequent lines as suggestions
196198
found_fix = false;
197199
} else if let Some(capture) = fixed_note.captures(line) {
198-
let fixed_line = capture[1]
199-
.parse()
200-
.expect("Failed to parse fixed line number's string as integer");
200+
let fixed_line = capture[1].parse()?;
201201
if let Some(note) = &mut notification {
202202
if !note.fixed_lines.contains(&fixed_line) {
203203
note.fixed_lines.push(fixed_line);
@@ -219,12 +219,12 @@ fn parse_tidy_output(
219219
result.push(note);
220220
}
221221
if result.is_empty() {
222-
None
222+
Ok(None)
223223
} else {
224-
Some(TidyAdvice {
224+
Ok(Some(TidyAdvice {
225225
notes: result,
226226
patched: None,
227-
})
227+
}))
228228
}
229229
}
230230

@@ -249,7 +249,7 @@ pub fn tally_tidy_advice(files: &[Arc<Mutex<FileObj>>]) -> u64 {
249249
pub fn run_clang_tidy(
250250
file: &mut MutexGuard<FileObj>,
251251
clang_params: &ClangParams,
252-
) -> Vec<(log::Level, std::string::String)> {
252+
) -> Result<Vec<(log::Level, std::string::String)>> {
253253
let mut cmd = Command::new(clang_params.clang_tidy_command.as_ref().unwrap());
254254
let mut logs = vec![];
255255
if !clang_params.tidy_checks.is_empty() {
@@ -258,19 +258,15 @@ pub fn run_clang_tidy(
258258
if let Some(db) = &clang_params.database {
259259
cmd.args(["-p", &db.to_string_lossy()]);
260260
}
261-
if let Some(extras) = &clang_params.extra_args {
262-
for arg in extras {
263-
cmd.args(["--extra-arg", format!("\"{}\"", arg).as_str()]);
264-
}
261+
for arg in &clang_params.extra_args {
262+
cmd.args(["--extra-arg", format!("\"{}\"", arg).as_str()]);
265263
}
264+
let file_name = file.name.to_string_lossy().to_string();
266265
if clang_params.lines_changed_only != LinesChangedOnly::Off {
267266
let ranges = file.get_ranges(&clang_params.lines_changed_only);
268267
let filter = format!(
269268
"[{{\"name\":{:?},\"lines\":{:?}}}]",
270-
&file
271-
.name
272-
.to_string_lossy()
273-
.replace('/', if OS == "windows" { "\\" } else { "/" }),
269+
&file_name.replace('/', if OS == "windows" { "\\" } else { "/" }),
274270
ranges
275271
.iter()
276272
.map(|r| [r.start(), r.end()])
@@ -281,10 +277,12 @@ pub fn run_clang_tidy(
281277
let mut original_content = None;
282278
if clang_params.tidy_review {
283279
cmd.arg("--fix-errors");
284-
original_content =
285-
Some(fs::read_to_string(&file.name).expect(
286-
"Failed to cache file's original content before applying clang-tidy changes.",
287-
));
280+
original_content = Some(fs::read_to_string(&file.name).with_context(|| {
281+
format!(
282+
"Failed to cache file's original content before applying clang-tidy changes: {}",
283+
file_name.clone()
284+
)
285+
})?);
288286
}
289287
if !clang_params.style.is_empty() {
290288
cmd.args(["--format-style", clang_params.style.as_str()]);
@@ -310,29 +308,32 @@ pub fn run_clang_tidy(
310308
),
311309
));
312310
if !output.stderr.is_empty() {
313-
logs.push((
314-
log::Level::Debug,
315-
format!(
316-
"clang-tidy made the following summary:\n{}",
317-
String::from_utf8(output.stderr).unwrap()
318-
),
319-
));
311+
if let Ok(stderr) = String::from_utf8(output.stderr) {
312+
logs.push((
313+
log::Level::Debug,
314+
format!("clang-tidy made the following summary:\n{}", stderr),
315+
));
316+
} else {
317+
logs.push((
318+
log::Level::Error,
319+
"clang-tidy stderr is not UTF-8 encoded".to_string(),
320+
));
321+
}
320322
}
321-
file.tidy_advice = parse_tidy_output(&output.stdout, &clang_params.database_json);
323+
file.tidy_advice = parse_tidy_output(&output.stdout, &clang_params.database_json)?;
322324
if clang_params.tidy_review {
323-
let file_name = &file.name.to_owned();
324325
if let Some(tidy_advice) = &mut file.tidy_advice {
325326
// cache file changes in a buffer and restore the original contents for further analysis by clang-format
326327
tidy_advice.patched =
327-
Some(fs::read(file_name).expect("Failed to read changes from clang-tidy"));
328+
Some(fs::read(&file_name).with_context(|| {
329+
format!("Failed to read changes from clang-tidy: {file_name}")
330+
})?);
328331
}
329-
fs::write(
330-
file_name,
331-
original_content.expect("original content of file was not cached"),
332-
)
333-
.expect("failed to restore file's original content.");
332+
// original_content is guaranteed to be Some() value at this point
333+
fs::write(&file_name, original_content.unwrap())
334+
.with_context(|| format!("Failed to restore file's original content: {file_name}"))?;
334335
}
335-
logs
336+
Ok(logs)
336337
}
337338

338339
#[cfg(test)]
@@ -427,7 +428,7 @@ mod test {
427428
tidy_checks: "".to_string(), // use .clang-tidy config file
428429
lines_changed_only: LinesChangedOnly::Off,
429430
database: None,
430-
extra_args: Some(extra_args.clone()), // <---- the reason for this test
431+
extra_args: extra_args.clone(), // <---- the reason for this test
431432
database_json: None,
432433
format_filter: None,
433434
tidy_filter: None,
@@ -438,6 +439,7 @@ mod test {
438439
};
439440
let mut file_lock = arc_ref.lock().unwrap();
440441
let logs = run_clang_tidy(&mut file_lock, &clang_params)
442+
.unwrap()
441443
.into_iter()
442444
.filter_map(|(_lvl, msg)| {
443445
if msg.contains("Running ") {

0 commit comments

Comments
 (0)