aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml47
-rw-r--r--crates/assists/src/assist_context.rs16
-rw-r--r--crates/assists/src/lib.rs13
-rw-r--r--crates/hir_def/src/diagnostics.rs6
-rw-r--r--crates/hir_expand/src/diagnostics.rs11
-rw-r--r--crates/hir_ty/src/diagnostics.rs34
-rw-r--r--crates/hir_ty/src/tests.rs17
-rw-r--r--crates/ide/src/diagnostics.rs146
-rw-r--r--crates/ide/src/diagnostics/fixes.rs (renamed from crates/ide/src/diagnostics/diagnostics_with_fix.rs)10
-rw-r--r--crates/ide/src/lib.rs43
-rw-r--r--crates/ide_db/src/label.rs49
-rw-r--r--crates/ide_db/src/lib.rs1
-rw-r--r--crates/rust-analyzer/src/cli/analysis_bench.rs7
-rw-r--r--crates/rust-analyzer/src/cli/diagnostics.rs5
-rw-r--r--crates/rust-analyzer/src/config.rs39
-rw-r--r--crates/rust-analyzer/src/diagnostics.rs2
-rw-r--r--crates/rust-analyzer/src/diagnostics/to_proto.rs18
-rw-r--r--crates/rust-analyzer/src/handlers.rs48
-rw-r--r--crates/rust-analyzer/src/main_loop.rs2
-rw-r--r--crates/rust-analyzer/src/to_proto.rs2
-rw-r--r--crates/stdx/src/lib.rs30
-rw-r--r--docs/dev/style.md9
-rw-r--r--editors/code/package.json12
23 files changed, 303 insertions, 264 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 612e6809f..218581d9d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,37 +2,34 @@
2members = [ "crates/*", "xtask/" ] 2members = [ "crates/*", "xtask/" ]
3 3
4[profile.dev] 4[profile.dev]
5# disabling debug info speeds up builds a bunch, 5# Disabling debug info speeds up builds a bunch,
6# and we don't rely on it for debugging that much. 6# and we don't rely on it for debugging that much.
7debug = 0 7debug = 0
8 8
9[profile.dev.package]
10# These speed up local tests.
11rowan.opt-level = 3
12rustc-hash.opt-level = 3
13smol_str.opt-level = 3
14text-size.opt-level = 3
15# This speeds up `cargo xtask dist`.
16miniz_oxide.opt-level = 3
17
9[profile.release] 18[profile.release]
10incremental = true 19incremental = true
11debug = 0 # set this to 1 or 2 to get more useful backtraces in debugger 20debug = 0 # Set this to 1 or 2 to get more useful backtraces in debugger.
12
13# ideally, we would use `build-override` here, but some crates are also
14# needed at run-time and we end up compiling them twice
15[profile.release.package.proc-macro2]
16opt-level = 0
17[profile.release.package.quote]
18opt-level = 0
19[profile.release.package.syn]
20opt-level = 0
21[profile.release.package.serde_derive]
22opt-level = 0
23[profile.release.package.chalk-derive]
24opt-level = 0
25[profile.release.package.salsa-macros]
26opt-level = 0
27[profile.release.package.tracing-attributes]
28opt-level = 0
29[profile.release.package.xtask]
30opt-level = 0
31 21
32# Gzipping the artifacts is up to 10 times faster with optimizations (`cargo xtask dist`). 22# Ideally, we would use `build-override` here, but some crates are also
33# `miniz_oxide` is the direct dependency of `flate2` which does all the heavy lifting 23# needed at run-time and we end up compiling them twice.
34[profile.dev.package.miniz_oxide] 24[profile.release.package]
35opt-level = 3 25chalk-derive.opt-level = 0
26proc-macro2.opt-level = 0
27quote.opt-level = 0
28salsa-macros.opt-level = 0
29serde_derive.opt-level = 0
30syn.opt-level = 0
31tracing-attributes.opt-level = 0
32xtask.opt-level = 0
36 33
37[patch.'crates-io'] 34[patch.'crates-io']
38# rowan = { path = "../rowan" } 35# rowan = { path = "../rowan" }
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs
index 79574b9ac..11c171fc2 100644
--- a/crates/assists/src/assist_context.rs
+++ b/crates/assists/src/assist_context.rs
@@ -6,6 +6,7 @@ use algo::find_covering_element;
6use base_db::{FileId, FileRange}; 6use base_db::{FileId, FileRange};
7use hir::Semantics; 7use hir::Semantics;
8use ide_db::{ 8use ide_db::{
9 label::Label,
9 source_change::{SourceChange, SourceFileEdit}, 10 source_change::{SourceChange, SourceFileEdit},
10 RootDatabase, 11 RootDatabase,
11}; 12};
@@ -157,8 +158,9 @@ impl Assists {
157 if !self.is_allowed(&id) { 158 if !self.is_allowed(&id) {
158 return None; 159 return None;
159 } 160 }
160 let label = Assist::new(id, label.into(), None, target); 161 let label = Label::new(label.into());
161 self.add_impl(label, f) 162 let assist = Assist { id, label, group: None, target };
163 self.add_impl(assist, f)
162 } 164 }
163 165
164 pub(crate) fn add_group( 166 pub(crate) fn add_group(
@@ -172,12 +174,12 @@ impl Assists {
172 if !self.is_allowed(&id) { 174 if !self.is_allowed(&id) {
173 return None; 175 return None;
174 } 176 }
175 177 let label = Label::new(label.into());
176 let label = Assist::new(id, label.into(), Some(group.clone()), target); 178 let assist = Assist { id, label, group: Some(group.clone()), target };
177 self.add_impl(label, f) 179 self.add_impl(assist, f)
178 } 180 }
179 181
180 fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> { 182 fn add_impl(&mut self, assist: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
181 let source_change = if self.resolve { 183 let source_change = if self.resolve {
182 let mut builder = AssistBuilder::new(self.file); 184 let mut builder = AssistBuilder::new(self.file);
183 f(&mut builder); 185 f(&mut builder);
@@ -186,7 +188,7 @@ impl Assists {
186 None 188 None
187 }; 189 };
188 190
189 self.buf.push((label, source_change)); 191 self.buf.push((assist, source_change));
190 Some(()) 192 Some(())
191 } 193 }
192 194
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index c589b08dc..14834480a 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -19,7 +19,7 @@ pub mod ast_transform;
19 19
20use base_db::FileRange; 20use base_db::FileRange;
21use hir::Semantics; 21use hir::Semantics;
22use ide_db::{source_change::SourceChange, RootDatabase}; 22use ide_db::{label::Label, source_change::SourceChange, RootDatabase};
23use syntax::TextRange; 23use syntax::TextRange;
24 24
25pub(crate) use crate::assist_context::{AssistContext, Assists}; 25pub(crate) use crate::assist_context::{AssistContext, Assists};
@@ -68,7 +68,7 @@ pub struct GroupLabel(pub String);
68pub struct Assist { 68pub struct Assist {
69 pub id: AssistId, 69 pub id: AssistId,
70 /// Short description of the assist, as shown in the UI. 70 /// Short description of the assist, as shown in the UI.
71 label: String, 71 pub label: Label,
72 pub group: Option<GroupLabel>, 72 pub group: Option<GroupLabel>,
73 /// Target ranges are used to sort assists: the smaller the target range, 73 /// Target ranges are used to sort assists: the smaller the target range,
74 /// the more specific assist is, and so it should be sorted first. 74 /// the more specific assist is, and so it should be sorted first.
@@ -82,11 +82,6 @@ pub struct ResolvedAssist {
82} 82}
83 83
84impl Assist { 84impl Assist {
85 fn new(id: AssistId, label: String, group: Option<GroupLabel>, target: TextRange) -> Assist {
86 assert!(label.starts_with(char::is_uppercase));
87 Assist { id, label, group, target }
88 }
89
90 /// Return all the assists applicable at the given position. 85 /// Return all the assists applicable at the given position.
91 /// 86 ///
92 /// Assists are returned in the "unresolved" state, that is only labels are 87 /// Assists are returned in the "unresolved" state, that is only labels are
@@ -118,10 +113,6 @@ impl Assist {
118 }); 113 });
119 acc.finish_resolved() 114 acc.finish_resolved()
120 } 115 }
121
122 pub fn label(&self) -> &str {
123 self.label.as_str()
124 }
125} 116}
126 117
127mod handlers { 118mod handlers {
diff --git a/crates/hir_def/src/diagnostics.rs b/crates/hir_def/src/diagnostics.rs
index c7723de00..3e19d9117 100644
--- a/crates/hir_def/src/diagnostics.rs
+++ b/crates/hir_def/src/diagnostics.rs
@@ -2,7 +2,7 @@
2 2
3use std::any::Any; 3use std::any::Any;
4 4
5use hir_expand::diagnostics::Diagnostic; 5use hir_expand::diagnostics::{Diagnostic, DiagnosticCode};
6use syntax::{ast, AstPtr, SyntaxNodePtr}; 6use syntax::{ast, AstPtr, SyntaxNodePtr};
7 7
8use hir_expand::{HirFileId, InFile}; 8use hir_expand::{HirFileId, InFile};
@@ -15,8 +15,8 @@ pub struct UnresolvedModule {
15} 15}
16 16
17impl Diagnostic for UnresolvedModule { 17impl Diagnostic for UnresolvedModule {
18 fn name(&self) -> &'static str { 18 fn code(&self) -> DiagnosticCode {
19 "unresolved-module" 19 DiagnosticCode("unresolved-module")
20 } 20 }
21 fn message(&self) -> String { 21 fn message(&self) -> String {
22 "unresolved module".to_string() 22 "unresolved module".to_string()
diff --git a/crates/hir_expand/src/diagnostics.rs b/crates/hir_expand/src/diagnostics.rs
index 6c81b2501..78ccc212c 100644
--- a/crates/hir_expand/src/diagnostics.rs
+++ b/crates/hir_expand/src/diagnostics.rs
@@ -20,8 +20,17 @@ use syntax::SyntaxNodePtr;
20 20
21use crate::InFile; 21use crate::InFile;
22 22
23#[derive(Copy, Clone, PartialEq)]
24pub struct DiagnosticCode(pub &'static str);
25
26impl DiagnosticCode {
27 pub fn as_str(&self) -> &str {
28 self.0
29 }
30}
31
23pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { 32pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static {
24 fn name(&self) -> &'static str; 33 fn code(&self) -> DiagnosticCode;
25 fn message(&self) -> String; 34 fn message(&self) -> String;
26 /// Used in highlighting and related purposes 35 /// Used in highlighting and related purposes
27 fn display_source(&self) -> InFile<SyntaxNodePtr>; 36 fn display_source(&self) -> InFile<SyntaxNodePtr>;
diff --git a/crates/hir_ty/src/diagnostics.rs b/crates/hir_ty/src/diagnostics.rs
index 38fa24ee0..9ba005fab 100644
--- a/crates/hir_ty/src/diagnostics.rs
+++ b/crates/hir_ty/src/diagnostics.rs
@@ -6,7 +6,7 @@ mod unsafe_check;
6use std::any::Any; 6use std::any::Any;
7 7
8use hir_def::DefWithBodyId; 8use hir_def::DefWithBodyId;
9use hir_expand::diagnostics::{Diagnostic, DiagnosticSink}; 9use hir_expand::diagnostics::{Diagnostic, DiagnosticCode, DiagnosticSink};
10use hir_expand::{name::Name, HirFileId, InFile}; 10use hir_expand::{name::Name, HirFileId, InFile};
11use stdx::format_to; 11use stdx::format_to;
12use syntax::{ast, AstPtr, SyntaxNodePtr}; 12use syntax::{ast, AstPtr, SyntaxNodePtr};
@@ -32,8 +32,8 @@ pub struct NoSuchField {
32} 32}
33 33
34impl Diagnostic for NoSuchField { 34impl Diagnostic for NoSuchField {
35 fn name(&self) -> &'static str { 35 fn code(&self) -> DiagnosticCode {
36 "no-such-field" 36 DiagnosticCode("no-such-field")
37 } 37 }
38 38
39 fn message(&self) -> String { 39 fn message(&self) -> String {
@@ -58,8 +58,8 @@ pub struct MissingFields {
58} 58}
59 59
60impl Diagnostic for MissingFields { 60impl Diagnostic for MissingFields {
61 fn name(&self) -> &'static str { 61 fn code(&self) -> DiagnosticCode {
62 "missing-structure-fields" 62 DiagnosticCode("missing-structure-fields")
63 } 63 }
64 fn message(&self) -> String { 64 fn message(&self) -> String {
65 let mut buf = String::from("Missing structure fields:\n"); 65 let mut buf = String::from("Missing structure fields:\n");
@@ -94,8 +94,8 @@ pub struct MissingPatFields {
94} 94}
95 95
96impl Diagnostic for MissingPatFields { 96impl Diagnostic for MissingPatFields {
97 fn name(&self) -> &'static str { 97 fn code(&self) -> DiagnosticCode {
98 "missing-pat-fields" 98 DiagnosticCode("missing-pat-fields")
99 } 99 }
100 fn message(&self) -> String { 100 fn message(&self) -> String {
101 let mut buf = String::from("Missing structure fields:\n"); 101 let mut buf = String::from("Missing structure fields:\n");
@@ -127,8 +127,8 @@ pub struct MissingMatchArms {
127} 127}
128 128
129impl Diagnostic for MissingMatchArms { 129impl Diagnostic for MissingMatchArms {
130 fn name(&self) -> &'static str { 130 fn code(&self) -> DiagnosticCode {
131 "missing-match-arm" 131 DiagnosticCode("missing-match-arm")
132 } 132 }
133 fn message(&self) -> String { 133 fn message(&self) -> String {
134 String::from("Missing match arm") 134 String::from("Missing match arm")
@@ -148,8 +148,8 @@ pub struct MissingOkInTailExpr {
148} 148}
149 149
150impl Diagnostic for MissingOkInTailExpr { 150impl Diagnostic for MissingOkInTailExpr {
151 fn name(&self) -> &'static str { 151 fn code(&self) -> DiagnosticCode {
152 "missing-ok-in-tail-expr" 152 DiagnosticCode("missing-ok-in-tail-expr")
153 } 153 }
154 fn message(&self) -> String { 154 fn message(&self) -> String {
155 "wrap return expression in Ok".to_string() 155 "wrap return expression in Ok".to_string()
@@ -169,8 +169,8 @@ pub struct BreakOutsideOfLoop {
169} 169}
170 170
171impl Diagnostic for BreakOutsideOfLoop { 171impl Diagnostic for BreakOutsideOfLoop {
172 fn name(&self) -> &'static str { 172 fn code(&self) -> DiagnosticCode {
173 "break-outside-of-loop" 173 DiagnosticCode("break-outside-of-loop")
174 } 174 }
175 fn message(&self) -> String { 175 fn message(&self) -> String {
176 "break outside of loop".to_string() 176 "break outside of loop".to_string()
@@ -190,8 +190,8 @@ pub struct MissingUnsafe {
190} 190}
191 191
192impl Diagnostic for MissingUnsafe { 192impl Diagnostic for MissingUnsafe {
193 fn name(&self) -> &'static str { 193 fn code(&self) -> DiagnosticCode {
194 "missing-unsafe" 194 DiagnosticCode("missing-unsafe")
195 } 195 }
196 fn message(&self) -> String { 196 fn message(&self) -> String {
197 format!("This operation is unsafe and requires an unsafe function or block") 197 format!("This operation is unsafe and requires an unsafe function or block")
@@ -213,8 +213,8 @@ pub struct MismatchedArgCount {
213} 213}
214 214
215impl Diagnostic for MismatchedArgCount { 215impl Diagnostic for MismatchedArgCount {
216 fn name(&self) -> &'static str { 216 fn code(&self) -> DiagnosticCode {
217 "mismatched-arg-count" 217 DiagnosticCode("mismatched-arg-count")
218 } 218 }
219 fn message(&self) -> String { 219 fn message(&self) -> String {
220 let s = if self.expected == 1 { "" } else { "s" }; 220 let s = if self.expected == 1 { "" } else { "s" };
diff --git a/crates/hir_ty/src/tests.rs b/crates/hir_ty/src/tests.rs
index c953925ec..91c9d38c5 100644
--- a/crates/hir_ty/src/tests.rs
+++ b/crates/hir_ty/src/tests.rs
@@ -8,7 +8,7 @@ mod method_resolution;
8mod macros; 8mod macros;
9mod display_source_code; 9mod display_source_code;
10 10
11use std::sync::Arc; 11use std::{env, sync::Arc};
12 12
13use base_db::{fixture::WithFixture, FileRange, SourceDatabase, SourceDatabaseExt}; 13use base_db::{fixture::WithFixture, FileRange, SourceDatabase, SourceDatabaseExt};
14use expect::Expect; 14use expect::Expect;
@@ -22,12 +22,14 @@ use hir_def::{
22 AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId, 22 AssocItemId, DefWithBodyId, LocalModuleId, Lookup, ModuleDefId,
23}; 23};
24use hir_expand::{db::AstDatabase, InFile}; 24use hir_expand::{db::AstDatabase, InFile};
25use stdx::format_to; 25use stdx::{format_to, RacyFlag};
26use syntax::{ 26use syntax::{
27 algo, 27 algo,
28 ast::{self, AstNode}, 28 ast::{self, AstNode},
29 SyntaxNode, 29 SyntaxNode,
30}; 30};
31use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry};
32use tracing_tree::HierarchicalLayer;
31 33
32use crate::{ 34use crate::{
33 db::HirDatabase, display::HirDisplay, infer::TypeMismatch, test_db::TestDB, InferenceResult, Ty, 35 db::HirDatabase, display::HirDisplay, infer::TypeMismatch, test_db::TestDB, InferenceResult, Ty,
@@ -37,9 +39,12 @@ use crate::{
37// against snapshots of the expected results using expect. Use 39// against snapshots of the expected results using expect. Use
38// `env UPDATE_EXPECT=1 cargo test -p hir_ty` to update the snapshots. 40// `env UPDATE_EXPECT=1 cargo test -p hir_ty` to update the snapshots.
39 41
40fn setup_tracing() -> tracing::subscriber::DefaultGuard { 42fn setup_tracing() -> Option<tracing::subscriber::DefaultGuard> {
41 use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry}; 43 static ENABLE: RacyFlag = RacyFlag::new();
42 use tracing_tree::HierarchicalLayer; 44 if !ENABLE.get(|| env::var("CHALK_DEBUG").is_ok()) {
45 return None;
46 }
47
43 let filter = EnvFilter::from_env("CHALK_DEBUG"); 48 let filter = EnvFilter::from_env("CHALK_DEBUG");
44 let layer = HierarchicalLayer::default() 49 let layer = HierarchicalLayer::default()
45 .with_indent_lines(true) 50 .with_indent_lines(true)
@@ -47,7 +52,7 @@ fn setup_tracing() -> tracing::subscriber::DefaultGuard {
47 .with_indent_amount(2) 52 .with_indent_amount(2)
48 .with_writer(std::io::stderr); 53 .with_writer(std::io::stderr);
49 let subscriber = Registry::default().with(filter).with(layer); 54 let subscriber = Registry::default().with(filter).with(layer);
50 tracing::subscriber::set_default(subscriber) 55 Some(tracing::subscriber::set_default(subscriber))
51} 56}
52 57
53fn check_types(ra_fixture: &str) { 58fn check_types(ra_fixture: &str) {
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 606a6064b..92b5adaa2 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -4,22 +4,48 @@
4//! macro-expanded files, but we need to present them to the users in terms of 4//! macro-expanded files, but we need to present them to the users in terms of
5//! original files. So we need to map the ranges. 5//! original files. So we need to map the ranges.
6 6
7use std::{cell::RefCell, collections::HashSet}; 7mod fixes;
8
9use std::cell::RefCell;
8 10
9use base_db::SourceDatabase; 11use base_db::SourceDatabase;
10use hir::{diagnostics::DiagnosticSinkBuilder, Semantics}; 12use hir::{diagnostics::DiagnosticSinkBuilder, Semantics};
11use ide_db::RootDatabase; 13use ide_db::RootDatabase;
12use itertools::Itertools; 14use itertools::Itertools;
15use rustc_hash::FxHashSet;
13use syntax::{ 16use syntax::{
14 ast::{self, AstNode}, 17 ast::{self, AstNode},
15 SyntaxNode, TextRange, T, 18 SyntaxNode, TextRange, T,
16}; 19};
17use text_edit::TextEdit; 20use text_edit::TextEdit;
18 21
19use crate::{Diagnostic, FileId, Fix, SourceFileEdit}; 22use crate::{FileId, Label, SourceChange, SourceFileEdit};
23
24use self::fixes::DiagnosticWithFix;
25
26#[derive(Debug)]
27pub struct Diagnostic {
28 // pub name: Option<String>,
29 pub message: String,
30 pub range: TextRange,
31 pub severity: Severity,
32 pub fix: Option<Fix>,
33}
20 34
21mod diagnostics_with_fix; 35#[derive(Debug)]
22use diagnostics_with_fix::DiagnosticWithFix; 36pub struct Fix {
37 pub label: Label,
38 pub source_change: SourceChange,
39 /// Allows to trigger the fix only when the caret is in the range given
40 pub fix_trigger_range: TextRange,
41}
42
43impl Fix {
44 fn new(label: &str, source_change: SourceChange, fix_trigger_range: TextRange) -> Self {
45 let label = Label::new(label);
46 Self { label, source_change, fix_trigger_range }
47 }
48}
23 49
24#[derive(Debug, Copy, Clone)] 50#[derive(Debug, Copy, Clone)]
25pub enum Severity { 51pub enum Severity {
@@ -27,11 +53,16 @@ pub enum Severity {
27 WeakWarning, 53 WeakWarning,
28} 54}
29 55
56#[derive(Default, Debug, Clone)]
57pub struct DiagnosticsConfig {
58 pub disable_experimental: bool,
59 pub disabled: FxHashSet<String>,
60}
61
30pub(crate) fn diagnostics( 62pub(crate) fn diagnostics(
31 db: &RootDatabase, 63 db: &RootDatabase,
64 config: &DiagnosticsConfig,
32 file_id: FileId, 65 file_id: FileId,
33 enable_experimental: bool,
34 disabled_diagnostics: Option<HashSet<String>>,
35) -> Vec<Diagnostic> { 66) -> Vec<Diagnostic> {
36 let _p = profile::span("diagnostics"); 67 let _p = profile::span("diagnostics");
37 let sema = Semantics::new(db); 68 let sema = Semantics::new(db);
@@ -40,7 +71,7 @@ pub(crate) fn diagnostics(
40 71
41 // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. 72 // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
42 res.extend(parse.errors().iter().take(128).map(|err| Diagnostic { 73 res.extend(parse.errors().iter().take(128).map(|err| Diagnostic {
43 name: None, 74 // name: None,
44 range: err.range(), 75 range: err.range(),
45 message: format!("Syntax Error: {}", err), 76 message: format!("Syntax Error: {}", err),
46 severity: Severity::Error, 77 severity: Severity::Error,
@@ -52,7 +83,7 @@ pub(crate) fn diagnostics(
52 check_struct_shorthand_initialization(&mut res, file_id, &node); 83 check_struct_shorthand_initialization(&mut res, file_id, &node);
53 } 84 }
54 let res = RefCell::new(res); 85 let res = RefCell::new(res);
55 let mut sink_builder = DiagnosticSinkBuilder::new() 86 let sink_builder = DiagnosticSinkBuilder::new()
56 .on::<hir::diagnostics::UnresolvedModule, _>(|d| { 87 .on::<hir::diagnostics::UnresolvedModule, _>(|d| {
57 res.borrow_mut().push(diagnostic_with_fix(d, &sema)); 88 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
58 }) 89 })
@@ -66,19 +97,15 @@ pub(crate) fn diagnostics(
66 res.borrow_mut().push(diagnostic_with_fix(d, &sema)); 97 res.borrow_mut().push(diagnostic_with_fix(d, &sema));
67 }) 98 })
68 // Only collect experimental diagnostics when they're enabled. 99 // Only collect experimental diagnostics when they're enabled.
69 .filter(|diag| !diag.is_experimental() || enable_experimental); 100 .filter(|diag| !(diag.is_experimental() && config.disable_experimental))
70 101 .filter(|diag| !config.disabled.contains(diag.code().as_str()));
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 102
76 // Finalize the `DiagnosticSink` building process. 103 // Finalize the `DiagnosticSink` building process.
77 let mut sink = sink_builder 104 let mut sink = sink_builder
78 // Diagnostics not handled above get no fix and default treatment. 105 // Diagnostics not handled above get no fix and default treatment.
79 .build(|d| { 106 .build(|d| {
80 res.borrow_mut().push(Diagnostic { 107 res.borrow_mut().push(Diagnostic {
81 name: Some(d.name().into()), 108 // name: Some(d.name().into()),
82 message: d.message(), 109 message: d.message(),
83 range: sema.diagnostics_display_range(d).range, 110 range: sema.diagnostics_display_range(d).range,
84 severity: Severity::Error, 111 severity: Severity::Error,
@@ -95,7 +122,7 @@ pub(crate) fn diagnostics(
95 122
96fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 123fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic {
97 Diagnostic { 124 Diagnostic {
98 name: Some(d.name().into()), 125 // name: Some(d.name().into()),
99 range: sema.diagnostics_display_range(d).range, 126 range: sema.diagnostics_display_range(d).range,
100 message: d.message(), 127 message: d.message(),
101 severity: Severity::Error, 128 severity: Severity::Error,
@@ -122,7 +149,7 @@ fn check_unnecessary_braces_in_use_statement(
122 }); 149 });
123 150
124 acc.push(Diagnostic { 151 acc.push(Diagnostic {
125 name: None, 152 // name: None,
126 range: use_range, 153 range: use_range,
127 message: "Unnecessary braces in use statement".to_string(), 154 message: "Unnecessary braces in use statement".to_string(),
128 severity: Severity::WeakWarning, 155 severity: Severity::WeakWarning,
@@ -169,7 +196,7 @@ fn check_struct_shorthand_initialization(
169 196
170 let field_range = record_field.syntax().text_range(); 197 let field_range = record_field.syntax().text_range();
171 acc.push(Diagnostic { 198 acc.push(Diagnostic {
172 name: None, 199 // name: None,
173 range: field_range, 200 range: field_range,
174 message: "Shorthand struct initialization".to_string(), 201 message: "Shorthand struct initialization".to_string(),
175 severity: Severity::WeakWarning, 202 severity: Severity::WeakWarning,
@@ -187,12 +214,14 @@ fn check_struct_shorthand_initialization(
187 214
188#[cfg(test)] 215#[cfg(test)]
189mod tests { 216mod tests {
190 use std::collections::HashSet; 217 use expect::{expect, Expect};
191 use stdx::trim_indent; 218 use stdx::trim_indent;
192 use test_utils::assert_eq_text; 219 use test_utils::assert_eq_text;
193 220
194 use crate::mock_analysis::{analysis_and_position, single_file, MockAnalysis}; 221 use crate::{
195 use expect::{expect, Expect}; 222 mock_analysis::{analysis_and_position, single_file, MockAnalysis},
223 DiagnosticsConfig,
224 };
196 225
197 /// Takes a multi-file input fixture with annotated cursor positions, 226 /// Takes a multi-file input fixture with annotated cursor positions,
198 /// and checks that: 227 /// and checks that:
@@ -203,8 +232,11 @@ mod tests {
203 let after = trim_indent(ra_fixture_after); 232 let after = trim_indent(ra_fixture_after);
204 233
205 let (analysis, file_position) = analysis_and_position(ra_fixture_before); 234 let (analysis, file_position) = analysis_and_position(ra_fixture_before);
206 let diagnostic = 235 let diagnostic = analysis
207 analysis.diagnostics(file_position.file_id, true, None).unwrap().pop().unwrap(); 236 .diagnostics(&DiagnosticsConfig::default(), file_position.file_id)
237 .unwrap()
238 .pop()
239 .unwrap();
208 let mut fix = diagnostic.fix.unwrap(); 240 let mut fix = diagnostic.fix.unwrap();
209 let edit = fix.source_change.source_file_edits.pop().unwrap().edit; 241 let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
210 let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); 242 let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
@@ -230,7 +262,11 @@ mod tests {
230 let ra_fixture_after = &trim_indent(ra_fixture_after); 262 let ra_fixture_after = &trim_indent(ra_fixture_after);
231 let (analysis, file_pos) = analysis_and_position(ra_fixture_before); 263 let (analysis, file_pos) = analysis_and_position(ra_fixture_before);
232 let current_file_id = file_pos.file_id; 264 let current_file_id = file_pos.file_id;
233 let diagnostic = analysis.diagnostics(current_file_id, true, None).unwrap().pop().unwrap(); 265 let diagnostic = analysis
266 .diagnostics(&DiagnosticsConfig::default(), current_file_id)
267 .unwrap()
268 .pop()
269 .unwrap();
234 let mut fix = diagnostic.fix.unwrap(); 270 let mut fix = diagnostic.fix.unwrap();
235 let edit = fix.source_change.source_file_edits.pop().unwrap(); 271 let edit = fix.source_change.source_file_edits.pop().unwrap();
236 let changed_file_id = edit.file_id; 272 let changed_file_id = edit.file_id;
@@ -251,58 +287,16 @@ mod tests {
251 let analysis = mock.analysis(); 287 let analysis = mock.analysis();
252 let diagnostics = files 288 let diagnostics = files
253 .into_iter() 289 .into_iter()
254 .flat_map(|file_id| analysis.diagnostics(file_id, true, None).unwrap())
255 .collect::<Vec<_>>();
256 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
257 }
258
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| { 290 .flat_map(|file_id| {
273 analysis.diagnostics(file_id, true, Some(disabled_diagnostics.clone())).unwrap() 291 analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap()
274 }) 292 })
275 .collect::<Vec<_>>(); 293 .collect::<Vec<_>>();
276 294 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
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 } 295 }
302 296
303 fn check_expect(ra_fixture: &str, expect: Expect) { 297 fn check_expect(ra_fixture: &str, expect: Expect) {
304 let (analysis, file_id) = single_file(ra_fixture); 298 let (analysis, file_id) = single_file(ra_fixture);
305 let diagnostics = analysis.diagnostics(file_id, true, None).unwrap(); 299 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
306 expect.assert_debug_eq(&diagnostics) 300 expect.assert_debug_eq(&diagnostics)
307 } 301 }
308 302
@@ -562,9 +556,6 @@ fn test_fn() {
562 expect![[r#" 556 expect![[r#"
563 [ 557 [
564 Diagnostic { 558 Diagnostic {
565 name: Some(
566 "unresolved-module",
567 ),
568 message: "unresolved module", 559 message: "unresolved module",
569 range: 0..8, 560 range: 0..8,
570 severity: Error, 561 severity: Error,
@@ -741,6 +732,15 @@ struct Foo {
741 732
742 #[test] 733 #[test]
743 fn test_disabled_diagnostics() { 734 fn test_disabled_diagnostics() {
744 check_disabled_diagnostics(r#"mod foo;"#, &["unresolved-module"]); 735 let mut config = DiagnosticsConfig::default();
736 config.disabled.insert("unresolved-module".into());
737
738 let (analysis, file_id) = single_file(r#"mod foo;"#);
739
740 let diagnostics = analysis.diagnostics(&config, file_id).unwrap();
741 assert!(diagnostics.is_empty());
742
743 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
744 assert!(!diagnostics.is_empty());
745 } 745 }
746} 746}
diff --git a/crates/ide/src/diagnostics/diagnostics_with_fix.rs b/crates/ide/src/diagnostics/fixes.rs
index 85b46c995..68ae1c239 100644
--- a/crates/ide/src/diagnostics/diagnostics_with_fix.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -1,7 +1,5 @@
1//! Provides a way to attach fixes to the diagnostics. 1//! Provides a way to attach fixes to the diagnostics.
2//! The same module also has all curret custom fixes for the diagnostics implemented. 2//! The same module also has all curret custom fixes for the diagnostics implemented.
3use crate::Fix;
4use ast::{edit::IndentLevel, make};
5use base_db::FileId; 3use base_db::FileId;
6use hir::{ 4use hir::{
7 db::AstDatabase, 5 db::AstDatabase,
@@ -12,9 +10,15 @@ use ide_db::{
12 source_change::{FileSystemEdit, SourceFileEdit}, 10 source_change::{FileSystemEdit, SourceFileEdit},
13 RootDatabase, 11 RootDatabase,
14}; 12};
15use syntax::{algo, ast, AstNode}; 13use syntax::{
14 algo,
15 ast::{self, edit::IndentLevel, make},
16 AstNode,
17};
16use text_edit::TextEdit; 18use text_edit::TextEdit;
17 19
20use crate::diagnostics::Fix;
21
18/// A [Diagnostic] that potentially has a fix available. 22/// A [Diagnostic] that potentially has a fix available.
19/// 23///
20/// [Diagnostic]: hir::diagnostics::Diagnostic 24/// [Diagnostic]: hir::diagnostics::Diagnostic
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 4b797f374..e3af6d5bc 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -44,7 +44,7 @@ mod syntax_highlighting;
44mod syntax_tree; 44mod syntax_tree;
45mod typing; 45mod typing;
46 46
47use std::{collections::HashSet, sync::Arc}; 47use std::sync::Arc;
48 48
49use base_db::{ 49use base_db::{
50 salsa::{self, ParallelDatabase}, 50 salsa::{self, ParallelDatabase},
@@ -65,7 +65,7 @@ pub use crate::{
65 completion::{ 65 completion::{
66 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat, 66 CompletionConfig, CompletionItem, CompletionItemKind, CompletionScore, InsertTextFormat,
67 }, 67 },
68 diagnostics::Severity, 68 diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity},
69 display::NavigationTarget, 69 display::NavigationTarget,
70 expand_macro::ExpandedMacro, 70 expand_macro::ExpandedMacro,
71 file_structure::StructureNode, 71 file_structure::StructureNode,
@@ -88,6 +88,7 @@ pub use base_db::{
88pub use hir::{Documentation, Semantics}; 88pub use hir::{Documentation, Semantics};
89pub use ide_db::{ 89pub use ide_db::{
90 change::AnalysisChange, 90 change::AnalysisChange,
91 label::Label,
91 line_index::{LineCol, LineIndex}, 92 line_index::{LineCol, LineIndex},
92 search::SearchScope, 93 search::SearchScope,
93 source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, 94 source_change::{FileSystemEdit, SourceChange, SourceFileEdit},
@@ -99,35 +100,6 @@ pub use text_edit::{Indel, TextEdit};
99 100
100pub type Cancelable<T> = Result<T, Canceled>; 101pub type Cancelable<T> = Result<T, Canceled>;
101 102
102#[derive(Debug)]
103pub struct Diagnostic {
104 pub name: Option<String>,
105 pub message: String,
106 pub range: TextRange,
107 pub severity: Severity,
108 pub fix: Option<Fix>,
109}
110
111#[derive(Debug)]
112pub struct Fix {
113 pub label: String,
114 pub source_change: SourceChange,
115 /// Allows to trigger the fix only when the caret is in the range given
116 pub fix_trigger_range: TextRange,
117}
118
119impl Fix {
120 pub fn new(
121 label: impl Into<String>,
122 source_change: SourceChange,
123 fix_trigger_range: TextRange,
124 ) -> Self {
125 let label = label.into();
126 assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.'));
127 Self { label, source_change, fix_trigger_range }
128 }
129}
130
131/// Info associated with a text range. 103/// Info associated with a text range.
132#[derive(Debug)] 104#[derive(Debug)]
133pub struct RangeInfo<T> { 105pub struct RangeInfo<T> {
@@ -148,7 +120,7 @@ pub struct AnalysisHost {
148} 120}
149 121
150impl AnalysisHost { 122impl AnalysisHost {
151 pub fn new(lru_capacity: Option<usize>) -> Self { 123 pub fn new(lru_capacity: Option<usize>) -> AnalysisHost {
152 AnalysisHost { db: RootDatabase::new(lru_capacity) } 124 AnalysisHost { db: RootDatabase::new(lru_capacity) }
153 } 125 }
154 126
@@ -495,13 +467,10 @@ impl Analysis {
495 /// Computes the set of diagnostics for the given file. 467 /// Computes the set of diagnostics for the given file.
496 pub fn diagnostics( 468 pub fn diagnostics(
497 &self, 469 &self,
470 config: &DiagnosticsConfig,
498 file_id: FileId, 471 file_id: FileId,
499 enable_experimental: bool,
500 disabled_diagnostics: Option<HashSet<String>>,
501 ) -> Cancelable<Vec<Diagnostic>> { 472 ) -> Cancelable<Vec<Diagnostic>> {
502 self.with_db(|db| { 473 self.with_db(|db| diagnostics::diagnostics(db, config, file_id))
503 diagnostics::diagnostics(db, file_id, enable_experimental, disabled_diagnostics)
504 })
505 } 474 }
506 475
507 /// Returns the edit required to rename reference at the position to the new 476 /// Returns the edit required to rename reference at the position to the new
diff --git a/crates/ide_db/src/label.rs b/crates/ide_db/src/label.rs
new file mode 100644
index 000000000..c0e89e72f
--- /dev/null
+++ b/crates/ide_db/src/label.rs
@@ -0,0 +1,49 @@
1//! See `Label`
2use std::fmt;
3
4/// A type to specify UI label, like an entry in the list of assists. Enforces
5/// proper casing:
6///
7/// Frobnicate bar
8///
9/// Note the upper-case first letter and the absence of `.` at the end.
10#[derive(Clone)]
11pub struct Label(String);
12
13impl PartialEq<str> for Label {
14 fn eq(&self, other: &str) -> bool {
15 self.0 == other
16 }
17}
18
19impl PartialEq<&'_ str> for Label {
20 fn eq(&self, other: &&str) -> bool {
21 self == *other
22 }
23}
24
25impl From<Label> for String {
26 fn from(label: Label) -> String {
27 label.0
28 }
29}
30
31impl Label {
32 pub fn new(label: impl Into<String>) -> Label {
33 let label = label.into();
34 assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.'));
35 Label(label)
36 }
37}
38
39impl fmt::Display for Label {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 fmt::Display::fmt(&self.0, f)
42 }
43}
44
45impl fmt::Debug for Label {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 fmt::Debug::fmt(&self.0, f)
48 }
49}
diff --git a/crates/ide_db/src/lib.rs b/crates/ide_db/src/lib.rs
index fd474cd0f..70ada02f3 100644
--- a/crates/ide_db/src/lib.rs
+++ b/crates/ide_db/src/lib.rs
@@ -2,6 +2,7 @@
2//! 2//!
3//! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search. 3//! It is mainly a `HirDatabase` for semantic analysis, plus a `SymbolsDatabase`, for fuzzy search.
4 4
5pub mod label;
5pub mod line_index; 6pub mod line_index;
6pub mod symbol_index; 7pub mod symbol_index;
7pub mod change; 8pub mod change;
diff --git a/crates/rust-analyzer/src/cli/analysis_bench.rs b/crates/rust-analyzer/src/cli/analysis_bench.rs
index 43f0196af..c312e0a2e 100644
--- a/crates/rust-analyzer/src/cli/analysis_bench.rs
+++ b/crates/rust-analyzer/src/cli/analysis_bench.rs
@@ -7,7 +7,10 @@ use base_db::{
7 salsa::{Database, Durability}, 7 salsa::{Database, Durability},
8 FileId, 8 FileId,
9}; 9};
10use ide::{Analysis, AnalysisChange, AnalysisHost, CompletionConfig, FilePosition, LineCol}; 10use ide::{
11 Analysis, AnalysisChange, AnalysisHost, CompletionConfig, DiagnosticsConfig, FilePosition,
12 LineCol,
13};
11use vfs::AbsPathBuf; 14use vfs::AbsPathBuf;
12 15
13use crate::{ 16use crate::{
@@ -71,7 +74,7 @@ impl BenchCmd {
71 match &self.what { 74 match &self.what {
72 BenchWhat::Highlight { .. } => { 75 BenchWhat::Highlight { .. } => {
73 let res = do_work(&mut host, file_id, |analysis| { 76 let res = do_work(&mut host, file_id, |analysis| {
74 analysis.diagnostics(file_id, true, None).unwrap(); 77 analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap();
75 analysis.highlight_as_html(file_id, false).unwrap() 78 analysis.highlight_as_html(file_id, false).unwrap()
76 }); 79 });
77 if verbosity.is_verbose() { 80 if verbosity.is_verbose() {
diff --git a/crates/rust-analyzer/src/cli/diagnostics.rs b/crates/rust-analyzer/src/cli/diagnostics.rs
index 31eb7ff3f..c424aa6e2 100644
--- a/crates/rust-analyzer/src/cli/diagnostics.rs
+++ b/crates/rust-analyzer/src/cli/diagnostics.rs
@@ -8,7 +8,7 @@ use rustc_hash::FxHashSet;
8 8
9use base_db::SourceDatabaseExt; 9use base_db::SourceDatabaseExt;
10use hir::Crate; 10use hir::Crate;
11use ide::Severity; 11use ide::{DiagnosticsConfig, Severity};
12 12
13use crate::cli::{load_cargo::load_cargo, Result}; 13use crate::cli::{load_cargo::load_cargo, Result};
14 14
@@ -47,7 +47,8 @@ pub fn diagnostics(
47 String::from("unknown") 47 String::from("unknown")
48 }; 48 };
49 println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id)); 49 println!("processing crate: {}, module: {}", crate_name, _vfs.file_path(file_id));
50 for diagnostic in analysis.diagnostics(file_id, true, None).unwrap() { 50 for diagnostic in analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap()
51 {
51 if matches!(diagnostic.severity, Severity::Error) { 52 if matches!(diagnostic.severity, Severity::Error) {
52 found_error = true; 53 found_error = true;
53 } 54 }
diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs
index 44fd7c286..99f7751ac 100644
--- a/crates/rust-analyzer/src/config.rs
+++ b/crates/rust-analyzer/src/config.rs
@@ -7,24 +7,25 @@
7//! configure the server itself, feature flags are passed into analysis, and 7//! configure the server itself, feature flags are passed into analysis, and
8//! tweak things like automatic insertion of `()` in completions. 8//! tweak things like automatic insertion of `()` in completions.
9 9
10use std::{collections::HashSet, ffi::OsString, path::PathBuf}; 10use std::{ffi::OsString, path::PathBuf};
11 11
12use flycheck::FlycheckConfig; 12use flycheck::FlycheckConfig;
13use ide::{AssistConfig, CompletionConfig, HoverConfig, InlayHintsConfig}; 13use ide::{AssistConfig, CompletionConfig, DiagnosticsConfig, HoverConfig, InlayHintsConfig};
14use lsp_types::ClientCapabilities; 14use lsp_types::ClientCapabilities;
15use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest}; 15use project_model::{CargoConfig, ProjectJson, ProjectJsonData, ProjectManifest};
16use rustc_hash::FxHashSet;
16use serde::Deserialize; 17use serde::Deserialize;
17use vfs::AbsPathBuf; 18use vfs::AbsPathBuf;
18 19
19use crate::diagnostics::DiagnosticsConfig; 20use crate::diagnostics::DiagnosticsMapConfig;
20 21
21#[derive(Debug, Clone)] 22#[derive(Debug, Clone)]
22pub struct Config { 23pub struct Config {
23 pub client_caps: ClientCapsConfig, 24 pub client_caps: ClientCapsConfig,
24 25
25 pub publish_diagnostics: bool, 26 pub publish_diagnostics: bool,
26 pub experimental_diagnostics: bool,
27 pub diagnostics: DiagnosticsConfig, 27 pub diagnostics: DiagnosticsConfig,
28 pub diagnostics_map: DiagnosticsMapConfig,
28 pub lru_capacity: Option<usize>, 29 pub lru_capacity: Option<usize>,
29 pub proc_macro_srv: Option<(PathBuf, Vec<OsString>)>, 30 pub proc_macro_srv: Option<(PathBuf, Vec<OsString>)>,
30 pub files: FilesConfig, 31 pub files: FilesConfig,
@@ -45,14 +46,6 @@ pub struct Config {
45 pub with_sysroot: bool, 46 pub with_sysroot: bool,
46 pub linked_projects: Vec<LinkedProject>, 47 pub linked_projects: Vec<LinkedProject>,
47 pub root_path: AbsPathBuf, 48 pub root_path: AbsPathBuf,
48
49 pub analysis: AnalysisConfig,
50}
51
52/// Configuration parameters for the analysis run.
53#[derive(Debug, Default, Clone)]
54pub struct AnalysisConfig {
55 pub disabled_diagnostics: HashSet<String>,
56} 49}
57 50
58#[derive(Debug, Clone, Eq, PartialEq)] 51#[derive(Debug, Clone, Eq, PartialEq)]
@@ -146,8 +139,8 @@ impl Config {
146 139
147 with_sysroot: true, 140 with_sysroot: true,
148 publish_diagnostics: true, 141 publish_diagnostics: true,
149 experimental_diagnostics: true,
150 diagnostics: DiagnosticsConfig::default(), 142 diagnostics: DiagnosticsConfig::default(),
143 diagnostics_map: DiagnosticsMapConfig::default(),
151 lru_capacity: None, 144 lru_capacity: None,
152 proc_macro_srv: None, 145 proc_macro_srv: None,
153 files: FilesConfig { watcher: FilesWatcher::Notify, exclude: Vec::new() }, 146 files: FilesConfig { watcher: FilesWatcher::Notify, exclude: Vec::new() },
@@ -184,8 +177,6 @@ impl Config {
184 hover: HoverConfig::default(), 177 hover: HoverConfig::default(),
185 linked_projects: Vec::new(), 178 linked_projects: Vec::new(),
186 root_path, 179 root_path,
187
188 analysis: AnalysisConfig::default(),
189 } 180 }
190 } 181 }
191 182
@@ -200,8 +191,11 @@ impl Config {
200 191
201 self.with_sysroot = data.withSysroot; 192 self.with_sysroot = data.withSysroot;
202 self.publish_diagnostics = data.diagnostics_enable; 193 self.publish_diagnostics = data.diagnostics_enable;
203 self.experimental_diagnostics = data.diagnostics_enableExperimental;
204 self.diagnostics = DiagnosticsConfig { 194 self.diagnostics = DiagnosticsConfig {
195 disable_experimental: !data.diagnostics_enableExperimental,
196 disabled: data.diagnostics_disabled,
197 };
198 self.diagnostics_map = DiagnosticsMapConfig {
205 warnings_as_info: data.diagnostics_warningsAsInfo, 199 warnings_as_info: data.diagnostics_warningsAsInfo,
206 warnings_as_hint: data.diagnostics_warningsAsHint, 200 warnings_as_hint: data.diagnostics_warningsAsHint,
207 }; 201 };
@@ -303,8 +297,6 @@ impl Config {
303 goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef, 297 goto_type_def: data.hoverActions_enable && data.hoverActions_gotoTypeDef,
304 }; 298 };
305 299
306 self.analysis = AnalysisConfig { disabled_diagnostics: data.analysis_disabledDiagnostics };
307
308 log::info!("Config::update() = {:#?}", self); 300 log::info!("Config::update() = {:#?}", self);
309 } 301 }
310 302
@@ -369,14 +361,6 @@ impl Config {
369 self.client_caps.status_notification = get_bool("statusNotification"); 361 self.client_caps.status_notification = get_bool("statusNotification");
370 } 362 }
371 } 363 }
372
373 pub fn disabled_diagnostics(&self) -> Option<HashSet<String>> {
374 if self.analysis.disabled_diagnostics.is_empty() {
375 None
376 } else {
377 Some(self.analysis.disabled_diagnostics.clone())
378 }
379 }
380} 364}
381 365
382#[derive(Deserialize)] 366#[derive(Deserialize)]
@@ -434,6 +418,7 @@ config_data! {
434 418
435 diagnostics_enable: bool = true, 419 diagnostics_enable: bool = true,
436 diagnostics_enableExperimental: bool = true, 420 diagnostics_enableExperimental: bool = true,
421 diagnostics_disabled: FxHashSet<String> = FxHashSet::default(),
437 diagnostics_warningsAsHint: Vec<String> = Vec::new(), 422 diagnostics_warningsAsHint: Vec<String> = Vec::new(),
438 diagnostics_warningsAsInfo: Vec<String> = Vec::new(), 423 diagnostics_warningsAsInfo: Vec<String> = Vec::new(),
439 424
@@ -464,7 +449,5 @@ config_data! {
464 rustfmt_overrideCommand: Option<Vec<String>> = None, 449 rustfmt_overrideCommand: Option<Vec<String>> = None,
465 450
466 withSysroot: bool = true, 451 withSysroot: bool = true,
467
468 analysis_disabledDiagnostics: HashSet<String> = HashSet::new(),
469 } 452 }
470} 453}
diff --git a/crates/rust-analyzer/src/diagnostics.rs b/crates/rust-analyzer/src/diagnostics.rs
index 108df3eb0..ee6f2a867 100644
--- a/crates/rust-analyzer/src/diagnostics.rs
+++ b/crates/rust-analyzer/src/diagnostics.rs
@@ -11,7 +11,7 @@ use crate::lsp_ext;
11pub(crate) type CheckFixes = Arc<FxHashMap<FileId, Vec<Fix>>>; 11pub(crate) type CheckFixes = Arc<FxHashMap<FileId, Vec<Fix>>>;
12 12
13#[derive(Debug, Default, Clone)] 13#[derive(Debug, Default, Clone)]
14pub struct DiagnosticsConfig { 14pub struct DiagnosticsMapConfig {
15 pub warnings_as_info: Vec<String>, 15 pub warnings_as_info: Vec<String>,
16 pub warnings_as_hint: Vec<String>, 16 pub warnings_as_hint: Vec<String>,
17} 17}
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs
index 6d5408156..df5583897 100644
--- a/crates/rust-analyzer/src/diagnostics/to_proto.rs
+++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs
@@ -7,11 +7,11 @@ use stdx::format_to;
7 7
8use crate::{lsp_ext, to_proto::url_from_abs_path}; 8use crate::{lsp_ext, to_proto::url_from_abs_path};
9 9
10use super::DiagnosticsConfig; 10use super::DiagnosticsMapConfig;
11 11
12/// Determines the LSP severity from a diagnostic 12/// Determines the LSP severity from a diagnostic
13fn diagnostic_severity( 13fn diagnostic_severity(
14 config: &DiagnosticsConfig, 14 config: &DiagnosticsMapConfig,
15 level: flycheck::DiagnosticLevel, 15 level: flycheck::DiagnosticLevel,
16 code: Option<flycheck::DiagnosticCode>, 16 code: Option<flycheck::DiagnosticCode>,
17) -> Option<lsp_types::DiagnosticSeverity> { 17) -> Option<lsp_types::DiagnosticSeverity> {
@@ -141,7 +141,7 @@ pub(crate) struct MappedRustDiagnostic {
141/// 141///
142/// If the diagnostic has no primary span this will return `None` 142/// If the diagnostic has no primary span this will return `None`
143pub(crate) fn map_rust_diagnostic_to_lsp( 143pub(crate) fn map_rust_diagnostic_to_lsp(
144 config: &DiagnosticsConfig, 144 config: &DiagnosticsMapConfig,
145 rd: &flycheck::Diagnostic, 145 rd: &flycheck::Diagnostic,
146 workspace_root: &Path, 146 workspace_root: &Path,
147) -> Vec<MappedRustDiagnostic> { 147) -> Vec<MappedRustDiagnostic> {
@@ -259,10 +259,10 @@ mod tests {
259 use expect::{expect_file, ExpectFile}; 259 use expect::{expect_file, ExpectFile};
260 260
261 fn check(diagnostics_json: &str, expect: ExpectFile) { 261 fn check(diagnostics_json: &str, expect: ExpectFile) {
262 check_with_config(DiagnosticsConfig::default(), diagnostics_json, expect) 262 check_with_config(DiagnosticsMapConfig::default(), diagnostics_json, expect)
263 } 263 }
264 264
265 fn check_with_config(config: DiagnosticsConfig, diagnostics_json: &str, expect: ExpectFile) { 265 fn check_with_config(config: DiagnosticsMapConfig, diagnostics_json: &str, expect: ExpectFile) {
266 let diagnostic: flycheck::Diagnostic = serde_json::from_str(diagnostics_json).unwrap(); 266 let diagnostic: flycheck::Diagnostic = serde_json::from_str(diagnostics_json).unwrap();
267 let workspace_root = Path::new("/test/"); 267 let workspace_root = Path::new("/test/");
268 let actual = map_rust_diagnostic_to_lsp(&config, &diagnostic, workspace_root); 268 let actual = map_rust_diagnostic_to_lsp(&config, &diagnostic, workspace_root);
@@ -402,9 +402,9 @@ mod tests {
402 #[cfg(not(windows))] 402 #[cfg(not(windows))]
403 fn rustc_unused_variable_as_info() { 403 fn rustc_unused_variable_as_info() {
404 check_with_config( 404 check_with_config(
405 DiagnosticsConfig { 405 DiagnosticsMapConfig {
406 warnings_as_info: vec!["unused_variables".to_string()], 406 warnings_as_info: vec!["unused_variables".to_string()],
407 ..DiagnosticsConfig::default() 407 ..DiagnosticsMapConfig::default()
408 }, 408 },
409 r##"{ 409 r##"{
410 "message": "unused variable: `foo`", 410 "message": "unused variable: `foo`",
@@ -486,9 +486,9 @@ mod tests {
486 #[cfg(not(windows))] 486 #[cfg(not(windows))]
487 fn rustc_unused_variable_as_hint() { 487 fn rustc_unused_variable_as_hint() {
488 check_with_config( 488 check_with_config(
489 DiagnosticsConfig { 489 DiagnosticsMapConfig {
490 warnings_as_hint: vec!["unused_variables".to_string()], 490 warnings_as_hint: vec!["unused_variables".to_string()],
491 ..DiagnosticsConfig::default() 491 ..DiagnosticsMapConfig::default()
492 }, 492 },
493 r##"{ 493 r##"{
494 "message": "unused variable: `foo`", 494 "message": "unused variable: `foo`",
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 4f77b1b4d..33e60b500 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -271,24 +271,24 @@ pub(crate) fn handle_document_symbol(
271 }; 271 };
272 parents.push((doc_symbol, symbol.parent)); 272 parents.push((doc_symbol, symbol.parent));
273 } 273 }
274 let mut document_symbols = Vec::new();
275 // Constructs `document_symbols` from `parents`, in order from the end.
276 while let Some((node, parent)) = parents.pop() {
277 match parent {
278 None => document_symbols.push(node),
279 Some(i) => {
280 parents[i].0.children.get_or_insert_with(Vec::new).push(node);
281 }
282 }
283 }
284 274
285 fn reverse(symbols: &mut Vec<DocumentSymbol>) { 275 // Builds hierarchy from a flat list, in reverse order (so that indices
286 for sym in symbols.iter_mut() { 276 // makes sense)
287 sym.children.as_mut().map(|c| reverse(c)); 277 let document_symbols = {
278 let mut acc = Vec::new();
279 while let Some((mut node, parent_idx)) = parents.pop() {
280 if let Some(children) = &mut node.children {
281 children.reverse();
282 }
283 let parent = match parent_idx {
284 None => &mut acc,
285 Some(i) => parents[i].0.children.get_or_insert_with(Vec::new),
286 };
287 parent.push(node);
288 } 288 }
289 symbols.reverse(); 289 acc.reverse();
290 } 290 acc
291 reverse(&mut document_symbols); 291 };
292 292
293 let res = if snap.config.client_caps.hierarchical_symbols { 293 let res = if snap.config.client_caps.hierarchical_symbols {
294 document_symbols.into() 294 document_symbols.into()
@@ -775,21 +775,16 @@ fn handle_fixes(
775 None => {} 775 None => {}
776 }; 776 };
777 777
778 let diagnostics = snap.analysis.diagnostics( 778 let diagnostics = snap.analysis.diagnostics(&snap.config.diagnostics, file_id)?;
779 file_id,
780 snap.config.experimental_diagnostics,
781 snap.config.disabled_diagnostics(),
782 )?;
783 779
784 for fix in diagnostics 780 for fix in diagnostics
785 .into_iter() 781 .into_iter()
786 .filter_map(|d| d.fix) 782 .filter_map(|d| d.fix)
787 .filter(|fix| fix.fix_trigger_range.intersect(range).is_some()) 783 .filter(|fix| fix.fix_trigger_range.intersect(range).is_some())
788 { 784 {
789 let title = fix.label;
790 let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?; 785 let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?;
791 let action = lsp_ext::CodeAction { 786 let action = lsp_ext::CodeAction {
792 title, 787 title: fix.label.to_string(),
793 id: None, 788 id: None,
794 group: None, 789 group: None,
795 kind: Some(CodeActionKind::QUICKFIX), 790 kind: Some(CodeActionKind::QUICKFIX),
@@ -1051,13 +1046,10 @@ pub(crate) fn publish_diagnostics(
1051) -> Result<Vec<Diagnostic>> { 1046) -> Result<Vec<Diagnostic>> {
1052 let _p = profile::span("publish_diagnostics"); 1047 let _p = profile::span("publish_diagnostics");
1053 let line_index = snap.analysis.file_line_index(file_id)?; 1048 let line_index = snap.analysis.file_line_index(file_id)?;
1049
1054 let diagnostics: Vec<Diagnostic> = snap 1050 let diagnostics: Vec<Diagnostic> = snap
1055 .analysis 1051 .analysis
1056 .diagnostics( 1052 .diagnostics(&snap.config.diagnostics, file_id)?
1057 file_id,
1058 snap.config.experimental_diagnostics,
1059 snap.config.disabled_diagnostics(),
1060 )?
1061 .into_iter() 1053 .into_iter()
1062 .map(|d| Diagnostic { 1054 .map(|d| Diagnostic {
1063 range: to_proto::range(&line_index, d.range), 1055 range: to_proto::range(&line_index, d.range),
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index 66e04653a..f039cdc31 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -248,7 +248,7 @@ impl GlobalState {
248 Event::Flycheck(task) => match task { 248 Event::Flycheck(task) => match task {
249 flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => { 249 flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => {
250 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( 250 let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp(
251 &self.config.diagnostics, 251 &self.config.diagnostics_map,
252 &diagnostic, 252 &diagnostic,
253 &workspace_root, 253 &workspace_root,
254 ); 254 );
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 535de2f71..643dcb4fc 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -704,7 +704,7 @@ pub(crate) fn unresolved_code_action(
704 index: usize, 704 index: usize,
705) -> Result<lsp_ext::CodeAction> { 705) -> Result<lsp_ext::CodeAction> {
706 let res = lsp_ext::CodeAction { 706 let res = lsp_ext::CodeAction {
707 title: assist.label().to_string(), 707 title: assist.label.to_string(),
708 id: Some(format!("{}:{}", assist.id.0, index.to_string())), 708 id: Some(format!("{}:{}", assist.id.0, index.to_string())),
709 group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0), 709 group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0),
710 kind: Some(code_action_kind(assist.id.1)), 710 kind: Some(code_action_kind(assist.id.1)),
diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs
index 265d19288..5d60f0219 100644
--- a/crates/stdx/src/lib.rs
+++ b/crates/stdx/src/lib.rs
@@ -1,5 +1,8 @@
1//! Missing batteries for standard libraries. 1//! Missing batteries for standard libraries.
2use std::time::Instant; 2use std::{
3 sync::atomic::{AtomicUsize, Ordering},
4 time::Instant,
5};
3 6
4mod macros; 7mod macros;
5 8
@@ -134,6 +137,31 @@ where
134 left 137 left
135} 138}
136 139
140pub struct RacyFlag(AtomicUsize);
141
142impl RacyFlag {
143 pub const fn new() -> RacyFlag {
144 RacyFlag(AtomicUsize::new(0))
145 }
146
147 pub fn get(&self, init: impl FnMut() -> bool) -> bool {
148 let mut init = Some(init);
149 self.get_impl(&mut || init.take().map_or(false, |mut f| f()))
150 }
151
152 fn get_impl(&self, init: &mut dyn FnMut() -> bool) -> bool {
153 match self.0.load(Ordering::Relaxed) {
154 0 => false,
155 1 => true,
156 _ => {
157 let res = init();
158 self.0.store(if res { 1 } else { 0 }, Ordering::Relaxed);
159 res
160 }
161 }
162 }
163}
164
137#[cfg(test)] 165#[cfg(test)]
138mod tests { 166mod tests {
139 use super::*; 167 use super::*;
diff --git a/docs/dev/style.md b/docs/dev/style.md
index 8effddcda..2454087e8 100644
--- a/docs/dev/style.md
+++ b/docs/dev/style.md
@@ -148,8 +148,13 @@ struct Foo {
148Use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)). 148Use boring and long names for local variables ([yay code completion](https://github.com/rust-analyzer/rust-analyzer/pull/4162#discussion_r417130973)).
149The default name is a lowercased name of the type: `global_state: GlobalState`. 149The default name is a lowercased name of the type: `global_state: GlobalState`.
150Avoid ad-hoc acronyms and contractions, but use the ones that exist consistently (`db`, `ctx`, `acc`). 150Avoid ad-hoc acronyms and contractions, but use the ones that exist consistently (`db`, `ctx`, `acc`).
151The default name for "result of the function" local variable is `res`. 151
152The default name for "I don't really care about the name" variable is `it`. 152Default names:
153
154* `res` -- "result of the function" local variable
155* `it` -- I don't really care about the name
156* `n_foo` -- number of foos
157* `foo_idx` -- index of `foo`
153 158
154# Collection types 159# Collection types
155 160
diff --git a/editors/code/package.json b/editors/code/package.json
index 429ff5def..f079f73b8 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -592,31 +592,31 @@
592 "default": true, 592 "default": true,
593 "markdownDescription": "Whether to show experimental rust-analyzer diagnostics that might have more false positives than usual." 593 "markdownDescription": "Whether to show experimental rust-analyzer diagnostics that might have more false positives than usual."
594 }, 594 },
595 "rust-analyzer.diagnostics.warningsAsInfo": { 595 "rust-analyzer.diagnostics.disabled": {
596 "type": "array", 596 "type": "array",
597 "uniqueItems": true, 597 "uniqueItems": true,
598 "items": { 598 "items": {
599 "type": "string" 599 "type": "string"
600 }, 600 },
601 "description": "List of warnings that should be displayed with info severity.\nThe warnings will be indicated by a blue squiggly underline in code and a blue icon in the problems panel.", 601 "description": "List of rust-analyzer diagnostics to disable",
602 "default": [] 602 "default": []
603 }, 603 },
604 "rust-analyzer.diagnostics.warningsAsHint": { 604 "rust-analyzer.diagnostics.warningsAsInfo": {
605 "type": "array", 605 "type": "array",
606 "uniqueItems": true, 606 "uniqueItems": true,
607 "items": { 607 "items": {
608 "type": "string" 608 "type": "string"
609 }, 609 },
610 "description": "List of warnings that should be displayed with hint severity.\nThe warnings will be indicated by faded text or three dots in code and will not show up in the problems panel.", 610 "description": "List of warnings that should be displayed with info severity.\nThe warnings will be indicated by a blue squiggly underline in code and a blue icon in the problems panel.",
611 "default": [] 611 "default": []
612 }, 612 },
613 "rust-analyzer.analysis.disabledDiagnostics": { 613 "rust-analyzer.diagnostics.warningsAsHint": {
614 "type": "array", 614 "type": "array",
615 "uniqueItems": true, 615 "uniqueItems": true,
616 "items": { 616 "items": {
617 "type": "string" 617 "type": "string"
618 }, 618 },
619 "description": "List of rust-analyzer diagnostics to disable", 619 "description": "List of warnings that should be displayed with hint severity.\nThe warnings will be indicated by faded text or three dots in code and will not show up in the problems panel.",
620 "default": [] 620 "default": []
621 } 621 }
622 } 622 }