use rustc_hir as hir;
use rustc_hir::intravisit::{self, Visitor};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::TyCtxt;
use rustc_span::Span;
pub(crate) fn find_enclosing_hir_ids(tcx: TyCtxt<'_>, target_spans: &[Span]) -> Vec<hir::HirId> {
walk(tcx, target_spans, false)
}
#[cfg_attr(
dylint_lib = "perfectionist",
expect(
perfectionist::unicode_ellipsis_in_docs,
reason = "this doc names the U+2026 glyph the comment-walking rules handle"
)
)]
fn find_comment_anchor_hir_ids(tcx: TyCtxt<'_>, target_spans: &[Span]) -> Vec<hir::HirId> {
walk(tcx, target_spans, true)
}
fn walk(tcx: TyCtxt<'_>, target_spans: &[Span], include_attr_spans: bool) -> Vec<hir::HirId> {
let mut best: Vec<hir::HirId> = vec![hir::CRATE_HIR_ID; target_spans.len()];
let mut best_width: Vec<u32> = vec![u32::MAX; target_spans.len()];
let mut finder = EnclosingHirFinder {
tcx,
targets: target_spans,
best: &mut best,
best_width: &mut best_width,
include_attr_spans,
};
tcx.hir_walk_toplevel_module(&mut finder);
best
}
pub(crate) fn emit_at_enclosing_hir<Payload>(
tcx: TyCtxt<'_>,
violations: Vec<(Span, Payload)>,
mut emit: impl FnMut(hir::HirId, Span, Payload),
) {
if violations.is_empty() {
return;
}
let target_spans: Vec<Span> = violations.iter().map(|(span, _)| *span).collect();
let hir_ids = find_comment_anchor_hir_ids(tcx, &target_spans);
for ((span, payload), hir_id) in violations.into_iter().zip(hir_ids) {
emit(hir_id, span, payload);
}
}
struct EnclosingHirFinder<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
targets: &'a [Span],
best: &'a mut [hir::HirId],
best_width: &'a mut [u32],
include_attr_spans: bool,
}
impl<'a, 'tcx> EnclosingHirFinder<'a, 'tcx> {
fn register_documentable(&mut self, hir_id: hir::HirId, span: Span) {
self.update(hir_id, span);
if self.include_attr_spans {
for attr in self.tcx.hir_attrs(hir_id) {
if let Some(doc_span) = attr.is_doc_comment() {
self.update(hir_id, doc_span);
}
}
}
}
fn update(&mut self, hir_id: hir::HirId, span: Span) {
for (index, &target) in self.targets.iter().enumerate() {
if self.include_attr_spans {
let Some(width) = comment_enclosure_width(span, target) else {
continue;
};
if width >= self.best_width[index] {
continue;
}
self.best_width[index] = width;
self.best[index] = hir_id;
} else {
if !contains(span, target) {
continue;
}
self.best[index] = hir_id;
}
}
}
}
fn contains(item_span: Span, target: Span) -> bool {
item_span.contains(target)
|| item_span
.source_callsite()
.contains(target.source_callsite())
}
fn comment_enclosure_width(candidate: Span, target: Span) -> Option<u32> {
if candidate.contains(target) {
return Some(span_width(candidate));
}
let resolved = candidate.source_callsite();
if resolved.contains(target.source_callsite()) {
return Some(span_width(resolved));
}
None
}
fn span_width(span: Span) -> u32 {
span.hi().0.saturating_sub(span.lo().0)
}
impl<'tcx> Visitor<'tcx> for EnclosingHirFinder<'_, 'tcx> {
type NestedFilter = nested_filter::All;
fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.tcx
}
fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
self.register_documentable(item.hir_id(), item.span);
intravisit::walk_item(self, item);
}
fn visit_trait_item(&mut self, item: &'tcx hir::TraitItem<'tcx>) {
self.register_documentable(item.hir_id(), item.span);
intravisit::walk_trait_item(self, item);
}
fn visit_impl_item(&mut self, item: &'tcx hir::ImplItem<'tcx>) {
self.register_documentable(item.hir_id(), item.span);
intravisit::walk_impl_item(self, item);
}
fn visit_foreign_item(&mut self, item: &'tcx hir::ForeignItem<'tcx>) {
self.register_documentable(item.hir_id(), item.span);
intravisit::walk_foreign_item(self, item);
}
fn visit_variant(&mut self, variant: &'tcx hir::Variant<'tcx>) {
if self.include_attr_spans {
self.register_documentable(variant.hir_id, variant.span);
}
intravisit::walk_variant(self, variant);
}
fn visit_field_def(&mut self, field: &'tcx hir::FieldDef<'tcx>) {
if self.include_attr_spans {
self.register_documentable(field.hir_id, field.span);
}
intravisit::walk_field_def(self, field);
}
fn visit_block(&mut self, block: &'tcx hir::Block<'tcx>) {
self.update(block.hir_id, block.span);
intravisit::walk_block(self, block);
}
fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) {
self.update(stmt.hir_id, stmt.span);
intravisit::walk_stmt(self, stmt);
}
fn visit_local(&mut self, local: &'tcx hir::LetStmt<'tcx>) {
self.update(local.hir_id, local.span);
intravisit::walk_local(self, local);
}
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
self.update(expr.hir_id, expr.span);
intravisit::walk_expr(self, expr);
}
fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
self.update(pat.hir_id, pat.span);
intravisit::walk_pat(self, pat);
}
}