fluidattacks_blends_domain/stack/
imports.rs1use alloc::collections::BTreeMap;
2use alloc::string::String;
3use alloc::vec::Vec;
4
5use crate::stack::module_path::normalize_module_path;
6use crate::stack::reexports::reexport_sites;
7use crate::stack::requires::require_sites;
8use crate::stack::ImportBinding;
9use crate::syntax::{SyntaxGraph, SyntaxNode};
10use crate::{Language, NodeId};
11
12#[derive(Clone, Copy, PartialEq, Eq, Debug)]
14#[non_exhaustive]
15pub enum BindingReach {
16 Scope,
18 Importers,
20}
21
22#[derive(Clone, PartialEq, Eq, Debug)]
24#[non_exhaustive]
25pub struct ImportSite {
26 pub node: NodeId,
28 pub scope: NodeId,
30 pub symbol: String,
32 pub binding: ImportBinding,
34 pub reach: BindingReach,
36}
37
38#[must_use]
42pub fn import_sites(syntax: &SyntaxGraph, language: Language) -> Vec<ImportSite> {
43 let read_binding = match language {
44 Language::Python => python_binding,
45 Language::JavaScript | Language::TypeScript => script_binding,
46 _ => return Vec::new(),
47 };
48 let bound_names: BTreeMap<NodeId, (NodeId, &str)> = syntax
49 .symbol_index
50 .iter()
51 .flat_map(|(symbol, scopes)| {
52 scopes.iter().flat_map(move |(scope, nodes)| {
53 nodes
54 .iter()
55 .map(move |node| (*node, (*scope, symbol.as_str())))
56 })
57 })
58 .collect();
59 let mut sites: Vec<ImportSite> = bound_names
60 .iter()
61 .filter_map(|(node, (scope, symbol))| {
62 let binding = read_binding(syntax.nodes.get(node)?, symbol)?;
63 Some(ImportSite {
64 node: *node,
65 scope: *scope,
66 symbol: String::from(*symbol),
67 binding,
68 reach: BindingReach::Scope,
69 })
70 })
71 .collect();
72 if matches!(language, Language::JavaScript | Language::TypeScript) {
73 sites.extend(require_sites(syntax, &bound_names));
74 sites.extend(reexport_sites(syntax));
75 sites.sort_by_key(|site| site.node);
76 }
77 sites
78}
79
80fn python_binding(node: &SyntaxNode, symbol: &str) -> Option<ImportBinding> {
81 let SyntaxNode::Import {
82 expression: Some(expression),
83 alias,
84 module_name,
85 ..
86 } = node
87 else {
88 return None;
89 };
90 let Some(module_name) = module_name else {
91 let module = if alias.is_some() {
92 expression.as_str()
93 } else {
94 symbol
95 };
96 return Some(module_binding(module, 0));
97 };
98 let module = module_name.trim_start_matches('.');
99 let imported = expression.rsplit('.').next().unwrap_or(expression);
100 Some(symbol_binding(
101 module,
102 level_of(module_name.len().saturating_sub(module.len())),
103 imported,
104 symbol,
105 ))
106}
107
108fn script_binding(node: &SyntaxNode, symbol: &str) -> Option<ImportBinding> {
109 match node {
110 SyntaxNode::Import {
111 alias: Some(_),
112 module_name: Some(specifier),
113 import_type,
114 ..
115 } => {
116 let (module, relative_level) = script_module(specifier);
117 match import_type.as_deref() {
118 None => Some(symbol_binding(&module, relative_level, "default", symbol)),
119 Some("namespace_import" | "import_require_clause") => {
120 Some(module_binding(&module, relative_level))
121 }
122 Some(_) => None,
123 }
124 }
125 SyntaxNode::ModuleImport {
126 expression,
127 module_name: Some(specifier),
128 ..
129 } => {
130 let imported = expression
131 .strip_prefix(specifier.as_str())?
132 .strip_prefix('.')?;
133 let (module, relative_level) = script_module(specifier);
134 Some(symbol_binding(&module, relative_level, imported, symbol))
135 }
136 _ => None,
137 }
138}
139
140pub(super) fn script_module(specifier: &str) -> (String, u32) {
141 let mut segments = specifier.split('/').peekable();
142 let mut relative = false;
143 let mut parents = 0_usize;
144 while let Some(segment) = segments.next_if(|segment| matches!(*segment, "." | "..")) {
145 relative = true;
146 parents = parents.saturating_add(usize::from(segment == ".."));
147 }
148 let module = normalize_module_path(&segments.collect::<Vec<_>>().join("/"));
149 let relative_level = if relative {
150 level_of(parents.saturating_add(1))
151 } else {
152 0
153 };
154 (module, relative_level)
155}
156
157pub(super) fn module_binding(module: &str, relative_level: u32) -> ImportBinding {
158 ImportBinding {
159 module_stem: String::from(module),
160 source_symbol: None,
161 relative_level,
162 binds_module: true,
163 }
164}
165
166pub(super) fn symbol_binding(
167 module: &str,
168 relative_level: u32,
169 imported: &str,
170 symbol: &str,
171) -> ImportBinding {
172 ImportBinding {
173 module_stem: String::from(module),
174 source_symbol: (imported != symbol).then(|| String::from(imported)),
175 relative_level,
176 binds_module: false,
177 }
178}
179
180fn level_of(depth: usize) -> u32 {
181 u32::try_from(depth).unwrap_or(u32::MAX)
182}