1use std::collections::HashMap;
2
3use swc_atoms::JsWord;
4use swc_common::{
5 comments::{Comment, Comments},
6 Span,
7};
8use swc_ecma_ast as ast;
9use swc_ecma_visit::{self, Visit, VisitWith};
10
11pub fn analyze_dependencies(
12 module: &ast::Module,
13 comments: &dyn Comments,
14) -> Vec<DependencyDescriptor> {
15 let mut v = DependencyCollector {
16 comments,
17 items: vec![],
18 is_top_level: true,
19 };
20 module.visit_with(&mut v);
21 v.items
22}
23
24#[derive(Clone, Debug, Eq, PartialEq)]
25pub enum DependencyKind {
26 Import,
27 ImportType,
28 ImportEquals,
29 Export,
30 ExportType,
31 ExportEquals,
32 Require,
33}
34
35#[derive(Clone, Debug, Eq, PartialEq)]
36pub enum ImportAssertion {
37 Unknown,
39 Known(String),
41}
42
43#[derive(Clone, Debug, Eq, PartialEq)]
44pub enum ImportAttributes {
45 None,
47 Unknown,
49 Known(HashMap<String, ImportAssertion>),
52}
53
54impl Default for ImportAttributes {
55 fn default() -> Self {
56 ImportAttributes::None
57 }
58}
59
60impl ImportAttributes {
61 pub fn get(&self, key: &str) -> Option<&String> {
62 match self {
63 ImportAttributes::Known(map) => match map.get(key) {
64 Some(ImportAssertion::Known(value)) => Some(value),
65 _ => None,
66 },
67 _ => None,
68 }
69 }
70}
71
72#[derive(Clone, Debug, Eq, PartialEq)]
73pub struct DependencyDescriptor {
74 pub kind: DependencyKind,
75 pub is_dynamic: bool,
77 pub leading_comments: Vec<Comment>,
80 pub span: Span,
82 pub specifier: JsWord,
84 pub specifier_span: Span,
86 pub import_attributes: ImportAttributes,
88}
89
90struct DependencyCollector<'a> {
91 comments: &'a dyn Comments,
92 pub items: Vec<DependencyDescriptor>,
93 is_top_level: bool,
96}
97
98impl<'a> DependencyCollector<'a> {
99 fn get_leading_comments(&self, span: Span) -> Vec<Comment> {
100 self.comments.get_leading(span.lo).unwrap_or_default()
101 }
102}
103
104impl<'a> Visit for DependencyCollector<'a> {
105 fn visit_import_decl(&mut self, node: &ast::ImportDecl) {
106 let specifier = node.src.value.clone();
107 let leading_comments = self.get_leading_comments(node.span);
108 let kind = if node.type_only {
109 DependencyKind::ImportType
110 } else {
111 DependencyKind::Import
112 };
113 let import_attributes = parse_import_attributes(node.with.as_deref());
114 self.items.push(DependencyDescriptor {
115 kind,
116 is_dynamic: false,
117 leading_comments,
118 span: node.span,
119 specifier,
120 specifier_span: node.src.span,
121 import_attributes,
122 });
123 }
124
125 fn visit_named_export(&mut self, node: &ast::NamedExport) {
126 if let Some(src) = &node.src {
127 let specifier = src.value.clone();
128 let leading_comments = self.get_leading_comments(node.span);
129 let kind = if node.type_only {
130 DependencyKind::ExportType
131 } else {
132 DependencyKind::Export
133 };
134 let import_attributes = parse_import_attributes(node.with.as_deref());
135 self.items.push(DependencyDescriptor {
136 kind,
137 is_dynamic: false,
138 leading_comments,
139 span: node.span,
140 specifier,
141 specifier_span: src.span,
142 import_attributes,
143 });
144 }
145 }
146
147 fn visit_export_all(&mut self, node: &ast::ExportAll) {
148 let specifier = node.src.value.clone();
149 let leading_comments = self.get_leading_comments(node.span);
150 let kind = if node.type_only {
151 DependencyKind::ExportType
152 } else {
153 DependencyKind::Export
154 };
155 let import_attributes = parse_import_attributes(node.with.as_deref());
156 self.items.push(DependencyDescriptor {
157 kind,
158 is_dynamic: false,
159 leading_comments,
160 span: node.span,
161 specifier,
162 specifier_span: node.src.span,
163 import_attributes,
164 });
165 }
166
167 fn visit_ts_import_type(&mut self, node: &ast::TsImportType) {
168 let specifier = node.arg.value.clone();
169 let span = node.span;
170 let leading_comments = self.get_leading_comments(span);
171 self.items.push(DependencyDescriptor {
172 kind: DependencyKind::ImportType,
173 is_dynamic: false,
174 leading_comments,
175 span: node.span,
176 specifier,
177 specifier_span: node.arg.span,
178 import_attributes: Default::default(),
179 });
180 node.visit_children_with(self);
181 }
182
183 fn visit_module_items(&mut self, items: &[ast::ModuleItem]) {
184 swc_ecma_visit::visit_module_items(self, items);
185 }
186
187 fn visit_stmts(&mut self, items: &[ast::Stmt]) {
188 self.is_top_level = false;
189 swc_ecma_visit::visit_stmts(self, items);
190 self.is_top_level = true;
191 }
192
193 fn visit_call_expr(&mut self, node: &ast::CallExpr) {
194 use ast::{Callee, Expr, Ident, MemberProp};
195
196 swc_ecma_visit::visit_call_expr(self, node);
197 let kind = match &node.callee {
198 Callee::Super(_) => return,
199 Callee::Import(_) => DependencyKind::Import,
200 Callee::Expr(expr) => match &**expr {
201 Expr::Ident(Ident { sym: require, .. }) if &**require == "require" => {
202 DependencyKind::Require
203 }
204 Expr::Member(member) => match (&*member.obj, &member.prop) {
205 (
206 Expr::Ident(Ident { sym: obj_sym, .. }),
207 MemberProp::Ident(Ident { sym: prop_sym, .. }),
208 ) if obj_sym == "require" && prop_sym == "resolve" => DependencyKind::Require,
209 _ => return,
210 },
211 _ => return,
212 },
213 };
214
215 if let Some(arg) = node.args.first() {
216 if let Expr::Lit(ast::Lit::Str(str_)) = &*arg.expr {
217 let is_dynamic = !self.is_top_level || kind == DependencyKind::Import;
219 let dynamic_import_assertions = if kind == DependencyKind::Import {
220 parse_dynamic_import_assertions(node.args.get(1))
221 } else {
222 Default::default()
223 };
224 let specifier = str_.value.clone();
225 let leading_comments = self.get_leading_comments(node.span);
226 self.items.push(DependencyDescriptor {
227 kind,
228 is_dynamic,
229 leading_comments,
230 span: node.span,
231 specifier,
232 specifier_span: str_.span,
233 import_attributes: dynamic_import_assertions,
234 });
235 }
236 }
237 }
238
239 fn visit_ts_import_equals_decl(&mut self, node: &ast::TsImportEqualsDecl) {
240 use ast::TsModuleRef;
241
242 if let TsModuleRef::TsExternalModuleRef(module) = &node.module_ref {
243 let leading_comments = self.get_leading_comments(node.span);
244 let expr = &module.expr;
245 let specifier = expr.value.clone();
246
247 let kind = if node.is_type_only {
248 DependencyKind::ImportType
249 } else if node.is_export {
250 DependencyKind::ExportEquals
251 } else {
252 DependencyKind::ImportEquals
253 };
254
255 self.items.push(DependencyDescriptor {
256 kind,
257 is_dynamic: false,
258 leading_comments,
259 span: node.span,
260 specifier,
261 specifier_span: expr.span,
262 import_attributes: Default::default(),
263 });
264 }
265 }
266}
267
268fn parse_import_attributes(attrs: Option<&ast::ObjectLit>) -> ImportAttributes {
272 let attrs = match attrs {
273 Some(with) => with,
274 None => return ImportAttributes::None,
275 };
276 let mut import_assertions = HashMap::new();
277 for prop in attrs.props.iter() {
278 if let ast::PropOrSpread::Prop(prop) = prop {
279 if let ast::Prop::KeyValue(key_value) = &**prop {
280 let maybe_key = match &key_value.key {
281 ast::PropName::Str(key) => Some(key.value.to_string()),
282 ast::PropName::Ident(ident) => Some(ident.sym.to_string()),
283 _ => None,
284 };
285
286 if let Some(key) = maybe_key {
287 if let ast::Expr::Lit(ast::Lit::Str(str_)) = &*key_value.value {
288 import_assertions
289 .insert(key, ImportAssertion::Known(str_.value.to_string()));
290 }
291 }
292 }
293 }
294 }
295 ImportAttributes::Known(import_assertions)
296}
297
298fn parse_dynamic_import_assertions(arg: Option<&ast::ExprOrSpread>) -> ImportAttributes {
300 let arg = match arg {
301 Some(arg) => arg,
302 None => return ImportAttributes::None,
303 };
304
305 if arg.spread.is_some() {
306 return ImportAttributes::Unknown;
307 }
308
309 let object_lit = match &*arg.expr {
310 ast::Expr::Object(object_lit) => object_lit,
311 _ => return ImportAttributes::Unknown,
312 };
313
314 let mut assertions_map = HashMap::new();
315 let mut had_assert_key = false;
316
317 for prop in object_lit.props.iter() {
318 let prop = match prop {
319 ast::PropOrSpread::Prop(prop) => prop,
320 _ => return ImportAttributes::Unknown,
321 };
322 let key_value = match &**prop {
323 ast::Prop::KeyValue(key_value) => key_value,
324 _ => return ImportAttributes::Unknown,
325 };
326 let key = match &key_value.key {
327 ast::PropName::Str(key) => key.value.to_string(),
328 ast::PropName::Ident(ident) => ident.sym.to_string(),
329 _ => return ImportAttributes::Unknown,
330 };
331 if key == "assert" || key == "with" {
332 had_assert_key = true;
333 let assertions_lit = match &*key_value.value {
334 ast::Expr::Object(assertions_lit) => assertions_lit,
335 _ => return ImportAttributes::Unknown,
336 };
337
338 for prop in assertions_lit.props.iter() {
339 let prop = match prop {
340 ast::PropOrSpread::Prop(prop) => prop,
341 _ => return ImportAttributes::Unknown,
342 };
343 let key_value = match &**prop {
344 ast::Prop::KeyValue(key_value) => key_value,
345 _ => return ImportAttributes::Unknown,
346 };
347 let key = match &key_value.key {
348 ast::PropName::Str(key) => key.value.to_string(),
349 ast::PropName::Ident(ident) => ident.sym.to_string(),
350 _ => return ImportAttributes::Unknown,
351 };
352 if let ast::Expr::Lit(value_lit) = &*key_value.value {
353 assertions_map.insert(
354 key,
355 if let ast::Lit::Str(str_) = value_lit {
356 ImportAssertion::Known(str_.value.to_string())
357 } else {
358 ImportAssertion::Unknown
359 },
360 );
361 } else {
362 assertions_map.insert(key, ImportAssertion::Unknown);
363 }
364 }
365 }
366 }
367
368 if had_assert_key {
369 ImportAttributes::Known(assertions_map)
370 } else {
371 ImportAttributes::None
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use ast::EsVersion;
378 use pretty_assertions::assert_eq;
379 use swc_common::{
380 comments::{Comment, CommentKind, SingleThreadedComments},
381 BytePos, FileName, Span, SyntaxContext,
382 };
383 use swc_ecma_parser::{lexer::Lexer, Parser, Syntax, TsConfig};
384
385 use super::*;
386
387 fn helper(
388 file_name: &str,
389 source: &str,
390 ) -> Result<(ast::Module, SingleThreadedComments), testing::StdErr> {
391 ::testing::run_test(false, |cm, handler| {
392 let fm =
393 cm.new_source_file(FileName::Custom(file_name.to_string()), source.to_string());
394
395 let comments = SingleThreadedComments::default();
396 let lexer: Lexer = Lexer::new(
397 Syntax::Typescript(TsConfig {
398 dts: file_name.ends_with(".d.ts"),
399 tsx: file_name.contains("tsx"),
400 decorators: true,
401 no_early_errors: true,
402 disallow_ambiguous_jsx_like: false,
403 }),
404 EsVersion::Es2015,
405 (&*fm).into(),
406 Some(&comments),
407 );
408
409 let mut p = Parser::new_from(lexer);
410
411 let res = p
412 .parse_module()
413 .map_err(|e| e.into_diagnostic(handler).emit());
414
415 for err in p.take_errors() {
416 err.into_diagnostic(handler).emit();
417 }
418
419 if handler.has_errors() {
420 return Err(());
421 }
422
423 Ok((res.unwrap(), comments))
424 })
425 }
426
427 #[test]
428 fn test_parsed_module_get_dependencies() {
429 let source = r#"import * as bar from "./test.ts";
430/** JSDoc */
431import type { Foo } from "./foo.d.ts";
432/// <reference foo="bar" />
433export * as Buzz from "./buzz.ts";
434// @some-pragma
435/**
436 * Foo
437 */
438export type { Fizz } from "./fizz.d.ts";
439const { join } = require("path");
440
441// dynamic
442await import("./foo1.ts");
443
444try {
445 const foo = await import("./foo.ts");
446} catch (e) {
447 // pass
448}
449
450try {
451 const foo = require("some_package");
452} catch (e) {
453 // pass
454}
455
456import foo2 = require("some_package_foo");
457import type FooType = require('some_package_foo_type');
458export import bar2 = require("some_package_bar");
459
460const foo3 = require.resolve("some_package_resolve");
461try {
462 const foo4 = require.resolve("some_package_resolve_foo");
463} catch (e) {
464 // pass
465}
466 "#;
467 let (module, comments) = helper("test.ts", source).unwrap();
468 let dependencies = analyze_dependencies(&module, &comments);
469 assert_eq!(dependencies.len(), 13);
470 assert_eq!(
471 dependencies,
472 vec![
473 DependencyDescriptor {
474 kind: DependencyKind::Import,
475 is_dynamic: false,
476 leading_comments: Vec::new(),
477 span: Span::new(BytePos(1), BytePos(34), Default::default()),
478 specifier: JsWord::from("./test.ts"),
479 specifier_span: Span::new(BytePos(22), BytePos(33), Default::default()),
480 import_attributes: Default::default(),
481 },
482 DependencyDescriptor {
483 kind: DependencyKind::ImportType,
484 is_dynamic: false,
485 leading_comments: vec![Comment {
486 kind: CommentKind::Block,
487 text: r#"* JSDoc "#.into(),
488 span: Span::new(BytePos(35), BytePos(47), SyntaxContext::empty()),
489 }],
490 span: Span::new(BytePos(48), BytePos(86), Default::default()),
491 specifier: JsWord::from("./foo.d.ts"),
492 specifier_span: Span::new(BytePos(73), BytePos(85), Default::default()),
493 import_attributes: Default::default(),
494 },
495 DependencyDescriptor {
496 kind: DependencyKind::Export,
497 is_dynamic: false,
498 leading_comments: vec![Comment {
499 kind: CommentKind::Line,
500 text: r#"/ <reference foo="bar" />"#.into(),
501 span: Span::new(BytePos(87), BytePos(114), SyntaxContext::empty()),
502 }],
503 span: Span::new(BytePos(115), BytePos(149), Default::default()),
504 specifier: JsWord::from("./buzz.ts"),
505 specifier_span: Span::new(BytePos(137), BytePos(148), Default::default()),
506 import_attributes: Default::default(),
507 },
508 DependencyDescriptor {
509 kind: DependencyKind::ExportType,
510 is_dynamic: false,
511 leading_comments: vec![
512 Comment {
513 kind: CommentKind::Line,
514 text: r#" @some-pragma"#.into(),
515 span: Span::new(BytePos(150), BytePos(165), SyntaxContext::empty()),
516 },
517 Comment {
518 kind: CommentKind::Block,
519 text: "*\n * Foo\n ".into(),
520 span: Span::new(BytePos(166), BytePos(180), SyntaxContext::empty()),
521 }
522 ],
523 span: Span::new(BytePos(181), BytePos(221), Default::default()),
524 specifier: JsWord::from("./fizz.d.ts"),
525 specifier_span: Span::new(BytePos(207), BytePos(220), Default::default()),
526 import_attributes: Default::default(),
527 },
528 DependencyDescriptor {
529 kind: DependencyKind::Require,
530 is_dynamic: false,
531 leading_comments: Vec::new(),
532 span: Span::new(BytePos(239), BytePos(254), Default::default()),
533 specifier: JsWord::from("path"),
534 specifier_span: Span::new(BytePos(247), BytePos(253), Default::default()),
535 import_attributes: Default::default(),
536 },
537 DependencyDescriptor {
538 kind: DependencyKind::Import,
539 is_dynamic: true,
540 leading_comments: Vec::new(),
541 span: Span::new(BytePos(274), BytePos(293), Default::default()),
542 specifier: JsWord::from("./foo1.ts"),
543 specifier_span: Span::new(BytePos(281), BytePos(292), Default::default()),
544 import_attributes: Default::default(),
545 },
546 DependencyDescriptor {
547 kind: DependencyKind::Import,
548 is_dynamic: true,
549 leading_comments: Vec::new(),
550 span: Span::new(BytePos(324), BytePos(342), Default::default()),
551 specifier: JsWord::from("./foo.ts"),
552 specifier_span: Span::new(BytePos(331), BytePos(341), Default::default()),
553 import_attributes: Default::default(),
554 },
555 DependencyDescriptor {
556 kind: DependencyKind::Require,
557 is_dynamic: true,
558 leading_comments: Vec::new(),
559 span: Span::new(BytePos(395), BytePos(418), Default::default()),
560 specifier: JsWord::from("some_package"),
561 specifier_span: Span::new(BytePos(403), BytePos(417), Default::default()),
562 import_attributes: Default::default(),
563 },
564 DependencyDescriptor {
565 kind: DependencyKind::ImportEquals,
566 is_dynamic: false,
567 leading_comments: Vec::new(),
568 span: Span::new(BytePos(449), BytePos(491), Default::default()),
569 specifier: JsWord::from("some_package_foo"),
570 specifier_span: Span::new(BytePos(471), BytePos(489), Default::default()),
571 import_attributes: Default::default(),
572 },
573 DependencyDescriptor {
574 kind: DependencyKind::ImportType,
575 is_dynamic: false,
576 leading_comments: Vec::new(),
577 span: Span::new(BytePos(492), BytePos(547), Default::default()),
578 specifier: JsWord::from("some_package_foo_type"),
579 specifier_span: Span::new(BytePos(522), BytePos(545), Default::default()),
580 import_attributes: Default::default(),
581 },
582 DependencyDescriptor {
583 kind: DependencyKind::ExportEquals,
584 is_dynamic: false,
585 leading_comments: Vec::new(),
586 span: Span::new(BytePos(548), BytePos(597), Default::default()),
587 specifier: JsWord::from("some_package_bar"),
588 specifier_span: Span::new(BytePos(577), BytePos(595), Default::default()),
589 import_attributes: Default::default(),
590 },
591 DependencyDescriptor {
592 kind: DependencyKind::Require,
593 is_dynamic: false,
594 leading_comments: Vec::new(),
595 span: Span::new(BytePos(612), BytePos(651), Default::default()),
596 specifier: JsWord::from("some_package_resolve"),
597 specifier_span: Span::new(BytePos(628), BytePos(650), Default::default()),
598 import_attributes: Default::default(),
599 },
600 DependencyDescriptor {
601 kind: DependencyKind::Require,
602 is_dynamic: true,
603 leading_comments: Vec::new(),
604 span: Span::new(BytePos(676), BytePos(719), Default::default()),
605 specifier: JsWord::from("some_package_resolve_foo"),
606 specifier_span: Span::new(BytePos(692), BytePos(718), Default::default()),
607 import_attributes: Default::default(),
608 },
609 ]
610 );
611 }
612
613 #[test]
614 fn test_import_assertions() {
615 let source = r#"import * as bar from "./test.ts" assert { "type": "typescript" };
616export * from "./test.ts" assert { "type": "typescript" };
617export { bar } from "./test.json" assert { "type": "json" };
618import foo from "./foo.json" assert { type: "json" };
619const fizz = await import("./fizz.json", { "assert": { type: "json" } });
620const buzz = await import("./buzz.json", { assert: { "type": "json" } });
621const d1 = await import("./d1.json");
622const d2 = await import("./d2.json", {});
623const d3 = await import("./d3.json", bar);
624const d4 = await import("./d4.json", { assert: {} });
625const d5 = await import("./d5.json", { assert: bar });
626const d6 = await import("./d6.json", { assert: {}, ...bar });
627const d7 = await import("./d7.json", { assert: {}, ["assert"]: "bad" });
628const d8 = await import("./d8.json", { assert: { type: bar } });
629const d9 = await import("./d9.json", { assert: { type: "json", ...bar } });
630const d10 = await import("./d10.json", { assert: { type: "json", ["type"]: "bad" } });
631 "#;
632 let (module, comments) = helper("test.ts", source).unwrap();
633 let expected_assertions1 = ImportAttributes::Known({
634 let mut map = HashMap::new();
635 map.insert(
636 "type".to_string(),
637 ImportAssertion::Known("typescript".to_string()),
638 );
639 map
640 });
641 let expected_assertions2 = ImportAttributes::Known({
642 let mut map = HashMap::new();
643 map.insert(
644 "type".to_string(),
645 ImportAssertion::Known("json".to_string()),
646 );
647 map
648 });
649 let dynamic_expected_assertions2 = ImportAttributes::Known({
650 let mut map = HashMap::new();
651 map.insert(
652 "type".to_string(),
653 ImportAssertion::Known("json".to_string()),
654 );
655 map
656 });
657 let dependencies = analyze_dependencies(&module, &comments);
658 assert_eq!(dependencies.len(), 16);
659 assert_eq!(
660 dependencies,
661 vec![
662 DependencyDescriptor {
663 kind: DependencyKind::Import,
664 is_dynamic: false,
665 leading_comments: Vec::new(),
666 span: Span::new(BytePos(1), BytePos(66), Default::default()),
667 specifier: JsWord::from("./test.ts"),
668 specifier_span: Span::new(BytePos(22), BytePos(33), Default::default()),
669 import_attributes: expected_assertions1.clone(),
670 },
671 DependencyDescriptor {
672 kind: DependencyKind::Export,
673 is_dynamic: false,
674 leading_comments: Vec::new(),
675 span: Span::new(BytePos(67), BytePos(125), Default::default()),
676 specifier: JsWord::from("./test.ts"),
677 specifier_span: Span::new(BytePos(81), BytePos(92), Default::default()),
678 import_attributes: expected_assertions1,
679 },
680 DependencyDescriptor {
681 kind: DependencyKind::Export,
682 is_dynamic: false,
683 leading_comments: Vec::new(),
684 span: Span::new(BytePos(126), BytePos(186), Default::default()),
685 specifier: JsWord::from("./test.json"),
686 specifier_span: Span::new(BytePos(146), BytePos(159), Default::default()),
687 import_attributes: expected_assertions2.clone(),
688 },
689 DependencyDescriptor {
690 kind: DependencyKind::Import,
691 is_dynamic: false,
692 leading_comments: Vec::new(),
693 span: Span::new(BytePos(187), BytePos(240), Default::default()),
694 specifier: JsWord::from("./foo.json"),
695 specifier_span: Span::new(BytePos(203), BytePos(215), Default::default()),
696 import_attributes: expected_assertions2,
697 },
698 DependencyDescriptor {
699 kind: DependencyKind::Import,
700 is_dynamic: true,
701 leading_comments: Vec::new(),
702 span: Span::new(BytePos(260), BytePos(313), Default::default()),
703 specifier: JsWord::from("./fizz.json"),
704 specifier_span: Span::new(BytePos(267), BytePos(280), Default::default()),
705 import_attributes: dynamic_expected_assertions2.clone(),
706 },
707 DependencyDescriptor {
708 kind: DependencyKind::Import,
709 is_dynamic: true,
710 leading_comments: Vec::new(),
711 span: Span::new(BytePos(334), BytePos(387), Default::default()),
712 specifier: JsWord::from("./buzz.json"),
713 specifier_span: Span::new(BytePos(341), BytePos(354), Default::default()),
714 import_attributes: dynamic_expected_assertions2,
715 },
716 DependencyDescriptor {
717 kind: DependencyKind::Import,
718 is_dynamic: true,
719 leading_comments: Vec::new(),
720 span: Span::new(BytePos(406), BytePos(425), Default::default()),
721 specifier: JsWord::from("./d1.json"),
722 specifier_span: Span::new(BytePos(413), BytePos(424), Default::default()),
723 import_attributes: Default::default(),
724 },
725 DependencyDescriptor {
726 kind: DependencyKind::Import,
727 is_dynamic: true,
728 leading_comments: Vec::new(),
729 span: Span::new(BytePos(444), BytePos(467), Default::default()),
730 specifier: JsWord::from("./d2.json"),
731 specifier_span: Span::new(BytePos(451), BytePos(462), Default::default()),
732 import_attributes: Default::default(),
733 },
734 DependencyDescriptor {
735 kind: DependencyKind::Import,
736 is_dynamic: true,
737 leading_comments: Vec::new(),
738 span: Span::new(BytePos(486), BytePos(510), Default::default()),
739 specifier: JsWord::from("./d3.json"),
740 specifier_span: Span::new(BytePos(493), BytePos(504), Default::default()),
741 import_attributes: ImportAttributes::Unknown,
742 },
743 DependencyDescriptor {
744 kind: DependencyKind::Import,
745 is_dynamic: true,
746 leading_comments: Vec::new(),
747 span: Span::new(BytePos(529), BytePos(564), Default::default()),
748 specifier: JsWord::from("./d4.json"),
749 specifier_span: Span::new(BytePos(536), BytePos(547), Default::default()),
750 import_attributes: ImportAttributes::Known(HashMap::new()),
751 },
752 DependencyDescriptor {
753 kind: DependencyKind::Import,
754 is_dynamic: true,
755 leading_comments: Vec::new(),
756 span: Span::new(BytePos(583), BytePos(619), Default::default()),
757 specifier: JsWord::from("./d5.json"),
758 specifier_span: Span::new(BytePos(590), BytePos(601), Default::default()),
759 import_attributes: ImportAttributes::Unknown,
760 },
761 DependencyDescriptor {
762 kind: DependencyKind::Import,
763 is_dynamic: true,
764 leading_comments: Vec::new(),
765 span: Span::new(BytePos(638), BytePos(681), Default::default()),
766 specifier: JsWord::from("./d6.json"),
767 specifier_span: Span::new(BytePos(645), BytePos(656), Default::default()),
768 import_attributes: ImportAttributes::Unknown,
769 },
770 DependencyDescriptor {
771 kind: DependencyKind::Import,
772 is_dynamic: true,
773 leading_comments: Vec::new(),
774 span: Span::new(BytePos(700), BytePos(754), Default::default()),
775 specifier: JsWord::from("./d7.json"),
776 specifier_span: Span::new(BytePos(707), BytePos(718), Default::default()),
777 import_attributes: ImportAttributes::Unknown,
778 },
779 DependencyDescriptor {
780 kind: DependencyKind::Import,
781 is_dynamic: true,
782 leading_comments: Vec::new(),
783 span: Span::new(BytePos(773), BytePos(819), Default::default()),
784 specifier: JsWord::from("./d8.json"),
785 specifier_span: Span::new(BytePos(780), BytePos(791), Default::default()),
786 import_attributes: ImportAttributes::Known({
787 let mut map = HashMap::new();
788 map.insert("type".to_string(), ImportAssertion::Unknown);
789 map
790 }),
791 },
792 DependencyDescriptor {
793 kind: DependencyKind::Import,
794 is_dynamic: true,
795 leading_comments: Vec::new(),
796 span: Span::new(BytePos(838), BytePos(895), Default::default()),
797 specifier: JsWord::from("./d9.json"),
798 specifier_span: Span::new(BytePos(845), BytePos(856), Default::default()),
799 import_attributes: ImportAttributes::Unknown,
800 },
801 DependencyDescriptor {
802 kind: DependencyKind::Import,
803 is_dynamic: true,
804 leading_comments: Vec::new(),
805 span: Span::new(BytePos(915), BytePos(982), Default::default()),
806 specifier: JsWord::from("./d10.json"),
807 specifier_span: Span::new(BytePos(922), BytePos(934), Default::default()),
808 import_attributes: ImportAttributes::Unknown,
809 },
810 ]
811 );
812 }
813
814 #[test]
815 fn ts_import_object_lit_property() {
816 let source = r#"
817export declare const SomeValue: typeof Core & import("./a.d.ts").Constructor<{
818 paginate: import("./b.d.ts").PaginateInterface;
819} & import("./c.d.ts").RestEndpointMethods>;
820"#;
821 let (module, comments) = helper("test.ts", source).unwrap();
822 let dependencies = analyze_dependencies(&module, &comments);
823 assert_eq!(
824 dependencies,
825 vec![
826 DependencyDescriptor {
827 kind: DependencyKind::ImportType,
828 is_dynamic: false,
829 leading_comments: Vec::new(),
830 span: Span::new(BytePos(48), BytePos(176), Default::default()),
831 specifier: JsWord::from("./a.d.ts"),
832 specifier_span: Span::new(BytePos(55), BytePos(65), Default::default()),
833 import_attributes: ImportAttributes::None,
834 },
835 DependencyDescriptor {
836 kind: DependencyKind::ImportType,
837 is_dynamic: false,
838 leading_comments: Vec::new(),
839 span: Span::new(BytePos(95), BytePos(131), Default::default()),
840 specifier: JsWord::from("./b.d.ts"),
841 specifier_span: Span::new(BytePos(102), BytePos(112), Default::default()),
842 import_attributes: ImportAttributes::None,
843 },
844 DependencyDescriptor {
845 kind: DependencyKind::ImportType,
846 is_dynamic: false,
847 leading_comments: Vec::new(),
848 span: Span::new(BytePos(137), BytePos(175), Default::default()),
849 specifier: JsWord::from("./c.d.ts"),
850 specifier_span: Span::new(BytePos(144), BytePos(154), Default::default()),
851 import_attributes: ImportAttributes::None,
852 }
853 ]
854 );
855 }
856}