mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
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.
This commit is contained in:
parent
898773d3f8
commit
f79cb08e0b
3 changed files with 194 additions and 0 deletions
38
Cargo.lock
generated
38
Cargo.lock
generated
|
@ -1,5 +1,15 @@
|
|||
# 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"
|
||||
|
@ -493,6 +503,7 @@ dependencies = [
|
|||
"serde_derive",
|
||||
"serde_json",
|
||||
"sourcemap",
|
||||
"swc_ecma_visit",
|
||||
"sys-info",
|
||||
"tempfile",
|
||||
"termcolor",
|
||||
|
@ -2463,6 +2474,33 @@ dependencies = [
|
|||
"syn 1.0.17",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
|
|
@ -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"
|
||||
|
|
155
cli/swc_util.rs
155
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(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue