diff options
author | Kitson Kelly <me@kitsonkelly.com> | 2020-10-12 13:25:27 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-12 13:25:27 +1100 |
commit | e877b36072039811066726ab1619e7b5142f5680 (patch) | |
tree | 2c97db700b09f9c830adc206e3448102d42c3d6d | |
parent | fede13f2eb64f648e6c39aa01e2e0ede4e5be25e (diff) |
refactor(cli): move info subcommand over to new module graph (#7892)
-rw-r--r-- | cli/info.rs | 648 | ||||
-rw-r--r-- | cli/main.rs | 33 | ||||
-rw-r--r-- | cli/media_type.rs | 125 | ||||
-rw-r--r-- | cli/module_graph2.rs | 189 | ||||
-rw-r--r-- | cli/specifier_handler.rs | 22 | ||||
-rw-r--r-- | cli/tests/022_info_flag_script.out | 13 | ||||
-rw-r--r-- | cli/tests/049_info_flag_script_jsx.out | 13 | ||||
-rw-r--r-- | cli/tests/054_info_local_imports.out | 1 | ||||
-rw-r--r-- | cli/tests/055_info_file_json.out | 42 | ||||
-rw-r--r-- | cli/tests/cafile_info.ts.out | 13 | ||||
-rw-r--r-- | cli/tests/info_recursive_imports_test.out | 7 |
11 files changed, 558 insertions, 548 deletions
diff --git a/cli/info.rs b/cli/info.rs index 6701aafbe..9ca294287 100644 --- a/cli/info.rs +++ b/cli/info.rs @@ -1,404 +1,197 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use crate::colors; -use crate::global_state::GlobalState; -use crate::module_graph::{ModuleGraph, ModuleGraphFile, ModuleGraphLoader}; +use crate::media_type::serialize_media_type; +use crate::MediaType; use crate::ModuleSpecifier; -use crate::Permissions; -use deno_core::error::AnyError; -use serde::ser::Serializer; -use serde::Serialize; -use std::collections::{BTreeMap, HashMap, HashSet}; -use std::sync::Arc; -// TODO(bartlomieju): rename -/// Struct containing a module's dependency information. -#[derive(Serialize)] +use serde::Serialize; +use serde::Serializer; +use std::cmp::Ordering; +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::fmt; +use std::path::PathBuf; + +/// The core structure representing information about a specific "root" file in +/// a module graph. This is used to represent information as part of the `info` +/// subcommand. +#[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ModuleDepInfo { - module: String, - local: String, - file_type: String, - compiled: Option<String>, - map: Option<String>, - dep_count: usize, +pub struct ModuleGraphInfo { + pub compiled: Option<PathBuf>, + pub dep_count: usize, + #[serde(serialize_with = "serialize_media_type")] + pub file_type: MediaType, + pub files: ModuleInfoMap, #[serde(skip_serializing)] - deps: FileInfoDepTree, - total_size: Option<usize>, - files: FileInfoDepFlatGraph, -} - -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<Self, AnyError> { - // 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 = - crate::media_type::enum_name_media_type(out.media_type).to_string(); - - let deps = FileInfoDepTree::new(&module_graph, &module_specifier); - let total_size = deps.total_size; - let dep_count = get_unique_dep_count(&module_graph) - 1; - let files = FileInfoDepFlatGraph::new(&module_graph); - - let info = Self { - module: module_specifier.to_string(), - local: local_filename, - file_type, - compiled: compiled_filename, - map: map_filename, - dep_count, - deps, - total_size, - files, - }; - - 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 - } - }, - ) + pub info: ModuleInfo, + pub local: PathBuf, + pub map: Option<PathBuf>, + pub module: ModuleSpecifier, + pub total_size: usize, } -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 - ))?; +impl fmt::Display for ModuleGraphInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!( + f, + "{} {}", + colors::bold("local:"), + self.local.to_string_lossy() + )?; + writeln!(f, "{} {}", colors::bold("type:"), self.file_type)?; if let Some(ref compiled) = self.compiled { - f.write_fmt(format_args!( - "{} {}\n", + writeln!( + f, + "{} {}", colors::bold("compiled:"), - compiled - ))?; + compiled.to_string_lossy() + )?; } if let Some(ref map) = self.map { - f.write_fmt(format_args!("{} {}\n", colors::bold("map:"), map))?; + writeln!(f, "{} {}", colors::bold("map:"), map.to_string_lossy())?; } - - f.write_fmt(format_args!( - "{} {} unique {}\n", + writeln!( + f, + "{} {} unique {}", colors::bold("deps:"), self.dep_count, colors::gray(&format!( "(total {})", - human_size(self.deps.total_size.unwrap_or(0) as f64), + human_size(self.info.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)?; + )?; + writeln!(f)?; + writeln!( + f, + "{} {}", + self.info.name, + colors::gray(&format!("({})", human_size(self.info.size as f64))) + )?; + + let dep_count = self.info.deps.len(); + for (idx, dep) in self.info.deps.iter().enumerate() { + dep.write_info(f, "", idx == dep_count - 1)?; } 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)] +/// Represents a unique dependency within the graph of the the dependencies for +/// a given module. +#[derive(Debug, Serialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] -struct FileInfoDepTree { - name: String, - size: usize, - total_size: Option<usize>, - deps: Vec<FileInfoDepTree>, +pub struct ModuleInfo { + pub deps: Vec<ModuleInfo>, + pub name: ModuleSpecifier, + pub size: usize, + pub total_size: Option<usize>, } -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, - ) +impl PartialOrd for ModuleInfo { + fn partial_cmp(&self, other: &ModuleInfo) -> Option<Ordering> { + Some(self.cmp(other)) } +} - /// 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; +impl Ord for ModuleInfo { + fn cmp(&self, other: &ModuleInfo) -> Ordering { + self.name.to_string().cmp(&other.name.to_string()) + } +} - total_sizes.insert(name.clone(), total); +impl ModuleInfo { + pub fn write_info( + &self, + f: &mut fmt::Formatter<'_>, + prefix: &str, + last: bool, + ) -> fmt::Result { + let sibling_connector = if last { '└' } else { '├' }; + let child_connector = if self.deps.is_empty() { '─' } else { '┬' }; + let totals = if self.total_size.is_some() { + colors::gray(&format!(" ({})", human_size(self.size as f64))) + } else { + colors::gray(" *") + }; - Some(total) - }; + writeln!( + f, + "{} {}{}", + colors::gray(&format!( + "{}{}─{}", + prefix, sibling_connector, child_connector + )), + self.name, + totals + )?; + + let mut prefix = prefix.to_string(); + if last { + prefix.push(' '); + } else { + prefix.push('│'); } + prefix.push(' '); - Self { - name, - size, - total_size, - deps, + let dep_count = self.deps.len(); + for (idx, dep) in self.deps.iter().enumerate() { + dep.write_info(f, &prefix, idx == dep_count - 1)?; } - } -} -/// Flat graph vertex with all shallow dependencies -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -struct FileInfoVertex { - size: usize, - deps: Vec<String>, -} - -impl FileInfoVertex { - /// Creates new `FileInfoVertex` that is a single vertex dependency module - fn new(size: usize, deps: Vec<String>) -> Self { - Self { size, deps } + Ok(()) } } -struct FileInfoDepFlatGraph(HashMap<String, FileInfoVertex>); +/// A flat map of dependencies for a given module graph. +#[derive(Debug)] +pub struct ModuleInfoMap(pub HashMap<ModuleSpecifier, ModuleInfoMapItem>); -impl FileInfoDepFlatGraph { - /// Creates new `FileInfoDepFlatGraph`, flat graph of a shallow module dependencies - /// - /// Each graph vertex represents unique dependency with its all shallow dependencies - fn new(module_graph: &ModuleGraph) -> Self { - let mut inner = HashMap::new(); - module_graph - .iter() - .for_each(|(module_name, module_graph_file)| { - let size = module_graph_file.size(); - let mut deps = Vec::new(); - module_graph_file.imports.iter().for_each(|import| { - deps.push(import.resolved_specifier.to_string()); - }); - inner.insert(module_name.clone(), FileInfoVertex::new(size, deps)); - }); - Self(inner) +impl ModuleInfoMap { + pub fn new(map: HashMap<ModuleSpecifier, ModuleInfoMapItem>) -> Self { + ModuleInfoMap(map) } } -impl Serialize for FileInfoDepFlatGraph { +impl Serialize for ModuleInfoMap { /// Serializes inner hash map which is ordered by the key fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { - let ordered: BTreeMap<_, _> = self.0.iter().collect(); + let ordered: BTreeMap<_, _> = + self.0.iter().map(|(k, v)| (k.to_string(), v)).collect(); ordered.serialize(serializer) } } -/// 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 +/// An entry in the `ModuleInfoMap` the provides the size of the module and +/// a vector of its dependencies, which should also be available as entries +/// in the map. +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ModuleInfoMapItem { + pub deps: Vec<ModuleSpecifier>, + pub size: usize, } -pub fn human_size(bytse: f64) -> String { - let negative = if bytse.is_sign_positive() { "" } else { "-" }; - let bytse = bytse.abs(); +/// A function that converts a float to a string the represents a human +/// readable version of that number. +pub fn human_size(size: f64) -> String { + let negative = if size.is_sign_positive() { "" } else { "-" }; + let size = size.abs(); let units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - if bytse < 1_f64 { - return format!("{}{}{}", negative, bytse, "B"); + if size < 1_f64 { + return format!("{}{}{}", negative, size, "B"); } let delimiter = 1024_f64; let exponent = std::cmp::min( - (bytse.ln() / delimiter.ln()).floor() as i32, + (size.ln() / delimiter.ln()).floor() as i32, (units.len() - 1) as i32, ); - let pretty_bytes = format!("{:.2}", bytse / delimiter.powi(exponent)) + let pretty_bytes = format!("{:.2}", size / delimiter.powi(exponent)) .parse::<f64>() .unwrap() * 1_f64; @@ -409,10 +202,7 @@ pub fn human_size(bytse: f64) -> String { #[cfg(test)] mod test { use super::*; - use crate::ast::Location; - use crate::media_type::MediaType; - use crate::module_graph::ImportDescriptor; - use deno_core::url::Url; + use deno_core::serde_json::json; #[test] fn human_size_test() { @@ -427,105 +217,89 @@ mod test { 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_vertical_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::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(), + fn get_fixture() -> ModuleGraphInfo { + let spec_c = + ModuleSpecifier::resolve_url_or_path("https://deno.land/x/a/b/c.ts") + .unwrap(); + let spec_d = + ModuleSpecifier::resolve_url_or_path("https://deno.land/x/a/b/c.ts") + .unwrap(); + let deps = vec![ModuleInfo { + deps: Vec::new(), + name: spec_d.clone(), + size: 12345, + total_size: None, + }]; + let info = ModuleInfo { + deps, + name: spec_c.clone(), + size: 12345, + total_size: Some(12345), }; + let mut items = HashMap::new(); + items.insert( + spec_c, + ModuleInfoMapItem { + deps: vec![spec_d.clone()], + size: 12345, + }, + ); + items.insert( + spec_d, + ModuleInfoMapItem { + deps: Vec::new(), + size: 12345, + }, + ); + let files = ModuleInfoMap(items); - (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()); + ModuleGraphInfo { + compiled: Some(PathBuf::from("/a/b/c.js")), + dep_count: 99, + file_type: MediaType::TypeScript, + files, + info, + local: PathBuf::from("/a/b/c.ts"), + map: None, + module: ModuleSpecifier::resolve_url_or_path( + "https://deno.land/x/a/b/c.ts", + ) + .unwrap(), + total_size: 999999, + } } #[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); + fn test_module_graph_info_display() { + let fixture = get_fixture(); + let actual = format!("{}", fixture); + assert!(actual.contains(" /a/b/c.ts")); + assert!(actual.contains(" 99 unique")); + assert!(actual.contains("(12.06KB)")); + assert!(actual.contains("\n\nhttps://deno.land/x/a/b/c.ts")); } #[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); + fn test_module_graph_info_json() { + let fixture = get_fixture(); + let actual = json!(fixture); + assert_eq!( + actual, + json!({ + "compiled": "/a/b/c.js", + "depCount": 99, + "fileType": "TypeScript", + "files": { + "https://deno.land/x/a/b/c.ts":{ + "deps": [], + "size": 12345 + } + }, + "local": "/a/b/c.ts", + "map": null, + "module": "https://deno.land/x/a/b/c.ts", + "totalSize": 999999 + }) + ); } } diff --git a/cli/main.rs b/cli/main.rs index 7a370d555..96cef4d77 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -81,12 +81,14 @@ use global_state::exit_unstable; use import_map::ImportMap; use log::Level; use log::LevelFilter; +use std::cell::RefCell; use std::env; use std::io::Read; use std::io::Write; use std::iter::once; use std::path::PathBuf; use std::pin::Pin; +use std::rc::Rc; use std::sync::Arc; use upgrade::upgrade_command; @@ -159,27 +161,36 @@ fn get_types(unstable: bool) -> String { async fn info_command( flags: Flags, - file: Option<String>, + maybe_specifier: Option<String>, json: bool, ) -> Result<(), AnyError> { if json && !flags.unstable { exit_unstable("--json"); } let global_state = GlobalState::new(flags)?; - // If it was just "deno info" print location of caches and exit - if file.is_none() { - print_cache_info(&global_state, json) - } else { - let main_module = ModuleSpecifier::resolve_url_or_path(&file.unwrap())?; - let info = - info::ModuleDepInfo::new(&global_state, main_module.clone()).await?; + if let Some(specifier) = maybe_specifier { + let specifier = ModuleSpecifier::resolve_url_or_path(&specifier)?; + let handler = Rc::new(RefCell::new(specifier_handler::FetchHandler::new( + &global_state, + Permissions::allow_all(), + )?)); + let mut builder = module_graph2::GraphBuilder2::new( + handler, + global_state.maybe_import_map.clone(), + ); + builder.insert(&specifier).await?; + let graph = builder.get_graph(&global_state.lockfile)?; + let info = graph.info()?; if json { - write_json_to_stdout(&json!(info)) + write_json_to_stdout(&json!(info))?; } else { - write_to_stdout_ignore_sigpipe(format!("{}", info).as_bytes()) - .map_err(AnyError::from) + write_to_stdout_ignore_sigpipe(format!("{}", info).as_bytes())?; } + Ok(()) + } else { + // If it was just "deno info" print location of caches and exit + print_cache_info(&global_state, json) } } diff --git a/cli/media_type.rs b/cli/media_type.rs index 84e6fbe8e..823112fa2 100644 --- a/cli/media_type.rs +++ b/cli/media_type.rs @@ -2,6 +2,7 @@ use serde::Serialize; use serde::Serializer; +use std::fmt; use std::path::Path; use std::path::PathBuf; @@ -22,6 +23,23 @@ pub enum MediaType { Unknown = 8, } +impl fmt::Display for MediaType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let value = match self { + MediaType::JavaScript => "JavaScript", + MediaType::JSX => "JSX", + MediaType::TypeScript => "TypeScript", + MediaType::Dts => "Dts", + MediaType::TSX => "TSX", + MediaType::Json => "Json", + MediaType::Wasm => "Wasm", + MediaType::BuildInfo => "BuildInfo", + MediaType::Unknown => "Unknown", + }; + write!(f, "{}", value) + } +} + impl<'a> From<&'a Path> for MediaType { fn from(path: &'a Path) -> Self { MediaType::from_path(path) @@ -103,45 +121,74 @@ impl Serialize for MediaType { } } -pub fn enum_name_media_type(mt: MediaType) -> &'static str { - match mt { - MediaType::JavaScript => "JavaScript", - MediaType::JSX => "JSX", - MediaType::TypeScript => "TypeScript", - MediaType::Dts => "Dts", - MediaType::TSX => "TSX", - MediaType::Json => "Json", - MediaType::Wasm => "Wasm", - MediaType::BuildInfo => "BuildInfo", - MediaType::Unknown => "Unknown", - } +/// Serialize a `MediaType` enum into a human readable string. The default +/// serialization for media types is and integer. +/// +/// TODO(@kitsonk) remove this once we stop sending MediaType into tsc. +pub fn serialize_media_type<S>(mt: &MediaType, s: S) -> Result<S::Ok, S::Error> +where + S: Serializer, +{ + s.serialize_str(&format!("{}", mt)) } -#[test] -fn test_map_file_extension() { - assert_eq!( - MediaType::from(Path::new("foo/bar.ts")), - MediaType::TypeScript - ); - assert_eq!(MediaType::from(Path::new("foo/bar.tsx")), MediaType::TSX); - assert_eq!( - MediaType::from(Path::new("foo/bar.d.ts")), - MediaType::TypeScript - ); - assert_eq!( - MediaType::from(Path::new("foo/bar.js")), - MediaType::JavaScript - ); - assert_eq!(MediaType::from(Path::new("foo/bar.jsx")), MediaType::JSX); - assert_eq!(MediaType::from(Path::new("foo/bar.json")), MediaType::Json); - assert_eq!(MediaType::from(Path::new("foo/bar.wasm")), MediaType::Wasm); - assert_eq!( - MediaType::from(Path::new("foo/bar.cjs")), - MediaType::JavaScript - ); - assert_eq!( - MediaType::from(Path::new("foo/bar.txt")), - MediaType::Unknown - ); - assert_eq!(MediaType::from(Path::new("foo/bar")), MediaType::Unknown); +#[cfg(test)] +mod tests { + use super::*; + use deno_core::serde_json::json; + + #[test] + fn test_map_file_extension() { + assert_eq!( + MediaType::from(Path::new("foo/bar.ts")), + MediaType::TypeScript + ); + assert_eq!(MediaType::from(Path::new("foo/bar.tsx")), MediaType::TSX); + assert_eq!( + MediaType::from(Path::new("foo/bar.d.ts")), + MediaType::TypeScript + ); + assert_eq!( + MediaType::from(Path::new("foo/bar.js")), + MediaType::JavaScript + ); + assert_eq!(MediaType::from(Path::new("foo/bar.jsx")), MediaType::JSX); + assert_eq!(MediaType::from(Path::new("foo/bar.json")), MediaType::Json); + assert_eq!(MediaType::from(Path::new("foo/bar.wasm")), MediaType::Wasm); + assert_eq!( + MediaType::from(Path::new("foo/bar.cjs")), + MediaType::JavaScript + ); + assert_eq!( + MediaType::from(Path::new("foo/bar.txt")), + MediaType::Unknown + ); + assert_eq!(MediaType::from(Path::new("foo/bar")), MediaType::Unknown); + } + + #[test] + fn test_serialization() { + assert_eq!(json!(MediaType::JavaScript), json!(0)); + assert_eq!(json!(MediaType::JSX), json!(1)); + assert_eq!(json!(MediaType::TypeScript), json!(2)); + assert_eq!(json!(MediaType::Dts), json!(3)); + assert_eq!(json!(MediaType::TSX), json!(4)); + assert_eq!(json!(MediaType::Json), json!(5)); + assert_eq!(json!(MediaType::Wasm), json!(6)); + assert_eq!(json!(MediaType::BuildInfo), json!(7)); + assert_eq!(json!(MediaType::Unknown), json!(8)); + } + + #[test] + fn test_display() { + assert_eq!(format!("{}", MediaType::JavaScript), "JavaScript"); + assert_eq!(format!("{}", MediaType::JSX), "JSX"); + assert_eq!(format!("{}", MediaType::TypeScript), "TypeScript"); + assert_eq!(format!("{}", MediaType::Dts), "Dts"); + assert_eq!(format!("{}", MediaType::TSX), "TSX"); + assert_eq!(format!("{}", MediaType::Json), "Json"); + assert_eq!(format!("{}", MediaType::Wasm), "Wasm"); + assert_eq!(format!("{}", MediaType::BuildInfo), "BuildInfo"); + assert_eq!(format!("{}", MediaType::Unknown), "Unknown"); + } } diff --git a/cli/module_graph2.rs b/cli/module_graph2.rs index 738fe188d..698586413 100644 --- a/cli/module_graph2.rs +++ b/cli/module_graph2.rs @@ -5,6 +5,10 @@ use crate::ast::parse; use crate::ast::Location; use crate::ast::ParsedModule; use crate::import_map::ImportMap; +use crate::info::ModuleGraphInfo; +use crate::info::ModuleInfo; +use crate::info::ModuleInfoMap; +use crate::info::ModuleInfoMapItem; use crate::lockfile::Lockfile; use crate::media_type::MediaType; use crate::specifier_handler::CachedModule; @@ -30,6 +34,7 @@ use std::collections::HashMap; use std::collections::HashSet; use std::error::Error; use std::fmt; +use std::path::PathBuf; use std::rc::Rc; use std::result; use std::sync::Mutex; @@ -162,13 +167,16 @@ struct Module { is_dirty: bool, is_hydrated: bool, is_parsed: bool, + maybe_emit_path: Option<PathBuf>, maybe_import_map: Option<Rc<RefCell<ImportMap>>>, + maybe_map_path: Option<PathBuf>, maybe_parsed_module: Option<ParsedModule>, maybe_types: Option<(String, ModuleSpecifier)>, maybe_version: Option<String>, media_type: MediaType, specifier: ModuleSpecifier, source: String, + source_path: PathBuf, } impl Default for Module { @@ -179,13 +187,16 @@ impl Default for Module { is_dirty: false, is_hydrated: false, is_parsed: false, + maybe_emit_path: None, maybe_import_map: None, + maybe_map_path: None, maybe_parsed_module: None, maybe_types: None, maybe_version: None, media_type: MediaType::Unknown, specifier: ModuleSpecifier::resolve_url("https://deno.land/x/").unwrap(), source: "".to_string(), + source_path: PathBuf::new(), } } } @@ -215,6 +226,9 @@ impl Module { pub fn hydrate(&mut self, cached_module: CachedModule) { self.media_type = cached_module.media_type; self.source = cached_module.source; + self.source_path = cached_module.source_path; + self.maybe_emit_path = cached_module.maybe_emit_path; + self.maybe_map_path = cached_module.maybe_map_path; if self.maybe_import_map.is_none() { if let Some(dependencies) = cached_module.maybe_dependencies { self.dependencies = dependencies; @@ -359,6 +373,10 @@ impl Module { pub fn set_version(&mut self, config: &[u8]) { self.maybe_version = Some(get_version(&self.source, version::DENO, config)) } + + pub fn size(&self) -> usize { + self.source.as_bytes().len() + } } #[derive(Clone, Debug, PartialEq)] @@ -421,6 +439,115 @@ impl Graph2 { } } + fn get_info( + &self, + specifier: &ModuleSpecifier, + seen: &mut HashSet<ModuleSpecifier>, + totals: &mut HashMap<ModuleSpecifier, usize>, + ) -> ModuleInfo { + let not_seen = seen.insert(specifier.clone()); + let module = self.modules.get(specifier).unwrap(); + let mut deps = Vec::new(); + let mut total_size = None; + + if not_seen { + let mut seen_deps = HashSet::new(); + // TODO(@kitsonk) https://github.com/denoland/deno/issues/7927 + for (_, dep) in module.dependencies.iter() { + // Check the runtime code dependency + if let Some(code_dep) = &dep.maybe_code { + if seen_deps.insert(code_dep.clone()) { + deps.push(self.get_info(code_dep, seen, totals)); + } + } + } + deps.sort(); + total_size = if let Some(total) = totals.get(specifier) { + Some(total.to_owned()) + } else { + let mut total = deps + .iter() + .map(|d| { + if let Some(total_size) = d.total_size { + total_size + } else { + 0 + } + }) + .sum(); + total += module.size(); + totals.insert(specifier.clone(), total); + Some(total) + }; + } + + ModuleInfo { + deps, + name: specifier.clone(), + size: module.size(), + total_size, + } + } + + fn get_info_map(&self) -> ModuleInfoMap { + let map = self + .modules + .iter() + .map(|(specifier, module)| { + let mut deps = HashSet::new(); + for (_, dep) in module.dependencies.iter() { + if let Some(code_dep) = &dep.maybe_code { + deps.insert(code_dep.clone()); + } + if let Some(type_dep) = &dep.maybe_type { + deps.insert(type_dep.clone()); + } + } + if let Some((_, types_dep)) = &module.maybe_types { + deps.insert(types_dep.clone()); + } + let item = ModuleInfoMapItem { + deps: deps.into_iter().collect(), + size: module.size(), + }; + (specifier.clone(), item) + }) + .collect(); + + ModuleInfoMap::new(map) + } + + /// Return a structure which provides information about the module graph and + /// the relationship of the modules in the graph. This structure is used to + /// provide information for the `info` subcommand. + pub fn info(&self) -> Result<ModuleGraphInfo, AnyError> { + if self.roots.is_empty() || self.roots.len() > 1 { + return Err(NotSupported(format!("Info is only supported when there is a single root module in the graph. Found: {}", self.roots.len())).into()); + } + + let module = self.roots[0].clone(); + let m = self.modules.get(&module).unwrap(); + + let mut seen = HashSet::new(); + let mut totals = HashMap::new(); + let info = self.get_info(&module, &mut seen, &mut totals); + + let files = self.get_info_map(); + let total_size = totals.get(&module).unwrap_or(&m.size()).to_owned(); + + Ok(ModuleGraphInfo { + compiled: m.maybe_emit_path.clone(), + dep_count: self.modules.len() - 1, + file_type: m.media_type, + files, + info, + local: m.source_path.clone(), + map: m.maybe_map_path.clone(), + module, + total_size, + }) + } + /// Update the handler with any modules that are marked as _dirty_ and update /// any build info if present. fn flush(&mut self, emit_type: &EmitType) -> Result<(), AnyError> { @@ -713,25 +840,26 @@ mod tests { .replace(":///", "_") .replace("://", "_") .replace("/", "-"); - let specifier_path = self.fixtures.join(specifier_text); - let media_type = - match specifier_path.extension().unwrap().to_str().unwrap() { - "ts" => { - if specifier_path.to_string_lossy().ends_with(".d.ts") { - MediaType::Dts - } else { - MediaType::TypeScript - } + let source_path = self.fixtures.join(specifier_text); + let media_type = match source_path.extension().unwrap().to_str().unwrap() + { + "ts" => { + if source_path.to_string_lossy().ends_with(".d.ts") { + MediaType::Dts + } else { + MediaType::TypeScript } - "tsx" => MediaType::TSX, - "js" => MediaType::JavaScript, - "jsx" => MediaType::JSX, - _ => MediaType::Unknown, - }; - let source = fs::read_to_string(specifier_path)?; + } + "tsx" => MediaType::TSX, + "js" => MediaType::JavaScript, + "jsx" => MediaType::JSX, + _ => MediaType::Unknown, + }; + let source = fs::read_to_string(&source_path)?; Ok(CachedModule { source, + source_path, specifier, media_type, ..CachedModule::default() @@ -880,6 +1008,37 @@ mod tests { } #[tokio::test] + async fn test_graph_info() { + let c = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); + let fixtures = c.join("tests/module_graph"); + let handler = Rc::new(RefCell::new(MockSpecifierHandler { + fixtures, + ..MockSpecifierHandler::default() + })); + let mut builder = GraphBuilder2::new(handler.clone(), None); + let specifier = + ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts") + .expect("could not resolve module"); + builder + .insert(&specifier) + .await + .expect("module not inserted"); + let graph = builder.get_graph(&None).expect("could not get graph"); + let info = graph.info().expect("could not get info"); + assert!(info.compiled.is_none()); + assert_eq!(info.dep_count, 6); + assert_eq!(info.file_type, MediaType::TypeScript); + assert_eq!(info.files.0.len(), 7); + assert!(info.local.to_string_lossy().ends_with("file_tests-main.ts")); + assert!(info.map.is_none()); + assert_eq!( + info.module, + ModuleSpecifier::resolve_url_or_path("file:///tests/main.ts").unwrap() + ); + assert_eq!(info.total_size, 344); + } + + #[tokio::test] async fn test_graph_transpile() { // This is a complex scenario of transpiling, where we have TypeScript // importing a JavaScript file (with type definitions) which imports diff --git a/cli/specifier_handler.rs b/cli/specifier_handler.rs index 92999632a..e392a3c3a 100644 --- a/cli/specifier_handler.rs +++ b/cli/specifier_handler.rs @@ -18,6 +18,7 @@ use std::collections::HashMap; use std::env; use std::error::Error; use std::fmt; +use std::path::PathBuf; use std::pin::Pin; use std::sync::Arc; @@ -30,10 +31,13 @@ pub type FetchFuture = pub struct CachedModule { pub emits: EmitMap, pub maybe_dependencies: Option<DependencyMap>, + pub maybe_emit_path: Option<PathBuf>, + pub maybe_map_path: Option<PathBuf>, pub maybe_types: Option<String>, pub maybe_version: Option<String>, pub media_type: MediaType, pub source: String, + pub source_path: PathBuf, pub specifier: ModuleSpecifier, } @@ -43,10 +47,13 @@ impl Default for CachedModule { CachedModule { emits: HashMap::new(), maybe_dependencies: None, + maybe_emit_path: None, + maybe_map_path: None, maybe_types: None, maybe_version: None, media_type: MediaType::Unknown, source: "".to_string(), + source_path: PathBuf::new(), specifier: ModuleSpecifier::resolve_url("https://deno.land/x/mod.ts") .unwrap(), } @@ -242,27 +249,34 @@ impl SpecifierHandler for FetchHandler { None }; - let filename = + let mut maybe_map_path: Option<PathBuf> = None; + let map_path = disk_cache.get_cache_filename_with_extension(&url, "js.map"); - let maybe_map: Option<String> = if let Ok(map) = disk_cache.get(&filename) + let maybe_map: Option<String> = if let Ok(map) = disk_cache.get(&map_path) { + maybe_map_path = Some(disk_cache.location.join(map_path)); Some(String::from_utf8(map)?) } else { None }; let mut emits = HashMap::new(); - let filename = disk_cache.get_cache_filename_with_extension(&url, "js"); - if let Ok(code) = disk_cache.get(&filename) { + let mut maybe_emit_path: Option<PathBuf> = None; + let emit_path = disk_cache.get_cache_filename_with_extension(&url, "js"); + if let Ok(code) = disk_cache.get(&emit_path) { + maybe_emit_path = Some(disk_cache.location.join(emit_path)); emits.insert(EmitType::Cli, (String::from_utf8(code)?, maybe_map)); }; Ok(CachedModule { emits, maybe_dependencies: None, + maybe_emit_path, + maybe_map_path, maybe_types: source_file.types_header, maybe_version, media_type: source_file.media_type, source: source_file.source_code, + source_path: source_file.filename, specifier, }) } diff --git a/cli/tests/022_info_flag_script.out b/cli/tests/022_info_flag_script.out index e84c253e9..83425f09f 100644 --- a/cli/tests/022_info_flag_script.out +++ b/cli/tests/022_info_flag_script.out @@ -2,12 +2,13 @@ local: [WILDCARD]http[WILDCARD]127.0.0.1_PORT4545[WILDCARD] type: TypeScript 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_application_x_javascript.j4.js ([WILDCARD]) +├── http://localhost:4545/cli/tests/subdir/mt_application_x_typescript.t4.ts ([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]) +├── http://localhost:4545/cli/tests/subdir/mt_text_javascript.j1.js ([WILDCARD]) +├── http://localhost:4545/cli/tests/subdir/mt_text_typescript.t1.ts ([WILDCARD]) +├── http://localhost:4545/cli/tests/subdir/mt_video_mp2t.t3.ts ([WILDCARD]) +└── http://localhost:4545/cli/tests/subdir/mt_video_vdn.t2.ts ([WILDCARD]) diff --git a/cli/tests/049_info_flag_script_jsx.out b/cli/tests/049_info_flag_script_jsx.out index b5fdd980a..9260e8f67 100644 --- a/cli/tests/049_info_flag_script_jsx.out +++ b/cli/tests/049_info_flag_script_jsx.out @@ -2,12 +2,13 @@ local: [WILDCARD]http[WILDCARD]127.0.0.1_PORT4545[WILDCARD] type: TypeScript 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_application_x_javascript_jsx.j4.jsx ([WILDCARD]) +├── http://localhost:4545/cli/tests/subdir/mt_application_x_typescript_tsx.t4.tsx ([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]) +├── http://localhost:4545/cli/tests/subdir/mt_text_javascript_jsx.j1.jsx ([WILDCARD]) +├── http://localhost:4545/cli/tests/subdir/mt_text_typescript_tsx.t1.tsx ([WILDCARD]) +├── http://localhost:4545/cli/tests/subdir/mt_video_mp2t_tsx.t3.tsx ([WILDCARD]) +└── http://localhost:4545/cli/tests/subdir/mt_video_vdn_tsx.t2.tsx ([WILDCARD]) diff --git a/cli/tests/054_info_local_imports.out b/cli/tests/054_info_local_imports.out index d199dccb3..1933a0c9c 100644 --- a/cli/tests/054_info_local_imports.out +++ b/cli/tests/054_info_local_imports.out @@ -1,6 +1,7 @@ local: [WILDCARD]005_more_imports.ts type: TypeScript 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]) diff --git a/cli/tests/055_info_file_json.out b/cli/tests/055_info_file_json.out index 7c7bfabb9..d0274b0b8 100644 --- a/cli/tests/055_info_file_json.out +++ b/cli/tests/055_info_file_json.out @@ -1,33 +1,33 @@ { - "module": "file://[WILDCARD]005_more_imports.ts", - "local": "[WILDCARD]005_more_imports.ts", - "fileType": "TypeScript", "compiled": null, - "map": null, "depCount": 3, - "totalSize": 757, + "fileType": "TypeScript", "files": { - "file://[WILDCARD]005_more_imports.ts": { - "size": 211, + "file:///[WILDCARD]/cli/tests/005_more_imports.ts": { "deps": [ - "file://[WILDCARD]/subdir/mod1.ts" - ] + "file:///[WILDCARD]/cli/tests/subdir/mod1.ts" + ], + "size": 211 }, - "file://[WILDCARD]/subdir/mod1.ts": { - "size": 320, + "file:///[WILDCARD]/cli/tests/subdir/mod1.ts": { "deps": [ - "file://[WILDCARD]/subdir/subdir2/mod2.ts" - ] + "file:///[WILDCARD]/cli/tests/subdir/subdir2/mod2.ts" + ], + "size": 320 }, - "file://[WILDCARD]/subdir/print_hello.ts": { - "size": 63, - "deps": [] + "file:///[WILDCARD]/cli/tests/subdir/print_hello.ts": { + "deps": [], + "size": 63 }, - "file://[WILDCARD]/subdir/subdir2/mod2.ts": { - "size": 163, + "file:///[WILDCARD]/cli/tests/subdir/subdir2/mod2.ts": { "deps": [ - "file://[WILDCARD]/subdir/print_hello.ts" - ] + "file:///[WILDCARD]/cli/tests/subdir/print_hello.ts" + ], + "size": 163 } - } + }, + "local": "[WILDCARD]005_more_imports.ts", + "map": null, + "module": "file:///[WILDCARD]/cli/tests/005_more_imports.ts", + "totalSize": 757 }
\ No newline at end of file diff --git a/cli/tests/cafile_info.ts.out b/cli/tests/cafile_info.ts.out index 98b82df38..62fc631f1 100644 --- a/cli/tests/cafile_info.ts.out +++ b/cli/tests/cafile_info.ts.out @@ -1,12 +1,13 @@ local: [WILDCARD]https[WILDCARD]localhost_PORT5545[WILDCARD] type: TypeScript 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_application_x_javascript.j4.js ([WILDCARD]) +├── https://localhost:5545/cli/tests/subdir/mt_application_x_typescript.t4.ts ([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]) +├── https://localhost:5545/cli/tests/subdir/mt_text_javascript.j1.js ([WILDCARD]) +├── https://localhost:5545/cli/tests/subdir/mt_text_typescript.t1.ts ([WILDCARD]) +├── https://localhost:5545/cli/tests/subdir/mt_video_mp2t.t3.ts ([WILDCARD]) +└── https://localhost:5545/cli/tests/subdir/mt_video_vdn.t2.ts ([WILDCARD]) diff --git a/cli/tests/info_recursive_imports_test.out b/cli/tests/info_recursive_imports_test.out index 12fb0e7d3..4afd00544 100644 --- a/cli/tests/info_recursive_imports_test.out +++ b/cli/tests/info_recursive_imports_test.out @@ -1,11 +1,12 @@ 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 * + │ │ └── file://[WILDCARD]cli/tests/recursive_imports/common.ts [WILDCARD] + │ └── file://[WILDCARD]cli/tests/recursive_imports/common.ts [WILDCARD] + └── file://[WILDCARD]cli/tests/recursive_imports/common.ts [WILDCARD] |