deno_graph/
lib.rs

1// Copyright 2018-2024 the Deno authors. MIT license.
2
3#![deny(clippy::print_stderr)]
4#![deny(clippy::print_stdout)]
5#![deny(clippy::unused_async)]
6#![deny(clippy::unnecessary_wraps)]
7
8mod analyzer;
9mod ast;
10mod collections;
11mod graph;
12mod jsr;
13mod module_specifier;
14mod rt;
15
16#[cfg(feature = "symbols")]
17pub mod symbols;
18
19mod fast_check;
20pub mod packages;
21pub mod source;
22
23use source::FileSystem;
24use source::JsrUrlProvider;
25use source::Resolver;
26
27use std::collections::HashMap;
28use std::sync::Arc;
29use std::time::SystemTime;
30
31pub use analyzer::DependencyDescriptor;
32pub use analyzer::DynamicArgument;
33pub use analyzer::DynamicDependencyDescriptor;
34pub use analyzer::DynamicTemplatePart;
35pub use analyzer::JsDocImportInfo;
36pub use analyzer::ModuleAnalyzer;
37pub use analyzer::ModuleInfo;
38pub use analyzer::PositionRange;
39pub use analyzer::SpecifierWithRange;
40pub use analyzer::StaticDependencyDescriptor;
41pub use analyzer::TypeScriptReference;
42pub use ast::CapturingEsParser;
43pub use ast::CapturingModuleAnalyzer;
44pub use ast::DefaultEsParser;
45pub use ast::DefaultModuleAnalyzer;
46pub use ast::DefaultParsedSourceStore;
47pub use ast::EsParser;
48pub use ast::ParseOptions;
49pub use ast::ParsedSourceStore;
50pub use ast::ParserModuleAnalyzer;
51pub use deno_ast::MediaType;
52pub use fast_check::FastCheckCache;
53pub use fast_check::FastCheckCacheItem;
54pub use fast_check::FastCheckCacheKey;
55pub use fast_check::FastCheckCacheModuleItem;
56pub use fast_check::FastCheckCacheModuleItemDiagnostic;
57pub use fast_check::FastCheckCacheModuleItemInfo;
58pub use fast_check::FastCheckDiagnostic;
59pub use fast_check::FastCheckDiagnosticRange;
60#[cfg(feature = "fast_check")]
61pub use fast_check::FastCheckModule;
62#[cfg(feature = "fast_check")]
63pub use graph::BuildFastCheckTypeGraphOptions;
64pub use graph::BuildOptions;
65pub use graph::CheckJsOption;
66pub use graph::CheckJsResolver;
67pub use graph::Dependency;
68pub use graph::ExternalModule;
69pub use graph::FastCheckTypeModule;
70pub use graph::FastCheckTypeModuleSlot;
71pub use graph::FillFromLockfileOptions;
72pub use graph::GraphImport;
73pub use graph::GraphKind;
74pub use graph::Import;
75pub use graph::ImportKind;
76pub use graph::JsModule;
77pub use graph::JsonModule;
78pub use graph::JsrLoadError;
79pub use graph::Module;
80pub use graph::ModuleEntryRef;
81pub use graph::ModuleError;
82pub use graph::ModuleGraph;
83pub use graph::ModuleGraphError;
84pub use graph::ModuleLoadError;
85pub use graph::NpmLoadError;
86pub use graph::NpmModule;
87pub use graph::Position;
88pub use graph::Range;
89pub use graph::Resolution;
90pub use graph::ResolutionError;
91pub use graph::ResolutionResolved;
92pub use graph::TypesDependency;
93pub use graph::WalkOptions;
94pub use graph::WasmModule;
95#[cfg(feature = "fast_check")]
96pub use graph::WorkspaceFastCheckOption;
97pub use graph::WorkspaceMember;
98pub use module_specifier::resolve_import;
99pub use module_specifier::ModuleSpecifier;
100pub use module_specifier::SpecifierError;
101pub use rt::Executor;
102pub use source::NpmResolvePkgReqsResult;
103
104pub use deno_ast::dep::ImportAttribute;
105pub use deno_ast::dep::ImportAttributes;
106pub use deno_ast::dep::StaticDependencyKind;
107
108/// Additional import that should be brought into the scope of
109/// the module graph to add to the graph's "imports". This may
110/// be extra modules such as TypeScript's "types" option or JSX
111/// runtime types.
112#[derive(Debug, Clone)]
113pub struct ReferrerImports {
114  /// The referrer to resolve the imports from.
115  pub referrer: ModuleSpecifier,
116  /// Specifiers relative to the referrer to resolve.
117  pub imports: Vec<String>,
118}
119
120pub struct ParseModuleOptions<'a> {
121  pub graph_kind: GraphKind,
122  pub specifier: ModuleSpecifier,
123  pub maybe_headers: Option<HashMap<String, String>>,
124  pub mtime: Option<SystemTime>,
125  pub content: Arc<[u8]>,
126  pub file_system: &'a FileSystem,
127  pub jsr_url_provider: &'a dyn JsrUrlProvider,
128  pub maybe_resolver: Option<&'a dyn Resolver>,
129  pub module_analyzer: &'a dyn ModuleAnalyzer,
130}
131
132/// Parse an individual module, returning the module as a result, otherwise
133/// erroring with a module graph error.
134#[allow(clippy::result_large_err)]
135pub async fn parse_module(
136  options: ParseModuleOptions<'_>,
137) -> Result<Module, ModuleError> {
138  let module_source_and_info = graph::parse_module_source_and_info(
139    options.module_analyzer,
140    graph::ParseModuleAndSourceInfoOptions {
141      specifier: options.specifier,
142      maybe_headers: options.maybe_headers,
143      mtime: options.mtime,
144      content: options.content,
145      maybe_attribute_type: None,
146      maybe_referrer: None,
147      is_root: true,
148      is_dynamic_branch: false,
149    },
150  )
151  .await?;
152  let module = graph::parse_module(
153    options.file_system,
154    options.jsr_url_provider,
155    options.maybe_resolver,
156    graph::ParseModuleOptions {
157      graph_kind: options.graph_kind,
158      module_source_and_info,
159    },
160  );
161
162  Ok(module)
163}
164
165pub struct ParseModuleFromAstOptions<'a> {
166  pub graph_kind: GraphKind,
167  pub specifier: ModuleSpecifier,
168  pub maybe_headers: Option<&'a HashMap<String, String>>,
169  pub mtime: Option<SystemTime>,
170  pub parsed_source: &'a deno_ast::ParsedSource,
171  pub file_system: &'a FileSystem,
172  pub jsr_url_provider: &'a dyn JsrUrlProvider,
173  pub maybe_resolver: Option<&'a dyn Resolver>,
174}
175
176/// Parse an individual module from an AST, returning the module.
177pub fn parse_module_from_ast(options: ParseModuleFromAstOptions) -> JsModule {
178  graph::parse_js_module_from_module_info(
179    options.graph_kind,
180    options.specifier,
181    options.parsed_source.media_type(),
182    options.maybe_headers,
183    ParserModuleAnalyzer::module_info(options.parsed_source),
184    options.mtime,
185    options.parsed_source.text().clone(),
186    options.file_system,
187    options.jsr_url_provider,
188    options.maybe_resolver,
189  )
190}
191
192#[cfg(test)]
193mod tests {
194  use crate::graph::Import;
195  use crate::graph::ImportKind;
196  use crate::graph::ResolutionResolved;
197  use crate::source::NullFileSystem;
198  use crate::source::ResolutionKind;
199
200  use self::graph::CheckJsOption;
201
202  use super::*;
203  use deno_error::JsErrorBox;
204  use indexmap::IndexMap;
205  use indexmap::IndexSet;
206  use pretty_assertions::assert_eq;
207  use serde_json::json;
208  use source::tests::MockResolver;
209  use source::CacheInfo;
210  use source::MemoryLoader;
211  use source::ResolutionMode;
212  use source::Source;
213  use source::DEFAULT_JSX_IMPORT_SOURCE_MODULE;
214  use std::cell::RefCell;
215  use std::collections::BTreeMap;
216
217  type Sources<'a> = Vec<(&'a str, Source<&'a str>)>;
218
219  fn setup(
220    sources: Sources,
221    cache_info: Vec<(&str, CacheInfo)>,
222  ) -> MemoryLoader {
223    MemoryLoader::new(sources, cache_info)
224  }
225
226  #[tokio::test]
227  async fn test_build_graph() {
228    let loader = setup(
229      vec![
230        (
231          "file:///a/test01.ts",
232          Source::Module {
233            specifier: "file:///a/test01.ts",
234            maybe_headers: None,
235            content: r#"import * as b from "./test02.ts";"#,
236          },
237        ),
238        (
239          "file:///a/test02.ts",
240          Source::Module {
241            specifier: "file:///a/test02.ts",
242            maybe_headers: None,
243            content: r#"export const b = "b";"#,
244          },
245        ),
246      ],
247      vec![],
248    );
249    let root_specifier =
250      ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url");
251    let mut graph = ModuleGraph::new(GraphKind::All);
252    graph
253      .build(
254        vec![root_specifier.clone()],
255        Default::default(),
256        &loader,
257        Default::default(),
258      )
259      .await;
260    assert_eq!(graph.module_slots.len(), 2);
261    assert_eq!(graph.roots, IndexSet::from([root_specifier.clone()]));
262    assert!(graph.contains(&root_specifier));
263    assert!(
264      !graph.contains(&ModuleSpecifier::parse("file:///a/test03.ts").unwrap())
265    );
266    let module = graph
267      .module_slots
268      .get(&root_specifier)
269      .unwrap()
270      .module()
271      .unwrap()
272      .js()
273      .unwrap();
274    assert_eq!(module.dependencies.len(), 1);
275    let maybe_dependency = module.dependencies.get("./test02.ts");
276    assert!(maybe_dependency.is_some());
277    let dependency_specifier =
278      ModuleSpecifier::parse("file:///a/test02.ts").unwrap();
279    let dependency = maybe_dependency.unwrap();
280    assert!(!dependency.is_dynamic);
281    assert_eq!(
282      dependency.maybe_code.ok().unwrap().specifier,
283      dependency_specifier
284    );
285    assert_eq!(dependency.maybe_type, Resolution::None);
286    let maybe_dep_module_slot = graph.get(&dependency_specifier);
287    assert!(maybe_dep_module_slot.is_some());
288  }
289
290  #[tokio::test]
291  async fn test_build_graph_multiple_roots() {
292    let loader = setup(
293      vec![
294        (
295          "file:///a/test01.ts",
296          Source::Module {
297            specifier: "file:///a/test01.ts",
298            maybe_headers: None,
299            content: r#"import * as b from "./test02.ts";"#,
300          },
301        ),
302        (
303          "file:///a/test02.ts",
304          Source::Module {
305            specifier: "file:///a/test02.ts",
306            maybe_headers: None,
307            content: r#"export const b = "b";"#,
308          },
309        ),
310        (
311          "https://example.com/a.ts",
312          Source::Module {
313            specifier: "https://example.com/a.ts",
314            maybe_headers: None,
315            content: r#"import * as c from "./c.ts";"#,
316          },
317        ),
318        (
319          "https://example.com/c.ts",
320          Source::Module {
321            specifier: "https://example.com/c.ts",
322            maybe_headers: None,
323            content: r#"export const c = "c";"#,
324          },
325        ),
326      ],
327      vec![],
328    );
329    let roots = IndexSet::from([
330      ModuleSpecifier::parse("file:///a/test01.ts").unwrap(),
331      ModuleSpecifier::parse("https://example.com/a.ts").unwrap(),
332    ]);
333    let mut graph = ModuleGraph::new(GraphKind::All);
334    graph
335      .build(
336        roots.iter().cloned().collect(),
337        Vec::new(),
338        &loader,
339        Default::default(),
340      )
341      .await;
342    assert_eq!(graph.module_slots.len(), 4);
343    assert_eq!(graph.roots, roots);
344    assert!(
345      graph.contains(&ModuleSpecifier::parse("file:///a/test01.ts").unwrap())
346    );
347    assert!(
348      graph.contains(&ModuleSpecifier::parse("file:///a/test02.ts").unwrap())
349    );
350    assert!(graph
351      .contains(&ModuleSpecifier::parse("https://example.com/a.ts").unwrap()));
352    assert!(graph
353      .contains(&ModuleSpecifier::parse("https://example.com/c.ts").unwrap()));
354  }
355
356  #[tokio::test]
357  async fn test_build_graph_multiple_times() {
358    let loader = setup(
359      vec![
360        (
361          "file:///a/test01.ts",
362          Source::Module {
363            specifier: "file:///a/test01.ts",
364            maybe_headers: None,
365            content: r#"import * as b from "./test02.ts";"#,
366          },
367        ),
368        (
369          "file:///a/test02.ts",
370          Source::Module {
371            specifier: "file:///a/test02.ts",
372            maybe_headers: None,
373            content: r#"import "https://example.com/c.ts"; export const b = "b";"#,
374          },
375        ),
376        (
377          "https://example.com/a.ts",
378          Source::Module {
379            specifier: "https://example.com/a.ts",
380            maybe_headers: None,
381            content: r#"import * as c from "./c.ts";"#,
382          },
383        ),
384        (
385          "https://example.com/c.ts",
386          Source::Module {
387            specifier: "https://example.com/c.ts",
388            maybe_headers: None,
389            content: r#"import "./d.ts"; export const c = "c";"#,
390          },
391        ),
392        (
393          "https://example.com/d.ts",
394          Source::Module {
395            specifier: "https://example.com/d.ts",
396            maybe_headers: None,
397            content: r#"export const d = "d";"#,
398          },
399        ),
400      ],
401      vec![],
402    );
403    let first_root = ModuleSpecifier::parse("file:///a/test01.ts").unwrap();
404    let second_root =
405      ModuleSpecifier::parse("https://example.com/a.ts").unwrap();
406    let third_root =
407      ModuleSpecifier::parse("https://example.com/d.ts").unwrap();
408    let mut graph = ModuleGraph::new(GraphKind::All);
409    graph
410      .build(
411        vec![first_root.clone()],
412        Default::default(),
413        &loader,
414        Default::default(),
415      )
416      .await;
417    assert_eq!(graph.module_slots.len(), 4);
418    assert_eq!(graph.roots, IndexSet::from([first_root.clone()]));
419
420    // now build with the second root
421    graph
422      .build(
423        vec![second_root.clone()],
424        Default::default(),
425        &loader,
426        Default::default(),
427      )
428      .await;
429    let mut roots = IndexSet::from([first_root, second_root]);
430    assert_eq!(graph.module_slots.len(), 5);
431    assert_eq!(graph.roots, roots);
432    assert!(
433      graph.contains(&ModuleSpecifier::parse("file:///a/test01.ts").unwrap())
434    );
435    assert!(
436      graph.contains(&ModuleSpecifier::parse("file:///a/test02.ts").unwrap())
437    );
438    assert!(graph
439      .contains(&ModuleSpecifier::parse("https://example.com/a.ts").unwrap()));
440    assert!(graph
441      .contains(&ModuleSpecifier::parse("https://example.com/c.ts").unwrap()));
442    assert!(graph
443      .contains(&ModuleSpecifier::parse("https://example.com/d.ts").unwrap()));
444
445    // now try making one of the already existing modules a root
446    graph
447      .build(
448        vec![third_root.clone()],
449        Default::default(),
450        &loader,
451        Default::default(),
452      )
453      .await;
454    roots.insert(third_root);
455    assert_eq!(graph.module_slots.len(), 5);
456    assert_eq!(graph.roots, roots);
457  }
458
459  #[tokio::test]
460  async fn test_build_graph_json_module_root() {
461    let loader = setup(
462      vec![(
463        "file:///a/test.json",
464        Source::Module {
465          specifier: "file:///a/test.json",
466          maybe_headers: None,
467          content: r#"{"a": 1, "b": "c"}"#,
468        },
469      )],
470      vec![],
471    );
472    let roots = vec![ModuleSpecifier::parse("file:///a/test.json").unwrap()];
473    let mut graph = ModuleGraph::new(GraphKind::All);
474    graph
475      .build(
476        roots.clone(),
477        Default::default(),
478        &loader,
479        BuildOptions {
480          is_dynamic: true,
481          ..Default::default()
482        },
483      )
484      .await;
485    assert_eq!(
486      json!(graph),
487      json!({
488        "roots": [
489          "file:///a/test.json"
490        ],
491        "modules": [
492          {
493            "size": 18,
494            "kind": "asserted",
495            "mediaType": "Json",
496            "specifier": "file:///a/test.json"
497          }
498        ],
499        "redirects": {}
500      })
501    );
502  }
503
504  #[tokio::test]
505  async fn test_valid_type_missing() {
506    let loader = setup(
507      vec![
508        (
509          "file:///a/test01.ts",
510          Source::Module {
511            specifier: "file:///a/test01.ts",
512            maybe_headers: None,
513            content: r#"// @deno-types=./test02.d.ts
514import * as a from "./test02.js";
515
516console.log(a);
517"#,
518          },
519        ),
520        (
521          "file:///a/test02.js",
522          Source::Module {
523            specifier: "file:///a/test02.js",
524            maybe_headers: None,
525            content: r#"export const b = "b";"#,
526          },
527        ),
528      ],
529      vec![],
530    );
531    let root_specifier =
532      ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url");
533    let mut graph = ModuleGraph::new(GraphKind::All);
534    graph
535      .build(
536        vec![root_specifier.clone()],
537        Default::default(),
538        &loader,
539        Default::default(),
540      )
541      .await;
542    assert!(graph.valid().is_ok());
543  }
544
545  #[tokio::test]
546  async fn test_valid_code_missing() {
547    let loader = setup(
548      vec![(
549        "file:///a/test01.ts",
550        Source::Module {
551          specifier: "file:///a/test01.ts",
552          maybe_headers: None,
553          content: r#"import * as a from "./test02.js";
554
555console.log(a);
556"#,
557        },
558      )],
559      vec![],
560    );
561    let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").unwrap();
562    let mut graph = ModuleGraph::new(GraphKind::All);
563    graph
564      .build(
565        vec![root_specifier.clone()],
566        Default::default(),
567        &loader,
568        Default::default(),
569      )
570      .await;
571    assert!(graph.valid().is_err());
572    assert_eq!(
573      graph.valid().err().unwrap().to_string(),
574      "Module not found \"file:///a/test02.js\"."
575    );
576  }
577
578  #[tokio::test]
579  async fn test_remote_import_data_url() {
580    let loader = setup(
581      vec![(
582        "https://deno.land/main.ts",
583        Source::Module {
584          specifier: "https://deno.land/main.ts",
585          maybe_headers: None,
586          content: r#"import * as a from "data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=";
587
588console.log(a);
589"#,
590        },
591      )],
592      vec![],
593    );
594    let root_specifier =
595      ModuleSpecifier::parse("https://deno.land/main.ts").unwrap();
596    let mut graph = ModuleGraph::new(GraphKind::All);
597    graph
598      .build(
599        vec![root_specifier.clone()],
600        Default::default(),
601        &loader,
602        Default::default(),
603      )
604      .await;
605    assert!(graph.valid().is_ok());
606  }
607
608  #[tokio::test]
609  async fn test_remote_import_local_url() {
610    for scheme in &["http", "https"] {
611      let root_specifier =
612        ModuleSpecifier::parse(&format!("{scheme}://deno.land/main.ts"))
613          .unwrap();
614      let loader = setup(
615        vec![
616          (
617            root_specifier.as_str(),
618            Source::Module {
619              specifier: root_specifier.as_str(),
620              maybe_headers: None,
621              content: r#"import * as a from "file:///local.ts";
622
623console.log(a);
624"#,
625            },
626          ),
627          (
628            "file:///local.ts",
629            Source::Module {
630              specifier: "file:///local.ts",
631              maybe_headers: None,
632              content: r#"console.log(1);"#,
633            },
634          ),
635        ],
636        vec![],
637      );
638      let mut graph = ModuleGraph::new(GraphKind::All);
639      graph
640        .build(
641          vec![root_specifier],
642          Default::default(),
643          &loader,
644          Default::default(),
645        )
646        .await;
647      assert!(matches!(
648        graph.valid().err().unwrap(),
649        ModuleGraphError::ResolutionError(
650          ResolutionError::InvalidLocalImport { .. },
651        )
652      ));
653    }
654  }
655
656  #[tokio::test]
657  async fn test_remote_import_local_url_remapped() {
658    for scheme in &["http", "https"] {
659      let root_specifier_str = format!("{scheme}://deno.land/main.ts");
660      let root_specifier = ModuleSpecifier::parse(&root_specifier_str).unwrap();
661      let loader = setup(
662        vec![
663          (
664            root_specifier.as_str(),
665            Source::Module {
666              specifier: root_specifier.as_str(),
667              maybe_headers: None,
668              content: r#"import * as a from "remapped";
669
670console.log(a);
671"#,
672            },
673          ),
674          (
675            "file:///local.ts",
676            Source::Module {
677              specifier: "file:///local.ts",
678              maybe_headers: None,
679              content: r#"console.log(1);"#,
680            },
681          ),
682        ],
683        vec![],
684      );
685      let resolver = MockResolver::new(
686        vec![(
687          root_specifier_str.as_str(),
688          vec![("remapped", "file:///local.ts")],
689        )],
690        vec![],
691      );
692      let maybe_resolver: Option<&dyn Resolver> = Some(&resolver);
693      let mut graph = ModuleGraph::new(GraphKind::All);
694      graph
695        .build(
696          vec![root_specifier.clone()],
697          Default::default(),
698          &loader,
699          BuildOptions {
700            resolver: maybe_resolver,
701            ..Default::default()
702          },
703        )
704        .await;
705      assert!(graph.valid().is_ok());
706    }
707  }
708
709  #[tokio::test]
710  async fn test_build_graph_imports() {
711    let loader = setup(
712      vec![
713        (
714          "file:///a/test01.ts",
715          Source::Module {
716            specifier: "file:///a/test01.ts",
717            maybe_headers: None,
718            content: r#"console.log("a");"#,
719          },
720        ),
721        (
722          "file:///a/types.d.ts",
723          Source::Module {
724            specifier: "file:///a/types.d.ts",
725            maybe_headers: None,
726            content: r#"export type { A } from "./types_01.d.ts";"#,
727          },
728        ),
729        (
730          "file:///a/types_01.d.ts",
731          Source::Module {
732            specifier: "file:///a/types_01.d.ts",
733            maybe_headers: None,
734            content: r#"export class A {};"#,
735          },
736        ),
737      ],
738      vec![],
739    );
740    let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").unwrap();
741    let config_specifier =
742      ModuleSpecifier::parse("file:///a/tsconfig.json").unwrap();
743    let imports = vec![ReferrerImports {
744      referrer: config_specifier,
745      imports: vec!["./types.d.ts".to_string()],
746    }];
747    let mut graph = ModuleGraph::new(GraphKind::All);
748    graph
749      .build(
750        vec![root_specifier],
751        imports,
752        &loader,
753        BuildOptions::default(),
754      )
755      .await;
756    assert_eq!(
757      json!(graph),
758      json!({
759        "roots": ["file:///a/test01.ts"],
760        "modules": [
761          {
762            "kind": "esm",
763            "mediaType": "TypeScript",
764            "size": 17,
765            "specifier": "file:///a/test01.ts"
766          },
767          {
768            "dependencies": [
769              {
770                "specifier": "./types_01.d.ts",
771                "type": {
772                  "specifier": "file:///a/types_01.d.ts",
773                  "span": {
774                    "start": {
775                      "line":0,
776                      "character":23
777                    },
778                    "end": {
779                      "line":0,
780                      "character":40
781                    }
782                  }
783                }
784              }
785            ],
786            "kind": "esm",
787            "mediaType": "Dts",
788            "size": 41,
789            "specifier": "file:///a/types.d.ts"
790          },
791          {
792            "kind": "esm",
793            "mediaType": "Dts",
794            "size": 18,
795            "specifier": "file:///a/types_01.d.ts"
796          }
797        ],
798        "imports": [
799          {
800            "referrer": "file:///a/tsconfig.json",
801            "dependencies": [
802              {
803                "specifier": "./types.d.ts",
804                "type": {
805                  "specifier": "file:///a/types.d.ts",
806                  "span": {
807                    "start": {
808                      "line": 0,
809                      "character": 0
810                    },
811                    "end": {
812                      "line": 0,
813                      "character": 0
814                    }
815                  }
816                }
817              }
818            ]
819          },
820        ],
821        "redirects":{},
822      })
823    );
824  }
825
826  #[tokio::test]
827  async fn test_build_graph_imports_imported() {
828    let loader = setup(
829      vec![
830        (
831          "file:///a/test01.ts",
832          Source::Module {
833            specifier: "file:///a/test01.ts",
834            maybe_headers: None,
835            content: r#"import config from "./deno.json" assert { type: "json" };
836
837            console.log(config);"#,
838          },
839        ),
840        (
841          "file:///a/deno.json",
842          Source::Module {
843            specifier: "file:///a/deno.json",
844            maybe_headers: None,
845            content: r#"{
846              "compilerOptions": {
847                "jsxImportSource": "https://esm.sh/preact"
848              }
849            }"#,
850          },
851        ),
852        (
853          "https://esm.sh/preact/runtime-jsx",
854          Source::Module {
855            specifier: "https://esm.sh/preact/runtime-jsx",
856            maybe_headers: Some(vec![(
857              "content-type",
858              "application/javascript",
859            )]),
860            content: r#"export function jsx() {}"#,
861          },
862        ),
863      ],
864      vec![],
865    );
866    let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").unwrap();
867    let config_specifier =
868      ModuleSpecifier::parse("file:///a/deno.json").unwrap();
869    let imports = vec![ReferrerImports {
870      referrer: config_specifier,
871      imports: vec!["https://esm.sh/preact/runtime-jsx".to_string()],
872    }];
873    let mut graph = ModuleGraph::new(GraphKind::All);
874    graph
875      .build(
876        vec![root_specifier],
877        imports,
878        &loader,
879        BuildOptions::default(),
880      )
881      .await;
882    assert_eq!(
883      json!(graph),
884      json!({
885        "roots": ["file:///a/test01.ts"],
886        "modules": [
887          {
888            "kind": "asserted",
889            "size": 125,
890            "mediaType": "Json",
891            "specifier": "file:///a/deno.json",
892          },
893          {
894            "dependencies": [
895              {
896                "specifier": "./deno.json",
897                "code": {
898                  "specifier": "file:///a/deno.json",
899                  "resolutionMode": "import",
900                  "span": {
901                    "start": {
902                      "line": 0,
903                      "character": 19,
904                    },
905                    "end": {
906                      "line": 0,
907                      "character": 32,
908                    }
909                  }
910                },
911                "assertionType": "json"
912              }
913            ],
914            "kind": "esm",
915            "size": 91,
916            "mediaType": "TypeScript",
917            "specifier": "file:///a/test01.ts",
918          },
919          {
920            "kind": "esm",
921            "size": 24,
922            "mediaType": "JavaScript",
923            "specifier": "https://esm.sh/preact/runtime-jsx",
924          },
925        ],
926        "imports": [
927          {
928            "referrer": "file:///a/deno.json",
929            "dependencies": [
930              {
931                "specifier": "https://esm.sh/preact/runtime-jsx",
932                "type": {
933                  "specifier": "https://esm.sh/preact/runtime-jsx",
934                  "span": {
935                    "start": {
936                      "line": 0,
937                      "character": 0,
938                    },
939                    "end": {
940                      "line": 0,
941                      "character": 0,
942                    }
943                  }
944                },
945              }
946            ],
947          }
948        ],
949        "redirects":{},
950      })
951    );
952  }
953
954  #[tokio::test]
955  async fn test_build_graph_imports_resolve_dependency() {
956    let loader = setup(
957      vec![
958        (
959          "file:///a/test01.ts",
960          Source::Module {
961            specifier: "file:///a/test01.ts",
962            maybe_headers: None,
963            content: r#"console.log("a");"#,
964          },
965        ),
966        (
967          "https://example.com/jsx-runtime",
968          Source::Module {
969            specifier: "https://example.com/jsx-runtime",
970            maybe_headers: Some(vec![
971              ("content-type", "application/javascript"),
972              ("x-typescript-types", "./jsx-runtime.d.ts"),
973            ]),
974            content: r#"export const a = "a";"#,
975          },
976        ),
977        (
978          "https://example.com/jsx-runtime.d.ts",
979          Source::Module {
980            specifier: "https://example.com/jsx-runtime.d.ts",
981            maybe_headers: Some(vec![(
982              "content-type",
983              "application/typescript",
984            )]),
985            content: r#"export const a: "a";"#,
986          },
987        ),
988      ],
989      vec![],
990    );
991    let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").unwrap();
992    let config_specifier =
993      ModuleSpecifier::parse("file:///a/tsconfig.json").unwrap();
994    let imports = vec![ReferrerImports {
995      referrer: config_specifier.clone(),
996      imports: vec!["https://example.com/jsx-runtime".to_string()],
997    }];
998    let mut graph = ModuleGraph::new(GraphKind::All);
999    graph
1000      .build(
1001        vec![root_specifier],
1002        imports,
1003        &loader,
1004        BuildOptions::default(),
1005      )
1006      .await;
1007    assert_eq!(
1008      graph.resolve_dependency(
1009        "https://example.com/jsx-runtime",
1010        &config_specifier,
1011        false
1012      ),
1013      Some(ModuleSpecifier::parse("https://example.com/jsx-runtime").unwrap())
1014        .as_ref()
1015    );
1016    assert_eq!(
1017      graph.resolve_dependency(
1018        "https://example.com/jsx-runtime",
1019        &config_specifier,
1020        true
1021      ),
1022      Some(
1023        ModuleSpecifier::parse("https://example.com/jsx-runtime.d.ts").unwrap()
1024      )
1025      .as_ref()
1026    );
1027    assert_eq!(
1028      graph
1029        .try_get(
1030          &ModuleSpecifier::parse("https://example.com/jsx-runtime").unwrap()
1031        )
1032        .unwrap()
1033        .unwrap()
1034        .specifier()
1035        .as_str(),
1036      "https://example.com/jsx-runtime"
1037    );
1038    assert_eq!(
1039      graph
1040        .try_get_prefer_types(
1041          &ModuleSpecifier::parse("https://example.com/jsx-runtime").unwrap()
1042        )
1043        .unwrap()
1044        .unwrap()
1045        .specifier()
1046        .as_str(),
1047      // should end up at the declaration file
1048      "https://example.com/jsx-runtime.d.ts"
1049    );
1050  }
1051
1052  #[tokio::test]
1053  async fn test_build_graph_with_headers() {
1054    let loader = setup(
1055      vec![(
1056        "https://example.com/a",
1057        Source::Module {
1058          specifier: "https://example.com/a",
1059          maybe_headers: Some(vec![(
1060            "content-type",
1061            "application/typescript; charset=utf-8",
1062          )]),
1063          content: r#"declare interface A { a: string; }"#,
1064        },
1065      )],
1066      vec![],
1067    );
1068    let root_specifier =
1069      ModuleSpecifier::parse("https://example.com/a").expect("bad url");
1070    let mut graph = ModuleGraph::new(GraphKind::All);
1071    graph
1072      .build(
1073        vec![root_specifier.clone()],
1074        Default::default(),
1075        &loader,
1076        Default::default(),
1077      )
1078      .await;
1079    assert_eq!(graph.module_slots.len(), 1);
1080    assert_eq!(graph.roots, IndexSet::from([root_specifier.clone()]));
1081    let module = graph
1082      .module_slots
1083      .get(&root_specifier)
1084      .unwrap()
1085      .module()
1086      .unwrap()
1087      .js()
1088      .unwrap();
1089    assert_eq!(module.media_type, MediaType::TypeScript);
1090  }
1091
1092  #[tokio::test]
1093  async fn test_build_graph_jsx_import_source() {
1094    let loader = setup(
1095      vec![
1096        (
1097          "file:///a/test01.tsx",
1098          Source::Module {
1099            specifier: "file:///a/test01.tsx",
1100            maybe_headers: None,
1101            content: r#"/* @jsxImportSource https://example.com/preact */
1102
1103            export function A() {
1104              <div>Hello Deno</div>
1105            }
1106            "#,
1107          },
1108        ),
1109        (
1110          "https://example.com/preact/jsx-runtime",
1111          Source::Module {
1112            specifier: "https://example.com/preact/jsx-runtime/index.js",
1113            maybe_headers: Some(vec![(
1114              "content-type",
1115              "application/javascript",
1116            )]),
1117            content: r#"export function jsx() {}"#,
1118          },
1119        ),
1120      ],
1121      vec![],
1122    );
1123    let root_specifier =
1124      ModuleSpecifier::parse("file:///a/test01.tsx").expect("bad url");
1125    let mut graph = ModuleGraph::new(GraphKind::All);
1126    graph
1127      .build(
1128        vec![root_specifier.clone()],
1129        Default::default(),
1130        &loader,
1131        Default::default(),
1132      )
1133      .await;
1134    assert_eq!(
1135      json!(graph),
1136      json!({
1137        "roots": [
1138          "file:///a/test01.tsx"
1139        ],
1140        "modules": [
1141          {
1142            "dependencies": [
1143              {
1144                "specifier": "https://example.com/preact/jsx-runtime",
1145                "code": {
1146                  "specifier": "https://example.com/preact/jsx-runtime",
1147                  "span": {
1148                    "start": {
1149                      "line": 0,
1150                      "character": 20
1151                    },
1152                    "end": {
1153                      "line": 0,
1154                      "character": 46
1155                    }
1156                  }
1157                }
1158              }
1159            ],
1160            "kind": "esm",
1161            "mediaType": "TSX",
1162            "size": 147,
1163            "specifier": "file:///a/test01.tsx"
1164          },
1165          {
1166            "kind": "esm",
1167            "mediaType": "JavaScript",
1168            "size": 24,
1169            "specifier": "https://example.com/preact/jsx-runtime/index.js"
1170          }
1171        ],
1172        "redirects": {
1173          "https://example.com/preact/jsx-runtime": "https://example.com/preact/jsx-runtime/index.js"
1174        }
1175      })
1176    );
1177  }
1178
1179  #[tokio::test]
1180  async fn test_build_graph_jsx_import_source_types() {
1181    let loader = setup(
1182      vec![
1183        (
1184          "file:///a/test01.tsx",
1185          Source::Module {
1186            specifier: "file:///a/test01.tsx",
1187            maybe_headers: None,
1188            content: r#"/* @jsxImportSource https://example.com/preact */
1189            /* @jsxImportSourceTypes https://example.com/preact-types */
1190
1191            export function A() {
1192              <div>Hello Deno</div>
1193            }
1194            "#,
1195          },
1196        ),
1197        (
1198          "https://example.com/preact/jsx-runtime",
1199          Source::Module {
1200            specifier: "https://example.com/preact/jsx-runtime/index.js",
1201            maybe_headers: Some(vec![(
1202              "content-type",
1203              "application/javascript",
1204            )]),
1205            content: r#"export function jsx() {}"#,
1206          },
1207        ),
1208        (
1209          "https://example.com/preact-types/jsx-runtime",
1210          Source::Module {
1211            specifier:
1212              "https://example.com/preact-types/jsx-runtime/index.d.ts",
1213            maybe_headers: Some(vec![(
1214              "content-type",
1215              "application/typescript",
1216            )]),
1217            content: r#"export declare function jsx();"#,
1218          },
1219        ),
1220      ],
1221      vec![],
1222    );
1223    let root_specifier =
1224      ModuleSpecifier::parse("file:///a/test01.tsx").expect("bad url");
1225    let mut graph = ModuleGraph::new(GraphKind::All);
1226    graph
1227      .build(
1228        vec![root_specifier.clone()],
1229        Default::default(),
1230        &loader,
1231        Default::default(),
1232      )
1233      .await;
1234    assert_eq!(
1235      json!(graph),
1236      json!({
1237        "roots": [
1238          "file:///a/test01.tsx"
1239        ],
1240        "modules": [
1241          {
1242            "dependencies": [
1243              {
1244                "specifier": "https://example.com/preact/jsx-runtime",
1245                "code": {
1246                  "specifier": "https://example.com/preact/jsx-runtime",
1247                  "span": {
1248                    "start": {
1249                      "line": 0,
1250                      "character": 20
1251                    },
1252                    "end": {
1253                      "line": 0,
1254                      "character": 46
1255                    }
1256                  }
1257                },
1258                "type": {
1259                  "specifier": "https://example.com/preact-types/jsx-runtime",
1260                  "span": {
1261                    "start": {
1262                      "line": 1,
1263                      "character": 37
1264                    },
1265                    "end": {
1266                      "line": 1,
1267                      "character": 69
1268                    }
1269                  }
1270                }
1271              }
1272            ],
1273            "kind": "esm",
1274            "mediaType": "TSX",
1275            "size": 220,
1276            "specifier": "file:///a/test01.tsx"
1277          },
1278          {
1279            "kind": "esm",
1280            "mediaType": "Dts",
1281            "size": 30,
1282            "specifier": "https://example.com/preact-types/jsx-runtime/index.d.ts"
1283          },
1284          {
1285            "kind": "esm",
1286            "mediaType": "JavaScript",
1287            "size": 24,
1288            "specifier": "https://example.com/preact/jsx-runtime/index.js"
1289          }
1290        ],
1291        "redirects": {
1292          "https://example.com/preact-types/jsx-runtime": "https://example.com/preact-types/jsx-runtime/index.d.ts",
1293          "https://example.com/preact/jsx-runtime": "https://example.com/preact/jsx-runtime/index.js"
1294        }
1295      })
1296    );
1297  }
1298
1299  #[tokio::test]
1300  async fn test_bare_specifier_error() {
1301    let loader = setup(
1302      vec![(
1303        "file:///a/test.ts",
1304        Source::Module {
1305          specifier: "file:///a/test.ts",
1306          maybe_headers: None,
1307          content: r#"import "foo";"#,
1308        },
1309      )],
1310      vec![],
1311    );
1312    let root_specifier =
1313      ModuleSpecifier::parse("file:///a/test.ts").expect("bad url");
1314    let mut graph = ModuleGraph::new(GraphKind::All);
1315    graph
1316      .build(
1317        vec![root_specifier.clone()],
1318        Default::default(),
1319        &loader,
1320        Default::default(),
1321      )
1322      .await;
1323    let result = graph.valid();
1324    assert!(result.is_err());
1325    let err = result.unwrap_err();
1326    match err {
1327      ModuleGraphError::ResolutionError(err) => {
1328        assert_eq!(err.range().specifier, root_specifier)
1329      }
1330      _ => unreachable!(),
1331    }
1332  }
1333
1334  #[tokio::test]
1335  async fn test_builtin_node_module() {
1336    let expectation = json!({
1337      "roots": [
1338        "file:///a/test.ts"
1339      ],
1340      "modules": [
1341        {
1342          "kind": "esm",
1343          "dependencies": [
1344            {
1345              "specifier": "node:path",
1346              "code": {
1347                "specifier": "node:path",
1348                "resolutionMode": "import",
1349                "span": {
1350                  "start": {
1351                    "line": 0,
1352                    "character": 7
1353                  },
1354                  "end": {
1355                    "line": 0,
1356                    "character": 18
1357                  }
1358                }
1359              },
1360            }
1361          ],
1362          "size": 19,
1363          "mediaType": "TypeScript",
1364          "specifier": "file:///a/test.ts"
1365        },
1366        {
1367          "kind": "node",
1368          "specifier": "node:path",
1369          "moduleName": "path",
1370        }
1371      ],
1372      "redirects": {}
1373    });
1374
1375    let loader = setup(
1376      vec![(
1377        "file:///a/test.ts",
1378        Source::Module {
1379          specifier: "file:///a/test.ts",
1380          maybe_headers: None,
1381          content: r#"import "node:path";"#,
1382        },
1383      )],
1384      vec![],
1385    );
1386    let root_specifier =
1387      ModuleSpecifier::parse("file:///a/test.ts").expect("bad url");
1388    let mut graph = ModuleGraph::new(GraphKind::All);
1389    graph
1390      .build(
1391        vec![root_specifier.clone()],
1392        Default::default(),
1393        &loader,
1394        Default::default(),
1395      )
1396      .await;
1397    graph.valid().unwrap();
1398    assert_eq!(json!(graph), expectation);
1399  }
1400
1401  #[tokio::test]
1402  async fn test_unsupported_media_type() {
1403    let loader = setup(
1404      vec![
1405        (
1406          "file:///a/test.ts",
1407          Source::Module {
1408            specifier: "file:///a/test.ts",
1409            maybe_headers: None,
1410            content: r#"import "./test.json";"#,
1411          },
1412        ),
1413        (
1414          "file:///a/test.json",
1415          Source::Module {
1416            specifier: "file:///a/test.json",
1417            maybe_headers: None,
1418            content: r#"{"hello":"world"}"#,
1419          },
1420        ),
1421      ],
1422      vec![],
1423    );
1424    let root_specifier =
1425      ModuleSpecifier::parse("file:///a/test.ts").expect("bad url");
1426    let mut graph = ModuleGraph::new(GraphKind::All);
1427    graph
1428      .build(
1429        vec![root_specifier.clone()],
1430        Default::default(),
1431        &loader,
1432        Default::default(),
1433      )
1434      .await;
1435    let result = graph.valid();
1436    assert!(result.is_err());
1437    let err = result.unwrap_err();
1438    assert!(matches!(
1439      err,
1440      ModuleGraphError::ModuleError(ModuleError::UnsupportedMediaType {
1441        media_type: MediaType::Json,
1442        ..
1443      }),
1444    ));
1445  }
1446
1447  #[tokio::test]
1448  async fn test_root_is_extensionless() {
1449    let loader = setup(
1450      vec![
1451        (
1452          "file:///a/test01",
1453          Source::Module {
1454            specifier: "file:///a/test01",
1455            maybe_headers: None,
1456            content: r#"import * as b from "./test02.ts";"#,
1457          },
1458        ),
1459        (
1460          "file:///a/test02.ts",
1461          Source::Module {
1462            specifier: "file:///a/test02.ts",
1463            maybe_headers: None,
1464            content: r#"export const b = "b";"#,
1465          },
1466        ),
1467      ],
1468      vec![],
1469    );
1470    let root_specifier =
1471      ModuleSpecifier::parse("file:///a/test01").expect("bad url");
1472    let mut graph = ModuleGraph::new(GraphKind::All);
1473    graph
1474      .build(
1475        vec![root_specifier.clone()],
1476        Default::default(),
1477        &loader,
1478        Default::default(),
1479      )
1480      .await;
1481    assert!(graph.valid().is_ok());
1482  }
1483
1484  #[tokio::test]
1485  async fn test_crate_graph_with_dynamic_imports() {
1486    let loader = setup(
1487      vec![
1488        (
1489          "file:///a.ts",
1490          Source::Module {
1491            specifier: "file:///a.ts",
1492            maybe_headers: None,
1493            content: r#"const b = await import("./b.ts");"#,
1494          },
1495        ),
1496        (
1497          "file:///b.ts",
1498          Source::Module {
1499            specifier: "file:///b.ts",
1500            maybe_headers: None,
1501            content: r#"export const b = "b";"#,
1502          },
1503        ),
1504      ],
1505      vec![],
1506    );
1507    let root_specifier =
1508      ModuleSpecifier::parse("file:///a.ts").expect("bad url");
1509    let mut graph = ModuleGraph::new(GraphKind::All);
1510    graph
1511      .build(
1512        vec![root_specifier.clone()],
1513        Default::default(),
1514        &loader,
1515        Default::default(),
1516      )
1517      .await;
1518    assert_eq!(
1519      json!(graph),
1520      json!({
1521        "roots": [
1522          "file:///a.ts"
1523        ],
1524        "modules": [
1525          {
1526            "dependencies": [
1527              {
1528                "specifier": "./b.ts",
1529                "code": {
1530                  "specifier": "file:///b.ts",
1531                  "resolutionMode": "import",
1532                  "span": {
1533                    "start": {
1534                      "line": 0,
1535                      "character": 23
1536                    },
1537                    "end": {
1538                      "line": 0,
1539                      "character": 31
1540                    }
1541                  }
1542                },
1543                "isDynamic": true
1544              }
1545            ],
1546            "kind": "esm",
1547            "mediaType": "TypeScript",
1548            "size": 33,
1549            "specifier": "file:///a.ts"
1550          },
1551          {
1552            "kind": "esm",
1553            "mediaType": "TypeScript",
1554            "size": 21,
1555            "specifier": "file:///b.ts"
1556          }
1557        ],
1558        "redirects": {}
1559      })
1560    );
1561  }
1562
1563  #[tokio::test]
1564  async fn test_build_graph_with_jsdoc_imports() {
1565    let loader = setup(
1566      vec![
1567        (
1568          "file:///a/test.js",
1569          Source::Module {
1570            specifier: "file:///a/test.js",
1571            maybe_headers: None,
1572            content: r#"
1573/**
1574 * Some js doc
1575 *
1576 * @param {import("./types.d.ts").A} a
1577 * @return {import("./other.ts").B}
1578 */
1579export function a(a) {
1580  return;
1581}
1582"#,
1583          },
1584        ),
1585        (
1586          "file:///a/types.d.ts",
1587          Source::Module {
1588            specifier: "file:///a/types.d.ts",
1589            maybe_headers: None,
1590            content: r#"export type A = string;"#,
1591          },
1592        ),
1593        (
1594          "file:///a/other.ts",
1595          Source::Module {
1596            specifier: "file:///a/other.ts",
1597            maybe_headers: None,
1598            content: r#"export type B = string | undefined;"#,
1599          },
1600        ),
1601      ],
1602      vec![],
1603    );
1604    let root = ModuleSpecifier::parse("file:///a/test.js").unwrap();
1605    let mut graph = ModuleGraph::new(GraphKind::All);
1606    graph
1607      .build(
1608        vec![root.clone()],
1609        Default::default(),
1610        &loader,
1611        Default::default(),
1612      )
1613      .await;
1614    assert_eq!(
1615      json!(graph),
1616      json!({
1617        "roots": [
1618          "file:///a/test.js"
1619        ],
1620        "modules": [
1621          {
1622            "kind": "esm",
1623            "mediaType": "TypeScript",
1624            "size": 35,
1625            "specifier": "file:///a/other.ts"
1626          },
1627          {
1628            "dependencies": [
1629              {
1630                "specifier": "./types.d.ts",
1631                "type": {
1632                  "specifier": "file:///a/types.d.ts",
1633                  "span": {
1634                    "start": {
1635                      "line": 4,
1636                      "character": 18
1637                    },
1638                    "end": {
1639                      "line": 4,
1640                      "character": 32
1641                    }
1642                  }
1643                }
1644              },
1645              {
1646                "specifier": "./other.ts",
1647                "type": {
1648                  "specifier": "file:///a/other.ts",
1649                  "span": {
1650                    "start": {
1651                      "line": 5,
1652                      "character": 19
1653                    },
1654                    "end": {
1655                      "line": 5,
1656                      "character": 31
1657                    }
1658                  }
1659                }
1660              },
1661            ],
1662            "kind": "esm",
1663            "mediaType": "JavaScript",
1664            "size": 137,
1665            "specifier": "file:///a/test.js"
1666          },
1667          {
1668            "kind": "esm",
1669            "mediaType": "Dts",
1670            "size": 23,
1671            "specifier": "file:///a/types.d.ts"
1672          }
1673        ],
1674        "redirects": {}
1675      })
1676    );
1677  }
1678
1679  #[tokio::test]
1680  async fn test_build_graph_with_redirects() {
1681    let loader = setup(
1682      vec![
1683        (
1684          "https://example.com/a",
1685          Source::Module {
1686            specifier: "https://example.com/a.ts",
1687            maybe_headers: Some(vec![(
1688              "content-type",
1689              "application/typescript",
1690            )]),
1691            content: r#"import * as b from "./b";"#,
1692          },
1693        ),
1694        (
1695          "https://example.com/b",
1696          Source::Module {
1697            specifier: "https://example.com/b.ts",
1698            maybe_headers: Some(vec![(
1699              "content-type",
1700              "application/typescript",
1701            )]),
1702            content: r#"export const b = "b";"#,
1703          },
1704        ),
1705      ],
1706      vec![],
1707    );
1708    let root_specifier =
1709      ModuleSpecifier::parse("https://example.com/a").expect("bad url");
1710    let mut graph = ModuleGraph::new(GraphKind::All);
1711    graph
1712      .build(
1713        vec![root_specifier.clone()],
1714        Default::default(),
1715        &loader,
1716        Default::default(),
1717      )
1718      .await;
1719    assert_eq!(
1720      graph.roots,
1721      IndexSet::from(
1722        [ModuleSpecifier::parse("https://example.com/a").unwrap()]
1723      ),
1724    );
1725    assert_eq!(graph.module_slots.len(), 2);
1726    assert!(
1727      graph.contains(&ModuleSpecifier::parse("https://example.com/a").unwrap())
1728    );
1729    assert!(graph
1730      .contains(&ModuleSpecifier::parse("https://example.com/a.ts").unwrap()));
1731    assert!(
1732      graph.contains(&ModuleSpecifier::parse("https://example.com/b").unwrap())
1733    );
1734    assert!(graph
1735      .contains(&ModuleSpecifier::parse("https://example.com/b.ts").unwrap()));
1736    assert_eq!(
1737      json!(graph.redirects),
1738      json!({
1739        "https://example.com/a": "https://example.com/a.ts",
1740        "https://example.com/b": "https://example.com/b.ts",
1741      })
1742    );
1743    let graph_json = json!(graph).to_string();
1744    assert!(graph_json.contains("https://example.com/a.ts"));
1745    assert!(graph_json.contains("https://example.com/b.ts"));
1746  }
1747
1748  #[tokio::test]
1749  async fn test_build_graph_with_circular_redirects() {
1750    let loader = setup(
1751      vec![
1752        (
1753          "https://example.com/a",
1754          Source::Module {
1755            specifier: "https://example.com/a.ts",
1756            maybe_headers: Some(vec![(
1757              "content-type",
1758              "application/typescript",
1759            )]),
1760            content: r#"import * as b from "./b";"#,
1761          },
1762        ),
1763        (
1764          "https://example.com/b",
1765          Source::Module {
1766            specifier: "https://example.com/b.ts",
1767            maybe_headers: Some(vec![(
1768              "content-type",
1769              "application/typescript",
1770            )]),
1771            content: r#"import * as a from "./a";
1772            export const b = "b";"#,
1773          },
1774        ),
1775      ],
1776      vec![],
1777    );
1778    let root_specifier =
1779      ModuleSpecifier::parse("https://example.com/a").expect("bad url");
1780    let mut graph = ModuleGraph::new(GraphKind::All);
1781    graph
1782      .build(
1783        vec![root_specifier.clone()],
1784        Default::default(),
1785        &loader,
1786        Default::default(),
1787      )
1788      .await;
1789    assert_eq!(
1790      graph.roots,
1791      IndexSet::from(
1792        [ModuleSpecifier::parse("https://example.com/a").unwrap()]
1793      ),
1794    );
1795    assert_eq!(graph.module_slots.len(), 2);
1796    assert!(
1797      graph.contains(&ModuleSpecifier::parse("https://example.com/a").unwrap())
1798    );
1799    assert!(graph
1800      .contains(&ModuleSpecifier::parse("https://example.com/a.ts").unwrap()));
1801    assert!(
1802      graph.contains(&ModuleSpecifier::parse("https://example.com/b").unwrap())
1803    );
1804    assert!(graph
1805      .contains(&ModuleSpecifier::parse("https://example.com/b.ts").unwrap()));
1806    assert_eq!(
1807      json!(graph.redirects),
1808      json!({
1809        "https://example.com/a": "https://example.com/a.ts",
1810        "https://example.com/b": "https://example.com/b.ts",
1811      })
1812    );
1813    let graph_json = json!(graph).to_string();
1814    assert!(graph_json.contains("https://example.com/a.ts"));
1815    assert!(graph_json.contains("https://example.com/b.ts"));
1816  }
1817
1818  #[tokio::test]
1819  async fn test_build_graph_with_data_url() {
1820    let loader = setup(
1821      vec![
1822        (
1823          "file:///a/test01.ts",
1824          Source::Module {
1825            specifier: "file:///a/test01.ts",
1826            maybe_headers: None,
1827            content: r#"import * as b from "data:application/typescript,export%20*%20from%20%22https://example.com/c.ts%22;";"#,
1828          },
1829        ),
1830        (
1831          "https://example.com/c.ts",
1832          Source::Module {
1833            specifier: "https://example.com/c.ts",
1834            maybe_headers: None,
1835            content: r#"export const c = """#,
1836          },
1837        ),
1838      ],
1839      vec![],
1840    );
1841    let root_specifier =
1842      ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url");
1843    let mut graph = ModuleGraph::new(GraphKind::All);
1844    graph
1845      .build(
1846        vec![root_specifier.clone()],
1847        Default::default(),
1848        &loader,
1849        Default::default(),
1850      )
1851      .await;
1852    assert_eq!(graph.module_slots.len(), 3);
1853    let data_specifier = ModuleSpecifier::parse("data:application/typescript,export%20*%20from%20%22https://example.com/c.ts%22;").unwrap();
1854    let module = graph.get(&data_specifier).unwrap().js().unwrap();
1855    assert_eq!(
1856      module.source.as_ref(),
1857      r#"export * from "https://example.com/c.ts";"#,
1858    );
1859  }
1860
1861  #[tokio::test]
1862  async fn test_build_graph_with_reference_types_in_js() {
1863    let loader = setup(
1864      vec![
1865        (
1866          "file:///test01.js",
1867          Source::Module {
1868            specifier: "file:///test01.js",
1869            maybe_headers: None,
1870            content: r#"/// <reference types="./test01.d.ts" />
1871export const foo = 'bar';"#,
1872          },
1873        ),
1874        (
1875          "file:///test01.d.ts",
1876          Source::Module {
1877            specifier: "file:///test01.d.ts",
1878            maybe_headers: None,
1879            content: r#"export declare const foo: string;"#,
1880          },
1881        ),
1882      ],
1883      vec![],
1884    );
1885    let root_specifier =
1886      ModuleSpecifier::parse("file:///test01.js").expect("bad url");
1887    let mut graph = ModuleGraph::new(GraphKind::All);
1888    graph
1889      .build(
1890        vec![root_specifier.clone()],
1891        Default::default(),
1892        &loader,
1893        Default::default(),
1894      )
1895      .await;
1896    assert_eq!(graph.module_slots.len(), 2);
1897    let module = graph.get(&root_specifier).unwrap().js().unwrap();
1898    assert_eq!(module.dependencies.len(), 0);
1899    let types_dep = module.maybe_types_dependency.as_ref().unwrap();
1900    assert_eq!(types_dep.specifier, "./test01.d.ts");
1901    assert_eq!(
1902      *types_dep.dependency.ok().unwrap(),
1903      ResolutionResolved {
1904        specifier: ModuleSpecifier::parse("file:///test01.d.ts").unwrap(),
1905        range: Range {
1906          specifier: ModuleSpecifier::parse("file:///test01.js").unwrap(),
1907          range: PositionRange {
1908            start: Position {
1909              line: 0,
1910              character: 21
1911            },
1912            end: Position {
1913              line: 0,
1914              character: 36
1915            },
1916          },
1917          resolution_mode: None,
1918        }
1919      }
1920    );
1921  }
1922
1923  #[tokio::test]
1924  async fn test_build_graph_with_self_types_in_js() {
1925    let loader = setup(
1926      vec![
1927        (
1928          "file:///test01.js",
1929          Source::Module {
1930            specifier: "file:///test01.js",
1931            maybe_headers: None,
1932            content: r#"// @ts-self-types="./test01.d.ts"
1933export const foo = 'bar';"#,
1934          },
1935        ),
1936        (
1937          "file:///test01.d.ts",
1938          Source::Module {
1939            specifier: "file:///test01.d.ts",
1940            maybe_headers: None,
1941            content: r#"export declare const foo: string;"#,
1942          },
1943        ),
1944      ],
1945      vec![],
1946    );
1947    let root_specifier =
1948      ModuleSpecifier::parse("file:///test01.js").expect("bad url");
1949    let mut graph = ModuleGraph::new(GraphKind::All);
1950    graph
1951      .build(
1952        vec![root_specifier.clone()],
1953        Default::default(),
1954        &loader,
1955        Default::default(),
1956      )
1957      .await;
1958    assert_eq!(graph.module_slots.len(), 2);
1959    let module = graph.get(&root_specifier).unwrap().js().unwrap();
1960    assert_eq!(module.dependencies.len(), 0);
1961    let types_dep = module.maybe_types_dependency.as_ref().unwrap();
1962    assert_eq!(types_dep.specifier, "./test01.d.ts");
1963    assert_eq!(
1964      *types_dep.dependency.ok().unwrap(),
1965      ResolutionResolved {
1966        specifier: ModuleSpecifier::parse("file:///test01.d.ts").unwrap(),
1967        range: Range {
1968          specifier: ModuleSpecifier::parse("file:///test01.js").unwrap(),
1969          range: PositionRange {
1970            start: Position {
1971              line: 0,
1972              character: 18
1973            },
1974            end: Position {
1975              line: 0,
1976              character: 33
1977            },
1978          },
1979          resolution_mode: None,
1980        }
1981      }
1982    );
1983  }
1984
1985  #[tokio::test]
1986  async fn test_build_graph_with_self_types_in_ts() {
1987    let loader = setup(
1988      vec![
1989        (
1990          "file:///test01.ts",
1991          Source::Module {
1992            specifier: "file:///test01.ts",
1993            maybe_headers: None,
1994            content: r#"// @ts-self-types="./test01.d.ts"
1995export const foo = 'bar';"#,
1996          },
1997        ),
1998        (
1999          "file:///test01.d.ts",
2000          Source::Module {
2001            specifier: "file:///test01.d.ts",
2002            maybe_headers: None,
2003            content: r#"export declare const foo: string;"#,
2004          },
2005        ),
2006      ],
2007      vec![],
2008    );
2009    let root_specifier =
2010      ModuleSpecifier::parse("file:///test01.ts").expect("bad url");
2011    let mut graph = ModuleGraph::new(GraphKind::All);
2012    graph
2013      .build(
2014        vec![root_specifier.clone()],
2015        Default::default(),
2016        &loader,
2017        Default::default(),
2018      )
2019      .await;
2020    assert_eq!(graph.module_slots.len(), 1);
2021    let module = graph.get(&root_specifier).unwrap().js().unwrap();
2022    assert_eq!(module.dependencies.len(), 0);
2023    assert!(module.maybe_types_dependency.is_none());
2024  }
2025
2026  #[tokio::test]
2027  async fn test_build_graph_with_resolver() {
2028    let loader = setup(
2029      vec![
2030        (
2031          "file:///a/test01.ts",
2032          Source::Module {
2033            specifier: "file:///a/test01.ts",
2034            maybe_headers: None,
2035            content: r#"import * as b from "b";"#,
2036          },
2037        ),
2038        (
2039          "file:///a/test02.ts",
2040          Source::Module {
2041            specifier: "file:///a/test02.ts",
2042            maybe_headers: None,
2043            content: r#"export const b = "b";"#,
2044          },
2045        ),
2046      ],
2047      vec![],
2048    );
2049    let resolver = MockResolver::new(
2050      vec![("file:///a/test01.ts", vec![("b", "file:///a/test02.ts")])],
2051      vec![],
2052    );
2053    let maybe_resolver: Option<&dyn Resolver> = Some(&resolver);
2054    let root_specifier = ModuleSpecifier::parse("file:///a/test01.ts").unwrap();
2055    let mut graph = ModuleGraph::new(GraphKind::All);
2056    graph
2057      .build(
2058        vec![root_specifier.clone()],
2059        Default::default(),
2060        &loader,
2061        BuildOptions {
2062          resolver: maybe_resolver,
2063          ..Default::default()
2064        },
2065      )
2066      .await;
2067    let module = graph.get(&root_specifier).unwrap().js().unwrap();
2068    let maybe_dep = module.dependencies.get("b");
2069    assert!(maybe_dep.is_some());
2070    let dep = maybe_dep.unwrap();
2071    assert_eq!(
2072      dep.maybe_code.maybe_specifier().unwrap(),
2073      &ModuleSpecifier::parse("file:///a/test02.ts").unwrap()
2074    );
2075  }
2076
2077  #[tokio::test]
2078  async fn test_build_graph_with_resolve_types() {
2079    let loader = setup(
2080      vec![
2081        (
2082          "file:///a.js",
2083          Source::Module {
2084            specifier: "file:///a.js",
2085            maybe_headers: None,
2086            content: r#"export const a = "a";"#,
2087          },
2088        ),
2089        (
2090          "file:///a.d.ts",
2091          Source::Module {
2092            specifier: "file:///a.d.ts",
2093            maybe_headers: None,
2094            content: r#"export const a: "a";"#,
2095          },
2096        ),
2097      ],
2098      vec![],
2099    );
2100    let resolver = MockResolver::new(
2101      vec![],
2102      vec![(
2103        "file:///a.js",
2104        (
2105          "file:///a.d.ts",
2106          Some(Range {
2107            specifier: ModuleSpecifier::parse("file:///package.json").unwrap(),
2108            range: PositionRange::zeroed(),
2109            resolution_mode: None,
2110          }),
2111        ),
2112      )],
2113    );
2114    let maybe_resolver: Option<&dyn Resolver> = Some(&resolver);
2115    let root_specifier = ModuleSpecifier::parse("file:///a.js").unwrap();
2116    let mut graph = ModuleGraph::new(GraphKind::All);
2117    graph
2118      .build(
2119        vec![root_specifier.clone()],
2120        Default::default(),
2121        &loader,
2122        BuildOptions {
2123          resolver: maybe_resolver,
2124          ..Default::default()
2125        },
2126      )
2127      .await;
2128    let module = graph.get(&root_specifier).unwrap().js().unwrap();
2129    let types_dep = module.maybe_types_dependency.as_ref().unwrap();
2130    assert_eq!(types_dep.specifier, "file:///a.js");
2131    assert_eq!(
2132      *types_dep.dependency.ok().unwrap(),
2133      ResolutionResolved {
2134        specifier: ModuleSpecifier::parse("file:///a.d.ts").unwrap(),
2135        range: Range {
2136          specifier: ModuleSpecifier::parse("file:///package.json").unwrap(),
2137          range: PositionRange::zeroed(),
2138          resolution_mode: None,
2139        }
2140      }
2141    );
2142  }
2143
2144  #[tokio::test]
2145  async fn test_build_graph_import_attributes() {
2146    let loader = setup(
2147      vec![
2148        (
2149          "file:///a/test01.ts",
2150          Source::Module {
2151            specifier: "file:///a/test01.ts",
2152            maybe_headers: None,
2153            content: r#"
2154            import a from "./a.json" with { type: "json" };
2155            const b = await import("./b.json", { with: { type: "json" } });
2156            export * as c from "./c.json" with { type: "json" };
2157            const json = "json";
2158            const d = await import("./d.json", { with: { type: json } });
2159            "#,
2160          },
2161        ),
2162        (
2163          "file:///a/a.json",
2164          Source::Module {
2165            specifier: "file:///a/a.json",
2166            maybe_headers: None,
2167            content: r#"{"a":"b"}"#,
2168          },
2169        ),
2170        (
2171          "file:///a/b.json",
2172          Source::Module {
2173            specifier: "file:///a/b.json",
2174            maybe_headers: None,
2175            content: r#"{"b":1}"#,
2176          },
2177        ),
2178        (
2179          "file:///a/c.json",
2180          Source::Module {
2181            specifier: "file:///a/c.json",
2182            maybe_headers: None,
2183            content: r#"{"c":"d"}"#,
2184          },
2185        ),
2186        (
2187          "file:///a/d.json",
2188          Source::Module {
2189            specifier: "file:///a/d.json",
2190            maybe_headers: None,
2191            content: r#"{"d":4}"#,
2192          },
2193        ),
2194      ],
2195      vec![],
2196    );
2197    let root_specifier =
2198      ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url");
2199    let mut graph = ModuleGraph::new(GraphKind::All);
2200    graph
2201      .build(
2202        vec![root_specifier.clone()],
2203        Default::default(),
2204        &loader,
2205        Default::default(),
2206      )
2207      .await;
2208    assert_eq!(
2209      json!(graph),
2210      json!({
2211        "roots": [
2212          "file:///a/test01.ts"
2213        ],
2214        "modules": [
2215          {
2216            "kind": "asserted",
2217            "size": 9,
2218            "mediaType": "Json",
2219            "specifier": "file:///a/a.json"
2220          },
2221          {
2222            "kind": "asserted",
2223            "size": 7,
2224            "mediaType": "Json",
2225            "specifier": "file:///a/b.json"
2226          },
2227          {
2228            "kind": "asserted",
2229            "size": 9,
2230            "mediaType": "Json",
2231            "specifier": "file:///a/c.json"
2232          },
2233          {
2234            "kind": "asserted",
2235            "size": 7,
2236            "mediaType": "Json",
2237            "specifier": "file:///a/d.json"
2238          },
2239          {
2240            "dependencies": [
2241              {
2242                "specifier": "./a.json",
2243                "code": {
2244                  "specifier": "file:///a/a.json",
2245                  "resolutionMode": "import",
2246                  "span": {
2247                    "start": {
2248                      "line": 1,
2249                      "character": 26
2250                    },
2251                    "end": {
2252                      "line": 1,
2253                      "character": 36
2254                    }
2255                  }
2256                },
2257                "assertionType": "json"
2258              },
2259              {
2260                "specifier": "./b.json",
2261                "code": {
2262                  "specifier": "file:///a/b.json",
2263                  "resolutionMode": "import",
2264                  "span": {
2265                    "start": {
2266                      "line": 2,
2267                      "character": 35
2268                    },
2269                    "end": {
2270                      "line": 2,
2271                      "character": 45
2272                    }
2273                  }
2274                },
2275                "isDynamic": true,
2276                "assertionType": "json"
2277              },
2278              {
2279                "specifier": "./c.json",
2280                "code": {
2281                  "specifier": "file:///a/c.json",
2282                  "resolutionMode": "import",
2283                  "span": {
2284                    "start": {
2285                      "line": 3,
2286                      "character": 31
2287                    },
2288                    "end": {
2289                      "line": 3,
2290                      "character": 41
2291                    }
2292                  }
2293                },
2294                "assertionType": "json"
2295              },
2296              {
2297                "specifier": "./d.json",
2298                "code": {
2299                  "specifier": "file:///a/d.json",
2300                  "resolutionMode": "import",
2301                  "span": {
2302                    "start": {
2303                      "line": 5,
2304                      "character": 35
2305                    },
2306                    "end": {
2307                      "line": 5,
2308                      "character": 45
2309                    }
2310                  }
2311                },
2312                "isDynamic": true
2313              }
2314            ],
2315            "kind": "esm",
2316            "mediaType": "TypeScript",
2317            "size": 321,
2318            "specifier": "file:///a/test01.ts"
2319          }
2320        ],
2321        "redirects": {}
2322      })
2323    );
2324  }
2325
2326  #[tokio::test]
2327  async fn test_build_graph_mixed_assertions() {
2328    let loader = setup(
2329      vec![
2330        (
2331          "file:///a/test01.ts",
2332          Source::Module {
2333            specifier: "file:///a/test01.ts",
2334            maybe_headers: None,
2335            content: r#"
2336            import a from "./a.json";
2337            import b from "./a.json" assert { type: "json" };
2338            "#,
2339          },
2340        ),
2341        (
2342          "file:///a/a.json",
2343          Source::Module {
2344            specifier: "file:///a/a.json",
2345            maybe_headers: None,
2346            content: r#"{"a":"b"}"#,
2347          },
2348        ),
2349      ],
2350      vec![],
2351    );
2352    let root_specifier =
2353      ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url");
2354    let mut graph = ModuleGraph::new(GraphKind::All);
2355    graph
2356      .build(
2357        vec![root_specifier.clone()],
2358        Default::default(),
2359        &loader,
2360        Default::default(),
2361      )
2362      .await;
2363    assert_eq!(
2364      json!(graph),
2365      json!({
2366        "roots": [
2367          "file:///a/test01.ts"
2368        ],
2369        "modules": [
2370          {
2371            "kind": "asserted",
2372            "size": 9,
2373            "mediaType": "Json",
2374            "specifier": "file:///a/a.json"
2375          },
2376          {
2377            "dependencies": [
2378              {
2379                "specifier": "./a.json",
2380                "code": {
2381                  "specifier": "file:///a/a.json",
2382                  "resolutionMode": "import",
2383                  "span": {
2384                    "start": {
2385                      "line": 1,
2386                      "character": 26
2387                    },
2388                    "end": {
2389                      "line": 1,
2390                      "character": 36
2391                    }
2392                  }
2393                },
2394                "assertionType": "json"
2395              }
2396            ],
2397            "kind": "esm",
2398            "size": 113,
2399            "mediaType": "TypeScript",
2400            "specifier": "file:///a/test01.ts"
2401          }
2402        ],
2403        "redirects": {}
2404      })
2405    );
2406  }
2407
2408  #[tokio::test]
2409  async fn test_build_graph_import_assertion_errors() {
2410    let loader = setup(
2411      vec![
2412        (
2413          "file:///a/test01.ts",
2414          Source::Module {
2415            specifier: "file:///a/test01.ts",
2416            maybe_headers: None,
2417            content: r#"
2418            import a from "./a.json";
2419            import b from "./b.json" assert { type: "json" };
2420            import c from "./c.js" assert { type: "json" };
2421            import d from "./d.json" assert { type: "css" };
2422            import e from "./e.wasm";
2423            "#,
2424          },
2425        ),
2426        (
2427          "file:///a/a.json",
2428          Source::Module {
2429            specifier: "file:///a/a.json",
2430            maybe_headers: None,
2431            content: r#"{"a":"b"}"#,
2432          },
2433        ),
2434        (
2435          "file:///a/b.json",
2436          Source::Module {
2437            specifier: "file:///a/b.json",
2438            maybe_headers: None,
2439            content: r#"{"a":"b"}"#,
2440          },
2441        ),
2442        (
2443          "file:///a/c.js",
2444          Source::Module {
2445            specifier: "file:///a/c.js",
2446            maybe_headers: None,
2447            content: r#"export const c = "c";"#,
2448          },
2449        ),
2450        (
2451          "file:///a/d.json",
2452          Source::Module {
2453            specifier: "file:///a/d.json",
2454            maybe_headers: None,
2455            content: r#"{"a":"b"}"#,
2456          },
2457        ),
2458        (
2459          "file:///a/e.wasm",
2460          Source::Module {
2461            specifier: "file:///a/e.wasm",
2462            maybe_headers: None,
2463            content: "",
2464          },
2465        ),
2466      ],
2467      vec![],
2468    );
2469    let root_specifier =
2470      ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url");
2471    let mut graph = ModuleGraph::new(GraphKind::All);
2472    graph
2473      .build(
2474        vec![root_specifier.clone()],
2475        Default::default(),
2476        &loader,
2477        Default::default(),
2478      )
2479      .await;
2480    assert_eq!(
2481      json!(graph),
2482      json!({
2483        "roots": [
2484          "file:///a/test01.ts"
2485        ],
2486        "modules": [
2487          {
2488            "specifier": "file:///a/a.json",
2489            "error": "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: file:///a/a.json"
2490          },
2491          {
2492            "kind": "asserted",
2493            "size": 9,
2494            "mediaType": "Json",
2495            "specifier": "file:///a/b.json"
2496          },
2497          {
2498            "specifier": "file:///a/c.js",
2499            "error": "Expected a Json module, but identified a JavaScript module.\n  Specifier: file:///a/c.js"
2500          },
2501          {
2502            "specifier": "file:///a/d.json",
2503            "error": "The import attribute type of \"css\" is unsupported.\n  Specifier: file:///a/d.json"
2504          },
2505          {
2506            "specifier": "file:///a/e.wasm",
2507            "error": "The Wasm module could not be parsed: not a Wasm module\n  Specifier: file:///a/e.wasm"
2508          },
2509          {
2510            "dependencies": [
2511              {
2512                "specifier": "./a.json",
2513                "code": {
2514                  "specifier": "file:///a/a.json",
2515                  "resolutionMode": "import",
2516                  "span": {
2517                    "start": {
2518                      "line": 1,
2519                      "character": 26
2520                    },
2521                    "end": {
2522                      "line": 1,
2523                      "character": 36
2524                    }
2525                  }
2526                }
2527              },
2528              {
2529                "specifier": "./b.json",
2530                "code": {
2531                  "specifier": "file:///a/b.json",
2532                  "resolutionMode": "import",
2533                  "span": {
2534                    "start": {
2535                      "line": 2,
2536                      "character": 26
2537                    },
2538                    "end": {
2539                      "line": 2,
2540                      "character": 36
2541                    }
2542                  }
2543                },
2544                "assertionType": "json"
2545              },
2546              {
2547                "specifier": "./c.js",
2548                "code": {
2549                  "specifier": "file:///a/c.js",
2550                  "resolutionMode": "import",
2551                  "span": {
2552                    "start": {
2553                      "line": 3,
2554                      "character": 26
2555                    },
2556                    "end": {
2557                      "line": 3,
2558                      "character": 34
2559                    }
2560                  }
2561                },
2562                "assertionType": "json"
2563              },
2564              {
2565                "specifier": "./d.json",
2566                "code": {
2567                  "specifier": "file:///a/d.json",
2568                  "resolutionMode": "import",
2569                  "span": {
2570                    "start": {
2571                      "line": 4,
2572                      "character": 26
2573                    },
2574                    "end": {
2575                      "line": 4,
2576                      "character": 36
2577                    }
2578                  }
2579                },
2580                "assertionType": "css"
2581              },
2582              {
2583                "specifier": "./e.wasm",
2584                "code": {
2585                  "specifier": "file:///a/e.wasm",
2586                  "resolutionMode": "import",
2587                  "span": {
2588                    "start": {
2589                      "line": 5,
2590                      "character": 26
2591                    },
2592                    "end": {
2593                      "line": 5,
2594                      "character": 36
2595                    }
2596                  }
2597                }
2598              }
2599            ],
2600            "mediaType": "TypeScript",
2601            "kind": "esm",
2602            "size": 272,
2603            "specifier": "file:///a/test01.ts"
2604          }
2605        ],
2606        "redirects": {}
2607      })
2608    );
2609  }
2610
2611  #[derive(Debug)]
2612  struct CollectingReporter {
2613    on_loads: RefCell<Vec<(ModuleSpecifier, usize, usize)>>,
2614  }
2615
2616  impl source::Reporter for CollectingReporter {
2617    fn on_load(
2618      &self,
2619      specifier: &ModuleSpecifier,
2620      modules_done: usize,
2621      modules_total: usize,
2622    ) {
2623      self.on_loads.borrow_mut().push((
2624        specifier.clone(),
2625        modules_done,
2626        modules_total,
2627      ));
2628    }
2629  }
2630
2631  #[tokio::test]
2632  async fn test_build_graph_with_reporter() {
2633    let loader = setup(
2634      vec![
2635        (
2636          "file:///a/test01.ts",
2637          Source::Module {
2638            specifier: "file:///a/test01.ts",
2639            maybe_headers: None,
2640            content: r#"
2641            import "./a.js";
2642            "#,
2643          },
2644        ),
2645        (
2646          "file:///a/a.js",
2647          Source::Module {
2648            specifier: "file:///a/a.js",
2649            maybe_headers: None,
2650            content: r#"import "./b.js";"#,
2651          },
2652        ),
2653        (
2654          "file:///a/b.js",
2655          Source::Module {
2656            specifier: "file:///a/b.js",
2657            maybe_headers: None,
2658            content: r#"import "./c.js"; import "./d.js""#,
2659          },
2660        ),
2661        (
2662          "file:///a/c.js",
2663          Source::Module {
2664            specifier: "file:///a/c.js",
2665            maybe_headers: None,
2666            content: r#""#,
2667          },
2668        ),
2669        (
2670          "file:///a/d.js",
2671          Source::Module {
2672            specifier: "file:///a/d.js",
2673            maybe_headers: None,
2674            content: r#""#,
2675          },
2676        ),
2677      ],
2678      vec![],
2679    );
2680    let root_specifier =
2681      ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url");
2682    let reporter = CollectingReporter {
2683      on_loads: RefCell::new(vec![]),
2684    };
2685    let mut graph = ModuleGraph::new(GraphKind::All);
2686    graph
2687      .build(
2688        vec![root_specifier.clone()],
2689        Default::default(),
2690        &loader,
2691        BuildOptions {
2692          reporter: Some(&reporter),
2693          ..Default::default()
2694        },
2695      )
2696      .await;
2697    assert_eq!(graph.modules().count(), 5);
2698
2699    let on_loads = reporter.on_loads.into_inner();
2700    assert_eq!(on_loads.len(), 5);
2701
2702    assert_eq!(
2703      on_loads[0],
2704      (ModuleSpecifier::parse("file:///a/test01.ts").unwrap(), 1, 2)
2705    );
2706    assert_eq!(
2707      on_loads[1],
2708      (ModuleSpecifier::parse("file:///a/a.js").unwrap(), 2, 3)
2709    );
2710    assert_eq!(
2711      on_loads[2],
2712      (ModuleSpecifier::parse("file:///a/b.js").unwrap(), 3, 5)
2713    );
2714    let c = ModuleSpecifier::parse("file:///a/c.js").unwrap();
2715    if on_loads[3].0 == c {
2716      assert_eq!(
2717        on_loads[3],
2718        (ModuleSpecifier::parse("file:///a/c.js").unwrap(), 4, 5)
2719      );
2720      assert_eq!(
2721        on_loads[4],
2722        (ModuleSpecifier::parse("file:///a/d.js").unwrap(), 5, 5)
2723      );
2724    } else {
2725      assert_eq!(
2726        on_loads[3],
2727        (ModuleSpecifier::parse("file:///a/d.js").unwrap(), 4, 5)
2728      );
2729      assert_eq!(
2730        on_loads[4],
2731        (ModuleSpecifier::parse("file:///a/c.js").unwrap(), 5, 5)
2732      );
2733    }
2734  }
2735
2736  #[tokio::test]
2737  async fn test_build_graph_types_only() {
2738    let loader = setup(
2739      vec![
2740        (
2741          "file:///a/test01.ts",
2742          Source::Module {
2743            specifier: "file:///a/test01.ts",
2744            maybe_headers: None,
2745            content: r#"
2746            // @deno-types="./a.d.ts"
2747            import * as a from "./a.js";
2748            import type { B } from "./b.d.ts";
2749            import * as c from "https://example.com/c";
2750            import * as d from "./d.js";
2751            "#,
2752          },
2753        ),
2754        (
2755          "file:///a/a.js",
2756          Source::Module {
2757            specifier: "file:///a/a.js",
2758            maybe_headers: None,
2759            content: r#"export const a = "a""#,
2760          },
2761        ),
2762        (
2763          "file:///a/a.d.ts",
2764          Source::Module {
2765            specifier: "file:///a/a.d.ts",
2766            maybe_headers: None,
2767            content: r#"export const a: "a";"#,
2768          },
2769        ),
2770        (
2771          "file:///a/b.d.ts",
2772          Source::Module {
2773            specifier: "file:///a/b.d.ts",
2774            maybe_headers: None,
2775            content: r#"export interface B {}"#,
2776          },
2777        ),
2778        (
2779          "https://example.com/c",
2780          Source::Module {
2781            specifier: "https://example.com/c",
2782            maybe_headers: Some(vec![
2783              ("x-typescript-types", "./c.d.ts"),
2784              ("content-type", "application/javascript"),
2785            ]),
2786            content: r#"export { c } from "./c.js";"#,
2787          },
2788        ),
2789        (
2790          "https://example.com/c.d.ts",
2791          Source::Module {
2792            specifier: "https://example.com/c.d.ts",
2793            maybe_headers: Some(vec![(
2794              "content-type",
2795              "application/typescript",
2796            )]),
2797            content: r#"export const c: "c";"#,
2798          },
2799        ),
2800        (
2801          "https://example.com/c.js",
2802          Source::Module {
2803            specifier: "https://example.com/c.js",
2804            maybe_headers: Some(vec![(
2805              "content-type",
2806              "application/javascript",
2807            )]),
2808            content: r#"export const c = "c";"#,
2809          },
2810        ),
2811        (
2812          "file:///a/d.js",
2813          Source::Module {
2814            specifier: "file:///a/d.js",
2815            maybe_headers: None,
2816            content: r#"export const d = "d";"#,
2817          },
2818        ),
2819      ],
2820      vec![],
2821    );
2822    let root_specifier =
2823      ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url");
2824    let mut graph = ModuleGraph::new(GraphKind::TypesOnly);
2825    graph
2826      .build(
2827        vec![root_specifier.clone()],
2828        Default::default(),
2829        &loader,
2830        Default::default(),
2831      )
2832      .await;
2833    assert_eq!(
2834      json!(graph),
2835      json!({
2836        "roots": [
2837          "file:///a/test01.ts"
2838        ],
2839        "modules": [
2840          {
2841            "kind": "esm",
2842            "mediaType": "Dts",
2843            "size": 20,
2844            "specifier": "file:///a/a.d.ts"
2845          },
2846          {
2847            "kind": "esm",
2848            "mediaType": "Dts",
2849            "size": 21,
2850            "specifier": "file:///a/b.d.ts"
2851          },
2852          {
2853            "kind": "esm",
2854            "mediaType": "JavaScript",
2855            "size": 21,
2856            "specifier": "file:///a/d.js"
2857          },
2858          {
2859            "dependencies": [
2860              {
2861                "specifier": "./a.js",
2862                "type": {
2863                  "specifier": "file:///a/a.d.ts",
2864                  "resolutionMode": "import",
2865                  "span": {
2866                    "start": {
2867                      "line": 1,
2868                      "character": 27
2869                    },
2870                    "end": {
2871                      "line": 1,
2872                      "character": 37
2873                    }
2874                  }
2875                }
2876              },
2877              {
2878                "specifier": "./b.d.ts",
2879                "type": {
2880                  "specifier": "file:///a/b.d.ts",
2881                  "resolutionMode": "import",
2882                  "span": {
2883                    "start": {
2884                      "line": 3,
2885                      "character": 35
2886                    },
2887                    "end": {
2888                      "line": 3,
2889                      "character": 45
2890                    }
2891                  }
2892                }
2893              },
2894              {
2895                "specifier": "https://example.com/c",
2896                "code": {
2897                  "specifier": "https://example.com/c",
2898                  "resolutionMode": "import",
2899                  "span": {
2900                    "start": {
2901                      "line": 4,
2902                      "character": 31
2903                    },
2904                    "end": {
2905                      "line": 4,
2906                      "character": 54
2907                    }
2908                  }
2909                }
2910              },
2911              {
2912                "specifier": "./d.js",
2913                "code": {
2914                  "specifier": "file:///a/d.js",
2915                  "resolutionMode": "import",
2916                  "span": {
2917                    "start": {
2918                      "line": 5,
2919                      "character": 31
2920                    },
2921                    "end": {
2922                      "line": 5,
2923                      "character": 39
2924                    }
2925                  }
2926                }
2927              },
2928            ],
2929            "kind": "esm",
2930            "mediaType": "TypeScript",
2931            "size": 236,
2932            "specifier": "file:///a/test01.ts"
2933          },
2934          {
2935            "typesDependency": {
2936              "specifier": "./c.d.ts",
2937              "dependency": {
2938                "specifier": "https://example.com/c.d.ts",
2939                "span": {
2940                  "start": {
2941                    "line": 0,
2942                    "character": 0
2943                  },
2944                  "end": {
2945                    "line": 0,
2946                    "character": 0
2947                  }
2948                }
2949              }
2950            },
2951            "kind": "esm",
2952            "mediaType": "JavaScript",
2953            "size": 27,
2954            "specifier": "https://example.com/c"
2955          },
2956          {
2957            "kind": "esm",
2958            "mediaType": "Dts",
2959            "size": 20,
2960            "specifier": "https://example.com/c.d.ts"
2961          }
2962        ],
2963        "redirects": {}
2964      })
2965    );
2966  }
2967
2968  #[tokio::test]
2969  async fn test_build_graph_code_only() {
2970    let loader = setup(
2971      vec![
2972        (
2973          "file:///a/test01.ts",
2974          Source::Module {
2975            specifier: "file:///a/test01.ts",
2976            maybe_headers: None,
2977            content: r#"
2978            // @deno-types="./a.d.ts"
2979            import * as a from "./a.js";
2980            import type { B } from "./b.d.ts";
2981            import * as c from "https://example.com/c";
2982            import * as d from "./d.js";
2983            "#,
2984          },
2985        ),
2986        (
2987          "file:///a/a.js",
2988          Source::Module {
2989            specifier: "file:///a/a.js",
2990            maybe_headers: None,
2991            content: r#"export const a = "a""#,
2992          },
2993        ),
2994        (
2995          "file:///a/a.d.ts",
2996          Source::Module {
2997            specifier: "file:///a/a.d.ts",
2998            maybe_headers: None,
2999            content: r#"export const a: "a";"#,
3000          },
3001        ),
3002        (
3003          "file:///a/b.d.ts",
3004          Source::Module {
3005            specifier: "file:///a/b.d.ts",
3006            maybe_headers: None,
3007            content: r#"export interface B {}"#,
3008          },
3009        ),
3010        (
3011          "https://example.com/c",
3012          Source::Module {
3013            specifier: "https://example.com/c",
3014            maybe_headers: Some(vec![
3015              ("x-typescript-types", "./c.d.ts"),
3016              ("content-type", "application/javascript"),
3017            ]),
3018            content: r#"export { c } from "./c.js";"#,
3019          },
3020        ),
3021        (
3022          "https://example.com/c.d.ts",
3023          Source::Module {
3024            specifier: "https://example.com/c.d.ts",
3025            maybe_headers: Some(vec![(
3026              "content-type",
3027              "application/typescript",
3028            )]),
3029            content: r#"export const c: "c";"#,
3030          },
3031        ),
3032        (
3033          "https://example.com/c.js",
3034          Source::Module {
3035            specifier: "https://example.com/c.js",
3036            maybe_headers: Some(vec![(
3037              "content-type",
3038              "application/javascript",
3039            )]),
3040            content: r#"export const c = "c";"#,
3041          },
3042        ),
3043        (
3044          "file:///a/d.js",
3045          Source::Module {
3046            specifier: "file:///a/d.js",
3047            maybe_headers: None,
3048            content: r#"export const d = "d";"#,
3049          },
3050        ),
3051        (
3052          "file:///a/test04.js",
3053          Source::Module {
3054            specifier: "file:///a/test04.js",
3055            maybe_headers: None,
3056            content: r#"/// <reference types="./test04.d.ts" />\nexport const d = "d";"#,
3057          },
3058        ),
3059      ],
3060      vec![],
3061    );
3062    let root_specifier =
3063      ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url");
3064    let mut graph = ModuleGraph::new(GraphKind::CodeOnly);
3065    graph
3066      .build(
3067        vec![root_specifier.clone()],
3068        Default::default(),
3069        &loader,
3070        Default::default(),
3071      )
3072      .await;
3073    assert_eq!(
3074      json!(graph),
3075      json!({
3076        "roots": [
3077          "file:///a/test01.ts"
3078        ],
3079        "modules": [
3080          {
3081            "mediaType": "JavaScript",
3082            "kind": "esm",
3083            "size": 20,
3084            "specifier": "file:///a/a.js"
3085          },
3086          {
3087            "mediaType": "JavaScript",
3088            "kind": "esm",
3089            "size": 21,
3090            "specifier": "file:///a/d.js"
3091          },
3092          {
3093            "dependencies": [
3094              {
3095                "specifier": "./a.js",
3096                "code": {
3097                  "specifier": "file:///a/a.js",
3098                  "resolutionMode": "import",
3099                  "span": {
3100                    "start": {
3101                      "line": 2,
3102                      "character": 31
3103                    },
3104                    "end": {
3105                      "line": 2,
3106                      "character": 39
3107                    }
3108                  }
3109                }
3110              },
3111              {
3112                "specifier": "https://example.com/c",
3113                "code": {
3114                  "specifier": "https://example.com/c",
3115                  "resolutionMode": "import",
3116                  "span": {
3117                    "start": {
3118                      "line": 4,
3119                      "character": 31
3120                    },
3121                    "end": {
3122                      "line": 4,
3123                      "character": 54
3124                    }
3125                  }
3126                }
3127              },
3128              {
3129                "specifier": "./d.js",
3130                "code": {
3131                  "specifier": "file:///a/d.js",
3132                  "resolutionMode": "import",
3133                  "span": {
3134                    "start": {
3135                      "line": 5,
3136                      "character": 31
3137                    },
3138                    "end": {
3139                      "line": 5,
3140                      "character": 39
3141                    }
3142                  }
3143                }
3144              },
3145            ],
3146            "mediaType": "TypeScript",
3147            "kind": "esm",
3148            "size": 236,
3149            "specifier": "file:///a/test01.ts"
3150          },
3151          {
3152            "dependencies": [
3153              {
3154                "specifier": "./c.js",
3155                "code": {
3156                  "specifier": "https://example.com/c.js",
3157                  "resolutionMode": "import",
3158                  "span": {
3159                    "start": {
3160                      "line": 0,
3161                      "character": 18
3162                    },
3163                    "end": {
3164                      "line": 0,
3165                      "character": 26
3166                    }
3167                  }
3168                }
3169              }
3170            ],
3171            "mediaType": "JavaScript",
3172            "kind": "esm",
3173            "size": 27,
3174            "specifier": "https://example.com/c"
3175          },
3176          {
3177            "mediaType": "JavaScript",
3178            "kind": "esm",
3179            "size": 21,
3180            "specifier": "https://example.com/c.js"
3181          }
3182        ],
3183        "redirects": {}
3184      })
3185    );
3186  }
3187
3188  #[tokio::test]
3189  async fn test_build_graph_with_builtin_external() {
3190    let loader = setup(
3191      vec![
3192        (
3193          "file:///a/test01.ts",
3194          Source::Module {
3195            specifier: "file:///a/test01.ts",
3196            maybe_headers: None,
3197            content: r#"
3198            import * as fs from "builtin:fs";
3199            import * as bundle from "https://example.com/bundle";
3200            "#,
3201          },
3202        ),
3203        ("builtin:fs", Source::External("builtin:fs")),
3204        (
3205          "https://example.com/bundle",
3206          Source::External("https://example.com/bundle"),
3207        ),
3208      ],
3209      vec![],
3210    );
3211    let root_specifier =
3212      ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url");
3213    let mut graph = ModuleGraph::new(GraphKind::All);
3214    graph
3215      .build(
3216        vec![root_specifier.clone()],
3217        Default::default(),
3218        &loader,
3219        Default::default(),
3220      )
3221      .await;
3222    assert_eq!(
3223      json!(graph),
3224      json!({
3225        "roots": [
3226          "file:///a/test01.ts"
3227        ],
3228        "modules": [
3229          {
3230            "kind": "external",
3231            "specifier": "builtin:fs"
3232          },
3233          {
3234            "dependencies": [
3235              {
3236                "specifier": "builtin:fs",
3237                "code": {
3238                  "specifier": "builtin:fs",
3239                  "resolutionMode": "import",
3240                  "span": {
3241                    "start": {
3242                      "line": 1,
3243                      "character": 32
3244                    },
3245                    "end": {
3246                      "line": 1,
3247                      "character": 44
3248                    }
3249                  }
3250                }
3251              },
3252              {
3253                "specifier": "https://example.com/bundle",
3254                "code": {
3255                  "specifier": "https://example.com/bundle",
3256                  "resolutionMode": "import",
3257                  "span": {
3258                    "start": {
3259                      "line": 2,
3260                      "character": 36
3261                    },
3262                    "end": {
3263                      "line": 2,
3264                      "character": 64
3265                    }
3266                  }
3267                }
3268              }
3269            ],
3270            "kind": "esm",
3271            "size": 125,
3272            "mediaType": "TypeScript",
3273            "specifier": "file:///a/test01.ts"
3274          },
3275          {
3276            "kind": "external",
3277            "specifier": "https://example.com/bundle"
3278          }
3279        ],
3280        "redirects": {}
3281      })
3282    );
3283  }
3284
3285  #[tokio::test]
3286  async fn test_parse_module() {
3287    let specifier = ModuleSpecifier::parse("file:///a/test01.ts").unwrap();
3288    let code = br#"
3289    /// <reference types="./a.d.ts" />
3290    import { a } from "./a.ts";
3291    import * as b from "./b.ts";
3292    export { c } from "./c.ts";
3293    const d = await import("./d.ts");
3294    import type { e } from "./e.ts";
3295    export type { f } from "./f.ts";
3296    "#;
3297    let actual = parse_module(ParseModuleOptions {
3298      graph_kind: GraphKind::All,
3299      specifier: specifier.clone(),
3300      maybe_headers: None,
3301      mtime: None,
3302      content: code.to_vec().into(),
3303      file_system: &NullFileSystem,
3304      jsr_url_provider: Default::default(),
3305      maybe_resolver: None,
3306      module_analyzer: Default::default(),
3307    })
3308    .await
3309    .unwrap();
3310    let actual = actual.js().unwrap();
3311    assert_eq!(actual.dependencies.len(), 7);
3312    assert_eq!(actual.specifier, specifier);
3313    assert_eq!(actual.media_type, MediaType::TypeScript);
3314
3315    // now try code only
3316    let actual = parse_module(ParseModuleOptions {
3317      graph_kind: GraphKind::CodeOnly,
3318      specifier: specifier.clone(),
3319      maybe_headers: None,
3320      mtime: None,
3321      content: code.to_vec().into(),
3322      file_system: &NullFileSystem,
3323      jsr_url_provider: Default::default(),
3324      maybe_resolver: None,
3325      module_analyzer: Default::default(),
3326    })
3327    .await
3328    .unwrap();
3329    let actual = actual.js().unwrap();
3330    assert_eq!(actual.dependencies.len(), 4);
3331  }
3332
3333  #[tokio::test]
3334  async fn test_parse_module_deno_types() {
3335    let specifier = ModuleSpecifier::parse("file:///a/test01.ts").unwrap();
3336    let code = br#"
3337    // @deno-types="./a.d.ts"
3338    import { a } from "./a.js";
3339    "#;
3340    let actual = parse_module(ParseModuleOptions {
3341      graph_kind: GraphKind::All,
3342      specifier: specifier.clone(),
3343      maybe_headers: None,
3344      mtime: None,
3345      content: code.to_vec().into(),
3346      file_system: &NullFileSystem,
3347      jsr_url_provider: Default::default(),
3348      maybe_resolver: None,
3349      module_analyzer: Default::default(),
3350    })
3351    .await
3352    .unwrap();
3353    let actual = actual.js().unwrap();
3354    assert_eq!(
3355      &actual.dependencies,
3356      &[(
3357        "./a.js".to_string(),
3358        Dependency {
3359          maybe_code: Resolution::Ok(Box::new(ResolutionResolved {
3360            specifier: ModuleSpecifier::parse("file:///a/a.js").unwrap(),
3361            range: Range {
3362              specifier: specifier.clone(),
3363              range: PositionRange {
3364                start: Position::new(2, 22),
3365                end: Position::new(2, 30),
3366              },
3367              resolution_mode: Some(ResolutionMode::Import),
3368            },
3369          })),
3370          maybe_type: Resolution::Ok(Box::new(ResolutionResolved {
3371            specifier: ModuleSpecifier::parse("file:///a/a.d.ts").unwrap(),
3372            range: Range {
3373              specifier: specifier.clone(),
3374              range: PositionRange {
3375                start: Position::new(1, 19),
3376                end: Position::new(1, 29),
3377              },
3378              resolution_mode: Some(ResolutionMode::Import),
3379            },
3380          })),
3381          maybe_deno_types_specifier: Some("./a.d.ts".to_string()),
3382          imports: vec![Import {
3383            specifier: "./a.js".to_string(),
3384            kind: ImportKind::Es,
3385            specifier_range: Range {
3386              specifier: specifier.clone(),
3387              range: PositionRange {
3388                start: Position::new(2, 22),
3389                end: Position::new(2, 30),
3390              },
3391              resolution_mode: Some(ResolutionMode::Import),
3392            },
3393            is_dynamic: false,
3394            attributes: Default::default(),
3395          }],
3396          ..Default::default()
3397        },
3398      )]
3399      .into_iter()
3400      .collect::<IndexMap<_, _>>()
3401    )
3402  }
3403
3404  #[tokio::test]
3405  async fn test_parse_module_import_assertions() {
3406    let specifier = ModuleSpecifier::parse("file:///a/test01.ts").unwrap();
3407    let actual = parse_module(ParseModuleOptions {
3408      graph_kind: GraphKind::All,
3409      specifier,
3410      maybe_headers: None,
3411      mtime: None,
3412      content: br#"
3413    import a from "./a.json" assert { type: "json" };
3414    await import("./b.json", { assert: { type: "json" } });
3415    "#
3416      .to_vec()
3417      .into(),
3418      file_system: &NullFileSystem,
3419      jsr_url_provider: Default::default(),
3420      maybe_resolver: None,
3421      module_analyzer: Default::default(),
3422    })
3423    .await
3424    .unwrap();
3425    assert_eq!(
3426      json!(actual),
3427      json!({
3428        "kind": "esm",
3429        "dependencies": [
3430          {
3431            "specifier": "./a.json",
3432            "code": {
3433              "specifier": "file:///a/a.json",
3434              "resolutionMode": "import",
3435              "span": {
3436                "start": {
3437                  "line": 1,
3438                  "character": 18
3439                },
3440                "end": {
3441                  "line": 1,
3442                  "character": 28
3443                }
3444              }
3445            },
3446            "assertionType": "json"
3447          },
3448          {
3449            "specifier": "./b.json",
3450            "code": {
3451              "specifier": "file:///a/b.json",
3452              "resolutionMode": "import",
3453              "span": {
3454                "start": {
3455                  "line": 2,
3456                  "character": 17
3457                },
3458                "end": {
3459                  "line": 2,
3460                  "character": 27
3461                }
3462              }
3463            },
3464            "isDynamic": true,
3465            "assertionType": "json"
3466          }
3467        ],
3468        "mediaType": "TypeScript",
3469        "size": 119,
3470        "specifier": "file:///a/test01.ts"
3471      })
3472    );
3473  }
3474
3475  #[tokio::test]
3476  async fn test_parse_module_jsx_import_source() {
3477    let specifier = ModuleSpecifier::parse("file:///a/test01.tsx").unwrap();
3478    let actual = parse_module(ParseModuleOptions {
3479      graph_kind: GraphKind::All,
3480      specifier: specifier.clone(),
3481      maybe_headers: None,
3482      mtime: None,
3483      content: br#"
3484    /** @jsxImportSource https://example.com/preact */
3485
3486    export function A() {
3487      return <div>Hello Deno</div>;
3488    }
3489    "#
3490      .to_vec()
3491      .into(),
3492      file_system: &NullFileSystem,
3493      jsr_url_provider: Default::default(),
3494      maybe_resolver: None,
3495      module_analyzer: Default::default(),
3496    })
3497    .await
3498    .unwrap();
3499    let actual = actual.js().unwrap();
3500    assert_eq!(actual.dependencies.len(), 1);
3501    let dep = actual
3502      .dependencies
3503      .get("https://example.com/preact/jsx-runtime")
3504      .unwrap();
3505    assert_eq!(
3506      dep.maybe_code.ok().unwrap().specifier,
3507      ModuleSpecifier::parse("https://example.com/preact/jsx-runtime").unwrap()
3508    );
3509    assert!(dep.maybe_type.is_none());
3510    assert_eq!(actual.specifier, specifier);
3511    assert_eq!(actual.media_type, MediaType::Tsx);
3512  }
3513
3514  #[tokio::test]
3515  async fn test_parse_module_jsx_import_source_types() {
3516    let specifier = ModuleSpecifier::parse("file:///a/test01.tsx").unwrap();
3517    let actual = parse_module(ParseModuleOptions {
3518      graph_kind: GraphKind::All,
3519      specifier: specifier.clone(),
3520      maybe_headers: None,
3521      mtime: None,
3522      content: br#"
3523    /** @jsxImportSource https://example.com/preact */
3524    /** @jsxImportSourceTypes https://example.com/preact-types */
3525
3526    export function A() {
3527      return <div>Hello Deno</div>;
3528    }
3529    "#
3530      .to_vec()
3531      .into(),
3532      file_system: &NullFileSystem,
3533      jsr_url_provider: Default::default(),
3534      maybe_resolver: None,
3535      module_analyzer: Default::default(),
3536    })
3537    .await
3538    .unwrap();
3539    let actual = actual.js().unwrap();
3540    assert_eq!(actual.dependencies.len(), 1);
3541    let dep = actual
3542      .dependencies
3543      .get("https://example.com/preact/jsx-runtime")
3544      .unwrap();
3545    assert_eq!(
3546      dep.maybe_code.ok().unwrap().specifier,
3547      ModuleSpecifier::parse("https://example.com/preact/jsx-runtime").unwrap()
3548    );
3549    assert_eq!(
3550      dep.maybe_type.ok().unwrap().specifier,
3551      ModuleSpecifier::parse("https://example.com/preact-types/jsx-runtime")
3552        .unwrap()
3553    );
3554    assert_eq!(actual.specifier, specifier);
3555    assert_eq!(actual.media_type, MediaType::Tsx);
3556  }
3557
3558  #[tokio::test]
3559  async fn test_parse_module_jsx_import_source_types_pragma() {
3560    #[derive(Debug)]
3561    struct R;
3562    impl Resolver for R {
3563      fn default_jsx_import_source(
3564        &self,
3565        _referrer: &ModuleSpecifier,
3566      ) -> Option<String> {
3567        Some("https://example.com/preact".into())
3568      }
3569    }
3570
3571    let specifier = ModuleSpecifier::parse("file:///a/test01.tsx").unwrap();
3572    let actual = parse_module(ParseModuleOptions {
3573      graph_kind: GraphKind::All,
3574      specifier: specifier.clone(),
3575      maybe_headers: None,
3576      mtime: None,
3577      content: br#"
3578    /** @jsxImportSourceTypes https://example.com/preact-types */
3579
3580    export function A() {
3581      return <div>Hello Deno</div>;
3582    }
3583    "#
3584      .to_vec()
3585      .into(),
3586      file_system: &NullFileSystem,
3587      jsr_url_provider: Default::default(),
3588      maybe_resolver: Some(&R),
3589      module_analyzer: Default::default(),
3590    })
3591    .await
3592    .unwrap();
3593    let actual = actual.js().unwrap();
3594    assert_eq!(actual.dependencies.len(), 1);
3595    let dep = actual
3596      .dependencies
3597      .get("https://example.com/preact/jsx-runtime")
3598      .unwrap();
3599    assert_eq!(
3600      dep.maybe_code.ok().unwrap().specifier,
3601      ModuleSpecifier::parse("https://example.com/preact/jsx-runtime").unwrap()
3602    );
3603    assert_eq!(
3604      dep.maybe_type.ok().unwrap().specifier,
3605      ModuleSpecifier::parse("https://example.com/preact-types/jsx-runtime")
3606        .unwrap()
3607    );
3608    assert_eq!(actual.specifier, specifier);
3609    assert_eq!(actual.media_type, MediaType::Tsx);
3610  }
3611
3612  #[tokio::test]
3613  async fn test_parse_module_jsx_import_source_pragma() {
3614    #[derive(Debug)]
3615    struct R;
3616    impl Resolver for R {
3617      fn default_jsx_import_source_types(
3618        &self,
3619        _referrer: &ModuleSpecifier,
3620      ) -> Option<String> {
3621        Some("https://example.com/preact-types".into())
3622      }
3623    }
3624
3625    let specifier = ModuleSpecifier::parse("file:///a/test01.tsx").unwrap();
3626    let actual = parse_module(ParseModuleOptions {
3627      graph_kind: GraphKind::All,
3628      specifier: specifier.clone(),
3629      maybe_headers: None,
3630      mtime: None,
3631      content: br#"
3632    /** @jsxImportSource https://example.com/preact */
3633
3634    export function A() {
3635      return <div>Hello Deno</div>;
3636    }
3637    "#
3638      .to_vec()
3639      .into(),
3640      file_system: &NullFileSystem,
3641      jsr_url_provider: Default::default(),
3642      maybe_resolver: Some(&R),
3643      module_analyzer: Default::default(),
3644    })
3645    .await
3646    .unwrap();
3647    let actual = actual.js().unwrap();
3648    assert_eq!(actual.dependencies.len(), 1);
3649    let dep = actual
3650      .dependencies
3651      .get("https://example.com/preact/jsx-runtime")
3652      .unwrap();
3653    assert_eq!(
3654      dep.maybe_code.ok().unwrap().specifier,
3655      ModuleSpecifier::parse("https://example.com/preact/jsx-runtime").unwrap()
3656    );
3657    assert!(dep.maybe_type.is_none());
3658    assert_eq!(actual.specifier, specifier);
3659    assert_eq!(actual.media_type, MediaType::Tsx);
3660  }
3661
3662  #[tokio::test]
3663  async fn test_default_jsx_import_source() {
3664    #[derive(Debug)]
3665    struct R;
3666    impl Resolver for R {
3667      fn default_jsx_import_source(
3668        &self,
3669        referrer: &ModuleSpecifier,
3670      ) -> Option<String> {
3671        assert_eq!(
3672          referrer,
3673          &ModuleSpecifier::parse("file:///a/test01.tsx").unwrap()
3674        );
3675        Some("https://example.com/preact".into())
3676      }
3677    }
3678
3679    let specifier = ModuleSpecifier::parse("file:///a/test01.tsx").unwrap();
3680    let actual = parse_module(ParseModuleOptions {
3681      graph_kind: GraphKind::All,
3682      specifier: specifier.clone(),
3683      maybe_headers: None,
3684      mtime: None,
3685      content: br#"
3686    export function A() {
3687      return <div>Hello Deno</div>;
3688    }
3689    "#
3690      .to_vec()
3691      .into(),
3692      file_system: &NullFileSystem,
3693      jsr_url_provider: Default::default(),
3694      maybe_resolver: Some(&R),
3695      module_analyzer: Default::default(),
3696    })
3697    .await
3698    .unwrap();
3699    let actual = actual.js().unwrap();
3700    assert_eq!(actual.dependencies.len(), 1);
3701    let dep = actual
3702      .dependencies
3703      .get("https://example.com/preact/jsx-runtime")
3704      .unwrap();
3705    assert_eq!(
3706      dep.maybe_code.ok().unwrap().specifier,
3707      ModuleSpecifier::parse("https://example.com/preact/jsx-runtime").unwrap()
3708    );
3709    assert!(dep.maybe_type.is_none());
3710    assert_eq!(actual.specifier, specifier);
3711    assert_eq!(actual.media_type, MediaType::Tsx);
3712  }
3713
3714  #[tokio::test]
3715  async fn test_default_jsx_import_source_types() {
3716    #[derive(Debug)]
3717    struct R;
3718    impl Resolver for R {
3719      fn default_jsx_import_source(
3720        &self,
3721        referrer: &ModuleSpecifier,
3722      ) -> Option<String> {
3723        assert_eq!(
3724          referrer,
3725          &ModuleSpecifier::parse("file:///a/test01.tsx").unwrap()
3726        );
3727        Some("https://example.com/preact".into())
3728      }
3729
3730      fn default_jsx_import_source_types(
3731        &self,
3732        referrer: &ModuleSpecifier,
3733      ) -> Option<String> {
3734        assert_eq!(
3735          referrer,
3736          &ModuleSpecifier::parse("file:///a/test01.tsx").unwrap()
3737        );
3738        Some("https://example.com/preact-types".into())
3739      }
3740
3741      fn jsx_import_source_module(&self, referrer: &ModuleSpecifier) -> &str {
3742        assert_eq!(
3743          referrer,
3744          &ModuleSpecifier::parse("file:///a/test01.tsx").unwrap()
3745        );
3746        DEFAULT_JSX_IMPORT_SOURCE_MODULE
3747      }
3748    }
3749
3750    let specifier = ModuleSpecifier::parse("file:///a/test01.tsx").unwrap();
3751    let actual = parse_module(ParseModuleOptions {
3752      graph_kind: GraphKind::All,
3753      specifier: specifier.clone(),
3754      maybe_headers: None,
3755      mtime: None,
3756      content: br#"
3757    export function A() {
3758      return <div>Hello Deno</div>;
3759    }
3760    "#
3761      .to_vec()
3762      .into(),
3763      file_system: &NullFileSystem,
3764      jsr_url_provider: Default::default(),
3765      maybe_resolver: Some(&R),
3766      module_analyzer: Default::default(),
3767    })
3768    .await
3769    .unwrap();
3770    let actual = actual.js().unwrap();
3771    assert_eq!(actual.dependencies.len(), 1);
3772    let dep = actual
3773      .dependencies
3774      .get("https://example.com/preact/jsx-runtime")
3775      .unwrap();
3776    assert_eq!(
3777      dep.maybe_code.ok().unwrap().specifier,
3778      ModuleSpecifier::parse("https://example.com/preact/jsx-runtime").unwrap()
3779    );
3780    assert_eq!(
3781      dep.maybe_type.ok().unwrap().specifier,
3782      ModuleSpecifier::parse("https://example.com/preact-types/jsx-runtime")
3783        .unwrap()
3784    );
3785    assert_eq!(actual.specifier, specifier);
3786    assert_eq!(actual.media_type, MediaType::Tsx);
3787  }
3788
3789  #[tokio::test]
3790  async fn test_parse_module_with_headers() {
3791    let specifier = ModuleSpecifier::parse("https://localhost/file").unwrap();
3792    let mut headers = HashMap::new();
3793    headers.insert(
3794      "content-type".to_string(),
3795      "application/typescript; charset=utf-8".to_string(),
3796    );
3797    let result = parse_module(ParseModuleOptions {
3798      graph_kind: GraphKind::All,
3799      specifier: specifier.clone(),
3800      maybe_headers: Some(headers),
3801      mtime: None,
3802      content: br#"declare interface A {
3803  a: string;
3804}"#
3805        .to_vec()
3806        .into(),
3807      file_system: &NullFileSystem,
3808      jsr_url_provider: Default::default(),
3809      maybe_resolver: None,
3810      module_analyzer: Default::default(),
3811    })
3812    .await;
3813    assert!(result.is_ok());
3814  }
3815
3816  #[tokio::test]
3817  async fn test_parse_module_with_jsdoc_imports() {
3818    let specifier = ModuleSpecifier::parse("file:///a/test.js").unwrap();
3819    let code = br#"
3820/**
3821 * Some js doc
3822 *
3823 * @param {import("./types.d.ts").A} a
3824 * @return {import("./other.ts").B}
3825 */
3826export function a(a) {
3827  return;
3828}
3829"#;
3830    let actual = parse_module(ParseModuleOptions {
3831      graph_kind: GraphKind::All,
3832      specifier: specifier.clone(),
3833      maybe_headers: None,
3834      mtime: None,
3835      content: code.to_vec().into(),
3836      file_system: &NullFileSystem,
3837      jsr_url_provider: Default::default(),
3838      maybe_resolver: None,
3839      module_analyzer: Default::default(),
3840    })
3841    .await
3842    .unwrap();
3843    assert_eq!(
3844      json!(actual),
3845      json!({
3846        "kind": "esm",
3847        "dependencies": [
3848          {
3849            "specifier": "./types.d.ts",
3850            "type": {
3851              "specifier": "file:///a/types.d.ts",
3852              "span": {
3853                "start": {
3854                  "line": 4,
3855                  "character": 18,
3856                },
3857                "end": {
3858                  "line": 4,
3859                  "character": 32,
3860                }
3861              }
3862            }
3863          },
3864          {
3865            "specifier": "./other.ts",
3866            "type": {
3867              "specifier": "file:///a/other.ts",
3868              "span": {
3869                "start": {
3870                  "line": 5,
3871                  "character": 19,
3872                },
3873                "end": {
3874                  "line": 5,
3875                  "character": 31
3876                }
3877              }
3878            }
3879          }
3880        ],
3881        "mediaType": "JavaScript",
3882        "size": 137,
3883        "specifier": "file:///a/test.js"
3884      })
3885    );
3886
3887    // GraphKind::CodeOnly should not include them
3888    let actual = parse_module(ParseModuleOptions {
3889      graph_kind: GraphKind::CodeOnly,
3890      specifier: specifier.clone(),
3891      maybe_headers: None,
3892      mtime: None,
3893      content: code.to_vec().into(),
3894      file_system: &NullFileSystem,
3895      jsr_url_provider: Default::default(),
3896      maybe_resolver: None,
3897      module_analyzer: Default::default(),
3898    })
3899    .await
3900    .unwrap();
3901    assert_eq!(
3902      json!(actual),
3903      json!({
3904        "kind": "esm",
3905        "mediaType": "JavaScript",
3906        "size": 137,
3907        "specifier": "file:///a/test.js"
3908      })
3909    );
3910  }
3911
3912  #[tokio::test]
3913  async fn test_parse_ts_jsdoc_imports_ignored() {
3914    let specifier = ModuleSpecifier::parse("file:///a/test.ts").unwrap();
3915    let actual = parse_module(ParseModuleOptions {
3916      graph_kind: GraphKind::All,
3917      specifier: specifier.clone(),
3918      maybe_headers: None,
3919      mtime: None,
3920      content: br#"
3921/**
3922 * Some js doc
3923 *
3924 * @param {import("./types.d.ts").A} a
3925 * @return {import("./other.ts").B}
3926 */
3927export function a(a: A): B {
3928  return;
3929}
3930"#
3931      .to_vec()
3932      .into(),
3933      file_system: &NullFileSystem,
3934      jsr_url_provider: Default::default(),
3935      maybe_resolver: None,
3936      module_analyzer: Default::default(),
3937    })
3938    .await
3939    .unwrap();
3940    assert_eq!(
3941      json!(actual),
3942      json!({
3943        "kind": "esm",
3944        "mediaType": "TypeScript",
3945        "size": 143,
3946        "specifier": "file:///a/test.ts"
3947      })
3948    );
3949  }
3950
3951  #[tokio::test]
3952  async fn test_segment_graph() {
3953    let loader = setup(
3954      vec![
3955        (
3956          "file:///a/test01.ts",
3957          Source::Module {
3958            specifier: "file:///a/test01.ts",
3959            maybe_headers: None,
3960            content: r#"import * as b from "./test02.ts"; import "https://example.com/a.ts";"#,
3961          },
3962        ),
3963        (
3964          "file:///a/test02.ts",
3965          Source::Module {
3966            specifier: "file:///a/test02.ts",
3967            maybe_headers: None,
3968            content: r#"export const b = "b";"#,
3969          },
3970        ),
3971        (
3972          "https://example.com/a.ts",
3973          Source::Module {
3974            specifier: "https://example.com/a.ts",
3975            maybe_headers: None,
3976            content: r#"import * as c from "./c";"#,
3977          },
3978        ),
3979        (
3980          "https://example.com/c",
3981          Source::Module {
3982            specifier: "https://example.com/c.ts",
3983            maybe_headers: None,
3984            content: r#"export const c = "c";"#,
3985          },
3986        ),
3987        (
3988          "https://example.com/jsx-runtime",
3989          Source::Module {
3990            specifier: "https://example.com/jsx-runtime",
3991            maybe_headers: Some(vec![
3992              ("content-type", "application/javascript"),
3993              ("x-typescript-types", "./jsx-runtime.d.ts"),
3994            ]),
3995            content: r#"export const a = "a";"#,
3996          },
3997        ),
3998        (
3999          "https://example.com/jsx-runtime.d.ts",
4000          Source::Module {
4001            specifier: "https://example.com/jsx-runtime.d.ts",
4002            maybe_headers: Some(vec![(
4003              "content-type",
4004              "application/typescript",
4005            )]),
4006            content: r#"export const a: "a";"#,
4007          },
4008        ),
4009      ],
4010      vec![],
4011    );
4012    let roots = vec![ModuleSpecifier::parse("file:///a/test01.ts").unwrap()];
4013    let mut graph = ModuleGraph::new(GraphKind::All);
4014    let config_specifier =
4015      ModuleSpecifier::parse("file:///a/tsconfig.json").unwrap();
4016    let imports = vec![ReferrerImports {
4017      referrer: config_specifier.clone(),
4018      imports: vec!["https://example.com/jsx-runtime".to_string()],
4019    }];
4020    graph
4021      .build(
4022        roots.clone(),
4023        imports.clone(),
4024        &loader,
4025        BuildOptions::default(),
4026      )
4027      .await;
4028    assert!(graph.valid().is_ok());
4029    assert_eq!(graph.module_slots.len(), 6);
4030    assert_eq!(graph.redirects.len(), 1);
4031
4032    let example_a_url =
4033      ModuleSpecifier::parse("https://example.com/a.ts").unwrap();
4034    let graph = graph.segment(&[example_a_url.clone()]);
4035    assert_eq!(graph.roots, IndexSet::from([example_a_url]));
4036    // should get the redirect
4037    assert_eq!(
4038      graph.redirects,
4039      BTreeMap::from([(
4040        ModuleSpecifier::parse("https://example.com/c").unwrap(),
4041        ModuleSpecifier::parse("https://example.com/c.ts").unwrap(),
4042      )])
4043    );
4044
4045    // should copy over the imports
4046    assert_eq!(graph.imports.len(), 1);
4047
4048    assert!(graph
4049      .contains(&ModuleSpecifier::parse("https://example.com/a.ts").unwrap()));
4050    assert!(graph
4051      .contains(&ModuleSpecifier::parse("https://example.com/c.ts").unwrap()));
4052
4053    assert_eq!(
4054      graph.resolve_dependency(
4055        "https://example.com/jsx-runtime",
4056        &config_specifier,
4057        false
4058      ),
4059      Some(ModuleSpecifier::parse("https://example.com/jsx-runtime").unwrap())
4060        .as_ref()
4061    );
4062    assert_eq!(
4063      graph.resolve_dependency(
4064        "https://example.com/jsx-runtime",
4065        &config_specifier,
4066        true
4067      ),
4068      Some(
4069        ModuleSpecifier::parse("https://example.com/jsx-runtime.d.ts").unwrap()
4070      )
4071      .as_ref()
4072    );
4073  }
4074
4075  #[tokio::test]
4076  async fn test_walk() {
4077    let loader = setup(
4078      vec![
4079        (
4080          "file:///a/test01.ts",
4081          Source::Module {
4082            specifier: "file:///a/test01.ts",
4083            maybe_headers: None,
4084            content: r#"import * as b from "./test02.ts"; import "https://example.com/a.ts"; import "./test04.js"; import "./test_no_dts.js"; await import("./test03.ts");"#,
4085          },
4086        ),
4087        (
4088          "file:///a/test02.ts",
4089          Source::Module {
4090            specifier: "file:///a/test02.ts",
4091            maybe_headers: None,
4092            content: r#"export const b = "b";"#,
4093          },
4094        ),
4095        (
4096          "file:///a/test03.ts",
4097          Source::Module {
4098            specifier: "file:///a/test03.ts",
4099            maybe_headers: None,
4100            content: r#"export const c = "c";"#,
4101          },
4102        ),
4103        (
4104          "file:///a/test04.js",
4105          Source::Module {
4106            specifier: "file:///a/test04.js",
4107            maybe_headers: None,
4108            content: r#"/// <reference types="./test04.d.ts" />\nexport const d = "d";"#,
4109          },
4110        ),
4111        (
4112          "file:///a/test04.d.ts",
4113          Source::Module {
4114            specifier: "file:///a/test04.d.ts",
4115            maybe_headers: None,
4116            content: r#"export const d: "d";"#,
4117          },
4118        ),
4119        (
4120          "file:///a/test_no_dts.js",
4121          Source::Module {
4122            specifier: "file:///a/test_no_dts.js",
4123            maybe_headers: None,
4124            content: r#"export const e = "e";"#,
4125          },
4126        ),
4127        (
4128          "https://example.com/a.ts",
4129          Source::Module {
4130            specifier: "https://example.com/a.ts",
4131            maybe_headers: None,
4132            content: r#"import * as c from "./c";"#,
4133          },
4134        ),
4135        (
4136          "https://example.com/c",
4137          Source::Module {
4138            specifier: "https://example.com/c.ts",
4139            maybe_headers: None,
4140            content: r#"export const c = "c";"#,
4141          },
4142        ),
4143        (
4144          "https://example.com/jsx-runtime",
4145          Source::Module {
4146            specifier: "https://example.com/jsx-runtime",
4147            maybe_headers: Some(vec![
4148              ("content-type", "application/javascript"),
4149              ("x-typescript-types", "./jsx-runtime.d.ts"),
4150            ]),
4151            content: r#"export const a = "a";"#,
4152          },
4153        ),
4154        (
4155          "https://example.com/jsx-runtime.d.ts",
4156          Source::Module {
4157            specifier: "https://example.com/jsx-runtime.d.ts",
4158            maybe_headers: Some(vec![(
4159              "content-type",
4160              "application/typescript",
4161            )]),
4162            content: r#"export const a: "a";"#,
4163          },
4164        ),
4165      ],
4166      vec![],
4167    );
4168    let root = ModuleSpecifier::parse("file:///a/test01.ts").unwrap();
4169    let mut graph = ModuleGraph::new(GraphKind::All);
4170    let config_specifier =
4171      ModuleSpecifier::parse("file:///a/tsconfig.json").unwrap();
4172    let imports = vec![ReferrerImports {
4173      referrer: config_specifier.clone(),
4174      imports: vec!["https://example.com/jsx-runtime".to_string()],
4175    }];
4176    graph
4177      .build(
4178        vec![root.clone()],
4179        imports.clone(),
4180        &loader,
4181        BuildOptions::default(),
4182      )
4183      .await;
4184    assert!(graph.valid().is_ok());
4185
4186    // all true
4187    let roots = [root.clone()];
4188    let result = graph.walk(
4189      roots.iter(),
4190      WalkOptions {
4191        check_js: CheckJsOption::True,
4192        follow_dynamic: true,
4193        kind: GraphKind::All,
4194        prefer_fast_check_graph: true,
4195      },
4196    );
4197    assert_eq!(
4198      result
4199        .map(|(specifier, _)| specifier.to_string())
4200        .collect::<Vec<_>>(),
4201      vec![
4202        "https://example.com/jsx-runtime",
4203        "https://example.com/jsx-runtime.d.ts",
4204        "file:///a/test01.ts",
4205        "file:///a/test02.ts",
4206        "https://example.com/a.ts",
4207        "https://example.com/c",
4208        "https://example.com/c.ts",
4209        "file:///a/test04.js",
4210        "file:///a/test04.d.ts",
4211        "file:///a/test_no_dts.js",
4212        "file:///a/test03.ts",
4213      ]
4214    );
4215
4216    // all false
4217    let result = graph.walk(
4218      roots.iter(),
4219      WalkOptions {
4220        check_js: CheckJsOption::False,
4221        follow_dynamic: false,
4222        kind: GraphKind::CodeOnly,
4223        prefer_fast_check_graph: true,
4224      },
4225    );
4226    assert_eq!(
4227      result
4228        .map(|(specifier, _)| specifier.to_string())
4229        .collect::<Vec<_>>(),
4230      vec![
4231        "file:///a/test01.ts",
4232        "file:///a/test02.ts",
4233        "https://example.com/a.ts",
4234        "https://example.com/c",
4235        "https://example.com/c.ts",
4236        "file:///a/test04.js", // no types
4237        "file:///a/test_no_dts.js",
4238      ]
4239    );
4240    // dynamic true
4241    let result = graph.walk(
4242      roots.iter(),
4243      WalkOptions {
4244        check_js: CheckJsOption::False,
4245        follow_dynamic: true,
4246        kind: GraphKind::CodeOnly,
4247        prefer_fast_check_graph: true,
4248      },
4249    );
4250    assert_eq!(
4251      result
4252        .map(|(specifier, _)| specifier.to_string())
4253        .collect::<Vec<_>>(),
4254      vec![
4255        "file:///a/test01.ts",
4256        "file:///a/test02.ts",
4257        "https://example.com/a.ts",
4258        "https://example.com/c",
4259        "https://example.com/c.ts",
4260        "file:///a/test04.js",
4261        "file:///a/test_no_dts.js",
4262        "file:///a/test03.ts",
4263      ]
4264    );
4265
4266    // check_js true (won't have any effect since GraphKind is CodeOnly)
4267    let result = graph.walk(
4268      roots.iter(),
4269      WalkOptions {
4270        check_js: CheckJsOption::True,
4271        follow_dynamic: false,
4272        kind: GraphKind::CodeOnly,
4273        prefer_fast_check_graph: true,
4274      },
4275    );
4276    assert_eq!(
4277      result
4278        .map(|(specifier, _)| specifier.to_string())
4279        .collect::<Vec<_>>(),
4280      vec![
4281        "file:///a/test01.ts",
4282        "file:///a/test02.ts",
4283        "https://example.com/a.ts",
4284        "https://example.com/c",
4285        "https://example.com/c.ts",
4286        "file:///a/test04.js",
4287        "file:///a/test_no_dts.js",
4288      ]
4289    );
4290
4291    // check_js false, GraphKind All
4292    let result = graph.walk(
4293      roots.iter(),
4294      WalkOptions {
4295        check_js: CheckJsOption::False,
4296        follow_dynamic: false,
4297        kind: GraphKind::All,
4298        prefer_fast_check_graph: true,
4299      },
4300    );
4301    assert_eq!(
4302      result
4303        .map(|(specifier, _)| specifier.to_string())
4304        .collect::<Vec<_>>(),
4305      vec![
4306        "https://example.com/jsx-runtime",
4307        "https://example.com/jsx-runtime.d.ts",
4308        "file:///a/test01.ts",
4309        "file:///a/test02.ts",
4310        "https://example.com/a.ts",
4311        "https://example.com/c",
4312        "https://example.com/c.ts",
4313        "file:///a/test04.js",
4314        "file:///a/test04.d.ts",
4315        "file:///a/test_no_dts.js",
4316      ]
4317    );
4318
4319    // check_js false, GraphKind TypesOnly
4320    let result = graph.walk(
4321      roots.iter(),
4322      WalkOptions {
4323        check_js: CheckJsOption::False,
4324        follow_dynamic: false,
4325        kind: GraphKind::TypesOnly,
4326        prefer_fast_check_graph: true,
4327      },
4328    );
4329    assert_eq!(
4330      result
4331        .map(|(specifier, _)| specifier.to_string())
4332        .collect::<Vec<_>>(),
4333      vec![
4334        "https://example.com/jsx-runtime.d.ts",
4335        "file:///a/test01.ts",
4336        "file:///a/test02.ts",
4337        "https://example.com/a.ts",
4338        "https://example.com/c",
4339        "https://example.com/c.ts",
4340        "file:///a/test04.d.ts",
4341      ]
4342    );
4343
4344    // check_js true, GraphKind::TypesOnly
4345    let result = graph.walk(
4346      roots.iter(),
4347      WalkOptions {
4348        check_js: CheckJsOption::True,
4349        follow_dynamic: false,
4350        kind: GraphKind::TypesOnly,
4351        prefer_fast_check_graph: true,
4352      },
4353    );
4354    assert_eq!(
4355      result
4356        .map(|(specifier, _)| specifier.to_string())
4357        .collect::<Vec<_>>(),
4358      vec![
4359        "https://example.com/jsx-runtime.d.ts",
4360        "file:///a/test01.ts",
4361        "file:///a/test02.ts",
4362        "https://example.com/a.ts",
4363        "https://example.com/c",
4364        "https://example.com/c.ts",
4365        "file:///a/test04.d.ts",
4366        // will include this now
4367        "file:///a/test_no_dts.js",
4368      ]
4369    );
4370
4371    // try skip analyzing the dependencies after getting the first module
4372    {
4373      let mut iterator = graph.walk(
4374        roots.iter(),
4375        WalkOptions {
4376          check_js: CheckJsOption::True,
4377          follow_dynamic: false,
4378          kind: GraphKind::All,
4379          prefer_fast_check_graph: false,
4380        },
4381      );
4382      assert_eq!(
4383        iterator.next().unwrap().0.as_str(),
4384        "https://example.com/jsx-runtime"
4385      );
4386      assert_eq!(
4387        iterator.next().unwrap().0.as_str(),
4388        "https://example.com/jsx-runtime.d.ts"
4389      );
4390      assert_eq!(iterator.next().unwrap().0.as_str(), "file:///a/test01.ts");
4391      iterator.skip_previous_dependencies();
4392      assert!(iterator.next().is_none());
4393    }
4394
4395    // try skipping after first remote
4396    {
4397      let mut iterator = graph.walk(
4398        roots.iter(),
4399        WalkOptions {
4400          check_js: CheckJsOption::True,
4401          follow_dynamic: false,
4402          kind: GraphKind::All,
4403          prefer_fast_check_graph: false,
4404        },
4405      );
4406      assert_eq!(
4407        iterator.next().unwrap().0.as_str(),
4408        "https://example.com/jsx-runtime"
4409      );
4410      assert_eq!(
4411        iterator.next().unwrap().0.as_str(),
4412        "https://example.com/jsx-runtime.d.ts"
4413      );
4414      assert_eq!(iterator.next().unwrap().0.as_str(), "file:///a/test01.ts");
4415      assert_eq!(iterator.next().unwrap().0.as_str(), "file:///a/test02.ts");
4416      assert_eq!(
4417        iterator.next().unwrap().0.as_str(),
4418        "https://example.com/a.ts"
4419      );
4420      iterator.skip_previous_dependencies(); // now won't analyze the remote's dependencies
4421      assert_eq!(iterator.next().unwrap().0.as_str(), "file:///a/test04.js");
4422      assert_eq!(iterator.next().unwrap().0.as_str(), "file:///a/test04.d.ts");
4423      assert_eq!(
4424        iterator.next().unwrap().0.as_str(),
4425        "file:///a/test_no_dts.js"
4426      );
4427      assert!(iterator.next().is_none());
4428    }
4429  }
4430
4431  #[tokio::test]
4432  async fn test_resolver_execution_and_types_resolution() {
4433    #[derive(Debug)]
4434    struct ExtResolver;
4435
4436    impl Resolver for ExtResolver {
4437      fn resolve(
4438        &self,
4439        specifier_text: &str,
4440        referrer_range: &Range,
4441        resolution_kind: ResolutionKind,
4442      ) -> Result<ModuleSpecifier, source::ResolveError> {
4443        let specifier_text = match resolution_kind {
4444          ResolutionKind::Types => format!("{}.d.ts", specifier_text),
4445          ResolutionKind::Execution => format!("{}.js", specifier_text),
4446        };
4447        Ok(resolve_import(&specifier_text, &referrer_range.specifier)?)
4448      }
4449    }
4450
4451    let loader = setup(
4452      vec![
4453        (
4454          "file:///a/test01.ts",
4455          Source::Module {
4456            specifier: "file:///a/test01.ts",
4457            maybe_headers: None,
4458            content: r#"
4459            import a from "./a";
4460            "#,
4461          },
4462        ),
4463        (
4464          "file:///a/a.js",
4465          Source::Module {
4466            specifier: "file:///a/a.js",
4467            maybe_headers: None,
4468            content: r#"export default 5;"#,
4469          },
4470        ),
4471        (
4472          "file:///a/a.d.ts",
4473          Source::Module {
4474            specifier: "file:///a/a.d.ts",
4475            maybe_headers: None,
4476            content: r#"export default 5;"#,
4477          },
4478        ),
4479      ],
4480      vec![],
4481    );
4482    let root_specifier =
4483      ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url");
4484    let resolver = ExtResolver;
4485
4486    // test GraphKind::All
4487    {
4488      let mut graph = ModuleGraph::new(GraphKind::All);
4489      graph
4490        .build(
4491          vec![root_specifier.clone()],
4492          Default::default(),
4493          &loader,
4494          BuildOptions {
4495            resolver: Some(&resolver),
4496            ..Default::default()
4497          },
4498        )
4499        .await;
4500      assert_eq!(
4501        json!(graph),
4502        json!({
4503          "roots": [
4504            "file:///a/test01.ts"
4505          ],
4506          "modules": [
4507            {
4508              "kind": "esm",
4509              "size": 17,
4510              "mediaType": "Dts",
4511              "specifier": "file:///a/a.d.ts"
4512            },
4513            {
4514              "kind": "esm",
4515              "size": 17,
4516              "mediaType": "JavaScript",
4517              "specifier": "file:///a/a.js"
4518            },
4519            {
4520              "dependencies": [
4521                {
4522                  "specifier": "./a",
4523                  "code": {
4524                    "specifier": "file:///a/a.js",
4525                    "resolutionMode": "import",
4526                    "span": {
4527                      "start": {
4528                        "line": 1,
4529                        "character": 26
4530                      },
4531                      "end": {
4532                        "line": 1,
4533                        "character": 31
4534                      }
4535                    }
4536                  },
4537                  "type": {
4538                    "specifier": "file:///a/a.d.ts",
4539                    "resolutionMode": "import",
4540                    "span": {
4541                      "start": {
4542                        "line": 1,
4543                        "character": 26
4544                      },
4545                      "end": {
4546                        "line": 1,
4547                        "character": 31
4548                      }
4549                    }
4550                  },
4551                }
4552              ],
4553              "kind": "esm",
4554              "size": 46,
4555              "mediaType": "TypeScript",
4556              "specifier": "file:///a/test01.ts"
4557            }
4558          ],
4559          "redirects": {}
4560        })
4561      );
4562    }
4563
4564    // test GraphKind::CodeOnly
4565    {
4566      let mut graph = ModuleGraph::new(GraphKind::CodeOnly);
4567      graph
4568        .build(
4569          vec![root_specifier.clone()],
4570          Default::default(),
4571          &loader,
4572          BuildOptions {
4573            resolver: Some(&resolver),
4574            ..Default::default()
4575          },
4576        )
4577        .await;
4578      assert_eq!(
4579        json!(graph),
4580        json!({
4581          "roots": [
4582            "file:///a/test01.ts"
4583          ],
4584          "modules": [
4585            {
4586              "kind": "esm",
4587              "size": 17,
4588              "mediaType": "JavaScript",
4589              "specifier": "file:///a/a.js"
4590            },
4591            {
4592              "dependencies": [
4593                {
4594                  "specifier": "./a",
4595                  "code": {
4596                    "specifier": "file:///a/a.js",
4597                    "resolutionMode": "import",
4598                    "span": {
4599                      "start": {
4600                        "line": 1,
4601                        "character": 26
4602                      },
4603                      "end": {
4604                        "line": 1,
4605                        "character": 31
4606                      }
4607                    }
4608                  },
4609                }
4610              ],
4611              "kind": "esm",
4612              "size": 46,
4613              "mediaType": "TypeScript",
4614              "specifier": "file:///a/test01.ts"
4615            }
4616          ],
4617          "redirects": {}
4618        })
4619      );
4620    }
4621  }
4622
4623  #[tokio::test]
4624  async fn test_resolver_failed_types_only() {
4625    #[derive(Debug)]
4626    struct FailForTypesResolver;
4627
4628    #[derive(Debug, thiserror::Error, deno_error::JsError)]
4629    #[class(generic)]
4630    #[error("Failed.")]
4631    struct FailedError;
4632
4633    impl Resolver for FailForTypesResolver {
4634      fn resolve(
4635        &self,
4636        specifier_text: &str,
4637        referrer_range: &Range,
4638        resolution_kind: ResolutionKind,
4639      ) -> Result<ModuleSpecifier, source::ResolveError> {
4640        match resolution_kind {
4641          ResolutionKind::Execution => {
4642            Ok(resolve_import(specifier_text, &referrer_range.specifier)?)
4643          }
4644          ResolutionKind::Types => Err(source::ResolveError::Other(
4645            JsErrorBox::from_err(FailedError),
4646          )),
4647        }
4648      }
4649    }
4650
4651    let loader = setup(
4652      vec![
4653        (
4654          "file:///a/test01.ts",
4655          Source::Module {
4656            specifier: "file:///a/test01.ts",
4657            maybe_headers: None,
4658            content: r#"
4659            import a from "./a.js";
4660            "#,
4661          },
4662        ),
4663        (
4664          "file:///a/a.js",
4665          Source::Module {
4666            specifier: "file:///a/a.js",
4667            maybe_headers: None,
4668            content: r#"export default 5;"#,
4669          },
4670        ),
4671      ],
4672      vec![],
4673    );
4674    let root_specifier =
4675      ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url");
4676    let resolver = FailForTypesResolver;
4677
4678    let mut graph = ModuleGraph::new(GraphKind::All);
4679    graph
4680      .build(
4681        vec![root_specifier.clone()],
4682        Default::default(),
4683        &loader,
4684        BuildOptions {
4685          resolver: Some(&resolver),
4686          ..Default::default()
4687        },
4688      )
4689      .await;
4690    let errors = graph
4691      .walk(
4692        graph.roots.iter(),
4693        WalkOptions {
4694          check_js: CheckJsOption::True,
4695          kind: GraphKind::TypesOnly,
4696          follow_dynamic: false,
4697          prefer_fast_check_graph: true,
4698        },
4699      )
4700      .errors()
4701      .collect::<Vec<_>>();
4702    assert_eq!(errors.len(), 1);
4703    assert!(matches!(
4704      &errors[0],
4705      ModuleGraphError::TypesResolutionError(_)
4706    ));
4707  }
4708
4709  #[tokio::test]
4710  async fn test_passthrough_jsr_specifiers() {
4711    let loader = setup(
4712      vec![
4713        (
4714          "file:///a/test01.ts",
4715          Source::Module {
4716            specifier: "file:///a/test01.ts",
4717            maybe_headers: None,
4718            content: r#"import "jsr:@foo/bar@1";"#,
4719          },
4720        ),
4721        ("jsr:@foo/bar@1", Source::External("jsr:@foo/bar@1")),
4722      ],
4723      vec![],
4724    );
4725    let root_specifier =
4726      ModuleSpecifier::parse("file:///a/test01.ts").expect("bad url");
4727    let mut graph = ModuleGraph::new(GraphKind::All);
4728    graph
4729      .build(
4730        vec![root_specifier.clone()],
4731        Default::default(),
4732        &loader,
4733        BuildOptions {
4734          passthrough_jsr_specifiers: true,
4735          ..Default::default()
4736        },
4737      )
4738      .await;
4739    assert_eq!(graph.module_slots.len(), 2);
4740    assert_eq!(graph.roots, IndexSet::from([root_specifier.clone()]));
4741    assert!(graph.contains(&root_specifier));
4742    let module = graph
4743      .module_slots
4744      .get(&root_specifier)
4745      .unwrap()
4746      .module()
4747      .unwrap()
4748      .js()
4749      .unwrap();
4750    assert_eq!(module.dependencies.len(), 1);
4751    let maybe_dependency = module.dependencies.get("jsr:@foo/bar@1");
4752    assert!(maybe_dependency.is_some());
4753    let dependency_specifier =
4754      ModuleSpecifier::parse("jsr:@foo/bar@1").unwrap();
4755    let maybe_dep_module_slot = graph.get(&dependency_specifier);
4756    let dep_module_slot = maybe_dep_module_slot.unwrap();
4757    assert!(dep_module_slot.external().is_some());
4758  }
4759}