summaryrefslogtreecommitdiff
path: root/cli/swc_util.rs
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2020-04-30 20:18:50 +0200
committerGitHub <noreply@github.com>2020-04-30 20:18:50 +0200
commitf79cb08e0b9afa7609a353a464e0876d8c8a593c (patch)
tree6bdd82b47db4f5f228a8271c04099dbc6218a176 /cli/swc_util.rs
parent898773d3f89b12e84d371644d4f13dec078bdc13 (diff)
feat: add SWC dependency analyzer (#5015)
This commit adds "analyze_dependencies" function that uses SWC (by the means of AstParser) to perform analysis of static and dynamic imports.
Diffstat (limited to 'cli/swc_util.rs')
-rw-r--r--cli/swc_util.rs155
1 files changed, 155 insertions, 0 deletions
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(),
+ ]
+ );
+}