Skip to main content

cargo_doc2readme/
input.rs

1use crate::{
2	diagnostic::{self, DiagnosticExt as _, DiagnosticPrinter},
3	preproc::Preprocessor
4};
5use camino::Utf8Path;
6use cargo_metadata::{Edition, Metadata, Package, Target};
7use either::Either;
8use hashbrown::{HashMap, HashSet};
9use miette::{IntoDiagnostic as _, NamedSource};
10use proc_macro2::{Span, TokenStream, TokenTree};
11use quote::ToTokens as _;
12use semver::{Comparator, Op, Version, VersionReq};
13use serde::Serialize;
14use std::{
15	collections::VecDeque,
16	fmt::{self, Debug, Formatter},
17	fs::File,
18	io::{self, BufReader, Cursor, Read, Write},
19	path::Path,
20	process::{Command, Output}
21};
22use syn::{
23	parse::{Parse, ParseStream},
24	spanned::Spanned as _,
25	Expr, ExprLit, Ident, Item, ItemMacro, ItemUse, Lit, LitStr, Meta, Token, UsePath,
26	UseTree, Visibility
27};
28
29type ScopeScope = HashMap<String, VecDeque<(LinkType, String)>>;
30
31pub struct Scope {
32	// use statements and declared items. maps name to path.
33	pub scope: ScopeScope,
34	// private modules so that `pub use`'d items are considered inlined.
35	pub privmods: HashSet<String>
36}
37
38impl Debug for Scope {
39	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
40		struct ScopeScopeDebug<'a>(&'a ScopeScope);
41		impl Debug for ScopeScopeDebug<'_> {
42			fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
43				let mut dbg = f.debug_map();
44				for (name, paths) in self.0 {
45					match paths.len() {
46						0 => {
47							// wtf, can this even happen?
48							continue;
49						},
50						1 => {
51							dbg.entry(name, &paths[0]);
52						},
53						_ => {
54							dbg.entry(name, paths);
55						}
56					}
57				}
58				dbg.finish()
59			}
60		}
61
62		f.debug_struct("Scope")
63			.field("scope", &ScopeScopeDebug(&self.scope))
64			.field("privmods", &self.privmods)
65			.finish()
66	}
67}
68
69#[derive(Clone, Copy, Debug, Eq, PartialEq)]
70pub enum LinkType {
71	/// A procedural macro attribute.
72	Attr,
73	Const,
74	/// A procedural derive macro.
75	Derive,
76	Enum,
77	ExternCrate,
78	Function,
79	/// A `macro` can be both a procedural and declarative macro.
80	Macro,
81	Mod,
82	Static,
83	Struct,
84	Trait,
85	TraitAlias,
86	Type,
87	Union,
88
89	/// `use` statement that links to a path.
90	Use,
91	/// `pub use` statement that links to the name pub used as.
92	PubUse,
93
94	/// Primitive from the standard library
95	Primitive
96}
97
98fn make_prelude<const N: usize>(
99	prelude: [(&'static str, &'static str, LinkType); N]
100) -> ScopeScope {
101	prelude
102		.into_iter()
103		.flat_map(|(name, path, link_type)| {
104			let path = match path {
105				"" => format!("::std::{name}"),
106				_ => format!("::std::{path}::{name}")
107			};
108			let items: VecDeque<_> = [(link_type, path)].into_iter().collect();
109			match link_type {
110				LinkType::Macro => Either::Left(
111					[(name.into(), items.clone()), (format!("{name}!"), items)]
112						.into_iter()
113				),
114				_ => Either::Right([(name.into(), items)].into_iter())
115			}
116		})
117		.collect()
118}
119
120impl Scope {
121	fn insert<K, V>(&mut self, key: K, ty: LinkType, value: V)
122	where
123		K: Into<String>,
124		V: Into<String>
125	{
126		self.scope
127			.entry(key.into())
128			.or_default()
129			.push_front((ty, value.into()));
130	}
131
132	pub(crate) fn empty() -> Self {
133		Self {
134			scope: HashMap::new(),
135			privmods: HashSet::new()
136		}
137	}
138
139	/// Create a new scope from the Rust prelude.
140	pub fn prelude(edition: Edition) -> Self {
141		let mut scope = Self {
142			scope: make_prelude([
143				// https://doc.rust-lang.org/stable/std/primitive/index.html#reexports
144				("bool", "", LinkType::Primitive),
145				("char", "", LinkType::Primitive),
146				("f32", "", LinkType::Primitive),
147				("f64", "", LinkType::Primitive),
148				("i128", "", LinkType::Primitive),
149				("i16", "", LinkType::Primitive),
150				("i32", "", LinkType::Primitive),
151				("i64", "", LinkType::Primitive),
152				("i8", "", LinkType::Primitive),
153				("isize", "", LinkType::Primitive),
154				("str", "", LinkType::Primitive),
155				("u128", "", LinkType::Primitive),
156				("u16", "", LinkType::Primitive),
157				("u32", "", LinkType::Primitive),
158				("u64", "", LinkType::Primitive),
159				("u8", "", LinkType::Primitive),
160				("usize", "", LinkType::Primitive),
161				// https://doc.rust-lang.org/stable/std/prelude/index.html#prelude-contents
162				("Copy", "marker", LinkType::Trait),
163				("Send", "marker", LinkType::Trait),
164				("Sized", "marker", LinkType::Trait),
165				("Sync", "marker", LinkType::Trait),
166				("Unpin", "marker", LinkType::Trait),
167				("Drop", "ops", LinkType::Trait),
168				("Fn", "ops", LinkType::Trait),
169				("FnMut", "ops", LinkType::Trait),
170				("FnOnce", "ops", LinkType::Trait),
171				("drop", "mem", LinkType::Function),
172				("Box", "boxed", LinkType::Struct),
173				("ToOwned", "borrow", LinkType::Trait),
174				("Clone", "clone", LinkType::Trait),
175				("PartialEq", "cmp", LinkType::Trait),
176				("PartialOrd", "cmp", LinkType::Trait),
177				("Eq", "cmp", LinkType::Trait),
178				("Ord", "cmp", LinkType::Trait),
179				("AsRef", "convert", LinkType::Trait),
180				("AsMut", "convert", LinkType::Trait),
181				("Into", "convert", LinkType::Trait),
182				("From", "convert", LinkType::Trait),
183				("Default", "default", LinkType::Trait),
184				("Iterator", "iter", LinkType::Trait),
185				("Extend", "iter", LinkType::Trait),
186				("IntoIterator", "iter", LinkType::Trait),
187				("DoubleEndedIterator", "iter", LinkType::Trait),
188				("ExactSizeIterator", "iter", LinkType::Trait),
189				("Option", "option", LinkType::Enum),
190				("Some", "option::Option", LinkType::Use),
191				("None", "option::Option", LinkType::Use),
192				("Result", "result", LinkType::Struct),
193				("Ok", "result::Result", LinkType::Use),
194				("Err", "result::Result", LinkType::Use),
195				("String", "string", LinkType::Struct),
196				("ToString", "string", LinkType::Trait),
197				("Vec", "vec", LinkType::Struct),
198				// https://doc.rust-lang.org/stable/std/index.html#macros
199				("assert", "", LinkType::Macro),
200				("assert_eq", "", LinkType::Macro),
201				("assert_ne", "", LinkType::Macro),
202				("cfg", "", LinkType::Macro),
203				("column", "", LinkType::Macro),
204				("compile_error", "", LinkType::Macro),
205				("concat", "", LinkType::Macro),
206				("dbg", "", LinkType::Macro),
207				("debug_assert", "", LinkType::Macro),
208				("debug_assert_eq", "", LinkType::Macro),
209				("debug_assert_ne", "", LinkType::Macro),
210				("env", "", LinkType::Macro),
211				("eprint", "", LinkType::Macro),
212				("eprintln", "", LinkType::Macro),
213				("file", "", LinkType::Macro),
214				("format", "", LinkType::Macro),
215				("format_args", "", LinkType::Macro),
216				("include", "", LinkType::Macro),
217				("include_bytes", "", LinkType::Macro),
218				("include_str", "", LinkType::Macro),
219				("is_x86_feature_detected", "", LinkType::Macro),
220				("line", "", LinkType::Macro),
221				("matches", "", LinkType::Macro),
222				("module_path", "", LinkType::Macro),
223				("option_env", "", LinkType::Macro),
224				("panic", "", LinkType::Macro),
225				("print", "", LinkType::Macro),
226				("println", "", LinkType::Macro),
227				("stringify", "", LinkType::Macro),
228				("thread_local", "", LinkType::Macro),
229				("todo", "", LinkType::Macro),
230				("unimplemented", "", LinkType::Macro),
231				("unreachable", "", LinkType::Macro),
232				("vec", "", LinkType::Macro),
233				("write", "", LinkType::Macro),
234				("writeln", "", LinkType::Macro)
235			]),
236			privmods: HashSet::new()
237		};
238
239		if edition >= Edition::E2021 {
240			// https://blog.rust-lang.org/2021/05/11/edition-2021.html#additions-to-the-prelude
241			for (key, value) in make_prelude([
242				("TryInto", "convert", LinkType::Use),
243				("TryFrom", "convert", LinkType::Use),
244				("FromIterator", "iter", LinkType::Use)
245			]) {
246				scope.scope.insert(key, value);
247			}
248		}
249
250		if edition >= Edition::E2024 {
251			// https://doc.rust-lang.org/edition-guide/rust-2024/prelude.html
252			for (key, value) in make_prelude([
253				("Future", "future", LinkType::Trait),
254				("IntoFuture", "future", LinkType::Trait)
255			]) {
256				scope.scope.insert(key, value);
257			}
258		}
259
260		scope
261	}
262}
263
264#[derive(Debug)]
265pub struct CrateCode {
266	pub filename: String,
267	pub code: String
268}
269
270impl From<&CrateCode> for NamedSource<String> {
271	fn from(code: &CrateCode) -> Self {
272		NamedSource::new(&code.filename, code.code.clone()).with_language("Rust")
273	}
274}
275
276impl CrateCode {
277	pub fn new_unknown() -> Self {
278		Self {
279			filename: "<unknown>".into(),
280			code: String::new()
281		}
282	}
283
284	fn read_from<R>(filename: String, read: R) -> io::Result<Self>
285	where
286		R: io::BufRead
287	{
288		let mut preproc = Preprocessor::new(read);
289		let mut code = String::new();
290		preproc.read_to_string(&mut code)?;
291
292		Ok(Self { filename, code })
293	}
294
295	pub fn read_from_disk<P>(path: P) -> io::Result<Self>
296	where
297		P: AsRef<Utf8Path>
298	{
299		let path = path.as_ref();
300		let filename = path.file_name().unwrap_or(path.as_str());
301		Self::read_from(filename.into(), BufReader::new(File::open(path)?))
302	}
303
304	pub fn read_expansion<P>(
305		manifest_path: Option<P>,
306		package: Option<&str>,
307		target: &Target,
308		features: Option<&str>,
309		no_default_features: bool,
310		all_features: bool
311	) -> miette::Result<CrateCode>
312	where
313		P: AsRef<Path>
314	{
315		let mut cmd = Command::new("cargo");
316		cmd.arg("+nightly").arg("rustc");
317		if let Some(manifest_path) = manifest_path {
318			cmd.arg("--manifest-path").arg(manifest_path.as_ref());
319		}
320		if let Some(package) = package {
321			cmd.arg("-p").arg(package);
322		}
323		if let Some(features) = features {
324			cmd.arg("--features").arg(features);
325		}
326		if no_default_features {
327			cmd.arg("--no-default-features");
328		}
329		if all_features {
330			cmd.arg("--all-features");
331		}
332		if target.is_lib() {
333			cmd.arg("--lib");
334		} else if target.is_bin() {
335			cmd.arg("--bin").arg(&target.name);
336		}
337		cmd.arg("--").arg("-Zunpretty=expanded");
338
339		let Output {
340			stdout,
341			stderr,
342			status
343		} = cmd.output()
344			.map_err(|err| diagnostic::ExecError::new(err, &cmd))?;
345
346		if !status.success() {
347			// Something bad happened during the compilation. Let's print
348			// anything Cargo reported to us and return an error.
349			io::stdout()
350				.lock()
351				.write_all(stderr.as_slice())
352				.expect("Failed to write cargo errors to stdout");
353
354			let err = diagnostic::ExecError::new(
355				format!("Command finished with non-zero exit code {status}"),
356				&cmd
357			);
358			return Err(err.into());
359		}
360
361		Self::read_from("<cargo-expand>".into(), Cursor::new(stdout)).into_diagnostic()
362	}
363}
364
365/// The crate target type, either `bin` or `lib`.
366///
367/// All variants are renamed to their lowercase counterparts for the template.
368#[derive(Clone, Copy, Debug, Serialize)]
369#[serde(rename_all = "lowercase")]
370pub enum TargetType {
371	Bin,
372	Lib
373}
374
375#[derive(Debug)]
376pub struct InputFile {
377	/// The name of the crate.
378	pub crate_name: String,
379	/// The version of the crate
380	pub crate_version: Version,
381	/// The target type.
382	pub target_type: TargetType,
383	/// The repository url (if specified).
384	pub repository: Option<String>,
385	/// The license field (if specified).
386	pub license: Option<String>,
387	/// The rust_version field (if specified).
388	pub rust_version: Option<Version>,
389	/// The unmodified rustdoc string
390	pub rustdoc: String,
391	/// The crate-level dependencies, mapping the valid identifier in rust code to the (possibly
392	/// renamed, containing invalid characters, etc.) crate name and version.
393	pub dependencies: HashMap<String, Dependency>,
394	/// The scope at the crate root.
395	pub scope: Scope
396}
397
398impl InputFile {
399	pub fn dummy() -> Self {
400		Self {
401			crate_name: "N/A".into(),
402			crate_version: Version::new(0, 0, 0),
403			target_type: TargetType::Lib,
404			repository: None,
405			license: None,
406			rust_version: None,
407			rustdoc: String::new(),
408			dependencies: HashMap::new(),
409			scope: Scope::empty()
410		}
411	}
412}
413
414pub struct Dependency {
415	/// The crate name as it appears on crates.io.
416	pub crate_name: String,
417
418	/// The version requirement of the dependency.
419	pub req: VersionReq,
420
421	/// The exact version of the dependency.
422	pub version: Version
423}
424
425impl Dependency {
426	pub fn new(crate_name: String, req: VersionReq, version: Version) -> Self {
427		Self {
428			crate_name,
429			req,
430			version
431		}
432	}
433
434	pub fn as_tuple(&self) -> (&str, Option<&Version>) {
435		(self.crate_name.as_str(), Some(&self.version))
436	}
437}
438
439impl Debug for Dependency {
440	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
441		write!(
442			f,
443			"{} = \"{}\" ({})",
444			self.crate_name, self.req, self.version
445		)
446	}
447}
448
449pub fn read_code<W: io::Write>(
450	metadata: &Metadata,
451	pkg: &Package,
452	code: &CrateCode,
453	target_type: TargetType,
454	printer_stderr: W
455) -> miette::Result<InputFile> {
456	let crate_name = pkg.name.as_str().to_owned();
457	let crate_version = pkg.version.clone();
458	let repository = pkg.repository.clone();
459	let license = pkg.license.clone();
460	let rust_version = pkg.rust_version.clone();
461
462	let file = match syn::parse_file(code.code.as_str()) {
463		Ok(file) => file,
464		Err(err) => {
465			let err = diagnostic::SyntaxError::new_rust(code, err);
466			return Err(err.into());
467		}
468	};
469
470	let mut printer = DiagnosticPrinter::new(code, printer_stderr);
471
472	let rustdoc = read_rustdoc_from_file(&file, &mut printer)?;
473	let dependencies = resolve_dependencies(metadata, pkg, &mut printer);
474	let scope = read_scope_from_file(pkg, &file, &mut printer)?;
475
476	Ok(InputFile {
477		crate_name,
478		crate_version,
479		target_type,
480		repository,
481		license,
482		rust_version,
483		rustdoc,
484		dependencies,
485		scope
486	})
487}
488
489fn read_rustdoc_from_file<W: io::Write>(
490	file: &syn::File,
491	printer: &mut DiagnosticPrinter<'_, W>
492) -> miette::Result<String> {
493	let mut doc = String::new();
494	let mut errs = Vec::new();
495	for attr in &file.attrs {
496		match &attr.meta {
497			Meta::NameValue(nv) if nv.path.is_ident("doc") => {
498				match parse_doc_attr(&nv.value, printer) {
499					Ok(Some(str)) => {
500						doc.push('\n');
501						doc.push_str(&str.value());
502					},
503					Ok(None) => {},
504					Err(err) => {
505						errs.push(err);
506					}
507				}
508			},
509
510			Meta::List(l) if l.path.is_ident("cfg_attr") => {
511				parse_cfg_attr(l.tokens.clone(), attr.span(), printer);
512			},
513
514			_ => {}
515		}
516	}
517	if !errs.is_empty() {
518		let err = diagnostic::SyntaxError::new_rust_multi(printer.code, errs);
519		Err(err.into())
520	} else {
521		Ok(doc)
522	}
523}
524
525/// Parse the expr of a `#[doc = ...]` attribute. Returns a string if possible, a warning
526/// if it encounters an unexpanded macro or an error if it finds something else.
527fn parse_doc_attr<W: io::Write>(
528	expr: &Expr,
529	printer: &mut DiagnosticPrinter<'_, W>
530) -> syn::Result<Option<LitStr>> {
531	match expr {
532		Expr::Lit(ExprLit {
533			lit: Lit::Str(lit), ..
534		}) => Ok(Some(lit.clone())),
535		Expr::Macro(makro) => {
536			diagnostic::MacroNotExpanded::new(printer.code, makro.span()).print(printer);
537			Ok(None)
538		},
539		expr => Err(syn::Error::new(
540			expr.span(),
541			"Expected string literal or macro invokation"
542		))
543	}
544}
545
546/// Parse a `#[cfg_attr(..., ...)]` attribute. Returns a warning if it contains a doc
547/// attribute.
548fn parse_cfg_attr<W: io::Write>(
549	tokens: TokenStream,
550	span: Span,
551	printer: &mut DiagnosticPrinter<'_, W>
552) {
553	struct CfgAttr;
554
555	impl Parse for CfgAttr {
556		fn parse(input: ParseStream) -> syn::Result<Self> {
557			// skip to the 2nd argument
558			while !input.peek(Token![,]) {
559				let _: TokenTree = input.parse()?;
560			}
561			let _: Token![,] = input.parse()?;
562
563			let path: syn::Path = input.parse()?;
564			if !path.is_ident("doc") {
565				return Err(syn::Error::new(
566					path.span(),
567					format!("Expected `doc`, found `{}`", path.into_token_stream())
568				));
569			}
570
571			let _: Token![=] = input.parse()?;
572			let _: TokenStream = input.parse()?;
573			Ok(CfgAttr)
574		}
575	}
576
577	if syn::parse2::<CfgAttr>(tokens).is_ok() {
578		diagnostic::MacroNotExpanded::new(printer.code, span).print(printer);
579	}
580}
581
582fn sanitize_crate_name<T: AsRef<str>>(name: T) -> String {
583	name.as_ref().replace('-', "_")
584}
585
586fn resolve_dependencies<W: io::Write>(
587	metadata: &Metadata,
588	pkg: &Package,
589	printer: &mut DiagnosticPrinter<'_, W>
590) -> HashMap<String, Dependency> {
591	let mut deps = HashMap::new();
592
593	// We currently insert our own crate as a dependency to allow doc links referencing
594	// ourself. However, we might want to change this so that custom doc urls can be used,
595	// so that e.g. the repository readme points to some rustdoc generated from the main
596	// branch, instead of the last release. Also, the version in the current manifest
597	// might not be released, so those links might be dead.
598	let version = pkg.version.clone();
599	deps.insert(
600		sanitize_crate_name(&pkg.name),
601		Dependency::new(
602			pkg.name.as_str().to_owned(),
603			[Comparator {
604				op: Op::Exact,
605				major: version.major,
606				minor: Some(version.minor),
607				patch: Some(version.patch),
608				pre: version.pre.clone()
609			}]
610			.into_iter()
611			.collect(),
612			version
613		)
614	);
615
616	for dep in &pkg.dependencies {
617		let dep_name = sanitize_crate_name(&dep.name);
618		let version = metadata
619			.packages
620			.iter()
621			.filter(|pkg| pkg.name.as_str() == dep.name)
622			.map(|pkg| &pkg.version)
623			.find(|pkgver| dep.req.matches(pkgver));
624		let rename = dep.rename.as_ref().unwrap_or(&dep_name);
625
626		if let Some(version) = version {
627			if deps
628				.get(rename)
629				.map(|dep| dep.version < *version)
630				.unwrap_or(true)
631			{
632				deps.insert(
633					rename.to_owned(),
634					Dependency::new(dep_name, dep.req.clone(), version.to_owned())
635				);
636			}
637		} else {
638			diagnostic::NoDependencyVersion::new(dep.name.clone()).print(printer);
639		}
640	}
641
642	deps
643}
644
645struct ScopeEditor<'a, 'w, W: io::Write> {
646	scope: &'a mut Scope,
647	crate_name: &'a str,
648	printer: &'a mut DiagnosticPrinter<'w, W>,
649	glob_warn: Option<diagnostic::GlobImport>
650}
651
652impl<W: io::Write> Drop for ScopeEditor<'_, '_, W> {
653	fn drop(&mut self) {
654		if let Some(warning) = self.glob_warn.take() {
655			self.printer.print(warning);
656		}
657	}
658}
659
660impl<'a, 'w, W: io::Write> ScopeEditor<'a, 'w, W> {
661	fn new(
662		scope: &'a mut Scope,
663		crate_name: &'a str,
664		printer: &'a mut DiagnosticPrinter<'w, W>
665	) -> Self {
666		Self {
667			scope,
668			crate_name,
669			printer,
670			glob_warn: None
671		}
672	}
673
674	fn add_privmod(&mut self, ident: &Ident) {
675		self.scope.privmods.insert(ident.to_string());
676	}
677
678	fn insert(&mut self, ident: &Ident, ty: LinkType) {
679		let path = format!("::{}::{ident}", self.crate_name);
680		self.scope.insert(ident.to_string(), ty, path);
681	}
682
683	fn insert_fun(&mut self, ident: &Ident) {
684		let path = format!("::{}::{ident}", self.crate_name);
685		self.scope
686			.insert(ident.to_string(), LinkType::Function, &path);
687		self.scope
688			.insert(format!("{ident}()"), LinkType::Function, path);
689	}
690
691	fn insert_macro(&mut self, ident: &Ident) {
692		let path = format!("::{}::{ident}", self.crate_name);
693		self.scope.insert(ident.to_string(), LinkType::Macro, &path);
694		self.scope
695			.insert(format!("{ident}!"), LinkType::Macro, path);
696	}
697
698	fn insert_use_tree(&mut self, vis: &Visibility, tree: &UseTree) {
699		self.insert_use_tree_impl(vis, String::new(), tree)
700	}
701
702	fn insert_use_tree_impl(&mut self, vis: &Visibility, prefix: String, tree: &UseTree) {
703		match tree {
704			UseTree::Path(path) if prefix.is_empty() && path.ident == "self" => {
705				self.insert_use_tree_impl(vis, prefix, &path.tree)
706			},
707			UseTree::Path(path) => self.insert_use_tree_impl(
708				vis,
709				format!("{prefix}{}::", path.ident),
710				&path.tree
711			),
712			UseTree::Name(name) => {
713				// skip `pub use dependency;` style uses; they don't add any unknown
714				// elements to the scope
715				if !prefix.is_empty() {
716					self.insert_use_item(vis, &prefix, &name.ident, &name.ident);
717				}
718			},
719			UseTree::Rename(name) => {
720				self.insert_use_item(vis, &prefix, &name.rename, &name.ident);
721			},
722			UseTree::Glob(glob) => {
723				let span = glob.star_token.spans[0];
724				if let Some(warning) = &mut self.glob_warn {
725					warning.add_span(span);
726				} else {
727					self.glob_warn =
728						Some(diagnostic::GlobImport::new(self.printer.code, span));
729				}
730			},
731			UseTree::Group(group) => {
732				for tree in &group.items {
733					self.insert_use_tree_impl(vis, prefix.clone(), tree);
734				}
735			},
736		};
737	}
738
739	fn insert_use_item(
740		&mut self,
741		vis: &Visibility,
742		prefix: &str,
743		rename: &Ident,
744		ident: &Ident
745	) {
746		if matches!(vis, Visibility::Public(_)) {
747			self.insert(rename, LinkType::PubUse);
748		}
749		self.scope.insert(
750			rename.to_string(),
751			LinkType::Use,
752			format!("{prefix}{ident}")
753		);
754	}
755}
756
757fn is_public(vis: &Visibility) -> bool {
758	matches!(vis, Visibility::Public(_))
759}
760
761fn is_exported(mac: &ItemMacro) -> bool {
762	mac.attrs
763		.iter()
764		.any(|attr| attr.path().is_ident("macro_export"))
765}
766
767fn read_scope_from_file<W: io::Write>(
768	pkg: &Package,
769	file: &syn::File,
770	printer: &mut DiagnosticPrinter<'_, W>
771) -> miette::Result<Scope> {
772	let crate_name = sanitize_crate_name(&pkg.name);
773	let mut scope = Scope::prelude(pkg.edition);
774	let mut editor = ScopeEditor::new(&mut scope, &crate_name, printer);
775	let mut errs = Vec::new();
776
777	'items: for i in &file.items {
778		match i {
779			Item::Const(i) if is_public(&i.vis) => {
780				editor.insert(&i.ident, LinkType::Const)
781			},
782			Item::Enum(i) if is_public(&i.vis) => editor.insert(&i.ident, LinkType::Enum),
783			Item::ExternCrate(i)
784				if is_public(&i.vis) && i.ident != "self" && i.rename.is_some() =>
785			{
786				editor.scope.insert(
787					i.rename.as_ref().unwrap().1.to_string(),
788					LinkType::ExternCrate,
789					format!("::{}", i.ident)
790				);
791			},
792			Item::Fn(i) if is_public(&i.vis) => {
793				for a in &i.attrs {
794					let ap = match &a.meta {
795						Meta::Path(p) => p,
796						Meta::List(l) => &l.path,
797						Meta::NameValue(nv) => &nv.path
798					};
799					let ai = match ap.get_ident() {
800						Some(ai) => ai,
801						None => continue
802					};
803					if ai == "proc_macro" {
804						editor.insert_macro(&i.sig.ident);
805					} else if ai == "proc_macro_attribute" {
806						editor.insert(&i.sig.ident, LinkType::Attr);
807					} else if ai == "proc_macro_derive" {
808						let Meta::List(l) = &a.meta else {
809							errs.push(syn::Error::new(
810								a.meta.span(),
811								"Expected proc_macro_derive to specify a name for the derive macro"
812							));
813							continue 'items;
814						};
815						let Some(derive_ident) =
816							l.tokens.clone().into_iter().next().and_then(|token| {
817								match token {
818									TokenTree::Ident(ident) => Some(ident),
819									_ => None
820								}
821							})
822						else {
823							errs.push(syn::Error::new(
824								l.tokens.span(),
825								"Expected proc_macro_derive to specify a name for the derive macro"
826							));
827							continue 'items;
828						};
829						editor.insert(&derive_ident, LinkType::Derive);
830					} else {
831						continue;
832					}
833					// if we didn't find a proc-macro attribute, we would've continued
834					// the inner loop before, so in this case, we continue the outer loop
835					continue 'items;
836				}
837				editor.insert_fun(&i.sig.ident)
838			},
839			Item::Macro(i) if is_exported(i) && i.ident.is_some() => {
840				editor.insert_macro(i.ident.as_ref().unwrap())
841			},
842			Item::Mod(i) if is_public(&i.vis) => editor.insert(&i.ident, LinkType::Mod),
843			Item::Mod(i) => editor.add_privmod(&i.ident),
844			Item::Static(i) if is_public(&i.vis) => {
845				editor.insert(&i.ident, LinkType::Static)
846			},
847			Item::Struct(i) if is_public(&i.vis) => {
848				editor.insert(&i.ident, LinkType::Struct)
849			},
850			Item::Trait(i) if is_public(&i.vis) => {
851				editor.insert(&i.ident, LinkType::Trait)
852			},
853			Item::TraitAlias(i) if is_public(&i.vis) => {
854				editor.insert(&i.ident, LinkType::TraitAlias)
855			},
856			Item::Type(i) if is_public(&i.vis) => editor.insert(&i.ident, LinkType::Type),
857			Item::Union(i) if is_public(&i.vis) => {
858				editor.insert(&i.ident, LinkType::Union)
859			},
860			Item::Use(i) if !is_prelude_import(i) => {
861				editor.insert_use_tree(&i.vis, &i.tree)
862			},
863			_ => {}
864		};
865	}
866	drop(editor);
867
868	// remove privmod imports from scope
869	for values in &mut scope.scope.values_mut() {
870		let mut i = 0;
871		while i < values.len() {
872			if values[i].0 == LinkType::Use {
873				let path = &values[i].1;
874				if (!path.starts_with("::")
875					|| path.starts_with(&format!("::{crate_name}::")))
876					&& Some(path.split("::").collect::<Vec<_>>())
877						.map(|segments| {
878							segments.len() > 1 && scope.privmods.contains(segments[0])
879						})
880						.unwrap()
881				{
882					values.remove(i);
883					continue;
884				}
885			}
886
887			i += 1;
888		}
889	}
890
891	if !errs.is_empty() {
892		let err = diagnostic::SyntaxError::new_rust_multi(printer.code, errs);
893		Err(err.into())
894	} else {
895		Ok(scope)
896	}
897}
898
899fn is_prelude_import(item_use: &ItemUse) -> bool {
900	match &item_use.tree {
901		UseTree::Path(UsePath { ident, tree, .. })
902			if ident == "std" || ident == "core" =>
903		{
904			match tree.as_ref() {
905				UseTree::Path(UsePath { ident, .. }) => ident == "prelude",
906				_ => false
907			}
908		},
909		_ => false
910	}
911}