0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 09:31:22 -05:00

feat(info): Dependency count and sizes (#6786)

This commit changes "deno info" subcommand logic.

- Modules are no longer loaded into V8 isolate - analysis
  is done using ModuleGraph.
- Removed deno_core::Deps structure.
- Modules are no longer type-checked and transpiled - 
  "compiled" file is shown only if it is already available.
- Added number of unique dependencies for root module.
- Changed tree output:
  - file size is shown next to the dependency
  - repeated dependencies are marked with "*"
  - used less spaces in prefix to save terminal width
This commit is contained in:
KrisChambers 2020-09-07 09:59:47 -04:00 committed by GitHub
parent 7a8b27aa25
commit 1cd2267500
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 692 additions and 430 deletions

View file

@ -160,11 +160,11 @@ impl GlobalState {
if should_compile {
if self.flags.no_check {
self.ts_compiler.transpile(module_graph).await?;
self.ts_compiler.transpile(&module_graph).await?;
} else {
self
.ts_compiler
.compile(self, &out, target_lib, permissions, module_graph, allow_js)
.compile(self, &out, target_lib, permissions, &module_graph, allow_js)
.await?;
}
}

471
cli/info.rs Normal file
View file

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

View file

@ -40,6 +40,7 @@ mod global_timer;
pub mod http_cache;
mod http_util;
mod import_map;
mod info;
mod inspector;
pub mod installer;
mod js;
@ -76,12 +77,9 @@ use crate::fs as deno_fs;
use crate::global_state::GlobalState;
use crate::msg::MediaType;
use crate::permissions::Permissions;
use crate::tsc::TargetLib;
use crate::worker::MainWorker;
use deno_core::v8_set_flags;
use deno_core::Deps;
use deno_core::ErrBox;
use deno_core::JsRuntime;
use deno_core::ModuleSpecifier;
use deno_doc as doc;
use deno_doc::parser::DocFileLoader;
@ -151,114 +149,6 @@ fn print_cache_info(
}
}
struct FileInfoOutput<'a> {
local: &'a str,
file_type: &'a str,
compiled: Option<String>,
map: Option<String>,
deps: Option<Deps>,
}
// TODO(bartlomieju): this function de facto repeats
// whole compilation stack. Can this be done better somehow?
async fn print_file_info(
worker: &MainWorker,
module_specifier: ModuleSpecifier,
json: bool,
) -> Result<(), ErrBox> {
let global_state = worker.state.global_state.clone();
let out = global_state
.file_fetcher
.fetch_source_file(&module_specifier, None, Permissions::allow_all())
.await?;
let mut output = FileInfoOutput {
local: out.filename.to_str().unwrap(),
file_type: msg::enum_name_media_type(out.media_type),
compiled: None,
map: None,
deps: None,
};
let module_specifier_ = module_specifier.clone();
global_state
.prepare_module_load(
module_specifier_.clone(),
None,
TargetLib::Main,
Permissions::allow_all(),
false,
global_state.maybe_import_map.clone(),
)
.await?;
global_state
.clone()
.fetch_compiled_module(module_specifier_, None)
.await?;
if out.media_type == msg::MediaType::TypeScript
|| (out.media_type == msg::MediaType::JavaScript
&& global_state.ts_compiler.compile_js)
{
let compiled_source_file = global_state
.ts_compiler
.get_compiled_source_file(&out.url)
.unwrap();
output.compiled =
compiled_source_file.filename.to_str().map(|s| s.to_owned());
}
if let Ok(source_map) = global_state
.clone()
.ts_compiler
.get_source_map_file(&module_specifier)
{
output.map = source_map.filename.to_str().map(|s| s.to_owned());
}
let es_state_rc = JsRuntime::state(&worker.isolate);
let es_state = es_state_rc.borrow();
if let Some(deps) = es_state.modules.deps(&module_specifier) {
output.deps = Some(deps);
}
if json {
let output = json!({
"local": output.local,
"fileType": output.file_type,
"compiled": output.compiled,
"map": output.map,
"deps": output.deps.map(|x| x.to_json())
});
write_json_to_stdout(&output)
} else {
println!("{} {}", colors::bold("local:"), output.local);
println!("{} {}", colors::bold("type:"), output.file_type);
if let Some(compiled) = output.compiled {
println!("{} {}", colors::bold("compiled:"), compiled);
}
if let Some(map) = output.map {
println!("{} {}", colors::bold("map:"), map);
}
if let Some(deps) = output.deps {
println!("{}{}", colors::bold("deps:\n"), deps.name);
if let Some(ref depsdeps) = deps.deps {
for d in depsdeps {
println!("{}", d);
}
}
} else {
println!(
"{} cannot retrieve full dependency graph",
colors::bold("deps:"),
);
}
Ok(())
}
}
fn get_types(unstable: bool) -> String {
let mut types = format!(
"{}\n{}\n{}\n{}",
@ -289,9 +179,15 @@ async fn info_command(
print_cache_info(&global_state, json)
} else {
let main_module = ModuleSpecifier::resolve_url_or_path(&file.unwrap())?;
let mut worker = MainWorker::create(&global_state, main_module.clone())?;
worker.preload_module(&main_module).await?;
print_file_info(&worker, main_module.clone(), json).await
let info =
info::ModuleDepInfo::new(&global_state, main_module.clone()).await?;
if json {
write_json_to_stdout(&json!(info))
} else {
print!("{}", info);
Ok(())
}
}
}
@ -414,7 +310,7 @@ async fn bundle_command(
"{} {:?} ({})",
colors::green("Emit"),
out_file_,
colors::gray(&human_size(output_len as f64))
colors::gray(&info::human_size(output_len as f64))
);
} else {
println!("{}", output);
@ -422,39 +318,6 @@ async fn bundle_command(
Ok(())
}
fn human_size(bytse: f64) -> String {
let negative = if bytse.is_sign_positive() { "" } else { "-" };
let bytse = bytse.abs();
let units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
if bytse < 1_f64 {
return format!("{}{} {}", negative, bytse, "Bytes");
}
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)
}
#[test]
fn human_size_test() {
assert_eq!(human_size(16_f64), "16 Bytes");
assert_eq!(human_size((16 * 1024) as f64), "16 KB");
assert_eq!(human_size((16 * 1024 * 1024) as f64), "16 MB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(3.0)), "16 GB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(4.0)), "16 TB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(5.0)), "16 PB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(6.0)), "16 EB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(7.0)), "16 ZB");
assert_eq!(human_size(16_f64 * 1024_f64.powf(8.0)), "16 YB");
}
async fn doc_command(
flags: Flags,
source_file: Option<String>,

View file

@ -197,7 +197,7 @@ const SUPPORTED_MEDIA_TYPES: [MediaType; 4] = [
pub type ModuleGraph = HashMap<String, ModuleGraphFile>;
#[derive(Debug, Serialize)]
#[derive(Clone, Debug, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ImportDescriptor {
pub specifier: String,
@ -241,6 +241,12 @@ pub struct ModuleGraphFile {
pub source_code: String,
}
impl ModuleGraphFile {
pub fn size(&self) -> usize {
self.source_code.as_bytes().len()
}
}
type SourceFileFuture =
Pin<Box<dyn Future<Output = Result<(ModuleSpecifier, SourceFile), ErrBox>>>>;

View file

@ -1,14 +1,13 @@
[WILDCARD]
local: [WILDCARD]http[WILDCARD]127.0.0.1_PORT4545[WILDCARD]
type: TypeScript
compiled: [WILDCARD].js
deps:
http://127.0.0.1:4545/cli/tests/019_media_types.ts
├── http://localhost:4545/cli/tests/subdir/mt_text_typescript.t1.ts
├── http://localhost:4545/cli/tests/subdir/mt_video_vdn.t2.ts
├── http://localhost:4545/cli/tests/subdir/mt_video_mp2t.t3.ts
├── http://localhost:4545/cli/tests/subdir/mt_application_x_typescript.t4.ts
├── http://localhost:4545/cli/tests/subdir/mt_text_javascript.j1.js
├── http://localhost:4545/cli/tests/subdir/mt_application_ecmascript.j2.js
├── http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js
└── http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js
deps: 8 unique (total [WILDCARD])
http://127.0.0.1:4545/cli/tests/019_media_types.ts ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_text_typescript.t1.ts ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_video_vdn.t2.ts ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_video_mp2t.t3.ts ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_application_x_typescript.t4.ts ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_text_javascript.j1.js ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_application_ecmascript.j2.js ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_text_ecmascript.j3.js ([WILDCARD])
└── http://localhost:4545/cli/tests/subdir/mt_application_x_javascript.j4.js ([WILDCARD])

View file

@ -1,6 +1,5 @@
[WILDCARD]
local: [WILDCARD]031_info_no_check.ts
type: TypeScript
compiled: [WILDCARD].js
deps:
[WILDCARD]031_info_no_check.ts
deps: 0 unique (total [WILDCARD])
[WILDCARD]031_info_no_check.ts ([WILDCARD])

View file

@ -1,14 +1,13 @@
[WILDCARD]
local: [WILDCARD]http[WILDCARD]127.0.0.1_PORT4545[WILDCARD]
type: TypeScript
compiled: [WILDCARD].js
deps:
http://127.0.0.1:4545/cli/tests/048_media_types_jsx.ts
├── http://localhost:4545/cli/tests/subdir/mt_text_typescript_tsx.t1.tsx
├── http://localhost:4545/cli/tests/subdir/mt_video_vdn_tsx.t2.tsx
├── http://localhost:4545/cli/tests/subdir/mt_video_mp2t_tsx.t3.tsx
├── http://localhost:4545/cli/tests/subdir/mt_application_x_typescript_tsx.t4.tsx
├── http://localhost:4545/cli/tests/subdir/mt_text_javascript_jsx.j1.jsx
├── http://localhost:4545/cli/tests/subdir/mt_application_ecmascript_jsx.j2.jsx
├── http://localhost:4545/cli/tests/subdir/mt_text_ecmascript_jsx.j3.jsx
└── http://localhost:4545/cli/tests/subdir/mt_application_x_javascript_jsx.j4.jsx
deps: 8 unique (total [WILDCARD])
http://127.0.0.1:4545/cli/tests/048_media_types_jsx.ts ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_text_typescript_tsx.t1.tsx ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_video_vdn_tsx.t2.tsx ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_video_mp2t_tsx.t3.tsx ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_application_x_typescript_tsx.t4.tsx ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_text_javascript_jsx.j1.jsx ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_application_ecmascript_jsx.j2.jsx ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_text_ecmascript_jsx.j3.jsx ([WILDCARD])
└── http://localhost:4545/cli/tests/subdir/mt_application_x_javascript_jsx.j4.jsx ([WILDCARD])

View file

@ -1,8 +1,7 @@
local: [WILDCARD]005_more_imports.ts
type: TypeScript
compiled: [WILDCARD].js
deps:
file://[WILDCARD]/005_more_imports.ts
└─┬ file://[WILDCARD]/subdir/mod1.ts
└─┬ file://[WILDCARD]/subdir/subdir2/mod2.ts
└── file://[WILDCARD]/subdir/print_hello.ts
deps: 3 unique (total [WILDCARD])
file://[WILDCARD]/005_more_imports.ts ([WILDCARD])
└─┬ file://[WILDCARD]/subdir/mod1.ts ([WILDCARD])
└─┬ file://[WILDCARD]/subdir/subdir2/mod2.ts ([WILDCARD])
└── file://[WILDCARD]/subdir/print_hello.ts ([WILDCARD])

View file

@ -1,25 +1,34 @@
{
"local": "[WILDCARD]005_more_imports.ts",
"fileType": "TypeScript",
"compiled": "[WILDCARD].js",
"compiled": null,
"map": null,
"deps": [
"file://[WILDCARD]/005_more_imports.ts",
[
[
"file://[WILDCARD]/subdir/mod1.ts",
[
[
"file://[WILDCARD]/subdir/subdir2/mod2.ts",
[
[
"file://[WILDCARD]/subdir/print_hello.ts",
[]
]
"depCount": 3,
"deps": {
"name": "file://[WILDCARD]/005_more_imports.ts",
"size": 211,
"totalSize": 757,
"deps": [
{
"name": "file://[WILDCARD]/subdir/mod1.ts",
"size": 320,
"totalSize": 546,
"deps": [
{
"name": "file://[WILDCARD]/subdir/subdir2/mod2.ts",
"size": 163,
"totalSize": 226,
"deps": [
{
"name": "file://[WILDCARD]/subdir/print_hello.ts",
"size": 63,
"totalSize": 63,
"deps": []
}
]
]
}
]
]
}
]
]
}
}

View file

@ -1,13 +1,12 @@
local: [WILDCARD]https[WILDCARD]localhost_PORT5545[WILDCARD]
type: TypeScript
compiled: [WILDCARD].js
deps:
https://localhost:5545/cli/tests/cafile_info.ts
├── https://localhost:5545/cli/tests/subdir/mt_text_typescript.t1.ts
├── https://localhost:5545/cli/tests/subdir/mt_video_vdn.t2.ts
├── https://localhost:5545/cli/tests/subdir/mt_video_mp2t.t3.ts
├── https://localhost:5545/cli/tests/subdir/mt_application_x_typescript.t4.ts
├── https://localhost:5545/cli/tests/subdir/mt_text_javascript.j1.js
├── https://localhost:5545/cli/tests/subdir/mt_application_ecmascript.j2.js
├── https://localhost:5545/cli/tests/subdir/mt_text_ecmascript.j3.js
└── https://localhost:5545/cli/tests/subdir/mt_application_x_javascript.j4.js
deps: 8 unique (total [WILDCARD])
https://localhost:5545/cli/tests/cafile_info.ts ([WILDCARD])
├── https://localhost:5545/cli/tests/subdir/mt_text_typescript.t1.ts ([WILDCARD])
├── https://localhost:5545/cli/tests/subdir/mt_video_vdn.t2.ts ([WILDCARD])
├── https://localhost:5545/cli/tests/subdir/mt_video_mp2t.t3.ts ([WILDCARD])
├── https://localhost:5545/cli/tests/subdir/mt_application_x_typescript.t4.ts ([WILDCARD])
├── https://localhost:5545/cli/tests/subdir/mt_text_javascript.j1.js ([WILDCARD])
├── https://localhost:5545/cli/tests/subdir/mt_application_ecmascript.j2.js ([WILDCARD])
├── https://localhost:5545/cli/tests/subdir/mt_text_ecmascript.j3.js ([WILDCARD])
└── https://localhost:5545/cli/tests/subdir/mt_application_x_javascript.j4.js ([WILDCARD])

View file

@ -0,0 +1,11 @@
local: [WILDCARD]info_recursive_imports_test.ts
type: TypeScript
deps: 4 unique (total [WILDCARD])
file://[WILDCARD]cli/tests/info_recursive_imports_test.ts ([WILDCARD])
└─┬ file://[WILDCARD]cli/tests/recursive_imports/A.ts ([WILDCARD])
├─┬ file://[WILDCARD]cli/tests/recursive_imports/B.ts ([WILDCARD])
│ ├─┬ file://[WILDCARD]cli/tests/recursive_imports/C.ts ([WILDCARD])
│ │ ├── file://[WILDCARD]cli/tests/recursive_imports/A.ts *
│ │ └── file://[WILDCARD]cli/tests/recursive_imports/common.ts ([WILDCARD])
│ └── file://[WILDCARD]cli/tests/recursive_imports/common.ts *
└── file://[WILDCARD]cli/tests/recursive_imports/common.ts *

View file

@ -0,0 +1,5 @@
import { A } from "./recursive_imports/A.ts";
export function test(): void {
A();
}

View file

@ -1,7 +1,5 @@
Check [WILDCARD]info_type_import.ts
local: [WILDCARD]info_type_import.ts
type: TypeScript
compiled: [WILDCARD]
deps:
[WILDCARD]info_type_import.ts
└── [WILDCARD]type_and_code.ts
deps: 1 unique (total [WILDCARD])
[WILDCARD]info_type_import.ts ([WILDCARD])
└── [WILDCARD]type_and_code.ts ([WILDCARD])

View file

@ -949,6 +949,38 @@ fn bundle_import_map() {
assert_eq!(output.stderr, b"");
}
#[test]
fn info_with_compiled_source() {
let _g = util::http_server();
let module_path = "http://127.0.0.1:4545/cli/tests/048_media_types_jsx.ts";
let t = TempDir::new().expect("tempdir fail");
let mut deno = util::deno_cmd()
.env("DENO_DIR", t.path())
.current_dir(util::root_path())
.arg("cache")
.arg(&module_path)
.spawn()
.expect("failed to spawn script");
let status = deno.wait().expect("failed to wait for the child process");
assert!(status.success());
let output = util::deno_cmd()
.env("DENO_DIR", t.path())
.env("NO_COLOR", "1")
.current_dir(util::root_path())
.arg("info")
.arg(&module_path)
.output()
.expect("failed to spawn script");
let str_output = std::str::from_utf8(&output.stdout).unwrap().trim();
eprintln!("{}", str_output);
// check the output of the test.ts program.
assert!(str_output.contains("compiled: "));
assert_eq!(output.stderr, b"");
}
#[test]
fn repl_test_console_log() {
let (out, err) = util::run_and_collect_output(
@ -2289,6 +2321,12 @@ itest!(import_file_with_colon {
http_server: true,
});
itest!(info_recursive_modules {
args: "info --quiet info_recursive_imports_test.ts",
output: "info_recursive_imports_test.out",
exit_code: 0,
});
itest!(info_type_import {
args: "info info_type_import.ts",
output: "info_type_import.out",

View file

@ -0,0 +1,7 @@
import { B } from "./B.ts";
import { thing } from "./common.ts";
export function A(): void {
thing();
B();
}

View file

@ -0,0 +1,7 @@
import { C } from "./C.ts";
import { thing } from "./common.ts";
export function B(): void {
thing();
C();
}

View file

@ -0,0 +1,8 @@
import { A } from "./A.ts";
import { thing } from "./common.ts";
export function C(): void {
if (A != null) {
thing();
}
}

View file

@ -0,0 +1,2 @@
export function thing(): void {
}

View file

@ -570,7 +570,7 @@ impl TsCompiler {
source_file: &SourceFile,
target: TargetLib,
permissions: Permissions,
module_graph: ModuleGraph,
module_graph: &ModuleGraph,
allow_js: bool,
) -> Result<(), ErrBox> {
let module_url = source_file.url.clone();
@ -790,7 +790,7 @@ impl TsCompiler {
pub async fn transpile(
&self,
module_graph: ModuleGraph,
module_graph: &ModuleGraph,
) -> Result<(), ErrBox> {
let mut source_files: Vec<TranspileSourceFile> = Vec::new();
for (_, value) in module_graph.iter() {
@ -1700,7 +1700,7 @@ mod tests {
&out,
TargetLib::Main,
Permissions::allow_all(),
module_graph,
&module_graph,
false,
)
.await;
@ -1770,7 +1770,7 @@ mod tests {
)
.unwrap();
let result = ts_compiler.transpile(module_graph).await;
let result = ts_compiler.transpile(&module_graph).await;
assert!(result.is_ok());
let compiled_file = ts_compiler.get_compiled_module(&out.url).unwrap();
let source_code = compiled_file.code;

View file

@ -31,7 +31,6 @@ pub use crate::errors::JsError;
pub use crate::flags::v8_set_flags;
pub use crate::module_specifier::ModuleResolutionError;
pub use crate::module_specifier::ModuleSpecifier;
pub use crate::modules::Deps;
pub use crate::modules::ModuleId;
pub use crate::modules::ModuleLoadId;
pub use crate::modules::ModuleLoader;

View file

@ -8,10 +8,8 @@ use futures::future::FutureExt;
use futures::stream::FuturesUnordered;
use futures::stream::Stream;
use futures::stream::TryStreamExt;
use serde_json::Value;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::rc::Rc;
@ -437,108 +435,6 @@ impl Modules {
}
self.info.get(&id)
}
pub fn deps(&self, module_specifier: &ModuleSpecifier) -> Option<Deps> {
Deps::new(self, module_specifier)
}
}
/// This is a tree structure representing the dependencies of a given module.
/// Use Modules::deps to construct it. The 'deps' member is None if this module
/// was already seen elsewhere in the tree.
#[derive(Debug, PartialEq)]
pub struct Deps {
pub name: String,
pub deps: Option<Vec<Deps>>,
prefix: String,
is_last: bool,
}
impl Deps {
fn new(
modules: &Modules,
module_specifier: &ModuleSpecifier,
) -> Option<Deps> {
let mut seen = HashSet::new();
Self::helper(&mut seen, "".to_string(), true, modules, module_specifier)
}
fn helper(
seen: &mut HashSet<String>,
prefix: String,
is_last: bool,
modules: &Modules,
module_specifier: &ModuleSpecifier,
) -> Option<Deps> {
let name = module_specifier.to_string();
if seen.contains(&name) {
Some(Deps {
name,
prefix,
deps: None,
is_last,
})
} else {
let mod_id = modules.get_id(&name)?;
let children = modules.get_children(mod_id).unwrap();
seen.insert(name.to_string());
let child_count = children.len();
let deps: Vec<Deps> = children
.iter()
.enumerate()
.map(|(index, dep_specifier)| {
let new_is_last = index == child_count - 1;
let mut new_prefix = prefix.clone();
new_prefix.push(if is_last { ' ' } else { '│' });
new_prefix.push(' ');
Self::helper(seen, new_prefix, new_is_last, modules, dep_specifier)
})
// If any of the children are missing, return None.
.collect::<Option<_>>()?;
Some(Deps {
name,
prefix,
deps: Some(deps),
is_last,
})
}
}
pub fn to_json(&self) -> Value {
let children;
if let Some(deps) = &self.deps {
children = deps.iter().map(|c| c.to_json()).collect();
} else {
children = Vec::new()
}
serde_json::json!([&self.name, children])
}
}
impl fmt::Display for Deps {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut has_children = false;
if let Some(ref deps) = self.deps {
has_children = !deps.is_empty();
}
write!(
f,
"{}{}─{} {}",
self.prefix,
if self.is_last { "" } else { "" },
if has_children { "" } else { "" },
self.name
)?;
if let Some(ref deps) = self.deps {
for d in deps {
write!(f, "\n{}", d)?;
}
}
Ok(())
}
}
#[cfg(test)]
@ -1072,56 +968,4 @@ mod tests {
);
assert_eq!(modules.get_children(d_id), Some(&vec![]));
}
#[test]
fn empty_deps() {
let modules = Modules::new();
let specifier = ModuleSpecifier::resolve_url("file:///foo").unwrap();
assert!(modules.deps(&specifier).is_none());
}
#[test]
fn deps_to_json() {
fn dep(name: &str, deps: Option<Vec<Deps>>) -> Deps {
Deps {
name: name.to_string(),
deps,
prefix: "".to_string(),
is_last: false,
}
}
let deps = dep(
"a",
Some(vec![
dep("b", Some(vec![dep("b2", None)])),
dep("c", Some(vec![])),
]),
);
assert_eq!(
serde_json::json!(["a", [["b", [["b2", []]]], ["c", []]]]),
deps.to_json()
);
}
/* TODO(bartlomieju): reenable
#[test]
fn deps() {
// "foo" -> "bar"
let mut modules = Modules::new();
modules.register(1, "foo");
modules.register(2, "bar");
modules.add_child(1, "bar");
let maybe_deps = modules.deps("foo");
assert!(maybe_deps.is_some());
let mut foo_deps = maybe_deps.unwrap();
assert_eq!(foo_deps.name, "foo");
assert!(foo_deps.deps.is_some());
let foo_children = foo_deps.deps.take().unwrap();
assert_eq!(foo_children.len(), 1);
let bar_deps = &foo_children[0];
assert_eq!(bar_deps.name, "bar");
assert_eq!(bar_deps.deps, Some(vec![]));
}
*/
}

View file

@ -3,62 +3,61 @@
`deno info [URL]` will inspect ES module and all of its dependencies.
```shell
deno info https://deno.land/std@0.52.0/http/file_server.ts
Download https://deno.land/std@0.52.0/http/file_server.ts
deno info https://deno.land/std@0.67.0/http/file_server.ts
Download https://deno.land/std@0.67.0/http/file_server.ts
...
local: /Users/deno/Library/Caches/deno/deps/https/deno.land/5bd138988e9d20db1a436666628ffb3f7586934e0a2a9fe2a7b7bf4fb7f70b98
local: /home/deno/.cache/deno/deps/https/deno.land/f57792e36f2dbf28b14a75e2372a479c6392780d4712d76698d5031f943c0020
type: TypeScript
compiled: /Users/deno/Library/Caches/deno/gen/https/deno.land/std@0.52.0/http/file_server.ts.js
map: /Users/deno/Library/Caches/deno/gen/https/deno.land/std@0.52.0/http/file_server.ts.js.map
deps:
https://deno.land/std@0.52.0/http/file_server.ts
├─┬ https://deno.land/std@0.52.0/path/mod.ts
│ ├─┬ https://deno.land/std@0.52.0/path/win32.ts
│ │ ├── https://deno.land/std@0.52.0/path/_constants.ts
│ │ ├─┬ https://deno.land/std@0.52.0/path/_util.ts
│ │ │ └── https://deno.land/std@0.52.0/path/_constants.ts
│ │ └─┬ https://deno.land/std@0.52.0/testing/asserts.ts
│ │ ├── https://deno.land/std@0.52.0/fmt/colors.ts
│ │ └── https://deno.land/std@0.52.0/testing/diff.ts
│ ├─┬ https://deno.land/std@0.52.0/path/posix.ts
│ │ ├── https://deno.land/std@0.52.0/path/_constants.ts
│ │ └── https://deno.land/std@0.52.0/path/_util.ts
│ ├─┬ https://deno.land/std@0.52.0/path/common.ts
│ │ └── https://deno.land/std@0.52.0/path/separator.ts
│ ├── https://deno.land/std@0.52.0/path/separator.ts
│ ├── https://deno.land/std@0.52.0/path/interface.ts
│ └─┬ https://deno.land/std@0.52.0/path/glob.ts
│ ├── https://deno.land/std@0.52.0/path/separator.ts
│ ├── https://deno.land/std@0.52.0/path/_globrex.ts
│ ├── https://deno.land/std@0.52.0/path/mod.ts
│ └── https://deno.land/std@0.52.0/testing/asserts.ts
├─┬ https://deno.land/std@0.52.0/http/server.ts
│ ├── https://deno.land/std@0.52.0/encoding/utf8.ts
│ ├─┬ https://deno.land/std@0.52.0/io/bufio.ts
│ │ ├─┬ https://deno.land/std@0.52.0/io/util.ts
│ │ │ ├── https://deno.land/std@0.52.0/path/mod.ts
│ │ │ └── https://deno.land/std@0.52.0/encoding/utf8.ts
│ │ └── https://deno.land/std@0.52.0/testing/asserts.ts
│ ├── https://deno.land/std@0.52.0/testing/asserts.ts
│ ├─┬ https://deno.land/std@0.52.0/async/mod.ts
│ │ ├── https://deno.land/std@0.52.0/async/deferred.ts
│ │ ├── https://deno.land/std@0.52.0/async/delay.ts
│ │ └─┬ https://deno.land/std@0.52.0/async/mux_async_iterator.ts
│ │ └── https://deno.land/std@0.52.0/async/deferred.ts
│ └─┬ https://deno.land/std@0.52.0/http/_io.ts
│ ├── https://deno.land/std@0.52.0/io/bufio.ts
│ ├─┬ https://deno.land/std@0.52.0/textproto/mod.ts
│ │ ├── https://deno.land/std@0.52.0/io/util.ts
│ │ ├─┬ https://deno.land/std@0.52.0/bytes/mod.ts
│ │ │ └── https://deno.land/std@0.52.0/io/util.ts
│ │ └── https://deno.land/std@0.52.0/encoding/utf8.ts
│ ├── https://deno.land/std@0.52.0/testing/asserts.ts
│ ├── https://deno.land/std@0.52.0/encoding/utf8.ts
│ ├── https://deno.land/std@0.52.0/http/server.ts
│ └── https://deno.land/std@0.52.0/http/http_status.ts
├─┬ https://deno.land/std@0.52.0/flags/mod.ts
│ └── https://deno.land/std@0.52.0/testing/asserts.ts
└── https://deno.land/std@0.52.0/testing/asserts.ts
compiled: /home/deno/.cache/deno/gen/https/deno.land/f57792e36f2dbf28b14a75e2372a479c6392780d4712d76698d5031f943c0020.js
deps: 23 unique (total 139.89KB)
https://deno.land/std@0.67.0/http/file_server.ts (10.49KB)
├─┬ https://deno.land/std@0.67.0/path/mod.ts (717B)
│ ├── https://deno.land/std@0.67.0/path/_constants.ts (2.35KB)
│ ├─┬ https://deno.land/std@0.67.0/path/win32.ts (27.36KB)
│ │ ├── https://deno.land/std@0.67.0/path/_interface.ts (657B)
│ │ ├── https://deno.land/std@0.67.0/path/_constants.ts *
│ │ ├─┬ https://deno.land/std@0.67.0/path/_util.ts (3.3KB)
│ │ │ ├── https://deno.land/std@0.67.0/path/_interface.ts *
│ │ │ └── https://deno.land/std@0.67.0/path/_constants.ts *
│ │ └── https://deno.land/std@0.67.0/_util/assert.ts (405B)
│ ├─┬ https://deno.land/std@0.67.0/path/posix.ts (12.67KB)
│ │ ├── https://deno.land/std@0.67.0/path/_interface.ts *
│ │ ├── https://deno.land/std@0.67.0/path/_constants.ts *
│ │ └── https://deno.land/std@0.67.0/path/_util.ts *
│ ├─┬ https://deno.land/std@0.67.0/path/common.ts (1.14KB)
│ │ └─┬ https://deno.land/std@0.67.0/path/separator.ts (264B)
│ │ └── https://deno.land/std@0.67.0/path/_constants.ts *
│ ├── https://deno.land/std@0.67.0/path/separator.ts *
│ ├── https://deno.land/std@0.67.0/path/_interface.ts *
│ └─┬ https://deno.land/std@0.67.0/path/glob.ts (8.12KB)
│ ├── https://deno.land/std@0.67.0/path/_constants.ts *
│ ├── https://deno.land/std@0.67.0/path/mod.ts *
│ └── https://deno.land/std@0.67.0/path/separator.ts *
├─┬ https://deno.land/std@0.67.0/http/server.ts (10.23KB)
│ ├── https://deno.land/std@0.67.0/encoding/utf8.ts (433B)
│ ├─┬ https://deno.land/std@0.67.0/io/bufio.ts (21.15KB)
│ │ ├── https://deno.land/std@0.67.0/bytes/mod.ts (4.34KB)
│ │ └── https://deno.land/std@0.67.0/_util/assert.ts *
│ ├── https://deno.land/std@0.67.0/_util/assert.ts *
│ ├─┬ https://deno.land/std@0.67.0/async/mod.ts (202B)
│ │ ├── https://deno.land/std@0.67.0/async/deferred.ts (1.03KB)
│ │ ├── https://deno.land/std@0.67.0/async/delay.ts (279B)
│ │ ├─┬ https://deno.land/std@0.67.0/async/mux_async_iterator.ts (1.98KB)
│ │ │ └── https://deno.land/std@0.67.0/async/deferred.ts *
│ │ └── https://deno.land/std@0.67.0/async/pool.ts (1.58KB)
│ └─┬ https://deno.land/std@0.67.0/http/_io.ts (11.25KB)
│ ├── https://deno.land/std@0.67.0/io/bufio.ts *
│ ├─┬ https://deno.land/std@0.67.0/textproto/mod.ts (4.52KB)
│ │ ├── https://deno.land/std@0.67.0/io/bufio.ts *
│ │ ├── https://deno.land/std@0.67.0/bytes/mod.ts *
│ │ └── https://deno.land/std@0.67.0/encoding/utf8.ts *
│ ├── https://deno.land/std@0.67.0/_util/assert.ts *
│ ├── https://deno.land/std@0.67.0/encoding/utf8.ts *
│ ├── https://deno.land/std@0.67.0/http/server.ts *
│ └── https://deno.land/std@0.67.0/http/http_status.ts (5.93KB)
├─┬ https://deno.land/std@0.67.0/flags/mod.ts (9.54KB)
│ └── https://deno.land/std@0.67.0/_util/assert.ts *
└── https://deno.land/std@0.67.0/_util/assert.ts *
```
Dependency inspector works with any local or remote ES modules.