1mod cache;
44mod context;
45mod description;
46mod entry;
47mod error;
48mod fs;
49mod info;
50mod kind;
51mod log;
52mod map;
53mod options;
54mod parse;
55mod plugin;
56mod resolve;
57mod resource;
58mod state;
59mod tsconfig;
60mod tsconfig_path;
61
62pub use cache::Cache;
63use context::Context;
64pub use description::DescriptionData;
65pub use error::Error;
66use info::Info;
67use kind::PathKind;
68use log::{color, depth};
69use options::EnforceExtension::{Auto, Disabled, Enabled};
70pub use options::{AliasMap, EnforceExtension, Options};
71use plugin::{
72 AliasPlugin, BrowserFieldPlugin, ImportsFieldPlugin, ParsePlugin, Plugin, PreferRelativePlugin,
73 SymlinkPlugin,
74};
75pub use resource::Resource;
76use state::State;
77
78#[derive(Debug)]
79pub struct Resolver {
80 pub options: Options,
81 pub(crate) cache: std::sync::Arc<Cache>,
82}
83
84#[derive(Debug, Clone)]
85pub enum ResolveResult<T: Clone> {
86 Resource(T),
87 Ignored,
88}
89
90pub type RResult<T> = Result<T, Error>;
91
92impl Resolver {
93 #[must_use]
94 pub fn new(options: Options) -> Self {
95 log::enable_by_env();
96
97 let cache = if let Some(external_cache) = options.external_cache.as_ref() {
98 external_cache.clone()
99 } else {
100 std::sync::Arc::new(Cache::default())
101 };
102
103 let enforce_extension = match options.enforce_extension {
104 Auto => {
105 if options.extensions.iter().any(|ext| ext.is_empty()) {
106 Enabled
107 } else {
108 Disabled
109 }
110 }
111 _ => options.enforce_extension,
112 };
113
114 let options = Options {
115 enforce_extension,
116 ..options
117 };
118 Self { options, cache }
119 }
120
121 pub fn resolve(
122 &self,
123 path: &std::path::Path,
124 request: &str,
125 ) -> RResult<ResolveResult<Resource>> {
126 tracing::debug!(
127 "{:-^30}\nTry to resolve '{}' in '{}'",
128 color::green(&"[RESOLVER]"),
129 color::cyan(&request),
130 color::cyan(&path.display().to_string())
131 );
132 let parsed = Self::parse(request);
134 let info = Info::new(path, parsed);
135 let mut context = Context::new(
136 self.options.fully_specified,
137 self.options.resolve_to_context,
138 );
139 let result = if let Some(tsconfig_location) = self.options.tsconfig.as_ref() {
140 self._resolve_with_tsconfig(info, tsconfig_location, &mut context)
141 } else {
142 self._resolve(info, &mut context)
143 };
144
145 let result = result.map_failed(|info| {
146 type FallbackPlugin<'a> = AliasPlugin<'a>;
147 FallbackPlugin::new(&self.options.fallback).apply(self, info, &mut context)
148 });
149 let result = result.map_success(|info| SymlinkPlugin::apply(self, info, &mut context));
150
151 match result {
163 State::Success(ResolveResult::Ignored) => Ok(ResolveResult::Ignored),
164 State::Success(ResolveResult::Resource(info)) => {
165 let resource = Resource::new(info, self);
166 Ok(ResolveResult::Resource(resource))
167 }
168 State::Error(err) => Err(err),
169 State::Resolving(_) | State::Failed(_) => Err(Error::ResolveFailedTag),
170 }
171 }
172
173 fn _resolve(&self, info: Info, context: &mut Context) -> State {
174 tracing::debug!(
175 "Resolving '{request}' in '{path}'",
176 request = color::cyan(&info.request().target()),
177 path = color::cyan(&info.normalized_path().as_ref().display())
178 );
179
180 context.depth.increase();
181 if context.depth.cmp(127).is_ge() {
182 return State::Error(Error::Overflow);
183 }
184
185 let state = ParsePlugin::apply(self, info, context)
186 .then(|info| AliasPlugin::new(&self.options.alias).apply(self, info, context))
187 .then(|info| PreferRelativePlugin::apply(self, info, context))
188 .then(|info| {
189 let request = info.to_resolved_path();
190 let entry = self.load_entry(&request);
191 let pkg_info = match entry.pkg_info(self) {
192 Ok(pkg_info) => pkg_info,
193 Err(error) => return State::Error(error),
194 };
195 if let Some(pkg_info) = pkg_info {
196 ImportsFieldPlugin::new(pkg_info)
197 .apply(self, info, context)
198 .then(|info| {
199 BrowserFieldPlugin::new(pkg_info, false).apply(self, info, context)
200 })
201 } else {
202 State::Resolving(info)
203 }
204 })
205 .then(|info| {
206 if matches!(
207 info.request().kind(),
208 PathKind::AbsolutePosix | PathKind::AbsoluteWin | PathKind::Relative
209 ) {
210 self.resolve_as_context(info, context)
211 .then(|info| self.resolve_as_fully_specified(info, context))
212 .then(|info| self.resolve_as_file(info, context))
213 .then(|info| self.resolve_as_dir(info, context))
214 } else {
215 self.resolve_as_modules(info, context)
216 }
217 });
218
219 context.depth.decrease();
220 state
221 }
222}
223
224#[cfg(debug_assertions)]
225pub mod test_helper {
226 #[must_use]
227 pub fn p(paths: Vec<&str>) -> std::path::PathBuf {
228 paths.iter().fold(
229 std::env::current_dir()
230 .unwrap()
231 .join("tests")
232 .join("fixtures"),
233 |acc, path| acc.join(path),
234 )
235 }
236
237 #[must_use]
238 pub fn vec_to_set(vec: Vec<&str>) -> std::collections::HashSet<String> {
239 std::collections::HashSet::from_iter(vec.into_iter().map(|s| s.to_string()))
240 }
241}