diff options
| author | Kyle Lin <minecraft.kyle.train@gmail.com> | 2023-06-30 18:10:01 +0800 |
|---|---|---|
| committer | Kyle Lin <minecraft.kyle.train@gmail.com> | 2023-08-18 15:19:15 +0800 |
| commit | c7369891ba3265e47346bf44d6f5b5bf4451f3ca (patch) | |
| tree | 38085c48a7c3a7cba9c17f706f752788746d52cf /src | |
| parent | e583318aa864ce0a3d53c339600d51a70d7b6440 (diff) | |
| download | rust-c7369891ba3265e47346bf44d6f5b5bf4451f3ca.tar.gz rust-c7369891ba3265e47346bf44d6f5b5bf4451f3ca.zip | |
Refactor lint from rustc to rustdoc
Diffstat (limited to 'src')
| -rw-r--r-- | src/librustdoc/passes/collect_intra_doc_links.rs | 30 | ||||
| -rw-r--r-- | src/librustdoc/passes/lint.rs | 2 | ||||
| -rw-r--r-- | src/librustdoc/passes/lint/redundant_explicit_links.rs | 188 |
3 files changed, 192 insertions, 28 deletions
diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index d78fec2cb0a..0432bd40c90 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -1041,7 +1041,6 @@ impl LinkCollector<'_, '_> { )?; self.check_redundant_explicit_link( - &res, path_str, ResolutionInfo { item_id, @@ -1388,7 +1387,6 @@ impl LinkCollector<'_, '_> { /// Check if resolution of inline link's display text and explicit link are same. fn check_redundant_explicit_link( &mut self, - explicit_res: &Res, explicit_link: &Box<str>, display_res_info: ResolutionInfo, ori_link: &MarkdownLink, @@ -1415,38 +1413,14 @@ impl LinkCollector<'_, '_> { if explicit_len >= display_len && &explicit_link[(explicit_len - display_len)..] == display_text { - let Some((display_res, _)) = self.resolve_with_disambiguator_cached( + self.resolve_with_disambiguator_cached( display_res_info, diag_info.clone(), // this struct should really be Copy, but Range is not :( // For reference-style links we want to report only one error so unsuccessful // 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), - ) else { - return; - }; - - if &display_res == explicit_res { - use crate::lint::REDUNDANT_EXPLICIT_LINKS; - - report_diagnostic( - self.cx.tcx, - REDUNDANT_EXPLICIT_LINKS, - "redundant explicit rustdoc link", - &diag_info, - |diag, sp, _link_range| { - if let Some(sp) = sp { - diag.note("Explicit link does not affect the original link") - .span_suggestion_hidden( - sp, - "Remove explicit link instead", - format!(""), - Applicability::MachineApplicable, - ); - } - }, - ); - } + ); } } } diff --git a/src/librustdoc/passes/lint.rs b/src/librustdoc/passes/lint.rs index e653207b9b6..c6d5b7bd346 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/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs new file mode 100644 index 00000000000..1bccedde92a --- /dev/null +++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs @@ -0,0 +1,188 @@ +use std::ops::Range; + +use pulldown_cmark::{Parser, BrokenLink, Event, Tag, LinkType, OffsetIter}; +use rustc_ast::NodeId; +use rustc_errors::SuggestionStyle; +use rustc_hir::HirId; +use rustc_hir::def::{Namespace, DefKind, DocLinkResMap, Res}; +use rustc_lint_defs::Applicability; +use rustc_span::Symbol; + +use crate::clean::Item; +use crate::clean::utils::find_nearest_parent_module; +use crate::core::DocContext; +use crate::html::markdown::main_body_opts; +use crate::passes::source_span_for_markdown_range; + +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; + } + + 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) { + 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(); + + 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); + let dest = dest.to_string(); + + if link_type == LinkType::Inline { + check_inline_link_redundancy(cx, item, hir_id, doc, link_range, dest, link_data); + } + } + _ => {} + } + } +} + +fn check_inline_link_redundancy(cx: &DocContext<'_>, item: &Item, hir_id: HirId, doc: &str, link_range: Range<usize>, dest: String, link_data: LinkData) -> Option<()> { + 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); + + 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, b'(', b')'), + &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 +} + +fn find_resolution<'tcx>(resolutions: &'tcx DocLinkResMap, path: &str) -> Option<&'tcx Res<NodeId>> { + for ns in [Namespace::TypeNS, Namespace::ValueNS, Namespace::MacroNS] { + let Some(Some(res)) = resolutions.get(&(Symbol::intern(path), ns)) + else { + continue; + }; + + return Some(res); + } + + None +} + +/// 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.clone(); + } + + 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.clone(); + } + // do not actually include braces in the span + (open_brace + 1)..close_brace +} |
