summaryrefslogtreecommitdiffstats
path: root/src/librustdoc/passes
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/passes')
-rw-r--r--src/librustdoc/passes/calculate_doc_coverage.rs8
-rw-r--r--src/librustdoc/passes/check_doc_test_visibility.rs11
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs222
-rw-r--r--src/librustdoc/passes/collect_trait_impls.rs4
-rw-r--r--src/librustdoc/passes/lint.rs2
-rw-r--r--src/librustdoc/passes/lint/bare_urls.rs13
-rw-r--r--src/librustdoc/passes/lint/check_code_block_syntax.rs19
-rw-r--r--src/librustdoc/passes/lint/html_tags.rs14
-rw-r--r--src/librustdoc/passes/lint/redundant_explicit_links.rs347
-rw-r--r--src/librustdoc/passes/propagate_doc_cfg.rs5
-rw-r--r--src/librustdoc/passes/strip_hidden.rs3
-rw-r--r--src/librustdoc/passes/strip_priv_imports.rs7
-rw-r--r--src/librustdoc/passes/strip_private.rs9
-rw-r--r--src/librustdoc/passes/stripper.rs19
14 files changed, 569 insertions, 114 deletions
diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs
index 6ead0cd96..592dd0a14 100644
--- a/src/librustdoc/passes/calculate_doc_coverage.rs
+++ b/src/librustdoc/passes/calculate_doc_coverage.rs
@@ -146,8 +146,10 @@ impl<'a, 'b> CoverageCalculator<'a, 'b> {
examples_percentage: f64,
) {
println!(
- "| {:<35} | {:>10} | {:>9.1}% | {:>10} | {:>9.1}% |",
- name, count.with_docs, percentage, count.with_examples, examples_percentage,
+ "| {name:<35} | {with_docs:>10} | {percentage:>9.1}% | {with_examples:>10} | \
+ {examples_percentage:>9.1}% |",
+ with_docs = count.with_docs,
+ with_examples = count.with_examples,
);
}
@@ -249,7 +251,7 @@ impl<'a, 'b> DocVisitor for CoverageCalculator<'a, 'b> {
if let Some(span) = i.span(self.ctx.tcx) {
let filename = span.filename(self.ctx.sess());
- debug!("counting {:?} {:?} in {:?}", i.type_(), i.name, filename);
+ debug!("counting {:?} {:?} in {filename:?}", i.type_(), i.name);
self.items.entry(filename).or_default().count_item(
has_docs,
has_doc_example,
diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs
index b6cd897d3..e732a4057 100644
--- a/src/librustdoc/passes/check_doc_test_visibility.rs
+++ b/src/librustdoc/passes/check_doc_test_visibility.rs
@@ -7,11 +7,11 @@
use super::Pass;
use crate::clean;
+use crate::clean::utils::inherits_doc_hidden;
use crate::clean::*;
use crate::core::DocContext;
use crate::html::markdown::{find_testable_code, ErrorCodes, Ignore, LangString};
use crate::visit::DocVisitor;
-use crate::visit_ast::inherits_doc_hidden;
use rustc_hir as hir;
use rustc_middle::lint::LintLevelSource;
use rustc_session::lint;
@@ -92,8 +92,8 @@ pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -
return false;
}
- if cx.tcx.is_doc_hidden(def_id.to_def_id())
- || inherits_doc_hidden(cx.tcx, def_id, None)
+ if (!cx.render_options.document_hidden
+ && (cx.tcx.is_doc_hidden(def_id.to_def_id()) || inherits_doc_hidden(cx.tcx, def_id, None)))
|| cx.tcx.def_span(def_id.to_def_id()).in_derive_expansion()
{
return false;
@@ -106,8 +106,7 @@ pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -
}
pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item) {
- let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id)
- else {
+ let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) else {
// If non-local, no need to check anything.
return;
};
@@ -118,7 +117,7 @@ pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item
if tests.found_tests == 0 && cx.tcx.features().rustdoc_missing_doc_code_examples {
if should_have_doc_example(cx, item) {
- debug!("reporting error for {:?} (hir_id={:?})", item, hir_id);
+ debug!("reporting error for {item:?} (hir_id={hir_id:?})");
let sp = item.attr_span(cx.tcx);
cx.tcx.struct_span_lint_hir(
crate::lint::MISSING_DOC_CODE_EXAMPLES,
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 0dd9e590b..7b0a7a90d 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -14,7 +14,7 @@ use rustc_hir::def::{DefKind, Namespace, PerNS};
use rustc_hir::def_id::{DefId, CRATE_DEF_ID};
use rustc_hir::Mutability;
use rustc_middle::ty::{Ty, TyCtxt};
-use rustc_middle::{bug, ty};
+use rustc_middle::{bug, span_bug, ty};
use rustc_resolve::rustdoc::{has_primitive_or_keyword_docs, prepare_to_doc_link_resolution};
use rustc_resolve::rustdoc::{strip_generics_from_path, MalformedGenerics};
use rustc_session::lint::Lint;
@@ -150,7 +150,7 @@ impl TryFrom<ResolveRes> for Res {
PrimTy(prim) => Ok(Res::Primitive(PrimitiveType::from_hir(prim))),
// e.g. `#[derive]`
ToolMod | NonMacroAttr(..) | Err => Result::Err(()),
- other => bug!("unrecognized res {:?}", other),
+ other => bug!("unrecognized res {other:?}"),
}
}
}
@@ -224,7 +224,7 @@ impl UrlFragment {
"structfield."
}
}
- kind => bug!("unexpected associated item kind: {:?}", kind),
+ kind => bug!("unexpected associated item kind: {kind:?}"),
};
s.push_str(kind);
s.push_str(tcx.item_name(def_id).as_str());
@@ -279,7 +279,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
unresolved: path_str.into(),
};
- debug!("looking for enum variant {}", path_str);
+ debug!("looking for enum variant {path_str}");
let mut split = path_str.rsplitn(3, "::");
let variant_field_name = split
.next()
@@ -298,7 +298,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
let ty_res = self.resolve_path(&path, TypeNS, item_id, module_id).ok_or_else(no_res)?;
match ty_res {
- Res::Def(DefKind::Enum, did) => match tcx.type_of(did).subst_identity().kind() {
+ Res::Def(DefKind::Enum, did) => match tcx.type_of(did).instantiate_identity().kind() {
ty::Adt(def, _) if def.is_enum() => {
if let Some(variant) = def.variants().iter().find(|v| v.name == variant_name)
&& let Some(field) = variant.fields.iter().find(|f| f.name == variant_field_name) {
@@ -402,10 +402,15 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
// `doc_link_resolutions` is missing a `path_str`, that means that there are valid links
// that are being missed. To fix the ICE, change
// `rustc_resolve::rustdoc::attrs_to_preprocessed_links` to cache the link.
- .unwrap_or_else(|| panic!("no resolution for {:?} {:?} {:?}", path_str, ns, module_id))
+ .unwrap_or_else(|| {
+ span_bug!(
+ self.cx.tcx.def_span(item_id),
+ "no resolution for {path_str:?} {ns:?} {module_id:?}",
+ )
+ })
.and_then(|res| res.try_into().ok())
.or_else(|| resolve_primitive(path_str, ns));
- debug!("{} resolved to {:?} in namespace {:?}", path_str, result, ns);
+ debug!("{path_str} resolved to {result:?} in namespace {ns:?}");
result
}
@@ -448,7 +453,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
// If there's no `::`, it's not an associated item.
// So we can be sure that `rustc_resolve` was accurate when it said it wasn't resolved.
.ok_or_else(|| {
- debug!("found no `::`, assuming {} was correctly not in scope", item_name);
+ debug!("found no `::`, assuming {item_name} was correctly not in scope");
UnresolvedPath {
item_id,
module_id,
@@ -493,7 +498,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
/// This is used for resolving type aliases.
fn def_id_to_res(&self, ty_id: DefId) -> Option<Res> {
use PrimitiveType::*;
- Some(match *self.cx.tcx.type_of(ty_id).subst_identity().kind() {
+ Some(match *self.cx.tcx.type_of(ty_id).instantiate_identity().kind() {
ty::Bool => Res::Primitive(Bool),
ty::Char => Res::Primitive(Char),
ty::Int(ity) => Res::Primitive(ity.into()),
@@ -587,7 +592,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
.unwrap_or(Vec::new())
}
}
- Res::Def(DefKind::TyAlias, did) => {
+ Res::Def(DefKind::TyAlias { .. }, did) => {
// Resolve the link on the type the alias points to.
// FIXME: if the associated item is defined directly on the type alias,
// it will show up on its documentation page, we should link there instead.
@@ -598,10 +603,10 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
def_kind @ (DefKind::Struct | DefKind::Union | DefKind::Enum | DefKind::ForeignTy),
did,
) => {
- debug!("looking for associated item named {} for item {:?}", item_name, did);
+ debug!("looking for associated item named {item_name} for item {did:?}");
// Checks if item_name is a variant of the `SomeItem` enum
if ns == TypeNS && def_kind == DefKind::Enum {
- match tcx.type_of(did).subst_identity().kind() {
+ match tcx.type_of(did).instantiate_identity().kind() {
ty::Adt(adt_def, _) => {
for variant in adt_def.variants() {
if variant.name == item_name {
@@ -635,7 +640,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
// To handle that properly resolve() would have to support
// something like [`ambi_fn`](<SomeStruct as SomeTrait>::ambi_fn)
assoc_items = resolve_associated_trait_item(
- tcx.type_of(did).subst_identity(),
+ tcx.type_of(did).instantiate_identity(),
module_id,
item_name,
ns,
@@ -646,7 +651,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
.collect::<Vec<_>>();
}
- debug!("got associated item {:?}", assoc_items);
+ debug!("got associated item {assoc_items:?}");
if !assoc_items.is_empty() {
return assoc_items;
@@ -655,7 +660,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
if ns != Namespace::ValueNS {
return Vec::new();
}
- debug!("looking for fields named {} for {:?}", item_name, did);
+ debug!("looking for fields named {item_name} for {did:?}");
// FIXME: this doesn't really belong in `associated_item` (maybe `variant_field` is better?)
// NOTE: it's different from variant_field because it only resolves struct fields,
// not variant fields (2 path segments, not 3).
@@ -671,7 +676,7 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> {
// they also look like associated items (`module::Type::Variant`),
// because they are real Rust syntax (unlike the intra-doc links
// field syntax) and are handled by the compiler's resolver.
- let def = match tcx.type_of(did).subst_identity().kind() {
+ let def = match tcx.type_of(did).instantiate_identity().kind() {
ty::Adt(def, _) if !def.is_enum() => def,
_ => return Vec::new(),
};
@@ -722,7 +727,7 @@ fn resolve_associated_trait_item<'a>(
// Give precedence to inherent impls.
let traits = trait_impls_for(cx, ty, module);
let tcx = cx.tcx;
- debug!("considering traits {:?}", traits);
+ debug!("considering traits {traits:?}");
let candidates = traits
.iter()
.flat_map(|&(impl_, trait_)| {
@@ -739,7 +744,7 @@ fn resolve_associated_trait_item<'a>(
})
.collect::<Vec<_>>();
// FIXME(#74563): warn about ambiguity
- debug!("the candidates were {:?}", candidates);
+ debug!("the candidates were {candidates:?}");
candidates
}
@@ -785,10 +790,8 @@ fn trait_impls_for<'a>(
// Check if these are the same type.
let impl_type = trait_ref.skip_binder().self_ty();
trace!(
- "comparing type {} with kind {:?} against type {:?}",
- impl_type,
- impl_type.kind(),
- ty
+ "comparing type {impl_type} with kind {kind:?} against type {ty:?}",
+ kind = impl_type.kind(),
);
// Fast path: if this is a primitive simple `==` will work
// NOTE: the `match` is necessary; see #92662.
@@ -935,7 +938,7 @@ fn preprocess_link(
let path_str = match strip_generics_from_path(path_str) {
Ok(path) => path,
Err(err) => {
- debug!("link has malformed generics: {}", path_str);
+ debug!("link has malformed generics: {path_str}");
return Some(Err(PreprocessingError::MalformedGenerics(err, path_str.to_owned())));
}
};
@@ -963,6 +966,7 @@ fn preprocessed_markdown_links(s: &str) -> Vec<PreprocessedMarkdownLink> {
}
impl LinkCollector<'_, '_> {
+ #[instrument(level = "debug", skip_all)]
fn resolve_links(&mut self, item: &Item) {
if !self.cx.render_options.document_private
&& let Some(def_id) = item.item_id.as_def_id()
@@ -981,7 +985,7 @@ impl LinkCollector<'_, '_> {
if !may_have_doc_links(&doc) {
continue;
}
- debug!("combined_docs={}", doc);
+ debug!("combined_docs={doc}");
// NOTE: if there are links that start in one crate and end in another, this will not resolve them.
// This is a degenerate case and it's not supported by rustdoc.
let item_id = item_id.unwrap_or_else(|| item.item_id.expect_def_id());
@@ -990,7 +994,7 @@ impl LinkCollector<'_, '_> {
_ => find_nearest_parent_module(self.cx.tcx, item_id).unwrap(),
};
for md_link in preprocessed_markdown_links(&doc) {
- let link = self.resolve_link(item, item_id, module_id, &doc, &md_link);
+ let link = self.resolve_link(&doc, item, item_id, module_id, &md_link);
if let Some(link) = link {
self.cx.cache.intra_doc_links.entry(item.item_id).or_default().insert(link);
}
@@ -1003,13 +1007,12 @@ impl LinkCollector<'_, '_> {
/// FIXME(jynelson): this is way too many arguments
fn resolve_link(
&mut self,
+ dox: &String,
item: &Item,
item_id: DefId,
module_id: DefId,
- dox: &str,
- link: &PreprocessedMarkdownLink,
+ PreprocessedMarkdownLink(pp_link, ori_link): &PreprocessedMarkdownLink,
) -> Option<ItemLink> {
- let PreprocessedMarkdownLink(pp_link, ori_link) = link;
trace!("considering link '{}'", ori_link.link);
let diag_info = DiagnosticInfo {
@@ -1018,7 +1021,6 @@ impl LinkCollector<'_, '_> {
ori_link: &ori_link.link,
link_range: ori_link.range.clone(),
};
-
let PreprocessingInfo { path_str, disambiguator, extra_fragment, link_text } =
pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?;
let disambiguator = *disambiguator;
@@ -1036,8 +1038,24 @@ impl LinkCollector<'_, '_> {
// resolutions are cached, for other links we want to report an error every
// time so they are not cached.
matches!(ori_link.kind, LinkType::Reference | LinkType::Shortcut),
+ false,
)?;
+ if ori_link.display_text.is_some() {
+ self.resolve_display_text(
+ path_str,
+ ResolutionInfo {
+ item_id,
+ module_id,
+ dis: disambiguator,
+ path_str: ori_link.display_text.clone()?.into_boxed_str(),
+ extra_fragment: extra_fragment.clone(),
+ },
+ &ori_link,
+ &diag_info,
+ );
+ }
+
// Check for a primitive which might conflict with a module
// Report the ambiguity and require that the user specify which one they meant.
// FIXME: could there ever be a primitive not in the type namespace?
@@ -1124,10 +1142,10 @@ impl LinkCollector<'_, '_> {
item: &Item,
diag_info: &DiagnosticInfo<'_>,
) -> Option<()> {
- debug!("intra-doc link to {} resolved to {:?}", path_str, (kind, id));
+ debug!("intra-doc link to {path_str} resolved to {:?}", (kind, id));
// Disallow e.g. linking to enums with `struct@`
- debug!("saw kind {:?} with disambiguator {:?}", kind, disambiguator);
+ debug!("saw kind {kind:?} with disambiguator {disambiguator:?}");
match (kind, disambiguator) {
| (DefKind::Const | DefKind::ConstParam | DefKind::AssocConst | DefKind::AnonConst, Some(Disambiguator::Kind(DefKind::Const)))
// NOTE: this allows 'method' to mean both normal functions and associated functions
@@ -1168,7 +1186,7 @@ impl LinkCollector<'_, '_> {
diag_info: &DiagnosticInfo<'_>,
) {
// The resolved item did not match the disambiguator; give a better error than 'not found'
- let msg = format!("incompatible link kind for `{}`", path_str);
+ let msg = format!("incompatible link kind for `{path_str}`");
let callback = |diag: &mut Diagnostic, sp: Option<rustc_span::Span>, link_range| {
let note = format!(
"this link resolved to {} {}, which is not {} {}",
@@ -1217,6 +1235,9 @@ impl LinkCollector<'_, '_> {
// If errors are cached then they are only reported on first occurrence
// which we want in some cases but not in others.
cache_errors: bool,
+ // If this call is intended to be recoverable, then pass true to silence.
+ // This is only recoverable when path is failed to resolved.
+ recoverable: bool,
) -> Option<(Res, Option<UrlFragment>)> {
if let Some(res) = self.visited_links.get(&key) {
if res.is_some() || cache_errors {
@@ -1224,7 +1245,7 @@ impl LinkCollector<'_, '_> {
}
}
- let mut candidates = self.resolve_with_disambiguator(&key, diag.clone());
+ let mut candidates = self.resolve_with_disambiguator(&key, diag.clone(), recoverable);
// FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether.
// However I'm not sure how to check that across crates.
@@ -1275,6 +1296,9 @@ impl LinkCollector<'_, '_> {
&mut self,
key: &ResolutionInfo,
diag: DiagnosticInfo<'_>,
+ // If this call is intended to be recoverable, then pass true to silence.
+ // This is only recoverable when path is failed to resolved.
+ recoverable: bool,
) -> Vec<(Res, Option<DefId>)> {
let disambiguator = key.dis;
let path_str = &key.path_str;
@@ -1304,7 +1328,9 @@ impl LinkCollector<'_, '_> {
}
}
}
- resolution_failure(self, diag, path_str, disambiguator, smallvec![err]);
+ if !recoverable {
+ resolution_failure(self, diag, path_str, disambiguator, smallvec![err]);
+ }
return vec![];
}
}
@@ -1341,13 +1367,15 @@ impl LinkCollector<'_, '_> {
.fold(0, |acc, res| if let Ok(res) = res { acc + res.len() } else { acc });
if len == 0 {
- resolution_failure(
- self,
- diag,
- path_str,
- disambiguator,
- candidates.into_iter().filter_map(|res| res.err()).collect(),
- );
+ if !recoverable {
+ resolution_failure(
+ self,
+ diag,
+ path_str,
+ disambiguator,
+ candidates.into_iter().filter_map(|res| res.err()).collect(),
+ );
+ }
return vec![];
} else if len == 1 {
candidates.into_iter().filter_map(|res| res.ok()).flatten().collect::<Vec<_>>()
@@ -1368,6 +1396,58 @@ impl LinkCollector<'_, '_> {
}
}
}
+
+ /// Resolve display text if the provided link has separated parts of links.
+ ///
+ /// For example:
+ /// Inline link `[display_text](dest_link)` and reference link `[display_text][reference_link]` has
+ /// separated parts of links.
+ fn resolve_display_text(
+ &mut self,
+ explicit_link: &Box<str>,
+ display_res_info: ResolutionInfo,
+ ori_link: &MarkdownLink,
+ diag_info: &DiagnosticInfo<'_>,
+ ) {
+ // Check if explicit resolution's path is same as resolution of original link's display text path, see
+ // tests/rustdoc-ui/lint/redundant_explicit_links.rs for more cases.
+ //
+ // To avoid disambiguator from panicking, we check if display text path is possible to be disambiguated
+ // into explicit path.
+ if !matches!(
+ ori_link.kind,
+ LinkType::Inline | LinkType::Reference | LinkType::ReferenceUnknown
+ ) {
+ return;
+ }
+
+ // Algorithm to check if display text could possibly be the explicit link:
+ //
+ // Consider 2 links which are display text and explicit link, pick the shorter
+ // one as symbol and longer one as full qualified path, and tries to match symbol
+ // to the full qualified path's last symbol.
+ //
+ // Otherwise, check if 2 links are same, if so, skip the resolve process.
+ //
+ // Notice that this algorithm is passive, might possibly miss actual redudant cases.
+ let explicit_link = explicit_link.to_string();
+ let display_text = ori_link.display_text.as_ref().unwrap();
+
+ if display_text.len() == explicit_link.len() {
+ // Whether they are same or not, skip the resolve process.
+ return;
+ }
+
+ if explicit_link.ends_with(&display_text[..]) || display_text.ends_with(&explicit_link[..])
+ {
+ self.resolve_with_disambiguator_cached(
+ display_res_info,
+ diag_info.clone(), // this struct should really be Copy, but Range is not :(
+ false,
+ true,
+ );
+ }
+ }
}
/// Get the section of a link between the backticks,
@@ -1453,7 +1533,7 @@ impl Disambiguator {
"value" => NS(Namespace::ValueNS),
"macro" => NS(Namespace::MacroNS),
"prim" | "primitive" => Primitive,
- _ => return Err((format!("unknown disambiguator `{}`", prefix), 0..idx)),
+ _ => return Err((format!("unknown disambiguator `{prefix}`"), 0..idx)),
};
Ok(Some((d, &rest[1..], &rest[1..])))
} else {
@@ -1521,7 +1601,7 @@ enum Suggestion {
impl Suggestion {
fn descr(&self) -> Cow<'static, str> {
match self {
- Self::Prefix(x) => format!("prefix with `{}@`", x).into(),
+ Self::Prefix(x) => format!("prefix with `{x}@`").into(),
Self::Function => "add parentheses".into(),
Self::Macro => "add an exclamation mark".into(),
Self::RemoveDisambiguator => "remove the disambiguator".into(),
@@ -1531,9 +1611,9 @@ impl Suggestion {
fn as_help(&self, path_str: &str) -> String {
// FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
match self {
- Self::Prefix(prefix) => format!("{}@{}", prefix, path_str),
- Self::Function => format!("{}()", path_str),
- Self::Macro => format!("{}!", path_str),
+ Self::Prefix(prefix) => format!("{prefix}@{path_str}"),
+ Self::Function => format!("{path_str}()"),
+ Self::Macro => format!("{path_str}!"),
Self::RemoveDisambiguator => path_str.into(),
}
}
@@ -1568,7 +1648,7 @@ impl Suggestion {
match self {
Self::Prefix(prefix) => {
// FIXME: if this is an implied shortcut link, it's bad style to suggest `@`
- let mut sugg = vec![(sp.with_hi(inner_sp.lo()), format!("{}@", prefix))];
+ let mut sugg = vec![(sp.with_hi(inner_sp.lo()), format!("{prefix}@"))];
if sp.hi() != inner_sp.hi() {
sugg.push((inner_sp.shrink_to_hi().with_hi(sp.hi()), String::new()));
}
@@ -1610,10 +1690,9 @@ fn report_diagnostic(
DiagnosticInfo { item, ori_link: _, dox, link_range }: &DiagnosticInfo<'_>,
decorate: impl FnOnce(&mut Diagnostic, Option<rustc_span::Span>, MarkdownLinkRange),
) {
- let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id)
- else {
+ let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id) else {
// If non-local, no need to check anything.
- info!("ignoring warning from parent crate: {}", msg);
+ info!("ignoring warning from parent crate: {msg}");
return;
};
@@ -1691,15 +1770,14 @@ fn resolution_failure(
report_diagnostic(
tcx,
BROKEN_INTRA_DOC_LINKS,
- format!("unresolved link to `{}`", path_str),
+ format!("unresolved link to `{path_str}`"),
&diag_info,
|diag, sp, link_range| {
- let item = |res: Res| format!("the {} `{}`", res.descr(), res.name(tcx),);
+ let item = |res: Res| format!("the {} `{}`", res.descr(), res.name(tcx));
let assoc_item_not_allowed = |res: Res| {
let name = res.name(tcx);
format!(
- "`{}` is {} {}, not a module or type, and cannot have associated items",
- name,
+ "`{name}` is {} {}, not a module or type, and cannot have associated items",
res.article(),
res.descr()
)
@@ -1745,7 +1823,7 @@ fn resolution_failure(
name = start;
for ns in [TypeNS, ValueNS, MacroNS] {
if let Ok(v_res) = collector.resolve(start, ns, item_id, module_id) {
- debug!("found partial_res={:?}", v_res);
+ debug!("found partial_res={v_res:?}");
if !v_res.is_empty() {
*partial_res = Some(full_res(tcx, v_res[0]));
*unresolved = end.into();
@@ -1766,10 +1844,10 @@ fn resolution_failure(
let note = if partial_res.is_some() {
// Part of the link resolved; e.g. `std::io::nonexistent`
let module_name = tcx.item_name(module);
- format!("no item named `{}` in module `{}`", unresolved, module_name)
+ format!("no item named `{unresolved}` in module `{module_name}`")
} else {
// None of the link resolved; e.g. `Notimported`
- format!("no item named `{}` in scope", unresolved)
+ format!("no item named `{unresolved}` in scope")
};
if let Some(span) = sp {
diag.span_label(span, note);
@@ -1811,7 +1889,7 @@ fn resolution_failure(
Res::Primitive(_) => None,
};
let is_struct_variant = |did| {
- if let ty::Adt(def, _) = tcx.type_of(did).subst_identity().kind()
+ if let ty::Adt(def, _) = tcx.type_of(did).instantiate_identity().kind()
&& def.is_enum()
&& let Some(variant) = def.variants().iter().find(|v| v.name == res.name(tcx)) {
// ctor is `None` if variant is a struct
@@ -1860,8 +1938,13 @@ fn resolution_failure(
}
return;
}
- Trait | TyAlias | ForeignTy | OpaqueTy | ImplTraitPlaceholder
- | TraitAlias | TyParam | Static(_) => "associated item",
+ Trait
+ | TyAlias { .. }
+ | ForeignTy
+ | OpaqueTy
+ | TraitAlias
+ | TyParam
+ | Static(_) => "associated item",
Impl { .. } | GlobalAsm => unreachable!("not a path"),
}
} else {
@@ -1869,11 +1952,9 @@ fn resolution_failure(
};
let name = res.name(tcx);
let note = format!(
- "the {} `{}` has no {} named `{}`",
- res.descr(),
- name,
- disambiguator.map_or(path_description, |d| d.descr()),
- unresolved,
+ "the {res} `{name}` has no {disamb_res} named `{unresolved}`",
+ res = res.descr(),
+ disamb_res = disambiguator.map_or(path_description, |d| d.descr()),
);
if let Some(span) = sp {
diag.span_label(span, note);
@@ -1968,7 +2049,7 @@ fn report_malformed_generics(
report_diagnostic(
cx.tcx,
BROKEN_INTRA_DOC_LINKS,
- format!("unresolved link to `{}`", path_str),
+ format!("unresolved link to `{path_str}`"),
&diag_info,
|diag, sp, _link_range| {
let note = match err {
@@ -2020,7 +2101,7 @@ fn ambiguity_error(
return false;
}
- let mut msg = format!("`{}` is ", path_str);
+ let mut msg = format!("`{path_str}` is ");
match kinds.as_slice() {
[res1, res2] => {
msg += &format!(
@@ -2084,7 +2165,7 @@ fn suggest_disambiguator(
diag.span_suggestion_verbose(sp, help, suggestion_text, Applicability::MaybeIncorrect);
}
} else {
- diag.help(format!("{}: {}", help, suggestion.as_help(path_str)));
+ diag.help(format!("{help}: {}", suggestion.as_help(path_str)));
}
}
@@ -2098,8 +2179,7 @@ fn privacy_error(cx: &DocContext<'_>, diag_info: &DiagnosticInfo<'_>, path_str:
}
None => "<unknown>",
};
- let msg =
- format!("public documentation for `{}` links to private item `{}`", item_name, path_str);
+ let msg = format!("public documentation for `{item_name}` links to private item `{path_str}`");
report_diagnostic(cx.tcx, PRIVATE_INTRA_DOC_LINKS, msg, diag_info, |diag, sp, _link_range| {
if let Some(sp) = sp {
@@ -2150,6 +2230,6 @@ fn resolve_primitive(path_str: &str, ns: Namespace) -> Option<Res> {
"never" | "!" => Never,
_ => return None,
};
- debug!("resolved primitives {:?}", prim);
+ debug!("resolved primitives {prim:?}");
Some(Res::Primitive(prim))
}
diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs
index fbf827cce..ff89d4e08 100644
--- a/src/librustdoc/passes/collect_trait_impls.rs
+++ b/src/librustdoc/passes/collect_trait_impls.rs
@@ -103,11 +103,11 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) ->
// nothing to do with the inherent impl.
//
// Rustdoc currently uses these `impl` block as a source of
- // the `Ty`, as well as the `ParamEnv`, `SubstsRef`, and
+ // the `Ty`, as well as the `ParamEnv`, `GenericArgsRef`, and
// `Generics`. To avoid relying on the `impl` block, these
// things would need to be created from wholecloth, in a
// form that is valid for use in type inference.
- let ty = tcx.type_of(def_id).subst_identity();
+ let ty = tcx.type_of(def_id).instantiate_identity();
match ty.kind() {
ty::Slice(ty)
| ty::Ref(_, ty, _)
diff --git a/src/librustdoc/passes/lint.rs b/src/librustdoc/passes/lint.rs
index e653207b9..c6d5b7bd3 100644
--- a/src/librustdoc/passes/lint.rs
+++ b/src/librustdoc/passes/lint.rs
@@ -4,6 +4,7 @@
mod bare_urls;
mod check_code_block_syntax;
mod html_tags;
+mod redundant_explicit_links;
mod unescaped_backticks;
use super::Pass;
@@ -29,6 +30,7 @@ impl<'a, 'tcx> DocVisitor for Linter<'a, 'tcx> {
check_code_block_syntax::visit_item(self.cx, item);
html_tags::visit_item(self.cx, item);
unescaped_backticks::visit_item(self.cx, item);
+ redundant_explicit_links::visit_item(self.cx, item);
self.visit_item_recur(item)
}
diff --git a/src/librustdoc/passes/lint/bare_urls.rs b/src/librustdoc/passes/lint/bare_urls.rs
index e9cee92d2..97078a5cb 100644
--- a/src/librustdoc/passes/lint/bare_urls.rs
+++ b/src/librustdoc/passes/lint/bare_urls.rs
@@ -13,11 +13,10 @@ use std::mem;
use std::sync::LazyLock;
pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item) {
- let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id)
- else {
- // If non-local, no need to check anything.
- return;
- };
+ let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) else {
+ // If non-local, no need to check anything.
+ return;
+ };
let dox = item.doc_value();
if !dox.is_empty() {
let report_diag =
@@ -29,7 +28,7 @@ pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item) {
.span_suggestion(
sp,
"use an automatic link instead",
- format!("<{}>", url),
+ format!("<{url}>"),
Applicability::MachineApplicable,
)
});
@@ -75,7 +74,7 @@ fn find_raw_urls(
range: Range<usize>,
f: &impl Fn(&DocContext<'_>, &'static str, &str, Range<usize>),
) {
- trace!("looking for raw urls in {}", text);
+ trace!("looking for raw urls in {text}");
// For now, we only check "full" URLs (meaning, starting with "http://" or "https://").
for match_ in URL_REGEX.find_iter(text) {
let url = match_.as_str();
diff --git a/src/librustdoc/passes/lint/check_code_block_syntax.rs b/src/librustdoc/passes/lint/check_code_block_syntax.rs
index f489f5081..37e28e1fb 100644
--- a/src/librustdoc/passes/lint/check_code_block_syntax.rs
+++ b/src/librustdoc/passes/lint/check_code_block_syntax.rs
@@ -40,7 +40,7 @@ fn check_rust_syntax(
let emitter = BufferEmitter { buffer: Lrc::clone(&buffer), fallback_bundle };
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
- let handler = Handler::with_emitter(false, None, Box::new(emitter));
+ let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings();
let source = dox[code_block.code].to_owned();
let sess = ParseSess::with_span_handler(handler, sm);
@@ -67,12 +67,11 @@ fn check_rust_syntax(
return;
}
- let Some(local_id) = item.item_id.as_def_id().and_then(|x| x.as_local())
- else {
- // We don't need to check the syntax for other crates so returning
- // without doing anything should not be a problem.
- return;
- };
+ let Some(local_id) = item.item_id.as_def_id().and_then(|x| x.as_local()) else {
+ // We don't need to check the syntax for other crates so returning
+ // without doing anything should not be a problem.
+ return;
+ };
let empty_block = code_block.lang_string == Default::default() && code_block.is_fenced;
let is_ignore = code_block.lang_string.ignore != markdown::Ignore::None;
@@ -108,7 +107,7 @@ fn check_rust_syntax(
// just give a `help` instead.
lint.span_help(
sp.from_inner(InnerSpan::new(0, 3)),
- format!("{}: ```text", explanation),
+ format!("{explanation}: ```text"),
);
} else if empty_block {
lint.span_suggestion(
@@ -119,7 +118,7 @@ fn check_rust_syntax(
);
}
} else if empty_block || is_ignore {
- lint.help(format!("{}: ```text", explanation));
+ lint.help(format!("{explanation}: ```text"));
}
// FIXME(#67563): Provide more context for these errors by displaying the spans inline.
@@ -161,7 +160,7 @@ impl Emitter for BufferEmitter {
.translate_message(&diag.message[0].0, &fluent_args)
.unwrap_or_else(|e| panic!("{e}"));
- buffer.messages.push(format!("error from rustc: {}", translated_main_message));
+ buffer.messages.push(format!("error from rustc: {translated_main_message}"));
if diag.is_error() {
buffer.has_errors = true;
}
diff --git a/src/librustdoc/passes/lint/html_tags.rs b/src/librustdoc/passes/lint/html_tags.rs
index 5273f52bc..c135c584c 100644
--- a/src/librustdoc/passes/lint/html_tags.rs
+++ b/src/librustdoc/passes/lint/html_tags.rs
@@ -14,7 +14,9 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
let tcx = cx.tcx;
let Some(hir_id) = DocContext::as_local_hir_id(tcx, item.item_id)
// If non-local, no need to check anything.
- else { return };
+ else {
+ return;
+ };
let dox = item.doc_value();
if !dox.is_empty() {
let report_diag = |msg: String, range: &Range<usize>, is_open_tag: bool| {
@@ -153,7 +155,7 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
let t = t.to_lowercase();
!ALLOWED_UNCLOSED.contains(&t.as_str())
}) {
- report_diag(format!("unclosed HTML tag `{}`", tag), range, true);
+ report_diag(format!("unclosed HTML tag `{tag}`"), range, true);
}
if let Some(range) = is_in_comment {
@@ -192,14 +194,14 @@ fn drop_tag(
// `tags` is used as a queue, meaning that everything after `pos` is included inside it.
// So `<h2><h3></h2>` will look like `["h2", "h3"]`. So when closing `h2`, we will still
// have `h3`, meaning the tag wasn't closed as it should have.
- f(format!("unclosed HTML tag `{}`", last_tag_name), &last_tag_span, true);
+ f(format!("unclosed HTML tag `{last_tag_name}`"), &last_tag_span, true);
}
// Remove the `tag_name` that was originally closed
tags.pop();
} else {
// It can happen for example in this case: `<h2></script></h2>` (the `h2` tag isn't required
// but it helps for the visualization).
- f(format!("unopened HTML tag `{}`", tag_name), &range, false);
+ f(format!("unopened HTML tag `{tag_name}`"), &range, false);
}
}
@@ -353,7 +355,7 @@ fn extract_html_tag(
if let Some(quote_pos) = quote_pos {
let qr = Range { start: quote_pos, end: quote_pos };
f(
- format!("unclosed quoted HTML attribute on tag `{}`", tag_name),
+ format!("unclosed quoted HTML attribute on tag `{tag_name}`"),
&qr,
false,
);
@@ -366,7 +368,7 @@ fn extract_html_tag(
at == "svg" || at == "math"
});
if !valid {
- f(format!("invalid self-closing HTML tag `{}`", tag_name), &r, false);
+ f(format!("invalid self-closing HTML tag `{tag_name}`"), &r, false);
}
} else {
tags.push((tag_name, r));
diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs
new file mode 100644
index 000000000..67cd2cc97
--- /dev/null
+++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs
@@ -0,0 +1,347 @@
+use std::ops::Range;
+
+use pulldown_cmark::{BrokenLink, CowStr, Event, LinkType, OffsetIter, Parser, Tag};
+use rustc_ast::NodeId;
+use rustc_errors::SuggestionStyle;
+use rustc_hir::def::{DefKind, DocLinkResMap, Namespace, Res};
+use rustc_hir::HirId;
+use rustc_lint_defs::Applicability;
+use rustc_span::Symbol;
+
+use crate::clean::utils::find_nearest_parent_module;
+use crate::clean::utils::inherits_doc_hidden;
+use crate::clean::Item;
+use crate::core::DocContext;
+use crate::html::markdown::main_body_opts;
+use crate::passes::source_span_for_markdown_range;
+
+#[derive(Debug)]
+struct LinkData {
+ resolvable_link: Option<String>,
+ resolvable_link_range: Option<Range<usize>>,
+ display_link: String,
+}
+
+pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
+ let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id) else {
+ // If non-local, no need to check anything.
+ return;
+ };
+
+ let doc = item.doc_value();
+ if doc.is_empty() {
+ return;
+ }
+
+ if item.link_names(&cx.cache).is_empty() {
+ // If there's no link names in this item,
+ // then we skip resolution querying to
+ // avoid from panicking.
+ return;
+ }
+
+ let Some(item_id) = item.def_id() else {
+ return;
+ };
+ let Some(local_item_id) = item_id.as_local() else {
+ return;
+ };
+
+ let is_hidden = !cx.render_options.document_hidden
+ && (item.is_doc_hidden() || inherits_doc_hidden(cx.tcx, local_item_id, None));
+ if is_hidden {
+ return;
+ }
+ let is_private = !cx.render_options.document_private
+ && !cx.cache.effective_visibilities.is_directly_public(cx.tcx, item_id);
+ if is_private {
+ return;
+ }
+
+ check_redundant_explicit_link(cx, item, hir_id, &doc);
+}
+
+fn check_redundant_explicit_link<'md>(
+ cx: &DocContext<'_>,
+ item: &Item,
+ hir_id: HirId,
+ doc: &'md str,
+) -> Option<()> {
+ let mut broken_line_callback = |link: BrokenLink<'md>| Some((link.reference, "".into()));
+ let mut offset_iter = Parser::new_with_broken_link_callback(
+ &doc,
+ main_body_opts(),
+ Some(&mut broken_line_callback),
+ )
+ .into_offset_iter();
+ let item_id = item.def_id()?;
+ let module_id = match cx.tcx.def_kind(item_id) {
+ DefKind::Mod if item.inner_docs(cx.tcx) => item_id,
+ _ => find_nearest_parent_module(cx.tcx, item_id).unwrap(),
+ };
+ let resolutions = cx.tcx.doc_link_resolutions(module_id);
+
+ while let Some((event, link_range)) = offset_iter.next() {
+ match event {
+ Event::Start(Tag::Link(link_type, dest, _)) => {
+ let link_data = collect_link_data(&mut offset_iter);
+
+ if let Some(resolvable_link) = link_data.resolvable_link.as_ref() {
+ if &link_data.display_link.replace("`", "") != resolvable_link {
+ // Skips if display link does not match to actual
+ // resolvable link, usually happens if display link
+ // has several segments, e.g.
+ // [this is just an `Option`](Option)
+ continue;
+ }
+ }
+
+ let explicit_link = dest.to_string();
+ let display_link = link_data.resolvable_link.clone()?;
+
+ if explicit_link.ends_with(&display_link) || display_link.ends_with(&explicit_link)
+ {
+ match link_type {
+ LinkType::Inline | LinkType::ReferenceUnknown => {
+ check_inline_or_reference_unknown_redundancy(
+ cx,
+ item,
+ hir_id,
+ doc,
+ resolutions,
+ link_range,
+ dest.to_string(),
+ link_data,
+ if link_type == LinkType::Inline {
+ (b'(', b')')
+ } else {
+ (b'[', b']')
+ },
+ );
+ }
+ LinkType::Reference => {
+ check_reference_redundancy(
+ cx,
+ item,
+ hir_id,
+ doc,
+ resolutions,
+ link_range,
+ &dest,
+ link_data,
+ );
+ }
+ _ => {}
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+
+ None
+}
+
+/// FIXME(ChAoSUnItY): Too many arguments.
+fn check_inline_or_reference_unknown_redundancy(
+ cx: &DocContext<'_>,
+ item: &Item,
+ hir_id: HirId,
+ doc: &str,
+ resolutions: &DocLinkResMap,
+ link_range: Range<usize>,
+ dest: String,
+ link_data: LinkData,
+ (open, close): (u8, u8),
+) -> Option<()> {
+ let (resolvable_link, resolvable_link_range) =
+ (&link_data.resolvable_link?, &link_data.resolvable_link_range?);
+ let (dest_res, display_res) =
+ (find_resolution(resolutions, &dest)?, find_resolution(resolutions, resolvable_link)?);
+
+ if dest_res == display_res {
+ let link_span = source_span_for_markdown_range(cx.tcx, &doc, &link_range, &item.attrs)
+ .unwrap_or(item.attr_span(cx.tcx));
+ let explicit_span = source_span_for_markdown_range(
+ cx.tcx,
+ &doc,
+ &offset_explicit_range(doc, link_range, open, close),
+ &item.attrs,
+ )?;
+ let display_span =
+ source_span_for_markdown_range(cx.tcx, &doc, &resolvable_link_range, &item.attrs)?;
+
+ cx.tcx.struct_span_lint_hir(crate::lint::REDUNDANT_EXPLICIT_LINKS, hir_id, explicit_span, "redundant explicit link target", |lint| {
+ lint.span_label(explicit_span, "explicit target is redundant")
+ .span_label(display_span, "because label contains path that resolves to same destination")
+ .note("when a link's destination is not specified,\nthe label is used to resolve intra-doc links")
+ .span_suggestion_with_style(link_span, "remove explicit link target", format!("[{}]", link_data.display_link), Applicability::MaybeIncorrect, SuggestionStyle::ShowAlways);
+
+ lint
+ });
+ }
+
+ None
+}
+
+/// FIXME(ChAoSUnItY): Too many arguments.
+fn check_reference_redundancy(
+ cx: &DocContext<'_>,
+ item: &Item,
+ hir_id: HirId,
+ doc: &str,
+ resolutions: &DocLinkResMap,
+ link_range: Range<usize>,
+ dest: &CowStr<'_>,
+ link_data: LinkData,
+) -> Option<()> {
+ let (resolvable_link, resolvable_link_range) =
+ (&link_data.resolvable_link?, &link_data.resolvable_link_range?);
+ let (dest_res, display_res) =
+ (find_resolution(resolutions, &dest)?, find_resolution(resolutions, resolvable_link)?);
+
+ if dest_res == display_res {
+ let link_span = source_span_for_markdown_range(cx.tcx, &doc, &link_range, &item.attrs)
+ .unwrap_or(item.attr_span(cx.tcx));
+ let explicit_span = source_span_for_markdown_range(
+ cx.tcx,
+ &doc,
+ &offset_explicit_range(doc, link_range.clone(), b'[', b']'),
+ &item.attrs,
+ )?;
+ let display_span =
+ source_span_for_markdown_range(cx.tcx, &doc, &resolvable_link_range, &item.attrs)?;
+ let def_span = source_span_for_markdown_range(
+ cx.tcx,
+ &doc,
+ &offset_reference_def_range(doc, dest, link_range),
+ &item.attrs,
+ )?;
+
+ cx.tcx.struct_span_lint_hir(crate::lint::REDUNDANT_EXPLICIT_LINKS, hir_id, explicit_span, "redundant explicit link target", |lint| {
+ lint.span_label(explicit_span, "explicit target is redundant")
+ .span_label(display_span, "because label contains path that resolves to same destination")
+ .span_note(def_span, "referenced explicit link target defined here")
+ .note("when a link's destination is not specified,\nthe label is used to resolve intra-doc links")
+ .span_suggestion_with_style(link_span, "remove explicit link target", format!("[{}]", link_data.display_link), Applicability::MaybeIncorrect, SuggestionStyle::ShowAlways);
+
+ lint
+ });
+ }
+
+ None
+}
+
+fn find_resolution(resolutions: &DocLinkResMap, path: &str) -> Option<Res<NodeId>> {
+ [Namespace::TypeNS, Namespace::ValueNS, Namespace::MacroNS]
+ .into_iter()
+ .find_map(|ns| resolutions.get(&(Symbol::intern(path), ns)).copied().flatten())
+}
+
+/// Collects all neccessary data of link.
+fn collect_link_data(offset_iter: &mut OffsetIter<'_, '_>) -> LinkData {
+ let mut resolvable_link = None;
+ let mut resolvable_link_range = None;
+ let mut display_link = String::new();
+
+ while let Some((event, range)) = offset_iter.next() {
+ match event {
+ Event::Text(code) => {
+ let code = code.to_string();
+ display_link.push_str(&code);
+ resolvable_link = Some(code);
+ resolvable_link_range = Some(range);
+ }
+ Event::Code(code) => {
+ let code = code.to_string();
+ display_link.push('`');
+ display_link.push_str(&code);
+ display_link.push('`');
+ resolvable_link = Some(code);
+ resolvable_link_range = Some(range);
+ }
+ Event::End(_) => {
+ break;
+ }
+ _ => {}
+ }
+ }
+
+ LinkData { resolvable_link, resolvable_link_range, display_link }
+}
+
+fn offset_explicit_range(md: &str, link_range: Range<usize>, open: u8, close: u8) -> Range<usize> {
+ let mut open_brace = !0;
+ let mut close_brace = !0;
+ for (i, b) in md.as_bytes()[link_range.clone()].iter().copied().enumerate().rev() {
+ let i = i + link_range.start;
+ if b == close {
+ close_brace = i;
+ break;
+ }
+ }
+
+ if close_brace < link_range.start || close_brace >= link_range.end {
+ return link_range;
+ }
+
+ let mut nesting = 1;
+
+ for (i, b) in md.as_bytes()[link_range.start..close_brace].iter().copied().enumerate().rev() {
+ let i = i + link_range.start;
+ if b == close {
+ nesting += 1;
+ }
+ if b == open {
+ nesting -= 1;
+ }
+ if nesting == 0 {
+ open_brace = i;
+ break;
+ }
+ }
+
+ assert!(open_brace != close_brace);
+
+ if open_brace < link_range.start || open_brace >= link_range.end {
+ return link_range;
+ }
+ // do not actually include braces in the span
+ (open_brace + 1)..close_brace
+}
+
+fn offset_reference_def_range(
+ md: &str,
+ dest: &CowStr<'_>,
+ link_range: Range<usize>,
+) -> Range<usize> {
+ // For diagnostics, we want to underline the link's definition but `span` will point at
+ // where the link is used. This is a problem for reference-style links, where the definition
+ // is separate from the usage.
+
+ match dest {
+ // `Borrowed` variant means the string (the link's destination) may come directly from
+ // the markdown text and we can locate the original link destination.
+ // NOTE: LinkReplacer also provides `Borrowed` but possibly from other sources,
+ // so `locate()` can fall back to use `span`.
+ CowStr::Borrowed(s) => {
+ // FIXME: remove this function once pulldown_cmark can provide spans for link definitions.
+ unsafe {
+ let s_start = dest.as_ptr();
+ let s_end = s_start.add(s.len());
+ let md_start = md.as_ptr();
+ let md_end = md_start.add(md.len());
+ if md_start <= s_start && s_end <= md_end {
+ let start = s_start.offset_from(md_start) as usize;
+ let end = s_end.offset_from(md_start) as usize;
+ start..end
+ } else {
+ link_range
+ }
+ }
+ }
+
+ // For anything else, we can only use the provided range.
+ CowStr::Boxed(_) | CowStr::Inlined(_) => link_range,
+ }
+}
diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs
index 8a33e51b3..95273a225 100644
--- a/src/librustdoc/passes/propagate_doc_cfg.rs
+++ b/src/librustdoc/passes/propagate_doc_cfg.rs
@@ -38,8 +38,9 @@ impl<'a, 'tcx> CfgPropagator<'a, 'tcx> {
_ => return,
};
- let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local())
- else { return };
+ let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) else {
+ return;
+ };
if check_parent {
let expected_parent = self.cx.tcx.opt_local_parent(def_id);
diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs
index e2e38d3e7..81a90ed49 100644
--- a/src/librustdoc/passes/strip_hidden.rs
+++ b/src/librustdoc/passes/strip_hidden.rs
@@ -6,11 +6,11 @@ use rustc_span::symbol::sym;
use std::mem;
use crate::clean;
+use crate::clean::utils::inherits_doc_hidden;
use crate::clean::{Item, ItemIdSet};
use crate::core::DocContext;
use crate::fold::{strip_item, DocFolder};
use crate::passes::{ImplStripper, Pass};
-use crate::visit_ast::inherits_doc_hidden;
pub(crate) const STRIP_HIDDEN: Pass = Pass {
name: "strip-hidden",
@@ -42,6 +42,7 @@ pub(crate) fn strip_hidden(krate: clean::Crate, cx: &mut DocContext<'_>) -> clea
cache: &cx.cache,
is_json_output,
document_private: cx.render_options.document_private,
+ document_hidden: cx.render_options.document_hidden,
};
stripper.fold_crate(krate)
}
diff --git a/src/librustdoc/passes/strip_priv_imports.rs b/src/librustdoc/passes/strip_priv_imports.rs
index 4c992e948..468712ba3 100644
--- a/src/librustdoc/passes/strip_priv_imports.rs
+++ b/src/librustdoc/passes/strip_priv_imports.rs
@@ -13,5 +13,10 @@ pub(crate) const STRIP_PRIV_IMPORTS: Pass = Pass {
pub(crate) fn strip_priv_imports(krate: clean::Crate, cx: &mut DocContext<'_>) -> clean::Crate {
let is_json_output = cx.output_format.is_json() && !cx.show_coverage;
- ImportStripper { tcx: cx.tcx, is_json_output }.fold_crate(krate)
+ ImportStripper {
+ tcx: cx.tcx,
+ is_json_output,
+ document_hidden: cx.render_options.document_hidden,
+ }
+ .fold_crate(krate)
}
diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs
index bb6dccb7c..3b6f484fd 100644
--- a/src/librustdoc/passes/strip_private.rs
+++ b/src/librustdoc/passes/strip_private.rs
@@ -28,8 +28,12 @@ pub(crate) fn strip_private(mut krate: clean::Crate, cx: &mut DocContext<'_>) ->
is_json_output,
tcx: cx.tcx,
};
- krate =
- ImportStripper { tcx: cx.tcx, is_json_output }.fold_crate(stripper.fold_crate(krate));
+ krate = ImportStripper {
+ tcx: cx.tcx,
+ is_json_output,
+ document_hidden: cx.render_options.document_hidden,
+ }
+ .fold_crate(stripper.fold_crate(krate));
}
// strip all impls referencing private items
@@ -39,6 +43,7 @@ pub(crate) fn strip_private(mut krate: clean::Crate, cx: &mut DocContext<'_>) ->
cache: &cx.cache,
is_json_output,
document_private: cx.render_options.document_private,
+ document_hidden: cx.render_options.document_hidden,
};
stripper.fold_crate(krate)
}
diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs
index 90c361d9d..64a4ace5b 100644
--- a/src/librustdoc/passes/stripper.rs
+++ b/src/librustdoc/passes/stripper.rs
@@ -3,6 +3,7 @@ use rustc_hir::def_id::DefId;
use rustc_middle::ty::{TyCtxt, Visibility};
use std::mem;
+use crate::clean::utils::inherits_doc_hidden;
use crate::clean::{self, Item, ItemId, ItemIdSet};
use crate::fold::{strip_item, DocFolder};
use crate::formats::cache::Cache;
@@ -151,6 +152,7 @@ pub(crate) struct ImplStripper<'a, 'tcx> {
pub(crate) cache: &'a Cache,
pub(crate) is_json_output: bool,
pub(crate) document_private: bool,
+ pub(crate) document_hidden: bool,
}
impl<'a> ImplStripper<'a, '_> {
@@ -162,7 +164,13 @@ impl<'a> ImplStripper<'a, '_> {
// If the "for" item is exported and the impl block isn't `#[doc(hidden)]`, then we
// need to keep it.
self.cache.effective_visibilities.is_exported(self.tcx, for_def_id)
- && !item.is_doc_hidden()
+ && (self.document_hidden
+ || ((!item.is_doc_hidden()
+ && for_def_id
+ .as_local()
+ .map(|def_id| !inherits_doc_hidden(self.tcx, def_id, None))
+ .unwrap_or(true))
+ || self.cache.inlined_items.contains(&for_def_id)))
} else {
false
}
@@ -231,6 +239,7 @@ impl<'a> DocFolder for ImplStripper<'a, '_> {
pub(crate) struct ImportStripper<'tcx> {
pub(crate) tcx: TyCtxt<'tcx>,
pub(crate) is_json_output: bool,
+ pub(crate) document_hidden: bool,
}
impl<'tcx> ImportStripper<'tcx> {
@@ -247,8 +256,12 @@ impl<'tcx> ImportStripper<'tcx> {
impl<'tcx> DocFolder for ImportStripper<'tcx> {
fn fold_item(&mut self, i: Item) -> Option<Item> {
match *i.kind {
- clean::ImportItem(imp) if self.import_should_be_hidden(&i, &imp) => None,
- clean::ImportItem(_) if i.is_doc_hidden() => None,
+ clean::ImportItem(imp)
+ if !self.document_hidden && self.import_should_be_hidden(&i, &imp) =>
+ {
+ None
+ }
+ // clean::ImportItem(_) if !self.document_hidden && i.is_doc_hidden() => None,
clean::ExternCrateItem { .. } | clean::ImportItem(..)
if i.visibility(self.tcx) != Some(Visibility::Public) =>
{