Skip to content

Commit b8dfc33

Browse files
bors[bot]popzxc
andauthored
Merge #5682
5682: Add an option to disable diagnostics r=matklad a=popzxc As far as I know, currently it's not possible to disable a selected type of diagnostics provided by `rust-analyzer`. This causes an inconvenient situation with a false-positive warnings: you either have to disable all the diagnostics, or you have to ignore these warnings. There are some open issues related to this problem, e.g.: #5412, #5502 This PR attempts to make it possible to selectively disable some diagnostics on per-project basis. Co-authored-by: Igor Aleksanov <[email protected]>
2 parents 2252a65 + 34847c8 commit b8dfc33

File tree

10 files changed

+155
-15
lines changed

10 files changed

+155
-15
lines changed

crates/hir_def/src/diagnostics.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ pub struct UnresolvedModule {
1515
}
1616

1717
impl Diagnostic for UnresolvedModule {
18+
fn name(&self) -> &'static str {
19+
"unresolved-module"
20+
}
1821
fn message(&self) -> String {
1922
"unresolved module".to_string()
2023
}

crates/hir_expand/src/diagnostics.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use syntax::SyntaxNodePtr;
2121
use crate::InFile;
2222

2323
pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static {
24+
fn name(&self) -> &'static str;
2425
fn message(&self) -> String;
2526
/// Used in highlighting and related purposes
2627
fn display_source(&self) -> InFile<SyntaxNodePtr>;

crates/hir_ty/src/diagnostics.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ pub struct NoSuchField {
3232
}
3333

3434
impl Diagnostic for NoSuchField {
35+
fn name(&self) -> &'static str {
36+
"no-such-field"
37+
}
38+
3539
fn message(&self) -> String {
3640
"no such field".to_string()
3741
}
@@ -54,6 +58,9 @@ pub struct MissingFields {
5458
}
5559

5660
impl Diagnostic for MissingFields {
61+
fn name(&self) -> &'static str {
62+
"missing-structure-fields"
63+
}
5764
fn message(&self) -> String {
5865
let mut buf = String::from("Missing structure fields:\n");
5966
for field in &self.missed_fields {
@@ -87,6 +94,9 @@ pub struct MissingPatFields {
8794
}
8895

8996
impl Diagnostic for MissingPatFields {
97+
fn name(&self) -> &'static str {
98+
"missing-pat-fields"
99+
}
90100
fn message(&self) -> String {
91101
let mut buf = String::from("Missing structure fields:\n");
92102
for field in &self.missed_fields {
@@ -117,6 +127,9 @@ pub struct MissingMatchArms {
117127
}
118128

119129
impl Diagnostic for MissingMatchArms {
130+
fn name(&self) -> &'static str {
131+
"missing-match-arm"
132+
}
120133
fn message(&self) -> String {
121134
String::from("Missing match arm")
122135
}
@@ -135,6 +148,9 @@ pub struct MissingOkInTailExpr {
135148
}
136149

137150
impl Diagnostic for MissingOkInTailExpr {
151+
fn name(&self) -> &'static str {
152+
"missing-ok-in-tail-expr"
153+
}
138154
fn message(&self) -> String {
139155
"wrap return expression in Ok".to_string()
140156
}
@@ -153,6 +169,9 @@ pub struct BreakOutsideOfLoop {
153169
}
154170

155171
impl Diagnostic for BreakOutsideOfLoop {
172+
fn name(&self) -> &'static str {
173+
"break-outside-of-loop"
174+
}
156175
fn message(&self) -> String {
157176
"break outside of loop".to_string()
158177
}
@@ -171,6 +190,9 @@ pub struct MissingUnsafe {
171190
}
172191

173192
impl Diagnostic for MissingUnsafe {
193+
fn name(&self) -> &'static str {
194+
"missing-unsafe"
195+
}
174196
fn message(&self) -> String {
175197
format!("This operation is unsafe and requires an unsafe function or block")
176198
}
@@ -191,6 +213,9 @@ pub struct MismatchedArgCount {
191213
}
192214

193215
impl Diagnostic for MismatchedArgCount {
216+
fn name(&self) -> &'static str {
217+
"mismatched-arg-count"
218+
}
194219
fn message(&self) -> String {
195220
let s = if self.expected == 1 { "" } else { "s" };
196221
format!("Expected {} argument{}, found {}", self.expected, s, self.found)

crates/ide/src/diagnostics.rs

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//! macro-expanded files, but we need to present them to the users in terms of
55
//! original files. So we need to map the ranges.
66
7-
use std::cell::RefCell;
7+
use std::{cell::RefCell, collections::HashSet};
88

99
use base_db::SourceDatabase;
1010
use hir::{diagnostics::DiagnosticSinkBuilder, Semantics};
@@ -31,6 +31,7 @@ pub(crate) fn diagnostics(
3131
db: &RootDatabase,
3232
file_id: FileId,
3333
enable_experimental: bool,
34+
disabled_diagnostics: Option<HashSet<String>>,
3435
) -> Vec<Diagnostic> {
3536
let _p = profile::span("diagnostics");
3637
let sema = Semantics::new(db);
@@ -39,6 +40,7 @@ pub(crate) fn diagnostics(
3940

4041
// [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
4142
res.extend(parse.errors().iter().take(128).map(|err| Diagnostic {
43+
name: None,
4244
range: err.range(),
4345
message: format!("Syntax Error: {}", err),
4446
severity: Severity::Error,
@@ -50,7 +52,7 @@ pub(crate) fn diagnostics(
5052
check_struct_shorthand_initialization(&mut res, file_id, &node);
5153
}
5254
let res = RefCell::new(res);
53-
let mut sink = DiagnosticSinkBuilder::new()
55+
let mut sink_builder = DiagnosticSinkBuilder::new()
5456
.on::<hir::diagnostics::UnresolvedModule, _>(|d| {
5557
res.borrow_mut().push(diagnostic_with_fix(d, &sema));
5658
})
@@ -64,10 +66,19 @@ pub(crate) fn diagnostics(
6466
res.borrow_mut().push(diagnostic_with_fix(d, &sema));
6567
})
6668
// Only collect experimental diagnostics when they're enabled.
67-
.filter(|diag| !diag.is_experimental() || enable_experimental)
69+
.filter(|diag| !diag.is_experimental() || enable_experimental);
70+
71+
if let Some(disabled_diagnostics) = disabled_diagnostics {
72+
// Do not collect disabled diagnostics.
73+
sink_builder = sink_builder.filter(move |diag| !disabled_diagnostics.contains(diag.name()));
74+
}
75+
76+
// Finalize the `DiagnosticSink` building process.
77+
let mut sink = sink_builder
6878
// Diagnostics not handled above get no fix and default treatment.
6979
.build(|d| {
7080
res.borrow_mut().push(Diagnostic {
81+
name: Some(d.name().into()),
7182
message: d.message(),
7283
range: sema.diagnostics_display_range(d).range,
7384
severity: Severity::Error,
@@ -84,6 +95,7 @@ pub(crate) fn diagnostics(
8495

8596
fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
8697
Diagnostic {
98+
name: Some(d.name().into()),
8799
range: sema.diagnostics_display_range(d).range,
88100
message: d.message(),
89101
severity: Severity::Error,
@@ -110,6 +122,7 @@ fn check_unnecessary_braces_in_use_statement(
110122
});
111123

112124
acc.push(Diagnostic {
125+
name: None,
113126
range: use_range,
114127
message: "Unnecessary braces in use statement".to_string(),
115128
severity: Severity::WeakWarning,
@@ -156,6 +169,7 @@ fn check_struct_shorthand_initialization(
156169

157170
let field_range = record_field.syntax().text_range();
158171
acc.push(Diagnostic {
172+
name: None,
159173
range: field_range,
160174
message: "Shorthand struct initialization".to_string(),
161175
severity: Severity::WeakWarning,
@@ -173,6 +187,7 @@ fn check_struct_shorthand_initialization(
173187

174188
#[cfg(test)]
175189
mod tests {
190+
use std::collections::HashSet;
176191
use stdx::trim_indent;
177192
use test_utils::assert_eq_text;
178193

@@ -188,7 +203,8 @@ mod tests {
188203
let after = trim_indent(ra_fixture_after);
189204

190205
let (analysis, file_position) = analysis_and_position(ra_fixture_before);
191-
let diagnostic = analysis.diagnostics(file_position.file_id, true).unwrap().pop().unwrap();
206+
let diagnostic =
207+
analysis.diagnostics(file_position.file_id, true, None).unwrap().pop().unwrap();
192208
let mut fix = diagnostic.fix.unwrap();
193209
let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
194210
let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
@@ -214,7 +230,7 @@ mod tests {
214230
let ra_fixture_after = &trim_indent(ra_fixture_after);
215231
let (analysis, file_pos) = analysis_and_position(ra_fixture_before);
216232
let current_file_id = file_pos.file_id;
217-
let diagnostic = analysis.diagnostics(current_file_id, true).unwrap().pop().unwrap();
233+
let diagnostic = analysis.diagnostics(current_file_id, true, None).unwrap().pop().unwrap();
218234
let mut fix = diagnostic.fix.unwrap();
219235
let edit = fix.source_change.source_file_edits.pop().unwrap();
220236
let changed_file_id = edit.file_id;
@@ -235,14 +251,58 @@ mod tests {
235251
let analysis = mock.analysis();
236252
let diagnostics = files
237253
.into_iter()
238-
.flat_map(|file_id| analysis.diagnostics(file_id, true).unwrap())
254+
.flat_map(|file_id| analysis.diagnostics(file_id, true, None).unwrap())
239255
.collect::<Vec<_>>();
240256
assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
241257
}
242258

259+
/// Takes a multi-file input fixture with annotated cursor position and the list of disabled diagnostics,
260+
/// and checks that provided diagnostics aren't spawned during analysis.
261+
fn check_disabled_diagnostics(ra_fixture: &str, disabled_diagnostics: &[&'static str]) {
262+
let disabled_diagnostics: HashSet<_> =
263+
disabled_diagnostics.into_iter().map(|diag| diag.to_string()).collect();
264+
265+
let mock = MockAnalysis::with_files(ra_fixture);
266+
let files = mock.files().map(|(it, _)| it).collect::<Vec<_>>();
267+
let analysis = mock.analysis();
268+
269+
let diagnostics = files
270+
.clone()
271+
.into_iter()
272+
.flat_map(|file_id| {
273+
analysis.diagnostics(file_id, true, Some(disabled_diagnostics.clone())).unwrap()
274+
})
275+
.collect::<Vec<_>>();
276+
277+
// First, we have to check that diagnostic is not emitted when it's added to the disabled diagnostics list.
278+
for diagnostic in diagnostics {
279+
if let Some(name) = diagnostic.name {
280+
assert!(!disabled_diagnostics.contains(&name), "Diagnostic {} is disabled", name);
281+
}
282+
}
283+
284+
// Then, we must reset the config and repeat the check, so that we'll be sure that without
285+
// config these diagnostics are emitted.
286+
// This is required for tests to not become outdated if e.g. diagnostics name changes:
287+
// without this additional run the test will pass simply because a diagnostic with an old name
288+
// will no longer exist.
289+
let diagnostics = files
290+
.into_iter()
291+
.flat_map(|file_id| analysis.diagnostics(file_id, true, None).unwrap())
292+
.collect::<Vec<_>>();
293+
294+
assert!(
295+
diagnostics
296+
.into_iter()
297+
.filter_map(|diag| diag.name)
298+
.any(|name| disabled_diagnostics.contains(&name)),
299+
"At least one of the diagnostics was not emitted even without config; are the diagnostics names correct?"
300+
);
301+
}
302+
243303
fn check_expect(ra_fixture: &str, expect: Expect) {
244304
let (analysis, file_id) = single_file(ra_fixture);
245-
let diagnostics = analysis.diagnostics(file_id, true).unwrap();
305+
let diagnostics = analysis.diagnostics(file_id, true, None).unwrap();
246306
expect.assert_debug_eq(&diagnostics)
247307
}
248308

@@ -502,6 +562,9 @@ fn test_fn() {
502562
expect![[r#"
503563
[
504564
Diagnostic {
565+
name: Some(
566+
"unresolved-module",
567+
),
505568
message: "unresolved module",
506569
range: 0..8,
507570
severity: Error,
@@ -675,4 +738,9 @@ struct Foo {
675738
",
676739
)
677740
}
741+
742+
#[test]
743+
fn test_disabled_diagnostics() {
744+
check_disabled_diagnostics(r#"mod foo;"#, &["unresolved-module"]);
745+
}
678746
}

crates/ide/src/lib.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ mod syntax_highlighting;
4444
mod syntax_tree;
4545
mod typing;
4646

47-
use std::sync::Arc;
47+
use std::{collections::HashSet, sync::Arc};
4848

4949
use base_db::{
5050
salsa::{self, ParallelDatabase},
@@ -101,6 +101,7 @@ pub type Cancelable<T> = Result<T, Canceled>;
101101

102102
#[derive(Debug)]
103103
pub struct Diagnostic {
104+
pub name: Option<String>,
104105
pub message: String,
105106
pub range: TextRange,
106107
pub severity: Severity,
@@ -147,7 +148,7 @@ pub struct AnalysisHost {
147148
}
148149

149150
impl AnalysisHost {
150-
pub fn new(lru_capacity: Option<usize>) -> AnalysisHost {
151+
pub fn new(lru_capacity: Option<usize>) -> Self {
151152
AnalysisHost { db: RootDatabase::new(lru_capacity) }
152153
}
153154

@@ -496,8 +497,11 @@ impl Analysis {
496497
&self,
497498
file_id: FileId,
498499
enable_experimental: bool,
500+
disabled_diagnostics: Option<HashSet<String>>,
499501
) -> Cancelable<Vec<Diagnostic>> {
500-
self.with_db(|db| diagnostics::diagnostics(db, file_id, enable_experimental))
502+
self.with_db(|db| {
503+
diagnostics::diagnostics(db, file_id, enable_experimental, disabled_diagnostics)
504+
})
501505
}
502506

503507
/// Returns the edit required to rename reference at the position to the new

crates/rust-analyzer/src/cli/analysis_bench.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ impl BenchCmd {
7171
match &self.what {
7272
BenchWhat::Highlight { .. } => {
7373
let res = do_work(&mut host, file_id, |analysis| {
74-
analysis.diagnostics(file_id, true).unwrap();
74+
analysis.diagnostics(file_id, true, None).unwrap();
7575
analysis.highlight_as_html(file_id, false).unwrap()
7676
});
7777
if verbosity.is_verbose() {

crates/rust-analyzer/src/cli/diagnostics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ pub fn diagnostics(
4747
String::from("unknown")
4848
};
4949
println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id));
50-
for diagnostic in analysis.diagnostics(file_id, true).unwrap() {
50+
for diagnostic in analysis.diagnostics(file_id, true, None).unwrap() {
5151
if matches!(diagnostic.severity, Severity::Error) {
5252
found_error = true;
5353
}

0 commit comments

Comments
 (0)