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 Display for CircularPathBufs {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 for (i, path) in self.0.iter().enumerate() {
193 if i != 0 {
194 write!(f, " -> ")?;
195 }
196 path.fmt(f)?;
197 }
198 Ok(())
199 }
200}
201
202impl From<Vec<PathBuf>> for CircularPathBufs {
203 #[cold]
204 fn from(value: Vec<PathBuf>) -> Self {
205 Self(value)
206 }
207}
208
209#[derive(Debug, Clone, PartialEq, Eq)]
211pub struct ConditionNames(Vec<String>);
212
213impl From<Vec<String>> for ConditionNames {
214 fn from(conditions: Vec<String>) -> Self {
215 Self(conditions)
216 }
217}
218
219impl Display for ConditionNames {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 match self.0.len() {
222 0 => write!(f, "no conditions"),
223 1 => write!(f, "the condition \"{}\"", self.0[0]),
224 _ => {
225 write!(f, "the conditions ")?;
226 let conditions_str =
227 self.0.iter().map(|s| format!("\"{s}\"")).collect::<Vec<_>>().join(", ");
228 write!(f, "[{conditions_str}]")
229 }
230 }
231 }
232}
233
234#[test]
235fn test_into_io_error() {
236 use std::io::{self, ErrorKind};
237 let error_string = "IOError occurred";
238 let string_error = io::Error::new(ErrorKind::Interrupted, error_string.to_string());
239 let string_error2 = io::Error::new(ErrorKind::Interrupted, error_string.to_string());
240 let resolve_io_error: ResolveError = ResolveError::from(string_error2);
241
242 assert_eq!(resolve_io_error, ResolveError::from(string_error));
243 assert_eq!(resolve_io_error.clone(), resolve_io_error);
244 let ResolveError::IOError(io_error) = resolve_io_error else { unreachable!() };
245 assert_eq!(
246 format!("{io_error:?}"),
247 r#"IOError(Custom { kind: Interrupted, error: "IOError occurred" })"#
248 );
249 let std_io_error: io::Error = io_error.into();
251 assert_eq!(std_io_error.kind(), ErrorKind::Interrupted);
252 assert_eq!(std_io_error.to_string(), error_string);
253 assert_eq!(
254 format!("{std_io_error:?}"),
255 r#"Custom { kind: Interrupted, error: "IOError occurred" }"#
256 );
257}
258
259#[test]
260fn test_coverage() {
261 let error = ResolveError::NotFound("x".into());
262 assert_eq!(format!("{error:?}"), r#"NotFound("x")"#);
263 assert_eq!(error.clone(), error);
264
265 let error = ResolveError::Specifier(SpecifierError::Empty("x".into()));
266 assert_eq!(format!("{error:?}"), r#"Specifier(Empty("x"))"#);
267 assert_eq!(error.clone(), error);
268}
269
270#[test]
271fn test_circular_path_bufs_display() {
272 use std::path::PathBuf;
273
274 let paths = vec![
275 PathBuf::from("/foo/tsconfig.json"),
276 PathBuf::from("/bar/tsconfig.json"),
277 PathBuf::from("/baz/tsconfig.json"),
278 ];
279 let circular = CircularPathBufs::from(paths);
280 let display_str = format!("{circular}");
281 assert!(display_str.contains("/foo/tsconfig.json"));
282 assert!(display_str.contains(" -> "));
283 assert!(display_str.contains("/bar/tsconfig.json"));
284 assert!(display_str.contains("/baz/tsconfig.json"));
285}