diff options
Diffstat (limited to 'cli')
-rw-r--r-- | cli/Cargo.toml | 1 | ||||
-rw-r--r-- | cli/swc_util.rs | 155 |
2 files changed, 156 insertions, 0 deletions
diff --git a/cli/Cargo.toml b/cli/Cargo.toml index ed3941f3c..7cc3c62d8 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -64,6 +64,7 @@ walkdir = "2.3.1" warp = "0.2.2" semver-parser = "0.9.0" uuid = { version = "0.8.1", features = ["v4"] } +swc_ecma_visit = { version = "=0.1.0" } [target.'cfg(windows)'.dependencies] winapi = "0.3.8" diff --git a/cli/swc_util.rs b/cli/swc_util.rs index 2086a5ded..a2e5d50fd 100644 --- a/cli/swc_util.rs +++ b/cli/swc_util.rs @@ -18,6 +18,8 @@ use crate::swc_ecma_parser::Session; use crate::swc_ecma_parser::SourceFileInput; use crate::swc_ecma_parser::Syntax; use crate::swc_ecma_parser::TsConfig; +use swc_ecma_visit::Node; +use swc_ecma_visit::Visit; use std::error::Error; use std::fmt; @@ -162,3 +164,156 @@ impl AstParser { .unwrap_or_else(|| vec![]) } } + +struct DependencyVisitor { + dependencies: Vec<String>, + analyze_dynamic_imports: bool, +} + +impl Visit for DependencyVisitor { + fn visit_import_decl( + &mut self, + import_decl: &swc_ecma_ast::ImportDecl, + _parent: &dyn Node, + ) { + let src_str = import_decl.src.value.to_string(); + self.dependencies.push(src_str); + } + + fn visit_named_export( + &mut self, + named_export: &swc_ecma_ast::NamedExport, + _parent: &dyn Node, + ) { + if let Some(src) = &named_export.src { + let src_str = src.value.to_string(); + self.dependencies.push(src_str); + } + } + + fn visit_export_all( + &mut self, + export_all: &swc_ecma_ast::ExportAll, + _parent: &dyn Node, + ) { + let src_str = export_all.src.value.to_string(); + self.dependencies.push(src_str); + } + + fn visit_call_expr( + &mut self, + call_expr: &swc_ecma_ast::CallExpr, + _parent: &dyn Node, + ) { + if !self.analyze_dynamic_imports { + return; + } + + use swc_ecma_ast::Expr::*; + use swc_ecma_ast::ExprOrSuper::*; + + let boxed_expr = match call_expr.callee.clone() { + Super(_) => return, + Expr(boxed) => boxed, + }; + + match &*boxed_expr { + Ident(ident) => { + if &ident.sym.to_string() != "import" { + return; + } + } + _ => return, + }; + + if let Some(arg) = call_expr.args.get(0) { + match &*arg.expr { + Lit(lit) => { + if let swc_ecma_ast::Lit::Str(str_) = lit { + let src_str = str_.value.to_string(); + self.dependencies.push(src_str); + } + } + _ => return, + } + } + } +} + +/// Given file name and source code return vector +/// of unresolved import specifiers. +/// +/// Returned vector may contain duplicate entries. +/// +/// Second argument allows to configure if dynamic +/// imports should be analyzed. +/// +/// NOTE: Only statically analyzable dynamic imports +/// are considered; ie. the ones that have plain string specifier: +/// +/// await import("./fizz.ts") +/// +/// These imports will be ignored: +/// +/// await import(`./${dir}/fizz.ts`) +/// await import("./" + "fizz.ts") +#[allow(unused)] +pub fn analyze_dependencies( + source_code: &str, + analyze_dynamic_imports: bool, +) -> Result<Vec<String>, SwcDiagnosticBuffer> { + let parser = AstParser::new(); + parser.parse_module("root.ts", source_code, |parse_result| { + let module = parse_result?; + let mut collector = DependencyVisitor { + dependencies: vec![], + analyze_dynamic_imports, + }; + collector.visit_module(&module, &module); + Ok(collector.dependencies) + }) +} + +#[test] +fn test_analyze_dependencies() { + let source = r#" +import { foo } from "./foo.ts"; +export { bar } from "./foo.ts"; +export * from "./bar.ts"; +"#; + + let dependencies = + analyze_dependencies(source, false).expect("Failed to parse"); + assert_eq!( + dependencies, + vec![ + "./foo.ts".to_string(), + "./foo.ts".to_string(), + "./bar.ts".to_string(), + ] + ); +} + +#[test] +fn test_analyze_dependencies_dyn_imports() { + let source = r#" +import { foo } from "./foo.ts"; +export { bar } from "./foo.ts"; +export * from "./bar.ts"; + +const a = await import("./fizz.ts"); +const a = await import("./" + "buzz.ts"); +"#; + + let dependencies = + analyze_dependencies(source, true).expect("Failed to parse"); + assert_eq!( + dependencies, + vec![ + "./foo.ts".to_string(), + "./foo.ts".to_string(), + "./bar.ts".to_string(), + "./fizz.ts".to_string(), + ] + ); +} |