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