summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock38
-rw-r--r--cli/Cargo.toml1
-rw-r--r--cli/swc_util.rs155
3 files changed, 194 insertions, 0 deletions
diff --git a/Cargo.lock b/Cargo.lock
index dd58066c5..dbf49bd03 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,16 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
+name = "Inflector"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
+dependencies = [
+ "lazy_static",
+ "regex",
+]
+
+[[package]]
name = "adler32"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -493,6 +503,7 @@ dependencies = [
"serde_derive",
"serde_json",
"sourcemap",
+ "swc_ecma_visit",
"sys-info",
"tempfile",
"termcolor",
@@ -2464,6 +2475,33 @@ dependencies = [
]
[[package]]
+name = "swc_ecma_visit"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bdd4d87e6499ff8cc3b32981ab2a3917cea4002a0c4523868181f59d14f4638"
+dependencies = [
+ "num-bigint",
+ "swc_atoms",
+ "swc_common",
+ "swc_ecma_ast",
+ "swc_ecma_visit_macros",
+]
+
+[[package]]
+name = "swc_ecma_visit_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ceb3a3184ba505b3f94fa4132a72e754d361ad9913b7e0dc4f01ab38649a9d26"
+dependencies = [
+ "Inflector",
+ "pmutil",
+ "proc-macro2 1.0.10",
+ "quote 1.0.3",
+ "swc_macros_common",
+ "syn 1.0.17",
+]
+
+[[package]]
name = "swc_macros_common"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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(),
+ ]
+ );
+}