1use std::{
2 fmt::{self, Debug, Display},
3 io,
4 path::PathBuf,
5 sync::Arc,
6};
7
8use thiserror::Error;
9
10#[derive(Debug, Clone, PartialEq, Error)]
14#[non_exhaustive]
15pub enum ResolveError {
16 #[error("Path is ignored {0}")]
28 Ignored(PathBuf),
29
30 #[error("Cannot find module '{0}'")]
32 NotFound(String),
33
34 #[error("Cannot find module '{0}' for matched aliased key '{1}'")]
36 MatchedAliasNotFound(String, String),
37
38 #[error("Tsconfig not found {0}")]
40 TsconfigNotFound(PathBuf),
41
42 #[error("Tsconfig's project reference path points to this tsconfig {0}")]
44 TsconfigSelfReference(PathBuf),
45
46 #[error("Tsconfig extends configs circularly: {0}")]
48 TsconfigCircularExtend(CircularPathBufs),
49
50 #[error("Failed to load tsconfig {path:?}: {source}")]
55 TsconfigLoadFailed { path: PathBuf, source: Box<Self> },
56
57 #[error("{0}")]
58 IOError(IOError),
59
60 #[error("Path {0:?} contains unsupported construct.")]
63 PathNotSupported(PathBuf),
64
65 #[error("Builtin module {resolved}")]
72 Builtin { resolved: String, is_runtime_module: bool },
73
74 #[error("Cannot resolve '{0}' for extension aliases '{1}' in '{2}'")]
78 ExtensionAlias(
79 String,
80 String,
81 PathBuf,
82 ),
83
84 #[error("{0}")]
86 Specifier(SpecifierError),
87
88 #[error("{0:?}")]
90 Json(JSONError),
91
92 #[error(r#"Invalid module "{0}" specifier is not a valid subpath for the "exports" resolution of {1}"#)]
93 InvalidModuleSpecifier(String, PathBuf),
94
95 #[error(r#"Invalid "exports" target "{0}" defined for '{1}' in the package config {2}"#)]
96 InvalidPackageTarget(String, String, PathBuf),
97
98 #[error(r#""{subpath}" is not exported under {conditions} from package {package_path} (see exports field in {package_json_path})"#)]
99 PackagePathNotExported {
100 subpath: String,
101 package_path: PathBuf,
102 package_json_path: PathBuf,
103 conditions: ConditionNames,
104 },
105
106 #[error(r#"Invalid package config "{0}", "exports" cannot contain some keys starting with '.' and some not. The exports object must either be an object of package subpath keys or an object of main entry condition name keys only."#)]
107 InvalidPackageConfig(PathBuf),
108
109 #[error(r#"Default condition should be last one in "{0}""#)]
110 InvalidPackageConfigDefault(PathBuf),
111
112 #[error(r#"Expecting folder to folder mapping. "{0}" should end with "/"#)]
113 InvalidPackageConfigDirectory(PathBuf),
114
115 #[error(r#"Package import specifier "{0}" is not defined in package {1}"#)]
116 PackageImportNotDefined(String, PathBuf),
117
118 #[error("{0} is unimplemented")]
119 Unimplemented(&'static str),
120
121 #[error("Recursion in resolving")]
123 Recursion,
124
125 #[cfg(feature = "yarn_pnp")]
126 #[error("Failed to find yarn pnp manifest in {0}.")]
127 FailedToFindYarnPnpManifest(PathBuf),
128
129 #[cfg(feature = "yarn_pnp")]
130 #[error("{0}")]
131 YarnPnpError(pnp::Error),
132}
133
134impl ResolveError {
135 #[must_use]
136 pub const fn is_ignore(&self) -> bool {
137 matches!(self, Self::Ignored(_))
138 }
139
140 #[cold]
141 #[must_use]
142 pub fn from_serde_json_error(path: PathBuf, error: &serde_json::Error) -> Self {
143 Self::Json(JSONError {
144 path,
145 message: error.to_string(),
146 line: error.line(),
147 column: error.column(),
148 })
149 }
150}
151
152#[derive(Debug, Clone, Eq, PartialEq, Error)]
154pub enum SpecifierError {
155 #[error("The specifiers must be a non-empty string. Received \"{0}\"")]
156 Empty(String),
157}
158
159#[derive(Debug, Clone, Eq, PartialEq, Error)]
161#[error("{message}")]
162pub struct JSONError {
163 pub path: PathBuf,
164 pub message: String,
165 pub line: usize,
166 pub column: usize,
167}
168
169#[derive(Debug, Clone, Error)]
170#[error("{0}")]
171pub struct IOError(Arc<io::Error>);
172
173impl PartialEq for IOError {
174 fn eq(&self, other: &Self) -> bool {
175 self.0.kind() == other.0.kind()
176 }
177}
178
179impl From<IOError> for io::Error {
180 #[cold]
181 fn from(error: IOError) -> Self {
182 let io_error = error.0.as_ref();
183 Self::new(io_error.kind(), io_error.to_string())
184 }
185}
186
187impl From<io::Error> for ResolveError {
188 #[cold]
189 fn from(err: io::Error) -> Self {
190 Self::IOError(IOError(Arc::new(err)))
191 }
192}
193
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub struct CircularPathBufs(Vec<PathBuf>);
196
197impl CircularPathBufs {
198 #[must_use]
199 pub fn paths(&self) -> &[PathBuf] {
200 &self.0
201 }
202}
203
204impl Display for CircularPathBufs {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 for (i, path) in self.0.iter().enumerate() {
207 if i != 0 {
208 write!(f, " -> ")?;
209 }
210 path.fmt(f)?;
211 }
212 Ok(())
213 }
214}
215
216impl From<Vec<PathBuf>> for CircularPathBufs {
217 #[cold]
218 fn from(value: Vec<PathBuf>) -> Self {
219 Self(value)
220 }
221}
222
223#[derive(Debug, Clone, PartialEq, Eq)]
225pub struct ConditionNames(Vec<String>);
226
227impl ConditionNames {
228 #[must_use]
229 pub fn names(&self) -> &[String] {
230 &self.0
231 }
232}
233
234impl From<Vec<String>> for ConditionNames {
235 fn from(conditions: Vec<String>) -> Self {
236 Self(conditions)
237 }
238}
239
240impl Display for ConditionNames {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 match self.0.len() {
243 0 => write!(f, "no conditions"),
244 1 => write!(f, "the condition \"{}\"", self.0[0]),
245 _ => {
246 write!(f, "the conditions ")?;
247 let conditions_str =
248 self.0.iter().map(|s| format!("\"{s}\"")).collect::<Vec<_>>().join(", ");
249 write!(f, "[{conditions_str}]")
250 }
251 }
252 }
253}
254
255#[test]
256fn test_into_io_error() {
257 use std::io::{self, ErrorKind};
258 let error_string = "IOError occurred";
259 let string_error = io::Error::new(ErrorKind::Interrupted, error_string.to_string());
260 let string_error2 = io::Error::new(ErrorKind::Interrupted, error_string.to_string());
261 let resolve_io_error: ResolveError = ResolveError::from(string_error2);
262
263 assert_eq!(resolve_io_error, ResolveError::from(string_error));
264 assert_eq!(resolve_io_error.clone(), resolve_io_error);
265 let ResolveError::IOError(io_error) = resolve_io_error else { unreachable!() };
266 assert_eq!(
267 format!("{io_error:?}"),
268 r#"IOError(Custom { kind: Interrupted, error: "IOError occurred" })"#
269 );
270 let std_io_error: io::Error = io_error.into();
272 assert_eq!(std_io_error.kind(), ErrorKind::Interrupted);
273 assert_eq!(std_io_error.to_string(), error_string);
274 assert_eq!(
275 format!("{std_io_error:?}"),
276 r#"Custom { kind: Interrupted, error: "IOError occurred" }"#
277 );
278}
279
280#[test]
281fn test_coverage() {
282 let error = ResolveError::NotFound("x".into());
283 assert_eq!(format!("{error:?}"), r#"NotFound("x")"#);
284 assert_eq!(error.clone(), error);
285
286 let error = ResolveError::Specifier(SpecifierError::Empty("x".into()));
287 assert_eq!(format!("{error:?}"), r#"Specifier(Empty("x"))"#);
288 assert_eq!(error.clone(), error);
289}
290
291#[test]
292fn test_circular_path_bufs_display() {
293 use std::path::PathBuf;
294
295 let paths = vec![
296 PathBuf::from("/foo/tsconfig.json"),
297 PathBuf::from("/bar/tsconfig.json"),
298 PathBuf::from("/baz/tsconfig.json"),
299 ];
300 let circular = CircularPathBufs::from(paths);
301 let display_str = format!("{circular}");
302 assert!(display_str.contains("/foo/tsconfig.json"));
303 assert!(display_str.contains(" -> "));
304 assert!(display_str.contains("/bar/tsconfig.json"));
305 assert!(display_str.contains("/baz/tsconfig.json"));
306}