1use std::env;
4use std::sync::Arc;
5
6use rustc_data_structures::fx::FxHashSet;
7use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId};
8use rustc_hir::{self as hir, CRATE_HIR_ID, intravisit};
9use rustc_middle::hir::nested_filter;
10use rustc_middle::ty::TyCtxt;
11use rustc_resolve::rustdoc::span_of_fragments;
12use rustc_span::source_map::SourceMap;
13use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span};
14
15use super::{DocTestVisitor, ScrapedDocTest};
16use crate::clean::{Attributes, extract_cfg_from_attrs};
17use crate::html::markdown::{self, ErrorCodes, LangString, MdRelLine};
18
19struct RustCollector {
20 source_map: Arc<SourceMap>,
21 tests: Vec<ScrapedDocTest>,
22 cur_path: Vec<String>,
23 position: Span,
24}
25
26impl RustCollector {
27 fn get_filename(&self) -> FileName {
28 let filename = self.source_map.span_to_filename(self.position);
29 if let FileName::Real(ref filename) = filename {
30 let path = filename.remapped_path_if_available();
31 let path = env::current_dir()
34 .map(|cur_dir| path.strip_prefix(&cur_dir).unwrap_or(path))
35 .unwrap_or(path);
36 return path.to_owned().into();
37 }
38 filename
39 }
40
41 fn get_base_line(&self) -> usize {
42 let sp_lo = self.position.lo().to_usize();
43 let loc = self.source_map.lookup_char_pos(BytePos(sp_lo as u32));
44 loc.line
45 }
46}
47
48impl DocTestVisitor for RustCollector {
49 fn visit_test(&mut self, test: String, config: LangString, rel_line: MdRelLine) {
50 let line = self.get_base_line() + rel_line.offset();
51 self.tests.push(ScrapedDocTest::new(
52 self.get_filename(),
53 line,
54 self.cur_path.clone(),
55 config,
56 test,
57 ));
58 }
59
60 fn visit_header(&mut self, _name: &str, _level: u32) {}
61}
62
63pub(super) struct HirCollector<'tcx> {
64 codes: ErrorCodes,
65 tcx: TyCtxt<'tcx>,
66 enable_per_target_ignores: bool,
67 collector: RustCollector,
68}
69
70impl<'tcx> HirCollector<'tcx> {
71 pub fn new(codes: ErrorCodes, enable_per_target_ignores: bool, tcx: TyCtxt<'tcx>) -> Self {
72 let collector = RustCollector {
73 source_map: tcx.sess.psess.clone_source_map(),
74 cur_path: vec![],
75 position: DUMMY_SP,
76 tests: vec![],
77 };
78 Self { codes, enable_per_target_ignores, tcx, collector }
79 }
80
81 pub fn collect_crate(mut self) -> Vec<ScrapedDocTest> {
82 let tcx = self.tcx;
83 self.visit_testable("".to_string(), CRATE_DEF_ID, tcx.hir().span(CRATE_HIR_ID), |this| {
84 tcx.hir().walk_toplevel_module(this)
85 });
86 self.collector.tests
87 }
88}
89
90impl HirCollector<'_> {
91 fn visit_testable<F: FnOnce(&mut Self)>(
92 &mut self,
93 name: String,
94 def_id: LocalDefId,
95 sp: Span,
96 nested: F,
97 ) {
98 let ast_attrs = self.tcx.hir().attrs(self.tcx.local_def_id_to_hir_id(def_id));
99 if let Some(ref cfg) =
100 extract_cfg_from_attrs(ast_attrs.iter(), self.tcx, &FxHashSet::default())
101 {
102 if !cfg.matches(&self.tcx.sess.psess, Some(self.tcx.features())) {
103 return;
104 }
105 }
106
107 let has_name = !name.is_empty();
108 if has_name {
109 self.collector.cur_path.push(name);
110 }
111
112 let attrs = Attributes::from_hir(ast_attrs);
115 if let Some(doc) = attrs.opt_doc_value() {
116 let span = span_of_fragments(&attrs.doc_strings).unwrap_or(sp);
117 self.collector.position = if span.edition().at_least_rust_2024() {
118 span
119 } else {
120 ast_attrs
123 .iter()
124 .find(|attr| attr.doc_str().is_some())
125 .map(|attr| {
126 attr.span.ctxt().outer_expn().expansion_cause().unwrap_or(attr.span)
127 })
128 .unwrap_or(DUMMY_SP)
129 };
130 markdown::find_testable_code(
131 &doc,
132 &mut self.collector,
133 self.codes,
134 self.enable_per_target_ignores,
135 Some(&crate::html::markdown::ExtraInfo::new(self.tcx, def_id, span)),
136 );
137 }
138
139 nested(self);
140
141 if has_name {
142 self.collector.cur_path.pop();
143 }
144 }
145}
146
147impl<'tcx> intravisit::Visitor<'tcx> for HirCollector<'tcx> {
148 type NestedFilter = nested_filter::All;
149
150 fn nested_visit_map(&mut self) -> Self::Map {
151 self.tcx.hir()
152 }
153
154 fn visit_item(&mut self, item: &'tcx hir::Item<'_>) {
155 let name = match &item.kind {
156 hir::ItemKind::Impl(impl_) => {
157 rustc_hir_pretty::id_to_string(&self.tcx.hir(), impl_.self_ty.hir_id)
158 }
159 _ => item.ident.to_string(),
160 };
161
162 self.visit_testable(name, item.owner_id.def_id, item.span, |this| {
163 intravisit::walk_item(this, item);
164 });
165 }
166
167 fn visit_trait_item(&mut self, item: &'tcx hir::TraitItem<'_>) {
168 self.visit_testable(item.ident.to_string(), item.owner_id.def_id, item.span, |this| {
169 intravisit::walk_trait_item(this, item);
170 });
171 }
172
173 fn visit_impl_item(&mut self, item: &'tcx hir::ImplItem<'_>) {
174 self.visit_testable(item.ident.to_string(), item.owner_id.def_id, item.span, |this| {
175 intravisit::walk_impl_item(this, item);
176 });
177 }
178
179 fn visit_foreign_item(&mut self, item: &'tcx hir::ForeignItem<'_>) {
180 self.visit_testable(item.ident.to_string(), item.owner_id.def_id, item.span, |this| {
181 intravisit::walk_foreign_item(this, item);
182 });
183 }
184
185 fn visit_variant(&mut self, v: &'tcx hir::Variant<'_>) {
186 self.visit_testable(v.ident.to_string(), v.def_id, v.span, |this| {
187 intravisit::walk_variant(this, v);
188 });
189 }
190
191 fn visit_field_def(&mut self, f: &'tcx hir::FieldDef<'_>) {
192 self.visit_testable(f.ident.to_string(), f.def_id, f.span, |this| {
193 intravisit::walk_field_def(this, f);
194 });
195 }
196}