Map our diagnostics to rustc and clippy's ones

This commit is contained in:
hkalbasi 2023-06-15 01:47:22 +03:30
parent 9c967d3809
commit e55a1f1916
44 changed files with 628 additions and 251 deletions

1
Cargo.lock generated
View file

@ -748,6 +748,7 @@ dependencies = [
"hir", "hir",
"ide-db", "ide-db",
"itertools", "itertools",
"once_cell",
"profile", "profile",
"serde_json", "serde_json",
"sourcegen", "sourcegen",

View file

@ -5,7 +5,7 @@ mod unsafe_check;
mod decl_check; mod decl_check;
pub use crate::diagnostics::{ pub use crate::diagnostics::{
decl_check::{incorrect_case, IncorrectCase}, decl_check::{incorrect_case, CaseType, IncorrectCase},
expr::{ expr::{
record_literal_missing_fields, record_pattern_missing_fields, BodyValidationDiagnostic, record_literal_missing_fields, record_pattern_missing_fields, BodyValidationDiagnostic,
}, },

View file

@ -57,11 +57,11 @@ pub fn incorrect_case(
#[derive(Debug)] #[derive(Debug)]
pub enum CaseType { pub enum CaseType {
// `some_var` /// `some_var`
LowerSnakeCase, LowerSnakeCase,
// `SOME_CONST` /// `SOME_CONST`
UpperSnakeCase, UpperSnakeCase,
// `SomeStruct` /// `SomeStruct`
UpperCamelCase, UpperCamelCase,
} }

View file

@ -3,7 +3,7 @@
//! //!
//! This probably isn't the best way to do this -- ideally, diagnostics should //! This probably isn't the best way to do this -- ideally, diagnostics should
//! be expressed in terms of hir types themselves. //! 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 base_db::CrateId;
use cfg::{CfgExpr, CfgOptions}; use cfg::{CfgExpr, CfgOptions};

View file

@ -89,11 +89,11 @@ use crate::db::{DefDatabase, HirDatabase};
pub use crate::{ pub use crate::{
attrs::{HasAttrs, Namespace}, attrs::{HasAttrs, Namespace},
diagnostics::{ diagnostics::{
AnyDiagnostic, BreakOutsideOfLoop, ExpectedFunction, InactiveCode, IncoherentImpl, AnyDiagnostic, BreakOutsideOfLoop, CaseType, ExpectedFunction, InactiveCode,
IncorrectCase, InvalidDeriveTarget, MacroDefError, MacroError, MacroExpansionParseError, IncoherentImpl, IncorrectCase, InvalidDeriveTarget, MacroDefError, MacroError,
MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms, MissingUnsafe, MacroExpansionParseError, MalformedDerive, MismatchedArgCount, MissingFields,
MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem, PrivateField, MissingMatchArms, MissingUnsafe, MovedOutOfRef, NeedMut, NoSuchField, PrivateAssocItem,
ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel, PrivateField, ReplaceFilterMapNextWithFindMap, TypeMismatch, TypedHole, UndeclaredLabel,
UnimplementedBuiltinMacro, UnreachableLabel, UnresolvedExternCrate, UnresolvedField, UnimplementedBuiltinMacro, UnreachableLabel, UnresolvedExternCrate, UnresolvedField,
UnresolvedImport, UnresolvedMacroCall, UnresolvedMethodCall, UnresolvedModule, UnresolvedImport, UnresolvedMacroCall, UnresolvedMethodCall, UnresolvedModule,
UnresolvedProcMacro, UnusedMut, UnresolvedProcMacro, UnusedMut,

View file

@ -16,6 +16,7 @@ cov-mark = "2.0.0-pre.1"
either = "1.7.0" either = "1.7.0"
itertools = "0.10.5" itertools = "0.10.5"
serde_json = "1.0.86" serde_json = "1.0.86"
once_cell = "1.17.0"
# local deps # local deps
profile.workspace = true profile.workspace = true

View file

@ -1,4 +1,4 @@
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: break-outside-of-loop // 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" }; let construct = if d.is_break { "break" } else { "continue" };
format!("{construct} outside of loop") format!("{construct} outside of loop")
}; };
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"break-outside-of-loop", ctx,
DiagnosticCode::RustcHardError("E0268"),
message, message,
ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range, d.expr.clone().map(|it| it.into()),
) )
} }

View file

@ -1,6 +1,6 @@
use hir::HirDisplay; use hir::HirDisplay;
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: expected-function // Diagnostic: expected-function
// //
@ -9,10 +9,11 @@ pub(crate) fn expected_function(
ctx: &DiagnosticsContext<'_>, ctx: &DiagnosticsContext<'_>,
d: &hir::ExpectedFunction, d: &hir::ExpectedFunction,
) -> Diagnostic { ) -> Diagnostic {
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"expected-function", ctx,
DiagnosticCode::RustcHardError("E0618"),
format!("expected function, found {}", d.found.display(ctx.sema.db)), 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() .experimental()
} }

View file

@ -5,7 +5,7 @@ use ide_db::{base_db::FileId, source_change::SourceChange};
use syntax::{ast, match_ast, AstNode, SyntaxNode}; use syntax::{ast, match_ast, AstNode, SyntaxNode};
use text_edit::TextEdit; 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) { pub(crate) fn field_shorthand(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) {
match_ast! { match_ast! {
@ -46,8 +46,11 @@ fn check_expr_field_shorthand(
let field_range = record_field.syntax().text_range(); let field_range = record_field.syntax().text_range();
acc.push( acc.push(
Diagnostic::new("use-field-shorthand", "Shorthand struct initialization", field_range) Diagnostic::new(
.severity(Severity::WeakWarning) DiagnosticCode::Clippy("redundant_field_names"),
"Shorthand struct initialization",
field_range,
)
.with_fixes(Some(vec![fix( .with_fixes(Some(vec![fix(
"use_expr_field_shorthand", "use_expr_field_shorthand",
"Use struct shorthand initialization", "Use struct shorthand initialization",
@ -87,8 +90,11 @@ fn check_pat_field_shorthand(
let field_range = record_pat_field.syntax().text_range(); let field_range = record_pat_field.syntax().text_range();
acc.push( acc.push(
Diagnostic::new("use-field-shorthand", "Shorthand struct pattern", field_range) Diagnostic::new(
.severity(Severity::WeakWarning) DiagnosticCode::Clippy("redundant_field_names"),
"Shorthand struct pattern",
field_range,
)
.with_fixes(Some(vec![fix( .with_fixes(Some(vec![fix(
"use_pat_field_shorthand", "use_pat_field_shorthand",
"Use struct field shorthand", "Use struct field shorthand",

View file

@ -1,7 +1,7 @@
use cfg::DnfExpr; use cfg::DnfExpr;
use stdx::format_to; use stdx::format_to;
use crate::{Diagnostic, DiagnosticsContext, Severity}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
// Diagnostic: inactive-code // Diagnostic: inactive-code
// //
@ -27,13 +27,12 @@ pub(crate) fn inactive_code(
format_to!(message, ": {}", inactive); format_to!(message, ": {}", inactive);
} }
} }
// FIXME: This shouldn't be a diagnostic
let res = Diagnostic::new( let res = Diagnostic::new(
"inactive-code", DiagnosticCode::Ra("inactive-code", Severity::WeakWarning),
message, message,
ctx.sema.diagnostics_display_range(d.node.clone()).range, ctx.sema.diagnostics_display_range(d.node.clone()).range,
) )
.severity(Severity::WeakWarning)
.with_unused(true); .with_unused(true);
Some(res) Some(res)
} }

View file

@ -1,17 +1,17 @@
use hir::InFile; use hir::InFile;
use crate::{Diagnostic, DiagnosticsContext, Severity}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: incoherent-impl // Diagnostic: incoherent-impl
// //
// This diagnostic is triggered if the targe type of an impl is from a foreign crate. // 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 { pub(crate) fn incoherent_impl(ctx: &DiagnosticsContext<'_>, d: &hir::IncoherentImpl) -> Diagnostic {
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"incoherent-impl", ctx,
DiagnosticCode::RustcHardError("E0210"),
format!("cannot define inherent `impl` for foreign type"), 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)] #[cfg(test)]

View file

@ -1,4 +1,4 @@
use hir::{db::ExpandDatabase, InFile}; use hir::{db::ExpandDatabase, CaseType, InFile};
use ide_db::{assists::Assist, defs::NameClass}; use ide_db::{assists::Assist, defs::NameClass};
use syntax::AstNode; use syntax::AstNode;
@ -6,23 +6,29 @@ use crate::{
// references::rename::rename_with_semantics, // references::rename::rename_with_semantics,
unresolved_fix, unresolved_fix,
Diagnostic, Diagnostic,
DiagnosticCode,
DiagnosticsContext, DiagnosticsContext,
Severity,
}; };
// Diagnostic: incorrect-ident-case // 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]. // 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 { pub(crate) fn incorrect_case(ctx: &DiagnosticsContext<'_>, d: &hir::IncorrectCase) -> Diagnostic {
Diagnostic::new( let code = match d.expected_case {
"incorrect-ident-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!( format!(
"{} `{}` should have {} name, e.g. `{}`", "{} `{}` should have {} name, e.g. `{}`",
d.ident_type, d.ident_text, d.expected_case, d.suggested_text 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)) .with_fixes(fixes(ctx, d))
} }
@ -149,7 +155,7 @@ impl TestStruct {
check_diagnostics( check_diagnostics(
r#" r#"
fn FOO() {} 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() {}"#); check_fix(r#"fn FOO$0() {}"#, r#"fn foo() {}"#);
@ -160,7 +166,7 @@ fn FOO() {}
check_diagnostics( check_diagnostics(
r#" r#"
fn NonSnakeCaseName() {} 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( check_diagnostics(
r#" r#"
fn foo(SomeParam: u8) {} 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) {} 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#" r#"
fn foo() { fn foo() {
let SOME_VALUE = 10; 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; 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( check_diagnostics(
r#" r#"
struct non_camel_case_name {} 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 {} 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( check_diagnostics(
r#" r#"
struct SomeStruct { SomeField: u8 } 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( check_diagnostics(
r#" r#"
enum some_enum { Val(u8) } 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 {} 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( check_diagnostics(
r#" r#"
enum SomeEnum { SOME_VARIANT(u8) } 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( check_diagnostics(
r#" r#"
const some_weird_const: u8 = 10; 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( check_diagnostics(
r#" r#"
static some_weird_const: u8 = 10; 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( check_diagnostics(
r#" r#"
struct someStruct; struct someStruct;
// ^^^^^^^^^^ 💡 weak: Structure `someStruct` should have CamelCase name, e.g. `SomeStruct` // ^^^^^^^^^^ 💡 warn: Structure `someStruct` should have CamelCase name, e.g. `SomeStruct`
impl someStruct { impl someStruct {
fn SomeFunc(&self) { 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; 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() { fn main() {
match Option::None { match Option::None {
SOME_VAR @ 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 => (), Some => (),
} }
} }
@ -461,7 +467,7 @@ mod CheckNonstandardStyle {
#[allow(bad_style)] #[allow(bad_style)]
mod CheckBadStyle { mod CheckBadStyle {
fn HiImABadFnName() {} struct fooo;
} }
mod F { 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`
"#,
);
}
} }

View file

@ -1,4 +1,4 @@
use crate::{Diagnostic, DiagnosticsContext, Severity}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: invalid-derive-target // 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; let display_range = ctx.sema.diagnostics_display_range(d.node.clone()).range;
Diagnostic::new( Diagnostic::new(
"invalid-derive-target", DiagnosticCode::RustcHardError("E0774"),
"`derive` may only be applied to `struct`s, `enum`s and `union`s", "`derive` may only be applied to `struct`s, `enum`s and `union`s",
display_range, display_range,
) )
.severity(Severity::Error)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -17,7 +17,7 @@ use syntax::{
}; };
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{fix, Diagnostic, DiagnosticsConfig, Severity}; use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsConfig, Severity};
#[derive(Default)] #[derive(Default)]
struct State { struct State {
@ -117,11 +117,10 @@ pub(crate) fn json_in_items(
edit.insert(range.start(), state.result); edit.insert(range.start(), state.result);
acc.push( acc.push(
Diagnostic::new( Diagnostic::new(
"json-is-not-rust", DiagnosticCode::Ra("json-is-not-rust", Severity::WeakWarning),
"JSON syntax is not valid as a Rust item", "JSON syntax is not valid as a Rust item",
range, range,
) )
.severity(Severity::WeakWarning)
.with_fixes(Some(vec![{ .with_fixes(Some(vec![{
let mut scb = SourceChangeBuilder::new(file_id); let mut scb = SourceChangeBuilder::new(file_id);
let scope = match import_scope { let scope = match import_scope {

View file

@ -1,4 +1,4 @@
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
// Diagnostic: macro-error // Diagnostic: macro-error
// //
@ -6,7 +6,12 @@ use crate::{Diagnostic, DiagnosticsContext};
pub(crate) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic { pub(crate) fn macro_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroError) -> Diagnostic {
// Use more accurate position if available. // Use more accurate position if available.
let display_range = ctx.resolve_precise_location(&d.node, d.precise_location); 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 // Diagnostic: macro-error
@ -16,7 +21,12 @@ pub(crate) fn macro_def_error(ctx: &DiagnosticsContext<'_>, d: &hir::MacroDefErr
// Use more accurate position if available. // Use more accurate position if available.
let display_range = let display_range =
ctx.resolve_precise_location(&d.node.clone().map(|it| it.syntax_node_ptr()), d.name); 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)] #[cfg(test)]

View file

@ -1,4 +1,4 @@
use crate::{Diagnostic, DiagnosticsContext, Severity}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: malformed-derive // Diagnostic: malformed-derive
// //
@ -10,11 +10,10 @@ pub(crate) fn malformed_derive(
let display_range = ctx.sema.diagnostics_display_range(d.node.clone()).range; let display_range = ctx.sema.diagnostics_display_range(d.node.clone()).range;
Diagnostic::new( Diagnostic::new(
"malformed-derive", DiagnosticCode::RustcHardError("E0777"),
"malformed derive input, derive attributes are of the form `#[derive(Derive1, Derive2, ...)]`", "malformed derive input, derive attributes are of the form `#[derive(Derive1, Derive2, ...)]`",
display_range, display_range,
) )
.severity(Severity::Error)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -3,7 +3,7 @@ use syntax::{
AstNode, TextRange, AstNode, TextRange,
}; };
use crate::{adjusted_display_range, Diagnostic, DiagnosticsContext}; use crate::{adjusted_display_range, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: mismatched-arg-count // Diagnostic: mismatched-arg-count
// //
@ -14,7 +14,7 @@ pub(crate) fn mismatched_arg_count(
) -> Diagnostic { ) -> Diagnostic {
let s = if d.expected == 1 { "" } else { "s" }; let s = if d.expected == 1 { "" } else { "s" };
let message = format!("expected {} argument{s}, found {}", d.expected, d.found); 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 { fn invalid_args_range(ctx: &DiagnosticsContext<'_>, d: &hir::MismatchedArgCount) -> TextRange {

View file

@ -15,7 +15,7 @@ use syntax::{
}; };
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{fix, Diagnostic, DiagnosticsContext}; use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: missing-fields // 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())), .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)) .with_fixes(fixes(ctx, d))
} }

View file

@ -1,4 +1,4 @@
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: missing-match-arm // Diagnostic: missing-match-arm
// //
@ -7,10 +7,11 @@ pub(crate) fn missing_match_arms(
ctx: &DiagnosticsContext<'_>, ctx: &DiagnosticsContext<'_>,
d: &hir::MissingMatchArms, d: &hir::MissingMatchArms,
) -> Diagnostic { ) -> Diagnostic {
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"missing-match-arm", ctx,
DiagnosticCode::RustcHardError("E0004"),
format!("missing match arm: {}", d.uncovered_patterns), 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),
) )
} }

View file

@ -4,16 +4,17 @@ use syntax::{ast, SyntaxNode};
use syntax::{match_ast, AstNode}; use syntax::{match_ast, AstNode};
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{fix, Diagnostic, DiagnosticsContext}; use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: missing-unsafe // Diagnostic: missing-unsafe
// //
// This diagnostic is triggered if an operation marked as `unsafe` is used outside of an `unsafe` function or block. // 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 { pub(crate) fn missing_unsafe(ctx: &DiagnosticsContext<'_>, d: &hir::MissingUnsafe) -> Diagnostic {
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"missing-unsafe", ctx,
DiagnosticCode::RustcHardError("E0133"),
"this operation is unsafe and requires an unsafe function or block", "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)) .with_fixes(fixes(ctx, d))
} }

View file

@ -1,14 +1,15 @@
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
use hir::HirDisplay; use hir::HirDisplay;
// Diagnostic: moved-out-of-ref // Diagnostic: moved-out-of-ref
// //
// This diagnostic is triggered on moving non copy things out of references. // 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 { pub(crate) fn moved_out_of_ref(ctx: &DiagnosticsContext<'_>, d: &hir::MovedOutOfRef) -> Diagnostic {
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"moved-out-of-ref", ctx,
DiagnosticCode::RustcHardError("E0507"),
format!("cannot move `{}` out of reference", d.ty.display(ctx.sema.db)), 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 .experimental() // spans are broken, and I'm not sure how precise we can detect copy types
} }

View file

@ -2,7 +2,7 @@ use ide_db::source_change::SourceChange;
use syntax::{AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T}; use syntax::{AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T};
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{fix, Diagnostic, DiagnosticsContext, Severity}; use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: need-mut // Diagnostic: need-mut
// //
@ -29,13 +29,15 @@ pub(crate) fn need_mut(ctx: &DiagnosticsContext<'_>, d: &hir::NeedMut) -> Diagno
use_range, use_range,
)]) )])
})(); })();
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"need-mut", ctx,
// FIXME: `E0384` is not the only error that this diagnostic handles
DiagnosticCode::RustcHardError("E0384"),
format!( format!(
"cannot mutate immutable variable `{}`", "cannot mutate immutable variable `{}`",
d.local.name(ctx.sema.db).display(ctx.sema.db) 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) .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(); let ast = d.local.primary_source(ctx.sema.db).syntax_ptr();
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"unused-mut", ctx,
DiagnosticCode::RustcLint("unused_mut"),
"variable does not need to be mutable", "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. .experimental() // Not supporting `#[allow(unused_mut)]` leads to false positive.
.with_fixes(fixes) .with_fixes(fixes)
} }
@ -93,7 +95,7 @@ mod tests {
fn f(_: i32) {} fn f(_: i32) {}
fn main() { fn main() {
let mut x = 2; let mut x = 2;
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
f(x); f(x);
} }
"#, "#,
@ -268,7 +270,7 @@ fn main() {
fn f(_: i32) {} fn f(_: i32) {}
fn main() { fn main() {
let mut x = (2, 7); let mut x = (2, 7);
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
f(x.1); f(x.1);
} }
"#, "#,
@ -302,7 +304,7 @@ fn main() {
r#" r#"
fn main() { fn main() {
let mut x = &mut 2; let mut x = &mut 2;
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
*x = 5; *x = 5;
} }
"#, "#,
@ -346,7 +348,7 @@ fn main() {
r#" r#"
//- minicore: copy, builtin_impls //- minicore: copy, builtin_impls
fn clone(mut i: &!) -> ! { fn clone(mut i: &!) -> ! {
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
*i *i
} }
"#, "#,
@ -360,7 +362,7 @@ fn main() {
//- minicore: option //- minicore: option
fn main() { fn main() {
let mut v = &mut Some(2); 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 { let _ = || match v {
Some(k) => { Some(k) => {
*k = 5; *k = 5;
@ -386,7 +388,7 @@ fn main() {
fn main() { fn main() {
match (2, 3) { match (2, 3) {
(x, mut y) => { (x, mut y) => {
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
x = 7; x = 7;
//^^^^^ 💡 error: cannot mutate immutable variable `x` //^^^^^ 💡 error: cannot mutate immutable variable `x`
} }
@ -407,7 +409,7 @@ fn main() {
fn main() { fn main() {
return; return;
let mut x = 2; let mut x = 2;
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
&mut x; &mut x;
} }
"#, "#,
@ -417,7 +419,7 @@ fn main() {
fn main() { fn main() {
loop {} loop {}
let mut x = 2; let mut x = 2;
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
&mut x; &mut x;
} }
"#, "#,
@ -438,7 +440,7 @@ fn main(b: bool) {
g(); g();
} }
let mut x = 2; let mut x = 2;
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
&mut x; &mut x;
} }
"#, "#,
@ -452,7 +454,7 @@ fn main(b: bool) {
return; return;
} }
let mut x = 2; let mut x = 2;
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
&mut x; &mut x;
} }
"#, "#,
@ -466,7 +468,7 @@ fn main(b: bool) {
fn f(_: i32) {} fn f(_: i32) {}
fn main() { fn main() {
let mut x; let mut x;
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
x = 5; x = 5;
f(x); f(x);
} }
@ -477,7 +479,7 @@ fn main() {
fn f(_: i32) {} fn f(_: i32) {}
fn main(b: bool) { fn main(b: bool) {
let mut x; let mut x;
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
if b { if b {
x = 1; x = 1;
} else { } else {
@ -552,15 +554,15 @@ fn f(_: i32) {}
fn main() { fn main() {
loop { loop {
let mut x = 1; let mut x = 1;
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
f(x); f(x);
if let mut y = 2 { if let mut y = 2 {
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
f(y); f(y);
} }
match 3 { match 3 {
mut z => f(z), 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 { loop {
let c @ ( let c @ (
mut b, mut b,
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
mut d mut d
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
); );
a = 1; a = 1;
//^^^^^ 💡 error: cannot mutate immutable variable `a` //^^^^^ 💡 error: cannot mutate immutable variable `a`
@ -597,7 +599,7 @@ fn main() {
check_diagnostics( check_diagnostics(
r#" r#"
fn f(mut x: i32) { 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 //- minicore: iterators, copy
fn f(x: [(i32, u8); 10]) { fn f(x: [(i32, u8); 10]) {
for (a, mut b) in x { for (a, mut b) in x {
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
a = 2; a = 2;
//^^^^^ 💡 error: cannot mutate immutable variable `a` //^^^^^ 💡 error: cannot mutate immutable variable `a`
} }
@ -657,9 +659,9 @@ fn f(x: [(i32, u8); 10]) {
fn f(x: [(i32, u8); 10]) { fn f(x: [(i32, u8); 10]) {
let mut it = x.into_iter(); let mut it = x.into_iter();
while let Some((a, mut b)) = it.next() { 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() { 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; a = 2;
//^^^^^ 💡 error: cannot mutate immutable variable `a` //^^^^^ 💡 error: cannot mutate immutable variable `a`
c = 2; c = 2;
@ -683,7 +685,7 @@ fn f() {
let x = &mut x; let x = &mut x;
//^^^^^^ 💡 error: cannot mutate immutable variable `x` //^^^^^^ 💡 error: cannot mutate immutable variable `x`
let mut x = x; let mut x = x;
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
x[2] = 5; x[2] = 5;
} }
"#, "#,
@ -711,13 +713,13 @@ impl IndexMut<usize> for Foo {
} }
fn f() { fn f() {
let mut x = Foo; 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 y = &x[2];
let x = Foo; let x = Foo;
let y = &mut x[2]; let y = &mut x[2];
//^💡 error: cannot mutate immutable variable `x` //^💡 error: cannot mutate immutable variable `x`
let mut x = &mut Foo; 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 y: &mut (i32, u8) = &mut x[2];
let x = Foo; let x = Foo;
let ref mut y = x[7]; let ref mut y = x[7];
@ -731,7 +733,7 @@ fn f() {
} }
let mut x = Foo; let mut x = Foo;
let mut i = 5; 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]; let y = &mut x[i];
} }
"#, "#,
@ -759,7 +761,7 @@ impl DerefMut for Foo {
} }
fn f() { fn f() {
let mut x = Foo; let mut x = Foo;
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
let y = &*x; let y = &*x;
let x = Foo; let x = Foo;
let y = &mut *x; let y = &mut *x;
@ -790,7 +792,7 @@ fn f() {
fn f(_: i32) {} fn f(_: i32) {}
fn main() { fn main() {
let ((Some(mut x), None) | (_, Some(mut x))) = (None, Some(7)); 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); f(x);
} }
"#, "#,
@ -842,7 +844,7 @@ pub struct TreeLeaf {
pub fn test() { pub fn test() {
let mut tree = Tree::Leaf( let mut tree = Tree::Leaf(
//^^^^^^^^ 💡 weak: variable does not need to be mutable //^^^^^^^^ 💡 warn: variable does not need to be mutable
TreeLeaf { TreeLeaf {
depth: 0, depth: 0,
data: 0 data: 0
@ -859,7 +861,7 @@ pub fn test() {
r#" r#"
//- minicore: fn //- minicore: fn
fn fn_ref(mut x: impl Fn(u8) -> u8) -> u8 { 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) x(2)
} }
fn fn_mut(x: impl FnMut(u8) -> u8) -> u8 { 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` //^ 💡 error: cannot mutate immutable variable `x`
} }
fn fn_borrow_mut(mut x: &mut impl FnMut(u8) -> u8) -> u8 { 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) x(2)
} }
fn fn_once(mut x: impl FnOnce(u8) -> u8) -> u8 { 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) x(2)
} }
"#, "#,
@ -915,14 +917,14 @@ fn fn_once(mut x: impl FnOnce(u8) -> u8) -> u8 {
//- minicore: copy, fn //- minicore: copy, fn
fn f() { fn f() {
let mut x = 5; let mut x = 5;
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
let mut y = 2; let mut y = 2;
y = 7; y = 7;
let closure = || { let closure = || {
let mut z = 8; let mut z = 8;
z = 3; z = 3;
let mut k = z; 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() { fn f() {
struct X; struct X;
let mut x = 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 c1 = || x;
let mut x = X; let mut x = X;
let c2 = || { x = X; x }; let c2 = || { x = X; x };
@ -965,12 +967,12 @@ fn f() {
fn f() { fn f() {
let mut x = &mut 5; 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 = || { *x = 2; };
let _ = closure1(); let _ = closure1();
//^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1` //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
let mut x = &mut 5; 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 = || { *x = 2; &x; };
let _ = closure1(); let _ = closure1();
//^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1` //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
@ -979,12 +981,12 @@ fn f() {
let _ = closure1(); let _ = closure1();
//^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1` //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
let mut x = &mut 5; 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 = move || { *x = 2; };
let _ = closure1(); let _ = closure1();
//^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1` //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
let mut x = &mut X(1, 2); 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 = || { x.0 = 2; };
let _ = closure1(); let _ = closure1();
//^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1` //^^^^^^^^ 💡 error: cannot mutate immutable variable `closure1`
@ -1001,7 +1003,7 @@ fn f() {
fn x(t: &[u8]) { fn x(t: &[u8]) {
match t { match t {
&[a, mut b] | &[a, _, mut b] => { &[a, mut b] | &[a, _, mut b] => {
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
a = 2; a = 2;
//^^^^^ 💡 error: cannot mutate immutable variable `a` //^^^^^ 💡 error: cannot mutate immutable variable `a`
@ -1055,7 +1057,7 @@ fn f() {
*x = 7; *x = 7;
//^^^^^^ 💡 error: cannot mutate immutable variable `x` //^^^^^^ 💡 error: cannot mutate immutable variable `x`
let mut y = Box::new(5); let mut y = Box::new(5);
//^^^^^ 💡 weak: variable does not need to be mutable //^^^^^ 💡 warn: variable does not need to be mutable
*x = *y; *x = *y;
//^^^^^^^ 💡 error: cannot mutate immutable variable `x` //^^^^^^^ 💡 error: cannot mutate immutable variable `x`
let x = Box::new(5); let x = Box::new(5);
@ -1080,18 +1082,39 @@ fn main() {
} }
#[test] #[test]
fn respect_allow_unused_mut() { fn respect_lint_attributes_for_unused_mut() {
// FIXME: respect
check_diagnostics( check_diagnostics(
r#" r#"
fn f(_: i32) {} fn f(_: i32) {}
fn main() { fn main() {
#[allow(unused_mut)] #[allow(unused_mut)]
let mut x = 2; 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); 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
}
"#,
);
} }
} }

View file

@ -6,16 +6,17 @@ use syntax::{
}; };
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{fix, Assist, Diagnostic, DiagnosticsContext}; use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: no-such-field // Diagnostic: no-such-field
// //
// This diagnostic is triggered if created structure does not have field provided in record. // 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 { pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic {
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"no-such-field", ctx,
DiagnosticCode::RustcHardError("E0559"),
"no such field", "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)) .with_fixes(fixes(ctx, d))
} }

View file

@ -1,6 +1,6 @@
use either::Either; use either::Either;
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: private-assoc-item // Diagnostic: private-assoc-item
// //
@ -16,8 +16,9 @@ pub(crate) fn private_assoc_item(
.name(ctx.sema.db) .name(ctx.sema.db)
.map(|name| format!("`{}` ", name.display(ctx.sema.db))) .map(|name| format!("`{}` ", name.display(ctx.sema.db)))
.unwrap_or_default(); .unwrap_or_default();
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"private-assoc-item", ctx,
DiagnosticCode::RustcHardError("E0624"),
format!( format!(
"{} {}is private", "{} {}is private",
match d.item { match d.item {
@ -27,15 +28,13 @@ pub(crate) fn private_assoc_item(
}, },
name, name,
), ),
ctx.sema d.expr_or_pat.clone().map(|it| match it {
.diagnostics_display_range(d.expr_or_pat.clone().map(|it| match it {
Either::Left(it) => it.into(), Either::Left(it) => it.into(),
Either::Right(it) => match it { Either::Right(it) => match it {
Either::Left(it) => it.into(), Either::Left(it) => it.into(),
Either::Right(it) => it.into(), Either::Right(it) => it.into(),
}, },
})) }),
.range,
) )
} }

View file

@ -1,18 +1,19 @@
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: private-field // Diagnostic: private-field
// //
// This diagnostic is triggered if the accessed field is not visible from the current module. // 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 { pub(crate) fn private_field(ctx: &DiagnosticsContext<'_>, d: &hir::PrivateField) -> Diagnostic {
// FIXME: add quickfix // FIXME: add quickfix
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"private-field", ctx,
DiagnosticCode::RustcHardError("E0616"),
format!( format!(
"field `{}` of `{}` is private", "field `{}` of `{}` is private",
d.field.name(ctx.sema.db).display(ctx.sema.db), 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) 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()),
) )
} }

View file

@ -6,7 +6,7 @@ use syntax::{
}; };
use text_edit::TextEdit; 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 // Diagnostic: replace-filter-map-next-with-find-map
// //
@ -15,12 +15,12 @@ pub(crate) fn replace_filter_map_next_with_find_map(
ctx: &DiagnosticsContext<'_>, ctx: &DiagnosticsContext<'_>,
d: &hir::ReplaceFilterMapNextWithFindMap, d: &hir::ReplaceFilterMapNextWithFindMap,
) -> Diagnostic { ) -> Diagnostic {
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"replace-filter-map-next-with-find-map", ctx,
DiagnosticCode::Clippy("filter_map_next"),
"replace filter_map(..).next() with find_map(..)", "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)) .with_fixes(fixes(ctx, d))
} }
@ -64,7 +64,7 @@ mod tests {
pub(crate) fn check_diagnostics(ra_fixture: &str) { pub(crate) fn check_diagnostics(ra_fixture: &str) {
let mut config = DiagnosticsConfig::test_sample(); let mut config = DiagnosticsConfig::test_sample();
config.disabled.insert("inactive-code".to_string()); 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) 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(..)
"#,
);
}
} }

View file

@ -7,7 +7,7 @@ use syntax::{
}; };
use text_edit::TextEdit; 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 // Diagnostic: type-mismatch
// //
@ -39,7 +39,7 @@ pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch)
} }
}; };
let mut diag = Diagnostic::new( let mut diag = Diagnostic::new(
"type-mismatch", DiagnosticCode::RustcHardError("E0308"),
format!( format!(
"expected {}, found {}", "expected {}, found {}",
d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId), d.expected.display(ctx.sema.db).with_closure_style(ClosureStyle::ClosureWithId),

View file

@ -7,7 +7,7 @@ use ide_db::{
use syntax::AstNode; use syntax::AstNode;
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: typed-hole // 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>> { fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypedHole) -> Option<Vec<Assist>> {

View file

@ -1,4 +1,4 @@
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: undeclared-label // Diagnostic: undeclared-label
pub(crate) fn undeclared_label( pub(crate) fn undeclared_label(
@ -6,10 +6,11 @@ pub(crate) fn undeclared_label(
d: &hir::UndeclaredLabel, d: &hir::UndeclaredLabel,
) -> Diagnostic { ) -> Diagnostic {
let name = &d.name; let name = &d.name;
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"undeclared-label", ctx,
DiagnosticCode::RustcHardError("undeclared-label"),
format!("use of undeclared label `{}`", name.display(ctx.sema.db)), 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()),
) )
} }

View file

@ -1,4 +1,4 @@
use crate::{Diagnostic, DiagnosticsContext, Severity}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
// Diagnostic: unimplemented-builtin-macro // Diagnostic: unimplemented-builtin-macro
// //
@ -7,10 +7,10 @@ pub(crate) fn unimplemented_builtin_macro(
ctx: &DiagnosticsContext<'_>, ctx: &DiagnosticsContext<'_>,
d: &hir::UnimplementedBuiltinMacro, d: &hir::UnimplementedBuiltinMacro,
) -> Diagnostic { ) -> Diagnostic {
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"unimplemented-builtin-macro", ctx,
DiagnosticCode::Ra("unimplemented-builtin-macro", Severity::WeakWarning),
"unimplemented built-in macro".to_string(), "unimplemented built-in macro".to_string(),
ctx.sema.diagnostics_display_range(d.node.clone()).range, d.node.clone(),
) )
.severity(Severity::WeakWarning)
} }

View file

@ -14,7 +14,7 @@ use syntax::{
}; };
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{fix, Assist, Diagnostic, DiagnosticsContext, Severity}; use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
// Diagnostic: unlinked-file // Diagnostic: unlinked-file
// //
@ -46,8 +46,7 @@ pub(crate) fn unlinked_file(
.unwrap_or(range); .unwrap_or(range);
acc.push( acc.push(
Diagnostic::new("unlinked-file", message, range) Diagnostic::new(DiagnosticCode::Ra("unlinked-file", Severity::WeakWarning), message, range)
.severity(Severity::WeakWarning)
.with_fixes(fixes), .with_fixes(fixes),
); );
} }

View file

@ -1,4 +1,4 @@
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unreachable-label // Diagnostic: unreachable-label
pub(crate) fn unreachable_label( pub(crate) fn unreachable_label(
@ -6,10 +6,11 @@ pub(crate) fn unreachable_label(
d: &hir::UnreachableLabel, d: &hir::UnreachableLabel,
) -> Diagnostic { ) -> Diagnostic {
let name = &d.name; let name = &d.name;
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"unreachable-label", ctx,
DiagnosticCode::RustcHardError("E0767"),
format!("use of unreachable label `{}`", name.display(ctx.sema.db)), 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()),
) )
} }

View file

@ -1,4 +1,4 @@
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unresolved-extern-crate // Diagnostic: unresolved-extern-crate
// //
@ -7,10 +7,11 @@ pub(crate) fn unresolved_extern_crate(
ctx: &DiagnosticsContext<'_>, ctx: &DiagnosticsContext<'_>,
d: &hir::UnresolvedExternCrate, d: &hir::UnresolvedExternCrate,
) -> Diagnostic { ) -> Diagnostic {
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"unresolved-extern-crate", ctx,
DiagnosticCode::RustcHardError("unresolved-extern-crate"),
"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()),
) )
} }

View file

@ -8,7 +8,7 @@ use ide_db::{
use syntax::{ast, AstNode, AstPtr}; use syntax::{ast, AstNode, AstPtr};
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unresolved-field // Diagnostic: unresolved-field
// //
@ -22,14 +22,15 @@ pub(crate) fn unresolved_field(
} else { } else {
"" ""
}; };
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"unresolved-field", ctx,
DiagnosticCode::RustcHardError("E0559"),
format!( format!(
"no field `{}` on type `{}`{method_suffix}", "no field `{}` on type `{}`{method_suffix}",
d.name.display(ctx.sema.db), d.name.display(ctx.sema.db),
d.receiver.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)) .with_fixes(fixes(ctx, d))
.experimental() .experimental()

View file

@ -1,4 +1,4 @@
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unresolved-import // Diagnostic: unresolved-import
// //
@ -8,10 +8,11 @@ pub(crate) fn unresolved_import(
ctx: &DiagnosticsContext<'_>, ctx: &DiagnosticsContext<'_>,
d: &hir::UnresolvedImport, d: &hir::UnresolvedImport,
) -> Diagnostic { ) -> Diagnostic {
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"unresolved-import", ctx,
DiagnosticCode::RustcHardError("E0432"),
"unresolved import", "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: // This currently results in false positives in the following cases:
// - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly) // - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)

View file

@ -1,4 +1,4 @@
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unresolved-macro-call // 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 display_range = ctx.resolve_precise_location(&d.macro_call, d.precise_location);
let bang = if d.is_bang { "!" } else { "" }; let bang = if d.is_bang { "!" } else { "" };
Diagnostic::new( Diagnostic::new(
"unresolved-macro-call", DiagnosticCode::RustcHardError("unresolved-macro-call"),
format!("unresolved macro `{}{bang}`", d.path.display(ctx.sema.db)), format!("unresolved macro `{}{bang}`", d.path.display(ctx.sema.db)),
display_range, display_range,
) )

View file

@ -8,7 +8,7 @@ use ide_db::{
use syntax::{ast, AstNode, TextRange}; use syntax::{ast, AstNode, TextRange};
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{Diagnostic, DiagnosticsContext}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unresolved-method // Diagnostic: unresolved-method
// //
@ -22,14 +22,15 @@ pub(crate) fn unresolved_method(
} else { } else {
"" ""
}; };
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"unresolved-method", ctx,
DiagnosticCode::RustcHardError("E0599"),
format!( format!(
"no method `{}` on type `{}`{field_suffix}", "no method `{}` on type `{}`{field_suffix}",
d.name.display(ctx.sema.db), d.name.display(ctx.sema.db),
d.receiver.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)) .with_fixes(fixes(ctx, d))
.experimental() .experimental()

View file

@ -3,7 +3,7 @@ use ide_db::{assists::Assist, base_db::AnchoredPathBuf, source_change::FileSyste
use itertools::Itertools; use itertools::Itertools;
use syntax::AstNode; use syntax::AstNode;
use crate::{fix, Diagnostic, DiagnosticsContext}; use crate::{fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: unresolved-module // Diagnostic: unresolved-module
// //
@ -12,8 +12,9 @@ pub(crate) fn unresolved_module(
ctx: &DiagnosticsContext<'_>, ctx: &DiagnosticsContext<'_>,
d: &hir::UnresolvedModule, d: &hir::UnresolvedModule,
) -> Diagnostic { ) -> Diagnostic {
Diagnostic::new( Diagnostic::new_with_syntax_node_ptr(
"unresolved-module", ctx,
DiagnosticCode::RustcHardError("E0583"),
match &*d.candidates { match &*d.candidates {
[] => "unresolved module".to_string(), [] => "unresolved module".to_string(),
[candidate] => format!("unresolved module, can't find module file: {candidate}"), [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)) .with_fixes(fixes(ctx, d))
} }
@ -82,8 +83,8 @@ mod baz {}
expect![[r#" expect![[r#"
[ [
Diagnostic { Diagnostic {
code: DiagnosticCode( code: RustcHardError(
"unresolved-module", "E0583",
), ),
message: "unresolved module, can't find module file: foo.rs, or foo/mod.rs", message: "unresolved module, can't find module file: foo.rs, or foo/mod.rs",
range: 0..8, 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 ";"
,
},
),
}, },
] ]
"#]], "#]],

View file

@ -1,6 +1,6 @@
use hir::db::DefDatabase; use hir::db::DefDatabase;
use crate::{Diagnostic, DiagnosticsContext, Severity}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
// Diagnostic: unresolved-proc-macro // Diagnostic: unresolved-proc-macro
// //
@ -41,5 +41,5 @@ pub(crate) fn unresolved_proc_macro(
}; };
let message = format!("{not_expanded_message}: {message}"); 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)
} }

View file

@ -3,7 +3,7 @@ use itertools::Itertools;
use syntax::{ast, AstNode, SyntaxNode, TextRange}; use syntax::{ast, AstNode, SyntaxNode, TextRange};
use text_edit::TextEdit; use text_edit::TextEdit;
use crate::{fix, Diagnostic, Severity}; use crate::{fix, Diagnostic, DiagnosticCode};
// Diagnostic: unnecessary-braces // Diagnostic: unnecessary-braces
// //
@ -32,11 +32,10 @@ pub(crate) fn useless_braces(
acc.push( acc.push(
Diagnostic::new( Diagnostic::new(
"unnecessary-braces", DiagnosticCode::RustcLint("unused_braces"),
"Unnecessary braces in use statement".to_string(), "Unnecessary braces in use statement".to_string(),
use_range, use_range,
) )
.severity(Severity::WeakWarning)
.with_fixes(Some(vec![fix( .with_fixes(Some(vec![fix(
"remove_braces", "remove_braces",
"Remove unnecessary braces", "Remove unnecessary braces",

View file

@ -67,24 +67,61 @@ mod handlers {
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use std::collections::HashMap;
use hir::{diagnostics::AnyDiagnostic, InFile, Semantics}; use hir::{diagnostics::AnyDiagnostic, InFile, Semantics};
use ide_db::{ use ide_db::{
assists::{Assist, AssistId, AssistKind, AssistResolveStrategy}, assists::{Assist, AssistId, AssistKind, AssistResolveStrategy},
base_db::{FileId, FileRange, SourceDatabase}, base_db::{FileId, FileRange, SourceDatabase},
generated::lints::{LintGroup, CLIPPY_LINT_GROUPS, DEFAULT_LINT_GROUPS},
imports::insert_use::InsertUseConfig, imports::insert_use::InsertUseConfig,
label::Label, label::Label,
source_change::SourceChange, 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 // FIXME: Make this an enum
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct DiagnosticCode(pub &'static str); pub enum DiagnosticCode {
RustcHardError(&'static str),
RustcLint(&'static str),
Clippy(&'static str),
Ra(&'static str, Severity),
}
impl DiagnosticCode { impl DiagnosticCode {
pub fn as_str(&self) -> &str { pub fn url(&self) -> String {
self.0 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 unused: bool,
pub experimental: bool, pub experimental: bool,
pub fixes: Option<Vec<Assist>>, pub fixes: Option<Vec<Assist>>,
// The node that will be affected by `#[allow]` and similar attributes.
pub main_node: Option<InFile<SyntaxNode>>,
} }
impl Diagnostic { 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(); let message = message.into();
Diagnostic { Diagnostic {
code: DiagnosticCode(code), code,
message, message,
range, 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, unused: false,
experimental: false, experimental: false,
fixes: None, 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 { fn experimental(mut self) -> Diagnostic {
self.experimental = true; self.experimental = true;
self self
} }
fn severity(mut self, severity: Severity) -> Diagnostic { fn with_main_node(mut self, main_node: InFile<SyntaxNode>) -> Diagnostic {
self.severity = severity; self.main_node = Some(main_node);
self self
} }
@ -134,12 +193,12 @@ impl Diagnostic {
} }
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Severity { pub enum Severity {
Error, Error,
// We don't actually emit this one yet, but we should at some point. Warning,
// Warning,
WeakWarning, WeakWarning,
Allow,
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@ -228,11 +287,13 @@ pub fn diagnostics(
let mut res = Vec::new(); let mut res = Vec::new();
// [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily. // [#34344] Only take first 128 errors to prevent slowing down editor/ide, the number 128 is chosen arbitrarily.
res.extend( res.extend(parse.errors().iter().take(128).map(|err| {
parse.errors().iter().take(128).map(|err| { Diagnostic::new(
Diagnostic::new("syntax-error", format!("Syntax Error: {err}"), err.range()) DiagnosticCode::RustcHardError("syntax-error"),
}), format!("Syntax Error: {err}"),
); err.range(),
)
}));
let parse = sema.parse(file_id); let parse = sema.parse(file_id);
@ -271,7 +332,7 @@ pub fn diagnostics(
res.extend(d.errors.iter().take(32).map(|err| { res.extend(d.errors.iter().take(32).map(|err| {
{ {
Diagnostic::new( Diagnostic::new(
"syntax-error", DiagnosticCode::RustcHardError("syntax-error"),
format!("Syntax Error in Expansion: {err}"), format!("Syntax Error in Expansion: {err}"),
ctx.resolve_precise_location(&d.node.clone(), d.precise_location), ctx.resolve_precise_location(&d.node.clone(), d.precise_location),
) )
@ -309,14 +370,168 @@ pub fn diagnostics(
res.push(d) 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| { 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) && !(ctx.config.disable_experimental && d.experimental)
}); });
res 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 { fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist {
let mut res = unresolved_fix(id, label, target); let mut res = unresolved_fix(id, label, target);
res.source_change = Some(source_change); res.source_change = Some(source_change);

View file

@ -114,6 +114,8 @@ pub(crate) fn check_diagnostics_with_config(config: DiagnosticsConfig, ra_fixtur
annotation.push_str(match d.severity { annotation.push_str(match d.severity {
Severity::Error => "error", Severity::Error => "error",
Severity::WeakWarning => "weak", Severity::WeakWarning => "weak",
Severity::Warning => "warn",
Severity::Allow => "allow",
}); });
annotation.push_str(": "); annotation.push_str(": ");
annotation.push_str(&d.message); 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] #[test]
fn test_disabled_diagnostics() { fn test_disabled_diagnostics() {
let mut config = DiagnosticsConfig::test_sample(); 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;"#); 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 source = minicore.source_code();
let mut config = DiagnosticsConfig::test_sample(); let mut config = DiagnosticsConfig::test_sample();
// This should be ignored since we conditionaly remove code which creates single item use with braces // 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); check_diagnostics_with_config(config, &source);
} }

View file

@ -831,11 +831,7 @@ impl GlobalState {
d.code.as_str().to_string(), d.code.as_str().to_string(),
)), )),
code_description: Some(lsp_types::CodeDescription { code_description: Some(lsp_types::CodeDescription {
href: lsp_types::Url::parse(&format!( href: lsp_types::Url::parse(&d.code.url()).unwrap(),
"https://rust-analyzer.github.io/manual.html#{}",
d.code.as_str()
))
.unwrap(),
}), }),
source: Some("rust-analyzer".to_string()), source: Some("rust-analyzer".to_string()),
message: d.message, message: d.message,

View file

@ -94,7 +94,10 @@ pub(crate) fn document_highlight_kind(
pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity { pub(crate) fn diagnostic_severity(severity: Severity) -> lsp_types::DiagnosticSeverity {
match severity { match severity {
Severity::Error => lsp_types::DiagnosticSeverity::ERROR, Severity::Error => lsp_types::DiagnosticSeverity::ERROR,
Severity::Warning => lsp_types::DiagnosticSeverity::WARNING,
Severity::WeakWarning => lsp_types::DiagnosticSeverity::HINT, Severity::WeakWarning => lsp_types::DiagnosticSeverity::HINT,
// unreachable
Severity::Allow => lsp_types::DiagnosticSeverity::INFORMATION,
} }
} }