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