about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--crates/hir-ty/src/diagnostics.rs2
-rw-r--r--crates/hir-ty/src/diagnostics/decl_check.rs6
-rw-r--r--crates/hir/src/diagnostics.rs2
-rw-r--r--crates/hir/src/lib.rs10
-rw-r--r--crates/ide-diagnostics/Cargo.toml1
-rw-r--r--crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs9
-rw-r--r--crates/ide-diagnostics/src/handlers/expected_function.rs9
-rw-r--r--crates/ide-diagnostics/src/handlers/field_shorthand.rs40
-rw-r--r--crates/ide-diagnostics/src/handlers/inactive_code.rs7
-rw-r--r--crates/ide-diagnostics/src/handlers/incoherent_impl.rs10
-rw-r--r--crates/ide-diagnostics/src/handlers/incorrect_case.rs112
-rw-r--r--crates/ide-diagnostics/src/handlers/invalid_derive_target.rs5
-rw-r--r--crates/ide-diagnostics/src/handlers/json_is_not_rust.rs5
-rw-r--r--crates/ide-diagnostics/src/handlers/macro_error.rs16
-rw-r--r--crates/ide-diagnostics/src/handlers/malformed_derive.rs5
-rw-r--r--crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs4
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_fields.rs4
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_match_arms.rs9
-rw-r--r--crates/ide-diagnostics/src/handlers/missing_unsafe.rs9
-rw-r--r--crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs9
-rw-r--r--crates/ide-diagnostics/src/handlers/mutability_errors.rs125
-rw-r--r--crates/ide-diagnostics/src/handlers/no_such_field.rs9
-rw-r--r--crates/ide-diagnostics/src/handlers/private_assoc_item.rs21
-rw-r--r--crates/ide-diagnostics/src/handlers/private_field.rs9
-rw-r--r--crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs41
-rw-r--r--crates/ide-diagnostics/src/handlers/type_mismatch.rs4
-rw-r--r--crates/ide-diagnostics/src/handlers/typed_hole.rs5
-rw-r--r--crates/ide-diagnostics/src/handlers/undeclared_label.rs9
-rw-r--r--crates/ide-diagnostics/src/handlers/unimplemented_builtin_macro.rs10
-rw-r--r--crates/ide-diagnostics/src/handlers/unlinked_file.rs5
-rw-r--r--crates/ide-diagnostics/src/handlers/unreachable_label.rs9
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_extern_crate.rs9
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_field.rs9
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_import.rs9
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs4
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_method.rs9
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_module.rs29
-rw-r--r--crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs4
-rw-r--r--crates/ide-diagnostics/src/handlers/useless_braces.rs5
-rw-r--r--crates/ide-diagnostics/src/lib.rs257
-rw-r--r--crates/ide-diagnostics/src/tests.rs13
-rw-r--r--crates/rust-analyzer/src/main_loop.rs6
-rw-r--r--crates/rust-analyzer/src/to_proto.rs3
44 files changed, 628 insertions, 251 deletions
diff --git a/Cargo.lock b/Cargo.lock
index e94dd9b4076..c8c1b881c3f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -760,6 +760,7 @@ dependencies = [
  "hir",
  "ide-db",
  "itertools",
+ "once_cell",
  "profile",
  "serde_json",
  "sourcegen",
diff --git a/crates/hir-ty/src/diagnostics.rs b/crates/hir-ty/src/diagnostics.rs
index 4b147b99707..ef43ed5c463 100644
--- a/crates/hir-ty/src/diagnostics.rs
+++ b/crates/hir-ty/src/diagnostics.rs
@@ -5,7 +5,7 @@ mod unsafe_check;
 mod decl_check;
 
 pub use crate::diagnostics::{
-    decl_check::{incorrect_case, IncorrectCase},
+    decl_check::{incorrect_case, CaseType, IncorrectCase},
     expr::{
         record_literal_missing_fields, record_pattern_missing_fields, BodyValidationDiagnostic,
     },
diff --git a/crates/hir-ty/src/diagnostics/decl_check.rs b/crates/hir-ty/src/diagnostics/decl_check.rs
index 9a2df1e9001..73c8ad3dd5a 100644
--- a/crates/hir-ty/src/diagnostics/decl_check.rs
+++ b/crates/hir-ty/src/diagnostics/decl_check.rs
@@ -57,11 +57,11 @@ pub fn incorrect_case(
 
 #[derive(Debug)]
 pub enum CaseType {
-    // `some_var`
+    /// `some_var`
     LowerSnakeCase,
-    // `SOME_CONST`
+    /// `SOME_CONST`
     UpperSnakeCase,
-    // `SomeStruct`
+    /// `SomeStruct`
     UpperCamelCase,
 }
 
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index b64d81490bb..80c3bcdca8b 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -3,7 +3,7 @@
 //!
 //! This probably isn't the best way to do this -- ideally, diagnostics should
 //! be expressed in terms of hir types themselves.
-pub use hir_ty::diagnostics::{IncoherentImpl, IncorrectCase};
+pub use hir_ty::diagnostics::{CaseType, IncoherentImpl, IncorrectCase};
 
 use base_db::CrateId;
 use cfg::{CfgExpr, CfgOptions};
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs
index 90825684368..645ea42f538 100644
--- a/crates/hir/src/lib.rs
+++ b/crates/hir/src/lib.rs
@@ -89,11 +89,11 @@ use crate::db::{DefDatabase, HirDatabase};
 pub use crate::{
     attrs::{HasAttrs, Namespace},
     diagnostics::{
-        AnyDiagnostic, BreakOutsideOfLoop, ExpectedFunction, InactiveCode, IncoherentImpl,
-        IncorrectCase, InvalidDeriveTarget, MacroDefError, MacroError, MacroExpansionParseError,
-        MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, MissingUnsafe,
-        MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem, PrivateField,
-        ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel,
+        AnyDiagnostic, BreakOutsideOfLoop, CaseType, ExpectedFunction, InactiveCode,
+        IncoherentImpl, IncorrectCase, InvalidDeriveTarget, MacroDefError, MacroError,
+        MacroExpansionParseError, MalformedDerive, MismatchedArgCount, MissingFields,
+        MissingMatchArms, MissingUnsafe, MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem,
+        PrivateField, ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel,
         UnimplementedBuiltinMacro, UnreachableLabel, UnresolvedExternCrate, UnresolvedField,
         UnresolvedImport, UnresolvedMacroCall, UnresolvedMethodCall, UnresolvedModule,
         UnresolvedProcMacro, UnusedMut,
diff --git a/crates/ide-diagnostics/Cargo.toml b/crates/ide-diagnostics/Cargo.toml
index e18624fcc26..14aa3940199 100644
--- a/crates/ide-diagnostics/Cargo.toml
+++ b/crates/ide-diagnostics/Cargo.toml
@@ -16,6 +16,7 @@ cov-mark = "2.0.0-pre.1"
 either = "1.7.0"
 itertools = "0.10.5"
 serde_json = "1.0.86"
+once_cell = "1.17.0"
 
 # local deps
 profile.workspace = true
diff --git a/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs b/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs
index 30576c71fb7..49100540388 100644
--- a/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs
+++ b/crates/ide-diagnostics/src/handlers/break_outside_of_loop.rs
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: break-outside-of-loop
 //
@@ -13,10 +13,11 @@ pub(crate) fn break_outside_of_loop(
         let construct = if d.is_break { "break" } else { "continue" };
         format!("{construct} outside of loop")
     };
-    Diagnostic::new(
-        "break-outside-of-loop",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0268"),
         message,
-        ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
+        d.expr.clone().map(|it| it.into()),
     )
 }
 
diff --git a/crates/ide-diagnostics/src/handlers/expected_function.rs b/crates/ide-diagnostics/src/handlers/expected_function.rs
index d2f27664d6f..e1e5db91c54 100644
--- a/crates/ide-diagnostics/src/handlers/expected_function.rs
+++ b/crates/ide-diagnostics/src/handlers/expected_function.rs
@@ -1,6 +1,6 @@
 use hir::HirDisplay;
 
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: expected-function
 //
@@ -9,10 +9,11 @@ pub(crate) fn expected_function(
     ctx: &DiagnosticsContext<'_>,
     d: &hir::ExpectedFunction,
 ) -> Diagnostic {
-    Diagnostic::new(
-        "expected-function",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0618"),
         format!("expected function, found {}", d.found.display(ctx.sema.db)),
-        ctx.sema.diagnostics_display_range(d.call.clone().map(|it| it.into())).range,
+        d.call.clone().map(|it| it.into()),
     )
     .experimental()
 }
diff --git a/crates/ide-diagnostics/src/handlers/field_shorthand.rs b/crates/ide-diagnostics/src/handlers/field_shorthand.rs
index 2b710536250..3b69640af9b 100644
--- a/crates/ide-diagnostics/src/handlers/field_shorthand.rs
+++ b/crates/ide-diagnostics/src/handlers/field_shorthand.rs
@@ -5,7 +5,7 @@ use ide_db::{base_db::FileId, source_change::SourceChange};
 use syntax::{ast, match_ast, AstNode, SyntaxNode};
 use text_edit::TextEdit;
 
-use crate::{fix, Diagnostic, Severity};
+use crate::{fix, Diagnostic, DiagnosticCode};
 
 pub(crate) fn field_shorthand(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) {
     match_ast! {
@@ -46,14 +46,17 @@ fn check_expr_field_shorthand(
 
         let field_range = record_field.syntax().text_range();
         acc.push(
-            Diagnostic::new("use-field-shorthand", "Shorthand struct initialization", field_range)
-                .severity(Severity::WeakWarning)
-                .with_fixes(Some(vec![fix(
-                    "use_expr_field_shorthand",
-                    "Use struct shorthand initialization",
-                    SourceChange::from_text_edit(file_id, edit),
-                    field_range,
-                )])),
+            Diagnostic::new(
+                DiagnosticCode::Clippy("redundant_field_names"),
+                "Shorthand struct initialization",
+                field_range,
+            )
+            .with_fixes(Some(vec![fix(
+                "use_expr_field_shorthand",
+                "Use struct shorthand initialization",
+                SourceChange::from_text_edit(file_id, edit),
+                field_range,
+            )])),
         );
     }
 }
@@ -87,14 +90,17 @@ fn check_pat_field_shorthand(
 
         let field_range = record_pat_field.syntax().text_range();
         acc.push(
-            Diagnostic::new("use-field-shorthand", "Shorthand struct pattern", field_range)
-                .severity(Severity::WeakWarning)
-                .with_fixes(Some(vec![fix(
-                    "use_pat_field_shorthand",
-                    "Use struct field shorthand",
-                    SourceChange::from_text_edit(file_id, edit),
-                    field_range,
-                )])),
+            Diagnostic::new(
+                DiagnosticCode::Clippy("redundant_field_names"),
+                "Shorthand struct pattern",
+                field_range,
+            )
+            .with_fixes(Some(vec![fix(
+                "use_pat_field_shorthand",
+                "Use struct field shorthand",
+                SourceChange::from_text_edit(file_id, edit),
+                field_range,
+            )])),
         );
     }
 }
diff --git a/crates/ide-diagnostics/src/handlers/inactive_code.rs b/crates/ide-diagnostics/src/handlers/inactive_code.rs
index f558b7256a4..9eb763d3e2c 100644
--- a/crates/ide-diagnostics/src/handlers/inactive_code.rs
+++ b/crates/ide-diagnostics/src/handlers/inactive_code.rs
@@ -1,7 +1,7 @@
 use cfg::DnfExpr;
 use stdx::format_to;
 
-use crate::{Diagnostic, DiagnosticsContext, Severity};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
 
 // Diagnostic: inactive-code
 //
@@ -27,13 +27,12 @@ pub(crate) fn inactive_code(
             format_to!(message, ": {}", inactive);
         }
     }
-
+    // FIXME: This shouldn't be a diagnostic
     let res = Diagnostic::new(
-        "inactive-code",
+        DiagnosticCode::Ra("inactive-code", Severity::WeakWarning),
         message,
         ctx.sema.diagnostics_display_range(d.node.clone()).range,
     )
-    .severity(Severity::WeakWarning)
     .with_unused(true);
     Some(res)
 }
diff --git a/crates/ide-diagnostics/src/handlers/incoherent_impl.rs b/crates/ide-diagnostics/src/handlers/incoherent_impl.rs
index 72af9ebfcbb..4afb4db03bd 100644
--- a/crates/ide-diagnostics/src/handlers/incoherent_impl.rs
+++ b/crates/ide-diagnostics/src/handlers/incoherent_impl.rs
@@ -1,17 +1,17 @@
 use hir::InFile;
 
-use crate::{Diagnostic, DiagnosticsContext, Severity};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: incoherent-impl
 //
 // This diagnostic is triggered if the targe type of an impl is from a foreign crate.
 pub(crate) fn incoherent_impl(ctx: &DiagnosticsContext<'_>, d: &hir::IncoherentImpl) -> Diagnostic {
-    Diagnostic::new(
-        "incoherent-impl",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0210"),
         format!("cannot define inherent `impl` for foreign type"),
-        ctx.sema.diagnostics_display_range(InFile::new(d.file_id, d.impl_.clone().into())).range,
+        InFile::new(d.file_id, d.impl_.clone().into()),
     )
-    .severity(Severity::Error)
 }
 
 #[cfg(test)]
diff --git a/crates/ide-diagnostics/src/handlers/incorrect_case.rs b/crates/ide-diagnostics/src/handlers/incorrect_case.rs
index 90279e14536..92fd4f71ca5 100644
--- a/crates/ide-diagnostics/src/handlers/incorrect_case.rs
+++ b/crates/ide-diagnostics/src/handlers/incorrect_case.rs
@@ -1,4 +1,4 @@
-use hir::{db::ExpandDatabase, InFile};
+use hir::{db::ExpandDatabase, CaseType, InFile};
 use ide_db::{assists::Assist, defs::NameClass};
 use syntax::AstNode;
 
@@ -6,23 +6,29 @@ use crate::{
     // references::rename::rename_with_semantics,
     unresolved_fix,
     Diagnostic,
+    DiagnosticCode,
     DiagnosticsContext,
-    Severity,
 };
 
 // Diagnostic: incorrect-ident-case
 //
 // This diagnostic is triggered if an item name doesn't follow https://doc.rust-lang.org/1.0.0/style/style/naming/README.html[Rust naming convention].
 pub(crate) fn incorrect_case(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Diagnostic {
-    Diagnostic::new(
-        "incorrect-ident-case",
+    let code = match d.expected_case {
+        CaseType::LowerSnakeCase => DiagnosticCode::RustcLint("non_snake_case"),
+        CaseType::UpperSnakeCase => DiagnosticCode::RustcLint("non_upper_case_globals"),
+        // The name is lying. It also covers variants, traits, ...
+        CaseType::UpperCamelCase => DiagnosticCode::RustcLint("non_camel_case_types"),
+    };
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        code,
         format!(
             "{} `{}` should have {} name, e.g. `{}`",
             d.ident_type, d.ident_text, d.expected_case, d.suggested_text
         ),
-        ctx.sema.diagnostics_display_range(InFile::new(d.file, d.ident.clone().into())).range,
+        InFile::new(d.file, d.ident.clone().into()),
     )
-    .severity(Severity::WeakWarning)
     .with_fixes(fixes(ctx, d))
 }
 
@@ -149,7 +155,7 @@ impl TestStruct {
         check_diagnostics(
             r#"
 fn FOO() {}
-// ^^^ 💡 weak: Function `FOO` should have snake_case name, e.g. `foo`
+// ^^^ 💡 warn: Function `FOO` should have snake_case name, e.g. `foo`
 "#,
         );
         check_fix(r#"fn FOO$0() {}"#, r#"fn foo() {}"#);
@@ -160,7 +166,7 @@ fn FOO() {}
         check_diagnostics(
             r#"
 fn NonSnakeCaseName() {}
-// ^^^^^^^^^^^^^^^^ 💡 weak: Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
+// ^^^^^^^^^^^^^^^^ 💡 warn: Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
 "#,
         );
     }
@@ -170,10 +176,10 @@ fn NonSnakeCaseName() {}
         check_diagnostics(
             r#"
 fn foo(SomeParam: u8) {}
-    // ^^^^^^^^^ 💡 weak: Parameter `SomeParam` should have snake_case name, e.g. `some_param`
+    // ^^^^^^^^^ 💡 warn: Parameter `SomeParam` should have snake_case name, e.g. `some_param`
 
 fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
-                     // ^^^^^^^^^^ 💡 weak: Parameter `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
+                     // ^^^^^^^^^^ 💡 warn: Parameter `CAPS_PARAM` should have snake_case name, e.g. `caps_param`
 "#,
         );
     }
@@ -184,9 +190,9 @@ fn foo2(ok_param: &str, CAPS_PARAM: u8) {}
             r#"
 fn foo() {
     let SOME_VALUE = 10;
-     // ^^^^^^^^^^ 💡 weak: Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
+     // ^^^^^^^^^^ 💡 warn: Variable `SOME_VALUE` should have snake_case name, e.g. `some_value`
     let AnotherValue = 20;
-     // ^^^^^^^^^^^^ 💡 weak: Variable `AnotherValue` should have snake_case name, e.g. `another_value`
+     // ^^^^^^^^^^^^ 💡 warn: Variable `AnotherValue` should have snake_case name, e.g. `another_value`
 }
 "#,
         );
@@ -197,10 +203,10 @@ fn foo() {
         check_diagnostics(
             r#"
 struct non_camel_case_name {}
-    // ^^^^^^^^^^^^^^^^^^^ 💡 weak: Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
+    // ^^^^^^^^^^^^^^^^^^^ 💡 warn: Structure `non_camel_case_name` should have CamelCase name, e.g. `NonCamelCaseName`
 
 struct SCREAMING_CASE {}
-    // ^^^^^^^^^^^^^^ 💡 weak: Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
+    // ^^^^^^^^^^^^^^ 💡 warn: Structure `SCREAMING_CASE` should have CamelCase name, e.g. `ScreamingCase`
 "#,
         );
     }
@@ -219,7 +225,7 @@ struct AABB {}
         check_diagnostics(
             r#"
 struct SomeStruct { SomeField: u8 }
-                 // ^^^^^^^^^ 💡 weak: Field `SomeField` should have snake_case name, e.g. `some_field`
+                 // ^^^^^^^^^ 💡 warn: Field `SomeField` should have snake_case name, e.g. `some_field`
 "#,
         );
     }
@@ -229,10 +235,10 @@ struct SomeStruct { SomeField: u8 }
         check_diagnostics(
             r#"
 enum some_enum { Val(u8) }
-  // ^^^^^^^^^ 💡 weak: Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
+  // ^^^^^^^^^ 💡 warn: Enum `some_enum` should have CamelCase name, e.g. `SomeEnum`
 
 enum SOME_ENUM {}
-  // ^^^^^^^^^ 💡 weak: Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
+  // ^^^^^^^^^ 💡 warn: Enum `SOME_ENUM` should have CamelCase name, e.g. `SomeEnum`
 "#,
         );
     }
