deno_graph/
graph.rs

1// Copyright 2018-2024 the Deno authors. MIT license.
2
3use crate::analysis::DependencyDescriptor;
4use crate::analysis::DynamicArgument;
5use crate::analysis::DynamicDependencyKind;
6use crate::analysis::DynamicTemplatePart;
7use crate::analysis::ImportAttributes;
8use crate::analysis::ModuleAnalyzer;
9use crate::analysis::ModuleInfo;
10use crate::analysis::SpecifierWithRange;
11use crate::analysis::StaticDependencyKind;
12use crate::analysis::TypeScriptReference;
13use crate::analysis::TypeScriptTypesResolutionMode;
14use crate::collections::SeenPendingCollection;
15use crate::jsr::JsrMetadataStore;
16use crate::jsr::JsrMetadataStoreServices;
17use crate::jsr::PendingJsrPackageVersionInfoLoadItem;
18use crate::jsr::PendingResult;
19use crate::packages::JsrVersionResolver;
20use crate::ReferrerImports;
21
22use crate::module_specifier::is_fs_root_specifier;
23use crate::module_specifier::resolve_import;
24use crate::module_specifier::ModuleSpecifier;
25use crate::module_specifier::SpecifierError;
26use crate::packages::JsrPackageInfo;
27use crate::packages::JsrPackageVersionInfo;
28use crate::packages::PackageSpecifiers;
29use crate::rt::Executor;
30
31use crate::source::*;
32
33use crate::MediaType;
34use boxed_error::Boxed;
35use deno_error::JsError;
36use deno_error::JsErrorBox;
37use deno_error::JsErrorClass;
38use deno_media_type::encoding::DecodedArcSourceDetailKind;
39use deno_media_type::encoding::BOM_CHAR;
40use deno_semver::jsr::JsrDepPackageReq;
41use deno_semver::jsr::JsrPackageNvReference;
42use deno_semver::jsr::JsrPackageReqReference;
43use deno_semver::npm::NpmPackageNvReference;
44use deno_semver::npm::NpmPackageReqReference;
45use deno_semver::package::PackageNv;
46use deno_semver::package::PackageNvReference;
47use deno_semver::package::PackageReq;
48use deno_semver::package::PackageReqReferenceParseError;
49use deno_semver::package::PackageSubPath;
50use deno_semver::RangeSetOrTag;
51use deno_semver::SmallStackString;
52use deno_semver::StackString;
53use deno_semver::Version;
54use deno_semver::VersionReq;
55use futures::future::LocalBoxFuture;
56use futures::stream::FuturesOrdered;
57use futures::stream::FuturesUnordered;
58use futures::stream::StreamExt;
59use futures::FutureExt;
60use indexmap::IndexMap;
61use indexmap::IndexSet;
62use serde::ser::SerializeSeq;
63use serde::ser::SerializeStruct;
64use serde::ser::SerializeTuple;
65use serde::Deserialize;
66use serde::Serialize;
67use serde::Serializer;
68use std::borrow::Cow;
69use std::cell::RefCell;
70use std::cmp::Ordering;
71use std::collections::BTreeMap;
72use std::collections::BTreeSet;
73use std::collections::HashMap;
74use std::collections::HashSet;
75use std::collections::VecDeque;
76use std::fmt;
77use std::path::Path;
78use std::rc::Rc;
79use std::sync::Arc;
80use std::time::SystemTime;
81use sys_traits::FileType;
82use sys_traits::FsDirEntry;
83use thiserror::Error;
84use url::Url;
85use wasm::wasm_module_to_dts;
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
88pub struct Position {
89  /// The 0-indexed line index.
90  pub line: usize,
91  /// The 0-indexed character index.
92  pub character: usize,
93}
94
95impl std::fmt::Display for Position {
96  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97    write!(f, "{}:{}", self.line + 1, self.character + 1)
98  }
99}
100
101impl PartialOrd for Position {
102  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
103    Some(self.cmp(other))
104  }
105}
106
107impl Ord for Position {
108  fn cmp(&self, other: &Self) -> Ordering {
109    match self.line.cmp(&other.line) {
110      Ordering::Equal => self.character.cmp(&other.character),
111      Ordering::Greater => Ordering::Greater,
112      Ordering::Less => Ordering::Less,
113    }
114  }
115}
116
117impl Position {
118  pub fn new(line: usize, character: usize) -> Self {
119    Self { line, character }
120  }
121
122  pub fn zeroed() -> Self {
123    Self {
124      line: 0,
125      character: 0,
126    }
127  }
128
129  #[cfg(feature = "swc")]
130  pub fn from_source_pos(
131    pos: deno_ast::SourcePos,
132    text_info: &deno_ast::SourceTextInfo,
133  ) -> Self {
134    let line_and_column_index = text_info.line_and_column_index(pos);
135    Self {
136      line: line_and_column_index.line_index,
137      character: line_and_column_index.column_index,
138    }
139  }
140
141  #[cfg(feature = "swc")]
142  pub fn as_source_pos(
143    &self,
144    text_info: &deno_ast::SourceTextInfo,
145  ) -> deno_ast::SourcePos {
146    text_info.loc_to_source_pos(deno_ast::LineAndColumnIndex {
147      line_index: self.line,
148      column_index: self.character,
149    })
150  }
151}
152
153#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Hash)]
154pub struct PositionRange {
155  #[serde(default = "Position::zeroed")]
156  pub start: Position,
157  #[serde(default = "Position::zeroed")]
158  pub end: Position,
159}
160
161impl PositionRange {
162  pub fn zeroed() -> Self {
163    Self {
164      start: Position::zeroed(),
165      end: Position::zeroed(),
166    }
167  }
168
169  /// Determines if a given position is within the range.
170  pub fn includes(&self, position: Position) -> bool {
171    (position >= self.start) && (position <= self.end)
172  }
173
174  #[cfg(feature = "swc")]
175  pub fn from_source_range(
176    range: deno_ast::SourceRange,
177    text_info: &deno_ast::SourceTextInfo,
178  ) -> Self {
179    Self {
180      start: Position::from_source_pos(range.start, text_info),
181      end: Position::from_source_pos(range.end, text_info),
182    }
183  }
184
185  #[cfg(feature = "swc")]
186  pub fn as_source_range(
187    &self,
188    text_info: &deno_ast::SourceTextInfo,
189  ) -> deno_ast::SourceRange {
190    deno_ast::SourceRange::new(
191      self.start.as_source_pos(text_info),
192      self.end.as_source_pos(text_info),
193    )
194  }
195}
196
197// Custom serialization to serialize to an array. Interestingly we
198// don't need to implement custom deserialization logic that does
199// the same thing, and serde_json will handle it fine.
200impl Serialize for PositionRange {
201  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
202  where
203    S: Serializer,
204  {
205    struct PositionSerializer<'a>(&'a Position);
206
207    impl Serialize for PositionSerializer<'_> {
208      fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
209      where
210        S: Serializer,
211      {
212        let mut seq = serializer.serialize_tuple(2)?;
213        seq.serialize_element(&self.0.line)?;
214        seq.serialize_element(&self.0.character)?;
215        seq.end()
216      }
217    }
218
219    let mut seq = serializer.serialize_tuple(2)?;
220    seq.serialize_element(&PositionSerializer(&self.start))?;
221    seq.serialize_element(&PositionSerializer(&self.end))?;
222    seq.end()
223  }
224}
225
226#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
227pub struct Range {
228  #[serde(skip_serializing)]
229  pub specifier: ModuleSpecifier,
230  #[serde(flatten, serialize_with = "serialize_position")]
231  pub range: PositionRange,
232  #[serde(default, skip_serializing)]
233  pub resolution_mode: Option<ResolutionMode>,
234}
235
236fn serialize_position<S: Serializer>(
237  range: &PositionRange,
238  serializer: S,
239) -> Result<S::Ok, S::Error> {
240  let mut seq = serializer.serialize_struct("PositionRange", 2)?;
241  seq.serialize_field("start", &range.start)?;
242  seq.serialize_field("end", &range.end)?;
243  seq.end()
244}
245
246impl fmt::Display for Range {
247  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248    write!(f, "{}:{}", self.specifier, self.range.start)
249  }
250}
251
252impl Range {
253  /// Determines if a given position is within the range.
254  pub fn includes(&self, position: Position) -> bool {
255    self.range.includes(position)
256  }
257}
258
259#[derive(Debug, Clone, Error, JsError)]
260pub enum JsrLoadError {
261  #[class(type)]
262  #[error(
263    "Unsupported checksum in JSR package manifest. Maybe try upgrading deno?"
264  )]
265  UnsupportedManifestChecksum,
266  #[class(inherit)]
267  #[error(transparent)]
268  ContentChecksumIntegrity(ChecksumIntegrityError),
269  #[class(generic)]
270  #[error("Loader should never return an external specifier for a jsr: specifier content load.")]
271  ContentLoadExternalSpecifier,
272  #[class(inherit)]
273  #[error(transparent)]
274  ContentLoad(Arc<LoadError>),
275  #[class(inherit)]
276  #[error("JSR package manifest for '{}' failed to load. {:#}", .0, .1)]
277  PackageManifestLoad(String, #[inherit] Arc<LoadError>),
278  #[class("NotFound")]
279  #[error("JSR package not found: {}", .0)]
280  PackageNotFound(String),
281  #[class("NotFound")]
282  #[error("JSR package version not found: {}", .0)]
283  PackageVersionNotFound(Box<PackageNv>),
284  #[class(inherit)]
285  #[error("JSR package version manifest for '{}' failed to load: {:#}", .0, .1)]
286  PackageVersionManifestLoad(Box<PackageNv>, #[inherit] Arc<dyn JsErrorClass>),
287  #[class(inherit)]
288  #[error("JSR package version manifest for '{}' failed to load: {:#}", .0, .1)]
289  PackageVersionManifestChecksumIntegrity(
290    Box<PackageNv>,
291    #[inherit] ChecksumIntegrityError,
292  ),
293  #[class(inherit)]
294  #[error(transparent)]
295  PackageFormat(JsrPackageFormatError),
296  #[class(inherit)]
297  #[error(transparent)]
298  PackageReqNotFound(JsrPackageReqNotFoundError),
299  #[class(generic)]
300  #[error("Redirects in the JSR registry are not supported (redirected to '{}')", .0)]
301  RedirectInPackage(ModuleSpecifier),
302  #[class("NotFound")]
303  #[error("Unknown export '{}' for '{}'.\n  Package exports:\n{}", export_name, .nv, .exports.iter().map(|e| format!(" * {}", e)).collect::<Vec<_>>().join("\n"))]
304  UnknownExport {
305    nv: Box<PackageNv>,
306    export_name: String,
307    exports: Vec<String>,
308  },
309}
310
311#[derive(Error, Debug, Clone, JsError)]
312#[class("NotFound")]
313#[error("Could not find version of '{}' that matches specified version constraint '{}'{}", req.name, req.version_req, newest_dependency_date.map(|v| format!("\n\nA newer matching version was found, but it was not used because it was newer than the specified minimum dependency date of {}", v)).unwrap_or_else(String::new))]
314pub struct JsrPackageReqNotFoundError {
315  pub req: PackageReq,
316  pub newest_dependency_date: Option<chrono::DateTime<chrono::Utc>>,
317}
318
319#[derive(Error, Debug, Clone, JsError)]
320#[class(type)]
321pub enum JsrPackageFormatError {
322  #[error(transparent)]
323  JsrPackageParseError(PackageReqReferenceParseError),
324  #[error("Version tag not supported in jsr specifiers ('{}').{}",
325    .tag,
326    match .tag.strip_prefix('v').and_then(|v| VersionReq::parse_from_specifier(v).ok().map(|s| s.tag().is_none())).unwrap_or(false) {
327      true => " Remove leading 'v' before version.",
328      false => ""
329    }
330  )]
331  VersionTagNotSupported { tag: SmallStackString },
332}
333
334#[derive(Debug, Clone, Error, JsError)]
335pub enum NpmLoadError {
336  #[class(type)]
337  #[error("npm specifiers are not supported in this environment")]
338  NotSupportedEnvironment,
339  #[class(inherit)]
340  #[error(transparent)]
341  PackageReqResolution(Arc<dyn JsErrorClass>),
342  #[class(inherit)]
343  #[error(transparent)]
344  PackageReqReferenceParse(PackageReqReferenceParseError),
345  #[class(inherit)]
346  #[error(transparent)]
347  RegistryInfo(Arc<dyn JsErrorClass>),
348}
349
350#[derive(Debug, Error, Clone, JsError)]
351pub enum ModuleLoadError {
352  #[class(inherit)]
353  #[error(transparent)]
354  HttpsChecksumIntegrity(ChecksumIntegrityError),
355  #[class(inherit)]
356  #[error(transparent)]
357  Decode(Arc<DecodeError>),
358  #[class(inherit)]
359  #[error(transparent)]
360  Loader(Arc<LoadError>),
361  #[class(inherit)]
362  #[error(transparent)]
363  Jsr(#[from] JsrLoadError),
364  #[class(inherit)]
365  #[error(transparent)]
366  Npm(#[from] NpmLoadError),
367  #[class(generic)]
368  #[error("Too many redirects.")]
369  TooManyRedirects,
370}
371
372#[derive(Debug, JsError)]
373#[class(inherit)]
374pub struct DecodeError {
375  /// Modified time of the underlying file. Used to tell whether
376  /// the file should be reloaded.
377  pub mtime: Option<SystemTime>,
378  #[inherit]
379  pub err: std::io::Error,
380}
381
382impl std::error::Error for DecodeError {
383  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
384    self.err.source()
385  }
386}
387
388impl std::fmt::Display for DecodeError {
389  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390    self.err.fmt(f)
391  }
392}
393
394#[derive(Debug, Clone, JsError, Boxed)]
395pub struct ModuleError(pub Box<ModuleErrorKind>);
396
397impl ModuleError {
398  pub fn specifier(&self) -> &ModuleSpecifier {
399    self.as_kind().specifier()
400  }
401
402  pub fn maybe_referrer(&self) -> Option<&Range> {
403    self.as_kind().maybe_referrer()
404  }
405
406  /// Gets the mtime (if able) of the loaded file that caused this error.
407  pub fn mtime(&self) -> Option<SystemTime> {
408    self.as_kind().mtime()
409  }
410
411  /// Converts the error into a string along with the range related to the error.
412  pub fn to_string_with_range(&self) -> String {
413    self.as_kind().to_string_with_range()
414  }
415}
416
417#[derive(Debug, Clone, JsError)]
418pub enum ModuleErrorKind {
419  #[class(inherit)]
420  Load {
421    specifier: ModuleSpecifier,
422    maybe_referrer: Option<Range>,
423    #[inherit]
424    err: ModuleLoadError,
425  },
426  #[class("NotFound")]
427  Missing {
428    specifier: ModuleSpecifier,
429    maybe_referrer: Option<Range>,
430  },
431  #[class("NotFound")]
432  MissingDynamic {
433    specifier: ModuleSpecifier,
434    referrer: Range,
435  },
436  #[class(inherit)]
437  Parse {
438    specifier: ModuleSpecifier,
439    /// Modified time of the underlying file. Used to tell whether
440    /// the file should be reloaded.
441    mtime: Option<SystemTime>,
442    #[inherit]
443    diagnostic: Arc<JsErrorBox>,
444  },
445  #[class(inherit)]
446  WasmParse {
447    specifier: ModuleSpecifier,
448    /// Modified time of the underlying file. Used to tell whether
449    /// the file should be reloaded.
450    mtime: Option<SystemTime>,
451    #[inherit]
452    err: wasm_dep_analyzer::ParseError,
453  },
454  #[class(type)]
455  UnsupportedMediaType {
456    specifier: ModuleSpecifier,
457    media_type: MediaType,
458    maybe_referrer: Option<Range>,
459  },
460  #[class(syntax)]
461  InvalidTypeAssertion {
462    specifier: ModuleSpecifier,
463    referrer: Range,
464    actual_media_type: MediaType,
465    expected_media_type: MediaType,
466  },
467  #[class(type)]
468  UnsupportedImportAttributeType {
469    specifier: ModuleSpecifier,
470    referrer: Range,
471    kind: String,
472  },
473}
474
475impl ModuleErrorKind {
476  pub fn specifier(&self) -> &ModuleSpecifier {
477    match self {
478      Self::Load { specifier, .. }
479      | Self::Parse { specifier, .. }
480      | Self::WasmParse { specifier, .. }
481      | Self::UnsupportedMediaType { specifier, .. }
482      | Self::Missing { specifier, .. }
483      | Self::MissingDynamic { specifier, .. }
484      | Self::InvalidTypeAssertion { specifier, .. }
485      | Self::UnsupportedImportAttributeType { specifier, .. } => specifier,
486    }
487  }
488
489  pub fn maybe_referrer(&self) -> Option<&Range> {
490    match self {
491      Self::Load { maybe_referrer, .. }
492      | Self::Missing { maybe_referrer, .. } => maybe_referrer.as_ref(),
493      Self::UnsupportedMediaType { maybe_referrer, .. } => {
494        maybe_referrer.as_ref()
495      }
496      Self::Parse { .. } => None,
497      Self::WasmParse { .. } => None,
498      Self::MissingDynamic { referrer, .. }
499      | Self::InvalidTypeAssertion { referrer, .. }
500      | Self::UnsupportedImportAttributeType { referrer, .. } => Some(referrer),
501    }
502  }
503
504  /// Gets the mtime (if able) of the loaded file that caused this error.
505  pub fn mtime(&self) -> Option<SystemTime> {
506    match self {
507      Self::Parse { mtime, .. } | Self::WasmParse { mtime, .. } => *mtime,
508      Self::Load { err, .. } => match err {
509        ModuleLoadError::Decode(decode_error) => decode_error.mtime,
510        ModuleLoadError::HttpsChecksumIntegrity { .. }
511        | ModuleLoadError::Loader { .. }
512        | ModuleLoadError::Jsr { .. }
513        | ModuleLoadError::Npm { .. }
514        | ModuleLoadError::TooManyRedirects => None,
515      },
516      Self::Missing { .. }
517      | Self::MissingDynamic { .. }
518      | Self::UnsupportedMediaType { .. }
519      | Self::InvalidTypeAssertion { .. }
520      | Self::UnsupportedImportAttributeType { .. } => None,
521    }
522  }
523
524  /// Converts the error into a string along with the range related to the error.
525  pub fn to_string_with_range(&self) -> String {
526    if let Some(range) = self.maybe_referrer() {
527      format!("{self:#}\n    at {range}")
528    } else {
529      format!("{self:#}")
530    }
531  }
532}
533
534impl std::error::Error for ModuleErrorKind {
535  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
536    match self {
537      Self::Load { err, .. } => Some(err),
538      Self::Missing { .. }
539      | Self::MissingDynamic { .. }
540      | Self::Parse { .. }
541      | Self::WasmParse { .. }
542      | Self::UnsupportedMediaType { .. }
543      | Self::InvalidTypeAssertion { .. }
544      | Self::UnsupportedImportAttributeType { .. } => None,
545    }
546  }
547}
548
549impl fmt::Display for ModuleErrorKind {
550  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
551    match self {
552      Self::Load { err, .. } => err.fmt(f),
553      Self::Parse { diagnostic, .. } => write!(f, "The module's source code could not be parsed: {diagnostic}"),
554      Self::WasmParse { specifier, err, .. } => write!(f, "The Wasm module could not be parsed: {err}\n  Specifier: {specifier}"),
555      Self::UnsupportedMediaType { specifier, media_type: MediaType::Json, .. } => write!(f, "Expected a JavaScript or TypeScript module, but identified a Json module. Consider importing Json modules with an import attribute with the type of \"json\".\n  Specifier: {specifier}"),
556      Self::UnsupportedMediaType { specifier, media_type: MediaType::Cjs | MediaType::Cts, .. } if specifier.scheme() != "file" => write!(f, "Remote CJS modules are not supported.\n  Specifier: {specifier}"),
557      Self::UnsupportedMediaType { specifier, media_type, .. } => write!(f, "Expected a JavaScript or TypeScript module, but identified a {media_type} module. Importing these types of modules is currently not supported.\n  Specifier: {specifier}"),
558      Self::Missing{ specifier, .. } => write!(f, "Module not found \"{specifier}\"."),
559      Self::MissingDynamic{ specifier, .. } => write!(f, "Dynamic import not found \"{specifier}\"."),
560      Self::InvalidTypeAssertion { specifier, actual_media_type: MediaType::Json, expected_media_type, .. } =>
561        write!(f, "Expected a {expected_media_type} module, but identified a Json module. Consider importing Json modules with an import attribute with the type of \"json\".\n  Specifier: {specifier}"),
562      Self::InvalidTypeAssertion { specifier, actual_media_type, expected_media_type, .. } =>
563        write!(f, "Expected a {expected_media_type} module, but identified a {actual_media_type} module.\n  Specifier: {specifier}"),
564      Self::UnsupportedImportAttributeType { specifier, kind, .. } =>
565        write!(f, "The import attribute type of \"{kind}\" is unsupported.\n  Specifier: {specifier}"),
566    }
567  }
568}
569
570#[derive(Debug, Clone, JsError)]
571pub enum ModuleGraphError {
572  #[class(inherit)]
573  ModuleError(ModuleError),
574  #[class(inherit)]
575  ResolutionError(ResolutionError),
576  #[class(inherit)]
577  TypesResolutionError(ResolutionError),
578}
579
580impl ModuleGraphError {
581  pub fn as_module_error_kind(&self) -> Option<&ModuleErrorKind> {
582    match self {
583      Self::ModuleError(err) => Some(err.as_kind()),
584      _ => None,
585    }
586  }
587
588  fn for_resolution_kind(kind: ResolutionKind, error: ResolutionError) -> Self {
589    match kind {
590      ResolutionKind::Execution => Self::ResolutionError(error),
591      ResolutionKind::Types => Self::TypesResolutionError(error),
592    }
593  }
594
595  /// Converts the error into a string along with the range related to the error.
596  ///
597  /// We don't include the range in the error messages by default because they're
598  /// not useful in cases like the LSP where the range is given by the editor itself.
599  pub fn to_string_with_range(&self) -> String {
600    match self {
601      ModuleGraphError::ModuleError(err) => err.to_string_with_range(),
602      ModuleGraphError::ResolutionError(err)
603      | ModuleGraphError::TypesResolutionError(err) => {
604        err.to_string_with_range()
605      }
606    }
607  }
608
609  pub fn maybe_range(&self) -> Option<&Range> {
610    match self {
611      Self::ModuleError(err) => err.maybe_referrer(),
612      Self::ResolutionError(err) | Self::TypesResolutionError(err) => {
613        Some(err.range())
614      }
615    }
616  }
617}
618
619impl std::error::Error for ModuleGraphError {
620  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
621    match self {
622      Self::ModuleError(ref err) => Some(err),
623      Self::ResolutionError(ref err) | Self::TypesResolutionError(ref err) => {
624        Some(err)
625      }
626    }
627  }
628}
629
630impl fmt::Display for ModuleGraphError {
631  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
632    match self {
633      Self::ModuleError(err) => err.fmt(f),
634      Self::ResolutionError(err) => err.fmt(f),
635      Self::TypesResolutionError(err) => {
636        f.write_str("Failed resolving types. ")?;
637        err.fmt(f)
638      }
639    }
640  }
641}
642
643#[derive(Debug, Clone, JsError)]
644#[class(type)]
645pub enum ResolutionError {
646  InvalidDowngrade {
647    specifier: ModuleSpecifier,
648    range: Range,
649  },
650  InvalidJsrHttpsTypesImport {
651    specifier: ModuleSpecifier,
652    range: Range,
653  },
654  InvalidLocalImport {
655    specifier: ModuleSpecifier,
656    range: Range,
657  },
658  InvalidSpecifier {
659    error: SpecifierError,
660    range: Range,
661  },
662  ResolverError {
663    error: Arc<ResolveError>,
664    specifier: String,
665    range: Range,
666  },
667}
668
669impl ResolutionError {
670  /// Return a reference to the range that the error applies to.
671  pub fn range(&self) -> &Range {
672    match self {
673      Self::InvalidDowngrade { range, .. }
674      | Self::InvalidJsrHttpsTypesImport { range, .. }
675      | Self::InvalidLocalImport { range, .. }
676      | Self::InvalidSpecifier { range, .. }
677      | Self::ResolverError { range, .. } => range,
678    }
679  }
680
681  /// Converts the error into a string along with the range related to the error.
682  pub fn to_string_with_range(&self) -> String {
683    format!("{}\n    at {}", self, self.range())
684  }
685}
686
687impl std::error::Error for ResolutionError {
688  fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
689    match self {
690      Self::InvalidDowngrade { .. }
691      | Self::InvalidJsrHttpsTypesImport { .. }
692      | Self::InvalidLocalImport { .. } => None,
693      Self::InvalidSpecifier { ref error, .. } => Some(error),
694      Self::ResolverError { error, .. } => Some(error.as_ref()),
695    }
696  }
697}
698
699impl PartialEq for ResolutionError {
700  fn eq(&self, other: &Self) -> bool {
701    match (self, other) {
702      (
703        Self::ResolverError {
704          specifier: a,
705          range: a_range,
706          ..
707        },
708        Self::ResolverError {
709          specifier: b,
710          range: b_range,
711          ..
712        },
713      ) => a == b && a_range == b_range,
714      (
715        Self::InvalidDowngrade {
716          specifier: a,
717          range: a_range,
718          ..
719        },
720        Self::InvalidDowngrade {
721          specifier: b,
722          range: b_range,
723          ..
724        },
725      )
726      | (
727        Self::InvalidJsrHttpsTypesImport {
728          specifier: a,
729          range: a_range,
730          ..
731        },
732        Self::InvalidJsrHttpsTypesImport {
733          specifier: b,
734          range: b_range,
735          ..
736        },
737      )
738      | (
739        Self::InvalidLocalImport {
740          specifier: a,
741          range: a_range,
742          ..
743        },
744        Self::InvalidLocalImport {
745          specifier: b,
746          range: b_range,
747          ..
748        },
749      ) => a == b && a_range == b_range,
750      (
751        Self::InvalidSpecifier {
752          error: a,
753          range: a_range,
754          ..
755        },
756        Self::InvalidSpecifier {
757          error: b,
758          range: b_range,
759          ..
760        },
761      ) => a == b && a_range == b_range,
762      _ => false,
763    }
764  }
765}
766
767impl Eq for ResolutionError {}
768
769impl fmt::Display for ResolutionError {
770  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771    match self {
772      Self::InvalidDowngrade { specifier, .. } => write!(f, "Modules imported via https are not allowed to import http modules.\n  Importing: {specifier}"),
773      Self::InvalidJsrHttpsTypesImport { specifier, .. } => write!(f, "Importing JSR packages via HTTPS specifiers for type checking is not supported for performance reasons. If you would like types, import via a `jsr:` specifier instead or else use a non-statically analyzable dynamic import.\n  Importing: {specifier}"),
774      Self::InvalidLocalImport { specifier, .. } => write!(f, "Remote modules are not allowed to import local modules. Consider using a dynamic import instead.\n  Importing: {specifier}"),
775      Self::ResolverError { error, .. } => error.fmt(f),
776      Self::InvalidSpecifier { error, .. } => error.fmt(f),
777    }
778  }
779}
780
781#[derive(Debug, Clone, PartialEq, Eq)]
782pub struct ResolutionResolved {
783  /// Specifier to.
784  pub specifier: ModuleSpecifier,
785  /// Referrer range.
786  pub range: Range,
787}
788
789#[derive(Debug, Clone, PartialEq, Eq)]
790pub enum Resolution {
791  None,
792  Ok(Box<ResolutionResolved>),
793  Err(Box<ResolutionError>),
794}
795
796impl Resolution {
797  pub fn from_resolve_result(
798    result: Result<ModuleSpecifier, ResolveError>,
799    specifier_text: &str,
800    range: Range,
801  ) -> Self {
802    match result {
803      Ok(specifier) => {
804        Resolution::Ok(Box::new(ResolutionResolved { specifier, range }))
805      }
806      Err(err) => {
807        let resolution_error =
808          if let ResolveError::Specifier(specifier_error) = err {
809            ResolutionError::InvalidSpecifier {
810              error: specifier_error.clone(),
811              range,
812            }
813          } else {
814            ResolutionError::ResolverError {
815              error: Arc::new(err),
816              specifier: specifier_text.to_string(),
817              range,
818            }
819          };
820        Self::Err(Box::new(resolution_error))
821      }
822    }
823  }
824
825  pub fn includes(&self, position: Position) -> Option<&Range> {
826    match self {
827      Self::Ok(resolution) if resolution.range.includes(position) => {
828        Some(&resolution.range)
829      }
830      Self::Err(err) => {
831        let range = err.range();
832        if range.includes(position) {
833          Some(range)
834        } else {
835          None
836        }
837      }
838      _ => None,
839    }
840  }
841
842  pub fn is_none(&self) -> bool {
843    matches!(self, Self::None)
844  }
845
846  pub fn maybe_specifier(&self) -> Option<&ModuleSpecifier> {
847    self.ok().map(|r| &r.specifier)
848  }
849
850  pub fn maybe_range(&self) -> Option<&Range> {
851    match self {
852      Resolution::None => None,
853      Resolution::Ok(r) => Some(&r.range),
854      Resolution::Err(e) => Some(e.range()),
855    }
856  }
857
858  pub fn ok(&self) -> Option<&ResolutionResolved> {
859    if let Resolution::Ok(resolved) = self {
860      Some(&**resolved)
861    } else {
862      None
863    }
864  }
865
866  pub fn err(&self) -> Option<&ResolutionError> {
867    if let Resolution::Err(err) = self {
868      Some(&**err)
869    } else {
870      None
871    }
872  }
873}
874
875impl Default for Resolution {
876  fn default() -> Self {
877    Self::None
878  }
879}
880
881fn is_false(v: &bool) -> bool {
882  !v
883}
884
885#[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq)]
886#[serde(rename_all = "camelCase")]
887pub enum ImportKind {
888  /// `import`/`export`
889  Es,
890  /// `require`
891  Require,
892  /// `import type`/`export type`
893  TsType,
894  /// `declare module "@tanstack/react-router" {}`
895  TsModuleAugmentation,
896  /// `/// <reference path="..." />`
897  TsReferencePath,
898  /// `/// <reference types="..." />`
899  TsReferenceTypes,
900  /// `/** @jsxImportSource ... */`
901  JsxImportSource,
902  /// `/** @typedef { import("./types").Pet } Pet */`
903  JsDoc,
904}
905
906impl ImportKind {
907  pub fn is_runtime(&self) -> bool {
908    match self {
909      ImportKind::Es | ImportKind::Require | ImportKind::JsxImportSource => {
910        true
911      }
912      ImportKind::TsType
913      | ImportKind::TsModuleAugmentation
914      | ImportKind::TsReferencePath
915      | ImportKind::TsReferenceTypes
916      | ImportKind::JsDoc => false,
917    }
918  }
919
920  fn is_es(&self) -> bool {
921    matches!(self, ImportKind::Es)
922  }
923}
924
925#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
926#[serde(rename_all = "camelCase")]
927pub struct Import {
928  pub specifier: String,
929  #[serde(skip_serializing_if = "ImportKind::is_es")]
930  pub kind: ImportKind,
931  #[serde(rename = "range")]
932  pub specifier_range: Range,
933  #[serde(skip_serializing_if = "is_false")]
934  pub is_dynamic: bool,
935  /// If this is an import for only side effects (ex. `import './load.js';`)
936  #[serde(skip_serializing)]
937  pub is_side_effect: bool,
938  // Don't include attributes in `deno info --json` until someone has a need.
939  // Attribute error strings eventually will be included in a separate `Import::errors`, however.
940  #[serde(skip_serializing)]
941  pub attributes: ImportAttributes,
942}
943
944#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize)]
945#[serde(rename_all = "camelCase")]
946pub struct Dependency {
947  #[serde(rename = "code", skip_serializing_if = "Resolution::is_none")]
948  pub maybe_code: Resolution,
949  #[serde(rename = "type", skip_serializing_if = "Resolution::is_none")]
950  pub maybe_type: Resolution,
951  #[serde(skip_serializing)]
952  pub maybe_deno_types_specifier: Option<String>,
953  #[serde(skip_serializing_if = "is_false")]
954  pub is_dynamic: bool,
955  // todo(dsherret): rename to attributeType in 3.0
956  #[serde(rename = "assertionType", skip_serializing_if = "Option::is_none")]
957  pub maybe_attribute_type: Option<String>,
958  // TODO(nayeemrmn): Replace `maybe_attribute_type` with this in the serialization
959  // for 3.0.
960  #[serde(skip_serializing)]
961  pub imports: Vec<Import>,
962}
963
964impl Dependency {
965  /// Optionally return the module specifier in the module graph that points to
966  /// the "code" dependency in the graph.
967  pub fn get_code(&self) -> Option<&ModuleSpecifier> {
968    self.maybe_code.maybe_specifier()
969  }
970
971  /// Optionally return the module specifier in the module graph that points to
972  /// the type only dependency in the graph.
973  pub fn get_type(&self) -> Option<&ModuleSpecifier> {
974    self.maybe_type.maybe_specifier()
975  }
976
977  /// Check to see if the position falls within the range of the code or types
978  /// entry for the dependency, returning a reference to the range if true,
979  /// otherwise none.
980  pub fn includes(&self, position: Position) -> Option<&Range> {
981    for import in &self.imports {
982      if import.specifier_range.includes(position) {
983        return Some(&import.specifier_range);
984      }
985    }
986    // `@deno-types` directives won't be associated with an import.
987    if let Some(range) = self.maybe_type.includes(position) {
988      return Some(range);
989    }
990    None
991  }
992
993  pub fn with_new_resolver(
994    &self,
995    specifier: &str,
996    jsr_url_provider: &dyn JsrUrlProvider,
997    maybe_resolver: Option<&dyn Resolver>,
998  ) -> Self {
999    let maybe_code = self
1000      .maybe_code
1001      .maybe_range()
1002      .map(|r| {
1003        resolve(
1004          specifier,
1005          r.clone(),
1006          ResolutionKind::Execution,
1007          jsr_url_provider,
1008          maybe_resolver,
1009        )
1010      })
1011      .unwrap_or_default();
1012    let maybe_type = self
1013      .maybe_type
1014      .maybe_range()
1015      .map(|r| {
1016        resolve(
1017          self
1018            .maybe_deno_types_specifier
1019            .as_deref()
1020            .unwrap_or(specifier),
1021          r.clone(),
1022          ResolutionKind::Types,
1023          jsr_url_provider,
1024          maybe_resolver,
1025        )
1026      })
1027      .unwrap_or_default();
1028    Self {
1029      maybe_code,
1030      maybe_type,
1031      maybe_deno_types_specifier: self.maybe_deno_types_specifier.clone(),
1032      is_dynamic: self.is_dynamic,
1033      maybe_attribute_type: self.maybe_attribute_type.clone(),
1034      imports: self.imports.clone(),
1035    }
1036  }
1037}
1038
1039#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
1040#[serde(rename_all = "camelCase")]
1041pub struct TypesDependency {
1042  pub specifier: String,
1043  pub dependency: Resolution,
1044}
1045
1046impl TypesDependency {
1047  pub fn with_new_resolver(
1048    &self,
1049    jsr_url_provider: &dyn JsrUrlProvider,
1050    maybe_resolver: Option<&dyn Resolver>,
1051  ) -> Self {
1052    let dependency = self
1053      .dependency
1054      .maybe_range()
1055      .map(|r| {
1056        resolve(
1057          &self.specifier,
1058          r.clone(),
1059          ResolutionKind::Types,
1060          jsr_url_provider,
1061          maybe_resolver,
1062        )
1063      })
1064      .unwrap_or_default();
1065    Self {
1066      specifier: self.specifier.clone(),
1067      dependency,
1068    }
1069  }
1070}
1071
1072fn is_media_type_unknown(media_type: &MediaType) -> bool {
1073  matches!(media_type, MediaType::Unknown)
1074}
1075
1076#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1077pub struct WorkspaceMember {
1078  pub base: Url,
1079  pub name: StackString,
1080  #[serde(skip_serializing_if = "Option::is_none")]
1081  pub version: Option<Version>,
1082  pub exports: IndexMap<String, String>,
1083}
1084
1085impl WorkspaceMember {
1086  pub fn as_nv(&self) -> PackageNv {
1087    PackageNv {
1088      name: self.name.clone(),
1089      version: self
1090        .version
1091        .clone()
1092        // use a dummy version
1093        .unwrap_or_else(|| Version::parse_standard("0.0.0").unwrap()),
1094    }
1095  }
1096}
1097
1098#[derive(Debug, Clone, Serialize)]
1099#[serde(rename_all = "camelCase")]
1100#[serde(tag = "kind")]
1101pub enum Module {
1102  // todo(#239): remove this when updating the --json output for 3.0
1103  #[serde(rename = "esm")]
1104  Js(JsModule),
1105  // todo(#239): remove this when updating the --json output for 3.0
1106  #[serde(rename = "asserted")]
1107  Json(JsonModule),
1108  Wasm(WasmModule),
1109  Npm(NpmModule),
1110  Node(BuiltInNodeModule),
1111  External(ExternalModule),
1112}
1113
1114impl Module {
1115  pub fn specifier(&self) -> &ModuleSpecifier {
1116    match self {
1117      Module::Js(module) => &module.specifier,
1118      Module::Json(module) => &module.specifier,
1119      Module::Wasm(module) => &module.specifier,
1120      Module::Npm(module) => &module.specifier,
1121      Module::Node(module) => &module.specifier,
1122      Module::External(module) => &module.specifier,
1123    }
1124  }
1125
1126  pub fn media_type(&self) -> MediaType {
1127    match self {
1128      Module::Js(module) => module.media_type,
1129      Module::Json(module) => module.media_type,
1130      Module::Wasm(_) => MediaType::Wasm,
1131      Module::Node(_) => MediaType::JavaScript,
1132      Module::Npm(_) | Module::External(_) => MediaType::Unknown,
1133    }
1134  }
1135
1136  pub fn mtime(&self) -> Option<SystemTime> {
1137    match self {
1138      Module::Js(m) => m.mtime,
1139      Module::Json(m) => m.mtime,
1140      Module::Wasm(m) => m.mtime,
1141      Module::Npm(_) | Module::Node(_) | Module::External(_) => None,
1142    }
1143  }
1144
1145  pub fn json(&self) -> Option<&JsonModule> {
1146    if let Module::Json(module) = &self {
1147      Some(module)
1148    } else {
1149      None
1150    }
1151  }
1152
1153  pub fn js(&self) -> Option<&JsModule> {
1154    if let Module::Js(module) = &self {
1155      Some(module)
1156    } else {
1157      None
1158    }
1159  }
1160
1161  pub fn npm(&self) -> Option<&NpmModule> {
1162    if let Module::Npm(module) = &self {
1163      Some(module)
1164    } else {
1165      None
1166    }
1167  }
1168
1169  pub fn node(&self) -> Option<&BuiltInNodeModule> {
1170    if let Module::Node(module) = &self {
1171      Some(module)
1172    } else {
1173      None
1174    }
1175  }
1176
1177  pub fn external(&self) -> Option<&ExternalModule> {
1178    if let Module::External(module) = &self {
1179      Some(module)
1180    } else {
1181      None
1182    }
1183  }
1184
1185  pub fn source(&self) -> Option<&Arc<str>> {
1186    match self {
1187      crate::Module::Js(m) => Some(&m.source.text),
1188      crate::Module::Json(m) => Some(&m.source.text),
1189      crate::Module::Wasm(_)
1190      | crate::Module::Npm(_)
1191      | crate::Module::Node(_)
1192      | crate::Module::External(_) => None,
1193    }
1194  }
1195
1196  pub fn maybe_types_dependency(&self) -> Option<&TypesDependency> {
1197    match self {
1198      Module::Js(js_module) => js_module.maybe_types_dependency.as_ref(),
1199      Module::Wasm(_)
1200      | Module::Json(_)
1201      | Module::Npm(_)
1202      | Module::Node(_)
1203      | Module::External(_) => None,
1204    }
1205  }
1206
1207  pub fn dependencies(&self) -> &IndexMap<String, Dependency> {
1208    match self {
1209      Module::Js(js_module) => &js_module.dependencies,
1210      Module::Wasm(wasm_module) => &wasm_module.dependencies,
1211      Module::Npm(_)
1212      | Module::Node(_)
1213      | Module::External(_)
1214      | Module::Json(_) => EMPTY_DEPS.get_or_init(Default::default),
1215    }
1216  }
1217
1218  pub fn dependencies_prefer_fast_check(
1219    &self,
1220  ) -> &IndexMap<String, Dependency> {
1221    match self {
1222      Module::Js(js_module) => js_module.dependencies_prefer_fast_check(),
1223      Module::Wasm(wasm_module) => &wasm_module.dependencies,
1224      Module::Npm(_)
1225      | Module::Node(_)
1226      | Module::External(_)
1227      | Module::Json(_) => EMPTY_DEPS.get_or_init(Default::default),
1228    }
1229  }
1230}
1231
1232static EMPTY_DEPS: std::sync::OnceLock<IndexMap<String, Dependency>> =
1233  std::sync::OnceLock::new();
1234
1235/// An npm package entrypoint.
1236#[derive(Debug, Clone, Serialize)]
1237#[serde(rename_all = "camelCase")]
1238pub struct NpmModule {
1239  pub specifier: ModuleSpecifier,
1240  #[serde(skip_serializing)]
1241  pub nv_reference: NpmPackageNvReference,
1242}
1243
1244/// Represents a module which is not statically analyzed and is only available
1245/// at runtime. It is up to the implementor to ensure that the module is
1246/// loaded and available as a dependency. The module does not contain source
1247/// code and will have no dependencies.
1248#[derive(Debug, Clone, Serialize)]
1249#[serde(rename_all = "camelCase")]
1250pub struct ExternalModule {
1251  pub specifier: ModuleSpecifier,
1252  #[serde(flatten, skip_serializing_if = "Option::is_none")]
1253  pub maybe_cache_info: Option<CacheInfo>,
1254  /// If this external module was the result of loading a "bytes" or "text" import.
1255  #[serde(skip_serializing)]
1256  pub was_asset_load: bool,
1257}
1258
1259#[derive(Debug, Clone, Serialize)]
1260#[serde(rename_all = "camelCase")]
1261pub struct BuiltInNodeModule {
1262  /// Specifier (ex. "node:fs")
1263  pub specifier: ModuleSpecifier,
1264  /// Module name (ex. "fs")
1265  pub module_name: String,
1266}
1267
1268#[derive(Debug, Clone, Serialize)]
1269#[serde(rename_all = "camelCase")]
1270pub struct JsonModule {
1271  pub specifier: ModuleSpecifier,
1272  #[serde(flatten, skip_serializing_if = "Option::is_none")]
1273  pub maybe_cache_info: Option<CacheInfo>,
1274  #[serde(rename = "size", serialize_with = "serialize_source")]
1275  pub source: ModuleTextSource,
1276  #[serde(skip_serializing)]
1277  pub mtime: Option<SystemTime>,
1278  // todo(#240): This will always be MediaType::Json, but it's currently
1279  // used in the --json output. It's redundant though.
1280  pub media_type: MediaType,
1281}
1282
1283impl JsonModule {
1284  /// Return the size in bytes of the content of the JSON module.
1285  pub fn size(&self) -> usize {
1286    self.source.text.len()
1287  }
1288}
1289
1290#[cfg(feature = "fast_check")]
1291#[derive(Debug, Clone)]
1292pub enum FastCheckTypeModuleSlot {
1293  Module(Box<FastCheckTypeModule>),
1294  Error(Vec<crate::fast_check::FastCheckDiagnostic>),
1295}
1296
1297#[cfg(feature = "fast_check")]
1298#[derive(Debug, Clone)]
1299pub struct FastCheckTypeModule {
1300  pub dependencies: IndexMap<String, Dependency>,
1301  pub source: Arc<str>,
1302  pub source_map: Arc<str>,
1303  pub dts: Option<crate::fast_check::FastCheckDtsModule>,
1304}
1305
1306#[derive(Debug, Clone)]
1307pub struct ModuleTextSource {
1308  /// How the source was decoded. This is useful to check when getting the bytes of
1309  /// the module for bytes imports in order to tell if we can just use the original
1310  /// source.
1311  pub text: Arc<str>,
1312  pub decoded_kind: DecodedArcSourceDetailKind,
1313}
1314
1315impl ModuleTextSource {
1316  pub fn new_unknown(text: Arc<str>) -> Self {
1317    Self {
1318      text,
1319      // force loading bytes imports for the file system because we don't
1320      // know what the original source was
1321      decoded_kind: DecodedArcSourceDetailKind::Changed,
1322    }
1323  }
1324
1325  /// Tries to get the original bytes of the module. If this returns `None`
1326  /// then the bytes need to be loaded from the cache instead of from memory.
1327  pub fn try_get_original_bytes(&self) -> Option<Arc<[u8]>> {
1328    match self.decoded_kind {
1329      DecodedArcSourceDetailKind::Unchanged => unsafe {
1330        let raw_ptr = Arc::into_raw(self.text.clone());
1331        Some(Arc::from_raw(
1332          std::mem::transmute::<*const str, *const [u8]>(raw_ptr),
1333        ))
1334      },
1335      DecodedArcSourceDetailKind::Changed => None,
1336      DecodedArcSourceDetailKind::OnlyUtf8Bom => {
1337        // add back the BOM
1338        let mut bytes =
1339          Vec::with_capacity(self.text.len() + BOM_CHAR.len_utf8());
1340        bytes.extend([0xEF, 0xBB, 0xBF]);
1341        bytes.extend(self.text.as_bytes());
1342        Some(Arc::from(bytes))
1343      }
1344    }
1345  }
1346}
1347
1348#[derive(Debug, Clone, Serialize)]
1349#[serde(rename_all = "camelCase")]
1350pub struct JsModule {
1351  #[serde(skip_serializing)]
1352  pub is_script: bool,
1353  #[serde(
1354    skip_serializing_if = "IndexMap::is_empty",
1355    serialize_with = "serialize_dependencies"
1356  )]
1357  pub dependencies: IndexMap<String, Dependency>,
1358  #[serde(flatten, skip_serializing_if = "Option::is_none")]
1359  pub maybe_cache_info: Option<CacheInfo>,
1360  #[serde(skip_serializing)]
1361  pub mtime: Option<SystemTime>,
1362  #[serde(rename = "size", serialize_with = "serialize_source")]
1363  pub source: ModuleTextSource,
1364  #[serde(rename = "typesDependency", skip_serializing_if = "Option::is_none")]
1365  pub maybe_types_dependency: Option<TypesDependency>,
1366  #[serde(skip_serializing_if = "is_media_type_unknown")]
1367  pub media_type: MediaType,
1368  pub specifier: ModuleSpecifier,
1369  #[cfg(feature = "fast_check")]
1370  #[serde(skip_serializing)]
1371  pub fast_check: Option<FastCheckTypeModuleSlot>,
1372}
1373
1374impl JsModule {
1375  /// Return the size in bytes of the content of the module.
1376  pub fn size(&self) -> usize {
1377    self.source.text.len()
1378  }
1379
1380  #[cfg(feature = "fast_check")]
1381  pub fn fast_check_diagnostics(
1382    &self,
1383  ) -> Option<&Vec<crate::fast_check::FastCheckDiagnostic>> {
1384    let module_slot = self.fast_check.as_ref()?;
1385    match module_slot {
1386      FastCheckTypeModuleSlot::Module(_) => None,
1387      FastCheckTypeModuleSlot::Error(d) => Some(d),
1388    }
1389  }
1390
1391  #[cfg(feature = "fast_check")]
1392  pub fn fast_check_module(&self) -> Option<&FastCheckTypeModule> {
1393    let module_slot = self.fast_check.as_ref()?;
1394    match module_slot {
1395      FastCheckTypeModuleSlot::Module(m) => Some(m),
1396      FastCheckTypeModuleSlot::Error(_) => None,
1397    }
1398  }
1399
1400  pub fn dependencies_prefer_fast_check(
1401    &self,
1402  ) -> &IndexMap<String, Dependency> {
1403    #[cfg(feature = "fast_check")]
1404    {
1405      match self.fast_check_module() {
1406        Some(fast_check) => &fast_check.dependencies,
1407        None => &self.dependencies,
1408      }
1409    }
1410    #[cfg(not(feature = "fast_check"))]
1411    &self.dependencies
1412  }
1413}
1414
1415#[derive(Debug, Clone, Serialize)]
1416#[serde(rename_all = "camelCase")]
1417pub struct WasmModule {
1418  pub specifier: ModuleSpecifier,
1419  #[serde(skip_serializing)]
1420  pub mtime: Option<SystemTime>,
1421  #[serde(
1422    skip_serializing_if = "IndexMap::is_empty",
1423    serialize_with = "serialize_dependencies"
1424  )]
1425  pub dependencies: IndexMap<String, Dependency>,
1426  #[serde(rename = "size", serialize_with = "serialize_source_bytes")]
1427  pub source: Arc<[u8]>,
1428  #[serde(skip_serializing)]
1429  pub source_dts: Arc<str>,
1430  #[serde(flatten, skip_serializing_if = "Option::is_none")]
1431  pub maybe_cache_info: Option<CacheInfo>,
1432}
1433
1434impl WasmModule {
1435  /// Return the size in bytes of the content of the module.
1436  pub fn size(&self) -> usize {
1437    self.source.len()
1438  }
1439}
1440
1441#[allow(clippy::large_enum_variant)]
1442#[derive(Debug, Clone)]
1443pub(crate) enum ModuleSlot {
1444  /// A module, with source code.
1445  Module(Module),
1446  /// When trying to load or parse the module, an error occurred.
1447  Err(ModuleError),
1448  /// An internal state set when loading a module asynchronously.
1449  Pending {
1450    /// If loading an asset, which won't load the content into memory.
1451    is_asset: bool,
1452  },
1453}
1454
1455impl ModuleSlot {
1456  #[cfg(test)]
1457  pub fn module(&self) -> Option<&Module> {
1458    if let ModuleSlot::Module(module) = self {
1459      Some(module)
1460    } else {
1461      None
1462    }
1463  }
1464
1465  pub fn is_pending(&self) -> bool {
1466    matches!(self, ModuleSlot::Pending { .. })
1467  }
1468
1469  pub fn was_external_asset_load(&self) -> bool {
1470    matches!(
1471      self,
1472      ModuleSlot::Module(Module::External(ExternalModule {
1473        was_asset_load: true,
1474        ..
1475      }))
1476    )
1477  }
1478
1479  pub fn is_pending_asset_load(&self) -> bool {
1480    matches!(self, ModuleSlot::Pending { is_asset: true })
1481  }
1482
1483  fn as_err_kind(&self) -> Option<&ModuleErrorKind> {
1484    match self {
1485      ModuleSlot::Err(err) => Some(err.as_kind()),
1486      _ => None,
1487    }
1488  }
1489}
1490
1491type ModuleResult<'a> =
1492  (&'a ModuleSpecifier, Result<&'a Module, &'a ModuleError>);
1493
1494/// Convert a module slot entry into a result which contains the resolved
1495/// module specifier, module kind, and media type or the module graph error.
1496fn to_result<'a>(
1497  (specifier, module_slot): (&'a ModuleSpecifier, &'a ModuleSlot),
1498) -> Option<ModuleResult<'a>> {
1499  match module_slot {
1500    ModuleSlot::Err(err) => Some((specifier, Err(err))),
1501    ModuleSlot::Module(module) => Some((specifier, Ok(module))),
1502    ModuleSlot::Pending { .. } => None,
1503  }
1504}
1505
1506/// Provides a way for imports, through configuration, to be imported to the
1507/// module graph without requiring the dependencies to be analyzed. This is
1508/// intended to be used for importing type dependencies or other externally
1509/// defined dependencies, like JSX runtimes.
1510#[derive(Debug, Clone, Serialize)]
1511pub struct GraphImport {
1512  /// A map of resolved dependencies, where the key is the value originally
1513  /// provided for the import and the value is the resolved dependency.
1514  #[serde(serialize_with = "serialize_dependencies")]
1515  pub dependencies: IndexMap<String, Dependency>,
1516}
1517
1518impl GraphImport {
1519  pub fn new(
1520    referrer: &ModuleSpecifier,
1521    imports: Vec<String>,
1522    jsr_url_provider: &dyn JsrUrlProvider,
1523    maybe_resolver: Option<&dyn Resolver>,
1524  ) -> Self {
1525    let dependencies = imports
1526      .into_iter()
1527      .map(|import| {
1528        let referrer_range = Range {
1529          specifier: referrer.clone(),
1530          range: PositionRange::zeroed(),
1531          resolution_mode: None,
1532        };
1533        let maybe_type = resolve(
1534          &import,
1535          referrer_range,
1536          ResolutionKind::Types,
1537          jsr_url_provider,
1538          maybe_resolver,
1539        );
1540        (
1541          import,
1542          Dependency {
1543            is_dynamic: false,
1544            maybe_code: Resolution::None,
1545            maybe_type,
1546            maybe_deno_types_specifier: None,
1547            maybe_attribute_type: None,
1548            imports: vec![],
1549          },
1550        )
1551      })
1552      .collect();
1553    Self { dependencies }
1554  }
1555}
1556
1557#[cfg(feature = "fast_check")]
1558#[derive(Debug, Default)]
1559pub enum WorkspaceFastCheckOption<'a> {
1560  #[default]
1561  Disabled,
1562  Enabled(&'a [WorkspaceMember]),
1563}
1564
1565#[cfg(feature = "fast_check")]
1566#[derive(Default)]
1567pub struct BuildFastCheckTypeGraphOptions<'a> {
1568  pub fast_check_cache: Option<&'a dyn crate::fast_check::FastCheckCache>,
1569  pub fast_check_dts: bool,
1570  pub jsr_url_provider: &'a dyn JsrUrlProvider,
1571  pub es_parser: Option<&'a dyn crate::ast::EsParser>,
1572  pub resolver: Option<&'a dyn Resolver>,
1573  /// Whether to fill workspace members with fast check TypeScript data.
1574  pub workspace_fast_check: WorkspaceFastCheckOption<'a>,
1575}
1576
1577pub struct BuildOptions<'a> {
1578  pub is_dynamic: bool,
1579  /// Skip loading statically analyzable dynamic dependencies.
1580  pub skip_dynamic_deps: bool,
1581  /// Support unstable bytes imports.
1582  pub unstable_bytes_imports: bool,
1583  /// Support unstable text imports.
1584  pub unstable_text_imports: bool,
1585  pub executor: &'a dyn Executor,
1586  pub locker: Option<&'a mut dyn Locker>,
1587  pub file_system: &'a FileSystem,
1588  pub jsr_url_provider: &'a dyn JsrUrlProvider,
1589  pub jsr_version_resolver: Cow<'a, JsrVersionResolver>,
1590  /// Whether to pass through JSR specifiers to the resolver instead of
1591  /// resolving them. This is useful in cases where you want to mark JSR
1592  /// specifiers as external.
1593  pub passthrough_jsr_specifiers: bool,
1594  pub module_analyzer: &'a dyn ModuleAnalyzer,
1595  pub module_info_cacher: &'a dyn ModuleInfoCacher,
1596  pub npm_resolver: Option<&'a dyn NpmResolver>,
1597  pub reporter: Option<&'a dyn Reporter>,
1598  pub resolver: Option<&'a dyn Resolver>,
1599  pub jsr_metadata_store: Option<Rc<JsrMetadataStore>>,
1600}
1601
1602impl Default for BuildOptions<'_> {
1603  fn default() -> Self {
1604    Self {
1605      is_dynamic: false,
1606      skip_dynamic_deps: false,
1607      unstable_bytes_imports: false,
1608      unstable_text_imports: false,
1609      executor: Default::default(),
1610      locker: None,
1611      file_system: &NullFileSystem,
1612      jsr_url_provider: Default::default(),
1613      jsr_version_resolver: Default::default(),
1614      passthrough_jsr_specifiers: false,
1615      module_analyzer: Default::default(),
1616      module_info_cacher: Default::default(),
1617      npm_resolver: None,
1618      reporter: None,
1619      resolver: None,
1620      jsr_metadata_store: None,
1621    }
1622  }
1623}
1624
1625#[derive(Debug, Copy, Clone)]
1626pub enum ModuleEntryRef<'a> {
1627  Module(&'a Module),
1628  Err(&'a ModuleError),
1629  Redirect(&'a ModuleSpecifier),
1630}
1631
1632pub trait CheckJsResolver: std::fmt::Debug {
1633  fn resolve(&self, specifier: &ModuleSpecifier) -> bool;
1634}
1635
1636#[derive(Debug, Clone, Copy)]
1637pub enum CheckJsOption<'a> {
1638  True,
1639  False,
1640  Custom(&'a dyn CheckJsResolver),
1641}
1642
1643impl CheckJsOption<'_> {
1644  pub fn resolve(&self, specifier: &ModuleSpecifier) -> bool {
1645    match self {
1646      CheckJsOption::True => true,
1647      CheckJsOption::False => false,
1648      CheckJsOption::Custom(check_js_resolver) => {
1649        check_js_resolver.resolve(specifier)
1650      }
1651    }
1652  }
1653}
1654
1655#[derive(Debug, Clone)]
1656pub struct WalkOptions<'a> {
1657  /// Whether to walk js modules when `kind` is `GraphKind::TypesOnly`.
1658  pub check_js: CheckJsOption<'a>,
1659  pub follow_dynamic: bool,
1660  /// Part of the graph to walk.
1661  pub kind: GraphKind,
1662  /// If the fast check module graph should be preferred
1663  /// to walk over walking all modules.
1664  ///
1665  /// For example, when this encounters a package with fast
1666  /// check modules, then it will only walk the fast checked
1667  /// modules and not the rest of the graph.
1668  pub prefer_fast_check_graph: bool,
1669}
1670
1671pub struct FillFromLockfileOptions<
1672  'a,
1673  TRedirectIter: Iterator<Item = (&'a str, &'a str)>,
1674  TPackageSpecifiersIter: Iterator<Item = (&'a JsrDepPackageReq, &'a str)>,
1675> {
1676  pub redirects: TRedirectIter,
1677  pub package_specifiers: TPackageSpecifiersIter,
1678}
1679
1680pub struct ModuleEntryIterator<'a, 'options> {
1681  graph: &'a ModuleGraph,
1682  seen: HashSet<&'a ModuleSpecifier>,
1683  visiting: VecDeque<&'a ModuleSpecifier>,
1684  follow_dynamic: bool,
1685  kind: GraphKind,
1686  check_js: CheckJsOption<'options>,
1687  prefer_fast_check_graph: bool,
1688  previous_module: Option<ModuleEntryRef<'a>>,
1689}
1690
1691impl<'a, 'options> ModuleEntryIterator<'a, 'options> {
1692  fn new(
1693    graph: &'a ModuleGraph,
1694    roots: impl Iterator<Item = &'a ModuleSpecifier>,
1695    options: WalkOptions<'options>,
1696  ) -> Self {
1697    let mut seen =
1698      HashSet::<&'a ModuleSpecifier>::with_capacity(graph.specifiers_count());
1699    let mut visiting = VecDeque::<&'a ModuleSpecifier>::new();
1700    for root in roots {
1701      seen.insert(root);
1702      visiting.push_back(root);
1703    }
1704    for (_, dep) in graph.imports.values().flat_map(|i| &i.dependencies) {
1705      let mut resolutions = Vec::with_capacity(2);
1706      resolutions.push(&dep.maybe_code);
1707      if options.kind.include_types() {
1708        resolutions.push(&dep.maybe_type);
1709      }
1710      #[allow(clippy::manual_flatten)]
1711      for resolution in resolutions {
1712        if let Resolution::Ok(resolved) = resolution {
1713          let specifier = &resolved.specifier;
1714          if seen.insert(specifier) {
1715            visiting.push_front(specifier);
1716          }
1717        }
1718      }
1719    }
1720
1721    Self {
1722      graph,
1723      seen,
1724      visiting,
1725      follow_dynamic: options.follow_dynamic,
1726      kind: options.kind,
1727      check_js: options.check_js,
1728      prefer_fast_check_graph: options.prefer_fast_check_graph,
1729      previous_module: None,
1730    }
1731  }
1732
1733  /// Skips analyzing the dependencies of the previously returned module.
1734  pub fn skip_previous_dependencies(&mut self) {
1735    self.previous_module = None;
1736  }
1737
1738  /// An iterator over all the errors found when walking this iterator.
1739  ///
1740  /// This can be useful in scenarios where you want to filter or ignore an error.
1741  pub fn errors(self) -> ModuleGraphErrorIterator<'a, 'options> {
1742    ModuleGraphErrorIterator::new(self)
1743  }
1744
1745  /// Consumes the iterator validating all the items for any resolution
1746  /// or module graph errors.
1747  ///
1748  /// This is different than calling `.valid()` on a module graph because
1749  /// it only applies to the roots filtered by the iterator with the provided
1750  /// options.
1751  #[allow(clippy::result_large_err)]
1752  pub fn validate(self) -> Result<(), ModuleGraphError> {
1753    if let Some(err) = self.errors().next() {
1754      Err(err)
1755    } else {
1756      Ok(())
1757    }
1758  }
1759
1760  /// Gets if the specified media type can be type checked.
1761  fn is_checkable(
1762    &self,
1763    specifier: &ModuleSpecifier,
1764    media_type: MediaType,
1765  ) -> bool {
1766    match media_type {
1767      MediaType::TypeScript
1768      | MediaType::Mts
1769      | MediaType::Cts
1770      | MediaType::Dts
1771      | MediaType::Dmts
1772      | MediaType::Dcts
1773      | MediaType::Tsx
1774      | MediaType::Json
1775      | MediaType::Wasm => true,
1776      MediaType::Css
1777      | MediaType::SourceMap
1778      | MediaType::Html
1779      | MediaType::Sql
1780      | MediaType::Unknown => false,
1781      MediaType::JavaScript
1782      | MediaType::Jsx
1783      | MediaType::Mjs
1784      | MediaType::Cjs => self.check_js.resolve(specifier),
1785    }
1786  }
1787
1788  fn analyze_module_deps(
1789    &mut self,
1790    module_deps: &'a IndexMap<String, Dependency>,
1791  ) {
1792    for dep in module_deps.values().rev() {
1793      if !dep.is_dynamic || self.follow_dynamic {
1794        let mut resolutions = Vec::with_capacity(2);
1795        resolutions.push(&dep.maybe_code);
1796        if self.kind.include_types() {
1797          resolutions.push(&dep.maybe_type);
1798        }
1799        #[allow(clippy::manual_flatten)]
1800        for resolution in resolutions {
1801          if let Resolution::Ok(resolved) = resolution {
1802            let specifier = &resolved.specifier;
1803            if self.seen.insert(specifier) {
1804              self.visiting.push_front(specifier);
1805            }
1806          }
1807        }
1808      }
1809    }
1810  }
1811}
1812
1813impl<'a> Iterator for ModuleEntryIterator<'a, '_> {
1814  type Item = (&'a ModuleSpecifier, ModuleEntryRef<'a>);
1815
1816  fn next(&mut self) -> Option<Self::Item> {
1817    match self.previous_module.take() {
1818      Some(ModuleEntryRef::Module(module)) => {
1819        let check_types = self.kind.include_types()
1820          && self.is_checkable(module.specifier(), module.media_type());
1821        let module_deps = if check_types && self.prefer_fast_check_graph {
1822          module.dependencies_prefer_fast_check()
1823        } else {
1824          module.dependencies()
1825        };
1826        self.analyze_module_deps(module_deps);
1827      }
1828      Some(ModuleEntryRef::Redirect(specifier)) => {
1829        if self.seen.insert(specifier) {
1830          self.visiting.push_front(specifier);
1831        }
1832      }
1833      Some(ModuleEntryRef::Err(_)) | None => {}
1834    }
1835
1836    let (specifier, module_entry) = loop {
1837      let specifier = self.visiting.pop_front()?;
1838      match self.graph.module_slots.get_key_value(specifier) {
1839        Some((specifier, module_slot)) => {
1840          match module_slot {
1841            ModuleSlot::Pending { .. } => {
1842              // ignore
1843            }
1844            ModuleSlot::Module(module) => {
1845              if let Module::Js(module) = &module {
1846                if self.kind.include_types() {
1847                  if let Some(Resolution::Ok(resolved)) = module
1848                    .maybe_types_dependency
1849                    .as_ref()
1850                    .map(|d| &d.dependency)
1851                  {
1852                    let specifier = &resolved.specifier;
1853                    if self.seen.insert(specifier) {
1854                      self.visiting.push_front(specifier);
1855                    }
1856                    if self.kind == GraphKind::TypesOnly {
1857                      continue; // skip visiting the code module
1858                    }
1859                  } else if self.kind == GraphKind::TypesOnly
1860                    && !self.is_checkable(&module.specifier, module.media_type)
1861                  {
1862                    continue; // skip visiting
1863                  }
1864                }
1865              }
1866              break (specifier, ModuleEntryRef::Module(module));
1867            }
1868            ModuleSlot::Err(err) => {
1869              break (specifier, ModuleEntryRef::Err(err))
1870            }
1871          }
1872        }
1873        None => {
1874          if let Some((specifier, to)) =
1875            self.graph.redirects.get_key_value(specifier)
1876          {
1877            break (specifier, ModuleEntryRef::Redirect(to));
1878          }
1879        }
1880      }
1881    };
1882
1883    self.previous_module = Some(module_entry);
1884
1885    Some((specifier, module_entry))
1886  }
1887}
1888
1889pub struct ModuleGraphErrorIterator<'a, 'options> {
1890  iterator: ModuleEntryIterator<'a, 'options>,
1891  next_errors: Vec<ModuleGraphError>,
1892}
1893
1894impl<'a, 'options> ModuleGraphErrorIterator<'a, 'options> {
1895  pub fn new(iterator: ModuleEntryIterator<'a, 'options>) -> Self {
1896    Self {
1897      iterator,
1898      next_errors: Default::default(),
1899    }
1900  }
1901
1902  fn check_resolution(
1903    &self,
1904    module: &Module,
1905    kind: ResolutionKind,
1906    specifier_text: &str,
1907    resolution: &Resolution,
1908    is_dynamic: bool,
1909  ) -> Option<ModuleGraphError> {
1910    match resolution {
1911      Resolution::Ok(resolved) => {
1912        let referrer_scheme = module.specifier().scheme();
1913        let specifier_scheme = resolved.specifier.scheme();
1914        if referrer_scheme == "https" && specifier_scheme == "http" {
1915          Some(ModuleGraphError::for_resolution_kind(
1916            kind,
1917            ResolutionError::InvalidDowngrade {
1918              specifier: resolved.specifier.clone(),
1919              range: resolved.range.clone(),
1920            },
1921          ))
1922        } else if matches!(referrer_scheme, "https" | "http")
1923          && matches!(specifier_scheme, "file")
1924          && specifier_text.to_lowercase().starts_with("file://")
1925        {
1926          Some(ModuleGraphError::for_resolution_kind(
1927            kind,
1928            ResolutionError::InvalidLocalImport {
1929              specifier: resolved.specifier.clone(),
1930              range: resolved.range.clone(),
1931            },
1932          ))
1933        } else if self.iterator.follow_dynamic {
1934          let resolved_specifier =
1935            self.iterator.graph.resolve(&resolved.specifier);
1936          let module_slot =
1937            self.iterator.graph.module_slots.get(resolved_specifier);
1938          if let Some(ModuleErrorKind::Missing {
1939            specifier,
1940            maybe_referrer,
1941          }) = module_slot.and_then(|m| m.as_err_kind())
1942          {
1943            // we want to surface module missing errors as dynamic missing errors
1944            if is_dynamic {
1945              Some(ModuleGraphError::ModuleError(
1946                ModuleErrorKind::MissingDynamic {
1947                  specifier: specifier.clone(),
1948                  referrer: resolved.range.clone(),
1949                }
1950                .into_box(),
1951              ))
1952            } else {
1953              Some(ModuleGraphError::ModuleError(
1954                ModuleErrorKind::Missing {
1955                  specifier: specifier.clone(),
1956                  maybe_referrer: maybe_referrer.clone(),
1957                }
1958                .into_box(),
1959              ))
1960            }
1961          } else {
1962            None
1963          }
1964        } else {
1965          None
1966        }
1967      }
1968      Resolution::Err(err) => {
1969        Some(ModuleGraphError::for_resolution_kind(kind, *err.clone()))
1970      }
1971      Resolution::None => None,
1972    }
1973  }
1974}
1975
1976impl Iterator for ModuleGraphErrorIterator<'_, '_> {
1977  type Item = ModuleGraphError;
1978
1979  fn next(&mut self) -> Option<Self::Item> {
1980    while self.next_errors.is_empty() {
1981      let kind = self.iterator.kind;
1982      let follow_dynamic = self.iterator.follow_dynamic;
1983      let prefer_fast_check_graph = self.iterator.prefer_fast_check_graph;
1984
1985      if let Some((_, module_entry)) = self.iterator.next() {
1986        match module_entry {
1987          ModuleEntryRef::Module(module) => {
1988            if kind.include_types() {
1989              if let Some(dep) = module.maybe_types_dependency().as_ref() {
1990                if let Some(err) = self.check_resolution(
1991                  module,
1992                  ResolutionKind::Types,
1993                  &dep.specifier,
1994                  &dep.dependency,
1995                  false,
1996                ) {
1997                  self.next_errors.push(err);
1998                }
1999              }
2000            }
2001
2002            let check_types = kind.include_types()
2003              && self
2004                .iterator
2005                .is_checkable(module.specifier(), module.media_type());
2006            let module_deps = if check_types && prefer_fast_check_graph {
2007              module.dependencies_prefer_fast_check()
2008            } else {
2009              module.dependencies()
2010            };
2011            for (specifier_text, dep) in module_deps {
2012              if follow_dynamic || !dep.is_dynamic {
2013                if let Some(err) = self.check_resolution(
2014                  module,
2015                  ResolutionKind::Execution,
2016                  specifier_text,
2017                  &dep.maybe_code,
2018                  dep.is_dynamic,
2019                ) {
2020                  self.next_errors.push(err);
2021                }
2022                if check_types {
2023                  if let Some(err) = self.check_resolution(
2024                    module,
2025                    ResolutionKind::Types,
2026                    specifier_text,
2027                    &dep.maybe_type,
2028                    dep.is_dynamic,
2029                  ) {
2030                    self.next_errors.push(err);
2031                  }
2032                }
2033              }
2034            }
2035          }
2036          ModuleEntryRef::Err(error) => {
2037            // ignore missing modules when following dynamic imports
2038            // because they will be resolved in place
2039            let should_ignore = follow_dynamic
2040              && matches!(error.as_kind(), ModuleErrorKind::Missing { .. });
2041            if !should_ignore {
2042              self
2043                .next_errors
2044                .push(ModuleGraphError::ModuleError(error.clone()));
2045            }
2046          }
2047          ModuleEntryRef::Redirect(_) => {
2048            // do nothing
2049          }
2050        }
2051      } else {
2052        break; // no more modules, stop searching
2053      }
2054    }
2055
2056    self.next_errors.pop()
2057  }
2058}
2059
2060/// The structure which represents a module graph, which can be serialized as
2061/// well as "printed". The roots of the graph represent the "starting" point
2062/// which can be located in the module "slots" in the graph. The graph also
2063/// contains any redirects where the requested module specifier was redirected
2064/// to another module specifier when being loaded.
2065#[derive(Debug, Clone, Serialize)]
2066pub struct ModuleGraph {
2067  #[serde(skip_serializing)]
2068  graph_kind: GraphKind,
2069  pub roots: IndexSet<ModuleSpecifier>,
2070  #[serde(rename = "modules")]
2071  #[serde(serialize_with = "serialize_module_slots")]
2072  pub(crate) module_slots: BTreeMap<ModuleSpecifier, ModuleSlot>,
2073  #[serde(skip_serializing_if = "IndexMap::is_empty")]
2074  #[serde(serialize_with = "serialize_graph_imports")]
2075  pub imports: IndexMap<ModuleSpecifier, GraphImport>,
2076  pub redirects: BTreeMap<ModuleSpecifier, ModuleSpecifier>,
2077  #[serde(skip_serializing)]
2078  pub npm_packages: IndexSet<PackageNv>,
2079  #[serde(skip_serializing)]
2080  pub has_node_specifier: bool,
2081  #[serde(rename = "packages")]
2082  #[serde(skip_serializing_if = "PackageSpecifiers::is_empty")]
2083  pub packages: PackageSpecifiers,
2084  /// The result of resolving all npm dependencies of non-dynamic
2085  /// npm specifiers in the graph.
2086  #[serde(skip_serializing)]
2087  pub npm_dep_graph_result: Result<(), Arc<dyn JsErrorClass>>,
2088}
2089
2090impl ModuleGraph {
2091  pub fn new(graph_kind: GraphKind) -> Self {
2092    Self {
2093      graph_kind,
2094      roots: Default::default(),
2095      module_slots: Default::default(),
2096      imports: Default::default(),
2097      redirects: Default::default(),
2098      npm_packages: Default::default(),
2099      has_node_specifier: false,
2100      packages: Default::default(),
2101      npm_dep_graph_result: Ok(()),
2102    }
2103  }
2104
2105  pub fn graph_kind(&self) -> GraphKind {
2106    self.graph_kind
2107  }
2108
2109  /// Fills the upfront information (redirects and JSR specifiers) from
2110  /// the lockfile into the graph.
2111  pub fn fill_from_lockfile<
2112    'a,
2113    TRedirectIter: Iterator<Item = (&'a str, &'a str)>,
2114    TPackageSpecifiersIter: Iterator<Item = (&'a JsrDepPackageReq, &'a str)>,
2115  >(
2116    &mut self,
2117    options: FillFromLockfileOptions<'a, TRedirectIter, TPackageSpecifiersIter>,
2118  ) {
2119    for (from, to) in options.redirects {
2120      if let Ok(from) = ModuleSpecifier::parse(from) {
2121        if let Ok(to) = ModuleSpecifier::parse(to) {
2122          if !matches!(from.scheme(), "file" | "npm" | "jsr") {
2123            self.redirects.insert(from, to);
2124          }
2125        }
2126      }
2127    }
2128    for (req_dep, value) in options.package_specifiers {
2129      match req_dep.kind {
2130        deno_semver::package::PackageKind::Jsr => {
2131          if let Ok(version) = Version::parse_standard(value) {
2132            self.packages.add_nv(
2133              req_dep.req.clone(),
2134              PackageNv {
2135                name: req_dep.req.name.clone(),
2136                version,
2137              },
2138            );
2139          }
2140        }
2141        deno_semver::package::PackageKind::Npm => {
2142          // ignore
2143        }
2144      }
2145    }
2146  }
2147
2148  pub async fn build<'a>(
2149    &mut self,
2150    roots: Vec<ModuleSpecifier>,
2151    imports: Vec<ReferrerImports>,
2152    loader: &'a dyn Loader,
2153    options: BuildOptions<'a>,
2154  ) {
2155    Builder::new(self, loader, options)
2156      .build(roots, imports)
2157      .await
2158  }
2159
2160  pub async fn reload<'a>(
2161    &mut self,
2162    specifiers: Vec<ModuleSpecifier>,
2163    loader: &'a dyn Loader,
2164    options: BuildOptions<'a>,
2165  ) {
2166    Builder::new(self, loader, options).reload(specifiers).await
2167  }
2168
2169  #[cfg(feature = "fast_check")]
2170  pub fn build_fast_check_type_graph(
2171    &mut self,
2172    options: BuildFastCheckTypeGraphOptions,
2173  ) {
2174    if !self.graph_kind().include_types() {
2175      return;
2176    }
2177
2178    let mut pending_nvs = self
2179      .packages
2180      .top_level_packages()
2181      .iter()
2182      .cloned()
2183      .collect::<VecDeque<_>>();
2184    if let WorkspaceFastCheckOption::Enabled(workspace_members) =
2185      options.workspace_fast_check
2186    {
2187      pending_nvs.extend(workspace_members.iter().map(|n| n.as_nv()));
2188    }
2189    if pending_nvs.is_empty() {
2190      return;
2191    }
2192
2193    let default_es_parser = crate::ast::CapturingModuleAnalyzer::default();
2194    let root_symbol = crate::symbols::RootSymbol::new(
2195      self,
2196      options.es_parser.unwrap_or(&default_es_parser),
2197    );
2198
2199    let modules = crate::fast_check::build_fast_check_type_graph(
2200      options.fast_check_cache,
2201      options.jsr_url_provider,
2202      self,
2203      &root_symbol,
2204      pending_nvs,
2205      &crate::fast_check::TransformOptions {
2206        workspace_members: match options.workspace_fast_check {
2207          WorkspaceFastCheckOption::Disabled => &[],
2208          WorkspaceFastCheckOption::Enabled(members) => members,
2209        },
2210        should_error_on_first_diagnostic: match options.workspace_fast_check {
2211          WorkspaceFastCheckOption::Disabled => true,
2212          WorkspaceFastCheckOption::Enabled(_) => false,
2213        },
2214        dts: options.fast_check_dts,
2215      },
2216    );
2217    for (specifier, fast_check_module_result) in modules {
2218      let module_slot = self.module_slots.get_mut(&specifier).unwrap();
2219      let module = match module_slot {
2220        ModuleSlot::Module(m) => match m {
2221          Module::Js(m) => m,
2222          _ => continue,
2223        },
2224        ModuleSlot::Err(_) | ModuleSlot::Pending { .. } => continue,
2225      };
2226      module.fast_check = Some(match fast_check_module_result {
2227        Ok(fast_check_module) => {
2228          let mut dependencies: IndexMap<String, Dependency> =
2229            Default::default();
2230          fill_module_dependencies(
2231            GraphKind::TypesOnly,
2232            module.media_type,
2233            match Arc::try_unwrap(fast_check_module.module_info) {
2234              Ok(module_info) => module_info.dependencies,
2235              Err(module_info) => module_info.dependencies.clone(),
2236            },
2237            &module.specifier,
2238            &mut dependencies,
2239            // no need to resolve dynamic imports
2240            &NullFileSystem,
2241            options.jsr_url_provider,
2242            options.resolver,
2243          );
2244          FastCheckTypeModuleSlot::Module(Box::new(FastCheckTypeModule {
2245            dependencies,
2246            source: fast_check_module.text,
2247            source_map: fast_check_module.source_map,
2248            dts: fast_check_module.dts,
2249          }))
2250        }
2251        Err(diagnostic) => FastCheckTypeModuleSlot::Error(diagnostic),
2252      });
2253    }
2254  }
2255
2256  /// Creates a new cloned module graph from the provided roots.
2257  pub fn segment(&self, roots: &[ModuleSpecifier]) -> Self {
2258    let roots = roots.iter().collect::<IndexSet<_>>();
2259    if roots.iter().all(|r| self.roots.contains(*r)) {
2260      // perf - do a straight clone since the roots are the same
2261      return self.clone();
2262    }
2263
2264    let mut new_graph = ModuleGraph::new(self.graph_kind);
2265    let entries = self.walk(
2266      roots.iter().copied(),
2267      WalkOptions {
2268        follow_dynamic: true,
2269        kind: self.graph_kind,
2270        check_js: CheckJsOption::True,
2271        prefer_fast_check_graph: false,
2272      },
2273    );
2274
2275    for (specifier, module_entry) in entries {
2276      match module_entry {
2277        ModuleEntryRef::Module(module) => {
2278          new_graph
2279            .module_slots
2280            .insert(specifier.clone(), ModuleSlot::Module(module.clone()));
2281        }
2282        ModuleEntryRef::Err(err) => {
2283          new_graph
2284            .module_slots
2285            .insert(specifier.clone(), ModuleSlot::Err(err.clone()));
2286        }
2287        ModuleEntryRef::Redirect(specifier_to) => {
2288          new_graph
2289            .redirects
2290            .insert(specifier.clone(), specifier_to.clone());
2291        }
2292      }
2293    }
2294    new_graph.imports.clone_from(&self.imports);
2295    new_graph.roots = roots.iter().map(|r| (*r).to_owned()).collect();
2296    new_graph.npm_packages.clone_from(&self.npm_packages);
2297    // todo(dsherret): it should be a bit smarter about this, but this is not terrible
2298    new_graph.packages.clone_from(&self.packages);
2299    new_graph.has_node_specifier = self.has_node_specifier;
2300
2301    new_graph
2302  }
2303
2304  /// Remove TypeScript from the module graph.
2305  pub fn prune_types(&mut self) {
2306    if !self.graph_kind.include_types() {
2307      return; // nothing to do
2308    }
2309
2310    self.graph_kind = GraphKind::CodeOnly;
2311
2312    let specifiers_count = self.specifiers_count();
2313    let mut seen_pending =
2314      SeenPendingCollection::with_capacity(specifiers_count);
2315    seen_pending.extend(self.roots.iter().cloned());
2316    let mut found_nvs = HashSet::with_capacity(self.npm_packages.len());
2317    let mut has_node_specifier = false;
2318
2319    let handle_dependencies =
2320      |seen_pending: &mut SeenPendingCollection<Url>,
2321       dependencies: &mut IndexMap<String, Dependency>| {
2322        for dependency in dependencies.values_mut() {
2323          dependency.maybe_deno_types_specifier = None;
2324          dependency.maybe_type = Resolution::None;
2325          if let Some(url) = dependency.get_code() {
2326            seen_pending.add(url.clone());
2327          }
2328          if let Some(url) = dependency.get_type() {
2329            seen_pending.add(url.clone());
2330          }
2331        }
2332      };
2333
2334    // these are always types
2335    self.imports.clear();
2336
2337    // walk the graph
2338    while let Some(specifier) = seen_pending.next_pending() {
2339      let specifier = match self.redirects.get(&specifier) {
2340        Some(redirected_specifier) => {
2341          seen_pending.add(redirected_specifier.clone());
2342          continue;
2343        }
2344        None => specifier,
2345      };
2346      let Some(module) = self.module_slots.get_mut(&specifier) else {
2347        continue;
2348      };
2349      let module = match module {
2350        ModuleSlot::Module(module) => module,
2351        ModuleSlot::Err(_) | ModuleSlot::Pending { .. } => {
2352          continue;
2353        }
2354      };
2355      match module {
2356        Module::Js(js_module) => {
2357          #[cfg(feature = "fast_check")]
2358          {
2359            js_module.fast_check = None;
2360          }
2361          js_module.maybe_types_dependency = None;
2362          handle_dependencies(&mut seen_pending, &mut js_module.dependencies);
2363        }
2364        Module::Wasm(wasm_module) => {
2365          wasm_module.source_dts = Default::default();
2366          handle_dependencies(&mut seen_pending, &mut wasm_module.dependencies);
2367        }
2368        Module::Npm(module) => {
2369          found_nvs.insert(module.nv_reference.nv().clone());
2370        }
2371        Module::Node(_) => {
2372          has_node_specifier = true;
2373        }
2374        Module::Json(_) | Module::External(_) => {
2375          // ignore
2376        }
2377      }
2378    }
2379
2380    // Remove any unwalked items. Use retain rather than replace for these
2381    // in order to maintain the original order
2382    self
2383      .module_slots
2384      .retain(|specifier, _| seen_pending.has_seen(specifier));
2385    self.npm_packages.retain(|nv| found_nvs.contains(nv));
2386    self
2387      .redirects
2388      .retain(|redirect, _| seen_pending.has_seen(redirect));
2389    self.has_node_specifier = has_node_specifier;
2390  }
2391
2392  /// Iterates over all the module entries in the module graph searching from the provided roots.
2393  pub fn walk<'a, 'options>(
2394    &'a self,
2395    roots: impl Iterator<Item = &'a ModuleSpecifier>,
2396    options: WalkOptions<'options>,
2397  ) -> ModuleEntryIterator<'a, 'options> {
2398    ModuleEntryIterator::new(self, roots, options)
2399  }
2400
2401  /// Returns `true` if the specifier resolves to a module within a graph,
2402  /// otherwise returns `false`.
2403  pub fn contains(&self, specifier: &ModuleSpecifier) -> bool {
2404    let specifier = self.resolve(specifier);
2405    self
2406      .module_slots
2407      .get(specifier)
2408      .is_some_and(|ms| matches!(ms, ModuleSlot::Module(_)))
2409  }
2410
2411  /// Returns any module errors found in the graph.
2412  ///
2413  /// NOTE: This does not return any resolution errors.
2414  pub fn module_errors(&self) -> impl Iterator<Item = &ModuleError> {
2415    self.module_slots.values().filter_map(|ms| match ms {
2416      ModuleSlot::Err(err) => Some(err),
2417      ModuleSlot::Module(_) | ModuleSlot::Pending { .. } => None,
2418    })
2419  }
2420
2421  /// Get a module from the module graph, returning `None` if the module is not
2422  /// part of the graph, or if when loading the module it errored. If any module
2423  /// resolution error is needed, then use the `try_get()` method which will
2424  /// return any resolution error as the error in the result.
2425  pub fn get(&self, specifier: &ModuleSpecifier) -> Option<&Module> {
2426    let specifier = self.resolve(specifier);
2427    match self.module_slots.get(specifier) {
2428      Some(ModuleSlot::Module(module)) => Some(module),
2429      _ => None,
2430    }
2431  }
2432
2433  /// List of all the url in the graph that are statically known to be
2434  /// imported with type "text" or "bytes".
2435  pub fn asset_module_urls(&self) -> IndexSet<&Url> {
2436    let mut result = IndexSet::with_capacity(self.module_slots.len());
2437    for module in self.module_slots.values() {
2438      match module {
2439        ModuleSlot::Module(module) => {
2440          for dep in module.dependencies().values() {
2441            let is_asset = dep.imports.iter().all(|i| i.attributes.has_asset());
2442            if is_asset {
2443              if let Some(url) = dep.get_code() {
2444                let url = self.resolve(url);
2445                result.insert(url);
2446              }
2447            }
2448          }
2449        }
2450        ModuleSlot::Pending { .. } | ModuleSlot::Err { .. } => {}
2451      }
2452    }
2453    result
2454  }
2455
2456  /// Return an iterator of references to module objects in the graph. Only modules
2457  /// that were fully resolved are present, as "errors" are omitted. If
2458  /// you need to know what errors are in the graph, walk the graph via `.walk`
2459  /// or if you just need to check if everything is "ok" with the graph, use the
2460  /// `.valid()` method.
2461  pub fn modules(&self) -> impl Iterator<Item = &Module> {
2462    self.module_slots.values().filter_map(|ms| match ms {
2463      ModuleSlot::Module(m) => Some(m),
2464      _ => None,
2465    })
2466  }
2467
2468  /// Resolve a specifier from the module graph following any possible redirects
2469  /// returning the "final" module.
2470  pub fn resolve<'a>(
2471    &'a self,
2472    specifier: &'a ModuleSpecifier,
2473  ) -> &'a ModuleSpecifier {
2474    const MAX_REDIRECTS: usize = 10;
2475    let mut redirected_specifier = specifier;
2476    if let Some(specifier) = self.redirects.get(specifier) {
2477      // only allocate if there's a redirect
2478      let mut seen = HashSet::with_capacity(MAX_REDIRECTS);
2479      seen.insert(redirected_specifier);
2480      seen.insert(specifier);
2481      redirected_specifier = specifier;
2482      while let Some(specifier) = self.redirects.get(redirected_specifier) {
2483        if !seen.insert(specifier) {
2484          log::warn!("An infinite loop of redirections detected.\n  Original specifier: {specifier}");
2485          break;
2486        }
2487        redirected_specifier = specifier;
2488        if seen.len() >= MAX_REDIRECTS {
2489          log::warn!("An excessive number of redirections detected.\n  Original specifier: {specifier}");
2490          break;
2491        }
2492      }
2493    }
2494    redirected_specifier
2495  }
2496
2497  /// Resolve a dependency of a referring module providing the string specifier
2498  /// of the dependency and returning an optional fully qualified module
2499  /// specifier.
2500  ///
2501  /// The `prefer_types` flags indicates if a type dependency is preferred over
2502  /// a code dependency. If `true`, a type dependency will be returned in favor
2503  /// of a code dependency. If `false` a code dependency will be returned in
2504  /// favor of a type dependency. The value should be set to `true` when
2505  /// resolving specifiers for type checking, or otherwise `false`.
2506  pub fn resolve_dependency<'a>(
2507    &'a self,
2508    specifier: &str,
2509    referrer: &ModuleSpecifier,
2510    prefer_types: bool,
2511  ) -> Option<&'a ModuleSpecifier> {
2512    let referrer = self.resolve(referrer);
2513    if let Some(ModuleSlot::Module(referring_module)) =
2514      self.module_slots.get(referrer)
2515    {
2516      self.resolve_dependency_from_module(
2517        specifier,
2518        referring_module,
2519        prefer_types,
2520      )
2521    } else if let Some(graph_import) = self.imports.get(referrer) {
2522      let dependency = graph_import.dependencies.get(specifier)?;
2523      self.resolve_dependency_from_dep(dependency, prefer_types)
2524    } else {
2525      None
2526    }
2527  }
2528
2529  pub fn resolve_dependency_from_module<'a>(
2530    &'a self,
2531    specifier: &str,
2532    referring_module: &'a Module,
2533    prefer_types: bool,
2534  ) -> Option<&'a ModuleSpecifier> {
2535    match referring_module {
2536      Module::Js(referring_module) => {
2537        let dependency = referring_module.dependencies.get(specifier)?;
2538        self.resolve_dependency_from_dep(dependency, prefer_types)
2539      }
2540      Module::Wasm(referring_module) => {
2541        let dependency = referring_module.dependencies.get(specifier)?;
2542        self.resolve_dependency_from_dep(dependency, prefer_types)
2543      }
2544      Module::Json(_)
2545      | Module::Npm(_)
2546      | Module::Node(_)
2547      | Module::External(_) => None,
2548    }
2549  }
2550
2551  pub fn resolve_dependency_from_dep<'a>(
2552    &'a self,
2553    dependency: &'a Dependency,
2554    prefer_types: bool,
2555  ) -> Option<&'a ModuleSpecifier> {
2556    let (maybe_first, maybe_second) = if prefer_types {
2557      (&dependency.maybe_type, &dependency.maybe_code)
2558    } else {
2559      (&dependency.maybe_code, &dependency.maybe_type)
2560    };
2561    let unresolved_specifier = maybe_first
2562      .maybe_specifier()
2563      .or_else(|| maybe_second.maybe_specifier())?;
2564    let resolved_specifier = self.resolve(unresolved_specifier);
2565    // Even if we resolved the specifier, it doesn't mean the module is actually
2566    // there, so check in the module slots
2567    match self.module_slots.get(resolved_specifier) {
2568      Some(ModuleSlot::Module(Module::Js(module))) if prefer_types => {
2569        // check for if this module has a types dependency
2570        if let Some(Resolution::Ok(resolved)) = module
2571          .maybe_types_dependency
2572          .as_ref()
2573          .map(|d| &d.dependency)
2574        {
2575          let resolved_specifier = self.resolve(&resolved.specifier);
2576          if matches!(
2577            self.module_slots.get(resolved_specifier),
2578            Some(ModuleSlot::Module(_))
2579          ) {
2580            return Some(resolved_specifier);
2581          }
2582        }
2583        Some(resolved_specifier)
2584      }
2585      Some(ModuleSlot::Module(_)) => Some(resolved_specifier),
2586      _ => None,
2587    }
2588  }
2589
2590  /// Return the entries of the specifiers in the graph, where the first value
2591  /// is a module specifier and the second value is a result that contains a tuple of
2592  /// the module specifier, module kind, and media type, or the module graph
2593  /// error.
2594  pub fn specifiers(
2595    &self,
2596  ) -> impl Iterator<Item = (&ModuleSpecifier, Result<&Module, &ModuleError>)>
2597  {
2598    self.module_slots.iter().filter_map(to_result).chain(
2599      self.redirects.iter().filter_map(|(specifier, found)| {
2600        let module_slot = self.module_slots.get(found)?;
2601        to_result((specifier, module_slot))
2602      }),
2603    )
2604  }
2605
2606  /// Retrieve a module from the module graph. If the module identified as a
2607  /// dependency of the graph, but resolving or loading that module resulted in
2608  /// an error, the error will be returned as the `Err` of the result. If the
2609  /// module is not part of the graph, or the module is missing from the graph,
2610  /// the result will be `Ok` with the option of the module.
2611  pub fn try_get(
2612    &self,
2613    specifier: &ModuleSpecifier,
2614  ) -> Result<Option<&Module>, &ModuleError> {
2615    let specifier = self.resolve(specifier);
2616    match self.module_slots.get(specifier) {
2617      Some(ModuleSlot::Module(module)) => Ok(Some(module)),
2618      Some(ModuleSlot::Err(err)) => Err(err),
2619      _ => Ok(None),
2620    }
2621  }
2622
2623  /// Similar to `try_get`, but will prefer resolving to the types dependency if
2624  /// the module has one.
2625  pub fn try_get_prefer_types(
2626    &self,
2627    specifier: &ModuleSpecifier,
2628  ) -> Result<Option<&Module>, &ModuleError> {
2629    let Some(module) = self.try_get(specifier)? else {
2630      return Ok(None);
2631    };
2632
2633    if let Some(specifier) = module.js().and_then(|m| {
2634      m.maybe_types_dependency
2635        .as_ref()
2636        .and_then(|d| d.dependency.ok())
2637        .map(|r| &r.specifier)
2638    }) {
2639      self.try_get(specifier)
2640    } else {
2641      Ok(Some(module))
2642    }
2643  }
2644
2645  /// Walk the graph from the root, checking to see if there are any module
2646  /// graph errors on non-type only, non-dynamic imports. The first error is
2647  /// returned as as error result, otherwise ok if there are no errors.
2648  #[allow(clippy::result_large_err)]
2649  pub fn valid(&self) -> Result<(), ModuleGraphError> {
2650    self
2651      .walk(
2652        self.roots.iter(),
2653        WalkOptions {
2654          check_js: CheckJsOption::True,
2655          kind: GraphKind::CodeOnly,
2656          follow_dynamic: false,
2657          prefer_fast_check_graph: false,
2658        },
2659      )
2660      .validate()
2661  }
2662
2663  /// Gets the approximate number of specifiers in the graph.
2664  ///
2665  /// This is useful for pre-allocating actions that will take
2666  /// place on the graph.
2667  pub fn specifiers_count(&self) -> usize {
2668    self.module_slots.len() + self.redirects.len() + self.imports.len()
2669  }
2670}
2671
2672/// Resolve a string specifier from a referring module, using the resolver if
2673/// present, returning the resolution result.
2674fn resolve(
2675  specifier_text: &str,
2676  referrer_range: Range,
2677  resolution_kind: ResolutionKind,
2678  jsr_url_provider: &dyn JsrUrlProvider,
2679  maybe_resolver: Option<&dyn Resolver>,
2680) -> Resolution {
2681  let response = if let Some(resolver) = maybe_resolver {
2682    resolver.resolve(specifier_text, &referrer_range, resolution_kind)
2683  } else {
2684    resolve_import(specifier_text, &referrer_range.specifier)
2685      .map_err(|err| err.into())
2686  };
2687  if resolution_kind.is_types() {
2688    if let Ok(resolved_url) = &response {
2689      if let Some(package_nv) = jsr_url_provider.package_url_to_nv(resolved_url)
2690      {
2691        if Some(package_nv)
2692          != jsr_url_provider.package_url_to_nv(&referrer_range.specifier)
2693        {
2694          return Resolution::Err(Box::new(
2695            ResolutionError::InvalidJsrHttpsTypesImport {
2696              specifier: resolved_url.clone(),
2697              range: referrer_range.clone(),
2698            },
2699          ));
2700        }
2701      }
2702    }
2703  }
2704  Resolution::from_resolve_result(response, specifier_text, referrer_range)
2705}
2706
2707fn serialize_module_slots<S>(
2708  module_slots: &BTreeMap<ModuleSpecifier, ModuleSlot>,
2709  serializer: S,
2710) -> Result<S::Ok, S::Error>
2711where
2712  S: Serializer,
2713{
2714  let mut seq = serializer.serialize_seq(Some(module_slots.len()))?;
2715  for (specifier, slot) in module_slots.iter() {
2716    match slot {
2717      ModuleSlot::Module(module) => seq.serialize_element(module)?,
2718      ModuleSlot::Err(err) => seq.serialize_element(&serde_json::json!({
2719        "specifier": specifier,
2720        "error": err.to_string(),
2721      }))?,
2722      ModuleSlot::Pending { .. } => {
2723        seq.serialize_element(&serde_json::json!({
2724          "specifier": specifier,
2725          "error": "[INTERNAL ERROR] A pending module load never completed.",
2726        }))?
2727      }
2728    };
2729  }
2730  seq.end()
2731}
2732
2733fn serialize_graph_imports<S>(
2734  graph_imports: &IndexMap<ModuleSpecifier, GraphImport>,
2735  serializer: S,
2736) -> Result<S::Ok, S::Error>
2737where
2738  S: Serializer,
2739{
2740  #[derive(Serialize)]
2741  struct GraphImportWithReferrer<'a> {
2742    referrer: &'a ModuleSpecifier,
2743    #[serde(flatten)]
2744    graph_import: &'a GraphImport,
2745  }
2746  let mut seq = serializer.serialize_seq(Some(graph_imports.len()))?;
2747  for (referrer, graph_import) in graph_imports {
2748    seq.serialize_element(&GraphImportWithReferrer {
2749      referrer,
2750      graph_import,
2751    })?
2752  }
2753  seq.end()
2754}
2755
2756#[derive(Clone)]
2757pub(crate) enum ModuleSourceAndInfo {
2758  Json {
2759    specifier: ModuleSpecifier,
2760    mtime: Option<SystemTime>,
2761    source: ModuleTextSource,
2762  },
2763  Js {
2764    specifier: ModuleSpecifier,
2765    media_type: MediaType,
2766    maybe_headers: Option<HashMap<String, String>>,
2767    module_info: Box<ModuleInfo>,
2768    mtime: Option<SystemTime>,
2769    source: ModuleTextSource,
2770  },
2771  Wasm {
2772    specifier: ModuleSpecifier,
2773    module_info: Box<ModuleInfo>,
2774    mtime: Option<SystemTime>,
2775    source: Arc<[u8]>,
2776    source_dts: Arc<str>,
2777  },
2778}
2779
2780impl ModuleSourceAndInfo {
2781  pub fn specifier(&self) -> &ModuleSpecifier {
2782    match self {
2783      Self::Json { specifier, .. } => specifier,
2784      Self::Js { specifier, .. } => specifier,
2785      Self::Wasm { specifier, .. } => specifier,
2786    }
2787  }
2788
2789  pub fn media_type(&self) -> MediaType {
2790    match self {
2791      Self::Json { .. } => MediaType::Json,
2792      Self::Js { media_type, .. } => *media_type,
2793      Self::Wasm { .. } => MediaType::Wasm,
2794    }
2795  }
2796
2797  pub fn source_bytes(&self) -> &[u8] {
2798    match self {
2799      Self::Json { source, .. } => source.text.as_bytes(),
2800      Self::Js { source, .. } => source.text.as_bytes(),
2801      Self::Wasm { source, .. } => source,
2802    }
2803  }
2804}
2805
2806pub(crate) struct ParseModuleAndSourceInfoOptions<'a> {
2807  pub specifier: ModuleSpecifier,
2808  pub maybe_headers: Option<HashMap<String, String>>,
2809  pub mtime: Option<SystemTime>,
2810  pub content: Arc<[u8]>,
2811  pub maybe_attribute_type: Option<&'a AttributeTypeWithRange>,
2812  pub maybe_referrer: Option<&'a Range>,
2813  pub is_root: bool,
2814  pub is_dynamic_branch: bool,
2815}
2816
2817/// With the provided information, parse a module and return its "module slot"
2818#[allow(clippy::too_many_arguments)]
2819#[allow(clippy::result_large_err)]
2820pub(crate) async fn parse_module_source_and_info(
2821  module_analyzer: &dyn ModuleAnalyzer,
2822  opts: ParseModuleAndSourceInfoOptions<'_>,
2823) -> Result<ModuleSourceAndInfo, ModuleError> {
2824  let (mut media_type, maybe_charset) =
2825    resolve_media_type_and_charset_from_headers(
2826      &opts.specifier,
2827      opts.maybe_headers.as_ref(),
2828    );
2829
2830  if opts.is_root && media_type == MediaType::Unknown {
2831    // assume javascript
2832    media_type = MediaType::JavaScript;
2833  }
2834
2835  // here we check any media types that should have assertions made against them
2836  // if they aren't the root and add them to the graph, otherwise we continue
2837  if media_type == MediaType::Json
2838    && (opts.is_root
2839      || opts.is_dynamic_branch
2840      || matches!(
2841        opts.maybe_attribute_type.map(|t| t.kind.as_str()),
2842        Some("json")
2843      ))
2844  {
2845    return new_source_with_text(
2846      &opts.specifier,
2847      opts.content,
2848      maybe_charset,
2849      opts.mtime,
2850    )
2851    .map(|source| ModuleSourceAndInfo::Json {
2852      specifier: opts.specifier,
2853      mtime: opts.mtime,
2854      source,
2855    });
2856  }
2857
2858  if let Some(attribute_type) = opts.maybe_attribute_type {
2859    if attribute_type.kind == "json" {
2860      return Err(
2861        ModuleErrorKind::InvalidTypeAssertion {
2862          specifier: opts.specifier.clone(),
2863          referrer: attribute_type.range.clone(),
2864          actual_media_type: media_type,
2865          expected_media_type: MediaType::Json,
2866        }
2867        .into_box(),
2868      );
2869    } else if !matches!(attribute_type.kind.as_str(), "text" | "bytes") {
2870      return Err(
2871        ModuleErrorKind::UnsupportedImportAttributeType {
2872          specifier: opts.specifier,
2873          referrer: attribute_type.range.clone(),
2874          kind: attribute_type.kind.clone(),
2875        }
2876        .into_box(),
2877      );
2878    }
2879  }
2880
2881  if matches!(media_type, MediaType::Cjs | MediaType::Cts)
2882    && opts.specifier.scheme() != "file"
2883  {
2884    return Err(
2885      ModuleErrorKind::UnsupportedMediaType {
2886        specifier: opts.specifier,
2887        media_type,
2888        maybe_referrer: opts.maybe_referrer.map(|r| r.to_owned()),
2889      }
2890      .into_box(),
2891    );
2892  }
2893
2894  // Here we check for known ES Modules that we will analyze the dependencies of
2895  match media_type {
2896    MediaType::JavaScript
2897    | MediaType::Mjs
2898    | MediaType::Jsx
2899    | MediaType::TypeScript
2900    | MediaType::Mts
2901    | MediaType::Tsx
2902    | MediaType::Cjs
2903    | MediaType::Cts
2904    | MediaType::Dts
2905    | MediaType::Dmts
2906    | MediaType::Dcts => {
2907      let source = new_source_with_text(
2908        &opts.specifier,
2909        opts.content,
2910        maybe_charset,
2911        opts.mtime,
2912      )?;
2913      match module_analyzer
2914        .analyze(&opts.specifier, source.text.clone(), media_type)
2915        .await
2916      {
2917        Ok(module_info) => {
2918          // Return the module as a valid module
2919          Ok(ModuleSourceAndInfo::Js {
2920            specifier: opts.specifier,
2921            media_type,
2922            mtime: opts.mtime,
2923            source,
2924            maybe_headers: opts.maybe_headers,
2925            module_info: Box::new(module_info),
2926          })
2927        }
2928        Err(diagnostic) => Err(
2929          ModuleErrorKind::Parse {
2930            specifier: opts.specifier,
2931            mtime: opts.mtime,
2932            diagnostic: Arc::new(diagnostic),
2933          }
2934          .into_box(),
2935        ),
2936      }
2937    }
2938    MediaType::Wasm => {
2939      let source_dts_result = wasm_module_to_dts(&opts.content);
2940      match source_dts_result {
2941        Ok(source_dts) => {
2942          let source_dts: Arc<str> = source_dts.into();
2943          match module_analyzer
2944            .analyze(&opts.specifier, source_dts.clone(), MediaType::Dmts)
2945            .await
2946          {
2947            Ok(module_info) => {
2948              // Return the module as a valid module
2949              Ok(ModuleSourceAndInfo::Wasm {
2950                specifier: opts.specifier,
2951                module_info: Box::new(module_info),
2952                mtime: opts.mtime,
2953                source: opts.content,
2954                source_dts,
2955              })
2956            }
2957            Err(diagnostic) => Err(
2958              ModuleErrorKind::Parse {
2959                specifier: opts.specifier,
2960                mtime: opts.mtime,
2961                diagnostic: Arc::new(diagnostic),
2962              }
2963              .into_box(),
2964            ),
2965          }
2966        }
2967        Err(err) => Err(
2968          ModuleErrorKind::WasmParse {
2969            specifier: opts.specifier,
2970            mtime: opts.mtime,
2971            err,
2972          }
2973          .into_box(),
2974        ),
2975      }
2976    }
2977    MediaType::Css
2978    | MediaType::Json
2979    | MediaType::SourceMap
2980    | MediaType::Html
2981    | MediaType::Sql
2982    | MediaType::Unknown => Err(
2983      ModuleErrorKind::UnsupportedMediaType {
2984        specifier: opts.specifier,
2985        media_type,
2986        maybe_referrer: opts.maybe_referrer.map(|r| r.to_owned()),
2987      }
2988      .into_box(),
2989    ),
2990  }
2991}
2992
2993pub(crate) struct ParseModuleOptions {
2994  pub graph_kind: GraphKind,
2995  pub module_source_and_info: ModuleSourceAndInfo,
2996}
2997
2998/// With the provided information, parse a module and return its "module slot"
2999#[allow(clippy::result_large_err)]
3000pub(crate) fn parse_module(
3001  file_system: &FileSystem,
3002  jsr_url_provider: &dyn JsrUrlProvider,
3003  maybe_resolver: Option<&dyn Resolver>,
3004  options: ParseModuleOptions,
3005) -> Module {
3006  match options.module_source_and_info {
3007    ModuleSourceAndInfo::Json {
3008      specifier,
3009      mtime,
3010      source,
3011    } => Module::Json(JsonModule {
3012      maybe_cache_info: None,
3013      source,
3014      mtime,
3015      media_type: MediaType::Json,
3016      specifier,
3017    }),
3018    ModuleSourceAndInfo::Js {
3019      specifier,
3020      media_type,
3021      mtime,
3022      maybe_headers,
3023      module_info,
3024      source,
3025    } => Module::Js(parse_js_module_from_module_info(
3026      options.graph_kind,
3027      specifier,
3028      media_type,
3029      maybe_headers.as_ref(),
3030      *module_info,
3031      mtime,
3032      source,
3033      file_system,
3034      jsr_url_provider,
3035      maybe_resolver,
3036    )),
3037    ModuleSourceAndInfo::Wasm {
3038      specifier,
3039      mtime,
3040      source,
3041      source_dts,
3042      module_info,
3043    } => Module::Wasm(parse_wasm_module_from_module_info(
3044      options.graph_kind,
3045      specifier,
3046      *module_info,
3047      mtime,
3048      source,
3049      source_dts,
3050      file_system,
3051      jsr_url_provider,
3052      maybe_resolver,
3053    )),
3054  }
3055}
3056
3057#[allow(clippy::too_many_arguments)]
3058pub(crate) fn parse_js_module_from_module_info(
3059  graph_kind: GraphKind,
3060  specifier: ModuleSpecifier,
3061  media_type: MediaType,
3062  maybe_headers: Option<&HashMap<String, String>>,
3063  module_info: ModuleInfo,
3064  mtime: Option<SystemTime>,
3065  source: ModuleTextSource,
3066  file_system: &FileSystem,
3067  jsr_url_provider: &dyn JsrUrlProvider,
3068  maybe_resolver: Option<&dyn Resolver>,
3069) -> JsModule {
3070  let mut module = JsModule {
3071    is_script: module_info.is_script,
3072    dependencies: Default::default(),
3073    maybe_cache_info: None,
3074    mtime,
3075    source,
3076    maybe_types_dependency: None,
3077    media_type,
3078    specifier,
3079    #[cfg(feature = "fast_check")]
3080    fast_check: None,
3081  };
3082
3083  // Analyze the TypeScript triple-slash references and self types specifier
3084  if graph_kind.include_types() {
3085    if let Some(specifier) = module_info.self_types_specifier.as_ref() {
3086      let range = Range {
3087        specifier: module.specifier.clone(),
3088        range: specifier.range,
3089        resolution_mode: None,
3090      };
3091      module.maybe_types_dependency = Some(TypesDependency {
3092        specifier: specifier.text.clone(),
3093        dependency: resolve(
3094          &specifier.text,
3095          range.clone(),
3096          ResolutionKind::Types,
3097          jsr_url_provider,
3098          maybe_resolver,
3099        ),
3100      });
3101    }
3102
3103    for reference in module_info.ts_references {
3104      match reference {
3105        TypeScriptReference::Path(specifier) => {
3106          let dep = module
3107            .dependencies
3108            .entry(specifier.text.clone())
3109            .or_default();
3110          let range = Range {
3111            specifier: module.specifier.clone(),
3112            range: specifier.range,
3113            resolution_mode: None,
3114          };
3115          if dep.maybe_type.is_none() {
3116            dep.maybe_type = resolve(
3117              &specifier.text,
3118              range.clone(),
3119              ResolutionKind::Types,
3120              jsr_url_provider,
3121              maybe_resolver,
3122            );
3123          }
3124          dep.imports.push(Import {
3125            specifier: specifier.text,
3126            kind: ImportKind::TsReferencePath,
3127            specifier_range: range,
3128            is_dynamic: false,
3129            attributes: Default::default(),
3130            is_side_effect: false,
3131          });
3132        }
3133        TypeScriptReference::Types {
3134          specifier,
3135          resolution_mode: mode,
3136        } => {
3137          let is_untyped = !module.media_type.is_typed();
3138          if is_untyped && module.maybe_types_dependency.is_some() {
3139            continue; // early exit if we already have a types dependency
3140          }
3141          let range = Range {
3142            specifier: module.specifier.clone(),
3143            range: specifier.range,
3144            resolution_mode: mode.map(|mode| mode.as_deno_graph()),
3145          };
3146          let dep_resolution = resolve(
3147            &specifier.text,
3148            range.clone(),
3149            ResolutionKind::Types,
3150            jsr_url_provider,
3151            maybe_resolver,
3152          );
3153          if is_untyped {
3154            module.maybe_types_dependency = Some(TypesDependency {
3155              specifier: specifier.text.clone(),
3156              dependency: dep_resolution,
3157            });
3158          } else {
3159            let dep = module
3160              .dependencies
3161              .entry(specifier.text.clone())
3162              .or_default();
3163            if dep.maybe_type.is_none() {
3164              dep.maybe_type = dep_resolution;
3165            }
3166            dep.imports.push(Import {
3167              specifier: specifier.text,
3168              kind: ImportKind::TsReferenceTypes,
3169              specifier_range: range,
3170              is_dynamic: false,
3171              attributes: Default::default(),
3172              is_side_effect: false,
3173            });
3174          }
3175        }
3176      }
3177    }
3178  }
3179
3180  // Inject the JSX import source dependency if needed. This is done as follows:
3181  // 1. Check that the module is a JSX or TSX module.
3182  // 2. If the module has a @jsxImportSource pragma, use that as the import
3183  //    source.
3184  // 3. If the resolver has a default JSX import source, use that as the import
3185  //    source.
3186  // 4. If none of the above are true, do not inject a dependency.
3187  //
3188  // Additionally we may augment the JSX import source dependency with a type
3189  // dependency. This happens as follows:
3190  // 1. Check if a JSX import source dependency was injected and it is untyped.
3191  // 2. If the file has a @jsxImportSourceTypes pragma, use that as the import
3192  //    source types.
3193  // 3. If the JSX import source was not set through the @jsxImportSource
3194  //    pragma and the resolver has a default JSX import source types, use
3195  //    that as the import source types.
3196  // 4. If none of the above are true, do not inject a type dependency.
3197  //
3198  // This means that a default JSX import source types will not be used if the
3199  // import source was set by the @jsxImportSource pragma. This is done to
3200  // prevent a default import source types from being injected when the user
3201  // has explicitly overridden the import source in the file.
3202  if media_type.is_jsx() {
3203    let has_jsx_import_source_pragma = module_info.jsx_import_source.is_some();
3204    let res = module_info.jsx_import_source.or_else(|| {
3205      maybe_resolver.and_then(|r| {
3206        r.default_jsx_import_source(&module.specifier)
3207          .map(|import_source| SpecifierWithRange {
3208            text: import_source,
3209            range: PositionRange {
3210              start: Position::zeroed(),
3211              end: Position::zeroed(),
3212            },
3213          })
3214      })
3215    });
3216    if let Some(import_source) = res {
3217      let jsx_import_source_module = maybe_resolver
3218        .map(|r| r.jsx_import_source_module(&module.specifier))
3219        .unwrap_or(DEFAULT_JSX_IMPORT_SOURCE_MODULE);
3220      let specifier_text =
3221        format!("{}/{}", import_source.text, jsx_import_source_module);
3222      let dep = module
3223        .dependencies
3224        .entry(specifier_text.clone())
3225        .or_default();
3226      let range = Range {
3227        specifier: module.specifier.clone(),
3228        range: import_source.range,
3229        resolution_mode: None,
3230      };
3231      if dep.maybe_code.is_none() {
3232        dep.maybe_code = resolve(
3233          &specifier_text,
3234          range.clone(),
3235          ResolutionKind::Execution,
3236          jsr_url_provider,
3237          maybe_resolver,
3238        );
3239      }
3240      if graph_kind.include_types() && dep.maybe_type.is_none() {
3241        let mut types_res = module_info.jsx_import_source_types;
3242        if types_res.is_none() && !has_jsx_import_source_pragma {
3243          types_res = maybe_resolver.and_then(|r| {
3244            r.default_jsx_import_source_types(&module.specifier).map(
3245              |import_source| SpecifierWithRange {
3246                text: import_source,
3247                range: PositionRange {
3248                  start: Position::zeroed(),
3249                  end: Position::zeroed(),
3250                },
3251              },
3252            )
3253          });
3254        }
3255        if let Some(import_source_types) = types_res {
3256          let specifier_text = format!(
3257            "{}/{}",
3258            import_source_types.text, jsx_import_source_module
3259          );
3260          let range = Range {
3261            specifier: module.specifier.clone(),
3262            range: import_source_types.range,
3263            resolution_mode: None,
3264          };
3265          dep.maybe_type = resolve(
3266            &specifier_text,
3267            range,
3268            ResolutionKind::Types,
3269            jsr_url_provider,
3270            maybe_resolver,
3271          );
3272          dep.maybe_deno_types_specifier = Some(specifier_text);
3273        } else {
3274          let types_resolution = resolve(
3275            &specifier_text,
3276            range.clone(),
3277            ResolutionKind::Types,
3278            jsr_url_provider,
3279            maybe_resolver,
3280          );
3281          if types_resolution.maybe_specifier()
3282            != dep.maybe_code.maybe_specifier()
3283          {
3284            dep.maybe_type = types_resolution;
3285          }
3286        }
3287      }
3288      dep.imports.push(Import {
3289        specifier: specifier_text,
3290        kind: ImportKind::JsxImportSource,
3291        specifier_range: range,
3292        is_dynamic: false,
3293        attributes: Default::default(),
3294        is_side_effect: false,
3295      });
3296    }
3297  }
3298
3299  // Analyze any JSDoc type imports
3300  if graph_kind.include_types() {
3301    for jsdoc_import in module_info.jsdoc_imports {
3302      let specifier = jsdoc_import.specifier;
3303      let dep = module
3304        .dependencies
3305        .entry(specifier.text.clone())
3306        .or_default();
3307      let specifier_range = Range {
3308        specifier: module.specifier.clone(),
3309        range: specifier.range,
3310        resolution_mode: jsdoc_import
3311          .resolution_mode
3312          .map(|mode| mode.as_deno_graph()),
3313      };
3314      if dep.maybe_type.is_none() {
3315        dep.maybe_type = resolve(
3316          &specifier.text,
3317          specifier_range.clone(),
3318          ResolutionKind::Types,
3319          jsr_url_provider,
3320          maybe_resolver,
3321        );
3322      }
3323      dep.imports.push(Import {
3324        specifier: specifier.text,
3325        kind: ImportKind::JsDoc,
3326        specifier_range,
3327        is_dynamic: false,
3328        attributes: Default::default(),
3329        is_side_effect: false,
3330      });
3331    }
3332  }
3333
3334  // Analyze the X-TypeScript-Types header
3335  if graph_kind.include_types() && module.maybe_types_dependency.is_none() {
3336    if let Some(headers) = maybe_headers {
3337      if let Some(types_header) = headers.get("x-typescript-types") {
3338        let range = Range {
3339          specifier: module.specifier.clone(),
3340          range: PositionRange::zeroed(),
3341          resolution_mode: None,
3342        };
3343        module.maybe_types_dependency = Some(TypesDependency {
3344          specifier: types_header.to_string(),
3345          dependency: resolve(
3346            types_header,
3347            range,
3348            ResolutionKind::Types,
3349            jsr_url_provider,
3350            maybe_resolver,
3351          ),
3352        });
3353      }
3354    }
3355  }
3356
3357  // Use resolve_types from maybe_resolver
3358  if let Some(resolver) = maybe_resolver {
3359    // this will only get called if there is no other types dependency and
3360    // the media type is untyped.
3361    if graph_kind.include_types()
3362      && module.maybe_types_dependency.is_none()
3363      && !module.media_type.is_typed()
3364    {
3365      module.maybe_types_dependency =
3366        match resolver.resolve_types(&module.specifier) {
3367          Ok(Some((specifier, maybe_range))) => {
3368            let specifier_text = module.specifier.to_string();
3369            Some(TypesDependency {
3370              specifier: specifier_text,
3371              dependency: Resolution::Ok(Box::new(ResolutionResolved {
3372                specifier: specifier.clone(),
3373                range: maybe_range.unwrap_or_else(|| Range {
3374                  specifier,
3375                  range: PositionRange::zeroed(),
3376                  resolution_mode: None,
3377                }),
3378              })),
3379            })
3380          }
3381          Ok(None) => None,
3382          Err(err) => Some(TypesDependency {
3383            specifier: module.specifier.to_string(),
3384            dependency: Resolution::Err(Box::new(
3385              ResolutionError::ResolverError {
3386                error: Arc::new(err),
3387                specifier: module.specifier.to_string(),
3388                range: Range {
3389                  specifier: module.specifier.clone(),
3390                  range: PositionRange::zeroed(),
3391                  resolution_mode: None,
3392                },
3393              },
3394            )),
3395          }),
3396        };
3397    }
3398  }
3399
3400  // Analyze ES dependencies
3401  fill_module_dependencies(
3402    graph_kind,
3403    module.media_type,
3404    module_info.dependencies,
3405    &module.specifier,
3406    &mut module.dependencies,
3407    file_system,
3408    jsr_url_provider,
3409    maybe_resolver,
3410  );
3411
3412  // Return the module as a valid module
3413  module
3414}
3415
3416#[allow(clippy::too_many_arguments)]
3417fn parse_wasm_module_from_module_info(
3418  graph_kind: GraphKind,
3419  specifier: Url,
3420  module_info: ModuleInfo,
3421  mtime: Option<SystemTime>,
3422  source: Arc<[u8]>,
3423  source_dts: Arc<str>,
3424  file_system: &FileSystem,
3425  jsr_url_provider: &dyn JsrUrlProvider,
3426  maybe_resolver: Option<&dyn Resolver>,
3427) -> WasmModule {
3428  let mut module = WasmModule {
3429    specifier,
3430    dependencies: Default::default(),
3431    mtime,
3432    source,
3433    source_dts,
3434    maybe_cache_info: None,
3435  };
3436  fill_module_dependencies(
3437    graph_kind,
3438    MediaType::Wasm,
3439    module_info.dependencies,
3440    &module.specifier,
3441    &mut module.dependencies,
3442    file_system,
3443    jsr_url_provider,
3444    maybe_resolver,
3445  );
3446  module
3447}
3448
3449#[allow(clippy::too_many_arguments)]
3450fn fill_module_dependencies(
3451  graph_kind: GraphKind,
3452  media_type: MediaType,
3453  dependencies: Vec<DependencyDescriptor>,
3454  module_specifier: &ModuleSpecifier,
3455  module_dependencies: &mut IndexMap<String, Dependency>,
3456  file_system: &FileSystem,
3457  jsr_url_provider: &dyn JsrUrlProvider,
3458  maybe_resolver: Option<&dyn Resolver>,
3459) {
3460  for desc in dependencies {
3461    let (imports, types_specifier) = match desc {
3462      DependencyDescriptor::Static(desc)
3463        if desc.kind == StaticDependencyKind::MaybeTsModuleAugmentation =>
3464      {
3465        if !graph_kind.include_types() {
3466          continue;
3467        }
3468        (
3469          vec![Import {
3470            specifier: desc.specifier,
3471            kind: ImportKind::TsModuleAugmentation,
3472            specifier_range: Range {
3473              specifier: module_specifier.clone(),
3474              range: desc.specifier_range,
3475              resolution_mode: Some(ResolutionMode::Import),
3476            },
3477            is_dynamic: false,
3478            attributes: desc.import_attributes,
3479            is_side_effect: desc.is_side_effect,
3480          }],
3481          desc.types_specifier,
3482        )
3483      }
3484      DependencyDescriptor::Static(desc) => {
3485        let is_import_or_export_type = matches!(
3486          desc.kind,
3487          StaticDependencyKind::ImportType | StaticDependencyKind::ExportType
3488        );
3489        if is_import_or_export_type && !graph_kind.include_types() {
3490          continue; // skip
3491        }
3492        let is_types = is_import_or_export_type || media_type.is_declaration();
3493        let specifier_range = Range {
3494          specifier: module_specifier.clone(),
3495          range: desc.specifier_range,
3496          resolution_mode: match desc.kind {
3497            StaticDependencyKind::Import
3498            | StaticDependencyKind::Export
3499            | StaticDependencyKind::ImportType
3500            | StaticDependencyKind::ExportType => is_types
3501              .then(|| {
3502                desc
3503                  .import_attributes
3504                  .get("resolution-mode")
3505                  .and_then(TypeScriptTypesResolutionMode::from_str)
3506                  .map(|m| m.as_deno_graph())
3507              })
3508              .flatten()
3509              .or_else(|| {
3510                if media_type.is_declaration() {
3511                  None
3512                } else {
3513                  Some(ResolutionMode::Import)
3514                }
3515              }),
3516            StaticDependencyKind::ImportEquals
3517            | StaticDependencyKind::ExportEquals => {
3518              Some(ResolutionMode::Require)
3519            }
3520            StaticDependencyKind::MaybeTsModuleAugmentation => unreachable!(),
3521          },
3522        };
3523        (
3524          vec![Import {
3525            specifier: desc.specifier,
3526            kind: match is_import_or_export_type {
3527              true => ImportKind::TsType,
3528              false => ImportKind::Es,
3529            },
3530            specifier_range,
3531            is_dynamic: false,
3532            attributes: desc.import_attributes,
3533            is_side_effect: desc.is_side_effect,
3534          }],
3535          desc.types_specifier,
3536        )
3537      }
3538      DependencyDescriptor::Dynamic(desc) => {
3539        let import_attributes = desc.import_attributes;
3540        let specifiers = match desc.argument {
3541          DynamicArgument::String(text) => {
3542            vec![text]
3543          }
3544          DynamicArgument::Template(parts)
3545            if module_specifier.scheme() == "file" =>
3546          {
3547            let mut parts = analyze_dynamic_arg_template_parts(
3548              &parts,
3549              module_specifier,
3550              &desc.argument_range,
3551              &import_attributes,
3552              file_system,
3553            );
3554            // operating systems won't always traverse directories in
3555            // the same order, so sort here to ensure output is stable
3556            parts.sort();
3557            parts
3558          }
3559          _ => continue,
3560        };
3561        let specifier_range = Range {
3562          specifier: module_specifier.clone(),
3563          range: desc.argument_range,
3564          resolution_mode: match desc.kind {
3565            DynamicDependencyKind::Import => {
3566              if media_type.is_declaration() {
3567                None
3568              } else {
3569                Some(ResolutionMode::Import)
3570              }
3571            }
3572            DynamicDependencyKind::Require => Some(ResolutionMode::Require),
3573          },
3574        };
3575        (
3576          specifiers
3577            .into_iter()
3578            .map(|specifier| Import {
3579              specifier,
3580              kind: match desc.kind {
3581                DynamicDependencyKind::Import => ImportKind::Es,
3582                DynamicDependencyKind::Require => ImportKind::Require,
3583              },
3584              specifier_range: specifier_range.clone(),
3585              is_dynamic: true,
3586              attributes: import_attributes.clone(),
3587              is_side_effect: false,
3588            })
3589            .collect::<Vec<_>>(),
3590          desc.types_specifier,
3591        )
3592      }
3593    };
3594
3595    for import in imports {
3596      let dep = module_dependencies
3597        .entry(import.specifier.clone())
3598        .or_default();
3599      // TODO(nayeemrmn): Import attributes should be visited and checked for
3600      // every import, not one per specifier.
3601      if dep.maybe_attribute_type.is_none() {
3602        dep.maybe_attribute_type =
3603          import.attributes.get("type").map(|s| s.to_string());
3604      }
3605
3606      if let Some(types_specifier) = &types_specifier {
3607        if graph_kind.include_types() && dep.maybe_type.is_none() {
3608          dep.maybe_deno_types_specifier = Some(types_specifier.text.clone());
3609          dep.maybe_type = resolve(
3610            &types_specifier.text,
3611            Range {
3612              specifier: module_specifier.clone(),
3613              range: types_specifier.range,
3614              resolution_mode: import.specifier_range.resolution_mode,
3615            },
3616            ResolutionKind::Types,
3617            jsr_url_provider,
3618            maybe_resolver,
3619          );
3620        }
3621      }
3622      if matches!(
3623        import.kind,
3624        ImportKind::TsType | ImportKind::TsModuleAugmentation
3625      ) {
3626        if dep.maybe_type.is_none() {
3627          dep.maybe_type = resolve(
3628            &import.specifier,
3629            import.specifier_range.clone(),
3630            ResolutionKind::Types,
3631            jsr_url_provider,
3632            maybe_resolver,
3633          );
3634        }
3635      } else if !media_type.is_declaration() {
3636        if dep.maybe_code.is_none() {
3637          // This is a code import, the first one of that specifier in this module.
3638          // Resolve and determine the initial `is_dynamic` value from it.
3639          dep.maybe_code = resolve(
3640            &import.specifier,
3641            import.specifier_range.clone(),
3642            ResolutionKind::Execution,
3643            jsr_url_provider,
3644            maybe_resolver,
3645          );
3646          dep.is_dynamic = import.is_dynamic;
3647        } else {
3648          // This is a code import, but not the first one of that specifier in this
3649          // module. Maybe update the `is_dynamic` value. Static imports take
3650          // precedence. Note that `@jsxImportSource` and `/// <reference path />`
3651          // count as static imports for this purpose.
3652          dep.is_dynamic = dep.is_dynamic && import.is_dynamic;
3653        }
3654      }
3655      if graph_kind.include_types() && dep.maybe_type.is_none() {
3656        let maybe_type = resolve(
3657          &import.specifier,
3658          import.specifier_range.clone(),
3659          ResolutionKind::Types,
3660          jsr_url_provider,
3661          maybe_resolver,
3662        );
3663        // don't surface type only errors for side effect imports that fail to resolve
3664        let is_side_effect_import_error =
3665          import.is_side_effect && maybe_type.err().is_some();
3666        if !is_side_effect_import_error
3667          // don't bother setting if the resolved specifier matches the code specifier
3668          && maybe_type.maybe_specifier() != dep.maybe_code.maybe_specifier()
3669        {
3670          dep.maybe_type = maybe_type
3671        }
3672      }
3673      dep.imports.push(import);
3674    }
3675  }
3676  // Remove any import of kind `TsModuleAugmentation` which doesn't have a type
3677  // resolution. If it doesn't point at something to augment, it doesn't induce
3678  // a dependency.
3679  if media_type.is_typed() {
3680    module_dependencies.retain(|_, dep| {
3681      if dep.get_type().is_some() {
3682        return true;
3683      }
3684      dep
3685        .imports
3686        .retain(|i| i.kind != ImportKind::TsModuleAugmentation);
3687      !dep.imports.is_empty()
3688    });
3689  }
3690}
3691
3692fn analyze_dynamic_arg_template_parts(
3693  parts: &[DynamicTemplatePart],
3694  referrer: &Url,
3695  referrer_range: &PositionRange,
3696  import_attributes: &ImportAttributes,
3697  file_system: &FileSystem,
3698) -> Vec<String> {
3699  fn resolve_initial_dir_path(
3700    parts: &[DynamicTemplatePart],
3701    referrer: &Url,
3702  ) -> Option<ModuleSpecifier> {
3703    match parts.first()? {
3704      DynamicTemplatePart::String { value } => {
3705        if value.starts_with("./") {
3706          referrer.join(value).ok()
3707        } else if value.starts_with("file://") {
3708          ModuleSpecifier::parse(value).ok()
3709        } else {
3710          None
3711        }
3712      }
3713      // could be a remote module, so ignore
3714      DynamicTemplatePart::Expr => None,
3715    }
3716  }
3717
3718  fn validate_string_parts(
3719    string_parts: &[&String],
3720    is_last_string: bool,
3721  ) -> bool {
3722    fn validate_part(
3723      index: usize,
3724      part: &str,
3725      path_parts: &[&String],
3726      is_last_string: bool,
3727    ) -> bool {
3728      !part.contains("/../")
3729        && if index == 0 {
3730          let valid_start = part.starts_with("./") || part.starts_with('/');
3731          let valid_end = part.ends_with('/');
3732          valid_start && valid_end
3733        } else if is_last_string && index == path_parts.len() - 1 {
3734          part.starts_with('/') || !part.contains('/')
3735        } else {
3736          part.starts_with('/') && part.ends_with('/')
3737        }
3738    }
3739
3740    string_parts.iter().enumerate().all(|(index, part)| {
3741      validate_part(index, part, string_parts, is_last_string)
3742    })
3743  }
3744
3745  let Some(dir_path) = resolve_initial_dir_path(parts, referrer) else {
3746    return Vec::new();
3747  };
3748
3749  let string_parts = parts
3750    .iter()
3751    .enumerate()
3752    .filter_map(|(i, p)| match p {
3753      DynamicTemplatePart::String { value } => {
3754        if i == 0 && value.starts_with("file://") {
3755          None // don't do comparisons on the base
3756        } else {
3757          Some(value)
3758        }
3759      }
3760      DynamicTemplatePart::Expr => None,
3761    })
3762    .collect::<Vec<_>>();
3763  let is_last_string =
3764    matches!(parts.last(), Some(DynamicTemplatePart::String { .. }));
3765  if !validate_string_parts(&string_parts, is_last_string) {
3766    return Vec::new(); // don't search for this
3767  }
3768
3769  let matching_media_types = if import_attributes.get("type") == Some("json") {
3770    vec![MediaType::Json]
3771  } else {
3772    vec![
3773      MediaType::JavaScript,
3774      MediaType::TypeScript,
3775      MediaType::Jsx,
3776      MediaType::Tsx,
3777      MediaType::Mjs,
3778      MediaType::Mts,
3779    ]
3780  };
3781  let mut specifiers = Vec::new();
3782  // skip the root specifier
3783  if is_fs_root_specifier(&dir_path) {
3784    return specifiers;
3785  }
3786  let Ok(dir_path) = deno_path_util::url_to_file_path(&dir_path) else {
3787    return specifiers;
3788  };
3789  let mut pending_dirs = VecDeque::from([dir_path]);
3790  let handle_err = |path: &Path, err: &std::io::Error| {
3791    if matches!(
3792      err.kind(),
3793      std::io::ErrorKind::PermissionDenied | std::io::ErrorKind::NotFound
3794    ) {
3795      return;
3796    }
3797    // For now, errors are swallowed and not stored in the graph.
3798    // If we decide to represent these in the graph, we'll need to
3799    // figure out what to do with errors like directory errors.
3800    // Additionally, take note that these are dynamic import errors,
3801    // so they shouldn't be eagerly surfaced.
3802    log::warn!(
3803      "Graph failed resolving '{}'. {:#}\n    at {}:{}:{}",
3804      path.display(),
3805      err,
3806      referrer,
3807      referrer_range.start.line + 1,
3808      referrer_range.start.character + 1,
3809    );
3810  };
3811  while let Some(dir_path) = pending_dirs.pop_front() {
3812    let entries = match file_system.fs_read_dir_boxed(&dir_path) {
3813      Ok(entries) => entries,
3814      Err(err) => {
3815        handle_err(&dir_path, &err);
3816        continue;
3817      }
3818    };
3819    for entry in entries {
3820      let entry = match entry {
3821        Ok(entry) => entry,
3822        Err(err) => {
3823          handle_err(&dir_path, &err);
3824          continue;
3825        }
3826      };
3827      let path = entry.path();
3828      match entry.file_type() {
3829        Ok(FileType::File) => {
3830          let Ok(url) = deno_path_util::url_from_file_path(&path) else {
3831            continue;
3832          };
3833          if matching_media_types.contains(&MediaType::from_specifier(&url)) {
3834            if url == *referrer {
3835              continue; // found itself, so skip
3836            }
3837            if let Some(specifier) = referrer.make_relative(&url) {
3838              let specifier = if !specifier.starts_with("../") {
3839                format!("./{}", specifier)
3840              } else {
3841                specifier
3842              };
3843              let mut valid = true;
3844              let mut last_index = 0;
3845              for part in &string_parts {
3846                if let Some(index) = &specifier[last_index..].find(*part) {
3847                  last_index += index + part.len();
3848                } else {
3849                  valid = false;
3850                  break;
3851                }
3852              }
3853              if valid {
3854                if let Some(DynamicTemplatePart::String { value }) =
3855                  parts.last()
3856                {
3857                  if specifier.ends_with(value) {
3858                    specifiers.push(specifier);
3859                  }
3860                } else {
3861                  specifiers.push(specifier);
3862                }
3863              }
3864            }
3865          }
3866        }
3867        Ok(FileType::Dir) => {
3868          // ignore hidden directories and any node_modules/vendor folders
3869          let is_allowed_dir = path
3870            .file_name()
3871            .map(|c| {
3872              !c.to_string_lossy().starts_with('.')
3873                && c != "node_modules"
3874                && c != "vendor"
3875            })
3876            .unwrap_or(true);
3877          if is_allowed_dir {
3878            pending_dirs.push_back(path.into_owned());
3879          }
3880        }
3881        Ok(_) => {
3882          // ignore
3883        }
3884        Err(err) => {
3885          handle_err(&path, &err);
3886        }
3887      };
3888    }
3889  }
3890
3891  specifiers
3892}
3893
3894/// The kind of module graph.
3895#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3896pub enum GraphKind {
3897  /// All types of dependencies should be analyzed and included in the graph.
3898  All,
3899  /// Only code dependencies should be analyzed and included in the graph. This
3900  /// is useful when transpiling and running code, but not caring about type
3901  /// only dependnecies.
3902  CodeOnly,
3903  /// Only type dependencies should be analyzed and included in the graph. This
3904  /// is useful when assessing types, like documentation or type checking, when
3905  /// the code will not be executed.
3906  ///
3907  /// Note that code which is overloaded with types upon access (like the
3908  /// `X-TypeScript-Types` header or types defined in the code itself) will
3909  /// still be loaded into the graph, but further code only dependencies will
3910  /// not be followed.
3911  TypesOnly,
3912}
3913
3914impl Default for GraphKind {
3915  fn default() -> Self {
3916    Self::All
3917  }
3918}
3919
3920impl GraphKind {
3921  pub fn include_types(&self) -> bool {
3922    matches!(self, Self::All | Self::TypesOnly)
3923  }
3924
3925  pub fn include_code(&self) -> bool {
3926    matches!(self, Self::All | Self::CodeOnly)
3927  }
3928}
3929
3930enum PendingInfoResponse {
3931  External {
3932    specifier: ModuleSpecifier,
3933    is_root: bool,
3934    is_asset: bool,
3935  },
3936  Module {
3937    specifier: ModuleSpecifier,
3938    module_source_and_info: ModuleSourceAndInfo,
3939    pending_load: Option<Box<(LoaderChecksum, ModuleInfo)>>,
3940    is_root: bool,
3941  },
3942  Redirect {
3943    count: usize,
3944    specifier: ModuleSpecifier,
3945    maybe_attribute_type: Option<AttributeTypeWithRange>,
3946    is_asset: bool,
3947    is_dynamic: bool,
3948    is_root: bool,
3949  },
3950}
3951
3952impl PendingInfoResponse {
3953  fn specifier(&self) -> &ModuleSpecifier {
3954    match self {
3955      Self::External { specifier, .. } => specifier,
3956      Self::Module {
3957        module_source_and_info,
3958        ..
3959      } => module_source_and_info.specifier(),
3960      Self::Redirect { specifier, .. } => specifier,
3961    }
3962  }
3963}
3964
3965#[derive(Debug, Clone)]
3966struct JsrPackageVersionInfoExt {
3967  base_url: Url,
3968  inner: Arc<JsrPackageVersionInfo>,
3969}
3970
3971impl JsrPackageVersionInfoExt {
3972  pub fn get_subpath<'a>(&self, specifier: &'a Url) -> Option<&'a str> {
3973    let base_url = self.base_url.as_str();
3974    let base_url = base_url.strip_suffix('/').unwrap_or(base_url);
3975    specifier.as_str().strip_prefix(base_url)
3976  }
3977
3978  pub fn get_checksum(&self, sub_path: &str) -> Result<&str, ModuleLoadError> {
3979    match self.inner.manifest.get(sub_path) {
3980      Some(manifest_entry) => {
3981        match manifest_entry.checksum.strip_prefix("sha256-") {
3982          Some(checksum) => Ok(checksum),
3983          None => Err(ModuleLoadError::Jsr(
3984            JsrLoadError::UnsupportedManifestChecksum,
3985          )),
3986        }
3987      }
3988      // If the checksum is missing then leave it up to the loader to error
3989      // by providing this special checksum value. For example, someone may
3990      // be making modifications to their vendor folder in which case this
3991      // checksum will be ignored and if not, then a loading error will
3992      // occur about an incorrect checksum.
3993      None => Ok("package-manifest-missing-checksum"),
3994    }
3995  }
3996}
3997
3998enum LoadSpecifierKind {
3999  Jsr(JsrPackageReqReference),
4000  Npm(NpmPackageReqReference),
4001  Node(String),
4002  Url,
4003}
4004
4005struct PendingInfo {
4006  requested_specifier: ModuleSpecifier,
4007  maybe_range: Option<Range>,
4008  result: Result<PendingInfoResponse, ModuleError>,
4009  maybe_version_info: Option<JsrPackageVersionInfoExt>,
4010  loaded_package_via_https_url: Option<LoadedJsrPackageViaHttpsUrl>,
4011}
4012
4013struct PendingModuleLoadItem {
4014  redirect_count: usize,
4015  requested_specifier: Url,
4016  maybe_attribute_type: Option<AttributeTypeWithRange>,
4017  maybe_range: Option<Range>,
4018  load_specifier: Url,
4019  in_dynamic_branch: bool,
4020  is_asset: bool,
4021  is_root: bool,
4022  maybe_checksum: Option<LoaderChecksum>,
4023  maybe_version_info: Option<JsrPackageVersionInfoExt>,
4024}
4025
4026struct LoadedJsrPackageViaHttpsUrl {
4027  nv: PackageNv,
4028  manifest_checksum_for_locker: Option<LoaderChecksum>,
4029}
4030
4031type PendingInfoFuture<'a> = LocalBoxFuture<'a, PendingInfo>;
4032
4033#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4034pub(crate) struct AttributeTypeWithRange {
4035  range: Range,
4036  /// The kind of attribute (ex. "json").
4037  kind: String,
4038}
4039
4040#[derive(Debug, Default)]
4041struct PendingNpmState {
4042  requested_registry_info_loads: HashSet<StackString>,
4043  pending_resolutions: Vec<PendingNpmResolutionItem>,
4044}
4045
4046#[derive(Debug)]
4047struct PendingJsrReqResolutionItem {
4048  specifier: ModuleSpecifier,
4049  package_ref: JsrPackageReqReference,
4050  maybe_attribute_type: Option<AttributeTypeWithRange>,
4051  maybe_range: Option<Range>,
4052  in_dynamic_branch: bool,
4053  is_asset: bool,
4054  is_root: bool,
4055}
4056
4057#[derive(Debug)]
4058struct PendingJsrNvResolutionItem {
4059  specifier: ModuleSpecifier,
4060  nv_ref: JsrPackageNvReference,
4061  maybe_attribute_type: Option<AttributeTypeWithRange>,
4062  maybe_range: Option<Range>,
4063  is_asset: bool,
4064  is_dynamic: bool,
4065  is_root: bool,
4066}
4067
4068#[derive(Debug)]
4069struct PendingContentLoadItem {
4070  specifier: ModuleSpecifier,
4071  maybe_range: Option<Range>,
4072  result: LoadResult,
4073  module_info: ModuleInfo,
4074}
4075
4076#[derive(Debug, Default)]
4077struct PendingJsrState {
4078  pending_resolutions: VecDeque<PendingJsrReqResolutionItem>,
4079  pending_content_loads:
4080    FuturesUnordered<LocalBoxFuture<'static, PendingContentLoadItem>>,
4081  metadata: Rc<JsrMetadataStore>,
4082}
4083
4084#[derive(Debug)]
4085struct PendingDynamicBranch {
4086  range: Range,
4087  is_asset: bool,
4088  maybe_attribute_type: Option<AttributeTypeWithRange>,
4089  maybe_version_info: Option<JsrPackageVersionInfoExt>,
4090}
4091
4092#[derive(Debug)]
4093struct DeferredLoad {
4094  maybe_range: Option<Range>,
4095  in_dynamic_branch: bool,
4096  is_root: bool,
4097  maybe_attribute_type: Option<AttributeTypeWithRange>,
4098  maybe_version_info: Option<JsrPackageVersionInfoExt>,
4099}
4100
4101struct LoadOptionsRef<'a> {
4102  specifier: &'a ModuleSpecifier,
4103  maybe_range: Option<&'a Range>,
4104  is_asset: bool,
4105  in_dynamic_branch: bool,
4106  is_root: bool,
4107  maybe_attribute_type: Option<AttributeTypeWithRange>,
4108  maybe_version_info: Option<&'a JsrPackageVersionInfoExt>,
4109}
4110
4111#[derive(Debug, Default)]
4112struct PendingState<'a> {
4113  deferred: HashMap<ModuleSpecifier, DeferredLoad>,
4114  pending: FuturesOrdered<PendingInfoFuture<'a>>,
4115  jsr: PendingJsrState,
4116  npm: PendingNpmState,
4117  dynamic_branches: HashMap<ModuleSpecifier, PendingDynamicBranch>,
4118}
4119
4120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4121enum FillPassMode {
4122  AllowRestart,
4123  NoRestart,
4124  CacheBusting,
4125}
4126
4127impl FillPassMode {
4128  fn to_cache_setting(self) -> CacheSetting {
4129    if self == FillPassMode::CacheBusting {
4130      CacheSetting::Reload
4131    } else {
4132      CacheSetting::Use
4133    }
4134  }
4135}
4136
4137struct Builder<'a, 'graph> {
4138  in_dynamic_branch: bool,
4139  skip_dynamic_deps: bool,
4140  was_dynamic_root: bool,
4141  unstable_bytes_imports: bool,
4142  unstable_text_imports: bool,
4143  file_system: &'a FileSystem,
4144  jsr_url_provider: &'a dyn JsrUrlProvider,
4145  jsr_version_resolver: Cow<'a, JsrVersionResolver>,
4146  passthrough_jsr_specifiers: bool,
4147  loader: &'a dyn Loader,
4148  locker: Option<&'a mut dyn Locker>,
4149  resolver: Option<&'a dyn Resolver>,
4150  module_analyzer: &'a dyn ModuleAnalyzer,
4151  module_info_cacher: &'a dyn ModuleInfoCacher,
4152  npm_resolver: Option<&'a dyn NpmResolver>,
4153  reporter: Option<&'a dyn Reporter>,
4154  graph: &'graph mut ModuleGraph,
4155  state: PendingState<'a>,
4156  fill_pass_mode: FillPassMode,
4157  executor: &'a dyn Executor,
4158  resolved_roots: BTreeSet<ModuleSpecifier>,
4159}
4160
4161impl<'a, 'graph> Builder<'a, 'graph> {
4162  pub fn new(
4163    graph: &'graph mut ModuleGraph,
4164    loader: &'a dyn Loader,
4165    options: BuildOptions<'a>,
4166  ) -> Self {
4167    let fill_pass_mode = match graph.roots.is_empty() {
4168      true => FillPassMode::AllowRestart,
4169      false => FillPassMode::NoRestart,
4170    };
4171    Self {
4172      in_dynamic_branch: options.is_dynamic,
4173      skip_dynamic_deps: options.skip_dynamic_deps,
4174      was_dynamic_root: options.is_dynamic,
4175      unstable_bytes_imports: options.unstable_bytes_imports,
4176      unstable_text_imports: options.unstable_text_imports,
4177      file_system: options.file_system,
4178      jsr_url_provider: options.jsr_url_provider,
4179      jsr_version_resolver: options.jsr_version_resolver,
4180      passthrough_jsr_specifiers: options.passthrough_jsr_specifiers,
4181      loader,
4182      locker: options.locker,
4183      resolver: options.resolver,
4184      npm_resolver: options.npm_resolver,
4185      module_analyzer: options.module_analyzer,
4186      module_info_cacher: options.module_info_cacher,
4187      reporter: options.reporter,
4188      graph,
4189      state: PendingState {
4190        jsr: PendingJsrState {
4191          metadata: options
4192            .jsr_metadata_store
4193            .unwrap_or(Rc::new(JsrMetadataStore::default())),
4194          ..Default::default()
4195        },
4196        ..Default::default()
4197      },
4198      fill_pass_mode,
4199      executor: options.executor,
4200      resolved_roots: Default::default(),
4201    }
4202  }
4203
4204  pub async fn build(
4205    &mut self,
4206    roots: Vec<ModuleSpecifier>,
4207    imports: Vec<ReferrerImports>,
4208  ) {
4209    let provided_roots = roots;
4210    let provided_imports = imports;
4211    let roots = provided_roots
4212      .iter()
4213      .filter(|r| !self.graph.roots.contains(*r))
4214      .cloned()
4215      .collect::<Vec<_>>();
4216    let imports = provided_imports
4217      .iter()
4218      .filter(|r| !self.graph.imports.contains_key(&r.referrer))
4219      .cloned()
4220      .collect::<Vec<_>>();
4221
4222    self.graph.roots.extend(roots.clone());
4223
4224    for root in roots {
4225      self.load(LoadOptionsRef {
4226        specifier: &root,
4227        maybe_range: None,
4228        is_asset: false,
4229        in_dynamic_branch: self.in_dynamic_branch,
4230        is_root: true,
4231        maybe_attribute_type: None,
4232        maybe_version_info: None,
4233      });
4234    }
4235
4236    // process any imports that are being added to the graph.
4237    self.handle_provided_imports(imports);
4238
4239    let should_restart = self.resolve_pending().await;
4240    if should_restart {
4241      self.restart(provided_roots, provided_imports).await
4242    }
4243  }
4244
4245  /// A naive reload of the specifiers. This does not remove modules no longer
4246  /// relevant data from the graph.
4247  pub async fn reload(&mut self, specifiers: Vec<ModuleSpecifier>) {
4248    let specifiers = specifiers
4249      .into_iter()
4250      .map(|s| {
4251        let resolved = self.graph.resolve(&s);
4252        if *resolved == s {
4253          s // avoid clone
4254        } else {
4255          resolved.clone()
4256        }
4257      })
4258      .collect::<Vec<_>>();
4259
4260    for specifier in &specifiers {
4261      self.graph.module_slots.remove(specifier);
4262      self.load(LoadOptionsRef {
4263        specifier,
4264        maybe_range: None,
4265        is_asset: false,
4266        in_dynamic_branch: self.in_dynamic_branch,
4267        is_root: true,
4268        maybe_attribute_type: None,
4269        maybe_version_info: None,
4270      });
4271    }
4272    self.resolve_pending().await;
4273  }
4274
4275  async fn resolve_pending(&mut self) -> bool {
4276    while !(self.state.pending.is_empty()
4277      && self.state.jsr.pending_resolutions.is_empty()
4278      && self.state.dynamic_branches.is_empty()
4279      && self.state.deferred.is_empty())
4280    {
4281      let specifier = match self.state.pending.next().await {
4282        Some(PendingInfo {
4283          requested_specifier,
4284          maybe_range,
4285          result,
4286          maybe_version_info,
4287          loaded_package_via_https_url,
4288        }) => {
4289          if let Some(pkg) = loaded_package_via_https_url {
4290            if let Some(locker) = &mut self.locker {
4291              if let Some(checksum) = pkg.manifest_checksum_for_locker {
4292                locker.set_pkg_manifest_checksum(&pkg.nv, checksum);
4293              }
4294            }
4295            self.graph.packages.ensure_package(pkg.nv);
4296          }
4297
4298          match result {
4299            Ok(response) => {
4300              self.check_specifier(&requested_specifier, response.specifier());
4301
4302              self.visit(
4303                response,
4304                maybe_range.clone(),
4305                maybe_version_info.as_ref(),
4306              );
4307
4308              Some(requested_specifier)
4309            }
4310            Err(err) => {
4311              self.check_specifier(&requested_specifier, err.specifier());
4312              self
4313                .graph
4314                .module_slots
4315                .insert(err.specifier().clone(), ModuleSlot::Err(err));
4316              Some(requested_specifier)
4317            }
4318          }
4319        }
4320        None => None,
4321      };
4322
4323      if let (Some(specifier), Some(reporter)) = (specifier, self.reporter) {
4324        let modules_total = self.graph.module_slots.len();
4325        let modules_done = modules_total - self.state.pending.len();
4326        reporter.on_load(&specifier, modules_done, modules_total);
4327      }
4328
4329      if self.state.pending.is_empty() {
4330        if !self.state.deferred.is_empty() {
4331          let items = std::mem::take(&mut self.state.deferred);
4332          for (specifier, item) in items {
4333            self.load(LoadOptionsRef {
4334              specifier: &specifier,
4335              maybe_range: item.maybe_range.as_ref(),
4336              is_asset: false, // deferred items are asset -> module
4337              in_dynamic_branch: item.in_dynamic_branch,
4338              is_root: item.is_root,
4339              maybe_attribute_type: item.maybe_attribute_type,
4340              maybe_version_info: item.maybe_version_info.as_ref(),
4341            });
4342          }
4343        } else {
4344          let should_restart = self.resolve_pending_jsr_specifiers().await;
4345          if should_restart {
4346            return true;
4347          }
4348
4349          // resolving jsr specifiers will load more specifiers
4350          if self.state.pending.is_empty() {
4351            self.resolve_dynamic_branches();
4352          }
4353        }
4354      }
4355    }
4356
4357    // handle any pending content loads from the Deno registry
4358    self.handle_jsr_registry_pending_content_loads().await;
4359
4360    // enrich with cache info from the loader
4361    self.fill_graph_with_cache_info();
4362
4363    // resolve any npm package requirements
4364    NpmSpecifierResolver::fill_builder(self).await;
4365
4366    false
4367  }
4368
4369  fn handle_provided_imports(&mut self, imports: Vec<ReferrerImports>) {
4370    for referrer_imports in imports {
4371      let referrer = referrer_imports.referrer;
4372      let imports = referrer_imports.imports;
4373      let graph_import = GraphImport::new(
4374        &referrer,
4375        imports,
4376        self.jsr_url_provider,
4377        self.resolver,
4378      );
4379      for dep in graph_import.dependencies.values() {
4380        if let Resolution::Ok(resolved) = &dep.maybe_type {
4381          self.load(LoadOptionsRef {
4382            specifier: &resolved.specifier,
4383            maybe_range: Some(&resolved.range),
4384            is_asset: false,
4385            in_dynamic_branch: self.in_dynamic_branch,
4386            is_root: self.resolved_roots.contains(&resolved.specifier),
4387            maybe_attribute_type: None,
4388            maybe_version_info: None,
4389          });
4390        }
4391      }
4392      self.graph.imports.insert(referrer, graph_import);
4393    }
4394  }
4395
4396  async fn resolve_pending_jsr_specifiers(&mut self) -> bool {
4397    // first load the package information
4398    let mut pending_resolutions =
4399      std::mem::take(&mut self.state.jsr.pending_resolutions);
4400    let mut pending_version_resolutions =
4401      Vec::with_capacity(pending_resolutions.len());
4402    let should_collect_top_level_nvs =
4403      self.graph.packages.top_level_packages().is_empty()
4404        && self.graph.graph_kind.include_types();
4405    // don't bother pre-allocating because adding to this should be rare
4406    let mut restarted_pkgs = HashSet::new();
4407    while let Some(pending_resolution) = pending_resolutions.pop_front() {
4408      let package_name = &pending_resolution.package_ref.req().name;
4409      let fut = self
4410        .state
4411        .jsr
4412        .metadata
4413        .get_package_metadata(package_name)
4414        .unwrap();
4415      match fut.await {
4416        Ok(info) => {
4417          let package_req = pending_resolution.package_ref.req();
4418          match self.resolve_jsr_nv(package_req, &info) {
4419            Ok(package_nv) => {
4420              // now queue a pending load for that version information
4421              self.queue_load_package_version_info(&package_nv);
4422              pending_version_resolutions.push(PendingJsrNvResolutionItem {
4423                specifier: pending_resolution.specifier,
4424                nv_ref: JsrPackageNvReference::new(PackageNvReference {
4425                  nv: package_nv,
4426                  sub_path: pending_resolution
4427                    .package_ref
4428                    .into_inner()
4429                    .sub_path,
4430                }),
4431                maybe_attribute_type: pending_resolution.maybe_attribute_type,
4432                maybe_range: pending_resolution.maybe_range,
4433                is_asset: pending_resolution.is_asset,
4434                is_dynamic: pending_resolution.in_dynamic_branch,
4435                is_root: pending_resolution.is_root,
4436              });
4437            }
4438            Err(package_req_not_found_err) => {
4439              // Generally, prefer a full restart that cache busts if we can
4440              // because it will cause the meta files for users to be updated
4441              // more frequently. In cases like non-statically analyzable dynamic
4442              // branches, FillPassMode will not allow restarting, so just update
4443              // the single package metadata.
4444              if self.fill_pass_mode == FillPassMode::AllowRestart {
4445                return true; // restart
4446              } else if self.fill_pass_mode != FillPassMode::CacheBusting
4447                && restarted_pkgs.insert(package_name.clone())
4448              {
4449                self
4450                  .state
4451                  .jsr
4452                  .metadata
4453                  .remove_package_metadata(package_name);
4454                self.state.jsr.metadata.queue_load_package_info(
4455                  package_name,
4456                  CacheSetting::Reload, // force reload this specific package
4457                  JsrMetadataStoreServices {
4458                    executor: self.executor,
4459                    jsr_url_provider: self.jsr_url_provider,
4460                    loader: self.loader,
4461                  },
4462                );
4463                pending_resolutions.push_front(pending_resolution);
4464              } else {
4465                self.graph.module_slots.insert(
4466                  pending_resolution.specifier.clone(),
4467                  ModuleSlot::Err(
4468                    ModuleErrorKind::Load {
4469                      specifier: pending_resolution.specifier.clone(),
4470                      maybe_referrer: pending_resolution.maybe_range.clone(),
4471                      err: JsrLoadError::PackageReqNotFound(
4472                        package_req_not_found_err,
4473                      )
4474                      .into(),
4475                    }
4476                    .into_box(),
4477                  ),
4478                );
4479              }
4480            }
4481          }
4482        }
4483        Err(err) => {
4484          self.graph.module_slots.insert(
4485            pending_resolution.specifier.clone(),
4486            ModuleSlot::Err(
4487              ModuleErrorKind::Load {
4488                specifier: pending_resolution.specifier,
4489                maybe_referrer: pending_resolution.maybe_range,
4490                err: err.into(),
4491              }
4492              .into_box(),
4493            ),
4494          );
4495        }
4496      }
4497    }
4498
4499    // now resolve the version information
4500    for resolution_item in pending_version_resolutions {
4501      let nv = resolution_item.nv_ref.nv();
4502      let version_info_result = self
4503        .state
4504        .jsr
4505        .metadata
4506        .get_package_version_metadata(nv)
4507        .unwrap()
4508        .await;
4509      match version_info_result {
4510        Ok(version_info_load_item) => {
4511          let version_info = version_info_load_item.info;
4512          self.graph.packages.ensure_package(nv.clone());
4513          if let Some(locker) = &mut self.locker {
4514            if let Some(checksum) = version_info_load_item.checksum_for_locker {
4515              locker.set_pkg_manifest_checksum(nv, checksum);
4516            }
4517          }
4518          let base_url = self.jsr_url_provider.package_url(nv);
4519          let export_name = resolution_item.nv_ref.export_name();
4520          match version_info.export(&export_name) {
4521            Some(export_value) => {
4522              self.graph.packages.add_export(
4523                nv,
4524                (
4525                  resolution_item.nv_ref.export_name().into_owned(),
4526                  export_value.to_string(),
4527                ),
4528              );
4529              if should_collect_top_level_nvs {
4530                self.graph.packages.add_top_level_package(nv.clone());
4531              }
4532
4533              let specifier = base_url.join(export_value).unwrap();
4534              self
4535                .graph
4536                .redirects
4537                .insert(resolution_item.specifier, specifier.clone());
4538              if resolution_item.is_root {
4539                self.resolved_roots.insert(specifier.clone());
4540              }
4541              let version_info = JsrPackageVersionInfoExt {
4542                base_url,
4543                inner: version_info,
4544              };
4545              self.load(LoadOptionsRef {
4546                specifier: &specifier,
4547                maybe_range: resolution_item.maybe_range.as_ref(),
4548                is_asset: resolution_item.is_asset,
4549                in_dynamic_branch: resolution_item.is_dynamic,
4550                is_root: resolution_item.is_root,
4551                maybe_attribute_type: resolution_item.maybe_attribute_type,
4552                maybe_version_info: Some(&version_info),
4553              });
4554            }
4555            None => {
4556              self.graph.module_slots.insert(
4557                resolution_item.specifier.clone(),
4558                ModuleSlot::Err(
4559                  ModuleErrorKind::Load {
4560                    specifier: resolution_item.specifier,
4561                    maybe_referrer: resolution_item.maybe_range,
4562                    err: JsrLoadError::UnknownExport {
4563                      export_name: export_name.to_string(),
4564                      nv: Box::new(resolution_item.nv_ref.into_inner().nv),
4565                      exports: version_info
4566                        .exports()
4567                        .map(|(k, _)| k.to_string())
4568                        .collect::<Vec<_>>(),
4569                    }
4570                    .into(),
4571                  }
4572                  .into_box(),
4573                ),
4574              );
4575            }
4576          }
4577        }
4578        Err(err) => {
4579          self.graph.module_slots.insert(
4580            resolution_item.specifier.clone(),
4581            ModuleSlot::Err(
4582              ModuleErrorKind::Load {
4583                specifier: resolution_item.specifier,
4584                maybe_referrer: resolution_item.maybe_range,
4585                err: err.into(),
4586              }
4587              .into_box(),
4588            ),
4589          );
4590        }
4591      }
4592    }
4593
4594    false // no restart
4595  }
4596
4597  fn resolve_dynamic_branches(&mut self) {
4598    // Start visiting queued up dynamic branches. We do this in a separate
4599    // pass after all static dependencies have been visited because:
4600    // - If a module is both statically and dynamically imported, we want
4601    //   the static import to take precedence and only load it with
4602    //   `is_dynamic: false`.
4603    // - It's more convenient for tracking whether or not we are currently
4604    //   visiting a dynamic branch.
4605    if !self.in_dynamic_branch {
4606      self.in_dynamic_branch = true;
4607      for (specifier, dynamic_branch) in
4608        std::mem::take(&mut self.state.dynamic_branches)
4609      {
4610        self.load(LoadOptionsRef {
4611          specifier: &specifier,
4612          maybe_range: Some(&dynamic_branch.range),
4613          is_asset: dynamic_branch.is_asset,
4614          in_dynamic_branch: true,
4615          is_root: self.resolved_roots.contains(&specifier),
4616          maybe_attribute_type: dynamic_branch.maybe_attribute_type,
4617          maybe_version_info: dynamic_branch.maybe_version_info.as_ref(),
4618        });
4619      }
4620    }
4621  }
4622
4623  async fn handle_jsr_registry_pending_content_loads(&mut self) {
4624    while let Some(item) = self.state.jsr.pending_content_loads.next().await {
4625      match item.result {
4626        Ok(Some(response)) => {
4627          match response {
4628            LoadResponse::External { .. } => {
4629              // this should never happen, and if it does it indicates the loader
4630              // was setup incorrectly, so return an error
4631              self.graph.module_slots.insert(
4632                item.specifier.clone(),
4633                ModuleSlot::Err(
4634                  ModuleErrorKind::Load {
4635                    specifier: item.specifier,
4636                    maybe_referrer: item.maybe_range,
4637                    err: JsrLoadError::ContentLoadExternalSpecifier.into(),
4638                  }
4639                  .into_box(),
4640                ),
4641              );
4642            }
4643            LoadResponse::Module {
4644              content,
4645              specifier,
4646              mtime: _,
4647              maybe_headers: _maybe_headers,
4648            } if specifier == item.specifier => {
4649              // fill the existing module slot with the loaded source
4650              let slot = self.graph.module_slots.get_mut(&specifier).unwrap();
4651              match slot {
4652                ModuleSlot::Module(module) => {
4653                  match module {
4654                    Module::Js(module) => {
4655                      self.module_info_cacher.cache_module_info(
4656                        &specifier,
4657                        module.media_type,
4658                        &content,
4659                        &item.module_info,
4660                      );
4661                      match new_source_with_text(
4662                        &module.specifier,
4663                        content,
4664                        None, // no charset for JSR
4665                        None, // no mtime for JSR
4666                      ) {
4667                        Ok(source) => {
4668                          module.source = source;
4669                        }
4670                        Err(err) => *slot = ModuleSlot::Err(err),
4671                      }
4672                    }
4673                    Module::Json(module) => {
4674                      match new_source_with_text(
4675                        &module.specifier,
4676                        content,
4677                        None, // no charset for JSR
4678                        None, // no mtime for JSR
4679                      ) {
4680                        Ok(source) => {
4681                          module.source = source;
4682                        }
4683                        Err(err) => *slot = ModuleSlot::Err(err),
4684                      }
4685                    }
4686                    Module::Wasm(module) => {
4687                      match wasm_module_to_dts(&content) {
4688                        Ok(source_dts) => {
4689                          module.source = content.clone();
4690                          module.source_dts = source_dts.into();
4691                        }
4692                        Err(err) => {
4693                          *slot = ModuleSlot::Err(
4694                            ModuleErrorKind::WasmParse {
4695                              specifier: module.specifier.clone(),
4696                              mtime: None,
4697                              err,
4698                            }
4699                            .into_box(),
4700                          );
4701                        }
4702                      }
4703                    }
4704                    Module::Npm(_) | Module::Node(_) | Module::External(_) => {
4705                      unreachable!(); // should not happen by design
4706                    }
4707                  }
4708                }
4709                ModuleSlot::Err(_) => {
4710                  // the module errored some other way, so ignore
4711                }
4712                ModuleSlot::Pending { .. } => {
4713                  unreachable!(); // should not happen by design
4714                }
4715              }
4716            }
4717            LoadResponse::Redirect { specifier }
4718            | LoadResponse::Module { specifier, .. } => {
4719              // redirects are not supported
4720              self.graph.module_slots.insert(
4721                item.specifier.clone(),
4722                ModuleSlot::Err(
4723                  ModuleErrorKind::Load {
4724                    specifier: item.specifier,
4725                    maybe_referrer: item.maybe_range,
4726                    err: JsrLoadError::RedirectInPackage(specifier).into(),
4727                  }
4728                  .into_box(),
4729                ),
4730              );
4731            }
4732          }
4733        }
4734        Ok(None) => {
4735          self.graph.module_slots.insert(
4736            item.specifier.clone(),
4737            ModuleSlot::Err(
4738              ModuleErrorKind::Missing {
4739                specifier: item.specifier,
4740                maybe_referrer: item.maybe_range,
4741              }
4742              .into_box(),
4743            ),
4744          );
4745        }
4746        Err(err) => {
4747          self.graph.module_slots.insert(
4748            item.specifier.clone(),
4749            ModuleSlot::Err(
4750              ModuleErrorKind::Load {
4751                specifier: item.specifier,
4752                maybe_referrer: item.maybe_range,
4753                err: JsrLoadError::ContentLoad(Arc::new(err)).into(),
4754              }
4755              .into_box(),
4756            ),
4757          );
4758        }
4759      }
4760    }
4761  }
4762
4763  fn fill_graph_with_cache_info(&mut self) {
4764    if !self.loader.cache_info_enabled() {
4765      return;
4766    }
4767    for slot in self.graph.module_slots.values_mut() {
4768      if let ModuleSlot::Module(ref mut module) = slot {
4769        match module {
4770          Module::Json(module) => {
4771            module.maybe_cache_info =
4772              self.loader.get_cache_info(&module.specifier);
4773          }
4774          Module::Js(module) => {
4775            module.maybe_cache_info =
4776              self.loader.get_cache_info(&module.specifier);
4777          }
4778          Module::Wasm(module) => {
4779            module.maybe_cache_info =
4780              self.loader.get_cache_info(&module.specifier);
4781          }
4782          Module::External(module) => {
4783            module.maybe_cache_info =
4784              self.loader.get_cache_info(&module.specifier);
4785          }
4786          Module::Npm(_) | Module::Node(_) => {}
4787        }
4788      }
4789    }
4790  }
4791
4792  fn restart(
4793    &mut self,
4794    roots: Vec<ModuleSpecifier>,
4795    imports: Vec<ReferrerImports>,
4796  ) -> LocalBoxFuture<'_, ()> {
4797    // if restarting is allowed, then the graph will have been empty at the start
4798    *self.graph = ModuleGraph::new(self.graph.graph_kind);
4799    self.state = PendingState::default();
4800    self.fill_pass_mode = FillPassMode::CacheBusting;
4801
4802    // boxed due to async recursion
4803    async move { self.build(roots, imports).await }.boxed_local()
4804  }
4805
4806  fn resolve_jsr_nv(
4807    &mut self,
4808    package_req: &PackageReq,
4809    package_info: &JsrPackageInfo,
4810  ) -> Result<PackageNv, JsrPackageReqNotFoundError> {
4811    let resolved_version = self.jsr_version_resolver.resolve_version(
4812      package_req,
4813      package_info,
4814      self
4815        .graph
4816        .packages
4817        .versions_by_name(&package_req.name)
4818        .into_iter()
4819        .flatten()
4820        .map(|nv| &nv.version),
4821    )?;
4822    let version = resolved_version.version.clone();
4823    if resolved_version.is_yanked {
4824      self.graph.packages.add_used_yanked_package(PackageNv {
4825        name: package_req.name.clone(),
4826        version: version.clone(),
4827      });
4828    }
4829    let package_nv = PackageNv {
4830      name: package_req.name.clone(),
4831      version,
4832    };
4833    if let Some(reporter) = &self.reporter {
4834      reporter.on_resolve(package_req, &package_nv);
4835    }
4836    self
4837      .graph
4838      .packages
4839      .add_nv(package_req.clone(), package_nv.clone());
4840    Ok(package_nv)
4841  }
4842
4843  /// Checks if the specifier is redirected or not and updates any redirects in
4844  /// the graph.
4845  fn check_specifier(
4846    &mut self,
4847    requested_specifier: &ModuleSpecifier,
4848    specifier: &ModuleSpecifier,
4849  ) {
4850    // If the response was redirected, then we add the module to the redirects
4851    if requested_specifier != specifier {
4852      self.add_redirect(requested_specifier.clone(), specifier.clone());
4853    }
4854  }
4855
4856  fn add_redirect(
4857    &mut self,
4858    requested_specifier: ModuleSpecifier,
4859    specifier: ModuleSpecifier,
4860  ) {
4861    debug_assert_ne!(requested_specifier, specifier);
4862    // remove a potentially pending redirect that will never resolve
4863    if let Some(slot) = self.graph.module_slots.get(&requested_specifier) {
4864      if matches!(slot, ModuleSlot::Pending { .. }) {
4865        self.graph.module_slots.remove(&requested_specifier);
4866      }
4867    }
4868
4869    self
4870      .graph
4871      .redirects
4872      .entry(requested_specifier)
4873      .or_insert(specifier);
4874  }
4875
4876  /// Enqueue a request to load the specifier via the loader.
4877  fn load(&mut self, options: LoadOptionsRef) {
4878    self.load_with_redirect_count(0, options)
4879  }
4880
4881  #[allow(clippy::too_many_arguments)]
4882  fn load_with_redirect_count(
4883    &mut self,
4884    redirect_count: usize,
4885    options: LoadOptionsRef,
4886  ) {
4887    let specifier = options.specifier;
4888    let maybe_range = options.maybe_range;
4889    let original_specifier = specifier;
4890    let specifier = self.graph.redirects.get(specifier).unwrap_or(specifier);
4891    if options.is_asset {
4892      // this will always be set when an asset
4893      if let Some(attribute) = &options.maybe_attribute_type {
4894        let is_allowed = match attribute.kind.as_str() {
4895          "bytes" => self.unstable_bytes_imports,
4896          "text" => self.unstable_text_imports,
4897          _ => false,
4898        };
4899        if !is_allowed {
4900          self.graph.module_slots.insert(
4901            specifier.clone(),
4902            ModuleSlot::Err(
4903              ModuleErrorKind::UnsupportedImportAttributeType {
4904                specifier: specifier.clone(),
4905                referrer: attribute.range.clone(),
4906                kind: attribute.kind.clone(),
4907              }
4908              .into_box(),
4909            ),
4910          );
4911          return;
4912        }
4913      }
4914    }
4915    if let Some(module_slot) = self.graph.module_slots.get(specifier) {
4916      let should_reload_immediately =
4917        module_slot.was_external_asset_load() && !options.is_asset;
4918      if !should_reload_immediately {
4919        let should_defer_reload =
4920          module_slot.is_pending_asset_load() && !options.is_asset;
4921        if should_defer_reload {
4922          // this will occur when something pending is only being cached
4923          // and in this case we'll defer loading until after it's been
4924          // cached as we don't want to start another or cancel the currently
4925          // loading specifier
4926          self
4927            .state
4928            .deferred
4929            .entry(specifier.clone())
4930            .or_insert_with(|| DeferredLoad {
4931              maybe_range: maybe_range.cloned(),
4932              in_dynamic_branch: options.in_dynamic_branch,
4933              is_root: options.is_root,
4934              maybe_attribute_type: options.maybe_attribute_type,
4935              maybe_version_info: options.maybe_version_info.cloned(),
4936            });
4937        }
4938
4939        // ensure any jsr/npm dependencies that we've already seen are marked
4940        // as a dependency of the referrer
4941        if matches!(original_specifier.scheme(), "jsr" | "npm") {
4942          if let Ok(load_specifier) =
4943            self.parse_load_specifier_kind(original_specifier, maybe_range)
4944          {
4945            self.maybe_mark_dep(&load_specifier, maybe_range);
4946          }
4947        }
4948
4949        return;
4950      }
4951    }
4952
4953    if let Some(version_info) = options.maybe_version_info {
4954      let specifier = specifier.clone();
4955      if let Some(sub_path) = version_info.get_subpath(&specifier) {
4956        self.load_jsr_subpath(
4957          redirect_count,
4958          &specifier,
4959          version_info,
4960          sub_path,
4961          options,
4962        );
4963        return;
4964      }
4965    }
4966
4967    let specifier = specifier.clone();
4968    match self.parse_load_specifier_kind(&specifier, maybe_range) {
4969      Ok(LoadSpecifierKind::Jsr(package_req_ref)) => {
4970        self.mark_jsr_dep(&package_req_ref, maybe_range);
4971        if self.passthrough_jsr_specifiers {
4972          // mark external
4973          self.graph.module_slots.insert(
4974            specifier.clone(),
4975            ModuleSlot::Module(Module::External(ExternalModule {
4976              specifier: specifier.clone(),
4977              maybe_cache_info: None,
4978              was_asset_load: false,
4979            })),
4980          );
4981        } else {
4982          self.load_jsr_specifier(
4983            specifier,
4984            package_req_ref,
4985            options.maybe_attribute_type,
4986            maybe_range,
4987            options.is_asset,
4988            options.in_dynamic_branch,
4989            options.is_root,
4990          );
4991        }
4992      }
4993      Ok(LoadSpecifierKind::Npm(package_req_ref)) => {
4994        self.mark_npm_dep(&package_req_ref, maybe_range);
4995        if let Some(npm_resolver) = self.npm_resolver {
4996          self.load_npm_specifier(
4997            npm_resolver,
4998            specifier.clone(),
4999            package_req_ref,
5000            maybe_range,
5001            options.in_dynamic_branch,
5002          );
5003        } else {
5004          // mark external
5005          self.graph.module_slots.insert(
5006            specifier.clone(),
5007            ModuleSlot::Module(Module::External(ExternalModule {
5008              maybe_cache_info: None,
5009              specifier: specifier.clone(),
5010              was_asset_load: false,
5011            })),
5012          );
5013        }
5014      }
5015      Ok(LoadSpecifierKind::Node(module_name)) => {
5016        self.graph.has_node_specifier = true;
5017        self.graph.module_slots.insert(
5018          specifier.clone(),
5019          ModuleSlot::Module(Module::Node(BuiltInNodeModule {
5020            specifier: specifier.clone(),
5021            module_name,
5022          })),
5023        );
5024      }
5025      Ok(LoadSpecifierKind::Url) => {
5026        self.load_pending_module(PendingModuleLoadItem {
5027          redirect_count,
5028          requested_specifier: specifier.clone(),
5029          maybe_attribute_type: options.maybe_attribute_type,
5030          maybe_range: maybe_range.cloned(),
5031          load_specifier: specifier.clone(),
5032          is_asset: options.is_asset,
5033          in_dynamic_branch: options.in_dynamic_branch,
5034          is_root: options.is_root,
5035          maybe_checksum: None,
5036          maybe_version_info: None,
5037        });
5038      }
5039      Err(err) => {
5040        self
5041          .graph
5042          .module_slots
5043          .insert(specifier.clone(), ModuleSlot::Err(err));
5044      }
5045    }
5046  }
5047
5048  fn load_jsr_subpath(
5049    &mut self,
5050    redirect_count: usize,
5051    specifier: &ModuleSpecifier,
5052    version_info: &JsrPackageVersionInfoExt,
5053    sub_path: &str,
5054    options: LoadOptionsRef,
5055  ) {
5056    struct ProvidedModuleAnalyzer(RefCell<Option<ModuleInfo>>);
5057
5058    #[async_trait::async_trait(?Send)]
5059    impl ModuleAnalyzer for ProvidedModuleAnalyzer {
5060      async fn analyze(
5061        &self,
5062        _specifier: &ModuleSpecifier,
5063        _source: Arc<str>,
5064        _media_type: MediaType,
5065      ) -> Result<ModuleInfo, JsErrorBox> {
5066        Ok(self.0.borrow_mut().take().unwrap()) // will only be called once
5067      }
5068    }
5069
5070    let checksum = match version_info.get_checksum(sub_path) {
5071      Ok(checksum) => checksum,
5072      Err(err) => {
5073        self.graph.module_slots.insert(
5074          specifier.clone(),
5075          ModuleSlot::Err(
5076            ModuleErrorKind::Load {
5077              specifier: specifier.clone(),
5078              maybe_referrer: options.maybe_range.cloned(),
5079              err,
5080            }
5081            .into_box(),
5082          ),
5083        );
5084        return;
5085      }
5086    };
5087    let checksum = LoaderChecksum::new(checksum.to_string());
5088
5089    if let Some(module_info) = (!options.is_asset)
5090      .then(|| version_info.inner.module_info(sub_path))
5091      .flatten()
5092    {
5093      // Check if this specifier is in the cache. If it is, then
5094      // don't use the module information as it may be out of date
5095      // with what's in the cache
5096      let fut = self.loader.load(
5097        specifier,
5098        LoadOptions {
5099          in_dynamic_branch: self.in_dynamic_branch,
5100          was_dynamic_root: self.was_dynamic_root,
5101          cache_setting: CacheSetting::Only,
5102          maybe_checksum: Some(checksum.clone()),
5103        },
5104      );
5105      let is_dynamic_branch = self.in_dynamic_branch;
5106      let module_analyzer = self.module_analyzer;
5107      self.state.pending.push_back({
5108        let requested_specifier = specifier.clone();
5109        let maybe_range = options.maybe_range.cloned();
5110        let version_info = version_info.clone();
5111        async move {
5112          let response = fut.await;
5113          let result = match response {
5114            Ok(None) => {
5115              parse_module_source_and_info(
5116                &ProvidedModuleAnalyzer(RefCell::new(Some(
5117                  module_info.clone(),
5118                ))),
5119                ParseModuleAndSourceInfoOptions {
5120                  specifier: requested_specifier.clone(),
5121                  maybe_headers: Default::default(),
5122                  mtime: None, // remote, no mtime
5123                  // we'll load the content later
5124                  content: if MediaType::from_specifier(&requested_specifier)
5125                    == MediaType::Wasm
5126                  {
5127                    // TODO(#561): temporary hack until a larger refactor can be done
5128                    Arc::new([
5129                      // minimum allowed wasm module
5130                      0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x0A,
5131                      0x01, 0x00,
5132                    ])
5133                  } else {
5134                    Arc::new([]) as Arc<[u8]>
5135                  },
5136                  maybe_attribute_type: options.maybe_attribute_type.as_ref(),
5137                  maybe_referrer: maybe_range.as_ref(),
5138                  is_root: options.is_root,
5139                  is_dynamic_branch,
5140                },
5141              )
5142              .await
5143              .map(|module_source_and_info| {
5144                PendingInfoResponse::Module {
5145                  specifier: requested_specifier.clone(),
5146                  module_source_and_info,
5147                  pending_load: Some(Box::new((checksum, module_info))),
5148                  is_root: options.is_root,
5149                }
5150              })
5151            }
5152            Ok(Some(response)) => match response {
5153              LoadResponse::External { specifier } => {
5154                Ok(PendingInfoResponse::External {
5155                  specifier,
5156                  is_root: options.is_root,
5157                  is_asset: false,
5158                })
5159              }
5160              LoadResponse::Redirect { specifier } => Err(
5161                ModuleErrorKind::Load {
5162                  specifier: requested_specifier.clone(),
5163                  maybe_referrer: maybe_range.clone(),
5164                  err: JsrLoadError::RedirectInPackage(specifier).into(),
5165                }
5166                .into_box(),
5167              ),
5168              LoadResponse::Module {
5169                content,
5170                specifier,
5171                mtime,
5172                maybe_headers,
5173              } => parse_module_source_and_info(
5174                module_analyzer,
5175                ParseModuleAndSourceInfoOptions {
5176                  specifier: specifier.clone(),
5177                  maybe_headers,
5178                  mtime,
5179                  content,
5180                  maybe_attribute_type: options.maybe_attribute_type.as_ref(),
5181                  maybe_referrer: maybe_range.as_ref(),
5182                  is_root: options.is_root,
5183                  is_dynamic_branch,
5184                },
5185              )
5186              .await
5187              .map(|module_source_and_info| {
5188                PendingInfoResponse::Module {
5189                  specifier: specifier.clone(),
5190                  module_source_and_info,
5191                  pending_load: None,
5192                  is_root: options.is_root,
5193                }
5194              }),
5195            },
5196            Err(err) => Err(
5197              ModuleErrorKind::Load {
5198                specifier: requested_specifier.clone(),
5199                maybe_referrer: maybe_range.clone(),
5200                err: ModuleLoadError::Loader(Arc::new(err)),
5201              }
5202              .into_box(),
5203            ),
5204          };
5205          PendingInfo {
5206            requested_specifier,
5207            maybe_range,
5208            result,
5209            maybe_version_info: Some(version_info),
5210            loaded_package_via_https_url: None,
5211          }
5212        }
5213        .boxed_local()
5214      });
5215      self
5216        .graph
5217        .module_slots
5218        .insert(specifier.clone(), ModuleSlot::Pending { is_asset: false });
5219    } else {
5220      self.load_pending_module(PendingModuleLoadItem {
5221        redirect_count,
5222        requested_specifier: specifier.clone(),
5223        maybe_attribute_type: options.maybe_attribute_type,
5224        maybe_range: options.maybe_range.cloned(),
5225        load_specifier: specifier.clone(),
5226        is_asset: options.is_asset,
5227        in_dynamic_branch: options.in_dynamic_branch,
5228        is_root: options.is_root,
5229        maybe_checksum: Some(checksum),
5230        maybe_version_info: Some(version_info.clone()),
5231      });
5232    }
5233  }
5234
5235  #[allow(clippy::too_many_arguments)]
5236  fn load_jsr_specifier(
5237    &mut self,
5238    specifier: Url,
5239    package_ref: JsrPackageReqReference,
5240    maybe_attribute_type: Option<AttributeTypeWithRange>,
5241    maybe_range: Option<&Range>,
5242    is_asset: bool,
5243    in_dynamic_branch: bool,
5244    is_root: bool,
5245  ) {
5246    let package_name = &package_ref.req().name;
5247    let specifier = specifier.clone();
5248    self.queue_load_package_info(package_name);
5249    self
5250      .state
5251      .jsr
5252      .pending_resolutions
5253      .push_back(PendingJsrReqResolutionItem {
5254        specifier,
5255        package_ref,
5256        maybe_attribute_type,
5257        maybe_range: maybe_range.cloned(),
5258        in_dynamic_branch,
5259        is_asset,
5260        is_root,
5261      });
5262  }
5263
5264  fn load_npm_specifier(
5265    &mut self,
5266    npm_resolver: &dyn NpmResolver,
5267    specifier: Url,
5268    package_ref: NpmPackageReqReference,
5269    maybe_range: Option<&Range>,
5270    in_dynamic_branch: bool,
5271  ) {
5272    if self
5273      .state
5274      .npm
5275      .requested_registry_info_loads
5276      .insert(package_ref.req().name.clone())
5277    {
5278      // request to load
5279      npm_resolver.load_and_cache_npm_package_info(&package_ref.req().name);
5280    }
5281
5282    self
5283      .state
5284      .npm
5285      .pending_resolutions
5286      .push(PendingNpmResolutionItem {
5287        specifier,
5288        package_ref,
5289        maybe_range: maybe_range.cloned(),
5290        in_dynamic_branch,
5291      });
5292  }
5293
5294  fn parse_load_specifier_kind(
5295    &self,
5296    specifier: &Url,
5297    maybe_range: Option<&Range>,
5298  ) -> Result<LoadSpecifierKind, ModuleError> {
5299    match specifier.scheme() {
5300      "jsr" => validate_jsr_specifier(specifier)
5301        .map(LoadSpecifierKind::Jsr)
5302        .map_err(|err| {
5303          ModuleErrorKind::Load {
5304            specifier: specifier.clone(),
5305            maybe_referrer: maybe_range.cloned(),
5306            err: JsrLoadError::PackageFormat(err).into(),
5307          }
5308          .into_box()
5309        }),
5310      "npm" => NpmPackageReqReference::from_specifier(specifier)
5311        .map(LoadSpecifierKind::Npm)
5312        .map_err(|err| {
5313          ModuleErrorKind::Load {
5314            specifier: specifier.clone(),
5315            maybe_referrer: maybe_range.cloned(),
5316            err: NpmLoadError::PackageReqReferenceParse(err).into(),
5317          }
5318          .into_box()
5319        }),
5320      "node" => Ok(LoadSpecifierKind::Node(specifier.path().to_string())),
5321      _ => Ok(LoadSpecifierKind::Url),
5322    }
5323  }
5324
5325  fn maybe_mark_dep(
5326    &mut self,
5327    load_specifier: &LoadSpecifierKind,
5328    maybe_range: Option<&Range>,
5329  ) {
5330    match load_specifier {
5331      LoadSpecifierKind::Jsr(package_ref) => {
5332        self.mark_jsr_dep(package_ref, maybe_range);
5333      }
5334      LoadSpecifierKind::Npm(package_ref) => {
5335        self.mark_npm_dep(package_ref, maybe_range);
5336      }
5337      LoadSpecifierKind::Node(_) | LoadSpecifierKind::Url => {
5338        // ignore the rest
5339      }
5340    }
5341  }
5342
5343  fn mark_jsr_dep(
5344    &mut self,
5345    package_ref: &JsrPackageReqReference,
5346    maybe_range: Option<&Range>,
5347  ) {
5348    if let Some(range) = &maybe_range {
5349      if let Some(nv) =
5350        self.jsr_url_provider.package_url_to_nv(&range.specifier)
5351      {
5352        self.graph.packages.add_dependency(
5353          &nv,
5354          JsrDepPackageReq::jsr(package_ref.req().clone()),
5355        );
5356      }
5357    }
5358  }
5359
5360  fn mark_npm_dep(
5361    &mut self,
5362    package_ref: &NpmPackageReqReference,
5363    maybe_range: Option<&Range>,
5364  ) {
5365    if let Some(range) = &maybe_range {
5366      if let Some(nv) =
5367        self.jsr_url_provider.package_url_to_nv(&range.specifier)
5368      {
5369        self.graph.packages.add_dependency(
5370          &nv,
5371          JsrDepPackageReq::npm(package_ref.req().clone()),
5372        );
5373      }
5374    }
5375  }
5376
5377  fn load_pending_module(&mut self, item: PendingModuleLoadItem) {
5378    let PendingModuleLoadItem {
5379      redirect_count,
5380      requested_specifier,
5381      maybe_attribute_type,
5382      maybe_range,
5383      load_specifier,
5384      in_dynamic_branch,
5385      is_asset,
5386      is_root,
5387      mut maybe_checksum,
5388      mut maybe_version_info,
5389    } = item;
5390    self.graph.module_slots.insert(
5391      requested_specifier.clone(),
5392      ModuleSlot::Pending { is_asset },
5393    );
5394    let loader = self.loader;
5395    let module_analyzer = self.module_analyzer;
5396    let jsr_url_provider = self.jsr_url_provider;
5397    let was_dynamic_root = self.was_dynamic_root;
5398    let maybe_nv_when_no_version_info = if maybe_version_info.is_none() {
5399      self
5400        .jsr_url_provider
5401        .package_url_to_nv(&requested_specifier)
5402    } else {
5403      None
5404    };
5405    let maybe_version_load_fut =
5406      maybe_nv_when_no_version_info.map(|package_nv| {
5407        self.queue_load_package_version_info(&package_nv);
5408        let fut = self
5409          .state
5410          .jsr
5411          .metadata
5412          .get_package_version_metadata(&package_nv)
5413          .unwrap();
5414        (package_nv, fut)
5415      });
5416    if maybe_checksum.is_none() {
5417      maybe_checksum = self
5418        .locker
5419        .as_ref()
5420        .and_then(|l| l.get_remote_checksum(&requested_specifier));
5421    }
5422    let fut = async move {
5423      #[allow(clippy::too_many_arguments)]
5424      async fn try_load(
5425        is_root: bool,
5426        redirect_count: usize,
5427        load_specifier: ModuleSpecifier,
5428        mut maybe_checksum: Option<LoaderChecksum>,
5429        maybe_range: Option<&Range>,
5430        maybe_version_info: &mut Option<JsrPackageVersionInfoExt>,
5431        maybe_attribute_type: Option<AttributeTypeWithRange>,
5432        loaded_package_via_https_url: &mut Option<LoadedJsrPackageViaHttpsUrl>,
5433        maybe_version_load_fut: Option<(
5434          PackageNv,
5435          PendingResult<PendingJsrPackageVersionInfoLoadItem>,
5436        )>,
5437        is_asset: bool,
5438        in_dynamic_branch: bool,
5439        was_dynamic_root: bool,
5440        loader: &dyn Loader,
5441        jsr_url_provider: &dyn JsrUrlProvider,
5442        module_analyzer: &dyn ModuleAnalyzer,
5443      ) -> Result<PendingInfoResponse, ModuleError> {
5444        async fn handle_success(
5445          module_analyzer: &dyn ModuleAnalyzer,
5446          specifier: Url,
5447          options: ParseModuleAndSourceInfoOptions<'_>,
5448        ) -> Result<PendingInfoResponse, ModuleError> {
5449          let is_root = options.is_root;
5450          parse_module_source_and_info(module_analyzer, options)
5451            .await
5452            .map(|module_source_and_info| PendingInfoResponse::Module {
5453              specifier: specifier.clone(),
5454              module_source_and_info,
5455              pending_load: None,
5456              is_root,
5457            })
5458        }
5459
5460        if let Some((package_nv, fut)) = maybe_version_load_fut {
5461          let inner = fut.await.map_err(|err| {
5462            ModuleErrorKind::Load {
5463              specifier: jsr_url_provider.package_url(&package_nv),
5464              maybe_referrer: maybe_range.cloned(),
5465              err: err.into(),
5466            }
5467            .into_box()
5468          })?;
5469          let info = JsrPackageVersionInfoExt {
5470            base_url: jsr_url_provider.package_url(&package_nv),
5471            inner: inner.info,
5472          };
5473          if let Some(sub_path) = info.get_subpath(&load_specifier) {
5474            maybe_checksum = Some(LoaderChecksum::new(
5475              info
5476                .get_checksum(sub_path)
5477                .map_err(|err| {
5478                  ModuleErrorKind::Load {
5479                    specifier: load_specifier.clone(),
5480                    maybe_referrer: maybe_range.cloned(),
5481                    err,
5482                  }
5483                  .into_box()
5484                })?
5485                .to_string(),
5486            ));
5487          }
5488          maybe_version_info.replace(info);
5489          loaded_package_via_https_url.replace(LoadedJsrPackageViaHttpsUrl {
5490            nv: package_nv,
5491            manifest_checksum_for_locker: inner.checksum_for_locker,
5492          });
5493        }
5494
5495        let load_options = LoadOptions {
5496          in_dynamic_branch,
5497          was_dynamic_root,
5498          cache_setting: CacheSetting::Use,
5499          maybe_checksum: maybe_checksum.clone(),
5500        };
5501
5502        let handle_redirect =
5503          |specifier: Url,
5504           maybe_attribute_type: Option<AttributeTypeWithRange>,
5505           maybe_checksum: Option<LoaderChecksum>| {
5506            if maybe_version_info.is_some() {
5507              // This should never happen on the JSR registry. If we ever
5508              // supported this we'd need a way for the registry to express
5509              // redirects in the manifest since we don't store checksums
5510              // or redirect information within the package.
5511              Err(
5512                ModuleErrorKind::Load {
5513                  specifier: load_specifier.clone(),
5514                  maybe_referrer: maybe_range.cloned(),
5515                  err: JsrLoadError::RedirectInPackage(specifier.clone())
5516                    .into(),
5517                }
5518                .into_box(),
5519              )
5520            } else if let Some(expected_checksum) = maybe_checksum {
5521              Err(
5522                ModuleErrorKind::Load {
5523                  specifier: load_specifier.clone(),
5524                  maybe_referrer: maybe_range.cloned(),
5525                  err: ModuleLoadError::HttpsChecksumIntegrity(
5526                    ChecksumIntegrityError {
5527                      actual: format!("Redirect to {}", specifier),
5528                      expected: expected_checksum.into_string(),
5529                    },
5530                  ),
5531                }
5532                .into_box(),
5533              )
5534            } else if redirect_count >= loader.max_redirects() {
5535              Err(
5536                ModuleErrorKind::Load {
5537                  specifier: load_specifier.clone(),
5538                  maybe_referrer: maybe_range.cloned(),
5539                  err: ModuleLoadError::TooManyRedirects,
5540                }
5541                .into_box(),
5542              )
5543            } else {
5544              Ok(PendingInfoResponse::Redirect {
5545                count: redirect_count + 1,
5546                specifier,
5547                maybe_attribute_type,
5548                is_asset,
5549                is_dynamic: in_dynamic_branch,
5550                is_root,
5551              })
5552            }
5553          };
5554
5555        if is_asset {
5556          let result = loader.ensure_cached(&load_specifier, load_options);
5557          return match result.await {
5558            Ok(Some(CacheResponse::Cached)) => {
5559              Ok(PendingInfoResponse::External {
5560                specifier: load_specifier,
5561                is_root,
5562                is_asset,
5563              })
5564            }
5565            Ok(Some(CacheResponse::Redirect { specifier })) => {
5566              handle_redirect(specifier, maybe_attribute_type, maybe_checksum)
5567            }
5568            Ok(None) => Err(
5569              ModuleErrorKind::Missing {
5570                specifier: load_specifier.clone(),
5571                maybe_referrer: maybe_range.cloned(),
5572              }
5573              .into_box(),
5574            ),
5575            Err(LoadError::ChecksumIntegrity(err)) => {
5576              if maybe_version_info.is_none() {
5577                // attempt to cache bust because the remote server might have changed
5578                let result = loader
5579                  .ensure_cached(
5580                    &load_specifier,
5581                    LoadOptions {
5582                      in_dynamic_branch,
5583                      was_dynamic_root,
5584                      cache_setting: CacheSetting::Reload,
5585                      maybe_checksum: maybe_checksum.clone(),
5586                    },
5587                  )
5588                  .await;
5589                if let Ok(Some(CacheResponse::Cached)) = result {
5590                  return Ok(PendingInfoResponse::External {
5591                    specifier: load_specifier,
5592                    is_root,
5593                    is_asset,
5594                  });
5595                }
5596              }
5597
5598              Err(
5599                ModuleErrorKind::Load {
5600                  specifier: load_specifier.clone(),
5601                  maybe_referrer: maybe_range.cloned(),
5602                  err: if maybe_version_info.is_some() {
5603                    ModuleLoadError::Jsr(
5604                      JsrLoadError::ContentChecksumIntegrity(err),
5605                    )
5606                  } else {
5607                    ModuleLoadError::HttpsChecksumIntegrity(err)
5608                  },
5609                }
5610                .into_box(),
5611              )
5612            }
5613            Err(err) => Err(
5614              ModuleErrorKind::Load {
5615                specifier: load_specifier.clone(),
5616                maybe_referrer: maybe_range.cloned(),
5617                err: ModuleLoadError::Loader(Arc::new(err)),
5618              }
5619              .into_box(),
5620            ),
5621          };
5622        }
5623
5624        let result = loader.load(&load_specifier, load_options);
5625        match result.await {
5626          Ok(Some(response)) => match response {
5627            LoadResponse::Redirect { specifier } => {
5628              handle_redirect(specifier, maybe_attribute_type, maybe_checksum)
5629            }
5630            LoadResponse::External { specifier } => {
5631              Ok(PendingInfoResponse::External {
5632                specifier,
5633                is_root,
5634                is_asset,
5635              })
5636            }
5637            LoadResponse::Module {
5638              content,
5639              mtime,
5640              specifier,
5641              maybe_headers,
5642            } => {
5643              handle_success(
5644                module_analyzer,
5645                specifier.clone(),
5646                ParseModuleAndSourceInfoOptions {
5647                  specifier,
5648                  maybe_headers,
5649                  mtime,
5650                  content,
5651                  maybe_attribute_type: maybe_attribute_type.as_ref(),
5652                  maybe_referrer: maybe_range,
5653                  is_root,
5654                  is_dynamic_branch: in_dynamic_branch,
5655                },
5656              )
5657              .await
5658            }
5659          },
5660          Ok(None) => Err(
5661            ModuleErrorKind::Missing {
5662              specifier: load_specifier.clone(),
5663              maybe_referrer: maybe_range.cloned(),
5664            }
5665            .into_box(),
5666          ),
5667          Err(LoadError::ChecksumIntegrity(err)) => {
5668            if maybe_version_info.is_none() {
5669              // attempt to cache bust because the remote server might have changed
5670              let result = loader
5671                .load(
5672                  &load_specifier,
5673                  LoadOptions {
5674                    in_dynamic_branch,
5675                    was_dynamic_root,
5676                    cache_setting: CacheSetting::Reload,
5677                    maybe_checksum: maybe_checksum.clone(),
5678                  },
5679                )
5680                .await;
5681              if let Ok(Some(LoadResponse::Module {
5682                specifier,
5683                maybe_headers,
5684                mtime,
5685                content,
5686              })) = result
5687              {
5688                return handle_success(
5689                  module_analyzer,
5690                  specifier.clone(),
5691                  ParseModuleAndSourceInfoOptions {
5692                    specifier,
5693                    maybe_headers,
5694                    mtime,
5695                    content,
5696                    maybe_attribute_type: maybe_attribute_type.as_ref(),
5697                    maybe_referrer: maybe_range,
5698                    is_root,
5699                    is_dynamic_branch: in_dynamic_branch,
5700                  },
5701                )
5702                .await;
5703              }
5704            }
5705
5706            Err(
5707              ModuleErrorKind::Load {
5708                specifier: load_specifier.clone(),
5709                maybe_referrer: maybe_range.cloned(),
5710                err: if maybe_version_info.is_some() {
5711                  ModuleLoadError::Jsr(JsrLoadError::ContentChecksumIntegrity(
5712                    err,
5713                  ))
5714                } else {
5715                  ModuleLoadError::HttpsChecksumIntegrity(err)
5716                },
5717              }
5718              .into_box(),
5719            )
5720          }
5721          Err(err) => Err(
5722            ModuleErrorKind::Load {
5723              specifier: load_specifier.clone(),
5724              maybe_referrer: maybe_range.cloned(),
5725              err: ModuleLoadError::Loader(Arc::new(err)),
5726            }
5727            .into_box(),
5728          ),
5729        }
5730      }
5731
5732      let mut loaded_package_via_https_url = None;
5733      let result = try_load(
5734        is_root,
5735        redirect_count,
5736        load_specifier,
5737        maybe_checksum,
5738        maybe_range.as_ref(),
5739        &mut maybe_version_info,
5740        maybe_attribute_type,
5741        &mut loaded_package_via_https_url,
5742        maybe_version_load_fut,
5743        is_asset,
5744        in_dynamic_branch,
5745        was_dynamic_root,
5746        loader,
5747        jsr_url_provider,
5748        module_analyzer,
5749      )
5750      .await;
5751
5752      PendingInfo {
5753        result,
5754        requested_specifier,
5755        maybe_range,
5756        maybe_version_info,
5757        loaded_package_via_https_url,
5758      }
5759    }
5760    .boxed_local();
5761    self.state.pending.push_back(fut);
5762  }
5763
5764  fn queue_load_package_info(&mut self, package_name: &str) {
5765    self.state.jsr.metadata.queue_load_package_info(
5766      package_name,
5767      self.fill_pass_mode.to_cache_setting(),
5768      JsrMetadataStoreServices {
5769        executor: self.executor,
5770        jsr_url_provider: self.jsr_url_provider,
5771        loader: self.loader,
5772      },
5773    );
5774  }
5775
5776  fn queue_load_package_version_info(&mut self, package_nv: &PackageNv) {
5777    self.state.jsr.metadata.queue_load_package_version_info(
5778      package_nv,
5779      self.fill_pass_mode.to_cache_setting(),
5780      self.locker.as_deref(),
5781      JsrMetadataStoreServices {
5782        executor: self.executor,
5783        jsr_url_provider: self.jsr_url_provider,
5784        loader: self.loader,
5785      },
5786    );
5787  }
5788
5789  fn visit(
5790    &mut self,
5791    response: PendingInfoResponse,
5792    maybe_referrer: Option<Range>,
5793    maybe_version_info: Option<&JsrPackageVersionInfoExt>,
5794  ) {
5795    match response {
5796      PendingInfoResponse::External {
5797        specifier,
5798        is_root,
5799        is_asset,
5800      } => {
5801        if is_root {
5802          self.resolved_roots.insert(specifier.clone());
5803        }
5804        if let Some(entry) = self.graph.module_slots.get_mut(&specifier) {
5805          if entry.is_pending() {
5806            *entry = ModuleSlot::Module(Module::External(ExternalModule {
5807              maybe_cache_info: None,
5808              specifier,
5809              was_asset_load: is_asset,
5810            }));
5811          }
5812        } else {
5813          self.graph.module_slots.insert(
5814            specifier.clone(),
5815            ModuleSlot::Module(Module::External(ExternalModule {
5816              maybe_cache_info: None,
5817              specifier,
5818              was_asset_load: is_asset,
5819            })),
5820          );
5821        }
5822      }
5823      PendingInfoResponse::Module {
5824        specifier,
5825        pending_load,
5826        module_source_and_info,
5827        is_root,
5828      } => {
5829        // this should have been handled by now
5830        debug_assert_eq!(
5831          maybe_version_info.is_none(),
5832          self
5833            .jsr_url_provider
5834            .package_url_to_nv(&specifier)
5835            .is_none(),
5836          "{}",
5837          specifier
5838        );
5839
5840        if is_root {
5841          self.resolved_roots.insert(specifier.clone());
5842        }
5843
5844        if let Some((checksum, module_info)) = pending_load.map(|v| *v) {
5845          self.state.jsr.pending_content_loads.push({
5846            let specifier = specifier.clone();
5847            let maybe_range = maybe_referrer.clone();
5848            let fut = self.loader.load(
5849              &specifier,
5850              LoadOptions {
5851                in_dynamic_branch: self.in_dynamic_branch,
5852                was_dynamic_root: self.was_dynamic_root,
5853                cache_setting: CacheSetting::Use,
5854                maybe_checksum: Some(checksum.clone()),
5855              },
5856            );
5857            async move {
5858              let result = fut.await;
5859              PendingContentLoadItem {
5860                specifier,
5861                maybe_range,
5862                result,
5863                module_info,
5864              }
5865            }
5866            .boxed_local()
5867          });
5868        } else if maybe_version_info.is_none()
5869          // do not insert checksums for declaration files
5870          && !module_source_and_info.media_type().is_declaration()
5871          && matches!(specifier.scheme(), "https" | "http")
5872        {
5873          if let Some(locker) = &mut self.locker {
5874            if !locker.has_remote_checksum(&specifier) {
5875              locker.set_remote_checksum(
5876                &specifier,
5877                LoaderChecksum::new(LoaderChecksum::gen(
5878                  module_source_and_info.source_bytes(),
5879                )),
5880              );
5881            }
5882          }
5883        }
5884
5885        let module_slot =
5886          self.visit_module(module_source_and_info, maybe_version_info);
5887        self.graph.module_slots.insert(specifier, module_slot);
5888      }
5889      PendingInfoResponse::Redirect {
5890        count,
5891        specifier,
5892        is_asset,
5893        is_dynamic,
5894        is_root,
5895        maybe_attribute_type,
5896      } => {
5897        self.load_with_redirect_count(
5898          count,
5899          LoadOptionsRef {
5900            specifier: &specifier,
5901            maybe_range: maybe_referrer.as_ref(),
5902            is_asset,
5903            in_dynamic_branch: is_dynamic,
5904            is_root,
5905            maybe_attribute_type,
5906            maybe_version_info: None,
5907          },
5908        );
5909      }
5910    }
5911  }
5912
5913  /// Visit a module, parsing it and resolving any dependencies.
5914  fn visit_module(
5915    &mut self,
5916    module_source_and_info: ModuleSourceAndInfo,
5917    maybe_version_info: Option<&JsrPackageVersionInfoExt>,
5918  ) -> ModuleSlot {
5919    let module = parse_module(
5920      self.file_system,
5921      self.jsr_url_provider,
5922      self.resolver,
5923      ParseModuleOptions {
5924        graph_kind: self.graph.graph_kind,
5925        module_source_and_info,
5926      },
5927    );
5928
5929    let mut module_slot = ModuleSlot::Module(module);
5930
5931    match &mut module_slot {
5932      ModuleSlot::Module(Module::Js(module)) => {
5933        if matches!(self.graph.graph_kind, GraphKind::All | GraphKind::CodeOnly)
5934          || module.maybe_types_dependency.is_none()
5935        {
5936          self.visit_module_dependencies(
5937            &mut module.dependencies,
5938            maybe_version_info,
5939          );
5940        } else {
5941          module.dependencies.clear();
5942        }
5943
5944        if self.graph.graph_kind.include_types() {
5945          if let Some(Resolution::Ok(resolved)) = module
5946            .maybe_types_dependency
5947            .as_ref()
5948            .map(|d| &d.dependency)
5949          {
5950            self.load(LoadOptionsRef {
5951              specifier: &resolved.specifier,
5952              maybe_range: Some(&resolved.range),
5953              is_asset: false, // types dependency can't be an asset
5954              in_dynamic_branch: false,
5955              is_root: self.resolved_roots.contains(&resolved.specifier),
5956              maybe_attribute_type: None,
5957              maybe_version_info,
5958            });
5959          }
5960        } else {
5961          module.maybe_types_dependency = None;
5962        }
5963      }
5964      ModuleSlot::Module(Module::Wasm(module)) => {
5965        self.visit_module_dependencies(
5966          &mut module.dependencies,
5967          maybe_version_info,
5968        );
5969      }
5970      _ => {}
5971    }
5972
5973    module_slot
5974  }
5975
5976  fn visit_module_dependencies(
5977    &mut self,
5978    dependencies: &mut IndexMap<String, Dependency>,
5979    maybe_version_info: Option<&JsrPackageVersionInfoExt>,
5980  ) {
5981    for dep in dependencies.values_mut() {
5982      if dep.is_dynamic && self.skip_dynamic_deps {
5983        continue;
5984      }
5985      let is_asset = dep.imports.iter().all(|i| i.attributes.has_asset());
5986
5987      if self.graph.graph_kind.include_code() || dep.maybe_type.is_none() {
5988        if let Resolution::Ok(resolved) = &dep.maybe_code {
5989          let specifier = &resolved.specifier;
5990          let range = &resolved.range;
5991          let maybe_attribute_type =
5992            dep.maybe_attribute_type.as_ref().map(|attribute_type| {
5993              AttributeTypeWithRange {
5994                range: range.clone(),
5995                kind: attribute_type.clone(),
5996              }
5997            });
5998          if dep.is_dynamic && !self.in_dynamic_branch {
5999            let value = self
6000              .state
6001              .dynamic_branches
6002              .entry(specifier.clone())
6003              .or_insert_with(|| PendingDynamicBranch {
6004                range: range.clone(),
6005                is_asset,
6006                maybe_attribute_type,
6007                maybe_version_info: maybe_version_info.map(ToOwned::to_owned),
6008              });
6009            if !is_asset {
6010              // overwrite to `false` if changing in order to ensure we load the contents
6011              value.is_asset = false;
6012            }
6013          } else {
6014            self.load(LoadOptionsRef {
6015              specifier,
6016              maybe_range: Some(range),
6017              is_asset,
6018              in_dynamic_branch: self.in_dynamic_branch,
6019              is_root: self.resolved_roots.contains(specifier),
6020              maybe_attribute_type,
6021              maybe_version_info,
6022            });
6023          }
6024        }
6025      } else {
6026        dep.maybe_code = Resolution::None;
6027      }
6028
6029      if self.graph.graph_kind.include_types() {
6030        if let Resolution::Ok(resolved) = &dep.maybe_type {
6031          let specifier = &resolved.specifier;
6032          let range = &resolved.range;
6033          let maybe_attribute_type =
6034            dep.maybe_attribute_type.as_ref().map(|assert_type| {
6035              AttributeTypeWithRange {
6036                range: range.clone(),
6037                kind: assert_type.clone(),
6038              }
6039            });
6040          if dep.is_dynamic && !self.in_dynamic_branch {
6041            self.state.dynamic_branches.insert(
6042              specifier.clone(),
6043              PendingDynamicBranch {
6044                is_asset,
6045                range: range.clone(),
6046                maybe_attribute_type,
6047                maybe_version_info: maybe_version_info.map(ToOwned::to_owned),
6048              },
6049            );
6050          } else {
6051            self.load(LoadOptionsRef {
6052              specifier,
6053              maybe_range: Some(range),
6054              is_asset,
6055              in_dynamic_branch: self.in_dynamic_branch,
6056              is_root: self.resolved_roots.contains(specifier),
6057              maybe_attribute_type,
6058              maybe_version_info,
6059            });
6060          }
6061        }
6062      } else {
6063        dep.maybe_type = Resolution::None;
6064      }
6065    }
6066  }
6067}
6068
6069fn validate_jsr_specifier(
6070  specifier: &Url,
6071) -> Result<JsrPackageReqReference, JsrPackageFormatError> {
6072  let package_ref = JsrPackageReqReference::from_specifier(specifier)
6073    .map_err(JsrPackageFormatError::JsrPackageParseError)?;
6074  match package_ref.req().version_req.inner() {
6075    RangeSetOrTag::Tag(tag) => {
6076      Err(JsrPackageFormatError::VersionTagNotSupported { tag: tag.clone() })
6077    }
6078    RangeSetOrTag::RangeSet(_) => Ok(package_ref),
6079  }
6080}
6081
6082/// Pending information to insert into the module graph once
6083/// npm specifier resolution has been finalized.
6084struct NpmSpecifierBuildPendingInfo {
6085  found_pkg_nvs: IndexSet<PackageNv>,
6086  module_slots: HashMap<ModuleSpecifier, ModuleSlot>,
6087  dependencies_resolution: Option<Result<(), Arc<dyn JsErrorClass>>>,
6088  redirects: HashMap<ModuleSpecifier, ModuleSpecifier>,
6089}
6090
6091impl NpmSpecifierBuildPendingInfo {
6092  pub fn with_capacity(capacity: usize) -> Self {
6093    Self {
6094      found_pkg_nvs: IndexSet::with_capacity(capacity),
6095      module_slots: HashMap::with_capacity(capacity),
6096      dependencies_resolution: None,
6097      redirects: HashMap::with_capacity(capacity),
6098    }
6099  }
6100}
6101
6102#[derive(Debug)]
6103struct PendingNpmResolutionItem {
6104  specifier: ModuleSpecifier,
6105  package_ref: NpmPackageReqReference,
6106  maybe_range: Option<Range>,
6107  in_dynamic_branch: bool,
6108}
6109
6110struct NpmSpecifierResolver<'a> {
6111  npm_resolver: Option<&'a dyn NpmResolver>,
6112  pending_info: NpmSpecifierBuildPendingInfo,
6113  pending_npm_specifiers: Vec<PendingNpmResolutionItem>,
6114}
6115
6116impl<'a> NpmSpecifierResolver<'a> {
6117  pub async fn fill_builder(builder: &mut Builder<'a, '_>) {
6118    let mut npm_specifier_resolver = NpmSpecifierResolver::new(
6119      builder.npm_resolver,
6120      std::mem::take(&mut builder.state.npm.pending_resolutions),
6121    );
6122
6123    npm_specifier_resolver.resolve().await;
6124
6125    npm_specifier_resolver.fill_graph(builder.graph);
6126  }
6127
6128  fn new(
6129    npm_resolver: Option<&'a dyn NpmResolver>,
6130    pending_npm_specifiers: Vec<PendingNpmResolutionItem>,
6131  ) -> Self {
6132    Self {
6133      npm_resolver,
6134      pending_info: NpmSpecifierBuildPendingInfo::with_capacity(
6135        pending_npm_specifiers.len(),
6136      ),
6137      pending_npm_specifiers,
6138    }
6139  }
6140
6141  async fn resolve(&mut self) {
6142    let Some(npm_resolver) = self.npm_resolver else {
6143      for item in self.pending_npm_specifiers.drain(..) {
6144        self.pending_info.module_slots.insert(
6145          item.specifier.clone(),
6146          ModuleSlot::Err(
6147            ModuleErrorKind::Load {
6148              specifier: item.specifier.clone(),
6149              maybe_referrer: item.maybe_range.clone(),
6150              err: NpmLoadError::NotSupportedEnvironment.into(),
6151            }
6152            .into_box(),
6153          ),
6154        );
6155      }
6156      return;
6157    };
6158
6159    let (main_items, dynamic_items) = self
6160      .pending_npm_specifiers
6161      .drain(..)
6162      .partition::<Vec<_>, _>(|item| !item.in_dynamic_branch);
6163
6164    // Unconditionally do an npm install if both of these are
6165    // empty and leave it up to the implementer to handle it
6166    // or not
6167    if !main_items.is_empty() || dynamic_items.is_empty() {
6168      let items_by_req: IndexMap<_, Vec<_>> =
6169        IndexMap::with_capacity(main_items.len());
6170      let items_by_req =
6171        main_items
6172          .into_iter()
6173          .fold(items_by_req, |mut items_by_req, item| {
6174            items_by_req
6175              .entry(item.package_ref.req().clone())
6176              .or_default()
6177              .push(item);
6178            items_by_req
6179          });
6180      let all_package_reqs = items_by_req.keys().cloned().collect::<Vec<_>>();
6181      let result = npm_resolver.resolve_pkg_reqs(&all_package_reqs).await;
6182
6183      self.pending_info.dependencies_resolution = Some(result.dep_graph_result);
6184
6185      assert_eq!(all_package_reqs.len(), result.results.len());
6186      for (req, resolution) in
6187        all_package_reqs.into_iter().zip(result.results.into_iter())
6188      {
6189        let items = items_by_req.get(&req).unwrap();
6190        for item in items {
6191          match &resolution {
6192            Ok(pkg_nv) => {
6193              self.add_nv_for_item(
6194                item.specifier.clone(),
6195                pkg_nv.clone(),
6196                item.package_ref.sub_path().map(PackageSubPath::from_str),
6197              );
6198            }
6199            Err(err) => {
6200              self.pending_info.module_slots.insert(
6201                item.specifier.clone(),
6202                ModuleSlot::Err(
6203                  ModuleErrorKind::Load {
6204                    specifier: item.specifier.clone(),
6205                    maybe_referrer: item.maybe_range.clone(),
6206                    err: err.clone().into(),
6207                  }
6208                  .into_box(),
6209                ),
6210              );
6211            }
6212          }
6213        }
6214      }
6215    }
6216
6217    for item in dynamic_items {
6218      // these need to be resolved one at a time in order to check if the dependency resolution fails
6219      let mut result = npm_resolver
6220        .resolve_pkg_reqs(&[item.package_ref.req().clone()])
6221        .await;
6222      assert_eq!(result.results.len(), 1);
6223      match result.results.remove(0) {
6224        Ok(pkg_nv) => {
6225          if let Err(err) = result.dep_graph_result {
6226            self.pending_info.module_slots.insert(
6227              item.specifier.clone(),
6228              ModuleSlot::Err(
6229                ModuleErrorKind::Load {
6230                  specifier: item.specifier,
6231                  maybe_referrer: item.maybe_range,
6232                  err: NpmLoadError::PackageReqResolution(err).into(),
6233                }
6234                .into_box(),
6235              ),
6236            );
6237          } else {
6238            self.add_nv_for_item(
6239              item.specifier,
6240              pkg_nv,
6241              item.package_ref.into_inner().sub_path,
6242            );
6243          }
6244        }
6245        Err(err) => {
6246          self.pending_info.module_slots.insert(
6247            item.specifier.clone(),
6248            ModuleSlot::Err(
6249              ModuleErrorKind::Load {
6250                specifier: item.specifier,
6251                maybe_referrer: item.maybe_range,
6252                err: err.into(),
6253              }
6254              .into_box(),
6255            ),
6256          );
6257        }
6258      }
6259    }
6260  }
6261
6262  fn add_nv_for_item(
6263    &mut self,
6264    specifier: ModuleSpecifier,
6265    pkg_nv: PackageNv,
6266    sub_path: Option<SmallStackString>,
6267  ) {
6268    let pkg_id_ref = NpmPackageNvReference::new(PackageNvReference {
6269      nv: pkg_nv.clone(),
6270      sub_path,
6271    });
6272    let resolved_specifier = pkg_id_ref.as_specifier();
6273    if resolved_specifier != specifier {
6274      self
6275        .pending_info
6276        .redirects
6277        .insert(specifier.clone(), resolved_specifier.clone());
6278    }
6279    self.pending_info.found_pkg_nvs.insert(pkg_nv);
6280    self.pending_info.module_slots.insert(
6281      resolved_specifier.clone(),
6282      ModuleSlot::Module(Module::Npm(NpmModule {
6283        specifier: resolved_specifier,
6284        nv_reference: pkg_id_ref,
6285      })),
6286    );
6287  }
6288
6289  fn fill_graph(self, graph: &mut ModuleGraph) {
6290    let pending_info = self.pending_info;
6291
6292    // update the graph with the pending information
6293    for (key, value) in pending_info.module_slots {
6294      // always keep the existing information in the graph
6295      // in case it was already used in the runtime
6296      graph.module_slots.entry(key).or_insert(value);
6297    }
6298    for (key, value) in pending_info.redirects {
6299      graph.redirects.entry(key).or_insert(value);
6300    }
6301
6302    graph.npm_packages.extend(pending_info.found_pkg_nvs);
6303    if let Some(result) = pending_info.dependencies_resolution {
6304      graph.npm_dep_graph_result = result;
6305    }
6306  }
6307}
6308
6309fn new_source_with_text(
6310  specifier: &ModuleSpecifier,
6311  bytes: Arc<[u8]>,
6312  maybe_charset: Option<&str>,
6313  mtime: Option<SystemTime>,
6314) -> Result<ModuleTextSource, ModuleError> {
6315  let charset = maybe_charset.unwrap_or_else(|| {
6316    deno_media_type::encoding::detect_charset(specifier, bytes.as_ref())
6317  });
6318  deno_media_type::encoding::decode_arc_source_detail(charset, bytes)
6319    .map(|detail| ModuleTextSource {
6320      text: detail.text,
6321      decoded_kind: detail.kind,
6322    })
6323    .map_err(|err| {
6324      ModuleErrorKind::Load {
6325        specifier: specifier.clone(),
6326        maybe_referrer: None,
6327        err: ModuleLoadError::Decode(Arc::new(DecodeError { mtime, err })),
6328      }
6329      .into_box()
6330    })
6331}
6332
6333impl Serialize for Resolution {
6334  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
6335  where
6336    S: Serializer,
6337  {
6338    match self {
6339      Resolution::Ok(resolved) => {
6340        let mut state = serializer.serialize_struct("ResolvedSpecifier", 3)?;
6341        state.serialize_field("specifier", &resolved.specifier)?;
6342        if resolved.range.resolution_mode.is_some() {
6343          state.serialize_field(
6344            "resolutionMode",
6345            &resolved.range.resolution_mode,
6346          )?;
6347        }
6348        state.serialize_field("span", &resolved.range)?;
6349        state.end()
6350      }
6351      Resolution::Err(err) => {
6352        let mut state = serializer.serialize_struct("ResolvedError", 3)?;
6353        state.serialize_field("error", &err.to_string())?;
6354        let range = err.range();
6355        if range.resolution_mode.is_some() {
6356          state.serialize_field("resolutionMode", &range.resolution_mode)?;
6357        }
6358        state.serialize_field("span", range)?;
6359        state.end()
6360      }
6361      Resolution::None => {
6362        Serialize::serialize(&serde_json::Value::Null, serializer)
6363      }
6364    }
6365  }
6366}
6367
6368fn serialize_dependencies<S>(
6369  dependencies: &IndexMap<String, Dependency>,
6370  serializer: S,
6371) -> Result<S::Ok, S::Error>
6372where
6373  S: Serializer,
6374{
6375  #[derive(Serialize)]
6376  struct DependencyWithSpecifier<'a> {
6377    specifier: &'a str,
6378    #[serde(flatten)]
6379    dependency: &'a Dependency,
6380  }
6381  let mut seq = serializer.serialize_seq(Some(dependencies.len()))?;
6382  for (specifier, dependency) in dependencies {
6383    seq.serialize_element(&DependencyWithSpecifier {
6384      specifier,
6385      dependency,
6386    })?
6387  }
6388  seq.end()
6389}
6390
6391fn serialize_source<S>(
6392  source: &ModuleTextSource,
6393  serializer: S,
6394) -> Result<S::Ok, S::Error>
6395where
6396  S: Serializer,
6397{
6398  serializer.serialize_u32(source.text.len() as u32)
6399}
6400
6401fn serialize_source_bytes<S>(
6402  source: &Arc<[u8]>,
6403  serializer: S,
6404) -> Result<S::Ok, S::Error>
6405where
6406  S: Serializer,
6407{
6408  serializer.serialize_u32(source.len() as u32)
6409}
6410
6411#[cfg(test)]
6412mod tests {
6413  use crate::analysis::ImportAttribute;
6414  use crate::packages::JsrPackageInfoVersion;
6415  use deno_ast::emit;
6416  use deno_ast::EmitOptions;
6417  use deno_ast::SourceMap;
6418  use pretty_assertions::assert_eq;
6419  use serde_json::json;
6420
6421  use super::*;
6422  use url::Url;
6423
6424  #[test]
6425  fn test_range_includes() {
6426    let range = PositionRange {
6427      start: Position {
6428        line: 1,
6429        character: 20,
6430      },
6431      end: Position {
6432        line: 1,
6433        character: 30,
6434      },
6435    };
6436    assert!(range.includes(Position {
6437      line: 1,
6438      character: 20
6439    }));
6440    assert!(range.includes(Position {
6441      line: 1,
6442      character: 25
6443    }));
6444    assert!(range.includes(Position {
6445      line: 1,
6446      character: 30
6447    }));
6448    assert!(!range.includes(Position {
6449      line: 0,
6450      character: 25
6451    }));
6452    assert!(!range.includes(Position {
6453      line: 2,
6454      character: 25
6455    }));
6456  }
6457
6458  #[test]
6459  fn test_jsr_import_format() {
6460    assert!(
6461      validate_jsr_specifier(&Url::parse("jsr:@scope/mod@tag").unwrap())
6462        .is_err(),
6463      "jsr import specifier with tag should be an error"
6464    );
6465
6466    assert!(
6467      validate_jsr_specifier(&Url::parse("jsr:@scope/mod@").unwrap()).is_err()
6468    );
6469
6470    assert!(validate_jsr_specifier(
6471      &Url::parse("jsr:@scope/mod@1.2.3").unwrap()
6472    )
6473    .is_ok());
6474
6475    assert!(
6476      validate_jsr_specifier(&Url::parse("jsr:@scope/mod").unwrap()).is_ok()
6477    );
6478  }
6479
6480  #[test]
6481  fn test_module_dependency_includes() {
6482    let specifier = ModuleSpecifier::parse("file:///a.ts").unwrap();
6483    let dependency = Dependency {
6484      maybe_code: Resolution::Ok(Box::new(ResolutionResolved {
6485        specifier: ModuleSpecifier::parse("file:///b.ts").unwrap(),
6486        range: Range {
6487          specifier: specifier.clone(),
6488          range: PositionRange {
6489            start: Position {
6490              line: 0,
6491              character: 19,
6492            },
6493            end: Position {
6494              line: 0,
6495              character: 27,
6496            },
6497          },
6498          resolution_mode: None,
6499        },
6500      })),
6501      imports: vec![
6502        Import {
6503          specifier: "./b.ts".to_string(),
6504          kind: ImportKind::Es,
6505          specifier_range: Range {
6506            specifier: specifier.clone(),
6507            range: PositionRange {
6508              start: Position {
6509                line: 0,
6510                character: 19,
6511              },
6512              end: Position {
6513                line: 0,
6514                character: 27,
6515              },
6516            },
6517            resolution_mode: None,
6518          },
6519          is_dynamic: false,
6520          attributes: Default::default(),
6521          is_side_effect: false,
6522        },
6523        Import {
6524          specifier: "./b.ts".to_string(),
6525          kind: ImportKind::Es,
6526          specifier_range: Range {
6527            specifier: specifier.clone(),
6528            range: PositionRange {
6529              start: Position {
6530                line: 1,
6531                character: 19,
6532              },
6533              end: Position {
6534                line: 1,
6535                character: 27,
6536              },
6537            },
6538            resolution_mode: None,
6539          },
6540          is_dynamic: false,
6541          attributes: Default::default(),
6542          is_side_effect: false,
6543        },
6544      ],
6545      ..Default::default()
6546    };
6547    assert_eq!(
6548      dependency.includes(Position {
6549        line: 0,
6550        character: 21,
6551      }),
6552      Some(&Range {
6553        specifier: specifier.clone(),
6554        range: PositionRange {
6555          start: Position {
6556            line: 0,
6557            character: 19
6558          },
6559          end: Position {
6560            line: 0,
6561            character: 27
6562          },
6563        },
6564        resolution_mode: None,
6565      })
6566    );
6567    assert_eq!(
6568      dependency.includes(Position {
6569        line: 1,
6570        character: 21,
6571      }),
6572      Some(&Range {
6573        specifier,
6574        range: PositionRange {
6575          start: Position {
6576            line: 1,
6577            character: 19
6578          },
6579          end: Position {
6580            line: 1,
6581            character: 27
6582          },
6583        },
6584        resolution_mode: None,
6585      })
6586    );
6587    assert_eq!(
6588      dependency.includes(Position {
6589        line: 0,
6590        character: 18,
6591      }),
6592      None,
6593    );
6594  }
6595
6596  #[test]
6597  fn dependency_with_new_resolver() {
6598    let referrer = ModuleSpecifier::parse("file:///a/main.ts").unwrap();
6599    let dependency = Dependency {
6600      maybe_code: Resolution::Ok(Box::new(ResolutionResolved {
6601        specifier: ModuleSpecifier::parse("file:///wrong.ts").unwrap(),
6602        range: Range {
6603          specifier: referrer.clone(),
6604          range: PositionRange::zeroed(),
6605          resolution_mode: None,
6606        },
6607      })),
6608      maybe_type: Resolution::Ok(Box::new(ResolutionResolved {
6609        specifier: ModuleSpecifier::parse("file:///wrong.ts").unwrap(),
6610        range: Range {
6611          specifier: referrer.clone(),
6612          range: PositionRange::zeroed(),
6613          resolution_mode: None,
6614        },
6615      })),
6616      maybe_deno_types_specifier: Some("./b.d.ts".to_string()),
6617      ..Default::default()
6618    };
6619    let new_dependency =
6620      dependency.with_new_resolver("./b.ts", Default::default(), None);
6621    assert_eq!(
6622      new_dependency,
6623      Dependency {
6624        maybe_code: Resolution::Ok(Box::new(ResolutionResolved {
6625          specifier: ModuleSpecifier::parse("file:///a/b.ts").unwrap(),
6626          range: Range {
6627            specifier: referrer.clone(),
6628            range: PositionRange::zeroed(),
6629            resolution_mode: None,
6630          },
6631        })),
6632        maybe_type: Resolution::Ok(Box::new(ResolutionResolved {
6633          specifier: ModuleSpecifier::parse("file:///a/b.d.ts").unwrap(),
6634          range: Range {
6635            specifier: referrer.clone(),
6636            range: PositionRange::zeroed(),
6637            resolution_mode: None,
6638          },
6639        })),
6640        maybe_deno_types_specifier: Some("./b.d.ts".to_string()),
6641        ..Default::default()
6642      }
6643    );
6644  }
6645
6646  #[test]
6647  fn types_dependency_with_new_resolver() {
6648    let referrer = ModuleSpecifier::parse("file:///a/main.ts").unwrap();
6649    let types_dependency = TypesDependency {
6650      specifier: "./main.d.ts".to_string(),
6651      dependency: Resolution::Ok(Box::new(ResolutionResolved {
6652        specifier: ModuleSpecifier::parse("file:///wrong.ts").unwrap(),
6653        range: Range {
6654          specifier: referrer.clone(),
6655          range: PositionRange::zeroed(),
6656          resolution_mode: None,
6657        },
6658      })),
6659    };
6660    let new_types_dependency =
6661      types_dependency.with_new_resolver(Default::default(), None);
6662    assert_eq!(
6663      new_types_dependency,
6664      TypesDependency {
6665        specifier: "./main.d.ts".to_string(),
6666        dependency: Resolution::Ok(Box::new(ResolutionResolved {
6667          specifier: ModuleSpecifier::parse("file:///a/main.d.ts").unwrap(),
6668          range: Range {
6669            specifier: referrer.clone(),
6670            range: PositionRange::zeroed(),
6671            resolution_mode: None,
6672          },
6673        })),
6674      }
6675    );
6676  }
6677
6678  #[tokio::test]
6679  async fn static_dep_of_dynamic_dep_is_dynamic() {
6680    #[derive(Default)]
6681    struct TestLoader {
6682      loaded_foo: RefCell<bool>,
6683      loaded_bar: RefCell<bool>,
6684      loaded_baz: RefCell<bool>,
6685      loaded_dynamic_root: RefCell<bool>,
6686    }
6687    impl Loader for TestLoader {
6688      fn load(
6689        &self,
6690        specifier: &ModuleSpecifier,
6691        options: LoadOptions,
6692      ) -> LoadFuture {
6693        let specifier = specifier.clone();
6694        match specifier.as_str() {
6695          "file:///foo.js" => {
6696            assert!(!options.in_dynamic_branch);
6697            assert!(!options.was_dynamic_root);
6698            *self.loaded_foo.borrow_mut() = true;
6699            Box::pin(async move {
6700              Ok(Some(LoadResponse::Module {
6701                specifier: specifier.clone(),
6702                maybe_headers: None,
6703                mtime: None,
6704                content: b"await import('file:///bar.js')".to_vec().into(),
6705              }))
6706            })
6707          }
6708          "file:///bar.js" => {
6709            assert!(options.in_dynamic_branch);
6710            assert!(!options.was_dynamic_root);
6711            *self.loaded_bar.borrow_mut() = true;
6712            Box::pin(async move {
6713              Ok(Some(LoadResponse::Module {
6714                specifier: specifier.clone(),
6715                maybe_headers: None,
6716                mtime: None,
6717                content: b"import 'file:///baz.js'".to_vec().into(),
6718              }))
6719            })
6720          }
6721          "file:///baz.js" => {
6722            assert!(options.in_dynamic_branch);
6723            assert!(!options.was_dynamic_root);
6724            *self.loaded_baz.borrow_mut() = true;
6725            Box::pin(async move {
6726              Ok(Some(LoadResponse::Module {
6727                specifier: specifier.clone(),
6728                maybe_headers: None,
6729                mtime: None,
6730                content: b"console.log('Hello, world!')".to_vec().into(),
6731              }))
6732            })
6733          }
6734          "file:///dynamic_root.js" => {
6735            assert!(options.in_dynamic_branch);
6736            assert!(options.was_dynamic_root);
6737            *self.loaded_dynamic_root.borrow_mut() = true;
6738            Box::pin(async move {
6739              Ok(Some(LoadResponse::Module {
6740                specifier: specifier.clone(),
6741                maybe_headers: None,
6742                mtime: None,
6743                content: b"console.log('Hello, world!')".to_vec().into(),
6744              }))
6745            })
6746          }
6747          _ => unreachable!(),
6748        }
6749      }
6750    }
6751
6752    let loader = TestLoader::default();
6753    let mut graph = ModuleGraph::new(GraphKind::All);
6754    graph
6755      .build(
6756        vec![Url::parse("file:///foo.js").unwrap()],
6757        Vec::new(),
6758        &loader,
6759        Default::default(),
6760      )
6761      .await;
6762    assert!(*loader.loaded_foo.borrow());
6763    assert!(*loader.loaded_bar.borrow());
6764    assert!(*loader.loaded_baz.borrow());
6765    assert!(!*loader.loaded_dynamic_root.borrow());
6766    assert_eq!(graph.specifiers_count(), 3);
6767
6768    graph
6769      .build(
6770        vec![Url::parse("file:///dynamic_root.js").unwrap()],
6771        Vec::new(),
6772        &loader,
6773        BuildOptions {
6774          is_dynamic: true,
6775          ..Default::default()
6776        },
6777      )
6778      .await;
6779    assert!(*loader.loaded_dynamic_root.borrow());
6780    assert_eq!(graph.specifiers_count(), 4);
6781  }
6782
6783  #[tokio::test]
6784  async fn missing_module_is_error() {
6785    struct TestLoader;
6786    impl Loader for TestLoader {
6787      fn load(
6788        &self,
6789        specifier: &ModuleSpecifier,
6790        _options: LoadOptions,
6791      ) -> LoadFuture {
6792        let specifier = specifier.clone();
6793        match specifier.as_str() {
6794          "file:///foo.js" => Box::pin(async move {
6795            Ok(Some(LoadResponse::Module {
6796              specifier: specifier.clone(),
6797              maybe_headers: None,
6798              mtime: None,
6799              content: b"await import('file:///bar.js')".to_vec().into(),
6800            }))
6801          }),
6802          "file:///bar.js" => Box::pin(async move { Ok(None) }),
6803          _ => unreachable!(),
6804        }
6805      }
6806    }
6807    let loader = TestLoader;
6808    let mut graph = ModuleGraph::new(GraphKind::All);
6809    let roots = vec![Url::parse("file:///foo.js").unwrap()];
6810    graph
6811      .build(roots.clone(), Vec::new(), &loader, Default::default())
6812      .await;
6813    assert!(graph
6814      .try_get(&Url::parse("file:///foo.js").unwrap())
6815      .is_ok());
6816    assert!(matches!(
6817      graph
6818        .try_get(&Url::parse("file:///bar.js").unwrap())
6819        .unwrap_err()
6820        .as_kind(),
6821      ModuleErrorKind::Missing { .. }
6822    ));
6823    let specifiers = graph.specifiers().collect::<HashMap<_, _>>();
6824    assert_eq!(specifiers.len(), 2);
6825    assert!(specifiers
6826      .get(&Url::parse("file:///foo.js").unwrap())
6827      .unwrap()
6828      .is_ok());
6829    assert!(matches!(
6830      specifiers
6831        .get(&Url::parse("file:///bar.js").unwrap())
6832        .unwrap()
6833        .as_ref()
6834        .unwrap_err()
6835        .as_kind(),
6836      ModuleErrorKind::Missing { .. }
6837    ));
6838
6839    // should not follow the dynamic import error when walking and not following
6840    let error_count = graph
6841      .walk(
6842        roots.iter(),
6843        WalkOptions {
6844          follow_dynamic: false,
6845          kind: GraphKind::All,
6846          check_js: CheckJsOption::True,
6847          prefer_fast_check_graph: false,
6848        },
6849      )
6850      .errors()
6851      .count();
6852    assert_eq!(error_count, 0);
6853
6854    // should return as dynamic import missing when walking
6855    let errors = graph
6856      .walk(
6857        roots.iter(),
6858        WalkOptions {
6859          follow_dynamic: true,
6860          kind: GraphKind::All,
6861          check_js: CheckJsOption::True,
6862          prefer_fast_check_graph: false,
6863        },
6864      )
6865      .errors()
6866      .collect::<Vec<_>>();
6867    assert_eq!(errors.len(), 1);
6868    let err = &errors[0];
6869    match err {
6870      ModuleGraphError::ModuleError(err) => {
6871        assert!(matches!(
6872          err.as_kind(),
6873          ModuleErrorKind::MissingDynamic { .. }
6874        ));
6875      }
6876      _ => unreachable!(),
6877    }
6878  }
6879
6880  #[tokio::test]
6881  async fn missing_wasm_module_dep_is_error() {
6882    struct TestLoader;
6883    impl Loader for TestLoader {
6884      fn load(
6885        &self,
6886        specifier: &ModuleSpecifier,
6887        _options: LoadOptions,
6888      ) -> LoadFuture {
6889        let specifier = specifier.clone();
6890        match specifier.as_str() {
6891          "file:///foo.js" => Box::pin(async move {
6892            Ok(Some(LoadResponse::Module {
6893              specifier: specifier.clone(),
6894              maybe_headers: None,
6895              mtime: None,
6896              content: b"import './math_with_import.wasm';".to_vec().into(),
6897            }))
6898          }),
6899          "file:///math_with_import.wasm" => Box::pin(async move {
6900            Ok(Some(LoadResponse::Module {
6901              specifier: specifier.clone(),
6902              maybe_headers: None,
6903              mtime: None,
6904              content: include_bytes!(
6905                "../tests/testdata/math_with_import.wasm"
6906              )
6907              .to_vec()
6908              .into(),
6909            }))
6910          }),
6911          "file:///math.ts" => Box::pin(async move { Ok(None) }),
6912          _ => unreachable!(),
6913        }
6914      }
6915    }
6916    let loader = TestLoader;
6917    let mut graph = ModuleGraph::new(GraphKind::All);
6918    let roots = vec![Url::parse("file:///foo.js").unwrap()];
6919    graph
6920      .build(roots.clone(), Vec::new(), &loader, Default::default())
6921      .await;
6922
6923    // should return the not found error for the Wasm module
6924    let errors = graph
6925      .walk(
6926        roots.iter(),
6927        WalkOptions {
6928          follow_dynamic: false,
6929          kind: GraphKind::All,
6930          check_js: CheckJsOption::True,
6931          prefer_fast_check_graph: false,
6932        },
6933      )
6934      .errors()
6935      .collect::<Vec<_>>();
6936    assert_eq!(errors.len(), 1);
6937    match &errors[0] {
6938      ModuleGraphError::ModuleError(err) => {
6939        match err.as_kind() {
6940          ModuleErrorKind::Missing {
6941            specifier,
6942            maybe_referrer,
6943          } => {
6944            assert_eq!(specifier.as_str(), "file:///math.ts");
6945            assert_eq!(
6946              maybe_referrer.clone(),
6947              Some(Range {
6948                specifier: Url::parse("file:///math_with_import.wasm").unwrap(),
6949                // this range is the range in the generated .d.ts file
6950                range: PositionRange {
6951                  start: Position {
6952                    line: 0,
6953                    character: 92,
6954                  },
6955                  end: Position {
6956                    line: 0,
6957                    character: 103,
6958                  }
6959                },
6960                resolution_mode: Some(ResolutionMode::Import),
6961              })
6962            );
6963          }
6964          _ => unreachable!(),
6965        }
6966      }
6967      _ => unreachable!(),
6968    }
6969  }
6970
6971  #[tokio::test]
6972  async fn redirected_specifiers() {
6973    struct TestLoader;
6974    impl Loader for TestLoader {
6975      fn load(
6976        &self,
6977        specifier: &ModuleSpecifier,
6978        _options: LoadOptions,
6979      ) -> LoadFuture {
6980        let specifier = specifier.clone();
6981        match specifier.as_str() {
6982          "file:///foo.js" => Box::pin(async move {
6983            Ok(Some(LoadResponse::Module {
6984              specifier: Url::parse("file:///foo_actual.js").unwrap(),
6985              maybe_headers: None,
6986              mtime: None,
6987              content: b"import 'file:///bar.js'".to_vec().into(),
6988            }))
6989          }),
6990          "file:///bar.js" => Box::pin(async move {
6991            Ok(Some(LoadResponse::Module {
6992              specifier: Url::parse("file:///bar_actual.js").unwrap(),
6993              maybe_headers: None,
6994              mtime: None,
6995              content: b"(".to_vec().into(),
6996            }))
6997          }),
6998          _ => unreachable!(),
6999        }
7000      }
7001    }
7002    let loader = TestLoader;
7003    let mut graph = ModuleGraph::new(GraphKind::All);
7004    graph
7005      .build(
7006        vec![Url::parse("file:///foo.js").unwrap()],
7007        Vec::new(),
7008        &loader,
7009        Default::default(),
7010      )
7011      .await;
7012    let specifiers = graph.specifiers().collect::<HashMap<_, _>>();
7013    assert_eq!(specifiers.len(), 4);
7014    assert!(specifiers
7015      .get(&Url::parse("file:///foo.js").unwrap())
7016      .unwrap()
7017      .is_ok());
7018    assert!(specifiers
7019      .get(&Url::parse("file:///foo_actual.js").unwrap())
7020      .unwrap()
7021      .is_ok());
7022    assert!(matches!(
7023      specifiers
7024        .get(&Url::parse("file:///bar.js").unwrap())
7025        .unwrap()
7026        .as_ref()
7027        .unwrap_err()
7028        .as_kind(),
7029      ModuleErrorKind::Parse { .. }
7030    ));
7031    assert!(matches!(
7032      specifiers
7033        .get(&Url::parse("file:///bar_actual.js").unwrap())
7034        .unwrap()
7035        .as_ref()
7036        .unwrap_err()
7037        .as_kind(),
7038      ModuleErrorKind::Parse { .. }
7039    ));
7040  }
7041
7042  #[tokio::test]
7043  async fn local_import_remote_module() {
7044    struct TestLoader;
7045    impl Loader for TestLoader {
7046      fn load(
7047        &self,
7048        specifier: &ModuleSpecifier,
7049        _options: LoadOptions,
7050      ) -> LoadFuture {
7051        let specifier = specifier.clone();
7052        match specifier.as_str() {
7053          "https://deno.land/foo.js" => Box::pin(async move {
7054            Ok(Some(LoadResponse::Module {
7055              specifier: specifier.clone(),
7056              maybe_headers: None,
7057              mtime: None,
7058              content:
7059                b"import 'FILE:///baz.js'; import 'file:///bar.js'; import 'http://deno.land/foo.js';"
7060                  .to_vec().into(),
7061            }))
7062          }),
7063          "http://deno.land/foo.js" => Box::pin(async move {
7064            Ok(Some(LoadResponse::Module {
7065              specifier: specifier.clone(),
7066              maybe_headers: None,
7067              mtime: None,
7068              content: b"export {}".to_vec().into(),
7069            }))
7070          }),
7071          "file:///bar.js" => Box::pin(async move {
7072            Ok(Some(LoadResponse::Module {
7073              specifier: specifier.clone(),
7074              maybe_headers: None,
7075              mtime: None,
7076              content: b"console.log('Hello, world!')".to_vec().into(),
7077            }))
7078          }),
7079          "file:///baz.js" => Box::pin(async move {
7080            Ok(Some(LoadResponse::Module {
7081              specifier: specifier.clone(),
7082              maybe_headers: None,
7083              mtime: None,
7084              content: b"console.log('Hello, world 2!')".to_vec().into(),
7085            }))
7086          }),
7087          _ => unreachable!(),
7088        }
7089      }
7090    }
7091    let loader = TestLoader;
7092    let mut graph = ModuleGraph::new(GraphKind::All);
7093    let roots = vec![Url::parse("https://deno.land/foo.js").unwrap()];
7094    graph
7095      .build(roots.clone(), Vec::new(), &loader, Default::default())
7096      .await;
7097    assert_eq!(graph.specifiers_count(), 4);
7098    let errors = graph
7099      .walk(
7100        roots.iter(),
7101        WalkOptions {
7102          check_js: CheckJsOption::True,
7103          follow_dynamic: false,
7104          kind: GraphKind::All,
7105          prefer_fast_check_graph: false,
7106        },
7107      )
7108      .errors()
7109      .collect::<Vec<_>>();
7110    assert_eq!(errors.len(), 3);
7111    let errors = errors
7112      .into_iter()
7113      .map(|err| match err {
7114        ModuleGraphError::ResolutionError(err) => err,
7115        _ => unreachable!(),
7116      })
7117      .collect::<Vec<_>>();
7118
7119    assert_eq!(
7120      errors[0],
7121      ResolutionError::InvalidDowngrade {
7122        range: Range {
7123          specifier: ModuleSpecifier::parse("https://deno.land/foo.js")
7124            .unwrap(),
7125          range: PositionRange {
7126            start: Position {
7127              line: 0,
7128              character: 57,
7129            },
7130            end: Position {
7131              line: 0,
7132              character: 82,
7133            },
7134          },
7135          resolution_mode: Some(ResolutionMode::Import),
7136        },
7137        specifier: ModuleSpecifier::parse("http://deno.land/foo.js").unwrap(),
7138      },
7139    );
7140    assert_eq!(
7141      errors[1],
7142      ResolutionError::InvalidLocalImport {
7143        range: Range {
7144          specifier: ModuleSpecifier::parse("https://deno.land/foo.js")
7145            .unwrap(),
7146          range: PositionRange {
7147            start: Position {
7148              line: 0,
7149              character: 32,
7150            },
7151            end: Position {
7152              line: 0,
7153              character: 48,
7154            },
7155          },
7156          resolution_mode: Some(ResolutionMode::Import),
7157        },
7158        specifier: ModuleSpecifier::parse("file:///bar.js").unwrap(),
7159      },
7160    );
7161
7162    assert_eq!(
7163      errors[2],
7164      ResolutionError::InvalidLocalImport {
7165        range: Range {
7166          specifier: ModuleSpecifier::parse("https://deno.land/foo.js")
7167            .unwrap(),
7168          range: PositionRange {
7169            start: Position {
7170              line: 0,
7171              character: 7,
7172            },
7173            end: Position {
7174              line: 0,
7175              character: 23,
7176            },
7177          },
7178          resolution_mode: Some(ResolutionMode::Import),
7179        },
7180        specifier: ModuleSpecifier::parse("file:///baz.js").unwrap(),
7181      },
7182    );
7183  }
7184
7185  #[tokio::test]
7186  async fn static_and_dynamic_dep_is_static() {
7187    struct TestLoader {
7188      loaded_bar: RefCell<bool>,
7189    }
7190    impl Loader for TestLoader {
7191      fn load(
7192        &self,
7193        specifier: &ModuleSpecifier,
7194        options: LoadOptions,
7195      ) -> LoadFuture {
7196        let specifier = specifier.clone();
7197        match specifier.as_str() {
7198          "file:///foo.js" => Box::pin(async move {
7199            Ok(Some(LoadResponse::Module {
7200              specifier: specifier.clone(),
7201              maybe_headers: None,
7202              mtime: None,
7203              content:
7204                b"import 'file:///bar.js'; await import('file:///bar.js')"
7205                  .to_vec()
7206                  .into(),
7207            }))
7208          }),
7209          "file:///bar.js" => {
7210            assert!(!options.in_dynamic_branch);
7211            *self.loaded_bar.borrow_mut() = true;
7212            Box::pin(async move {
7213              Ok(Some(LoadResponse::Module {
7214                specifier: specifier.clone(),
7215                maybe_headers: None,
7216                mtime: None,
7217                content: b"console.log('Hello, world!')".to_vec().into(),
7218              }))
7219            })
7220          }
7221          _ => unreachable!(),
7222        }
7223      }
7224    }
7225    let loader = TestLoader {
7226      loaded_bar: RefCell::new(false),
7227    };
7228    let mut graph = ModuleGraph::new(GraphKind::All);
7229    graph
7230      .build(
7231        vec![Url::parse("file:///foo.js").unwrap()],
7232        Default::default(),
7233        &loader,
7234        Default::default(),
7235      )
7236      .await;
7237    assert!(*loader.loaded_bar.borrow());
7238  }
7239
7240  #[tokio::test]
7241  async fn dependency_imports() {
7242    struct TestLoader;
7243    impl Loader for TestLoader {
7244      fn load(
7245        &self,
7246        specifier: &ModuleSpecifier,
7247        options: LoadOptions,
7248      ) -> LoadFuture {
7249        let specifier = specifier.clone();
7250        match specifier.as_str() {
7251          "file:///foo.ts" => Box::pin(async move {
7252            Ok(Some(LoadResponse::Module {
7253              specifier: specifier.clone(),
7254              maybe_headers: None,
7255              mtime: None,
7256              content: b"
7257                /// <reference path='file:///bar.ts' />
7258                /// <reference types='file:///bar.ts' />
7259                /* @jsxImportSource file:///bar.ts */
7260                import 'file:///bar.ts';
7261                await import('file:///bar.ts');
7262                await import('file:///bar.ts', { assert: eval('') });
7263                import 'file:///baz.json' assert { type: 'json' };
7264                import type {} from 'file:///bar.ts';
7265                /** @typedef { import('file:///bar.ts') } bar */
7266              "
7267              .to_vec()
7268              .into(),
7269            }))
7270          }),
7271          "file:///bar.ts" => {
7272            assert!(!options.in_dynamic_branch);
7273            Box::pin(async move {
7274              Ok(Some(LoadResponse::Module {
7275                specifier: specifier.clone(),
7276                maybe_headers: None,
7277                mtime: None,
7278                content: b"".to_vec().into(),
7279              }))
7280            })
7281          }
7282          "file:///baz.json" => {
7283            assert!(!options.in_dynamic_branch);
7284            Box::pin(async move {
7285              Ok(Some(LoadResponse::Module {
7286                specifier: specifier.clone(),
7287                maybe_headers: None,
7288                mtime: None,
7289                content: b"{}".to_vec().into(),
7290              }))
7291            })
7292          }
7293          _ => unreachable!(),
7294        }
7295      }
7296    }
7297    let mut graph = ModuleGraph::new(GraphKind::All);
7298    graph
7299      .build(
7300        vec![Url::parse("file:///foo.ts").unwrap()],
7301        Vec::new(),
7302        &TestLoader,
7303        Default::default(),
7304      )
7305      .await;
7306    graph.valid().unwrap();
7307    let module = graph.get(&Url::parse("file:///foo.ts").unwrap()).unwrap();
7308    let module = module.js().unwrap();
7309    let dependency_a = module.dependencies.get("file:///bar.ts").unwrap();
7310    let dependency_b = module.dependencies.get("file:///baz.json").unwrap();
7311    assert_eq!(
7312      dependency_a.imports,
7313      vec![
7314        Import {
7315          specifier: "file:///bar.ts".to_string(),
7316          kind: ImportKind::TsReferencePath,
7317          specifier_range: Range {
7318            specifier: Url::parse("file:///foo.ts").unwrap(),
7319            range: PositionRange {
7320              start: Position {
7321                line: 1,
7322                character: 36
7323              },
7324              end: Position {
7325                line: 1,
7326                character: 52,
7327              },
7328            },
7329            resolution_mode: None,
7330          },
7331          is_dynamic: false,
7332          attributes: ImportAttributes::None,
7333          is_side_effect: false,
7334        },
7335        Import {
7336          specifier: "file:///bar.ts".to_string(),
7337          kind: ImportKind::TsReferenceTypes,
7338          specifier_range: Range {
7339            specifier: Url::parse("file:///foo.ts").unwrap(),
7340            range: PositionRange {
7341              start: Position {
7342                line: 2,
7343                character: 37,
7344              },
7345              end: Position {
7346                line: 2,
7347                character: 53,
7348              },
7349            },
7350            resolution_mode: None,
7351          },
7352          is_dynamic: false,
7353          attributes: ImportAttributes::None,
7354          is_side_effect: false,
7355        },
7356        Import {
7357          specifier: "file:///bar.ts".to_string(),
7358          kind: ImportKind::Es,
7359          specifier_range: Range {
7360            specifier: Url::parse("file:///foo.ts").unwrap(),
7361            range: PositionRange {
7362              start: Position {
7363                line: 4,
7364                character: 23,
7365              },
7366              end: Position {
7367                line: 4,
7368                character: 39,
7369              },
7370            },
7371            resolution_mode: Some(ResolutionMode::Import),
7372          },
7373          is_dynamic: false,
7374          attributes: ImportAttributes::None,
7375          is_side_effect: true,
7376        },
7377        Import {
7378          specifier: "file:///bar.ts".to_string(),
7379          kind: ImportKind::Es,
7380          specifier_range: Range {
7381            specifier: Url::parse("file:///foo.ts").unwrap(),
7382            range: PositionRange {
7383              start: Position {
7384                line: 5,
7385                character: 29,
7386              },
7387              end: Position {
7388                line: 5,
7389                character: 45,
7390              },
7391            },
7392            resolution_mode: Some(ResolutionMode::Import),
7393          },
7394          is_dynamic: true,
7395          attributes: ImportAttributes::None,
7396          is_side_effect: false,
7397        },
7398        Import {
7399          specifier: "file:///bar.ts".to_string(),
7400          kind: ImportKind::Es,
7401          specifier_range: Range {
7402            specifier: Url::parse("file:///foo.ts").unwrap(),
7403            range: PositionRange {
7404              start: Position {
7405                line: 6,
7406                character: 29,
7407              },
7408              end: Position {
7409                line: 6,
7410                character: 45,
7411              },
7412            },
7413            resolution_mode: Some(ResolutionMode::Import),
7414          },
7415          is_dynamic: true,
7416          attributes: ImportAttributes::Unknown,
7417          is_side_effect: false,
7418        },
7419        Import {
7420          specifier: "file:///bar.ts".to_string(),
7421          kind: ImportKind::TsType,
7422          specifier_range: Range {
7423            specifier: Url::parse("file:///foo.ts").unwrap(),
7424            range: PositionRange {
7425              start: Position {
7426                line: 8,
7427                character: 36,
7428              },
7429              end: Position {
7430                line: 8,
7431                character: 52,
7432              },
7433            },
7434            resolution_mode: Some(ResolutionMode::Import),
7435          },
7436          is_dynamic: false,
7437          attributes: ImportAttributes::None,
7438          is_side_effect: true,
7439        },
7440      ]
7441    );
7442    assert_eq!(
7443      dependency_b.imports,
7444      vec![Import {
7445        specifier: "file:///baz.json".to_string(),
7446        kind: ImportKind::Es,
7447        specifier_range: Range {
7448          specifier: Url::parse("file:///foo.ts").unwrap(),
7449          range: PositionRange {
7450            start: Position {
7451              line: 7,
7452              character: 23,
7453            },
7454            end: Position {
7455              line: 7,
7456              character: 41,
7457            },
7458          },
7459          resolution_mode: Some(ResolutionMode::Import),
7460        },
7461        is_dynamic: false,
7462        attributes: ImportAttributes::Known(HashMap::from_iter(vec![(
7463          "type".to_string(),
7464          ImportAttribute::Known("json".to_string())
7465        )])),
7466        is_side_effect: true,
7467      }]
7468    );
7469  }
7470
7471  #[tokio::test]
7472  async fn dependency_ts_type_import_with_deno_types() {
7473    struct TestLoader;
7474    impl Loader for TestLoader {
7475      fn load(
7476        &self,
7477        specifier: &ModuleSpecifier,
7478        options: LoadOptions,
7479      ) -> LoadFuture {
7480        let specifier = specifier.clone();
7481        match specifier.as_str() {
7482          "file:///foo.ts" => Box::pin(async move {
7483            Ok(Some(LoadResponse::Module {
7484              specifier: specifier.clone(),
7485              maybe_headers: None,
7486              mtime: None,
7487              content: b"
7488                // @deno-types='file:///bar.d.ts'
7489                import type { Bar as _ } from 'bar';
7490              "
7491              .to_vec()
7492              .into(),
7493            }))
7494          }),
7495          "file:///bar.d.ts" => {
7496            assert!(!options.in_dynamic_branch);
7497            Box::pin(async move {
7498              Ok(Some(LoadResponse::Module {
7499                specifier: specifier.clone(),
7500                maybe_headers: None,
7501                mtime: None,
7502                content: b"export type Bar = null;\n".to_vec().into(),
7503              }))
7504            })
7505          }
7506          _ => unreachable!(),
7507        }
7508      }
7509    }
7510    let mut graph = ModuleGraph::new(GraphKind::TypesOnly);
7511    graph
7512      .build(
7513        vec![Url::parse("file:///foo.ts").unwrap()],
7514        Vec::new(),
7515        &TestLoader,
7516        Default::default(),
7517      )
7518      .await;
7519    graph.valid().unwrap();
7520    let module = graph.get(&Url::parse("file:///foo.ts").unwrap()).unwrap();
7521    let module = module.js().unwrap();
7522    let dependency = module.dependencies.get("bar").unwrap();
7523    assert_eq!(dependency.maybe_code, Resolution::None);
7524    assert_eq!(
7525      dependency.maybe_type.maybe_specifier().unwrap().as_str(),
7526      "file:///bar.d.ts",
7527    );
7528  }
7529
7530  #[tokio::test]
7531  async fn dependency_declare_module() {
7532    #[derive(Debug)]
7533    struct TestLoader;
7534    impl Loader for TestLoader {
7535      fn load(
7536        &self,
7537        specifier: &ModuleSpecifier,
7538        _options: LoadOptions,
7539      ) -> LoadFuture {
7540        let specifier = specifier.clone();
7541        match specifier.as_str() {
7542          "file:///main.ts" => Box::pin(async move {
7543            Ok(Some(LoadResponse::Module {
7544              specifier: specifier.clone(),
7545              maybe_headers: None,
7546              mtime: None,
7547              content: b"
7548                declare module 'foo' {}
7549              "
7550              .to_vec()
7551              .into(),
7552            }))
7553          }),
7554          "file:///foo.d.ts" => Box::pin(async move {
7555            Ok(Some(LoadResponse::Module {
7556              specifier: specifier.clone(),
7557              maybe_headers: None,
7558              mtime: None,
7559              content: vec![].into(),
7560            }))
7561          }),
7562          s => unreachable!("{s}"),
7563        }
7564      }
7565    }
7566    impl Resolver for TestLoader {
7567      fn resolve(
7568        &self,
7569        specifier_text: &str,
7570        _referrer_range: &Range,
7571        kind: ResolutionKind,
7572      ) -> Result<ModuleSpecifier, ResolveError> {
7573        match (specifier_text, kind) {
7574          ("foo", ResolutionKind::Types) => {
7575            Ok(ModuleSpecifier::parse("file:///foo.d.ts").unwrap())
7576          }
7577          e => unreachable!("{e:?}"),
7578        }
7579      }
7580    }
7581    let mut graph = ModuleGraph::new(GraphKind::TypesOnly);
7582    graph
7583      .build(
7584        vec![Url::parse("file:///main.ts").unwrap()],
7585        Vec::new(),
7586        &TestLoader,
7587        BuildOptions {
7588          resolver: Some(&TestLoader),
7589          ..Default::default()
7590        },
7591      )
7592      .await;
7593    graph.valid().unwrap();
7594    let module = graph.get(&Url::parse("file:///main.ts").unwrap()).unwrap();
7595    let module = module.js().unwrap();
7596    let dependency = module.dependencies.get("foo").unwrap();
7597    assert_eq!(dependency.maybe_code, Resolution::None);
7598    assert_eq!(
7599      dependency.maybe_type.maybe_specifier().unwrap().as_str(),
7600      "file:///foo.d.ts",
7601    );
7602  }
7603
7604  #[tokio::test]
7605  async fn dependency_jsx_import_source_types_resolution() {
7606    let mut mem_loader = MemoryLoader::default();
7607    mem_loader.add_source_with_text(
7608      "file:///foo.tsx",
7609      "
7610/* @jsxImportSource foo */
7611",
7612    );
7613    mem_loader.add_source(
7614      "file:///foo/jsx-runtime",
7615      Source::Module {
7616        specifier: "file:///foo/jsx-runtime",
7617        maybe_headers: Some(vec![("content-type", "application/javascript")]),
7618        content: "",
7619      },
7620    );
7621    mem_loader.add_source(
7622      "file:///foo/types/jsx-runtime",
7623      Source::Module {
7624        specifier: "file:///foo/types/jsx-runtime",
7625        maybe_headers: Some(vec![("content-type", "application/typescript")]),
7626        content: "",
7627      },
7628    );
7629    let mut graph = ModuleGraph::new(GraphKind::All);
7630
7631    #[derive(Debug)]
7632    struct TestResolver;
7633    impl Resolver for TestResolver {
7634      fn resolve(
7635        &self,
7636        specifier_text: &str,
7637        referrer_range: &Range,
7638        resolution_kind: ResolutionKind,
7639      ) -> Result<ModuleSpecifier, ResolveError> {
7640        if specifier_text == "foo/jsx-runtime" {
7641          match resolution_kind {
7642            ResolutionKind::Execution => {
7643              Ok(ModuleSpecifier::parse("file:///foo/jsx-runtime").unwrap())
7644            }
7645            ResolutionKind::Types => Ok(
7646              ModuleSpecifier::parse("file:///foo/types/jsx-runtime").unwrap(),
7647            ),
7648          }
7649        } else {
7650          Ok(resolve_import(specifier_text, &referrer_range.specifier)?)
7651        }
7652      }
7653    }
7654
7655    let resolver = TestResolver;
7656    graph
7657      .build(
7658        vec![Url::parse("file:///foo.tsx").unwrap()],
7659        Vec::new(),
7660        &mem_loader,
7661        BuildOptions {
7662          resolver: Some(&resolver),
7663          ..Default::default()
7664        },
7665      )
7666      .await;
7667    graph.valid().unwrap();
7668    let module = graph.get(&Url::parse("file:///foo.tsx").unwrap()).unwrap();
7669    let module = module.js().unwrap();
7670    let dependency_a = module.dependencies.get("foo/jsx-runtime").unwrap();
7671    assert_eq!(
7672      dependency_a.maybe_code.maybe_specifier().unwrap().as_str(),
7673      "file:///foo/jsx-runtime"
7674    );
7675    assert_eq!(
7676      dependency_a.maybe_type.maybe_specifier().unwrap().as_str(),
7677      "file:///foo/types/jsx-runtime"
7678    );
7679  }
7680
7681  #[tokio::test]
7682  async fn dependency_jsx_import_source_types_pragma() {
7683    let mut mem_loader = MemoryLoader::default();
7684    mem_loader.add_source_with_text(
7685      "file:///foo.tsx",
7686      "
7687/* @jsxImportSource http://localhost */
7688/* @jsxImportSourceTypes http://localhost/types */
7689",
7690    );
7691    mem_loader.add_source(
7692      "http://localhost/jsx-runtime",
7693      Source::Module {
7694        specifier: "http://localhost/jsx-runtime",
7695        maybe_headers: Some(vec![("content-type", "application/javascript")]),
7696        content: "",
7697      },
7698    );
7699    mem_loader.add_source(
7700      "http://localhost/types/jsx-runtime",
7701      Source::Module {
7702        specifier: "http://localhost/types/jsx-runtime",
7703        maybe_headers: Some(vec![("content-type", "application/typescript")]),
7704        content: "",
7705      },
7706    );
7707    let mut graph = ModuleGraph::new(GraphKind::All);
7708    graph
7709      .build(
7710        vec![Url::parse("file:///foo.tsx").unwrap()],
7711        Vec::new(),
7712        &mem_loader,
7713        Default::default(),
7714      )
7715      .await;
7716    graph.valid().unwrap();
7717    let module = graph.get(&Url::parse("file:///foo.tsx").unwrap()).unwrap();
7718    let module = module.js().unwrap();
7719    let dependency_a = module
7720      .dependencies
7721      .get("http://localhost/jsx-runtime")
7722      .unwrap();
7723    assert_eq!(
7724      dependency_a.maybe_type.maybe_specifier().unwrap().as_str(),
7725      "http://localhost/types/jsx-runtime"
7726    );
7727    assert_eq!(
7728      dependency_a.maybe_deno_types_specifier.as_ref().unwrap(),
7729      "http://localhost/types/jsx-runtime"
7730    );
7731  }
7732
7733  #[cfg(feature = "fast_check")]
7734  #[tokio::test]
7735  async fn fast_check_dts() {
7736    use deno_ast::EmittedSourceText;
7737
7738    let mut exports = IndexMap::new();
7739    exports.insert(".".to_string(), "./foo.ts".to_string());
7740
7741    let workspace_members = vec![WorkspaceMember {
7742      base: Url::parse("file:///").unwrap(),
7743      exports: exports.clone(),
7744      name: "@foo/bar".into(),
7745      version: Some(Version::parse_standard("1.0.0").unwrap()),
7746    }];
7747    let mut test_loader = MemoryLoader::default();
7748    test_loader.add_source_with_text(
7749      "file:///foo.ts",
7750      "
7751    export function add(a: number, b: number): number {
7752      return a + b;
7753    }
7754  ",
7755    );
7756    let mut graph = ModuleGraph::new(GraphKind::All);
7757    graph
7758      .build(
7759        vec![Url::parse("file:///foo.ts").unwrap()],
7760        Vec::new(),
7761        &test_loader,
7762        BuildOptions {
7763          ..Default::default()
7764        },
7765      )
7766      .await;
7767    graph.build_fast_check_type_graph(BuildFastCheckTypeGraphOptions {
7768      fast_check_cache: None,
7769      fast_check_dts: true,
7770      workspace_fast_check: WorkspaceFastCheckOption::Enabled(
7771        &workspace_members,
7772      ),
7773      ..Default::default()
7774    });
7775    graph.valid().unwrap();
7776    let module = graph.get(&Url::parse("file:///foo.ts").unwrap()).unwrap();
7777    let module = module.js().unwrap();
7778    let FastCheckTypeModuleSlot::Module(fsm) =
7779      module.fast_check.clone().unwrap()
7780    else {
7781      unreachable!();
7782    };
7783    let dts = fsm.dts.unwrap();
7784    let source_map = SourceMap::single(
7785      module.specifier.clone(),
7786      module.source.text.to_string(),
7787    );
7788    let EmittedSourceText { text, .. } = emit(
7789      (&dts.program).into(),
7790      &dts.comments.as_single_threaded(),
7791      &source_map,
7792      &EmitOptions {
7793        remove_comments: false,
7794        source_map: deno_ast::SourceMapOption::None,
7795        ..Default::default()
7796      },
7797    )
7798    .unwrap();
7799    assert_eq!(
7800      text.trim(),
7801      "export declare function add(a: number, b: number): number;"
7802    );
7803    assert!(dts.diagnostics.is_empty());
7804  }
7805
7806  #[cfg(feature = "fast_check")]
7807  #[tokio::test]
7808  async fn fast_check_external() {
7809    use deno_ast::EmittedSourceText;
7810
7811    let mut exports = IndexMap::new();
7812    exports.insert(".".to_string(), "./foo.ts".to_string());
7813
7814    let workspace_members = vec![WorkspaceMember {
7815      base: Url::parse("file:///").unwrap(),
7816      exports: exports.clone(),
7817      name: "@foo/bar".into(),
7818      version: Some(Version::parse_standard("1.0.0").unwrap()),
7819    }];
7820    let mut test_loader = MemoryLoader::default();
7821    test_loader.add_source_with_text(
7822      "file:///foo.ts",
7823      "export * from 'jsr:@package/foo';",
7824    );
7825    test_loader.add_jsr_package_info(
7826      "@package/foo",
7827      &JsrPackageInfo {
7828        versions: HashMap::from([(
7829          Version::parse_standard("1.0.0").unwrap(),
7830          JsrPackageInfoVersion::default(),
7831        )]),
7832      },
7833    );
7834    test_loader.add_jsr_version_info(
7835      "@package/foo",
7836      "1.0.0",
7837      &JsrPackageVersionInfo {
7838        exports: json!({ ".": "./mod.ts" }),
7839        module_graph_1: None,
7840        module_graph_2: None,
7841        manifest: Default::default(),
7842        lockfile_checksum: None,
7843      },
7844    );
7845    test_loader.add_external_source("https://jsr.io/@package/foo/1.0.0/mod.ts");
7846    let mut graph = ModuleGraph::new(GraphKind::All);
7847    graph
7848      .build(
7849        vec![Url::parse("file:///foo.ts").unwrap()],
7850        Vec::new(),
7851        &test_loader,
7852        BuildOptions::default(),
7853      )
7854      .await;
7855    graph.build_fast_check_type_graph(BuildFastCheckTypeGraphOptions {
7856      fast_check_cache: None,
7857      fast_check_dts: true,
7858      workspace_fast_check: WorkspaceFastCheckOption::Enabled(
7859        &workspace_members,
7860      ),
7861      ..Default::default()
7862    });
7863    graph.valid().unwrap();
7864    {
7865      let module = graph.get(&Url::parse("file:///foo.ts").unwrap()).unwrap();
7866      let FastCheckTypeModuleSlot::Module(fsm) =
7867        module.js().unwrap().fast_check.clone().unwrap()
7868      else {
7869        unreachable!();
7870      };
7871      let dts = fsm.dts.unwrap();
7872      let source_map = SourceMap::single(
7873        module.specifier().clone(),
7874        module.source().unwrap().to_string(),
7875      );
7876      let EmittedSourceText { text, .. } = emit(
7877        (&dts.program).into(),
7878        &dts.comments.as_single_threaded(),
7879        &source_map,
7880        &EmitOptions {
7881          remove_comments: false,
7882          source_map: deno_ast::SourceMapOption::None,
7883          ..Default::default()
7884        },
7885      )
7886      .unwrap();
7887      assert_eq!(text.trim(), "export * from 'jsr:@package/foo';");
7888      assert!(dts.diagnostics.is_empty());
7889    }
7890
7891    let module = graph
7892      .get(&Url::parse("https://jsr.io/@package/foo/1.0.0/mod.ts").unwrap())
7893      .unwrap();
7894    assert!(module.external().is_some());
7895  }
7896
7897  #[test]
7898  fn leading_v_version_tag_err() {
7899    {
7900      let err =
7901        JsrPackageFormatError::VersionTagNotSupported { tag: "v1.2".into() };
7902      assert_eq!(err.to_string(), "Version tag not supported in jsr specifiers ('v1.2'). Remove leading 'v' before version.");
7903    }
7904    {
7905      let err = JsrPackageFormatError::VersionTagNotSupported {
7906        tag: "latest".into(),
7907      };
7908      assert_eq!(
7909        err.to_string(),
7910        "Version tag not supported in jsr specifiers ('latest')."
7911      );
7912    }
7913    {
7914      let err = JsrPackageFormatError::VersionTagNotSupported {
7915        tag: "version".into(), // not a vversion with a leading 'v'
7916      };
7917      assert_eq!(
7918        err.to_string(),
7919        "Version tag not supported in jsr specifiers ('version')."
7920      );
7921    }
7922  }
7923
7924  #[tokio::test]
7925  async fn check_js_option_custom() {
7926    #[derive(Debug)]
7927    struct CustomResolver;
7928
7929    impl CheckJsResolver for CustomResolver {
7930      fn resolve(&self, specifier: &ModuleSpecifier) -> bool {
7931        specifier.as_str() == "file:///true.js"
7932      }
7933    }
7934
7935    struct TestLoader;
7936    impl Loader for TestLoader {
7937      fn load(
7938        &self,
7939        specifier: &ModuleSpecifier,
7940        _options: LoadOptions,
7941      ) -> LoadFuture {
7942        let specifier = specifier.clone();
7943        match specifier.as_str() {
7944          "file:///valid.js" => Box::pin(async move {
7945            Ok(Some(LoadResponse::Module {
7946              specifier: specifier.clone(),
7947              maybe_headers: None,
7948              mtime: None,
7949              content: b"export {}".to_vec().into(),
7950            }))
7951          }),
7952          "file:///true.js" => Box::pin(async move {
7953            Ok(Some(LoadResponse::Module {
7954              specifier: specifier.clone(),
7955              maybe_headers: None,
7956              mtime: None,
7957              content: b"// @ts-types='invalid'\nimport {} from './valid.js';"
7958                .to_vec()
7959                .into(),
7960            }))
7961          }),
7962          "file:///false.js" => Box::pin(async move {
7963            Ok(Some(LoadResponse::Module {
7964              specifier: specifier.clone(),
7965              maybe_headers: None,
7966              mtime: None,
7967              // the 'invalid' shouldn't be visited here
7968              content: b"// @ts-types='invalid'\nimport {} from './valid.js';"
7969                .to_vec()
7970                .into(),
7971            }))
7972          }),
7973          "file:///main.ts" => Box::pin(async move {
7974            Ok(Some(LoadResponse::Module {
7975              specifier: specifier.clone(),
7976              maybe_headers: None,
7977              mtime: None,
7978              content: b"import './true.js'; import './false.js'"
7979                .to_vec()
7980                .into(),
7981            }))
7982          }),
7983          _ => unreachable!(),
7984        }
7985      }
7986    }
7987    let loader = TestLoader;
7988    let mut graph = ModuleGraph::new(GraphKind::All);
7989    let roots = vec![Url::parse("file:///main.ts").unwrap()];
7990    graph
7991      .build(roots.clone(), Vec::new(), &loader, Default::default())
7992      .await;
7993    assert_eq!(graph.specifiers_count(), 4);
7994    let errors = graph
7995      .walk(
7996        roots.iter(),
7997        WalkOptions {
7998          check_js: CheckJsOption::Custom(&CustomResolver),
7999          follow_dynamic: false,
8000          kind: GraphKind::All,
8001          prefer_fast_check_graph: false,
8002        },
8003      )
8004      .errors()
8005      .collect::<Vec<_>>();
8006
8007    // should only be 1 for true.js and not false.js
8008    assert_eq!(errors.len(), 1);
8009  }
8010
8011  #[test]
8012  fn module_text_source_bom() {
8013    let module_text_source = ModuleTextSource {
8014      text: "test".into(),
8015      decoded_kind: DecodedArcSourceDetailKind::OnlyUtf8Bom,
8016    };
8017    assert_eq!(
8018      module_text_source
8019        .try_get_original_bytes()
8020        .unwrap()
8021        .to_vec(),
8022      vec![0xEF, 0xBB, 0xBF, b't', b'e', b's', b't']
8023    )
8024  }
8025}