Skip to main content

harn_modules/
namespace_imports.rs

1//! Namespace import (`import * as alias from "..."`) graph APIs.
2
3use std::collections::BTreeMap;
4use std::path::{Path, PathBuf};
5
6use crate::package_imports::resolve_import_path_with_snapshots;
7use crate::package_snapshot::PackageSnapshot;
8use crate::{decl_site, normalize_path, DefKind, DefSite, ImportRef, ModuleGraph, ModuleInfo};
9
10/// One `import * as alias from "..."` binding visible from a consumer file.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct NamespaceImportInfo {
13    pub alias: String,
14    pub raw_path: String,
15    pub resolved_path: Option<PathBuf>,
16    /// Public export names from the target module (empty when unresolved).
17    pub member_names: Vec<String>,
18    /// Declaration kind for each exported member name.
19    pub member_kinds: BTreeMap<String, DefKind>,
20}
21
22impl ModuleGraph {
23    /// Namespace imports (`import * as alias from "..."`) declared by `file`.
24    ///
25    /// Returns `None` when any namespace import path is unresolved so callers
26    /// can fall back to conservative checking. Member names/kinds come from
27    /// the target module's public export surface (`exports` + `DefKind`).
28    pub fn namespace_imports_for_file(&self, file: &Path) -> Option<Vec<NamespaceImportInfo>> {
29        let file = normalize_path(file);
30        let module = self.modules.get(&file)?;
31        if module.has_unresolved_namespace_import {
32            return None;
33        }
34
35        let mut out = Vec::new();
36        for import in &module.imports {
37            let Some(alias) = &import.namespace_alias else {
38                continue;
39            };
40            let (member_names, member_kinds) = match &import.path {
41                Some(import_path) => {
42                    let imported = self
43                        .modules
44                        .get(import_path)
45                        .or_else(|| self.modules.get(&normalize_path(import_path)))?;
46                    if imported.load_error.is_some() {
47                        return None;
48                    }
49                    let mut names: Vec<String> = imported.exports.iter().cloned().collect();
50                    names.sort();
51                    let mut kinds = BTreeMap::new();
52                    for name in &names {
53                        if let Some(kind) = self.exported_kind(import_path, name) {
54                            kinds.insert(name.clone(), kind);
55                        }
56                    }
57                    (names, kinds)
58                }
59                None => (Vec::new(), BTreeMap::new()),
60            };
61            out.push(NamespaceImportInfo {
62                alias: alias.clone(),
63                raw_path: import.raw_path.clone(),
64                resolved_path: import.path.as_ref().map(|path| normalize_path(path)),
65                member_names,
66                member_kinds,
67            });
68        }
69        Some(out)
70    }
71
72    /// Look up a member under a namespace import alias visible from `file`.
73    ///
74    /// Returns the target module's definition site for `member`, or `None`
75    /// when the alias is not a namespace import / the member is not exported.
76    pub fn namespace_member_lookup(
77        &self,
78        file: &Path,
79        alias: &str,
80        member: &str,
81    ) -> Option<DefSite> {
82        let file = normalize_path(file);
83        let module = self.modules.get(&file)?;
84        let target = module
85            .imports
86            .iter()
87            .find(|import| import.namespace_alias.as_deref() == Some(alias))
88            .and_then(|import| import.path.as_ref())
89            .or_else(|| module.namespace_re_exports.get(alias))?;
90        self.exported_kind(target, member)?;
91        self.export_definition_of(target, member)
92            .or_else(|| self.definition_of(target, member))
93    }
94}
95
96/// Record a `NamespaceImport` into `module` during graph construction.
97pub(crate) fn record_namespace_import(
98    module: &mut ModuleInfo,
99    file: &Path,
100    span: harn_lexer::Span,
101    alias: &str,
102    path: &str,
103    is_pub: bool,
104    package_snapshots: &[PackageSnapshot],
105) {
106    let import_path = resolve_import_path_with_snapshots(file, path, package_snapshots);
107    if import_path.is_none() {
108        module.has_unresolved_namespace_import = true;
109    }
110    // Bind the alias locally as a Variable. `pub import * as alias`
111    // re-exports the namespace object itself — never flatten target
112    // members into this module's public surface (contrast
113    // `wildcard_re_export_paths`).
114    module.declarations.insert(
115        alias.to_string(),
116        decl_site(file, span, alias, DefKind::Variable),
117    );
118    if is_pub {
119        module.own_exports.insert(alias.to_string());
120        module.exports.insert(alias.to_string());
121        if let Some(resolved) = &import_path {
122            module
123                .namespace_re_exports
124                .insert(alias.to_string(), normalize_path(resolved));
125        }
126    }
127    module.imports.push(ImportRef {
128        raw_path: path.to_string(),
129        path: import_path,
130        selective_names: None,
131        namespace_alias: Some(alias.to_string()),
132        import_span: span,
133    });
134}