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.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# 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]]
|
[[package]]
|
||||||
name = "adler32"
|
name = "adler32"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -493,6 +503,7 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sourcemap",
|
"sourcemap",
|
||||||
|
"swc_ecma_visit",
|
||||||
"sys-info",
|
"sys-info",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
|
@ -2463,6 +2474,33 @@ dependencies = [
|
||||||
"syn 1.0.17",
|
"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]]
|
[[package]]
|
||||||
name = "swc_macros_common"
|
name = "swc_macros_common"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
|
@ -64,6 +64,7 @@ walkdir = "2.3.1"
|
||||||
warp = "0.2.2"
|
warp = "0.2.2"
|
||||||
semver-parser = "0.9.0"
|
semver-parser = "0.9.0"
|
||||||
uuid = { version = "0.8.1", features = ["v4"] }
|
uuid = { version = "0.8.1", features = ["v4"] }
|
||||||
|
swc_ecma_visit = { version = "=0.1.0" }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = "0.3.8"
|
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::SourceFileInput;
|
||||||
use crate::swc_ecma_parser::Syntax;
|
use crate::swc_ecma_parser::Syntax;
|
||||||
use crate::swc_ecma_parser::TsConfig;
|
use crate::swc_ecma_parser::TsConfig;
|
||||||
|
use swc_ecma_visit::Node;
|
||||||
|
use swc_ecma_visit::Visit;
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -162,3 +164,156 @@ impl AstParser {
|
||||||
.unwrap_or_else(|| vec![])
|
.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