1#![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 pub referrer: ModuleSpecifier,
110 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#[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
167pub 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 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 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 "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 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 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 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 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 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 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", "file:///a/test_no_dts.js",
4070 ]
4071 );
4072 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 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 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 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 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 "file:///a/test_no_dts.js",
4200 ]
4201 );
4202
4203 {
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 {
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(); 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 {
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 {
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}