summaryrefslogtreecommitdiffstats
path: root/src/librustdoc/passes
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/librustdoc/passes/bare_urls.rs6
-rw-r--r--src/librustdoc/passes/calculate_doc_coverage.rs18
-rw-r--r--src/librustdoc/passes/check_code_block_syntax.rs112
-rw-r--r--src/librustdoc/passes/check_doc_test_visibility.rs16
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links.rs48
-rw-r--r--src/librustdoc/passes/collect_intra_doc_links/early.rs44
-rw-r--r--src/librustdoc/passes/html_tags.rs153
-rw-r--r--src/librustdoc/passes/propagate_doc_cfg.rs62
-rw-r--r--src/librustdoc/passes/strip_hidden.rs8
-rw-r--r--src/librustdoc/passes/strip_private.rs12
-rw-r--r--src/librustdoc/passes/stripper.rs99
11 files changed, 414 insertions, 164 deletions
diff --git a/src/librustdoc/passes/bare_urls.rs b/src/librustdoc/passes/bare_urls.rs
index 392e26ea6..7ff3ccef9 100644
--- a/src/librustdoc/passes/bare_urls.rs
+++ b/src/librustdoc/passes/bare_urls.rs
@@ -71,16 +71,14 @@ impl<'a, 'tcx> DocVisitor for BareUrlsLinter<'a, 'tcx> {
let report_diag = |cx: &DocContext<'_>, msg: &str, url: &str, range: Range<usize>| {
let sp = super::source_span_for_markdown_range(cx.tcx, &dox, &range, &item.attrs)
.unwrap_or_else(|| item.attr_span(cx.tcx));
- cx.tcx.struct_span_lint_hir(crate::lint::BARE_URLS, hir_id, sp, |lint| {
- lint.build(msg)
- .note("bare URLs are not automatically turned into clickable links")
+ cx.tcx.struct_span_lint_hir(crate::lint::BARE_URLS, hir_id, sp, msg, |lint| {
+ lint.note("bare URLs are not automatically turned into clickable links")
.span_suggestion(
sp,
"use an automatic link instead",
format!("<{}>", url),
Applicability::MachineApplicable,
)
- .emit();
});
};
diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs
index 4c6e3eb04..48835abf9 100644
--- a/src/librustdoc/passes/calculate_doc_coverage.rs
+++ b/src/librustdoc/passes/calculate_doc_coverage.rs
@@ -215,7 +215,6 @@ impl<'a, 'b> DocVisitor for CoverageCalculator<'a, 'b> {
None,
);
- let filename = i.span(self.ctx.tcx).filename(self.ctx.sess());
let has_doc_example = tests.found_tests != 0;
// The `expect_def_id()` should be okay because `local_def_id_to_hir_id`
// would presumably panic if a fake `DefIndex` were passed.
@@ -261,13 +260,16 @@ impl<'a, 'b> DocVisitor for CoverageCalculator<'a, 'b> {
let should_have_docs = !should_be_ignored
&& (level != lint::Level::Allow || matches!(source, LintLevelSource::Default));
- debug!("counting {:?} {:?} in {:?}", i.type_(), i.name, filename);
- self.items.entry(filename).or_default().count_item(
- has_docs,
- has_doc_example,
- should_have_doc_example(self.ctx, i),
- should_have_docs,
- );
+ if let Some(span) = i.span(self.ctx.tcx) {
+ let filename = span.filename(self.ctx.sess());
+ debug!("counting {:?} {:?} in {:?}", i.type_(), i.name, filename);
+ self.items.entry(filename).or_default().count_item(
+ has_docs,
+ has_doc_example,
+ should_have_doc_example(self.ctx, i),
+ should_have_docs,
+ );
+ }
}
}
diff --git a/src/librustdoc/passes/check_code_block_syntax.rs b/src/librustdoc/passes/check_code_block_syntax.rs
index 0172ef570..2e651b538 100644
--- a/src/librustdoc/passes/check_code_block_syntax.rs
+++ b/src/librustdoc/passes/check_code_block_syntax.rs
@@ -1,7 +1,9 @@
//! Validates syntax inside Rust code blocks (\`\`\`rust).
use rustc_data_structures::sync::{Lock, Lrc};
use rustc_errors::{
- emitter::Emitter, Applicability, Diagnostic, Handler, LazyFallbackBundle, LintDiagnosticBuilder,
+ emitter::Emitter,
+ translation::{to_fluent_args, Translate},
+ Applicability, Diagnostic, Handler, LazyFallbackBundle,
};
use rustc_parse::parse_stream_from_source_str;
use rustc_session::parse::ParseSess;
@@ -96,48 +98,10 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> {
None => (item.attr_span(self.cx.tcx), false),
};
- // lambda that will use the lint to start a new diagnostic and add
- // a suggestion to it when needed.
- let diag_builder = |lint: LintDiagnosticBuilder<'_, ()>| {
- let explanation = if is_ignore {
- "`ignore` code blocks require valid Rust code for syntax highlighting; \
- mark blocks that do not contain Rust code as text"
- } else {
- "mark blocks that do not contain Rust code as text"
- };
- let msg = if buffer.has_errors {
- "could not parse code block as Rust code"
- } else {
- "Rust code block is empty"
- };
- let mut diag = lint.build(msg);
-
- if precise_span {
- if is_ignore {
- // giving an accurate suggestion is hard because `ignore` might not have come first in the list.
- // just give a `help` instead.
- diag.span_help(
- sp.from_inner(InnerSpan::new(0, 3)),
- &format!("{}: ```text", explanation),
- );
- } else if empty_block {
- diag.span_suggestion(
- sp.from_inner(InnerSpan::new(0, 3)).shrink_to_hi(),
- explanation,
- "text",
- Applicability::MachineApplicable,
- );
- }
- } else if empty_block || is_ignore {
- diag.help(&format!("{}: ```text", explanation));
- }
-
- // FIXME(#67563): Provide more context for these errors by displaying the spans inline.
- for message in buffer.messages.iter() {
- diag.note(message);
- }
-
- diag.emit();
+ let msg = if buffer.has_errors {
+ "could not parse code block as Rust code"
+ } else {
+ "Rust code block is empty"
};
// Finally build and emit the completed diagnostic.
@@ -147,7 +111,42 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> {
crate::lint::INVALID_RUST_CODEBLOCKS,
hir_id,
sp,
- diag_builder,
+ msg,
+ |lint| {
+ let explanation = if is_ignore {
+ "`ignore` code blocks require valid Rust code for syntax highlighting; \
+ mark blocks that do not contain Rust code as text"
+ } else {
+ "mark blocks that do not contain Rust code as text"
+ };
+
+ if precise_span {
+ if is_ignore {
+ // giving an accurate suggestion is hard because `ignore` might not have come first in the list.
+ // just give a `help` instead.
+ lint.span_help(
+ sp.from_inner(InnerSpan::new(0, 3)),
+ &format!("{}: ```text", explanation),
+ );
+ } else if empty_block {
+ lint.span_suggestion(
+ sp.from_inner(InnerSpan::new(0, 3)).shrink_to_hi(),
+ explanation,
+ "text",
+ Applicability::MachineApplicable,
+ );
+ }
+ } else if empty_block || is_ignore {
+ lint.help(&format!("{}: ```text", explanation));
+ }
+
+ // FIXME(#67563): Provide more context for these errors by displaying the spans inline.
+ for message in buffer.messages.iter() {
+ lint.note(message);
+ }
+
+ lint
+ },
);
}
}
@@ -181,11 +180,24 @@ struct BufferEmitter {
fallback_bundle: LazyFallbackBundle,
}
+impl Translate for BufferEmitter {
+ fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> {
+ None
+ }
+
+ fn fallback_fluent_bundle(&self) -> &rustc_errors::FluentBundle {
+ &**self.fallback_bundle
+ }
+}
+
impl Emitter for BufferEmitter {
fn emit_diagnostic(&mut self, diag: &Diagnostic) {
let mut buffer = self.buffer.borrow_mut();
- // FIXME(davidtwco): need to support translation here eventually
- buffer.messages.push(format!("error from rustc: {}", diag.message[0].0.expect_str()));
+
+ let fluent_args = to_fluent_args(diag.args());
+ let translated_main_message = self.translate_message(&diag.message[0].0, &fluent_args);
+
+ buffer.messages.push(format!("error from rustc: {}", translated_main_message));
if diag.is_error() {
buffer.has_errors = true;
}
@@ -194,12 +206,4 @@ impl Emitter for BufferEmitter {
fn source_map(&self) -> Option<&Lrc<SourceMap>> {
None
}
-
- fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> {
- None
- }
-
- fn fallback_fluent_bundle(&self) -> &rustc_errors::FluentBundle {
- &**self.fallback_bundle
- }
}
diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs
index e86f90833..7740c6d5b 100644
--- a/src/librustdoc/passes/check_doc_test_visibility.rs
+++ b/src/librustdoc/passes/check_doc_test_visibility.rs
@@ -56,7 +56,7 @@ impl crate::doctest::Tester for Tests {
}
pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> bool {
- if !cx.cache.access_levels.is_public(item.item_id.expect_def_id())
+ if !cx.cache.effective_visibilities.is_directly_public(item.item_id.expect_def_id())
|| matches!(
*item.kind,
clean::StructFieldItem(_)
@@ -117,7 +117,7 @@ pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item
find_testable_code(dox, &mut tests, ErrorCodes::No, false, None);
- if tests.found_tests == 0 && cx.tcx.sess.is_nightly_build() {
+ 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);
let sp = item.attr_span(cx.tcx);
@@ -125,21 +125,19 @@ pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item
crate::lint::MISSING_DOC_CODE_EXAMPLES,
hir_id,
sp,
- |lint| {
- lint.build("missing code example in this documentation").emit();
- },
+ "missing code example in this documentation",
+ |lint| lint,
);
}
} else if tests.found_tests > 0
- && !cx.cache.access_levels.is_exported(item.item_id.expect_def_id())
+ && !cx.cache.effective_visibilities.is_exported(item.item_id.expect_def_id())
{
cx.tcx.struct_span_lint_hir(
crate::lint::PRIVATE_DOC_TESTS,
hir_id,
item.attr_span(cx.tcx),
- |lint| {
- lint.build("documentation test in private item").emit();
- },
+ "documentation test in private item",
+ |lint| lint,
);
}
}
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs
index 7d7a63c53..8aa0abd36 100644
--- a/src/librustdoc/passes/collect_intra_doc_links.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links.rs
@@ -80,10 +80,10 @@ impl Res {
}
}
- fn def_id(self, tcx: TyCtxt<'_>) -> DefId {
+ fn def_id(self, tcx: TyCtxt<'_>) -> Option<DefId> {
match self {
- Res::Def(_, id) => id,
- Res::Primitive(prim) => *PrimitiveType::primitive_locations(tcx).get(&prim).unwrap(),
+ Res::Def(_, id) => Some(id),
+ Res::Primitive(prim) => PrimitiveType::primitive_locations(tcx).get(&prim).copied(),
}
}
@@ -223,6 +223,9 @@ enum MalformedGenerics {
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub(crate) enum UrlFragment {
Item(DefId),
+ /// A part of a page that isn't a rust item.
+ ///
+ /// Eg: `[Vector Examples](std::vec::Vec#examples)`
UserWritten(String),
}
@@ -477,7 +480,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 `::`, assumming {} was correctly not in scope", item_name);
+ debug!("found no `::`, assuming {} was correctly not in scope", item_name);
UnresolvedPath {
item_id,
module_id,
@@ -750,7 +753,7 @@ fn resolve_associated_trait_item<'a>(
///
/// This is just a wrapper around [`TyCtxt::impl_item_implementor_ids()`] and
/// [`TyCtxt::associated_item()`] (with some helpful logging added).
-#[instrument(level = "debug", skip(tcx))]
+#[instrument(level = "debug", skip(tcx), ret)]
fn trait_assoc_to_impl_assoc_item<'tcx>(
tcx: TyCtxt<'tcx>,
impl_id: DefId,
@@ -760,9 +763,7 @@ fn trait_assoc_to_impl_assoc_item<'tcx>(
debug!(?trait_to_impl_assoc_map);
let impl_assoc_id = *trait_to_impl_assoc_map.get(&trait_assoc_id)?;
debug!(?impl_assoc_id);
- let impl_assoc = tcx.associated_item(impl_assoc_id);
- debug!(?impl_assoc);
- Some(impl_assoc)
+ Some(tcx.associated_item(impl_assoc_id))
}
/// Given a type, return all trait impls in scope in `module` for that type.
@@ -1126,10 +1127,10 @@ impl LinkCollector<'_, '_> {
}
}
- Some(ItemLink {
+ res.def_id(self.cx.tcx).map(|page_id| ItemLink {
link: ori_link.link.clone(),
link_text: link_text.clone(),
- did: res.def_id(self.cx.tcx),
+ page_id,
fragment,
})
}
@@ -1148,11 +1149,12 @@ impl LinkCollector<'_, '_> {
item,
&diag_info,
)?;
- let id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id));
+
+ let page_id = clean::register_res(self.cx, rustc_hir::def::Res::Def(kind, id));
Some(ItemLink {
link: ori_link.link.clone(),
link_text: link_text.clone(),
- did: id,
+ page_id,
fragment,
})
}
@@ -1200,8 +1202,8 @@ impl LinkCollector<'_, '_> {
item.item_id.expect_def_id().as_local().map(|src_id| (src_id, dst_id))
})
{
- if self.cx.tcx.privacy_access_levels(()).is_exported(src_id)
- && !self.cx.tcx.privacy_access_levels(()).is_exported(dst_id)
+ if self.cx.tcx.effective_visibilities(()).is_exported(src_id)
+ && !self.cx.tcx.effective_visibilities(()).is_exported(dst_id)
{
privacy_error(self.cx, diag_info, path_str);
}
@@ -1256,7 +1258,7 @@ impl LinkCollector<'_, '_> {
&mut self,
key: ResolutionInfo,
diag: DiagnosticInfo<'_>,
- // If errors are cached then they are only reported on first ocurrence
+ // 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,
) -> Option<(Res, Option<UrlFragment>)> {
@@ -1607,9 +1609,7 @@ fn report_diagnostic(
let sp = item.attr_span(tcx);
- tcx.struct_span_lint_hir(lint, hir_id, sp, |lint| {
- let mut diag = lint.build(msg);
-
+ tcx.struct_span_lint_hir(lint, hir_id, sp, msg, |lint| {
let span =
super::source_span_for_markdown_range(tcx, dox, link_range, &item.attrs).map(|sp| {
if dox.as_bytes().get(link_range.start) == Some(&b'`')
@@ -1622,7 +1622,7 @@ fn report_diagnostic(
});
if let Some(sp) = span {
- diag.set_span(sp);
+ lint.set_span(sp);
} else {
// blah blah blah\nblah\nblah [blah] blah blah\nblah blah
// ^ ~~~~
@@ -1632,7 +1632,7 @@ fn report_diagnostic(
let line = dox[last_new_line_offset..].lines().next().unwrap_or("");
// Print the line containing the `link_range` and manually mark it with '^'s.
- diag.note(&format!(
+ lint.note(&format!(
"the link appears in this line:\n\n{line}\n\
{indicator: <before$}{indicator:^<found$}",
line = line,
@@ -1642,9 +1642,9 @@ fn report_diagnostic(
));
}
- decorate(&mut diag, span);
+ decorate(lint, span);
- diag.emit();
+ lint
});
}
@@ -1807,8 +1807,8 @@ fn resolution_failure(
}
return;
}
- Trait | TyAlias | ForeignTy | OpaqueTy | TraitAlias | TyParam
- | Static(_) => "associated item",
+ Trait | TyAlias | ForeignTy | OpaqueTy | ImplTraitPlaceholder
+ | TraitAlias | TyParam | Static(_) => "associated item",
Impl | GlobalAsm => unreachable!("not a path"),
}
} else {
diff --git a/src/librustdoc/passes/collect_intra_doc_links/early.rs b/src/librustdoc/passes/collect_intra_doc_links/early.rs
index 38cfd7a27..1b373cfe5 100644
--- a/src/librustdoc/passes/collect_intra_doc_links/early.rs
+++ b/src/librustdoc/passes/collect_intra_doc_links/early.rs
@@ -37,7 +37,6 @@ pub(crate) fn early_resolve_intra_doc_links(
markdown_links: Default::default(),
doc_link_resolutions: Default::default(),
traits_in_scope: Default::default(),
- all_traits: Default::default(),
all_trait_impls: Default::default(),
all_macro_rules: Default::default(),
document_private_items,
@@ -48,7 +47,6 @@ pub(crate) fn early_resolve_intra_doc_links(
link_resolver.resolve_doc_links_local(&krate.attrs);
link_resolver.process_module_children_or_reexports(CRATE_DEF_ID.to_def_id());
visit::walk_crate(&mut link_resolver, krate);
- link_resolver.process_extern_impls();
// FIXME: somehow rustdoc is still missing crates even though we loaded all
// the known necessary crates. Load them all unconditionally until we find a way to fix this.
@@ -58,11 +56,12 @@ pub(crate) fn early_resolve_intra_doc_links(
link_resolver.resolver.resolve_rustdoc_path(extern_name, TypeNS, parent_scope);
}
+ link_resolver.process_extern_impls();
+
ResolverCaches {
markdown_links: Some(link_resolver.markdown_links),
doc_link_resolutions: link_resolver.doc_link_resolutions,
traits_in_scope: link_resolver.traits_in_scope,
- all_traits: Some(link_resolver.all_traits),
all_trait_impls: Some(link_resolver.all_trait_impls),
all_macro_rules: link_resolver.all_macro_rules,
}
@@ -80,7 +79,6 @@ struct EarlyDocLinkResolver<'r, 'ra> {
markdown_links: FxHashMap<String, Vec<PreprocessedMarkdownLink>>,
doc_link_resolutions: FxHashMap<(Symbol, Namespace, DefId), Option<Res<ast::NodeId>>>,
traits_in_scope: DefIdMap<Vec<TraitCandidate>>,
- all_traits: Vec<DefId>,
all_trait_impls: Vec<DefId>,
all_macro_rules: FxHashMap<Symbol, Res<ast::NodeId>>,
document_private_items: bool,
@@ -121,8 +119,6 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
loop {
let crates = Vec::from_iter(self.resolver.cstore().crates_untracked());
for &cnum in &crates[start_cnum..] {
- let all_traits =
- Vec::from_iter(self.resolver.cstore().traits_in_crate_untracked(cnum));
let all_trait_impls =
Vec::from_iter(self.resolver.cstore().trait_impls_in_crate_untracked(cnum));
let all_inherent_impls =
@@ -131,20 +127,18 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
self.resolver.cstore().incoherent_impls_in_crate_untracked(cnum),
);
- // Querying traits in scope is expensive so we try to prune the impl and traits lists
- // using privacy, private traits and impls from other crates are never documented in
+ // Querying traits in scope is expensive so we try to prune the impl lists using
+ // privacy, private traits and impls from other crates are never documented in
// the current crate, and links in their doc comments are not resolved.
- for &def_id in &all_traits {
- if self.resolver.cstore().visibility_untracked(def_id).is_public() {
- self.resolve_doc_links_extern_impl(def_id, false);
- }
- }
for &(trait_def_id, impl_def_id, simplified_self_ty) in &all_trait_impls {
if self.resolver.cstore().visibility_untracked(trait_def_id).is_public()
&& simplified_self_ty.and_then(|ty| ty.def()).map_or(true, |ty_def_id| {
self.resolver.cstore().visibility_untracked(ty_def_id).is_public()
})
{
+ if self.visited_mods.insert(trait_def_id) {
+ self.resolve_doc_links_extern_impl(trait_def_id, false);
+ }
self.resolve_doc_links_extern_impl(impl_def_id, false);
}
}
@@ -157,7 +151,6 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
self.resolve_doc_links_extern_impl(impl_def_id, true);
}
- self.all_traits.extend(all_traits);
self.all_trait_impls
.extend(all_trait_impls.into_iter().map(|(_, def_id, _)| def_id));
}
@@ -306,15 +299,20 @@ impl<'ra> EarlyDocLinkResolver<'_, 'ra> {
{
if let Some(def_id) = child.res.opt_def_id() && !def_id.is_local() {
let scope_id = match child.res {
- Res::Def(DefKind::Variant, ..) => self.resolver.parent(def_id),
+ Res::Def(
+ DefKind::Variant
+ | DefKind::AssocTy
+ | DefKind::AssocFn
+ | DefKind::AssocConst,
+ ..,
+ ) => self.resolver.parent(def_id),
_ => def_id,
};
self.resolve_doc_links_extern_outer(def_id, scope_id); // Outer attribute scope
if let Res::Def(DefKind::Mod, ..) = child.res {
self.resolve_doc_links_extern_inner(def_id); // Inner attribute scope
}
- // `DefKind::Trait`s are processed in `process_extern_impls`.
- if let Res::Def(DefKind::Mod | DefKind::Enum, ..) = child.res {
+ if let Res::Def(DefKind::Mod | DefKind::Enum | DefKind::Trait, ..) = child.res {
self.process_module_children_or_reexports(def_id);
}
if let Res::Def(DefKind::Struct | DefKind::Union | DefKind::Variant, _) =
@@ -356,10 +354,14 @@ impl Visitor<'_> for EarlyDocLinkResolver<'_, '_> {
self.parent_scope.module = old_module;
} else {
match &item.kind {
- ItemKind::Trait(..) => {
- self.all_traits.push(self.resolver.local_def_id(item.id).to_def_id());
- }
- ItemKind::Impl(box ast::Impl { of_trait: Some(..), .. }) => {
+ ItemKind::Impl(box ast::Impl { of_trait: Some(trait_ref), .. }) => {
+ if let Some(partial_res) = self.resolver.get_partial_res(trait_ref.ref_id)
+ && let Some(res) = partial_res.full_res()
+ && let Some(trait_def_id) = res.opt_def_id()
+ && !trait_def_id.is_local()
+ && self.visited_mods.insert(trait_def_id) {
+ self.resolve_doc_links_extern_impl(trait_def_id, false);
+ }
self.all_trait_impls.push(self.resolver.local_def_id(item.id).to_def_id());
}
ItemKind::MacroDef(macro_def) if macro_def.macro_rules => {
diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs
index f3a3c853c..a89ed7c7e 100644
--- a/src/librustdoc/passes/html_tags.rs
+++ b/src/librustdoc/passes/html_tags.rs
@@ -22,10 +22,8 @@ struct InvalidHtmlTagsLinter<'a, 'tcx> {
}
pub(crate) fn check_invalid_html_tags(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
- if cx.tcx.sess.is_nightly_build() {
- let mut coll = InvalidHtmlTagsLinter { cx };
- coll.visit_crate(&krate);
- }
+ let mut coll = InvalidHtmlTagsLinter { cx };
+ coll.visit_crate(&krate);
krate
}
@@ -94,6 +92,34 @@ fn extract_path_backwards(text: &str, end_pos: usize) -> Option<usize> {
if current_pos == end_pos { None } else { Some(current_pos) }
}
+fn extract_path_forward(text: &str, start_pos: usize) -> Option<usize> {
+ use rustc_lexer::{is_id_continue, is_id_start};
+ let mut current_pos = start_pos;
+ loop {
+ if current_pos < text.len() && text[current_pos..].starts_with("::") {
+ current_pos += 2;
+ } else {
+ break;
+ }
+ let mut chars = text[current_pos..].chars();
+ if let Some(c) = chars.next() {
+ if is_id_start(c) {
+ current_pos += c.len_utf8();
+ } else {
+ break;
+ }
+ }
+ while let Some(c) = chars.next() {
+ if is_id_continue(c) {
+ current_pos += c.len_utf8();
+ } else {
+ break;
+ }
+ }
+ }
+ if current_pos == start_pos { None } else { Some(current_pos) }
+}
+
fn is_valid_for_html_tag_name(c: char, is_empty: bool) -> bool {
// https://spec.commonmark.org/0.30/#raw-html
//
@@ -158,7 +184,60 @@ fn extract_html_tag(
}
drop_tag(tags, tag_name, r, f);
} else {
- tags.push((tag_name, r));
+ let mut is_self_closing = false;
+ let mut quote_pos = None;
+ if c != '>' {
+ let mut quote = None;
+ let mut after_eq = false;
+ for (i, c) in text[pos..].char_indices() {
+ if !c.is_whitespace() {
+ if let Some(q) = quote {
+ if c == q {
+ quote = None;
+ quote_pos = None;
+ after_eq = false;
+ }
+ } else if c == '>' {
+ break;
+ } else if c == '/' && !after_eq {
+ is_self_closing = true;
+ } else {
+ if is_self_closing {
+ is_self_closing = false;
+ }
+ if (c == '"' || c == '\'') && after_eq {
+ quote = Some(c);
+ quote_pos = Some(pos + i);
+ } else if c == '=' {
+ after_eq = true;
+ }
+ }
+ } else if quote.is_none() {
+ after_eq = false;
+ }
+ }
+ }
+ 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),
+ &qr,
+ false,
+ );
+ }
+ if is_self_closing {
+ // https://html.spec.whatwg.org/#parse-error-non-void-html-element-start-tag-with-trailing-solidus
+ let valid = ALLOWED_UNCLOSED.contains(&&tag_name[..])
+ || tags.iter().take(pos + 1).any(|(at, _)| {
+ let at = at.to_lowercase();
+ at == "svg" || at == "math"
+ });
+ if !valid {
+ f(&format!("invalid self-closing HTML tag `{}`", tag_name), &r, false);
+ }
+ } else {
+ tags.push((tag_name, r));
+ }
}
}
break;
@@ -212,27 +291,74 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> {
Some(sp) => sp,
None => item.attr_span(tcx),
};
- tcx.struct_span_lint_hir(crate::lint::INVALID_HTML_TAGS, hir_id, sp, |lint| {
+ tcx.struct_span_lint_hir(crate::lint::INVALID_HTML_TAGS, hir_id, sp, msg, |lint| {
use rustc_lint_defs::Applicability;
- let mut diag = lint.build(msg);
// If a tag looks like `<this>`, it might actually be a generic.
// We don't try to detect stuff `<like, this>` because that's not valid HTML,
// and we don't try to detect stuff `<like this>` because that's not valid Rust.
- if let Some(Some(generics_start)) = (is_open_tag
- && dox[..range.end].ends_with('>'))
+ let mut generics_end = range.end;
+ if let Some(Some(mut generics_start)) = (is_open_tag
+ && dox[..generics_end].ends_with('>'))
.then(|| extract_path_backwards(&dox, range.start))
{
+ while generics_start != 0
+ && generics_end < dox.len()
+ && dox.as_bytes()[generics_start - 1] == b'<'
+ && dox.as_bytes()[generics_end] == b'>'
+ {
+ generics_end += 1;
+ generics_start -= 1;
+ if let Some(new_start) = extract_path_backwards(&dox, generics_start) {
+ generics_start = new_start;
+ }
+ if let Some(new_end) = extract_path_forward(&dox, generics_end) {
+ generics_end = new_end;
+ }
+ }
+ if let Some(new_end) = extract_path_forward(&dox, generics_end) {
+ generics_end = new_end;
+ }
let generics_sp = match super::source_span_for_markdown_range(
tcx,
&dox,
- &(generics_start..range.end),
+ &(generics_start..generics_end),
&item.attrs,
) {
Some(sp) => sp,
None => item.attr_span(tcx),
};
+ // Sometimes, we only extract part of a path. For example, consider this:
+ //
+ // <[u32] as IntoIter<u32>>::Item
+ // ^^^^^ unclosed HTML tag `u32`
+ //
+ // We don't have any code for parsing fully-qualified trait paths.
+ // In theory, we could add it, but doing it correctly would require
+ // parsing the entire path grammar, which is problematic because of
+ // overlap between the path grammar and Markdown.
+ //
+ // The example above shows that ambiguity. Is `[u32]` intended to be an
+ // intra-doc link to the u32 primitive, or is it intended to be a slice?
+ //
+ // If the below conditional were removed, we would suggest this, which is
+ // not what the user probably wants.
+ //
+ // <[u32] as `IntoIter<u32>`>::Item
+ //
+ // We know that the user actually wants to wrap the whole thing in a code
+ // block, but the only reason we know that is because `u32` does not, in
+ // fact, implement IntoIter. If the example looks like this:
+ //
+ // <[Vec<i32>] as IntoIter<i32>::Item
+ //
+ // The ideal fix would be significantly different.
+ if (generics_start > 0 && dox.as_bytes()[generics_start - 1] == b'<')
+ || (generics_end < dox.len() && dox.as_bytes()[generics_end] == b'>')
+ {
+ return lint;
+ }
// multipart form is chosen here because ``Vec<i32>`` would be confusing.
- diag.multipart_suggestion(
+ lint.multipart_suggestion(
"try marking as source code",
vec![
(generics_sp.shrink_to_lo(), String::from("`")),
@@ -241,7 +367,8 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> {
Applicability::MaybeIncorrect,
);
}
- diag.emit()
+
+ lint
});
};
@@ -278,7 +405,7 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> {
for (event, range) in p {
match event {
Event::Start(Tag::CodeBlock(_)) => in_code_block = true,
- Event::Html(text) | Event::Text(text) if !in_code_block => {
+ Event::Html(text) if !in_code_block => {
extract_tags(&mut tags, &text, range, &mut is_in_comment, &report_diag)
}
Event::End(Tag::CodeBlock(_)) => in_code_block = false,
diff --git a/src/librustdoc/passes/propagate_doc_cfg.rs b/src/librustdoc/passes/propagate_doc_cfg.rs
index 0c5d83655..de3a4b339 100644
--- a/src/librustdoc/passes/propagate_doc_cfg.rs
+++ b/src/librustdoc/passes/propagate_doc_cfg.rs
@@ -2,29 +2,74 @@
use std::sync::Arc;
use crate::clean::cfg::Cfg;
-use crate::clean::{Crate, Item};
+use crate::clean::inline::{load_attrs, merge_attrs};
+use crate::clean::{Crate, Item, ItemKind};
use crate::core::DocContext;
use crate::fold::DocFolder;
use crate::passes::Pass;
+use rustc_hir::def_id::LocalDefId;
+
pub(crate) const PROPAGATE_DOC_CFG: Pass = Pass {
name: "propagate-doc-cfg",
run: propagate_doc_cfg,
description: "propagates `#[doc(cfg(...))]` to child items",
};
-pub(crate) fn propagate_doc_cfg(cr: Crate, _: &mut DocContext<'_>) -> Crate {
- CfgPropagator { parent_cfg: None }.fold_crate(cr)
+pub(crate) fn propagate_doc_cfg(cr: Crate, cx: &mut DocContext<'_>) -> Crate {
+ CfgPropagator { parent_cfg: None, parent: None, cx }.fold_crate(cr)
}
-struct CfgPropagator {
+struct CfgPropagator<'a, 'tcx> {
parent_cfg: Option<Arc<Cfg>>,
+ parent: Option<LocalDefId>,
+ cx: &'a mut DocContext<'tcx>,
+}
+
+impl<'a, 'tcx> CfgPropagator<'a, 'tcx> {
+ // Some items need to merge their attributes with their parents' otherwise a few of them
+ // (mostly `cfg` ones) will be missing.
+ fn merge_with_parent_attributes(&mut self, item: &mut Item) {
+ let check_parent = match &*item.kind {
+ // impl blocks can be in different modules with different cfg and we need to get them
+ // as well.
+ ItemKind::ImplItem(_) => false,
+ kind if kind.is_non_assoc() => true,
+ _ => return,
+ };
+
+ let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local())
+ else { return };
+
+ let hir = self.cx.tcx.hir();
+ let hir_id = hir.local_def_id_to_hir_id(def_id);
+
+ if check_parent {
+ let expected_parent = hir.get_parent_item(hir_id);
+ // If parents are different, it means that `item` is a reexport and we need
+ // to compute the actual `cfg` by iterating through its "real" parents.
+ if self.parent == Some(expected_parent.def_id) {
+ return;
+ }
+ }
+
+ let mut attrs = Vec::new();
+ for (parent_hir_id, _) in hir.parent_iter(hir_id) {
+ if let Some(def_id) = hir.opt_local_def_id(parent_hir_id) {
+ attrs.extend_from_slice(load_attrs(self.cx, def_id.to_def_id()));
+ }
+ }
+ let (_, cfg) = merge_attrs(self.cx, None, item.attrs.other_attrs.as_slice(), Some(&attrs));
+ item.cfg = cfg;
+ }
}
-impl DocFolder for CfgPropagator {
+impl<'a, 'tcx> DocFolder for CfgPropagator<'a, 'tcx> {
fn fold_item(&mut self, mut item: Item) -> Option<Item> {
let old_parent_cfg = self.parent_cfg.clone();
+ self.merge_with_parent_attributes(&mut item);
+
let new_cfg = match (self.parent_cfg.take(), item.cfg.take()) {
(None, None) => None,
(Some(rc), None) | (None, Some(rc)) => Some(rc),
@@ -37,8 +82,15 @@ impl DocFolder for CfgPropagator {
self.parent_cfg = new_cfg.clone();
item.cfg = new_cfg;
+ let old_parent =
+ if let Some(def_id) = item.item_id.as_def_id().and_then(|def_id| def_id.as_local()) {
+ self.parent.replace(def_id)
+ } else {
+ self.parent.take()
+ };
let result = self.fold_item_recur(item);
self.parent_cfg = old_parent_cfg;
+ self.parent = old_parent;
Some(result)
}
diff --git a/src/librustdoc/passes/strip_hidden.rs b/src/librustdoc/passes/strip_hidden.rs
index 533e2ce46..9914edf30 100644
--- a/src/librustdoc/passes/strip_hidden.rs
+++ b/src/librustdoc/passes/strip_hidden.rs
@@ -17,6 +17,7 @@ pub(crate) const STRIP_HIDDEN: Pass = Pass {
/// Strip items marked `#[doc(hidden)]`
pub(crate) fn strip_hidden(krate: clean::Crate, cx: &mut DocContext<'_>) -> clean::Crate {
let mut retained = ItemIdSet::default();
+ let is_json_output = cx.output_format.is_json() && !cx.show_coverage;
// strip all #[doc(hidden)] items
let krate = {
@@ -25,7 +26,12 @@ pub(crate) fn strip_hidden(krate: clean::Crate, cx: &mut DocContext<'_>) -> clea
};
// strip all impls referencing stripped items
- let mut stripper = ImplStripper { retained: &retained, cache: &cx.cache };
+ let mut stripper = ImplStripper {
+ retained: &retained,
+ cache: &cx.cache,
+ is_json_output,
+ document_private: cx.render_options.document_private,
+ };
stripper.fold_crate(krate)
}
diff --git a/src/librustdoc/passes/strip_private.rs b/src/librustdoc/passes/strip_private.rs
index 9ba841a31..450f69e15 100644
--- a/src/librustdoc/passes/strip_private.rs
+++ b/src/librustdoc/passes/strip_private.rs
@@ -17,19 +17,25 @@ pub(crate) const STRIP_PRIVATE: Pass = Pass {
pub(crate) fn strip_private(mut krate: clean::Crate, cx: &mut DocContext<'_>) -> clean::Crate {
// This stripper collects all *retained* nodes.
let mut retained = ItemIdSet::default();
+ let is_json_output = cx.output_format.is_json() && !cx.show_coverage;
// strip all private items
{
let mut stripper = Stripper {
retained: &mut retained,
- access_levels: &cx.cache.access_levels,
+ effective_visibilities: &cx.cache.effective_visibilities,
update_retained: true,
- is_json_output: cx.output_format.is_json() && !cx.show_coverage,
+ is_json_output,
};
krate = ImportStripper.fold_crate(stripper.fold_crate(krate));
}
// strip all impls referencing private items
- let mut stripper = ImplStripper { retained: &retained, cache: &cx.cache };
+ let mut stripper = ImplStripper {
+ retained: &retained,
+ cache: &cx.cache,
+ is_json_output,
+ document_private: cx.render_options.document_private,
+ };
stripper.fold_crate(krate)
}
diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs
index 0d419042a..0089ce63d 100644
--- a/src/librustdoc/passes/stripper.rs
+++ b/src/librustdoc/passes/stripper.rs
@@ -1,30 +1,34 @@
//! A collection of utility functions for the `strip_*` passes.
use rustc_hir::def_id::DefId;
-use rustc_middle::middle::privacy::AccessLevels;
+use rustc_middle::middle::privacy::EffectiveVisibilities;
+use rustc_span::symbol::sym;
+
use std::mem;
-use crate::clean::{self, Item, ItemId, ItemIdSet};
+use crate::clean::{self, Item, ItemId, ItemIdSet, NestedAttributesExt};
use crate::fold::{strip_item, DocFolder};
use crate::formats::cache::Cache;
pub(crate) struct Stripper<'a> {
pub(crate) retained: &'a mut ItemIdSet,
- pub(crate) access_levels: &'a AccessLevels<DefId>,
+ pub(crate) effective_visibilities: &'a EffectiveVisibilities<DefId>,
pub(crate) update_retained: bool,
pub(crate) is_json_output: bool,
}
-impl<'a> Stripper<'a> {
- // We need to handle this differently for the JSON output because some non exported items could
- // be used in public API. And so, we need these items as well. `is_exported` only checks if they
- // are in the public API, which is not enough.
- #[inline]
- fn is_item_reachable(&self, item_id: ItemId) -> bool {
- if self.is_json_output {
- self.access_levels.is_reachable(item_id.expect_def_id())
- } else {
- self.access_levels.is_exported(item_id.expect_def_id())
- }
+// We need to handle this differently for the JSON output because some non exported items could
+// be used in public API. And so, we need these items as well. `is_exported` only checks if they
+// are in the public API, which is not enough.
+#[inline]
+fn is_item_reachable(
+ is_json_output: bool,
+ effective_visibilities: &EffectiveVisibilities<DefId>,
+ item_id: ItemId,
+) -> bool {
+ if is_json_output {
+ effective_visibilities.is_reachable(item_id.expect_def_id())
+ } else {
+ effective_visibilities.is_exported(item_id.expect_def_id())
}
}
@@ -61,7 +65,9 @@ impl<'a> DocFolder for Stripper<'a> {
| clean::MacroItem(..)
| clean::ForeignTypeItem => {
let item_id = i.item_id;
- if item_id.is_local() && !self.is_item_reachable(item_id) {
+ if item_id.is_local()
+ && !is_item_reachable(self.is_json_output, self.effective_visibilities, item_id)
+ {
debug!("Stripper: stripping {:?} {:?}", i.type_(), i.name);
return None;
}
@@ -84,7 +90,17 @@ impl<'a> DocFolder for Stripper<'a> {
}
// handled in the `strip-priv-imports` pass
- clean::ExternCrateItem { .. } | clean::ImportItem(..) => {}
+ clean::ExternCrateItem { .. } => {}
+ clean::ImportItem(ref imp) => {
+ // Because json doesn't inline imports from private modules, we need to mark
+ // the imported item as retained so it's impls won't be stripped.
+ //
+ // FIXME: Is it necessary to check for json output here: See
+ // https://github.com/rust-lang/rust/pull/100325#discussion_r941495215
+ if let Some(did) = imp.source.did && self.is_json_output {
+ self.retained.insert(did.into());
+ }
+ }
clean::ImplItem(..) => {}
@@ -133,6 +149,24 @@ impl<'a> DocFolder for Stripper<'a> {
pub(crate) struct ImplStripper<'a> {
pub(crate) retained: &'a ItemIdSet,
pub(crate) cache: &'a Cache,
+ pub(crate) is_json_output: bool,
+ pub(crate) document_private: bool,
+}
+
+impl<'a> ImplStripper<'a> {
+ #[inline]
+ fn should_keep_impl(&self, item: &Item, for_def_id: DefId) -> bool {
+ if !for_def_id.is_local() || self.retained.contains(&for_def_id.into()) {
+ true
+ } else if self.is_json_output {
+ // 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(for_def_id)
+ && !item.attrs.lists(sym::doc).has_word(sym::hidden)
+ } else {
+ false
+ }
+ }
}
impl<'a> DocFolder for ImplStripper<'a> {
@@ -140,18 +174,39 @@ impl<'a> DocFolder for ImplStripper<'a> {
if let clean::ImplItem(ref imp) = *i.kind {
// Impl blocks can be skipped if they are: empty; not a trait impl; and have no
// documentation.
- if imp.trait_.is_none() && imp.items.is_empty() && i.doc_value().is_none() {
- return None;
+ //
+ // There is one special case: if the impl block contains only private items.
+ if imp.trait_.is_none() {
+ // If the only items present are private ones and we're not rendering private items,
+ // we don't document it.
+ if !imp.items.is_empty()
+ && !self.document_private
+ && imp.items.iter().all(|i| {
+ let item_id = i.item_id;
+ item_id.is_local()
+ && !is_item_reachable(
+ self.is_json_output,
+ &self.cache.effective_visibilities,
+ item_id,
+ )
+ })
+ {
+ return None;
+ } else if imp.items.is_empty() && i.doc_value().is_none() {
+ return None;
+ }
}
+ // Because we don't inline in `maybe_inline_local` if the output format is JSON,
+ // we need to make a special check for JSON output: we want to keep it unless it has
+ // a `#[doc(hidden)]` attribute if the `for_` type is exported.
if let Some(did) = imp.for_.def_id(self.cache) {
- if did.is_local() && !imp.for_.is_assoc_ty() && !self.retained.contains(&did.into())
- {
+ if !imp.for_.is_assoc_ty() && !self.should_keep_impl(&i, did) {
debug!("ImplStripper: impl item for stripped type; removing");
return None;
}
}
if let Some(did) = imp.trait_.as_ref().map(|t| t.def_id()) {
- if did.is_local() && !self.retained.contains(&did.into()) {
+ if !self.should_keep_impl(&i, did) {
debug!("ImplStripper: impl item for stripped trait; removing");
return None;
}
@@ -159,7 +214,7 @@ impl<'a> DocFolder for ImplStripper<'a> {
if let Some(generics) = imp.trait_.as_ref().and_then(|t| t.generics()) {
for typaram in generics {
if let Some(did) = typaram.def_id(self.cache) {
- if did.is_local() && !self.retained.contains(&did.into()) {
+ if !self.should_keep_impl(&i, did) {
debug!(
"ImplStripper: stripped item in trait's generics; removing impl"
);