Skip to main content

harn_modules/
source_reads.rs

1//! Where module sources come from: disk, the embedded stdlib, or a caller's
2//! unsaved buffers, plus the count of disk reads that editor caching is
3//! measured against.
4
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
7
8use super::{
9    build_inner, normalize_path, stdlib, stdlib_module_name, PackageContext, ParsedSourceRetention,
10};
11use crate::ModuleGraph;
12
13/// Return the source for a resolved module path.
14///
15/// Real paths are read from disk. `<std>/<module>` virtual paths are backed by
16/// the embedded stdlib source table, so callers can parse resolved stdlib
17/// modules without knowing about the stdlib mirror layout.
18pub fn read_module_source(path: &Path) -> Option<String> {
19    read_module_source_checked(path).ok().flatten()
20}
21
22/// Like [`read_module_source`], but a module the process may not read is an
23/// error rather than an absent module, so importers can say why it is empty.
24pub(crate) fn read_module_source_checked(path: &Path) -> Result<Option<String>, String> {
25    if let Some(stdlib_module) = stdlib_module_name(path) {
26        return Ok(stdlib::get_stdlib_source(stdlib_module).map(ToString::to_string));
27    }
28    MODULE_DISK_READS.with(|reads| reads.set(reads.get() + 1));
29    match std::fs::read_to_string(path) {
30        Ok(source) => Ok(Some(source)),
31        // Workspace confinement (Landlock, Seatbelt) refuses reads outside
32        // its granted roots with EACCES or EPERM, both reported as this kind.
33        Err(error) if error.kind() == std::io::ErrorKind::PermissionDenied => Err(format!(
34            "read denied: {} (outside confined roots)",
35            path.display()
36        )),
37        Err(_) => Ok(None),
38    }
39}
40
41thread_local! {
42    static MODULE_DISK_READS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
43}
44
45/// How many module sources this thread has read from disk while building
46/// module graphs. Embedded stdlib modules and caller-supplied sources are not
47/// disk reads.
48pub fn module_source_disk_reads() -> usize {
49    MODULE_DISK_READS.with(std::cell::Cell::get)
50}
51
52/// Build a module graph using caller-owned source for one root file.
53///
54/// Imported modules still resolve from their normal filesystem or embedded
55/// stdlib locations. This keeps editor diagnostics aligned with unsaved root
56/// buffers without creating a second module resolver.
57pub fn build_with_source(file: &Path, source: &str) -> ModuleGraph {
58    let file = normalize_path(file);
59    let source_overrides = HashMap::from([(file.clone(), source.to_string())]);
60    build_inner(
61        &[file],
62        ParsedSourceRetention::None,
63        Some(&source_overrides),
64        PackageContext::Project,
65        None,
66    )
67    .graph
68}
69
70/// Build a module graph for one root file using caller-owned source for any
71/// module in `sources`, such as an editor's unsaved buffers. Modules without an
72/// entry resolve from their filesystem or embedded stdlib locations.
73pub fn build_with_sources(file: &Path, sources: &HashMap<PathBuf, String>) -> ModuleGraph {
74    let file = normalize_path(file);
75    let source_overrides: HashMap<PathBuf, String> = sources
76        .iter()
77        .map(|(path, source)| (normalize_path(path), source.clone()))
78        .collect();
79    build_inner(
80        &[file],
81        ParsedSourceRetention::None,
82        Some(&source_overrides),
83        PackageContext::Project,
84        None,
85    )
86    .graph
87}