summaryrefslogtreecommitdiff
path: root/cli/info.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/info.rs')
-rw-r--r--cli/info.rs471
1 files changed, 471 insertions, 0 deletions
diff --git a/cli/info.rs b/cli/info.rs
new file mode 100644
index 000000000..c876c57d5
--- /dev/null
+++ b/cli/info.rs
@@ -0,0 +1,471 @@
+use crate::colors;
+use crate::global_state::GlobalState;
+use crate::module_graph::{ModuleGraph, ModuleGraphFile, ModuleGraphLoader};
+use crate::msg;
+use crate::ModuleSpecifier;
+use crate::Permissions;
+use deno_core::ErrBox;
+use serde::Serialize;
+use std::collections::{HashMap, HashSet};
+use std::sync::Arc;
+
+// TODO(bartlomieju): rename
+/// Struct containing a module's dependency information.
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ModuleDepInfo {
+ local: String,
+ file_type: String,
+ compiled: Option<String>,
+ map: Option<String>,
+ dep_count: usize,
+ deps: FileInfoDepTree,
+}
+
+impl ModuleDepInfo {
+ /// Creates a new `ModuleDepInfo` struct for the module with the provided `ModuleSpecifier`.
+ pub async fn new(
+ global_state: &Arc<GlobalState>,
+ module_specifier: ModuleSpecifier,
+ ) -> Result<ModuleDepInfo, ErrBox> {
+ // First load module as if it was to be executed by worker
+ // including compilation step
+ let mut module_graph_loader = ModuleGraphLoader::new(
+ global_state.file_fetcher.clone(),
+ global_state.maybe_import_map.clone(),
+ Permissions::allow_all(),
+ false,
+ true,
+ );
+ module_graph_loader
+ .add_to_graph(&module_specifier, None)
+ .await?;
+ let module_graph = module_graph_loader.get_graph();
+
+ let ts_compiler = &global_state.ts_compiler;
+ let file_fetcher = &global_state.file_fetcher;
+ let out = file_fetcher
+ .fetch_cached_source_file(&module_specifier, Permissions::allow_all())
+ .expect("Source file should already be cached");
+ let local_filename = out.filename.to_string_lossy().to_string();
+ let compiled_filename = ts_compiler
+ .get_compiled_source_file(&out.url)
+ .ok()
+ .map(|file| file.filename.to_string_lossy().to_string());
+ let map_filename = ts_compiler
+ .get_source_map_file(&module_specifier)
+ .ok()
+ .map(|file| file.filename.to_string_lossy().to_string());
+ let file_type = msg::enum_name_media_type(out.media_type).to_string();
+
+ let deps = FileInfoDepTree::new(&module_graph, &module_specifier);
+ let dep_count = get_unique_dep_count(&module_graph) - 1;
+
+ let info = Self {
+ local: local_filename,
+ file_type,
+ compiled: compiled_filename,
+ map: map_filename,
+ dep_count,
+ deps,
+ };
+
+ Ok(info)
+ }
+}
+
+/// Counts the number of dependencies in the graph.
+///
+/// We are counting only the dependencies that are not http redirects to other files.
+fn get_unique_dep_count(graph: &ModuleGraph) -> usize {
+ graph.iter().fold(
+ 0,
+ |acc, e| {
+ if e.1.redirect.is_none() {
+ acc + 1
+ } else {
+ acc
+ }
+ },
+ )
+}
+
+impl std::fmt::Display for ModuleDepInfo {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_fmt(format_args!("{} {}\n", colors::bold("local:"), self.local))?;
+ f.write_fmt(format_args!(
+ "{} {}\n",
+ colors::bold("type:"),
+ self.file_type
+ ))?;
+ if let Some(ref compiled) = self.compiled {
+ f.write_fmt(format_args!(
+ "{} {}\n",
+ colors::bold("compiled:"),
+ compiled
+ ))?;
+ }
+ if let Some(ref map) = self.map {
+ f.write_fmt(format_args!("{} {}\n", colors::bold("map:"), map))?;
+ }
+
+ f.write_fmt(format_args!(
+ "{} {} unique {}\n",
+ colors::bold("deps:"),
+ self.dep_count,
+ colors::gray(&format!(
+ "(total {})",
+ human_size(self.deps.total_size.unwrap_or(0) as f64),
+ ))
+ ))?;
+ f.write_fmt(format_args!(
+ "{} {}\n",
+ self.deps.name,
+ colors::gray(&format!("({})", human_size(self.deps.size as f64)))
+ ))?;
+
+ for (idx, dep) in self.deps.deps.iter().enumerate() {
+ print_file_dep_info(&dep, "", idx == self.deps.deps.len() - 1, f)?;
+ }
+
+ Ok(())
+ }
+}
+
+/// A dependency tree of the basic module information.
+///
+/// Constructed from a `ModuleGraph` and `ModuleSpecifier` that
+/// acts as the root of the tree.
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+struct FileInfoDepTree {
+ name: String,
+ size: usize,
+ total_size: Option<usize>,
+ deps: Vec<FileInfoDepTree>,
+}
+
+impl FileInfoDepTree {
+ /// Create a `FileInfoDepTree` tree from a `ModuleGraph` and the root `ModuleSpecifier`.
+ pub fn new(
+ module_graph: &ModuleGraph,
+ root_specifier: &ModuleSpecifier,
+ ) -> Self {
+ let mut seen = HashSet::new();
+ let mut total_sizes = HashMap::new();
+
+ Self::visit_module(
+ &mut seen,
+ &mut total_sizes,
+ module_graph,
+ root_specifier,
+ )
+ }
+
+ /// Visit modules recursively.
+ ///
+ /// If currently visited module has not yet been seen it will be annotated with dependencies
+ /// and cumulative size of those deps.
+ fn visit_module(
+ seen: &mut HashSet<String>,
+ total_sizes: &mut HashMap<String, usize>,
+ graph: &ModuleGraph,
+ specifier: &ModuleSpecifier,
+ ) -> Self {
+ let name = specifier.to_string();
+ let never_seen = seen.insert(name.clone());
+ let file = get_resolved_file(&graph, &specifier);
+ let size = file.size();
+ let mut deps = vec![];
+ let mut total_size = None;
+
+ if never_seen {
+ let mut seen_deps = HashSet::new();
+ deps = file
+ .imports
+ .iter()
+ .map(|import| &import.resolved_specifier)
+ .filter(|module_specifier| {
+ seen_deps.insert(module_specifier.as_str().to_string())
+ })
+ .map(|specifier| {
+ Self::visit_module(seen, total_sizes, graph, specifier)
+ })
+ .collect::<Vec<_>>();
+
+ total_size = if let Some(total_size) = total_sizes.get(&name) {
+ Some(total_size.to_owned())
+ } else {
+ let total: usize = deps
+ .iter()
+ .map(|dep| {
+ if let Some(total_size) = dep.total_size {
+ total_size
+ } else {
+ 0
+ }
+ })
+ .sum();
+ let total = size + total;
+
+ total_sizes.insert(name.clone(), total);
+
+ Some(total)
+ };
+ }
+
+ Self {
+ name,
+ size,
+ total_size,
+ deps,
+ }
+ }
+}
+
+/// Returns a `ModuleGraphFile` associated to the provided `ModuleSpecifier`.
+///
+/// If the `specifier` is associated with a file that has a populated redirect field,
+/// it returns the file associated to the redirect, otherwise the file associated to `specifier`.
+fn get_resolved_file<'a>(
+ graph: &'a ModuleGraph,
+ specifier: &ModuleSpecifier,
+) -> &'a ModuleGraphFile {
+ // Note(kc): This code is dependent on how we are injecting a dummy ModuleGraphFile
+ // into the graph with a "redirect" property.
+ let result = graph.get(specifier.as_str()).unwrap();
+
+ if let Some(ref import) = result.redirect {
+ graph.get(import).unwrap()
+ } else {
+ result
+ }
+}
+
+/// Prints the `FileInfoDepTree` tree to stdout.
+fn print_file_dep_info(
+ info: &FileInfoDepTree,
+ prefix: &str,
+ is_last: bool,
+ formatter: &mut std::fmt::Formatter<'_>,
+) -> std::fmt::Result {
+ print_dep(prefix, is_last, info, formatter)?;
+
+ let prefix = &get_new_prefix(prefix, is_last);
+ let child_count = info.deps.len();
+ for (idx, dep) in info.deps.iter().enumerate() {
+ print_file_dep_info(dep, prefix, idx == child_count - 1, formatter)?;
+ }
+
+ Ok(())
+}
+
+/// Prints a single `FileInfoDepTree` to stdout.
+fn print_dep(
+ prefix: &str,
+ is_last: bool,
+ info: &FileInfoDepTree,
+ formatter: &mut std::fmt::Formatter<'_>,
+) -> std::fmt::Result {
+ let has_children = !info.deps.is_empty();
+
+ formatter.write_fmt(format_args!(
+ "{} {}{}\n",
+ colors::gray(&format!(
+ "{}{}─{}",
+ prefix,
+ get_sibling_connector(is_last),
+ get_child_connector(has_children),
+ ))
+ .to_string(),
+ info.name,
+ get_formatted_totals(info)
+ ))
+}
+
+/// Gets the formatted totals for the provided `FileInfoDepTree`.
+///
+/// If the total size is reported as 0 then an empty string is returned.
+fn get_formatted_totals(info: &FileInfoDepTree) -> String {
+ if let Some(_total_size) = info.total_size {
+ colors::gray(&format!(" ({})", human_size(info.size as f64),)).to_string()
+ } else {
+ // This dependency has already been displayed somewhere else in the tree.
+ colors::gray(" *").to_string()
+ }
+}
+
+/// Gets the sibling portion of the tree branch.
+fn get_sibling_connector(is_last: bool) -> char {
+ if is_last {
+ '└'
+ } else {
+ '├'
+ }
+}
+
+/// Gets the child connector for the branch.
+fn get_child_connector(has_children: bool) -> char {
+ if has_children {
+ '┬'
+ } else {
+ '─'
+ }
+}
+
+/// Creates a new prefix for a dependency tree item.
+fn get_new_prefix(prefix: &str, is_last: bool) -> String {
+ let mut prefix = prefix.to_string();
+ if is_last {
+ prefix.push(' ');
+ } else {
+ prefix.push('│');
+ }
+
+ prefix.push(' ');
+ prefix
+}
+
+pub fn human_size(bytse: f64) -> String {
+ let negative = if bytse.is_sign_positive() { "" } else { "-" };
+ let bytse = bytse.abs();
+ let units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
+ if bytse < 1_f64 {
+ return format!("{}{}{}", negative, bytse, "B");
+ }
+ let delimiter = 1024_f64;
+ let exponent = std::cmp::min(
+ (bytse.ln() / delimiter.ln()).floor() as i32,
+ (units.len() - 1) as i32,
+ );
+ let pretty_bytes = format!("{:.2}", bytse / delimiter.powi(exponent))
+ .parse::<f64>()
+ .unwrap()
+ * 1_f64;
+ let unit = units[exponent as usize];
+ format!("{}{}{}", negative, pretty_bytes, unit)
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::module_graph::ImportDescriptor;
+ use crate::swc_util::Location;
+ use crate::MediaType;
+
+ #[test]
+ fn human_size_test() {
+ assert_eq!(human_size(16_f64), "16B");
+ assert_eq!(human_size((16 * 1024) as f64), "16KB");
+ assert_eq!(human_size((16 * 1024 * 1024) as f64), "16MB");
+ assert_eq!(human_size(16_f64 * 1024_f64.powf(3.0)), "16GB");
+ assert_eq!(human_size(16_f64 * 1024_f64.powf(4.0)), "16TB");
+ assert_eq!(human_size(16_f64 * 1024_f64.powf(5.0)), "16PB");
+ assert_eq!(human_size(16_f64 * 1024_f64.powf(6.0)), "16EB");
+ assert_eq!(human_size(16_f64 * 1024_f64.powf(7.0)), "16ZB");
+ assert_eq!(human_size(16_f64 * 1024_f64.powf(8.0)), "16YB");
+ }
+
+ #[test]
+ fn get_new_prefix_adds_spaces_if_is_last() {
+ let prefix = get_new_prefix("", true);
+
+ assert_eq!(prefix, " ".to_string());
+ }
+
+ #[test]
+ fn get_new_prefix_adds_a_vertial_bar_if_not_is_last() {
+ let prefix = get_new_prefix("", false);
+
+ assert_eq!(prefix, "│ ".to_string());
+ }
+
+ fn create_mock_file(
+ name: &str,
+ imports: Vec<ModuleSpecifier>,
+ redirect: Option<ModuleSpecifier>,
+ ) -> (ModuleGraphFile, ModuleSpecifier) {
+ let spec = ModuleSpecifier::from(
+ url::Url::parse(&format!("http://{}", name)).unwrap(),
+ );
+ let file = ModuleGraphFile {
+ filename: "name".to_string(),
+ imports: imports
+ .iter()
+ .map(|import| ImportDescriptor {
+ specifier: import.to_string(),
+ resolved_specifier: import.clone(),
+ resolved_type_directive: None,
+ type_directive: None,
+ location: Location {
+ col: 0,
+ filename: "".to_string(),
+ line: 0,
+ },
+ })
+ .collect(),
+ lib_directives: vec![],
+ media_type: MediaType::TypeScript,
+ redirect: redirect.map(|x| x.to_string()),
+ referenced_files: vec![],
+ source_code: "".to_string(),
+ specifier: spec.to_string(),
+ type_headers: vec![],
+ types_directives: vec![],
+ version_hash: "".to_string(),
+ url: "".to_string(),
+ };
+
+ (file, spec)
+ }
+
+ #[test]
+ fn get_resolved_file_test() {
+ let (test_file_redirect, redirect) =
+ create_mock_file("test_redirect", vec![], None);
+ let (test_file, original) =
+ create_mock_file("test", vec![], Some(redirect.clone()));
+
+ let mut graph = ModuleGraph::new();
+ graph.insert(original.to_string(), test_file);
+ graph.insert(redirect.to_string(), test_file_redirect);
+
+ let file = get_resolved_file(&graph, &original);
+
+ assert_eq!(file.specifier, redirect.to_string());
+ }
+
+ #[test]
+ fn dependency_count_no_redirects() {
+ let (a, aspec) = create_mock_file("a", vec![], None);
+ let (b, bspec) = create_mock_file("b", vec![aspec.clone()], None);
+ let (c, cspec) = create_mock_file("c", vec![bspec.clone()], None);
+
+ let mut graph = ModuleGraph::new();
+
+ graph.insert(aspec.to_string(), a);
+ graph.insert(bspec.to_string(), b);
+ graph.insert(cspec.to_string(), c);
+
+ let count = get_unique_dep_count(&graph);
+
+ assert_eq!(graph.len(), count);
+ }
+
+ #[test]
+ fn dependency_count_with_redirects() {
+ let (a, aspec) = create_mock_file("a", vec![], None);
+ let (b, bspec) = create_mock_file("b", vec![], Some(aspec.clone()));
+ let (c, cspec) = create_mock_file("c", vec![bspec.clone()], None);
+
+ let mut graph = ModuleGraph::new();
+
+ graph.insert(aspec.to_string(), a);
+ graph.insert(bspec.to_string(), b);
+ graph.insert(cspec.to_string(), c);
+
+ let count = get_unique_dep_count(&graph);
+
+ assert_eq!(graph.len() - 1, count);
+ }
+}