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