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",
"ide-db",
"itertools",
"once_cell",
"profile",
"serde_json",
"sourcegen",

View File

@ -5,7 +5,7 @@
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,
},

View File

@ -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,
}

View File

@ -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};

View File

@ -89,11 +89,11 @@
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,

View File

@ -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

View File

@ -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()),
)
}

View File

@ -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()
}

View File

@ -5,7 +5,7 @@
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,
)])),
);
}
}

View File

@ -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)
}

View File

@ -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)]

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 syntax::AstNode;
@ -6,23 +6,29 @@
// 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 @@ fn test_single_incorrect_case_diagnostic_in_function_name_issue_6970() {
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 incorrect_function_name() {
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 incorrect_function_params() {
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 incorrect_variable_names() {
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 incorrect_struct_names() {
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 @@ fn incorrect_struct_field() {
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 @@ fn incorrect_enum_names() {
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 @@ fn incorrect_enum_variant_name() {
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 @@ fn incorrect_const_name() {
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 @@ fn incorrect_static_name() {
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 @@ fn fn_inside_impl_struct() {
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 @@ fn HiImABadFnName() {}
#[allow(bad_style)]
mod CheckBadStyle {
fn HiImABadFnName() {}
struct fooo;
}
mod F {
@ -483,4 +489,60 @@ pub struct some_type {
"#,
);
}
#[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
//
@ -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)]

View File

@ -17,7 +17,7 @@
};
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 {

View File

@ -1,4 +1,4 @@
use crate::{Diagnostic, DiagnosticsContext};
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, Severity};
// Diagnostic: macro-error
//
@ -6,7 +6,12 @@
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)]

View File

@ -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)]

View File

@ -3,7 +3,7 @@
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 {

View File

@ -15,7 +15,7 @@
};
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))
}

View File

@ -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),
)
}

View File

@ -4,16 +4,17 @@
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))
}

View File

@ -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
}

View File

@ -2,7 +2,7 @@
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 @@ fn unused_mut_simple() {
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 field_mutate() {
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 mutable_reference() {
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 regression_14310() {
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 match_closure_capture() {
//- 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 match_bindings() {
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 mutation_in_dead_code() {
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 initialization_is_not_mutation() {
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 function_arguments_are_initialized() {
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 for_loop() {
//- 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 while_let() {
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 @@ fn index_mut(&mut self, index: usize) -> &mut (i32, u8) {
}
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 @@ fn deref_mut(&mut self) -> &mut (i32, u8) {
}
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 or_pattern() {
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 @@ fn fn_traits() {
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 f() {
//- 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 slice_pattern() {
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,18 +1082,39 @@ 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
}
"#,
);
}
}

View File

@ -6,16 +6,17 @@
};
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))
}

View File

@ -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(),
},
}),
)
}

View File

@ -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()),
)
}

View File

@ -6,7 +6,7 @@
};
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(..)
"#,
);
}
}

View File

@ -7,7 +7,7 @@
};
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),

View File

@ -7,7 +7,7 @@
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>> {

View File

@ -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()),
)
}

View File

@ -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)
}

View File

@ -14,7 +14,7 @@
};
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),
);
}

View File

@ -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()),
)
}

View File

@ -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()),
)
}

View File

@ -8,7 +8,7 @@
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()

View File

@ -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)

View File

@ -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,
)

View File

@ -8,7 +8,7 @@
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()

View File

@ -3,7 +3,7 @@
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 @@ fn test_unresolved_module_diagnostic() {
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 @@ fn test_unresolved_module_diagnostic() {
},
],
),
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 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)
}

View File

@ -3,7 +3,7 @@
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",

View File

@ -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 @@ fn with_unused(mut self, unused: bool) -> 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)]
@ -228,11 +287,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);
@ -271,7 +332,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),
)
@ -309,14 +370,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);

View File

@ -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 check(minicore: MiniCore) {
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);
}

View File

@ -831,11 +831,7 @@ fn update_diagnostics(&mut self) {
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,

View File

@ -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,
}
}