@@ -251,7 +257,7 @@ enum AABB {}
         check_diagnostics(
             r#"
 enum SomeEnum { SOME_VARIANT(u8) }
-             // ^^^^^^^^^^^^ 💡 weak: Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
+             // ^^^^^^^^^^^^ 💡 warn: Variant `SOME_VARIANT` should have CamelCase name, e.g. `SomeVariant`
 "#,
         );
     }
@@ -261,7 +267,7 @@ enum SomeEnum { SOME_VARIANT(u8) }
         check_diagnostics(
             r#"
 const some_weird_const: u8 = 10;
-   // ^^^^^^^^^^^^^^^^ 💡 weak: Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
+   // ^^^^^^^^^^^^^^^^ 💡 warn: Constant `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
 "#,
         );
     }
@@ -271,7 +277,7 @@ const some_weird_const: u8 = 10;
         check_diagnostics(
             r#"
 static some_weird_const: u8 = 10;
-    // ^^^^^^^^^^^^^^^^ 💡 weak: Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
+    // ^^^^^^^^^^^^^^^^ 💡 warn: Static variable `some_weird_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_WEIRD_CONST`
 "#,
         );
     }
@@ -281,13 +287,13 @@ static some_weird_const: u8 = 10;
         check_diagnostics(
             r#"
 struct someStruct;
-    // ^^^^^^^^^^ 💡 weak: Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
+    // ^^^^^^^^^^ 💡 warn: Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
 
 impl someStruct {
     fn SomeFunc(&self) {
-    // ^^^^^^^^ 💡 weak: Function `SomeFunc` should have snake_case name, e.g. `some_func`
+    // ^^^^^^^^ 💡 warn: Function `SomeFunc` should have snake_case name, e.g. `some_func`
         let WHY_VAR_IS_CAPS = 10;
-         // ^^^^^^^^^^^^^^^ 💡 weak: Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
+         // ^^^^^^^^^^^^^^^ 💡 warn: Variable `WHY_VAR_IS_CAPS` should have snake_case name, e.g. `why_var_is_caps`
     }
 }
 "#,
@@ -319,7 +325,7 @@ enum Option { Some, None }
 fn main() {
     match Option::None {
         SOME_VAR @ None => (),
-     // ^^^^^^^^ 💡 weak: Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
+     // ^^^^^^^^ 💡 warn: Variable `SOME_VAR` should have snake_case name, e.g. `some_var`
         Some => (),
     }
 }
@@ -461,7 +467,7 @@ mod CheckNonstandardStyle {
 
 #[allow(bad_style)]
 mod CheckBadStyle {
-    fn HiImABadFnName() {}
+    struct fooo;
 }
 
 mod F {
@@ -483,4 +489,60 @@ pub static SomeStatic: u8 = 10;
     "#,
         );
     }
+
+    #[test]
+    fn deny_attributes() {
+        check_diagnostics(
+            r#"
+#[deny(non_snake_case)]
+fn NonSnakeCaseName(some_var: u8) -> u8 {
+ //^^^^^^^^^^^^^^^^ 💡 error: Function `NonSnakeCaseName` should have snake_case name, e.g. `non_snake_case_name`
+    // cov_flags generated output from elsewhere in this file
+    extern "C" {
+        #[no_mangle]
+        static lower_case: u8;
+    }
+
+    let OtherVar = some_var + 1;
+      //^^^^^^^^ 💡 error: Variable `OtherVar` should have snake_case name, e.g. `other_var`
+    OtherVar
+}
+
+#[deny(nonstandard_style)]
+mod CheckNonstandardStyle {
+    fn HiImABadFnName() {}
+     //^^^^^^^^^^^^^^ 💡 error: Function `HiImABadFnName` should have snake_case name, e.g. `hi_im_abad_fn_name`
+}
+
+#[deny(warnings)]
+mod CheckBadStyle {
+    struct fooo;
+         //^^^^ 💡 error: Structure `fooo` should have CamelCase name, e.g. `Fooo`
+}
+
+mod F {
+    #![deny(non_snake_case)]
+    fn CheckItWorksWithModAttr() {}
+     //^^^^^^^^^^^^^^^^^^^^^^^ 💡 error: Function `CheckItWorksWithModAttr` should have snake_case name, e.g. `check_it_works_with_mod_attr`
+}
+
+#[deny(non_snake_case, non_camel_case_types)]
+pub struct some_type {
+         //^^^^^^^^^ 💡 error: Structure `some_type` should have CamelCase name, e.g. `SomeType`
+    SOME_FIELD: u8,
+  //^^^^^^^^^^ 💡 error: Field `SOME_FIELD` should have snake_case name, e.g. `some_field`
+    SomeField: u16,
+  //^^^^^^^^^  💡 error: Field `SomeField` should have snake_case name, e.g. `some_field`
+}
+
+#[deny(non_upper_case_globals)]
+pub const some_const: u8 = 10;
+        //^^^^^^^^^^ 💡 error: Constant `some_const` should have UPPER_SNAKE_CASE name, e.g. `SOME_CONST`
+
+#[deny(non_upper_case_globals)]
+pub static SomeStatic: u8 = 10;
+         //^^^^^^^^^^ 💡 error: Static variable `SomeStatic` should have UPPER_SNAKE_CASE name, e.g. `SOME_STATIC`
+    "#,
+        );
+    }
 }
