deno_graph/
graph.rs

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