1
0
Fork 0
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:
Bartek Iwańczuk 2020-04-30 20:18:50 +02:00 committed by GitHub
parent 898773d3f8
commit f79cb08e0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 194 additions and 0 deletions

38
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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(),
]
);
}