1use std::sync::Arc;
4
5use deno_ast::dep::DynamicDependencyKind;
6use deno_ast::dep::ImportAttributes;
7use deno_ast::dep::StaticDependencyKind;
8use deno_ast::MediaType;
9use deno_ast::ModuleSpecifier;
10use deno_ast::ParseDiagnostic;
11use deno_ast::SourceRange;
12use deno_ast::SourceTextInfo;
13use regex::Match;
14use serde::ser::SerializeTuple;
15use serde::Deserialize;
16use serde::Serialize;
17use serde::Serializer;
18
19use crate::ast::DENO_TYPES_RE;
20use crate::graph::Position;
21use crate::source::ResolutionMode;
22use crate::DefaultModuleAnalyzer;
23
24#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Hash)]
25pub struct PositionRange {
26 #[serde(default = "Position::zeroed")]
27 pub start: Position,
28 #[serde(default = "Position::zeroed")]
29 pub end: Position,
30}
31
32impl PositionRange {
33 pub fn zeroed() -> Self {
34 Self {
35 start: Position::zeroed(),
36 end: Position::zeroed(),
37 }
38 }
39
40 pub fn includes(&self, position: Position) -> bool {
42 (position >= self.start) && (position <= self.end)
43 }
44
45 pub fn from_source_range(
46 range: SourceRange,
47 text_info: &SourceTextInfo,
48 ) -> Self {
49 Self {
50 start: Position::from_source_pos(range.start, text_info),
51 end: Position::from_source_pos(range.end, text_info),
52 }
53 }
54
55 pub fn as_source_range(&self, text_info: &SourceTextInfo) -> SourceRange {
56 SourceRange::new(
57 self.start.as_source_pos(text_info),
58 self.end.as_source_pos(text_info),
59 )
60 }
61}
62
63impl Serialize for PositionRange {
67 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
68 where
69 S: Serializer,
70 {
71 struct PositionSerializer<'a>(&'a Position);
72
73 impl Serialize for PositionSerializer<'_> {
74 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
75 where
76 S: Serializer,
77 {
78 let mut seq = serializer.serialize_tuple(2)?;
79 seq.serialize_element(&self.0.line)?;
80 seq.serialize_element(&self.0.character)?;
81 seq.end()
82 }
83 }
84
85 let mut seq = serializer.serialize_tuple(2)?;
86 seq.serialize_element(&PositionSerializer(&self.start))?;
87 seq.serialize_element(&PositionSerializer(&self.end))?;
88 seq.end()
89 }
90}
91
92#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase", tag = "type")]
94pub enum DependencyDescriptor {
95 Static(StaticDependencyDescriptor),
96 Dynamic(DynamicDependencyDescriptor),
97}
98
99impl DependencyDescriptor {
100 pub fn as_static(&self) -> Option<&StaticDependencyDescriptor> {
101 match self {
102 Self::Static(descriptor) => Some(descriptor),
103 Self::Dynamic(_) => None,
104 }
105 }
106
107 pub fn as_dynamic(&self) -> Option<&DynamicDependencyDescriptor> {
108 match self {
109 Self::Static(_) => None,
110 Self::Dynamic(d) => Some(d),
111 }
112 }
113
114 pub fn import_attributes(&self) -> &ImportAttributes {
115 match self {
116 DependencyDescriptor::Static(d) => &d.import_attributes,
117 DependencyDescriptor::Dynamic(d) => &d.import_attributes,
118 }
119 }
120}
121
122#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
123#[serde(rename_all = "camelCase")]
124pub struct StaticDependencyDescriptor {
125 pub kind: StaticDependencyKind,
127 #[serde(skip_serializing_if = "Option::is_none", default)]
130 pub types_specifier: Option<SpecifierWithRange>,
131 pub specifier: String,
133 pub specifier_range: PositionRange,
135 #[serde(skip_serializing_if = "ImportAttributes::is_none", default)]
137 pub import_attributes: ImportAttributes,
138}
139
140impl From<StaticDependencyDescriptor> for DependencyDescriptor {
141 fn from(descriptor: StaticDependencyDescriptor) -> Self {
142 DependencyDescriptor::Static(descriptor)
143 }
144}
145
146#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
147#[serde(rename_all = "camelCase", untagged)]
148pub enum DynamicArgument {
149 String(String),
150 Template(Vec<DynamicTemplatePart>),
151 #[default]
153 Expr,
154}
155
156impl DynamicArgument {
157 pub fn is_expr(&self) -> bool {
158 matches!(self, DynamicArgument::Expr)
159 }
160}
161
162#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
163#[serde(rename_all = "camelCase", tag = "type")]
164pub enum DynamicTemplatePart {
165 String {
166 value: String,
167 },
168 Expr,
170}
171
172#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
173#[serde(rename_all = "camelCase")]
174pub struct DynamicDependencyDescriptor {
175 #[serde(skip_serializing_if = "is_dynamic_esm", default)]
176 pub kind: DynamicDependencyKind,
177 #[serde(skip_serializing_if = "Option::is_none", default)]
180 pub types_specifier: Option<SpecifierWithRange>,
181 #[serde(skip_serializing_if = "DynamicArgument::is_expr", default)]
183 pub argument: DynamicArgument,
184 pub argument_range: PositionRange,
186 #[serde(skip_serializing_if = "ImportAttributes::is_none", default)]
188 pub import_attributes: ImportAttributes,
189}
190
191fn is_dynamic_esm(kind: &DynamicDependencyKind) -> bool {
192 *kind == DynamicDependencyKind::Import
193}
194
195impl From<DynamicDependencyDescriptor> for DependencyDescriptor {
196 fn from(descriptor: DynamicDependencyDescriptor) -> Self {
197 DependencyDescriptor::Dynamic(descriptor)
198 }
199}
200
201#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
202#[serde(rename_all = "camelCase")]
203pub struct SpecifierWithRange {
204 pub text: String,
205 pub range: PositionRange,
206}
207
208#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
209#[serde(rename_all = "camelCase")]
210pub enum TypeScriptTypesResolutionMode {
211 Require,
212 Import,
213}
214
215impl TypeScriptTypesResolutionMode {
216 pub fn from_str(text: &str) -> Option<Self> {
217 match text {
218 "import" => Some(Self::Import),
219 "require" => Some(Self::Require),
220 _ => None,
221 }
222 }
223
224 pub fn as_deno_graph(&self) -> ResolutionMode {
225 match self {
226 Self::Require => ResolutionMode::Require,
227 Self::Import => ResolutionMode::Import,
228 }
229 }
230}
231
232#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
233#[serde(rename_all = "camelCase")]
234#[serde(tag = "type")]
235pub enum TypeScriptReference {
236 Path(SpecifierWithRange),
237 #[serde(rename_all = "camelCase")]
238 Types {
239 #[serde(flatten)]
240 specifier: SpecifierWithRange,
241 #[serde(skip_serializing_if = "Option::is_none", default)]
242 resolution_mode: Option<TypeScriptTypesResolutionMode>,
243 },
244}
245
246#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
247#[serde(rename_all = "camelCase")]
248pub struct JsDocImportInfo {
249 #[serde(flatten)]
250 pub specifier: SpecifierWithRange,
251 #[serde(skip_serializing_if = "Option::is_none", default)]
252 pub resolution_mode: Option<TypeScriptTypesResolutionMode>,
253}
254
255#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
257#[serde(rename_all = "camelCase")]
258pub struct ModuleInfo {
259 #[serde(skip_serializing_if = "is_false", default, rename = "script")]
262 pub is_script: bool,
263 #[serde(skip_serializing_if = "Vec::is_empty", default)]
265 pub dependencies: Vec<DependencyDescriptor>,
266 #[serde(skip_serializing_if = "Vec::is_empty", default)]
268 pub ts_references: Vec<TypeScriptReference>,
269 #[serde(skip_serializing_if = "Option::is_none", default)]
271 pub self_types_specifier: Option<SpecifierWithRange>,
272 #[serde(skip_serializing_if = "Option::is_none", default)]
274 pub jsx_import_source: Option<SpecifierWithRange>,
275 #[serde(skip_serializing_if = "Option::is_none", default)]
277 pub jsx_import_source_types: Option<SpecifierWithRange>,
278 #[serde(skip_serializing_if = "Vec::is_empty", default)]
281 pub jsdoc_imports: Vec<JsDocImportInfo>,
282}
283
284pub fn module_graph_1_to_2(module_info: &mut serde_json::Value) {
285 #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
286 #[serde(rename_all = "camelCase")]
287 struct Comment {
288 text: String,
289 range: PositionRange,
290 }
291
292 fn analyze_deno_types(
294 leading_comments: &[Comment],
295 ) -> Option<SpecifierWithRange> {
296 fn comment_position_to_position_range(
297 mut comment_start: Position,
298 m: &Match,
299 ) -> PositionRange {
300 comment_start.character += 2;
302 PositionRange {
303 start: Position {
306 line: comment_start.line,
307 character: comment_start.character + m.start() - 1,
308 },
309 end: Position {
310 line: comment_start.line,
311 character: comment_start.character + m.end() + 1,
312 },
313 }
314 }
315
316 let comment = leading_comments.last()?;
317 let captures = DENO_TYPES_RE.captures(&comment.text)?;
318 if let Some(m) = captures.get(1) {
319 Some(SpecifierWithRange {
320 text: m.as_str().to_string(),
321 range: comment_position_to_position_range(comment.range.start, &m),
322 })
323 } else if let Some(m) = captures.get(2) {
324 Some(SpecifierWithRange {
325 text: m.as_str().to_string(),
326 range: comment_position_to_position_range(comment.range.start, &m),
327 })
328 } else {
329 unreachable!("Unexpected captures from deno types regex")
330 }
331 }
332
333 if let serde_json::Value::Object(module_info) = module_info {
337 if let Some(dependencies) = module_info
338 .get_mut("dependencies")
339 .and_then(|v| v.as_array_mut())
340 {
341 for dependency in dependencies {
342 if let Some(dependency) = dependency.as_object_mut() {
343 if let Some(leading_comments) = dependency
344 .get("leadingComments")
345 .and_then(|v| v.as_array())
346 .and_then(|v| {
347 v.iter()
348 .map(|v| serde_json::from_value(v.clone()).ok())
349 .collect::<Option<Vec<Comment>>>()
350 })
351 {
352 if let Some(deno_types) = analyze_deno_types(&leading_comments) {
353 dependency.insert(
354 "typesSpecifier".to_string(),
355 serde_json::to_value(deno_types).unwrap(),
356 );
357 }
358 dependency.remove("leadingComments");
359 }
360 }
361 }
362 }
363 };
364}
365
366#[async_trait::async_trait(?Send)]
371pub trait ModuleAnalyzer {
372 async fn analyze(
374 &self,
375 specifier: &ModuleSpecifier,
376 source: Arc<str>,
377 media_type: MediaType,
378 ) -> Result<ModuleInfo, ParseDiagnostic>;
379}
380
381impl<'a> Default for &'a dyn ModuleAnalyzer {
382 fn default() -> &'a dyn ModuleAnalyzer {
383 &DefaultModuleAnalyzer
384 }
385}
386
387fn is_false(v: &bool) -> bool {
388 !v
389}
390
391#[cfg(test)]
392mod test {
393 use std::collections::HashMap;
394
395 use deno_ast::dep::ImportAttribute;
396 use pretty_assertions::assert_eq;
397 use serde::de::DeserializeOwned;
398 use serde_json::json;
399
400 use super::*;
401
402 #[test]
403 fn module_info_serialization_empty() {
404 let module_info = ModuleInfo {
406 is_script: false,
407 dependencies: Vec::new(),
408 ts_references: Vec::new(),
409 self_types_specifier: None,
410 jsx_import_source: None,
411 jsx_import_source_types: None,
412 jsdoc_imports: Vec::new(),
413 };
414 run_serialization_test(&module_info, json!({}));
415 }
416
417 #[test]
418 fn module_info_serialization_deps() {
419 let module_info = ModuleInfo {
421 is_script: true,
422 dependencies: Vec::from([
423 StaticDependencyDescriptor {
424 kind: StaticDependencyKind::ImportEquals,
425 types_specifier: Some(SpecifierWithRange {
426 text: "a".to_string(),
427 range: PositionRange {
428 start: Position::zeroed(),
429 end: Position::zeroed(),
430 },
431 }),
432 specifier: "./test".to_string(),
433 specifier_range: PositionRange {
434 start: Position {
435 line: 1,
436 character: 2,
437 },
438 end: Position {
439 line: 3,
440 character: 4,
441 },
442 },
443 import_attributes: ImportAttributes::None,
444 }
445 .into(),
446 DynamicDependencyDescriptor {
447 kind: DynamicDependencyKind::Import,
448 types_specifier: None,
449 argument: DynamicArgument::String("./test2".to_string()),
450 argument_range: PositionRange {
451 start: Position::zeroed(),
452 end: Position::zeroed(),
453 },
454 import_attributes: ImportAttributes::Known(HashMap::from([
455 ("key".to_string(), ImportAttribute::Unknown),
456 (
457 "key2".to_string(),
458 ImportAttribute::Known("value".to_string()),
459 ),
460 ("kind".to_string(), ImportAttribute::Unknown),
461 ])),
462 }
463 .into(),
464 DynamicDependencyDescriptor {
465 kind: DynamicDependencyKind::Require,
466 types_specifier: None,
467 argument: DynamicArgument::String("./test3".to_string()),
468 argument_range: PositionRange {
469 start: Position::zeroed(),
470 end: Position::zeroed(),
471 },
472 import_attributes: ImportAttributes::None,
473 }
474 .into(),
475 ]),
476 ts_references: Vec::new(),
477 self_types_specifier: None,
478 jsx_import_source: None,
479 jsx_import_source_types: None,
480 jsdoc_imports: Vec::new(),
481 };
482 run_serialization_test(
483 &module_info,
484 json!({
487 "script": true,
488 "dependencies": [{
489 "type": "static",
490 "kind": "importEquals",
491 "typesSpecifier": {
492 "text": "a",
493 "range": [[0, 0], [0, 0]],
494 },
495 "specifier": "./test",
496 "specifierRange": [[1, 2], [3, 4]],
497 }, {
498 "type": "dynamic",
499 "argument": "./test2",
500 "argumentRange": [[0, 0], [0, 0]],
501 "importAttributes": {
502 "known": {
503 "key": null,
504 "kind": null,
505 "key2": "value",
506 }
507 }
508 }, {
509 "type": "dynamic",
510 "kind": "require",
511 "argument": "./test3",
512 "argumentRange": [[0, 0], [0, 0]]
513 }]
514 }),
515 );
516 }
517
518 #[test]
519 fn module_info_serialization_ts_references() {
520 let module_info = ModuleInfo {
521 is_script: false,
522 dependencies: Vec::new(),
523 ts_references: Vec::from([
524 TypeScriptReference::Path(SpecifierWithRange {
525 text: "a".to_string(),
526 range: PositionRange {
527 start: Position::zeroed(),
528 end: Position::zeroed(),
529 },
530 }),
531 TypeScriptReference::Types {
532 specifier: SpecifierWithRange {
533 text: "b".to_string(),
534 range: PositionRange {
535 start: Position::zeroed(),
536 end: Position::zeroed(),
537 },
538 },
539 resolution_mode: None,
540 },
541 TypeScriptReference::Types {
542 specifier: SpecifierWithRange {
543 text: "node".to_string(),
544 range: PositionRange {
545 start: Position::zeroed(),
546 end: Position::zeroed(),
547 },
548 },
549 resolution_mode: Some(TypeScriptTypesResolutionMode::Require),
550 },
551 TypeScriptReference::Types {
552 specifier: SpecifierWithRange {
553 text: "node-esm".to_string(),
554 range: PositionRange {
555 start: Position::zeroed(),
556 end: Position::zeroed(),
557 },
558 },
559 resolution_mode: Some(TypeScriptTypesResolutionMode::Import),
560 },
561 ]),
562 self_types_specifier: None,
563 jsx_import_source: None,
564 jsx_import_source_types: None,
565 jsdoc_imports: Vec::new(),
566 };
567 run_serialization_test(
568 &module_info,
569 json!({
572 "tsReferences": [{
573 "type": "path",
574 "text": "a",
575 "range": [[0, 0], [0, 0]],
576 }, {
577 "type": "types",
578 "text": "b",
579 "range": [[0, 0], [0, 0]],
580 }, {
581 "type": "types",
582 "text": "node",
583 "range": [[0, 0], [0, 0]],
584 "resolutionMode": "require",
585 }, {
586 "type": "types",
587 "text": "node-esm",
588 "range": [[0, 0], [0, 0]],
589 "resolutionMode": "import",
590 }]
591 }),
592 );
593 }
594
595 #[test]
596 fn module_info_serialization_self_types_specifier() {
597 let module_info = ModuleInfo {
598 is_script: false,
599 dependencies: Vec::new(),
600 ts_references: Vec::new(),
601 self_types_specifier: Some(SpecifierWithRange {
602 text: "a".to_string(),
603 range: PositionRange {
604 start: Position::zeroed(),
605 end: Position::zeroed(),
606 },
607 }),
608 jsx_import_source: None,
609 jsx_import_source_types: None,
610 jsdoc_imports: Vec::new(),
611 };
612 run_serialization_test(
613 &module_info,
614 json!({
617 "selfTypesSpecifier": {
618 "text": "a",
619 "range": [[0, 0], [0, 0]],
620 }
621 }),
622 );
623 }
624
625 #[test]
626 fn module_info_serialization_jsx_import_source() {
627 let module_info = ModuleInfo {
628 is_script: false,
629 dependencies: Vec::new(),
630 ts_references: Vec::new(),
631 self_types_specifier: None,
632 jsx_import_source: Some(SpecifierWithRange {
633 text: "a".to_string(),
634 range: PositionRange {
635 start: Position::zeroed(),
636 end: Position::zeroed(),
637 },
638 }),
639 jsx_import_source_types: None,
640 jsdoc_imports: Vec::new(),
641 };
642 run_serialization_test(
643 &module_info,
644 json!({
647 "jsxImportSource": {
648 "text": "a",
649 "range": [[0, 0], [0, 0]],
650 }
651 }),
652 );
653 }
654
655 #[test]
656 fn module_info_serialization_jsx_import_source_types() {
657 let module_info = ModuleInfo {
658 is_script: false,
659 dependencies: Vec::new(),
660 ts_references: Vec::new(),
661 self_types_specifier: None,
662 jsx_import_source: None,
663 jsx_import_source_types: Some(SpecifierWithRange {
664 text: "a".to_string(),
665 range: PositionRange {
666 start: Position::zeroed(),
667 end: Position::zeroed(),
668 },
669 }),
670 jsdoc_imports: Vec::new(),
671 };
672 run_serialization_test(
673 &module_info,
674 json!({
677 "jsxImportSourceTypes": {
678 "text": "a",
679 "range": [[0, 0], [0, 0]],
680 }
681 }),
682 );
683 }
684
685 #[test]
686 fn module_info_jsdoc_imports() {
687 let module_info = ModuleInfo {
688 is_script: false,
689 dependencies: Vec::new(),
690 ts_references: Vec::new(),
691 self_types_specifier: None,
692 jsx_import_source: None,
693 jsx_import_source_types: None,
694 jsdoc_imports: Vec::from([
695 JsDocImportInfo {
696 specifier: SpecifierWithRange {
697 text: "a".to_string(),
698 range: PositionRange {
699 start: Position::zeroed(),
700 end: Position::zeroed(),
701 },
702 },
703 resolution_mode: None,
704 },
705 JsDocImportInfo {
706 specifier: SpecifierWithRange {
707 text: "b".to_string(),
708 range: PositionRange {
709 start: Position::zeroed(),
710 end: Position::zeroed(),
711 },
712 },
713 resolution_mode: Some(TypeScriptTypesResolutionMode::Import),
714 },
715 JsDocImportInfo {
716 specifier: SpecifierWithRange {
717 text: "c".to_string(),
718 range: PositionRange {
719 start: Position::zeroed(),
720 end: Position::zeroed(),
721 },
722 },
723 resolution_mode: Some(TypeScriptTypesResolutionMode::Require),
724 },
725 ]),
726 };
727 run_serialization_test(
728 &module_info,
729 json!({
732 "jsdocImports": [{
733 "text": "a",
734 "range": [[0, 0], [0, 0]],
735 }, {
736 "text": "b",
737 "range": [[0, 0], [0, 0]],
738 "resolutionMode": "import",
739 }, {
740 "text": "c",
741 "range": [[0, 0], [0, 0]],
742 "resolutionMode": "require",
743 }]
744 }),
745 );
746 }
747
748 #[test]
749 fn static_dependency_descriptor_serialization() {
750 let descriptor = DependencyDescriptor::Static(StaticDependencyDescriptor {
752 kind: StaticDependencyKind::ExportEquals,
753 types_specifier: Some(SpecifierWithRange {
754 text: "a".to_string(),
755 range: PositionRange {
756 start: Position::zeroed(),
757 end: Position::zeroed(),
758 },
759 }),
760 specifier: "./test".to_string(),
761 specifier_range: PositionRange {
762 start: Position::zeroed(),
763 end: Position::zeroed(),
764 },
765 import_attributes: ImportAttributes::Unknown,
766 });
767 run_serialization_test(
768 &descriptor,
769 json!({
772 "type": "static",
773 "kind": "exportEquals",
774 "typesSpecifier": {
775 "text": "a",
776 "range": [[0, 0], [0, 0]],
777 },
778 "specifier": "./test",
779 "specifierRange": [[0, 0], [0, 0]],
780 "importAttributes": "unknown",
781 }),
782 );
783 }
784
785 #[test]
786 fn dynamic_dependency_descriptor_serialization() {
787 run_serialization_test(
788 &DependencyDescriptor::Dynamic(DynamicDependencyDescriptor {
789 kind: DynamicDependencyKind::Import,
790 types_specifier: Some(SpecifierWithRange {
791 text: "a".to_string(),
792 range: PositionRange {
793 start: Position::zeroed(),
794 end: Position::zeroed(),
795 },
796 }),
797 argument: DynamicArgument::Expr,
798 argument_range: PositionRange {
799 start: Position::zeroed(),
800 end: Position::zeroed(),
801 },
802 import_attributes: ImportAttributes::Unknown,
803 }),
804 json!({
807 "type": "dynamic",
808 "typesSpecifier": {
809 "text": "a",
810 "range": [[0, 0], [0, 0]],
811 },
812 "argumentRange": [[0, 0], [0, 0]],
813 "importAttributes": "unknown",
814 }),
815 );
816
817 run_serialization_test(
818 &DependencyDescriptor::Dynamic(DynamicDependencyDescriptor {
819 kind: DynamicDependencyKind::Import,
820 types_specifier: None,
821 argument: DynamicArgument::String("test".to_string()),
822 argument_range: PositionRange {
823 start: Position::zeroed(),
824 end: Position::zeroed(),
825 },
826 import_attributes: ImportAttributes::Unknown,
827 }),
828 json!({
831 "type": "dynamic",
832 "argument": "test",
833 "argumentRange": [[0, 0], [0, 0]],
834 "importAttributes": "unknown",
835 }),
836 );
837 }
838
839 #[test]
840 fn test_dynamic_argument_serialization() {
841 run_serialization_test(
842 &DynamicArgument::String("test".to_string()),
843 json!("test"),
844 );
845 run_serialization_test(
846 &DynamicArgument::Template(vec![
847 DynamicTemplatePart::String {
848 value: "test".to_string(),
849 },
850 DynamicTemplatePart::Expr,
851 ]),
852 json!([{
855 "type": "string",
856 "value": "test",
857 }, {
858 "type": "expr",
859 }]),
860 );
861 }
862
863 #[test]
864 fn test_import_attributes_serialization() {
865 run_serialization_test(&ImportAttributes::Unknown, json!("unknown"));
866 run_serialization_test(
867 &ImportAttributes::Known(HashMap::from([(
868 "type".to_string(),
869 ImportAttribute::Unknown,
870 )])),
871 json!({
872 "known": {
873 "type": null,
874 }
875 }),
876 );
877 run_serialization_test(
878 &ImportAttributes::Known(HashMap::from([(
879 "type".to_string(),
880 ImportAttribute::Known("test".to_string()),
881 )])),
882 json!({
883 "known": {
884 "type": "test",
885 }
886 }),
887 );
888 }
889
890 #[test]
891 fn test_v1_to_v2_deserialization_with_leading_comment() {
892 let expected = ModuleInfo {
893 is_script: false,
894 dependencies: vec![DependencyDescriptor::Static(
895 StaticDependencyDescriptor {
896 kind: StaticDependencyKind::Import,
897 specifier: "./a.js".to_string(),
898 specifier_range: PositionRange {
899 start: Position {
900 line: 1,
901 character: 2,
902 },
903 end: Position {
904 line: 3,
905 character: 4,
906 },
907 },
908 types_specifier: Some(SpecifierWithRange {
909 text: "./a.d.ts".to_string(),
910 range: PositionRange {
911 start: Position {
912 line: 0,
913 character: 15,
914 },
915 end: Position {
916 line: 0,
917 character: 25,
918 },
919 },
920 }),
921 import_attributes: ImportAttributes::None,
922 },
923 )],
924 ts_references: Vec::new(),
925 self_types_specifier: None,
926 jsx_import_source: None,
927 jsx_import_source_types: None,
928 jsdoc_imports: Vec::new(),
929 };
930 let json = json!({
931 "dependencies": [{
932 "type": "static",
933 "kind": "import",
934 "specifier": "./a.js",
935 "specifierRange": [[1, 2], [3, 4]],
936 "leadingComments": [{
937 "text": " @deno-types=\"./a.d.ts\"",
938 "range": [[0, 0], [0, 25]],
939 }]
940 }]
941 });
942 run_v1_deserialization_test(json, &expected);
943 }
944
945 #[test]
946 fn test_v1_to_v2_deserialization_no_leading_comment() {
947 let expected = ModuleInfo {
948 is_script: false,
949 dependencies: vec![DependencyDescriptor::Static(
950 StaticDependencyDescriptor {
951 kind: StaticDependencyKind::Import,
952 specifier: "./a.js".to_string(),
953 specifier_range: PositionRange {
954 start: Position {
955 line: 1,
956 character: 2,
957 },
958 end: Position {
959 line: 3,
960 character: 4,
961 },
962 },
963 types_specifier: None,
964 import_attributes: ImportAttributes::None,
965 },
966 )],
967 ts_references: Vec::new(),
968 self_types_specifier: None,
969 jsx_import_source: None,
970 jsx_import_source_types: None,
971 jsdoc_imports: Vec::new(),
972 };
973 let json = json!({
974 "dependencies": [{
975 "type": "static",
976 "kind": "import",
977 "specifier": "./a.js",
978 "specifierRange": [[1, 2], [3, 4]],
979 }]
980 });
981 run_v1_deserialization_test(json, &expected);
982 }
983
984 #[track_caller]
985 fn run_serialization_test<
986 T: DeserializeOwned + Serialize + std::fmt::Debug + PartialEq + Eq,
987 >(
988 value: &T,
989 expected_json: serde_json::Value,
990 ) {
991 let json = serde_json::to_value(value).unwrap();
992 assert_eq!(json, expected_json);
993 let deserialized_value = serde_json::from_value::<T>(json).unwrap();
994 assert_eq!(deserialized_value, *value);
995 }
996
997 #[track_caller]
998 fn run_v1_deserialization_test<
999 T: DeserializeOwned + Serialize + std::fmt::Debug + PartialEq + Eq,
1000 >(
1001 mut json: serde_json::Value,
1002 value: &T,
1003 ) {
1004 module_graph_1_to_2(&mut json);
1005 let deserialized_value = serde_json::from_value::<T>(json).unwrap();
1006 assert_eq!(deserialized_value, *value);
1007 }
1008}