Skip to content

Commit a282841

Browse files
authored
fix: skip parsing clang-tidy diagnostic rationale (#237)
Specifically when the rationale line has added context in square brackets. This includes a test based on the actual bug reported in cpp-linter/cpp-linter-action#389. Corresponds to cpp-linter/cpp-linter#176
1 parent 38ab160 commit a282841

File tree

2 files changed

+111
-49
lines changed

2 files changed

+111
-49
lines changed

cpp-linter/src/clang_tools/clang_tidy.rs

Lines changed: 110 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -157,59 +157,69 @@ fn parse_tidy_output(
157157
let cur_dir = current_dir().unwrap();
158158
for line in String::from_utf8(tidy_stdout.to_vec()).unwrap().lines() {
159159
if let Some(captured) = note_header.captures(line) {
160-
if let Some(note) = notification {
161-
result.push(note);
162-
}
160+
// First check that the diagnostic name is a actual diagnostic name.
161+
// Sometimes clang-tidy uses square brackets to enclose additional context
162+
// about the diagnostic rationale. For example: '[with auto = typename ...]'
163+
// We need to ignore such cases as they do not start a diagnostic report.
164+
if captured
165+
.get(6)
166+
.is_some_and(|diag| !diag.as_str().contains(' ') && diag.as_str().contains('-'))
167+
{
168+
// starting a new TidyNotification; store the previous one (if any) to results
169+
if let Some(note) = notification {
170+
result.push(note);
171+
}
163172

164-
// normalize the filename path and try to make it relative to the repo root
165-
let mut filename = PathBuf::from(&captured[1]);
166-
// if database was given try to use that first
167-
if let Some(db_json) = &database_json {
168-
let mut found_unit = false;
169-
for unit in db_json {
170-
let unit_path =
171-
PathBuf::from_iter([unit.directory.as_str(), unit.file.as_str()]);
172-
if unit_path == filename {
173-
filename =
174-
normalize_path(&PathBuf::from_iter([&unit.directory, &unit.file]));
175-
found_unit = true;
176-
break;
173+
// normalize the filename path and try to make it relative to the repo root
174+
let mut filename = PathBuf::from(&captured[1]);
175+
// if database was given try to use that first
176+
if let Some(db_json) = &database_json {
177+
let mut found_unit = false;
178+
for unit in db_json {
179+
let unit_path =
180+
PathBuf::from_iter([unit.directory.as_str(), unit.file.as_str()]);
181+
if unit_path == filename {
182+
filename =
183+
normalize_path(&PathBuf::from_iter([&unit.directory, &unit.file]));
184+
found_unit = true;
185+
break;
186+
}
177187
}
178-
}
179-
if !found_unit {
180-
// file was not a named unit in the database;
181-
// try to normalize path as if relative to working directory.
182-
// NOTE: This shouldn't happen with a properly formed JSON database
188+
if !found_unit {
189+
// file was not a named unit in the database;
190+
// try to normalize path as if relative to working directory.
191+
// NOTE: This shouldn't happen with a properly formed JSON database
192+
filename = normalize_path(&PathBuf::from_iter([&cur_dir, &filename]));
193+
}
194+
} else {
195+
// still need to normalize the relative path despite missing database info.
196+
// let's assume the file is relative to current working directory.
183197
filename = normalize_path(&PathBuf::from_iter([&cur_dir, &filename]));
184198
}
185-
} else {
186-
// still need to normalize the relative path despite missing database info.
187-
// let's assume the file is relative to current working directory.
188-
filename = normalize_path(&PathBuf::from_iter([&cur_dir, &filename]));
189-
}
190-
assert!(filename.is_absolute());
191-
if filename.is_absolute() && filename.starts_with(&cur_dir) {
192-
// if this filename can't be made into a relative path, then it is
193-
// likely not a member of the project's sources (ie /usr/include/stdio.h)
194-
filename = filename
195-
.strip_prefix(&cur_dir)
196-
// we already checked above that filename.starts_with(current_directory)
197-
.unwrap()
198-
.to_path_buf();
199-
}
199+
debug_assert!(filename.is_absolute());
200+
if filename.is_absolute() && filename.starts_with(&cur_dir) {
201+
// if this filename can't be made into a relative path, then it is
202+
// 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();
208+
}
200209

201-
notification = Some(TidyNotification {
202-
filename: filename.to_string_lossy().to_string().replace('\\', "/"),
203-
line: captured[2].parse()?,
204-
cols: captured[3].parse()?,
205-
severity: String::from(&captured[4]),
206-
rationale: String::from(&captured[5]).trim().to_string(),
207-
diagnostic: String::from(&captured[6]),
208-
suggestion: Vec::new(),
209-
fixed_lines: Vec::new(),
210-
});
211-
// begin capturing subsequent lines as suggestions
212-
found_fix = false;
210+
notification = Some(TidyNotification {
211+
filename: filename.to_string_lossy().to_string().replace('\\', "/"),
212+
line: captured[2].parse()?,
213+
cols: captured[3].parse()?,
214+
severity: String::from(&captured[4]),
215+
rationale: String::from(&captured[5]).trim().to_string(),
216+
diagnostic: String::from(&captured[6]),
217+
suggestion: Vec::new(),
218+
fixed_lines: Vec::new(),
219+
});
220+
// begin capturing subsequent lines as suggestions
221+
found_fix = false;
222+
}
213223
} else if let Some(capture) = fixed_note.captures(line) {
214224
let fixed_line = capture[1].parse()?;
215225
if let Some(note) = &mut notification
@@ -356,7 +366,7 @@ mod test {
356366
use regex::Regex;
357367

358368
use crate::{
359-
clang_tools::ClangTool,
369+
clang_tools::{ClangTool, clang_tidy::parse_tidy_output},
360370
cli::{ClangParams, LinesChangedOnly, RequestedVersion},
361371
common_fs::FileObj,
362372
};
@@ -488,4 +498,55 @@ mod test {
488498
assert!(args.contains(&extra_arg.as_str()));
489499
}
490500
}
501+
502+
#[test]
503+
fn skip_parse_tidy_diagnostic_rationale() {
504+
let tidy_out = r#"
505+
TrenchBroom/TrenchBroom/common/test/src/mdl/tst_ReadFreeImageTexture.cpp:46:19: error: use of undeclared identifier 'readFreeImageTexture' [clang-diagnostic-error]
506+
46 | return readFreeImageTexture(reader);
507+
| ^
508+
TrenchBroom/TrenchBroom/lib/KdLib/include/kd/result.h:659:32: note: in instantiation of function template specialization 'tb::mdl::(anonymous namespace)::loadTexture(const std::string &)::(anonymous class)::operator()<std::shared_ptr<tb::fs::File>>' requested here
509+
659 | using Fn_Result = decltype(f(std::declval<Value&&>()));
510+
| ^
511+
TrenchBroom/TrenchBroom/lib/KdLib/include/kd/result.h:2949:29: note: in instantiation of function template specialization 'kdl::result<std::shared_ptr<tb::fs::File>, kdl::result_error>::and_then<(lambda at TrenchBroom/TrenchBroom/common/test/src/mdl/tst_ReadFreeImageTexture.cpp:44:48)>' requested here
512+
2949 | return std::forward<R>(r).and_then(t.and_then);
513+
| ^
514+
TrenchBroom/TrenchBroom/common/test/src/mdl/tst_ReadFreeImageTexture.cpp:44:32: note: in instantiation of function template specialization 'kdl::detail::operator|<kdl::result<std::shared_ptr<tb::fs::File>, kdl::result_error>, (lambda at TrenchBroom/TrenchBroom/common/test/src/mdl/tst_ReadFreeImageTexture.cpp:44:48)>' requested here
515+
44 | return diskFS.openFile(name) | kdl::and_then([](const auto& file) {
516+
| ^
517+
TrenchBroom/TrenchBroom/lib/KdLib/include/kd/result.h:661:19: error: static assertion failed due to requirement 'is_result_v<int>': Function must return a result type [clang-diagnostic-error]
518+
661 | static_assert(is_result_v<Fn_Result>, "Function must return a result type");
519+
| ^~~~~~~~~~~~~~~~~~~~~~
520+
TrenchBroom/TrenchBroom/lib/KdLib/include/kd/result.h:2949:29: note: in instantiation of function template specialization 'kdl::result<std::shared_ptr<tb::fs::File>, kdl::result_error>::and_then<(lambda at TrenchBroom/TrenchBroom/common/test/src/mdl/tst_ReadFreeImageTexture.cpp:44:48)>' requested here
521+
2949 | return std::forward<R>(r).and_then(t.and_then);
522+
| ^
523+
TrenchBroom/TrenchBroom/common/test/src/mdl/tst_ReadFreeImageTexture.cpp:44:32: note: in instantiation of function template specialization 'kdl::detail::operator|<kdl::result<std::shared_ptr<tb::fs::File>, kdl::result_error>, (lambda at TrenchBroom/TrenchBroom/common/test/src/mdl/tst_ReadFreeImageTexture.cpp:44:48)>' requested here
524+
44 | return diskFS.openFile(name) | kdl::and_then([](const auto& file) {
525+
| ^
526+
TrenchBroom/TrenchBroom/lib/KdLib/include/kd/result.h:663:77: error: no type named 'type' in 'kdl::detail::chain_results<kdl::result<std::shared_ptr<tb::fs::File>, kdl::result_error>, int>' [clang-diagnostic-error]
527+
663 | using Cm_Result = typename detail::chain_results<My_Result, Fn_Result>::type;
528+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~
529+
TrenchBroom/TrenchBroom/lib/KdLib/include/kd/result.h:667:48: error: no matching function for call to object of type 'const (lambda at TrenchBroom/TrenchBroom/common/test/src/mdl/tst_ReadFreeImageTexture.cpp:44:48)' [clang-diagnostic-error]
530+
667 | [&](value_type&& v) { return Cm_Result{f(std::move(v))}; },
531+
| ^
532+
TrenchBroom/TrenchBroom/lib/KdLib/include/kd/result.h:667:29: note: while substituting into a lambda expression here
533+
667 | [&](value_type&& v) { return Cm_Result{f(std::move(v))}; },
534+
| ^
535+
TrenchBroom/TrenchBroom/lib/KdLib/include/kd/result.h:2949:29: note: in instantiation of function template specialization 'kdl::result<std::shared_ptr<tb::fs::File>, kdl::result_error>::and_then<(lambda at TrenchBroom/TrenchBroom/common/test/src/mdl/tst_ReadFreeImageTexture.cpp:44:48)>' requested here
536+
2949 | return std::forward<R>(r).and_then(t.and_then);
537+
| ^
538+
TrenchBroom/TrenchBroom/common/test/src/mdl/tst_ReadFreeImageTexture.cpp:44:32: note: in instantiation of function template specialization 'kdl::detail::operator|<kdl::result<std::shared_ptr<tb::fs::File>, kdl::result_error>, (lambda at TrenchBroom/TrenchBroom/common/test/src/mdl/tst_ReadFreeImageTexture.cpp:44:48)>' requested here
539+
44 | return diskFS.openFile(name) | kdl::and_then([](const auto& file) {
540+
| ^
541+
TrenchBroom/TrenchBroom/common/test/src/mdl/tst_ReadFreeImageTexture.cpp:44:48: note: candidate template ignored: substitution failure [with file:auto = typename std::remove_reference<shared_ptr<File> &>::type]
542+
44 | return diskFS.openFile(name) | kdl::and_then([](const auto& file) {
543+
| ^
544+
"#;
545+
let advice = parse_tidy_output(tidy_out.as_bytes(), &None).unwrap();
546+
assert_eq!(advice.notes.len(), 4);
547+
for note in advice.notes {
548+
assert!(note.diagnostic.contains('-'));
549+
assert!(!note.diagnostic.contains(' '));
550+
}
551+
}
491552
}

cspell.config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ words:
1919
- cstdio
2020
- cstdlib
2121
- DCMAKE
22+
- declval
2223
- devel
2324
- Doherty
2425
- dtolnay

0 commit comments

Comments
 (0)