diff --git a/crates/ide-diagnostics/src/handlers/invalid_derive_target.rs b/crates/ide-diagnostics/src/handlers/invalid_derive_target.rs
index c779266bc97..1ec17952b23 100644
--- a/crates/ide-diagnostics/src/handlers/invalid_derive_target.rs
+++ b/crates/ide-diagnostics/src/handlers/invalid_derive_target.rs
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext, Severity};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: invalid-derive-target
 //
@@ -11,11 +11,10 @@ pub(crate) fn invalid_derive_target(
     let display_range = ctx.sema.diagnostics_display_range(d.node.clone()).range;
 
     Diagnostic::new(
-        "invalid-derive-target",
+        DiagnosticCode::RustcHardError("E0774"),
         "`derive` may only be applied to `struct`s, `enum`s and `union`s",
         display_range,
     )
-    .severity(Severity::Error)
 }
 
 #[cfg(test)]
diff --git a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
index 04ce1e0feee..a337e2660d6 100644
--- a/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
+++ b/crates/ide-diagnostics/src/handlers/json_is_not_rust.rs
@@ -17,7 +17,7 @@ use syntax::{
 };
 use text_edit::TextEdit;
 
-use crate::{fix, Diagnostic, DiagnosticsConfig, Severity};
+use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsConfig, Severity};
 
 #[derive(Default)]
 struct State {
@@ -117,11 +117,10 @@ pub(crate) fn json_in_items(
                 edit.insert(range.start(), state.result);
                 acc.push(
                     Diagnostic::new(
-                        "json-is-not-rust",
+                        DiagnosticCode::Ra("json-is-not-rust", Severity::WeakWarning),
                         "JSON syntax is not valid as a Rust item",
                         range,
                     )
-                    .severity(Severity::WeakWarning)
                     .with_fixes(Some(vec![{
                         let mut scb = SourceChangeBuilder::new(file_id);
                         let scope = match import_scope {
diff --git a/crates/ide-diagnostics/src/handlers/macro_error.rs b/crates/ide-diagnostics/src/handlers/macro_error.rs
index 7547779a95c..937e2f96642 100644
--- a/crates/ide-diagnostics/src/handlers/macro_error.rs
+++ b/crates/ide-diagnostics/src/handlers/macro_error.rs
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
 
 // Diagnostic: macro-error
 //
@@ -6,7 +6,12 @@ use crate::{Diagnostic, DiagnosticsContext};
 pub(crate) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic {
     // Use more accurate position if available.
     let display_range = ctx.resolve_precise_location(&d.node, d.precise_location);
-    Diagnostic::new("macro-error", d.message.clone(), display_range).experimental()
+    Diagnostic::new(
+        DiagnosticCode::Ra("macro-error", Severity::Error),
+        d.message.clone(),
+        display_range,
+    )
+    .experimental()
 }
 
 // Diagnostic: macro-error
@@ -16,7 +21,12 @@ pub(crate) fn macro_def_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroDefErr
     // Use more accurate position if available.
     let display_range =
         ctx.resolve_precise_location(&d.node.clone().map(|it| it.syntax_node_ptr()), d.name);
-    Diagnostic::new("macro-def-error", d.message.clone(), display_range).experimental()
+    Diagnostic::new(
+        DiagnosticCode::Ra("macro-def-error", Severity::Error),
+        d.message.clone(),
+        display_range,
+    )
+    .experimental()
 }
 
 #[cfg(test)]
diff --git a/crates/ide-diagnostics/src/handlers/malformed_derive.rs b/crates/ide-diagnostics/src/handlers/malformed_derive.rs
index cd48bdba07e..fc57dde69f2 100644
--- a/crates/ide-diagnostics/src/handlers/malformed_derive.rs
+++ b/crates/ide-diagnostics/src/handlers/malformed_derive.rs
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext, Severity};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: malformed-derive
 //
@@ -10,11 +10,10 @@ pub(crate) fn malformed_derive(
     let display_range = ctx.sema.diagnostics_display_range(d.node.clone()).range;
 
     Diagnostic::new(
-        "malformed-derive",
+        DiagnosticCode::RustcHardError("E0777"),
         "malformed derive input, derive attributes are of the form `#[derive(Derive1, Derive2, ...)]`",
         display_range,
     )
-    .severity(Severity::Error)
 }
 
 #[cfg(test)]
diff --git a/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs b/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs
index c5db8c3741b..6238c7e09e9 100644
--- a/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs
+++ b/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs
@@ -3,7 +3,7 @@ use syntax::{
     AstNode, TextRange,
 };
 
-use crate::{adjusted_display_range, Diagnostic, DiagnosticsContext};
+use crate::{adjusted_display_range, Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: mismatched-arg-count
 //
@@ -14,7 +14,7 @@ pub(crate) fn mismatched_arg_count(
 ) -> Diagnostic {
     let s = if d.expected == 1 { "" } else { "s" };
     let message = format!("expected {} argument{s}, found {}", d.expected, d.found);
-    Diagnostic::new("mismatched-arg-count", message, invalid_args_range(ctx, d))
+    Diagnostic::new(DiagnosticCode::RustcHardError("E0107"), message, invalid_args_range(ctx, d))
 }
 
 fn invalid_args_range(ctx: &DiagnosticsContext<'_>, d: &hir::MismatchedArgCount) -> TextRange {
diff --git a/crates/ide-diagnostics/src/handlers/missing_fields.rs b/crates/ide-diagnostics/src/handlers/missing_fields.rs
index 60ccc41df01..bb0e36ff3a1 100644
--- a/crates/ide-diagnostics/src/handlers/missing_fields.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_fields.rs
@@ -15,7 +15,7 @@ use syntax::{
 };
 use text_edit::TextEdit;
 
-use crate::{fix, Diagnostic, DiagnosticsContext};
+use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: missing-fields
 //
@@ -42,7 +42,7 @@ pub(crate) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingField
             .unwrap_or_else(|| d.field_list_parent.clone().either(|it| it.into(), |it| it.into())),
     );
 
-    Diagnostic::new("missing-fields", message, ctx.sema.diagnostics_display_range(ptr).range)
+    Diagnostic::new_with_syntax_node_ptr(ctx, DiagnosticCode::RustcHardError("E0063"), message, ptr)
         .with_fixes(fixes(ctx, d))
 }
 
diff --git a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
index 3f13b97a447..82a9a3bd559 100644
--- a/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_match_arms.rs
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: missing-match-arm
 //
@@ -7,10 +7,11 @@ pub(crate) fn missing_match_arms(
     ctx: &DiagnosticsContext<'_>,
     d: &hir::MissingMatchArms,
 ) -> Diagnostic {
-    Diagnostic::new(
-        "missing-match-arm",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0004"),
         format!("missing match arm: {}", d.uncovered_patterns),
-        ctx.sema.diagnostics_display_range(d.scrutinee_expr.clone().map(Into::into)).range,
+        d.scrutinee_expr.clone().map(Into::into),
     )
 }
 
diff --git a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs
index 2026b6fcef5..70b26009bae 100644
--- a/crates/ide-diagnostics/src/handlers/missing_unsafe.rs
+++ b/crates/ide-diagnostics/src/handlers/missing_unsafe.rs
@@ -4,16 +4,17 @@ use syntax::{ast, SyntaxNode};
 use syntax::{match_ast, AstNode};
 use text_edit::TextEdit;
 
-use crate::{fix, Diagnostic, DiagnosticsContext};
+use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: missing-unsafe
 //
 // This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block.
 pub(crate) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Diagnostic {
-    Diagnostic::new(
-        "missing-unsafe",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0133"),
         "this operation is unsafe and requires an unsafe function or block",
-        ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
+        d.expr.clone().map(|it| it.into()),
     )
     .with_fixes(fixes(ctx, d))
 }
diff --git a/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs b/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs
index 32e321107e6..3aa4aa97026 100644
--- a/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs
+++ b/crates/ide-diagnostics/src/handlers/moved_out_of_ref.rs
@@ -1,14 +1,15 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 use hir::HirDisplay;
 
 // Diagnostic: moved-out-of-ref
 //
 // This diagnostic is triggered on moving non copy things out of references.
 pub(crate) fn moved_out_of_ref(ctx: &DiagnosticsContext<'_>, d: &hir::MovedOutOfRef) -> Diagnostic {
-    Diagnostic::new(
-        "moved-out-of-ref",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0507"),
         format!("cannot move `{}` out of reference", d.ty.display(ctx.sema.db)),
-        ctx.sema.diagnostics_display_range(d.span.clone()).range,
+        d.span.clone(),
     )
     .experimental() // spans are broken, and I'm not sure how precise we can detect copy types
 }
diff --git a/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/crates/ide-diagnostics/src/handlers/mutability_errors.rs
index c52d8042c75..0a004c0647d 100644
--- a/crates/ide-diagnostics/src/handlers/mutability_errors.rs
+++ b/crates/ide-diagnostics/src/handlers/mutability_errors.rs
@@ -2,7 +2,7 @@ use ide_db::source_change::SourceChange;
 use syntax::{AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T};
 use text_edit::TextEdit;
 
-use crate::{fix, Diagnostic, DiagnosticsContext, Severity};
+use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: need-mut
 //
@@ -29,13 +29,15 @@ pub(crate) fn need_mut(ctx: &DiagnosticsContext<'_>, d: &hir::NeedMut) -> Diagno
             use_range,
         )])
     })();
-    Diagnostic::new(
-        "need-mut",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        // FIXME: `E0384` is not the only error that this diagnostic handles
+        DiagnosticCode::RustcHardError("E0384"),
         format!(
             "cannot mutate immutable variable `{}`",
             d.local.name(ctx.sema.db).display(ctx.sema.db)
         ),
-        ctx.sema.diagnostics_display_range(d.span.clone()).range,
+        d.span.clone(),
     )
     .with_fixes(fixes)
 }
@@ -68,12 +70,12 @@ pub(crate) fn unused_mut(ctx: &DiagnosticsContext<'_>, d: &hir::UnusedMut) -> Di
         )])
     })();
     let ast = d.local.primary_source(ctx.sema.db).syntax_ptr();
