Respect #[doc(hidden)] in dot-completion

This commit is contained in:
Jonas Schievink 2021-07-23 15:36:43 +02:00
parent 1dd1814100
commit b0f7aac72f
6 changed files with 136 additions and 13 deletions

View File

@ -11,8 +11,8 @@
use syntax::ast;
use crate::{
Adt, Const, ConstParam, Enum, Field, Function, GenericParam, Impl, LifetimeParam, MacroDef,
Module, ModuleDef, Static, Struct, Trait, TypeAlias, TypeParam, Union, Variant,
Adt, AssocItem, Const, ConstParam, Enum, Field, Function, GenericParam, Impl, LifetimeParam,
MacroDef, Module, ModuleDef, Static, Struct, Trait, TypeAlias, TypeParam, Union, Variant,
};
pub trait HasAttrs {
@ -86,6 +86,37 @@ fn resolve_doc_path(self, db: &dyn HirDatabase, link: &str, ns: Option<Namespace
impl_has_attrs_enum![Struct, Union, Enum for Adt];
impl_has_attrs_enum![TypeParam, ConstParam, LifetimeParam for GenericParam];
impl HasAttrs for AssocItem {
fn attrs(self, db: &dyn HirDatabase) -> AttrsWithOwner {
match self {
AssocItem::Function(it) => it.attrs(db),
AssocItem::Const(it) => it.attrs(db),
AssocItem::TypeAlias(it) => it.attrs(db),
}
}
fn docs(self, db: &dyn HirDatabase) -> Option<Documentation> {
match self {
AssocItem::Function(it) => it.docs(db),
AssocItem::Const(it) => it.docs(db),
AssocItem::TypeAlias(it) => it.docs(db),
}
}
fn resolve_doc_path(
self,
db: &dyn HirDatabase,
link: &str,
ns: Option<Namespace>,
) -> Option<ModuleDef> {
match self {
AssocItem::Function(it) => it.resolve_doc_path(db, link, ns),
AssocItem::Const(it) => it.resolve_doc_path(db, link, ns),
AssocItem::TypeAlias(it) => it.resolve_doc_path(db, link, ns),
}
}
}
fn resolve_doc_path(
db: &dyn HirDatabase,
def: AttrDefId,

View File

@ -2744,3 +2744,32 @@ fn is_visible_from(&self, db: &dyn HirDatabase, module: Module) -> bool {
vis.is_visible_from(db.upcast(), module.id)
}
}
/// Trait for obtaining the defining crate of an item.
pub trait HasCrate {
fn krate(&self, db: &dyn HirDatabase) -> Crate;
}
impl<T: hir_def::HasModule> HasCrate for T {
fn krate(&self, db: &dyn HirDatabase) -> Crate {
self.module(db.upcast()).krate().into()
}
}
impl HasCrate for AssocItem {
fn krate(&self, db: &dyn HirDatabase) -> Crate {
self.module(db).krate()
}
}
impl HasCrate for Field {
fn krate(&self, db: &dyn HirDatabase) -> Crate {
self.parent_def(db).module(db).krate()
}
}
impl HasCrate for Function {
fn krate(&self, db: &dyn HirDatabase) -> Crate {
self.module(db).krate()
}
}

View File

@ -12,7 +12,7 @@
use hir_expand::{hygiene::Hygiene, name::AsName, AstId, InFile};
use itertools::Itertools;
use la_arena::ArenaMap;
use mbe::ast_to_token_tree;
use mbe::{ast_to_token_tree, DelimiterKind};
use smallvec::{smallvec, SmallVec};
use syntax::{
ast::{self, AstNode, AttrsOwner},
@ -290,6 +290,13 @@ pub fn docs(&self) -> Option<Documentation> {
Some(Documentation(buf))
}
}
pub fn has_doc_hidden(&self) -> bool {
self.by_key("doc").tt_values().find(|tt| {
tt.delimiter_kind() == Some(DelimiterKind::Parenthesis) &&
matches!(&*tt.token_trees, [tt::TokenTree::Leaf(tt::Leaf::Ident(ident))] if ident.text == "hidden")
}).is_some()
}
}
impl AttrsWithOwner {

View File

@ -1,7 +1,7 @@
//! Completes references after dot (fields and method calls).
use either::Either;
use hir::{HasVisibility, ScopeDef};
use hir::ScopeDef;
use rustc_hash::FxHashSet;
use crate::{context::CompletionContext, patterns::ImmediateLocation, Completions};
@ -63,9 +63,7 @@ fn complete_fields(
) {
for receiver in receiver.autoderef(ctx.db) {
for (field, ty) in receiver.fields(ctx.db) {
if ctx.scope.module().map_or(false, |m| !field.is_visible_from(ctx.db, m)) {
// Skip private field. FIXME: If the definition location of the
// field is editable, we should show the completion
if !ctx.is_visible(&field) {
continue;
}
f(Either::Left(field), ty);
@ -87,7 +85,7 @@ fn complete_methods(
let traits_in_scope = ctx.scope.traits_in_scope();
receiver.iterate_method_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, func| {
if func.self_param(ctx.db).is_some()
&& ctx.scope.module().map_or(true, |m| func.is_visible_from(ctx.db, m))
&& ctx.is_visible(&func)
&& seen_methods.insert(func.name(ctx.db))
{
f(func);
@ -210,6 +208,33 @@ fn foo(a: A) { a.$0 }
);
}
#[test]
fn test_doc_hidden_filtering() {
check(
r#"
//- /lib.rs crate:lib deps:dep
fn foo(a: dep::A) { a.$0 }
//- /dep.rs crate:dep
pub struct A {
#[doc(hidden)]
pub hidden_field: u32,
pub pub_field: u32,
}
impl A {
pub fn pub_method(&self) {}
#[doc(hidden)]
pub fn hidden_method(&self) {}
}
"#,
expect![[r#"
fd pub_field u32
me pub_method() fn(&self)
"#]]
)
}
#[test]
fn test_union_field_completion() {
check(

View File

@ -2,7 +2,6 @@
use std::iter;
use hir::HasVisibility;
use rustc_hash::FxHashSet;
use syntax::{ast, AstNode};
@ -120,6 +119,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
_ => true,
};
// FIXME: respect #[doc(hidden)] (see `CompletionContext::is_visible`)
if add_resolution {
acc.add_resolution(ctx, name, &def);
}
@ -163,7 +163,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
if let Some(krate) = krate {
let traits_in_scope = ctx.scope.traits_in_scope();
ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| {
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
if !ctx.is_visible(&item) {
return None;
}
add_assoc_item(acc, ctx, item);
@ -172,7 +172,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
// Iterate assoc types separately
ty.iterate_assoc_items(ctx.db, krate, |item| {
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
if !ctx.is_visible(&item) {
return None;
}
if let hir::AssocItem::TypeAlias(ty) = item {
@ -185,7 +185,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => {
// Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`.
for item in t.items(ctx.db) {
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
if !ctx.is_visible(&item) {
continue;
}
add_assoc_item(acc, ctx, item);
@ -206,7 +206,7 @@ pub(crate) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
let traits_in_scope = ctx.scope.traits_in_scope();
let mut seen = FxHashSet::default();
ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| {
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
if !ctx.is_visible(&item) {
return None;
}

View File

@ -361,6 +361,37 @@ pub(crate) fn path_qual(&self) -> Option<&ast::Path> {
self.path_context.as_ref().and_then(|it| it.qualifier.as_ref())
}
/// Checks if an item is visible and not `doc(hidden)` at the completion site.
pub(crate) fn is_visible<I>(&self, item: &I) -> bool
where
I: hir::HasVisibility + hir::HasAttrs + hir::HasCrate + Copy,
{
self.is_visible_impl(&item.visibility(self.db), &item.attrs(self.db), item.krate(self.db))
}
fn is_visible_impl(
&self,
vis: &hir::Visibility,
attrs: &hir::Attrs,
defining_crate: hir::Crate,
) -> bool {
let module = match self.scope.module() {
Some(it) => it,
None => return false,
};
if !vis.is_visible_from(self.db, module.into()) {
// FIXME: if the definition location is editable, also show private items
return false;
}
if module.krate() != defining_crate && attrs.has_doc_hidden() {
// `doc(hidden)` items are only completed within the defining crate.
return false;
}
true
}
fn fill_impl_def(&mut self) {
self.impl_def = self
.sema