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