-    Diagnostic::new(
-        "unused-mut",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcLint("unused_mut"),
         "variable does not need to be mutable",
-        ctx.sema.diagnostics_display_range(ast).range,
+        ast,
     )
-    .severity(Severity::WeakWarning)
     .experimental() // Not supporting `#[allow(unused_mut)]` leads to false positive.
     .with_fixes(fixes)
 }
@@ -93,7 +95,7 @@ mod tests {
 fn f(_: i32) {}
 fn main() {
     let mut x = 2;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     f(x);
 }
 "#,
@@ -268,7 +270,7 @@ fn main() {
 fn f(_: i32) {}
 fn main() {
     let mut x = (2, 7);
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     f(x.1);
 }
 "#,
@@ -302,7 +304,7 @@ fn main() {
             r#"
 fn main() {
     let mut x = &mut 2;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     *x = 5;
 }
 "#,
@@ -346,7 +348,7 @@ fn main() {
             r#"
             //- minicore: copy, builtin_impls
             fn clone(mut i: &!) -> ! {
-                   //^^^^^ 💡 weak: variable does not need to be mutable
+                   //^^^^^ 💡 warn: variable does not need to be mutable
                 *i
             }
         "#,
@@ -360,7 +362,7 @@ fn main() {
 //- minicore: option
 fn main() {
     let mut v = &mut Some(2);
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     let _ = || match v {
         Some(k) => {
             *k = 5;
@@ -386,7 +388,7 @@ fn main() {
 fn main() {
     match (2, 3) {
         (x, mut y) => {
-          //^^^^^ 💡 weak: variable does not need to be mutable
+          //^^^^^ 💡 warn: variable does not need to be mutable
             x = 7;
           //^^^^^ 💡 error: cannot mutate immutable variable `x`
         }
@@ -407,7 +409,7 @@ fn main() {
 fn main() {
     return;
     let mut x = 2;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     &mut x;
 }
 "#,
@@ -417,7 +419,7 @@ fn main() {
 fn main() {
     loop {}
     let mut x = 2;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     &mut x;
 }
 "#,
@@ -438,7 +440,7 @@ fn main(b: bool) {
         g();
     }
     let mut x = 2;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     &mut x;
 }
 "#,
@@ -452,7 +454,7 @@ fn main(b: bool) {
         return;
     }
     let mut x = 2;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     &mut x;
 }
 "#,
@@ -466,7 +468,7 @@ fn main(b: bool) {
 fn f(_: i32) {}
 fn main() {
     let mut x;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     x = 5;
     f(x);
 }
@@ -477,7 +479,7 @@ fn main() {
 fn f(_: i32) {}
 fn main(b: bool) {
     let mut x;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     if b {
         x = 1;
     } else {
@@ -552,15 +554,15 @@ fn f(_: i32) {}
 fn main() {
     loop {
         let mut x = 1;
-          //^^^^^ 💡 weak: variable does not need to be mutable
+          //^^^^^ 💡 warn: variable does not need to be mutable
         f(x);
         if let mut y = 2 {
-             //^^^^^ 💡 weak: variable does not need to be mutable
+             //^^^^^ 💡 warn: variable does not need to be mutable
             f(y);
         }
         match 3 {
             mut z => f(z),
-          //^^^^^ 💡 weak: variable does not need to be mutable
+          //^^^^^ 💡 warn: variable does not need to be mutable
         }
     }
 }
@@ -577,9 +579,9 @@ fn main() {
     loop {
         let c @ (
             mut b,
-          //^^^^^ 💡 weak: variable does not need to be mutable
+          //^^^^^ 💡 warn: variable does not need to be mutable
             mut d
-          //^^^^^ 💡 weak: variable does not need to be mutable
+          //^^^^^ 💡 warn: variable does not need to be mutable
         );
         a = 1;
       //^^^^^ 💡 error: cannot mutate immutable variable `a`
@@ -597,7 +599,7 @@ fn main() {
         check_diagnostics(
             r#"
 fn f(mut x: i32) {
-   //^^^^^ 💡 weak: variable does not need to be mutable
+   //^^^^^ 💡 warn: variable does not need to be mutable
 }
 "#,
         );
@@ -640,7 +642,7 @@ fn f() {
 //- minicore: iterators, copy
 fn f(x: [(i32, u8); 10]) {
     for (a, mut b) in x {
-          //^^^^^ 💡 weak: variable does not need to be mutable
+          //^^^^^ 💡 warn: variable does not need to be mutable
         a = 2;
       //^^^^^ 💡 error: cannot mutate immutable variable `a`
     }
@@ -657,9 +659,9 @@ fn f(x: [(i32, u8); 10]) {
 fn f(x: [(i32, u8); 10]) {
     let mut it = x.into_iter();
     while let Some((a, mut b)) = it.next() {
-                     //^^^^^ 💡 weak: variable does not need to be mutable
+                     //^^^^^ 💡 warn: variable does not need to be mutable
         while let Some((c, mut d)) = it.next() {
-                         //^^^^^ 💡 weak: variable does not need to be mutable
+                         //^^^^^ 💡 warn: variable does not need to be mutable
             a = 2;
           //^^^^^ 💡 error: cannot mutate immutable variable `a`
             c = 2;
@@ -683,7 +685,7 @@ fn f() {
     let x = &mut x;
           //^^^^^^ 💡 error: cannot mutate immutable variable `x`
     let mut x = x;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     x[2] = 5;
 }
 "#,
@@ -711,13 +713,13 @@ impl IndexMut<usize> for Foo {
 }
 fn f() {
     let mut x = Foo;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     let y = &x[2];
     let x = Foo;
     let y = &mut x[2];
                //^💡 error: cannot mutate immutable variable `x`
     let mut x = &mut Foo;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     let y: &mut (i32, u8) = &mut x[2];
     let x = Foo;
     let ref mut y = x[7];
@@ -731,7 +733,7 @@ fn f() {
     }
     let mut x = Foo;
     let mut i = 5;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     let y = &mut x[i];
 }
 "#,
@@ -759,7 +761,7 @@ impl DerefMut for Foo {
 }
 fn f() {
     let mut x = Foo;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     let y = &*x;
     let x = Foo;
     let y = &mut *x;
@@ -790,7 +792,7 @@ fn f() {
 fn f(_: i32) {}
 fn main() {
     let ((Some(mut x), None) | (_, Some(mut x))) = (None, Some(7));
-             //^^^^^ 💡 weak: variable does not need to be mutable
+             //^^^^^ 💡 warn: variable does not need to be mutable
     f(x);
 }
 "#,
@@ -842,7 +844,7 @@ pub struct TreeLeaf {
 
 pub fn test() {
     let mut tree = Tree::Leaf(
-      //^^^^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^^^^ 💡 warn: variable does not need to be mutable
         TreeLeaf {
             depth: 0,
             data: 0
@@ -859,7 +861,7 @@ pub fn test() {
             r#"
 //- minicore: fn
 fn fn_ref(mut x: impl Fn(u8) -> u8) -> u8 {
-        //^^^^^ 💡 weak: variable does not need to be mutable
+        //^^^^^ 💡 warn: variable does not need to be mutable
     x(2)
 }
 fn fn_mut(x: impl FnMut(u8) -> u8) -> u8 {
@@ -867,11 +869,11 @@ fn fn_mut(x: impl FnMut(u8) -> u8) -> u8 {
   //^ 💡 error: cannot mutate immutable variable `x`
 }
 fn fn_borrow_mut(mut x: &mut impl FnMut(u8) -> u8) -> u8 {
-               //^^^^^ 💡 weak: variable does not need to be mutable
+               //^^^^^ 💡 warn: variable does not need to be mutable
     x(2)
 }
 fn fn_once(mut x: impl FnOnce(u8) -> u8) -> u8 {
-         //^^^^^ 💡 weak: variable does not need to be mutable
+         //^^^^^ 💡 warn: variable does not need to be mutable
     x(2)
 }
 "#,
@@ -915,14 +917,14 @@ fn fn_once(mut x: impl FnOnce(u8) -> u8) -> u8 {
         //- minicore: copy, fn
         fn f() {
             let mut x = 5;
-              //^^^^^ 💡 weak: variable does not need to be mutable
+              //^^^^^ 💡 warn: variable does not need to be mutable
             let mut y = 2;
             y = 7;
             let closure = || {
                 let mut z = 8;
                 z = 3;
                 let mut k = z;
-                  //^^^^^ 💡 weak: variable does not need to be mutable
+                  //^^^^^ 💡 warn: variable does not need to be mutable
             };
         }
                     "#,
@@ -949,7 +951,7 @@ fn f() {
 fn f() {
     struct X;
     let mut x = X;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     let c1 = || x;
     let mut x = X;
     let c2 = || { x = X; x };
@@ -965,12 +967,12 @@ fn f() {
 
         fn f() {
             let mut x = &mut 5;
-              //^^^^^ 💡 weak: variable does not need to be mutable
+              //^^^^^ 💡 warn: variable does not need to be mutable
             let closure1 = || { *x = 2; };
             let _ = closure1();
                   //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
             let mut x = &mut 5;
-              //^^^^^ 💡 weak: variable does not need to be mutable
+              //^^^^^ 💡 warn: variable does not need to be mutable
             let closure1 = || { *x = 2; &x; };
             let _ = closure1();
                   //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
@@ -979,12 +981,12 @@ fn f() {
             let _ = closure1();
                   //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
             let mut x = &mut 5;
-              //^^^^^ 💡 weak: variable does not need to be mutable
+              //^^^^^ 💡 warn: variable does not need to be mutable
             let closure1 = move || { *x = 2; };
             let _ = closure1();
                   //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
             let mut x = &mut X(1, 2);
-              //^^^^^ 💡 weak: variable does not need to be mutable
+              //^^^^^ 💡 warn: variable does not need to be mutable
             let closure1 = || { x.0 = 2; };
             let _ = closure1();
                   //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
@@ -1001,7 +1003,7 @@ fn f() {
 fn x(t: &[u8]) {
     match t {
         &[a, mut b] | &[a, _, mut b] => {
-           //^^^^^ 💡 weak: variable does not need to be mutable
+           //^^^^^ 💡 warn: variable does not need to be mutable
 
             a = 2;
           //^^^^^ 💡 error: cannot mutate immutable variable `a`
@@ -1055,7 +1057,7 @@ fn f() {
     *x = 7;
   //^^^^^^ 💡 error: cannot mutate immutable variable `x`
     let mut y = Box::new(5);
-      //^^^^^ 💡 weak: variable does not need to be mutable
+      //^^^^^ 💡 warn: variable does not need to be mutable
     *x = *y;
   //^^^^^^^ 💡 error: cannot mutate immutable variable `x`
     let x = Box::new(5);
@@ -1080,19 +1082,40 @@ fn main() {
     }
 
     #[test]
-    fn respect_allow_unused_mut() {
-        // FIXME: respect
+    fn respect_lint_attributes_for_unused_mut() {
         check_diagnostics(
             r#"
 fn f(_: i32) {}
 fn main() {
     #[allow(unused_mut)]
     let mut x = 2;
-      //^^^^^ 💡 weak: variable does not need to be mutable
+    f(x);
+}
+
+fn main2() {
+    #[deny(unused_mut)]
+    let mut x = 2;
+      //^^^^^ 💡 error: variable does not need to be mutable
     f(x);
 }
 "#,
         );
+        check_diagnostics(
+            r#"
+macro_rules! mac {
+    ($($x:expr),*$(,)*) => ({
+        #[allow(unused_mut)]
+        let mut vec = 2;
+        vec
+    });
+}
+
+fn main2() {
+    let mut x = mac![];
+      //^^^^^ 💡 warn: variable does not need to be mutable
+}
+        "#,
+        );
     }
 
     #[test]
diff --git a/crates/ide-diagnostics/src/handlers/no_such_field.rs b/crates/ide-diagnostics/src/handlers/no_such_field.rs
index a39eceab243..a34a5824f29 100644
--- a/crates/ide-diagnostics/src/handlers/no_such_field.rs
+++ b/crates/ide-diagnostics/src/handlers/no_such_field.rs
@@ -6,16 +6,17 @@ use syntax::{
 };
 use text_edit::TextEdit;
 
-use crate::{fix, Assist, Diagnostic, DiagnosticsContext};
+use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: no-such-field
 //
 // This diagnostic is triggered if created structure does not have field provided in record.
 pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic {
-    Diagnostic::new(
-        "no-such-field",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0559"),
         "no such field",
-        ctx.sema.diagnostics_display_range(d.field.clone().map(|it| it.into())).range,
+        d.field.clone().map(|it| it.into()),
     )
     .with_fixes(fixes(ctx, d))
 }
diff --git a/crates/ide-diagnostics/src/handlers/private_assoc_item.rs b/crates/ide-diagnostics/src/handlers/private_assoc_item.rs
index 4cd85a479a0..c44d28e77f6 100644
--- a/crates/ide-diagnostics/src/handlers/private_assoc_item.rs
+++ b/crates/ide-diagnostics/src/handlers/private_assoc_item.rs
@@ -1,6 +1,6 @@
 use either::Either;
 
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: private-assoc-item
 //
@@ -16,8 +16,9 @@ pub(crate) fn private_assoc_item(
         .name(ctx.sema.db)
         .map(|name| format!("`{}` ", name.display(ctx.sema.db)))
         .unwrap_or_default();
-    Diagnostic::new(
-        "private-assoc-item",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0624"),
         format!(
             "{} {}is private",
             match d.item {
@@ -27,15 +28,13 @@ pub(crate) fn private_assoc_item(
             },
             name,
         ),
-        ctx.sema
-            .diagnostics_display_range(d.expr_or_pat.clone().map(|it| match it {
+        d.expr_or_pat.clone().map(|it| match it {
+            Either::Left(it) => it.into(),
+            Either::Right(it) => match it {
                 Either::Left(it) => it.into(),
-                Either::Right(it) => match it {
-                    Either::Left(it) => it.into(),
-                    Either::Right(it) => it.into(),
-                },
-            }))
-            .range,
+                Either::Right(it) => it.into(),
+            },
+        }),
     )
 }
 
diff --git a/crates/ide-diagnostics/src/handlers/private_field.rs b/crates/ide-diagnostics/src/handlers/private_field.rs
index de7f51f693c..553defcf990 100644
--- a/crates/ide-diagnostics/src/handlers/private_field.rs
+++ b/crates/ide-diagnostics/src/handlers/private_field.rs
@@ -1,18 +1,19 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: private-field
 //
 // This diagnostic is triggered if the accessed field is not visible from the current module.
 pub(crate) fn private_field(ctx: &DiagnosticsContext<'_>, d: &hir::PrivateField) -> Diagnostic {
     // FIXME: add quickfix
-    Diagnostic::new(
-        "private-field",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0616"),
         format!(
             "field `{}` of `{}` is private",
             d.field.name(ctx.sema.db).display(ctx.sema.db),
             d.field.parent_def(ctx.sema.db).name(ctx.sema.db).display(ctx.sema.db)
         ),
-        ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
+        d.expr.clone().map(|it| it.into()),
     )
 }
 
diff --git a/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs b/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
index d3eda3c5ebc..083ef3e8dc1 100644
--- a/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
+++ b/crates/ide-diagnostics/src/handlers/replace_filter_map_next_with_find_map.rs
@@ -6,7 +6,7 @@ use syntax::{
 };
 use text_edit::TextEdit;
 
-use crate::{fix, Assist, Diagnostic, DiagnosticsContext, Severity};
+use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: replace-filter-map-next-with-find-map
 //
@@ -15,12 +15,12 @@ pub(crate) fn replace_filter_map_next_with_find_map(
     ctx: &DiagnosticsContext<'_>,
     d: &hir::ReplaceFilterMapNextWithFindMap,
 ) -> Diagnostic {
-    Diagnostic::new(
-        "replace-filter-map-next-with-find-map",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::Clippy("filter_map_next"),
         "replace filter_map(..).next() with find_map(..)",
-        ctx.sema.diagnostics_display_range(InFile::new(d.file, d.next_expr.clone().into())).range,
+        InFile::new(d.file, d.next_expr.clone().into()),
     )
-    .severity(Severity::WeakWarning)
     .with_fixes(fixes(ctx, d))
 }
 
@@ -64,7 +64,7 @@ mod tests {
     pub(crate) fn check_diagnostics(ra_fixture: &str) {
         let mut config = DiagnosticsConfig::test_sample();
         config.disabled.insert("inactive-code".to_string());
-        config.disabled.insert("unresolved-method".to_string());
+        config.disabled.insert("E0599".to_string());
         check_diagnostics_with_config(config, ra_fixture)
     }
 
@@ -139,4 +139,33 @@ fn foo() {
 "#,
         )
     }
+
+    #[test]
+    fn respect_lint_attributes_for_clippy_equivalent() {
+        check_diagnostics(
+            r#"
+//- minicore: iterators
+
+fn foo() {
+    #[allow(clippy::filter_map_next)]
+    let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
+}
+
+#[deny(clippy::filter_map_next)]
+fn foo() {
+    let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
+}         //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 error: replace filter_map(..).next() with find_map(..)
+
+fn foo() {
+    let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
+}         //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 weak: replace filter_map(..).next() with find_map(..)
+
+#[warn(clippy::filter_map_next)]
+fn foo() {
+    let m = core::iter::repeat(()).filter_map(|()| Some(92)).next();
+}         //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 💡 warn: replace filter_map(..).next() with find_map(..)
+
+"#,
+        );
+    }
 }
diff --git a/crates/ide-diagnostics/src/handlers/type_mismatch.rs b/crates/ide-diagnostics/src/handlers/type_mismatch.rs
index c28f98d8333..15bd28c00df 100644
--- a/crates/ide-diagnostics/src/handlers/type_mismatch.rs
+++ b/crates/ide-diagnostics/src/handlers/type_mismatch.rs
@@ -7,7 +7,7 @@ use syntax::{
 };
 use text_edit::TextEdit;
 
-use crate::{adjusted_display_range, fix, Assist, Diagnostic, DiagnosticsContext};
+use crate::{adjusted_display_range, fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: type-mismatch
 //
@@ -39,7 +39,7 @@ pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch)
         }
     };
     let mut diag = Diagnostic::new(
-        "type-mismatch",
+        DiagnosticCode::RustcHardError("E0308"),
         format!(
             "expected {}, found {}",
             d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId),
diff --git a/crates/ide-diagnostics/src/handlers/typed_hole.rs b/crates/ide-diagnostics/src/handlers/typed_hole.rs
index e12bbcf6820..4af67227115 100644
--- a/crates/ide-diagnostics/src/handlers/typed_hole.rs
+++ b/crates/ide-diagnostics/src/handlers/typed_hole.rs
@@ -7,7 +7,7 @@ use ide_db::{
 use syntax::AstNode;
 use text_edit::TextEdit;
 
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: typed-hole
 //
@@ -26,7 +26,8 @@ pub(crate) fn typed_hole(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Di
         )
     };
 
-    Diagnostic::new("typed-hole", message, display_range.range).with_fixes(fixes)
+    Diagnostic::new(DiagnosticCode::RustcHardError("typed-hole"), message, display_range.range)
+        .with_fixes(fixes)
 }
 
 fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option<Vec<Assist>> {
diff --git a/crates/ide-diagnostics/src/handlers/undeclared_label.rs b/crates/ide-diagnostics/src/handlers/undeclared_label.rs
index 034e4fcfb85..7de9a9a323e 100644
--- a/crates/ide-diagnostics/src/handlers/undeclared_label.rs
+++ b/crates/ide-diagnostics/src/handlers/undeclared_label.rs
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: undeclared-label
 pub(crate) fn undeclared_label(
@@ -6,10 +6,11 @@ pub(crate) fn undeclared_label(
     d: &hir::UndeclaredLabel,
 ) -> Diagnostic {
     let name = &d.name;
-    Diagnostic::new(
-        "undeclared-label",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("undeclared-label"),
         format!("use of undeclared label `{}`", name.display(ctx.sema.db)),
-        ctx.sema.diagnostics_display_range(d.node.clone().map(|it| it.into())).range,
+        d.node.clone().map(|it| it.into()),
     )
 }
 
diff --git a/crates/ide-diagnostics/src/handlers/unimplemented_builtin_macro.rs b/crates/ide-diagnostics/src/handlers/unimplemented_builtin_macro.rs
index e879de75cd8..bcce72a7d01 100644
--- a/crates/ide-diagnostics/src/handlers/unimplemented_builtin_macro.rs
+++ b/crates/ide-diagnostics/src/handlers/unimplemented_builtin_macro.rs
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext, Severity};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
 
 // Diagnostic: unimplemented-builtin-macro
 //
@@ -7,10 +7,10 @@ pub(crate) fn unimplemented_builtin_macro(
     ctx: &DiagnosticsContext<'_>,
     d: &hir::UnimplementedBuiltinMacro,
 ) -> Diagnostic {
-    Diagnostic::new(
-        "unimplemented-builtin-macro",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::Ra("unimplemented-builtin-macro", Severity::WeakWarning),
         "unimplemented built-in macro".to_string(),
-        ctx.sema.diagnostics_display_range(d.node.clone()).range,
+        d.node.clone(),
     )
-    .severity(Severity::WeakWarning)
 }
diff --git a/crates/ide-diagnostics/src/handlers/unlinked_file.rs b/crates/ide-diagnostics/src/handlers/unlinked_file.rs
index 7e0d9532819..e04f27c27fd 100644
--- a/crates/ide-diagnostics/src/handlers/unlinked_file.rs
+++ b/crates/ide-diagnostics/src/handlers/unlinked_file.rs
@@ -14,7 +14,7 @@ use syntax::{
 };
 use text_edit::TextEdit;
 
-use crate::{fix, Assist, Diagnostic, DiagnosticsContext, Severity};
+use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
 
 // Diagnostic: unlinked-file
 //
@@ -46,8 +46,7 @@ pub(crate) fn unlinked_file(
         .unwrap_or(range);
 
     acc.push(
-        Diagnostic::new("unlinked-file", message, range)
-            .severity(Severity::WeakWarning)
+        Diagnostic::new(DiagnosticCode::Ra("unlinked-file", Severity::WeakWarning), message, range)
             .with_fixes(fixes),
     );
 }
diff --git a/crates/ide-diagnostics/src/handlers/unreachable_label.rs b/crates/ide-diagnostics/src/handlers/unreachable_label.rs
index 9fedadeae03..1c5d6cd0909 100644
--- a/crates/ide-diagnostics/src/handlers/unreachable_label.rs
+++ b/crates/ide-diagnostics/src/handlers/unreachable_label.rs
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: unreachable-label
 pub(crate) fn unreachable_label(
@@ -6,10 +6,11 @@ pub(crate) fn unreachable_label(
     d: &hir::UnreachableLabel,
 ) -> Diagnostic {
     let name = &d.name;
-    Diagnostic::new(
-        "unreachable-label",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0767"),
         format!("use of unreachable label `{}`", name.display(ctx.sema.db)),
-        ctx.sema.diagnostics_display_range(d.node.clone().map(|it| it.into())).range,
+        d.node.clone().map(|it| it.into()),
     )
 }
 
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_extern_crate.rs b/crates/ide-diagnostics/src/handlers/unresolved_extern_crate.rs
index 74e4a69c64b..f8265b63275 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_extern_crate.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_extern_crate.rs
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: unresolved-extern-crate
 //
@@ -7,10 +7,11 @@ pub(crate) fn unresolved_extern_crate(
     ctx: &DiagnosticsContext<'_>,
     d: &hir::UnresolvedExternCrate,
 ) -> Diagnostic {
-    Diagnostic::new(
-        "unresolved-extern-crate",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("unresolved-extern-crate"),
         "unresolved extern crate",
-        ctx.sema.diagnostics_display_range(d.decl.clone().map(|it| it.into())).range,
+        d.decl.clone().map(|it| it.into()),
     )
 }
 
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_field.rs b/crates/ide-diagnostics/src/handlers/unresolved_field.rs
index 5e4efa41fd9..94d49d4d48b 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_field.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_field.rs
@@ -8,7 +8,7 @@ use ide_db::{
 use syntax::{ast, AstNode, AstPtr};
 use text_edit::TextEdit;
 
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: unresolved-field
 //
@@ -22,14 +22,15 @@ pub(crate) fn unresolved_field(
     } else {
         ""
     };
-    Diagnostic::new(
-        "unresolved-field",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0559"),
         format!(
             "no field `{}` on type `{}`{method_suffix}",
             d.name.display(ctx.sema.db),
             d.receiver.display(ctx.sema.db)
         ),
-        ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
+        d.expr.clone().map(|it| it.into()),
     )
     .with_fixes(fixes(ctx, d))
     .experimental()
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_import.rs b/crates/ide-diagnostics/src/handlers/unresolved_import.rs
index e52a88459d5..6b8026c0341 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_import.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_import.rs
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: unresolved-import
 //
@@ -8,10 +8,11 @@ pub(crate) fn unresolved_import(
     ctx: &DiagnosticsContext<'_>,
     d: &hir::UnresolvedImport,
 ) -> Diagnostic {
-    Diagnostic::new(
-        "unresolved-import",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0432"),
         "unresolved import",
-        ctx.sema.diagnostics_display_range(d.decl.clone().map(|it| it.into())).range,
+        d.decl.clone().map(|it| it.into()),
     )
     // This currently results in false positives in the following cases:
     // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs b/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs
index 3943b51ab42..33e7c2e37c3 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_macro_call.rs
@@ -1,4 +1,4 @@
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: unresolved-macro-call
 //
@@ -12,7 +12,7 @@ pub(crate) fn unresolved_macro_call(
     let display_range = ctx.resolve_precise_location(&d.macro_call, d.precise_location);
     let bang = if d.is_bang { "!" } else { "" };
     Diagnostic::new(
-        "unresolved-macro-call",
+        DiagnosticCode::RustcHardError("unresolved-macro-call"),
         format!("unresolved macro `{}{bang}`", d.path.display(ctx.sema.db)),
         display_range,
     )
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_method.rs b/crates/ide-diagnostics/src/handlers/unresolved_method.rs
index 8bbb837e670..ae9f6744c40 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_method.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_method.rs
@@ -8,7 +8,7 @@ use ide_db::{
 use syntax::{ast, AstNode, TextRange};
 use text_edit::TextEdit;
 
-use crate::{Diagnostic, DiagnosticsContext};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: unresolved-method
 //
@@ -22,14 +22,15 @@ pub(crate) fn unresolved_method(
     } else {
         ""
     };
-    Diagnostic::new(
-        "unresolved-method",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0599"),
         format!(
             "no method `{}` on type `{}`{field_suffix}",
             d.name.display(ctx.sema.db),
             d.receiver.display(ctx.sema.db)
         ),
-        ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
+        d.expr.clone().map(|it| it.into()),
     )
     .with_fixes(fixes(ctx, d))
     .experimental()
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_module.rs b/crates/ide-diagnostics/src/handlers/unresolved_module.rs
index 6e3fd3b42b0..be24e50c987 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_module.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_module.rs
@@ -3,7 +3,7 @@ use ide_db::{assists::Assist, base_db::AnchoredPathBuf, source_change::FileSyste
 use itertools::Itertools;
 use syntax::AstNode;
 
-use crate::{fix, Diagnostic, DiagnosticsContext};
+use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
 
 // Diagnostic: unresolved-module
 //
@@ -12,8 +12,9 @@ pub(crate) fn unresolved_module(
     ctx: &DiagnosticsContext<'_>,
     d: &hir::UnresolvedModule,
 ) -> Diagnostic {
-    Diagnostic::new(
-        "unresolved-module",
+    Diagnostic::new_with_syntax_node_ptr(
+        ctx,
+        DiagnosticCode::RustcHardError("E0583"),
         match &*d.candidates {
             [] => "unresolved module".to_string(),
             [candidate] => format!("unresolved module, can't find module file: {candidate}"),
@@ -25,7 +26,7 @@ pub(crate) fn unresolved_module(
                 )
             }
         },
-        ctx.sema.diagnostics_display_range(d.decl.clone().map(|it| it.into())).range,
+        d.decl.clone().map(|it| it.into()),
     )
     .with_fixes(fixes(ctx, d))
 }
@@ -82,8 +83,8 @@ mod baz {}
             expect![[r#"
                 [
                     Diagnostic {
-                        code: DiagnosticCode(
-                            "unresolved-module",
+                        code: RustcHardError(
+                            "E0583",
                         ),
                         message: "unresolved module, can't find module file: foo.rs, or foo/mod.rs",
                         range: 0..8,
@@ -148,6 +149,22 @@ mod baz {}
                                 },
                             ],
                         ),
+                        main_node: Some(
+                            InFile {
+                                file_id: FileId(
+                                    FileId(
+                                        0,
+                                    ),
+                                ),
+                                value: MODULE@0..8
+                                  MOD_KW@0..3 "mod"
+                                  WHITESPACE@3..4 " "
+                                  NAME@4..7
+                                    IDENT@4..7 "foo"
+                                  SEMICOLON@7..8 ";"
+                                ,
+                            },
+                        ),
                     },
                 ]
             "#]],
diff --git a/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs b/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs
index ae5cf135839..015a3d6b2ce 100644
--- a/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs
+++ b/crates/ide-diagnostics/src/handlers/unresolved_proc_macro.rs
@@ -1,6 +1,6 @@
 use hir::db::DefDatabase;
 
-use crate::{Diagnostic, DiagnosticsContext, Severity};
+use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
 
 // Diagnostic: unresolved-proc-macro
 //
@@ -41,5 +41,5 @@ pub(crate) fn unresolved_proc_macro(
     };
     let message = format!("{not_expanded_message}: {message}");
 
-    Diagnostic::new("unresolved-proc-macro", message, display_range).severity(severity)
+    Diagnostic::new(DiagnosticCode::Ra("unresolved-proc-macro", severity), message, display_range)
 }
diff --git a/crates/ide-diagnostics/src/handlers/useless_braces.rs b/crates/ide-diagnostics/src/handlers/useless_braces.rs
index 289ed0458c6..11ce811efaa 100644
--- a/crates/ide-diagnostics/src/handlers/useless_braces.rs
+++ b/crates/ide-diagnostics/src/handlers/useless_braces.rs
@@ -3,7 +3,7 @@ use itertools::Itertools;
 use syntax::{ast, AstNode, SyntaxNode, TextRange};
 use text_edit::TextEdit;
 
-use crate::{fix, Diagnostic, Severity};
+use crate::{fix, Diagnostic, DiagnosticCode};
 
 // Diagnostic: unnecessary-braces
 //
@@ -32,11 +32,10 @@ pub(crate) fn useless_braces(
 
         acc.push(
             Diagnostic::new(
-                "unnecessary-braces",
+                DiagnosticCode::RustcLint("unused_braces"),
                 "Unnecessary braces in use statement".to_string(),
                 use_range,
             )
-            .severity(Severity::WeakWarning)
             .with_fixes(Some(vec![fix(
                 "remove_braces",
                 "Remove unnecessary braces",
diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs
index 3d6cfddb3b6..b1b9b4b8ea5 100644
--- a/crates/ide-diagnostics/src/lib.rs
+++ b/crates/ide-diagnostics/src/lib.rs
@@ -67,24 +67,61 @@ mod handlers {
 #[cfg(test)]
 mod tests;
 
+use std::collections::HashMap;
+
 use hir::{diagnostics::AnyDiagnostic, InFile, Semantics};
 use ide_db::{
     assists::{Assist, AssistId, AssistKind, AssistResolveStrategy},
     base_db::{FileId, FileRange, SourceDatabase},
+    generated::lints::{LintGroup, CLIPPY_LINT_GROUPS, DEFAULT_LINT_GROUPS},
     imports::insert_use::InsertUseConfig,
     label::Label,
     source_change::SourceChange,
-    FxHashSet, RootDatabase,
+    syntax_helpers::node_ext::parse_tt_as_comma_sep_paths,
+    FxHashMap, FxHashSet, RootDatabase,
+};
+use once_cell::sync::Lazy;
+use stdx::never;
+use syntax::{
+    algo::find_node_at_range,
+    ast::{self, AstNode},
+    SyntaxNode, SyntaxNodePtr, TextRange,
 };
-use syntax::{algo::find_node_at_range, ast::AstNode, SyntaxNodePtr, TextRange};
 
 // FIXME: Make this an enum
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub struct DiagnosticCode(pub &'static str);
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum DiagnosticCode {
+    RustcHardError(&'static str),
+    RustcLint(&'static str),
+    Clippy(&'static str),
+    Ra(&'static str, Severity),
+}
 
 impl DiagnosticCode {
-    pub fn as_str(&self) -> &str {
-        self.0
+    pub fn url(&self) -> String {
+        match self {
+            DiagnosticCode::RustcHardError(e) => {
+                format!("https://doc.rust-lang.org/stable/error_codes/{e}.html")
+            }
+            DiagnosticCode::RustcLint(e) => {
+                format!("https://doc.rust-lang.org/rustc/?search={e}")
+            }
+            DiagnosticCode::Clippy(e) => {
+                format!("https://rust-lang.github.io/rust-clippy/master/#/{e}")
+            }
+            DiagnosticCode::Ra(e, _) => {
+                format!("https://rust-analyzer.github.io/manual.html#{e}")
+            }
+        }
+    }
+
+    pub fn as_str(&self) -> &'static str {
+        match self {
+            DiagnosticCode::RustcHardError(r)
+            | DiagnosticCode::RustcLint(r)
+            | DiagnosticCode::Clippy(r)
+            | DiagnosticCode::Ra(r, _) => r,
+        }
     }
 }
 
@@ -97,29 +134,51 @@ pub struct Diagnostic {
     pub unused: bool,
     pub experimental: bool,
     pub fixes: Option<Vec<Assist>>,
+    // The node that will be affected by `#[allow]` and similar attributes.
+    pub main_node: Option<InFile<SyntaxNode>>,
 }
 
 impl Diagnostic {
-    fn new(code: &'static str, message: impl Into<String>, range: TextRange) -> Diagnostic {
+    fn new(code: DiagnosticCode, message: impl Into<String>, range: TextRange) -> Diagnostic {
         let message = message.into();
         Diagnostic {
-            code: DiagnosticCode(code),
+            code,
             message,
             range,
-            severity: Severity::Error,
+            severity: match code {
+                DiagnosticCode::RustcHardError(_) => Severity::Error,
+                // FIXME: Rustc lints are not always warning, but the ones that are currently implemented are all warnings.
+                DiagnosticCode::RustcLint(_) => Severity::Warning,
+                // FIXME: We can make this configurable, and if the user uses `cargo clippy` on flycheck, we can
+                // make it normal warning.
+                DiagnosticCode::Clippy(_) => Severity::WeakWarning,
+                DiagnosticCode::Ra(_, s) => s,
+            },
             unused: false,
             experimental: false,
             fixes: None,
+            main_node: None,
         }
     }
 
+    fn new_with_syntax_node_ptr(
+        ctx: &DiagnosticsContext<'_>,
+        code: DiagnosticCode,
+        message: impl Into<String>,
+        node: InFile<SyntaxNodePtr>,
+    ) -> Diagnostic {
+        let file_id = node.file_id;
+        Diagnostic::new(code, message, ctx.sema.diagnostics_display_range(node.clone()).range)
+            .with_main_node(node.map(|x| x.to_node(&ctx.sema.parse_or_expand(file_id))))
+    }
+
     fn experimental(mut self) -> Diagnostic {
         self.experimental = true;
         self
     }
 
-    fn severity(mut self, severity: Severity) -> Diagnostic {
-        self.severity = severity;
+    fn with_main_node(mut self, main_node: InFile<SyntaxNode>) -> Diagnostic {
+        self.main_node = Some(main_node);
         self
     }
 
@@ -134,12 +193,12 @@ impl Diagnostic {
     }
 }
 
-#[derive(Debug, Copy, Clone)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
 pub enum Severity {
     Error,
-    // We don't actually emit this one yet, but we should at some point.
-    // Warning,
+    Warning,
     WeakWarning,
+    Allow,
 }
 
 #[derive(Clone, Debug, PartialEq, Eq)]
@@ -231,11 +290,13 @@ pub fn diagnostics(
     let mut res = Vec::new();
 
     // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
-    res.extend(
-        parse.errors().iter().take(128).map(|err| {
-            Diagnostic::new("syntax-error", format!("Syntax Error: {err}"), err.range())
-        }),
-    );
+    res.extend(parse.errors().iter().take(128).map(|err| {
+        Diagnostic::new(
+            DiagnosticCode::RustcHardError("syntax-error"),
+            format!("Syntax Error: {err}"),
+            err.range(),
+        )
+    }));
 
     let parse = sema.parse(file_id);
 
@@ -274,7 +335,7 @@ pub fn diagnostics(
                 res.extend(d.errors.iter().take(32).map(|err| {
                     {
                         Diagnostic::new(
-                            "syntax-error",
+                            DiagnosticCode::RustcHardError("syntax-error"),
                             format!("Syntax Error in Expansion: {err}"),
                             ctx.resolve_precise_location(&d.node.clone(), d.precise_location),
                         )
@@ -312,14 +373,168 @@ pub fn diagnostics(
         res.push(d)
     }
 
+    let mut diagnostics_of_range =
+        res.iter_mut().filter_map(|x| Some((x.main_node.clone()?, x))).collect::<FxHashMap<_, _>>();
+
+    let mut rustc_stack: FxHashMap<String, Vec<Severity>> = FxHashMap::default();
+    let mut clippy_stack: FxHashMap<String, Vec<Severity>> = FxHashMap::default();
+
+    handle_lint_attributes(
+        &ctx.sema,
+        parse.syntax(),
+        &mut rustc_stack,
+        &mut clippy_stack,
+        &mut diagnostics_of_range,
+    );
+
     res.retain(|d| {
-        !ctx.config.disabled.contains(d.code.as_str())
+        d.severity != Severity::Allow
+            && !ctx.config.disabled.contains(d.code.as_str())
             && !(ctx.config.disable_experimental && d.experimental)
     });
 
     res
 }
 
+// `__RA_EVERY_LINT` is a fake lint group to allow every lint in proc macros
+
+static RUSTC_LINT_GROUPS_DICT: Lazy<HashMap<&str, Vec<&str>>> =
+    Lazy::new(|| build_group_dict(DEFAULT_LINT_GROUPS, &["warnings", "__RA_EVERY_LINT"], ""));
+
+static CLIPPY_LINT_GROUPS_DICT: Lazy<HashMap<&str, Vec<&str>>> =
+    Lazy::new(|| build_group_dict(CLIPPY_LINT_GROUPS, &["__RA_EVERY_LINT"], "clippy::"));
+
+fn build_group_dict(
+    lint_group: &'static [LintGroup],
+    all_groups: &'static [&'static str],
+    prefix: &'static str,
+) -> HashMap<&'static str, Vec<&'static str>> {
+    let mut r: HashMap<&str, Vec<&str>> = HashMap::new();
+    for g in lint_group {
+        for child in g.children {
+            r.entry(child.strip_prefix(prefix).unwrap())
+                .or_default()
+                .push(g.lint.label.strip_prefix(prefix).unwrap());
+        }
+    }
+    for (lint, groups) in r.iter_mut() {
+        groups.push(lint);
+        groups.extend_from_slice(all_groups);
+    }
+    r
+}
+
+fn handle_lint_attributes(
+    sema: &Semantics<'_, RootDatabase>,
+    root: &SyntaxNode,
+    rustc_stack: &mut FxHashMap<String, Vec<Severity>>,
+    clippy_stack: &mut FxHashMap<String, Vec<Severity>>,
+    diagnostics_of_range: &mut FxHashMap<InFile<SyntaxNode>, &mut Diagnostic>,
+) {
+    let file_id = sema.hir_file_for(root);
+    for ev in root.preorder() {
+        match ev {
+            syntax::WalkEvent::Enter(node) => {
+                for attr in node.children().filter_map(ast::Attr::cast) {
+                    parse_lint_attribute(attr, rustc_stack, clippy_stack, |stack, severity| {
+                        stack.push(severity);
+                    });
+                }
+                if let Some(x) =
+                    diagnostics_of_range.get_mut(&InFile { file_id, value: node.clone() })
+                {
+                    const EMPTY_LINTS: &[&str] = &[];
+                    let (names, stack) = match x.code {
+                        DiagnosticCode::RustcLint(name) => (
+                            RUSTC_LINT_GROUPS_DICT.get(name).map_or(EMPTY_LINTS, |x| &**x),
+                            &mut *rustc_stack,
+                        ),
+                        DiagnosticCode::Clippy(name) => (
+                            CLIPPY_LINT_GROUPS_DICT.get(name).map_or(EMPTY_LINTS, |x| &**x),
+                            &mut *clippy_stack,
+                        ),
+                        _ => continue,
+                    };
+                    for &name in names {
+                        if let Some(s) = stack.get(name).and_then(|x| x.last()) {
+                            x.severity = *s;
+                        }
+                    }
+                }
+                if let Some(item) = ast::Item::cast(node.clone()) {
+                    if let Some(me) = sema.expand_attr_macro(&item) {
+                        for stack in [&mut *rustc_stack, &mut *clippy_stack] {
+                            stack
+                                .entry("__RA_EVERY_LINT".to_owned())
+                                .or_default()
+                                .push(Severity::Allow);
+                        }
+                        handle_lint_attributes(
+                            sema,
+                            &me,
+                            rustc_stack,
+                            clippy_stack,
+                            diagnostics_of_range,
+                        );
+                        for stack in [&mut *rustc_stack, &mut *clippy_stack] {
+                            stack.entry("__RA_EVERY_LINT".to_owned()).or_default().pop();
+                        }
+                    }
+                }
+                if let Some(mc) = ast::MacroCall::cast(node) {
+                    if let Some(me) = sema.expand(&mc) {
+                        handle_lint_attributes(
+                            sema,
+                            &me,
+                            rustc_stack,
+                            clippy_stack,
+                            diagnostics_of_range,
+                        );
+                    }
+                }
+            }
+            syntax::WalkEvent::Leave(node) => {
+                for attr in node.children().filter_map(ast::Attr::cast) {
+                    parse_lint_attribute(attr, rustc_stack, clippy_stack, |stack, severity| {
+                        if stack.pop() != Some(severity) {
+                            never!("Mismatched serevity in walking lint attributes");
+                        }
+                    });
+                }
+            }
+        }
+    }
+}
+
+fn parse_lint_attribute(
+    attr: ast::Attr,
+    rustc_stack: &mut FxHashMap<String, Vec<Severity>>,
+    clippy_stack: &mut FxHashMap<String, Vec<Severity>>,
+    job: impl Fn(&mut Vec<Severity>, Severity),
+) {
+    let Some((tag, args_tt)) = attr.as_simple_call() else {
+        return;
+    };
+    let serevity = match tag.as_str() {
+        "allow" => Severity::Allow,
+        "warn" => Severity::Warning,
+        "forbid" | "deny" => Severity::Error,
+        _ => return,
+    };
+    for lint in parse_tt_as_comma_sep_paths(args_tt).into_iter().flatten() {
+        if let Some(lint) = lint.as_single_name_ref() {
+            job(rustc_stack.entry(lint.to_string()).or_default(), serevity);
+        }
+        if let Some(tool) = lint.qualifier().and_then(|x| x.as_single_name_ref()) {
+            if let Some(name_ref) = &lint.segment().and_then(|x| x.name_ref()) {
+                if tool.to_string() == "clippy" {
+                    job(clippy_stack.entry(name_ref.to_string()).or_default(), serevity);
+                }
+            }
+        }
+    }
+}
+
 fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist {
     let mut res = unresolved_fix(id, label, target);
     res.source_change = Some(source_change);
diff --git a/crates/ide-diagnostics/src/tests.rs b/crates/ide-diagnostics/src/tests.rs
index b5cd4e0d689..4ac9d0a9fb7 100644
--- a/crates/ide-diagnostics/src/tests.rs
+++ b/crates/ide-diagnostics/src/tests.rs
@@ -114,6 +114,8 @@ pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixtur
                 annotation.push_str(match d.severity {
                     Severity::Error => "error",
                     Severity::WeakWarning => "weak",
+                    Severity::Warning => "warn",
+                    Severity::Allow => "allow",
                 });
                 annotation.push_str(": ");
                 annotation.push_str(&d.message);
@@ -130,14 +132,19 @@ pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixtur
                 )
             }
         }
-        assert_eq!(expected, actual);
+        if expected != actual {
+            let fneg = expected.iter().filter(|x| !actual.contains(x)).collect::<Vec<_>>();
+            let fpos = actual.iter().filter(|x| !expected.contains(x)).collect::<Vec<_>>();
+
+            panic!("Diagnostic test failed.\nFalse negatives: {fneg:?}\nFalse positives: {fpos:?}");
+        }
     }
 }
 
 #[test]
 fn test_disabled_diagnostics() {
     let mut config = DiagnosticsConfig::test_sample();
-    config.disabled.insert("unresolved-module".into());
+    config.disabled.insert("E0583".into());
 
     let (db, file_id) = RootDatabase::with_single_file(r#"mod foo;"#);
 
@@ -159,7 +166,7 @@ fn minicore_smoke_test() {
         let source = minicore.source_code();
         let mut config = DiagnosticsConfig::test_sample();
         // This should be ignored since we conditionaly remove code which creates single item use with braces
-        config.disabled.insert("unnecessary-braces".to_string());
+        config.disabled.insert("unused_braces".to_string());
         check_diagnostics_with_config(config, &source);
     }
 
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index a6640570fcb..9eecf4957af 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -842,11 +842,7 @@ impl GlobalState {
                                     d.code.as_str().to_string(),
                                 )),
                                 code_description: Some(lsp_types::CodeDescription {
-                                    href: lsp_types::Url::parse(&format!(
-                                        "https://rust-analyzer.github.io/manual.html#{}",
-                                        d.code.as_str()
-                                    ))
-                                    .unwrap(),
+                                    href: lsp_types::Url::parse(&d.code.url()).unwrap(),
                                 }),
                                 source: Some("rust-analyzer".to_string()),
                                 message: d.message,
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 648bc995ad5..ba3421bf9e7 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -94,7 +94,10 @@ pub(crate) fn document_highlight_kind(
 pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity {
     match severity {
         Severity::Error => lsp_types::DiagnosticSeverity::ERROR,
+        Severity::Warning => lsp_types::DiagnosticSeverity::WARNING,
         Severity::WeakWarning => lsp_types::DiagnosticSeverity::HINT,
+        // unreachable
+        Severity::Allow => lsp_types::DiagnosticSeverity::INFORMATION,
     }
 }