internal: refactor unresolved import diagnostic

This commit is contained in:
Aleksey Kladov 2021-06-13 16:42:34 +03:00
parent 39f190b72c
commit 6d104de15a
6 changed files with 127 additions and 92 deletions

View File

@ -32,7 +32,7 @@ macro_rules! diagnostics {
};
}
diagnostics![UnresolvedModule, UnresolvedExternCrate, MissingFields];
diagnostics![UnresolvedModule, UnresolvedExternCrate, UnresolvedImport, MissingFields];
#[derive(Debug)]
pub struct UnresolvedModule {
@ -47,30 +47,7 @@ pub struct UnresolvedExternCrate {
#[derive(Debug)]
pub struct UnresolvedImport {
pub file: HirFileId,
pub node: AstPtr<ast::UseTree>,
}
impl Diagnostic for UnresolvedImport {
fn code(&self) -> DiagnosticCode {
DiagnosticCode("unresolved-import")
}
fn message(&self) -> String {
"unresolved import".to_string()
}
fn display_source(&self) -> InFile<SyntaxNodePtr> {
InFile::new(self.file, self.node.clone().into())
}
fn as_any(&self) -> &(dyn Any + Send + 'static) {
self
}
fn is_experimental(&self) -> bool {
// This currently results in false positives in the following cases:
// - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
// - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
// - proc macros and/or proc macro generated code
true
}
pub decl: InFile<AstPtr<ast::UseTree>>,
}
// Diagnostic: unresolved-macro-call

View File

@ -498,7 +498,10 @@ impl Module {
let import = &item_tree[id.value];
let use_tree = import.use_tree_to_ast(db.upcast(), file_id, *index);
sink.push(UnresolvedImport { file: file_id, node: AstPtr::new(&use_tree) });
acc.push(
UnresolvedImport { decl: InFile::new(file_id, AstPtr::new(&use_tree)) }
.into(),
);
}
DefDiagnosticKind::UnconfiguredCode { ast, cfg, opts } => {

View File

@ -12,43 +12,6 @@ fn check_no_diagnostics(ra_fixture: &str) {
db.check_no_diagnostics();
}
#[test]
fn unresolved_import() {
check_diagnostics(
r"
use does_exist;
use does_not_exist;
//^^^^^^^^^^^^^^^^^^^ UnresolvedImport
mod does_exist {}
",
);
}
#[test]
fn dedup_unresolved_import_from_unresolved_crate() {
check_diagnostics(
r"
//- /main.rs crate:main
mod a {
extern crate doesnotexist;
//^^^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedExternCrate
// Should not error, since we already errored for the missing crate.
use doesnotexist::{self, bla, *};
use crate::doesnotexist;
//^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedImport
}
mod m {
use super::doesnotexist;
//^^^^^^^^^^^^^^^^^^^^^^^^ UnresolvedImport
}
",
);
}
#[test]
fn inactive_item() {
// Additional tests in `cfg` crate. This only tests disabled cfgs.

View File

@ -6,6 +6,7 @@
mod unresolved_module;
mod unresolved_extern_crate;
mod unresolved_import;
mod missing_fields;
mod fixes;
@ -43,17 +44,39 @@ pub struct Diagnostic {
pub fixes: Option<Vec<Assist>>,
pub unused: bool,
pub code: Option<DiagnosticCode>,
pub experimental: bool,
}
impl Diagnostic {
fn new(code: &'static str, message: impl Into<String>, range: TextRange) -> Diagnostic {
let message = message.into();
let code = Some(DiagnosticCode(code));
Self { message, range, severity: Severity::Error, fixes: None, unused: false, code }
Self {
message,
range,
severity: Severity::Error,
fixes: None,
unused: false,
code,
experimental: false,
}
}
fn experimental(mut self) -> Diagnostic {
self.experimental = true;
self
}
fn error(range: TextRange, message: String) -> Self {
Self { message, range, severity: Severity::Error, fixes: None, unused: false, code: None }
Self {
message,
range,
severity: Severity::Error,
fixes: None,
unused: false,
code: None,
experimental: false,
}
}
fn hint(range: TextRange, message: String) -> Self {
@ -64,6 +87,7 @@ impl Diagnostic {
fixes: None,
unused: false,
code: None,
experimental: false,
}
}
@ -234,6 +258,7 @@ pub(crate) fn diagnostics(
let d = match diag {
AnyDiagnostic::UnresolvedModule(d) => unresolved_module::unresolved_module(&ctx, &d),
AnyDiagnostic::UnresolvedExternCrate(d) => unresolved_extern_crate::unresolved_extern_crate(&ctx, &d),
AnyDiagnostic::UnresolvedImport(d) => unresolved_import::unresolved_import(&ctx, &d),
AnyDiagnostic::MissingFields(d) => missing_fields::missing_fields(&ctx, &d),
};
if let Some(code) = d.code {
@ -241,6 +266,9 @@ pub(crate) fn diagnostics(
continue;
}
}
if ctx.config.disable_experimental && d.experimental {
continue;
}
res.push(d)
}
@ -462,33 +490,6 @@ foo::bar!(92);
);
}
#[test]
fn unresolved_import_in_use_tree() {
// Only the relevant part of a nested `use` item should be highlighted.
check_diagnostics(
r#"
use does_exist::{Exists, DoesntExist};
//^^^^^^^^^^^ unresolved import
use {does_not_exist::*, does_exist};
//^^^^^^^^^^^^^^^^^ unresolved import
use does_not_exist::{
a,
//^ unresolved import
b,
//^ unresolved import
c,
//^ unresolved import
};
mod does_exist {
pub struct Exists;
}
"#,
);
}
#[test]
fn range_mapping_out_of_macros() {
// FIXME: this is very wrong, but somewhat tricky to fix.

View File

@ -0,0 +1,90 @@
use crate::diagnostics::{Diagnostic, DiagnosticsContext};
// Diagnostic: unresolved-import
//
// This diagnostic is triggered if rust-analyzer is unable to resolve a path in
// a `use` declaration.
pub(super) fn unresolved_import(
ctx: &DiagnosticsContext<'_>,
d: &hir::UnresolvedImport,
) -> Diagnostic {
Diagnostic::new(
"unresolved-import",
"unresolved import",
ctx.sema.diagnostics_display_range(d.decl.clone().map(|it| it.into())).range,
)
// This currently results in false positives in the following cases:
// - `cfg_if!`-generated code in libstd (we don't load the sysroot correctly)
// - `core::arch` (we don't handle `#[path = "../<path>"]` correctly)
// - proc macros and/or proc macro generated code
.experimental()
}
#[cfg(test)]
mod tests {
use crate::diagnostics::tests::check_diagnostics;
#[test]
fn unresolved_import() {
check_diagnostics(
r#"
use does_exist;
use does_not_exist;
//^^^^^^^^^^^^^^ unresolved import
mod does_exist {}
"#,
);
}
#[test]
fn unresolved_import_in_use_tree() {
// Only the relevant part of a nested `use` item should be highlighted.
check_diagnostics(
r#"
use does_exist::{Exists, DoesntExist};
//^^^^^^^^^^^ unresolved import
use {does_not_exist::*, does_exist};
//^^^^^^^^^^^^^^^^^ unresolved import
use does_not_exist::{
a,
//^ unresolved import
b,
//^ unresolved import
c,
//^ unresolved import
};
mod does_exist {
pub struct Exists;
}
"#,
);
}
#[test]
fn dedup_unresolved_import_from_unresolved_crate() {
check_diagnostics(
r#"
//- /main.rs crate:main
mod a {
extern crate doesnotexist;
//^^^^^^^^^^^^^^^^^^^^^^^^^^ unresolved extern crate
// Should not error, since we already errored for the missing crate.
use doesnotexist::{self, bla, *};
use crate::doesnotexist;
//^^^^^^^^^^^^^^^^^^^ unresolved import
}
mod m {
use super::doesnotexist;
//^^^^^^^^^^^^^^^^^^^ unresolved import
}
"#,
);
}
}

View File

@ -104,6 +104,7 @@ mod baz {}
"unresolved-module",
),
),
experimental: false,
},
]
"#]],