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