unrspack_resolver/
tsconfig.rs1use std::{
2 hash::BuildHasherDefault,
3 path::{Path, PathBuf},
4 sync::Arc,
5};
6
7use indexmap::IndexMap;
8use rustc_hash::FxHasher;
9
10use crate::{TsconfigReferences, path::PathUtil};
11
12pub type CompilerOptionsPathsMap = IndexMap<String, Vec<String>, BuildHasherDefault<FxHasher>>;
13
14#[allow(clippy::missing_errors_doc)] pub trait TsConfig: Sized {
21 type Co: CompilerOptions;
22
23 fn root(&self) -> bool;
26
27 #[must_use]
31 fn path(&self) -> &Path;
32
33 #[must_use]
39 fn directory(&self) -> &Path;
40
41 #[must_use]
43 fn compiler_options(&self) -> &Self::Co;
44
45 #[must_use]
48 fn compiler_options_mut(&mut self) -> &mut Self::Co;
49
50 #[must_use]
52 fn extends(&self) -> impl Iterator<Item = &str>;
53
54 fn load_references(&mut self, references: &TsconfigReferences) -> bool;
58
59 #[must_use]
61 fn references(&self) -> impl Iterator<Item = &impl ProjectReference<Tc = Self>>;
62
63 #[must_use]
65 fn references_mut(&mut self) -> impl Iterator<Item = &mut impl ProjectReference<Tc = Self>>;
66
67 #[must_use]
73 fn base_path(&self) -> &Path {
74 self.compiler_options().base_url().unwrap_or_else(|| self.directory())
75 }
76
77 fn expand_template_variables(&mut self) {
79 if self.root() {
80 let dir = self.directory().to_path_buf();
81 if let Some(paths) = &mut self.compiler_options_mut().paths_mut() {
83 for paths in paths.values_mut() {
84 for path in paths {
85 Self::substitute_template_variable(&dir, path);
86 }
87 }
88 }
89 }
90 }
91
92 fn extend_tsconfig(&mut self, tsconfig: &Self) {
94 let compiler_options = self.compiler_options_mut();
95 if compiler_options.paths().is_none() {
96 compiler_options.set_paths_base(compiler_options.base_url().map_or_else(
97 || tsconfig.compiler_options().paths_base().to_path_buf(),
98 Path::to_path_buf,
99 ));
100 compiler_options.set_paths(tsconfig.compiler_options().paths().cloned());
101 }
102 if compiler_options.base_url().is_none() {
103 if let Some(base_url) = tsconfig.compiler_options().base_url() {
104 compiler_options.set_base_url(base_url.to_path_buf());
105 }
106 }
107
108 if compiler_options.experimental_decorators().is_none() {
109 if let Some(experimental_decorators) =
110 tsconfig.compiler_options().experimental_decorators()
111 {
112 compiler_options.set_experimental_decorators(*experimental_decorators);
113 }
114 }
115
116 if compiler_options.jsx().is_none() {
117 if let Some(jsx) = tsconfig.compiler_options().jsx() {
118 compiler_options.set_jsx(jsx.to_string());
119 }
120 }
121
122 if compiler_options.jsx_factory().is_none() {
123 if let Some(jsx_factory) = tsconfig.compiler_options().jsx_factory() {
124 compiler_options.set_jsx_factory(jsx_factory.to_string());
125 }
126 }
127
128 if compiler_options.jsx_fragment_factory().is_none() {
129 if let Some(jsx_fragment_factory) = tsconfig.compiler_options().jsx_fragment_factory() {
130 compiler_options.set_jsx_fragment_factory(jsx_fragment_factory.to_string());
131 }
132 }
133
134 if compiler_options.jsx_import_source().is_none() {
135 if let Some(jsx_import_source) = tsconfig.compiler_options().jsx_import_source() {
136 compiler_options.set_jsx_import_source(jsx_import_source.to_string());
137 }
138 }
139 }
140
141 #[must_use]
146 fn resolve(&self, path: &Path, specifier: &str) -> Vec<PathBuf> {
147 let paths = self.resolve_path_alias(specifier);
148 for tsconfig in self.references().filter_map(ProjectReference::tsconfig) {
149 if path.starts_with(tsconfig.base_path()) {
150 return [tsconfig.resolve_path_alias(specifier), paths].concat();
151 }
152 }
153 paths
154 }
155
156 #[must_use]
163 fn resolve_path_alias(&self, specifier: &str) -> Vec<PathBuf> {
164 if specifier.starts_with(['/', '.']) {
165 return Vec::new();
166 }
167
168 let compiler_options = self.compiler_options();
169 let base_url_iter = compiler_options
170 .base_url()
171 .map_or_else(Vec::new, |base_url| vec![base_url.normalize_with(specifier)]);
172
173 let Some(paths_map) = compiler_options.paths() else {
174 return base_url_iter;
175 };
176
177 let paths = paths_map.get(specifier).map_or_else(
178 || {
179 let mut longest_prefix_length = 0;
180 let mut longest_suffix_length = 0;
181 let mut best_key: Option<&String> = None;
182
183 for key in paths_map.keys() {
184 if let Some((prefix, suffix)) = key.split_once('*') {
185 if (best_key.is_none() || prefix.len() > longest_prefix_length)
186 && specifier.starts_with(prefix)
187 && specifier.ends_with(suffix)
188 {
189 longest_prefix_length = prefix.len();
190 longest_suffix_length = suffix.len();
191 best_key.replace(key);
192 }
193 }
194 }
195
196 best_key.and_then(|key| paths_map.get(key)).map_or_else(Vec::new, |paths| {
197 paths
198 .iter()
199 .map(|path| {
200 path.replace(
201 '*',
202 &specifier[longest_prefix_length
203 ..specifier.len() - longest_suffix_length],
204 )
205 })
206 .collect::<Vec<_>>()
207 })
208 },
209 Clone::clone,
210 );
211
212 paths
213 .into_iter()
214 .map(|p| compiler_options.paths_base().normalize_with(p))
215 .chain(base_url_iter)
216 .collect()
217 }
218
219 fn substitute_template_variable(directory: &Path, path: &mut String) {
227 const TEMPLATE_VARIABLE: &str = "${configDir}/";
228 if let Some(stripped_path) = path.strip_prefix(TEMPLATE_VARIABLE) {
229 *path = directory.join(stripped_path).to_string_lossy().to_string();
230 }
231 }
232}
233
234pub trait CompilerOptions {
238 #[must_use]
240 fn base_url(&self) -> Option<&Path>;
241
242 fn set_base_url(&mut self, base_url: PathBuf);
244
245 #[must_use]
247 fn paths(&self) -> Option<&CompilerOptionsPathsMap>;
248
249 #[must_use]
251 fn paths_mut(&mut self) -> Option<&mut CompilerOptionsPathsMap>;
252
253 fn set_paths(&mut self, paths: Option<CompilerOptionsPathsMap>);
255
256 #[must_use]
258 fn paths_base(&self) -> &Path;
259
260 fn set_paths_base(&mut self, paths_base: PathBuf);
262
263 fn experimental_decorators(&self) -> Option<&bool> {
265 None
266 }
267
268 fn set_experimental_decorators(&mut self, _experimental_decorators: bool) {}
270
271 fn jsx(&self) -> Option<&str> {
273 None
274 }
275
276 fn set_jsx(&mut self, _jsx: String) {}
278
279 fn jsx_factory(&self) -> Option<&str> {
281 None
282 }
283
284 fn set_jsx_factory(&mut self, _jsx_factory: String) {}
286
287 fn jsx_fragment_factory(&self) -> Option<&str> {
289 None
290 }
291
292 fn set_jsx_fragment_factory(&mut self, _jsx_fragment_factory: String) {}
294
295 fn jsx_import_source(&self) -> Option<&str> {
297 None
298 }
299
300 fn set_jsx_import_source(&mut self, _jsx_import_source: String) {}
302}
303
304pub trait ProjectReference {
308 type Tc: TsConfig;
309
310 #[must_use]
313 fn path(&self) -> &Path;
314
315 #[must_use]
317 fn tsconfig(&self) -> Option<Arc<Self::Tc>>;
318
319 fn set_tsconfig(&mut self, tsconfig: Arc<Self::Tc>);
321}