From cc66e1d7dcdbaeed27863304de40eafd92faafc4 Mon Sep 17 00:00:00 2001 From: Hegui Dai Date: Tue, 11 Nov 2025 10:39:17 +0800 Subject: [PATCH] Add visibility diagnostics for private fields to the correct location --- .../src/handlers/private_field.rs | 206 +++++++++++++++++- 1 file changed, 199 insertions(+), 7 deletions(-) diff --git a/crates/ide-diagnostics/src/handlers/private_field.rs b/crates/ide-diagnostics/src/handlers/private_field.rs index 69cd0d27cb06..23f046007592 100644 --- a/crates/ide-diagnostics/src/handlers/private_field.rs +++ b/crates/ide-diagnostics/src/handlers/private_field.rs @@ -1,6 +1,9 @@ use hir::{EditionedFileId, FileRange, HasCrate, HasSource, Semantics}; use ide_db::{RootDatabase, assists::Assist, source_change::SourceChange, text_edit::TextEdit}; -use syntax::{AstNode, TextRange, TextSize, ast::HasVisibility}; +use syntax::{ + AstNode, TextRange, + ast::{HasName, HasVisibility}, +}; use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, fix}; @@ -8,7 +11,6 @@ use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, fix}; // // 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_with_syntax_node_ptr( ctx, DiagnosticCode::RustcHardError("E0616"), @@ -50,11 +52,19 @@ pub(crate) fn field_is_private_fixes( source.with_value(visibility.syntax()).original_file_range_opt(sema.db)?.0 } None => { - let (range, _) = source.syntax().original_file_range_opt(sema.db)?; - FileRange { - file_id: range.file_id, - range: TextRange::at(range.range.start(), TextSize::new(0)), - } + let (range, _) = source + .map(|it| { + Some(match it { + hir::FieldSource::Named(it) => { + it.unsafe_token().or(it.name()?.ident_token())?.text_range() + } + hir::FieldSource::Pos(it) => it.ty()?.syntax().text_range(), + }) + }) + .transpose()? + .original_node_file_range_opt(sema.db)?; + + FileRange { file_id: range.file_id, range: TextRange::empty(range.range.start()) } } }; let source_change = SourceChange::from_text_edit( @@ -225,6 +235,188 @@ pub mod foo { fn foo(v: foo::bar::Struct) { v.field; +} + "#, + ); + } + + #[test] + fn change_visibility_of_field_with_doc_comment() { + check_fix( + r#" +pub mod foo { + pub struct Foo { + /// This is a doc comment + bar: u32, + } +} + +fn main() { + let x = foo::Foo { bar: 0 }; + x.bar$0; +} + "#, + r#" +pub mod foo { + pub struct Foo { + /// This is a doc comment + pub(crate) bar: u32, + } +} + +fn main() { + let x = foo::Foo { bar: 0 }; + x.bar; +} + "#, + ); + } + + #[test] + fn change_visibility_of_field_with_line_comment() { + check_fix( + r#" +pub mod foo { + pub struct Foo { + // This is a line comment + bar: u32, + } +} + +fn main() { + let x = foo::Foo { bar: 0 }; + x.bar$0; +} + "#, + r#" +pub mod foo { + pub struct Foo { + // This is a line comment + pub(crate) bar: u32, + } +} + +fn main() { + let x = foo::Foo { bar: 0 }; + x.bar; +} + "#, + ); + } + + #[test] + fn change_visibility_of_field_with_multiple_doc_comments() { + check_fix( + r#" +pub mod foo { + pub struct Foo { + /// First line + /// Second line + bar: u32, + } +} + +fn main() { + let x = foo::Foo { bar: 0 }; + x.bar$0; +} + "#, + r#" +pub mod foo { + pub struct Foo { + /// First line + /// Second line + pub(crate) bar: u32, + } +} + +fn main() { + let x = foo::Foo { bar: 0 }; + x.bar; +} + "#, + ); + } + + #[test] + fn change_visibility_of_field_with_attr_and_comment() { + check_fix( + r#" +mod foo { + pub struct Foo { + #[rustfmt::skip] + /// First line + /// Second line + bar: u32, + } +} +fn main() { + foo::Foo { $0bar: 42 }; +} + "#, + r#" +mod foo { + pub struct Foo { + #[rustfmt::skip] + /// First line + /// Second line + pub(crate) bar: u32, + } +} +fn main() { + foo::Foo { bar: 42 }; +} + "#, + ); + } + + #[test] + fn change_visibility_of_field_with_macro() { + check_fix( + r#" +macro_rules! allow_unused { + ($vis:vis $struct:ident $name:ident { $($fvis:vis $field:ident : $ty:ty,)* }) => { + $vis $struct $name { + $( + #[allow(unused)] + $fvis $field : $ty, + )* + } + }; +} +mod foo { + allow_unused!( + pub struct Foo { + x: i32, + } + ); +} +fn main() { + let foo = foo::Foo { x: 2 }; + let _ = foo.$0x +} + "#, + r#" +macro_rules! allow_unused { + ($vis:vis $struct:ident $name:ident { $($fvis:vis $field:ident : $ty:ty,)* }) => { + $vis $struct $name { + $( + #[allow(unused)] + $fvis $field : $ty, + )* + } + }; +} +mod foo { + allow_unused!( + pub struct Foo { + pub(crate) x: i32, + } + ); +} +fn main() { + let foo = foo::Foo { x: 2 }; + let _ = foo.x } "#, );