feat(cli): represent type dependencies in info (#9630)

Fixes #7927
This commit is contained in:
Kitson Kelly 2021-03-01 22:49:58 +11:00 committed by GitHub
parent 0dc89c0a79
commit 6dae627749
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 699 additions and 433 deletions

View File

@ -39,95 +39,107 @@ pub fn enable_ansi() {
BufferWriter::stdout(ColorChoice::AlwaysAnsi);
}
fn style(s: &str, colorspec: ColorSpec) -> impl fmt::Display {
fn style<S: AsRef<str>>(s: S, colorspec: ColorSpec) -> impl fmt::Display {
if !use_color() {
return String::from(s);
return String::from(s.as_ref());
}
let mut v = Vec::new();
let mut ansi_writer = Ansi::new(&mut v);
ansi_writer.set_color(&colorspec).unwrap();
ansi_writer.write_all(s.as_bytes()).unwrap();
ansi_writer.write_all(s.as_ref().as_bytes()).unwrap();
ansi_writer.reset().unwrap();
String::from_utf8_lossy(&v).into_owned()
}
pub fn red_bold(s: &str) -> impl fmt::Display {
pub fn red_bold<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_fg(Some(Red)).set_bold(true);
style(&s, style_spec)
style(s, style_spec)
}
pub fn green_bold(s: &str) -> impl fmt::Display {
pub fn green_bold<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_fg(Some(Green)).set_bold(true);
style(&s, style_spec)
style(s, style_spec)
}
pub fn italic_bold(s: &str) -> impl fmt::Display {
pub fn italic<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_italic(true);
style(s, style_spec)
}
pub fn italic_gray<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_fg(Some(Ansi256(8))).set_italic(true);
style(s, style_spec)
}
pub fn italic_bold<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_bold(true).set_italic(true);
style(&s, style_spec)
style(s, style_spec)
}
pub fn white_on_red(s: &str) -> impl fmt::Display {
pub fn white_on_red<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_bg(Some(Red)).set_fg(Some(White));
style(&s, style_spec)
style(s, style_spec)
}
pub fn black_on_green(s: &str) -> impl fmt::Display {
pub fn black_on_green<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_bg(Some(Green)).set_fg(Some(Black));
style(&s, style_spec)
style(s, style_spec)
}
pub fn yellow(s: &str) -> impl fmt::Display {
pub fn yellow<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_fg(Some(Yellow));
style(&s, style_spec)
style(s, style_spec)
}
pub fn cyan(s: &str) -> impl fmt::Display {
pub fn cyan<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_fg(Some(Cyan));
style(&s, style_spec)
style(s, style_spec)
}
pub fn red(s: &str) -> impl fmt::Display {
pub fn red<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_fg(Some(Red));
style(&s, style_spec)
style(s, style_spec)
}
pub fn green(s: &str) -> impl fmt::Display {
pub fn green<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_fg(Some(Green));
style(&s, style_spec)
style(s, style_spec)
}
pub fn bold(s: &str) -> impl fmt::Display {
pub fn bold<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_bold(true);
style(&s, style_spec)
style(s, style_spec)
}
pub fn gray(s: &str) -> impl fmt::Display {
pub fn gray<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_fg(Some(Ansi256(8)));
style(&s, style_spec)
style(s, style_spec)
}
pub fn italic_bold_gray(s: &str) -> impl fmt::Display {
pub fn italic_bold_gray<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec
.set_fg(Some(Ansi256(8)))
.set_bold(true)
.set_italic(true);
style(&s, style_spec)
style(s, style_spec)
}
pub fn intense_blue(s: &str) -> impl fmt::Display {
pub fn intense_blue<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_fg(Some(Blue)).set_intense(true);
style(&s, style_spec)
style(s, style_spec)
}

View File

@ -4,166 +4,239 @@ use crate::colors;
use crate::media_type::serialize_media_type;
use crate::media_type::MediaType;
use deno_core::resolve_url;
use deno_core::serde::Serialize;
use deno_core::serde::Serializer;
use deno_core::ModuleSpecifier;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::fmt;
use std::iter::Iterator;
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)]
const SIBLING_CONNECTOR: char = '├';
const LAST_SIBLING_CONNECTOR: char = '└';
const CHILD_DEPS_CONNECTOR: char = '┬';
const CHILD_NO_DEPS_CONNECTOR: char = '─';
const VERTICAL_CONNECTOR: char = '│';
const EMPTY_CONNECTOR: char = ' ';
#[derive(Debug, Serialize, Ord, PartialOrd, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
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)]
pub info: ModuleInfo,
pub local: PathBuf,
pub map: Option<PathBuf>,
pub module: ModuleSpecifier,
pub total_size: usize,
pub struct ModuleGraphInfoDep {
pub specifier: String,
pub is_dynamic: bool,
#[serde(rename = "code", skip_serializing_if = "Option::is_none")]
pub maybe_code: Option<ModuleSpecifier>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub maybe_type: Option<ModuleSpecifier>,
}
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 {
writeln!(
f,
"{} {}",
colors::bold("compiled:"),
compiled.to_string_lossy()
)?;
}
if let Some(ref map) = self.map {
writeln!(f, "{} {}", colors::bold("map:"), map.to_string_lossy())?;
}
writeln!(
f,
"{} {} unique {}",
colors::bold("deps:"),
self.dep_count,
colors::gray(&format!(
"(total {})",
human_size(self.info.total_size.unwrap_or(0) as f64)
))
)?;
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(())
}
}
/// Represents a unique dependency within the graph of the the dependencies for
/// a given module.
#[derive(Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ModuleInfo {
pub deps: Vec<ModuleInfo>,
pub name: ModuleSpecifier,
pub size: usize,
pub total_size: Option<usize>,
}
impl PartialOrd for ModuleInfo {
fn partial_cmp(&self, other: &ModuleInfo) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ModuleInfo {
fn cmp(&self, other: &ModuleInfo) -> Ordering {
self.name.to_string().cmp(&other.name.to_string())
}
}
impl ModuleInfo {
pub fn write_info(
impl ModuleGraphInfoDep {
fn write_info<S: AsRef<str> + fmt::Display + Clone>(
&self,
f: &mut fmt::Formatter<'_>,
prefix: &str,
f: &mut fmt::Formatter,
prefix: S,
last: bool,
modules: &[ModuleGraphInfoMod],
seen: &mut HashSet<ModuleSpecifier>,
) -> 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)))
let maybe_code = self
.maybe_code
.as_ref()
.and_then(|s| modules.iter().find(|m| &m.specifier == s));
let maybe_type = self
.maybe_type
.as_ref()
.and_then(|s| modules.iter().find(|m| &m.specifier == s));
match (maybe_code, maybe_type) {
(Some(code), Some(types)) => {
code.write_info(f, prefix.clone(), false, false, modules, seen)?;
types.write_info(f, prefix, last, true, modules, seen)
}
(Some(code), None) => {
code.write_info(f, prefix, last, false, modules, seen)
}
(None, Some(types)) => {
types.write_info(f, prefix, last, true, modules, seen)
}
_ => Ok(()),
}
}
}
#[derive(Debug, Serialize, Ord, PartialOrd, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ModuleGraphInfoMod {
pub specifier: ModuleSpecifier,
pub dependencies: Vec<ModuleGraphInfoDep>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<usize>,
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_media_type"
)]
pub media_type: Option<MediaType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub local: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub checksum: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub emit: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub map: Option<PathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
impl Default for ModuleGraphInfoMod {
fn default() -> Self {
ModuleGraphInfoMod {
specifier: resolve_url("https://deno.land/x/mod.ts").unwrap(),
dependencies: Vec::new(),
size: None,
media_type: None,
local: None,
checksum: None,
emit: None,
map: None,
error: None,
}
}
}
impl ModuleGraphInfoMod {
fn write_info<S: AsRef<str> + fmt::Display>(
&self,
f: &mut fmt::Formatter,
prefix: S,
last: bool,
type_dep: bool,
modules: &[ModuleGraphInfoMod],
seen: &mut HashSet<ModuleSpecifier>,
) -> fmt::Result {
let was_seen = seen.contains(&self.specifier);
let sibling_connector = if last {
LAST_SIBLING_CONNECTOR
} else {
colors::gray(" *")
SIBLING_CONNECTOR
};
let child_connector = if self.dependencies.is_empty() || was_seen {
CHILD_NO_DEPS_CONNECTOR
} else {
CHILD_DEPS_CONNECTOR
};
let (size, specifier) = if self.error.is_some() {
(
colors::red_bold(" (error)").to_string(),
colors::red(&self.specifier).to_string(),
)
} else if was_seen {
let name = if type_dep {
colors::italic_gray(&self.specifier).to_string()
} else {
colors::gray(&self.specifier).to_string()
};
(colors::gray(" *").to_string(), name)
} else {
let name = if type_dep {
colors::italic(&self.specifier).to_string()
} else {
self.specifier.to_string()
};
(
colors::gray(format!(
" ({})",
human_size(self.size.unwrap_or(0) as f64)
))
.to_string(),
name,
)
};
seen.insert(self.specifier.clone());
writeln!(
f,
"{} {}{}",
colors::gray(&format!(
colors::gray(format!(
"{}{}─{}",
prefix, sibling_connector, child_connector
)),
self.name,
totals
specifier,
size
)?;
let mut prefix = prefix.to_string();
if last {
prefix.push(' ');
} else {
prefix.push('│');
}
prefix.push(' ');
let dep_count = self.deps.len();
for (idx, dep) in self.deps.iter().enumerate() {
dep.write_info(f, &prefix, idx == dep_count - 1)?;
if !was_seen {
let mut prefix = prefix.to_string();
if last {
prefix.push(EMPTY_CONNECTOR);
} else {
prefix.push(VERTICAL_CONNECTOR);
}
prefix.push(EMPTY_CONNECTOR);
let dep_count = self.dependencies.len();
for (idx, dep) in self.dependencies.iter().enumerate() {
dep.write_info(f, &prefix, idx == dep_count - 1, modules, seen)?;
}
}
Ok(())
}
}
/// A flat map of dependencies for a given module graph.
#[derive(Debug)]
pub struct ModuleInfoMap(pub HashMap<ModuleSpecifier, ModuleInfoMapItem>);
impl ModuleInfoMap {
pub fn new(map: HashMap<ModuleSpecifier, ModuleInfoMapItem>) -> Self {
ModuleInfoMap(map)
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ModuleGraphInfo {
pub root: ModuleSpecifier,
pub modules: Vec<ModuleGraphInfoMod>,
pub size: usize,
}
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().map(|(k, v)| (k.to_string(), v)).collect();
ordered.serialize(serializer)
impl fmt::Display for ModuleGraphInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let root = self
.modules
.iter()
.find(|m| m.specifier == self.root)
.unwrap();
if let Some(err) = &root.error {
writeln!(f, "{} {}", colors::red("error:"), err)
} else {
if let Some(local) = &root.local {
writeln!(f, "{} {}", colors::bold("local:"), local.to_string_lossy())?;
}
if let Some(media_type) = &root.media_type {
writeln!(f, "{} {}", colors::bold("type:"), media_type)?;
}
if let Some(emit) = &root.emit {
writeln!(f, "{} {}", colors::bold("emit:"), emit.to_string_lossy())?;
}
if let Some(map) = &root.map {
writeln!(f, "{} {}", colors::bold("map:"), map.to_string_lossy())?;
}
let dep_count = self.modules.len() - 1;
writeln!(
f,
"{} {} unique {}",
colors::bold("dependencies:"),
dep_count,
colors::gray(format!("(total {})", human_size(self.size as f64)))
)?;
writeln!(
f,
"\n{} {}",
self.root,
colors::gray(format!(
"({})",
human_size(root.size.unwrap_or(0) as f64)
))
)?;
let mut seen = HashSet::new();
let dep_len = root.dependencies.len();
for (idx, dep) in root.dependencies.iter().enumerate() {
dep.write_info(f, "", idx == dep_len - 1, &self.modules, &mut seen)?;
}
Ok(())
}
}
}
@ -202,7 +275,7 @@ pub fn human_size(size: f64) -> String {
#[cfg(test)]
mod test {
use super::*;
use deno_core::resolve_url_or_path;
use deno_core::resolve_url;
use deno_core::serde_json::json;
#[test]
@ -219,58 +292,81 @@ mod test {
}
fn get_fixture() -> ModuleGraphInfo {
let spec_c = resolve_url_or_path("https://deno.land/x/a/b/c.ts").unwrap();
let spec_d = 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,
let specifier_a = resolve_url("https://deno.land/x/a.ts").unwrap();
let specifier_b = resolve_url("https://deno.land/x/b.ts").unwrap();
let specifier_c_js = resolve_url("https://deno.land/x/c.js").unwrap();
let specifier_c_dts = resolve_url("https://deno.land/x/c.d.ts").unwrap();
let modules = vec![
ModuleGraphInfoMod {
specifier: specifier_a.clone(),
dependencies: vec![ModuleGraphInfoDep {
specifier: "./b.ts".to_string(),
is_dynamic: false,
maybe_code: Some(specifier_b.clone()),
maybe_type: None,
}],
size: Some(123),
media_type: Some(MediaType::TypeScript),
local: Some(PathBuf::from("/cache/deps/https/deno.land/x/a.ts")),
checksum: Some("abcdef".to_string()),
emit: Some(PathBuf::from("/cache/emit/https/deno.land/x/a.js")),
..Default::default()
},
);
items.insert(
spec_d,
ModuleInfoMapItem {
deps: Vec::new(),
size: 12345,
ModuleGraphInfoMod {
specifier: specifier_b,
dependencies: vec![ModuleGraphInfoDep {
specifier: "./c.js".to_string(),
is_dynamic: false,
maybe_code: Some(specifier_c_js.clone()),
maybe_type: Some(specifier_c_dts.clone()),
}],
size: Some(456),
media_type: Some(MediaType::TypeScript),
local: Some(PathBuf::from("/cache/deps/https/deno.land/x/b.ts")),
checksum: Some("def123".to_string()),
emit: Some(PathBuf::from("/cache/emit/https/deno.land/x/b.js")),
..Default::default()
},
);
let files = ModuleInfoMap(items);
ModuleGraphInfoMod {
specifier: specifier_c_js,
size: Some(789),
media_type: Some(MediaType::JavaScript),
local: Some(PathBuf::from("/cache/deps/https/deno.land/x/c.js")),
checksum: Some("9876abcef".to_string()),
..Default::default()
},
ModuleGraphInfoMod {
specifier: specifier_c_dts,
size: Some(999),
media_type: Some(MediaType::Dts),
local: Some(PathBuf::from("/cache/deps/https/deno.land/x/c.d.ts")),
checksum: Some("a2b3c4d5".to_string()),
..Default::default()
},
];
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: resolve_url_or_path("https://deno.land/x/a/b/c.ts").unwrap(),
total_size: 999999,
root: specifier_a,
modules,
size: 99999,
}
}
#[test]
fn test_module_graph_info_display() {
fn text_module_graph_info_display() {
let fixture = get_fixture();
let actual = fixture.to_string();
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"));
let text = fixture.to_string();
let actual = colors::strip_ansi_codes(&text);
let expected = r#"local: /cache/deps/https/deno.land/x/a.ts
type: TypeScript
emit: /cache/emit/https/deno.land/x/a.js
dependencies: 3 unique (total 97.66KB)
https://deno.land/x/a.ts (123B)
https://deno.land/x/b.ts (456B)
https://deno.land/x/c.js (789B)
https://deno.land/x/c.d.ts (999B)
"#;
assert_eq!(actual, expected);
}
#[test]
@ -280,19 +376,57 @@ mod test {
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
"root": "https://deno.land/x/a.ts",
"modules": [
{
"specifier": "https://deno.land/x/a.ts",
"dependencies": [
{
"specifier": "./b.ts",
"isDynamic": false,
"code": "https://deno.land/x/b.ts"
}
],
"size": 123,
"mediaType": "TypeScript",
"local": "/cache/deps/https/deno.land/x/a.ts",
"checksum": "abcdef",
"emit": "/cache/emit/https/deno.land/x/a.js"
},
{
"specifier": "https://deno.land/x/b.ts",
"dependencies": [
{
"specifier": "./c.js",
"isDynamic": false,
"code": "https://deno.land/x/c.js",
"type": "https://deno.land/x/c.d.ts"
}
],
"size": 456,
"mediaType": "TypeScript",
"local": "/cache/deps/https/deno.land/x/b.ts",
"checksum": "def123",
"emit": "/cache/emit/https/deno.land/x/b.js"
},
{
"specifier": "https://deno.land/x/c.js",
"dependencies": [],
"size": 789,
"mediaType": "JavaScript",
"local": "/cache/deps/https/deno.land/x/c.js",
"checksum": "9876abcef"
},
{
"specifier": "https://deno.land/x/c.d.ts",
"dependencies": [],
"size": 999,
"mediaType": "Dts",
"local": "/cache/deps/https/deno.land/x/c.d.ts",
"checksum": "a2b3c4d5"
}
},
"local": "/a/b/c.ts",
"map": null,
"module": "https://deno.land/x/a/b/c.ts",
"totalSize": 999999
],
"size": 99999
})
);
}

View File

@ -386,11 +386,11 @@ async fn info_command(
let info = graph.info()?;
if json {
write_json_to_stdout(&json!(info))?;
write_json_to_stdout(&json!(info))
} else {
write_to_stdout_ignore_sigpipe(info.to_string().as_bytes())?;
write_to_stdout_ignore_sigpipe(info.to_string().as_bytes())
.map_err(|err| err.into())
}
Ok(())
} else {
// If it was just "deno info" print location of caches and exit
print_cache_info(&program_state, json)

View File

@ -11,7 +11,7 @@ use std::path::PathBuf;
// Update carefully!
#[allow(non_camel_case_types)]
#[repr(i32)]
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Debug)]
pub enum MediaType {
JavaScript = 0,
JSX = 1,
@ -184,11 +184,17 @@ impl Serialize for MediaType {
/// 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>
pub fn serialize_media_type<S>(
mmt: &Option<MediaType>,
s: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_str(&mt.to_string())
match *mmt {
Some(ref mt) => s.serialize_some(&mt.to_string()),
None => s.serialize_none(),
}
}
#[cfg(test)]

View File

@ -6,13 +6,11 @@ use crate::ast::transpile_module;
use crate::ast::BundleHook;
use crate::ast::Location;
use crate::ast::ParsedModule;
use crate::checksum;
use crate::colors;
use crate::diagnostics::Diagnostics;
use crate::import_map::ImportMap;
use crate::info::ModuleGraphInfo;
use crate::info::ModuleInfo;
use crate::info::ModuleInfoMap;
use crate::info::ModuleInfoMapItem;
use crate::info;
use crate::lockfile::Lockfile;
use crate::media_type::MediaType;
use crate::specifier_handler::CachedModule;
@ -44,8 +42,8 @@ use deno_core::ModuleResolutionError;
use deno_core::ModuleSource;
use deno_core::ModuleSpecifier;
use regex::Regex;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::{BTreeSet, HashMap};
use std::error::Error;
use std::fmt;
use std::path::PathBuf;
@ -1193,95 +1191,6 @@ impl Graph {
Ok(())
}
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 = match self.get_module(specifier) {
ModuleSlot::Module(module) => module,
ModuleSlot::Err(err) => {
error!("{}: {}", colors::red_bold("error"), err.to_string());
std::process::exit(1);
}
_ => unreachable!(),
};
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()
.filter_map(|(specifier, module_slot)| {
if let ModuleSlot::Module(module) = module_slot {
let mut deps = BTreeSet::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(),
};
Some((specifier.clone(), item))
} else {
None
}
})
.collect();
ModuleInfoMap::new(map)
}
/// Retrieve a map that contains a representation of each module in the graph
/// which can be used to provide code to a module loader without holding all
/// the state to be able to operate on the graph.
@ -1425,51 +1334,75 @@ impl Graph {
/// 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> {
pub fn info(&self) -> Result<info::ModuleGraphInfo, AnyError> {
if self.roots.is_empty() || self.roots.len() > 1 {
return Err(GraphError::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 = if let ModuleSlot::Module(module) = self.get_module(&module) {
module
} else {
return Err(GraphError::MissingSpecifier(module.clone()).into());
};
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();
let (compiled, map) =
if let Some((emit_path, maybe_map_path)) = &m.maybe_emit_path {
(Some(emit_path.clone()), maybe_map_path.clone())
} else {
(None, None)
};
let dep_count = self
let root = self.resolve_specifier(&self.roots[0]).clone();
let mut modules: Vec<info::ModuleGraphInfoMod> = self
.modules
.iter()
.filter_map(|(_, m)| match m {
ModuleSlot::Module(_) => Some(1),
.filter_map(|(sp, sl)| match sl {
ModuleSlot::Module(module) => {
let mut dependencies: Vec<info::ModuleGraphInfoDep> = module
.dependencies
.iter()
.map(|(k, v)| info::ModuleGraphInfoDep {
specifier: k.clone(),
is_dynamic: v.is_dynamic,
maybe_code: v
.maybe_code
.clone()
.map(|s| self.resolve_specifier(&s).clone()),
maybe_type: v
.maybe_type
.clone()
.map(|s| self.resolve_specifier(&s).clone()),
})
.collect();
dependencies.sort();
let (emit, map) =
if let Some((emit, maybe_map)) = &module.maybe_emit_path {
(Some(emit.clone()), maybe_map.clone())
} else {
(None, None)
};
Some(info::ModuleGraphInfoMod {
specifier: sp.clone(),
dependencies,
size: Some(module.size()),
media_type: Some(module.media_type),
local: Some(module.source_path.clone()),
checksum: Some(checksum::gen(&[module.source.as_bytes()])),
emit,
map,
..Default::default()
})
}
ModuleSlot::Err(err) => Some(info::ModuleGraphInfoMod {
specifier: sp.clone(),
error: Some(err.to_string()),
..Default::default()
}),
_ => None,
})
.count()
- 1;
.collect();
Ok(ModuleGraphInfo {
compiled,
dep_count,
file_type: m.media_type,
files,
info,
local: m.source_path.clone(),
map,
module,
total_size,
modules.sort();
let size = modules.iter().fold(0_usize, |acc, m| {
if let Some(size) = &m.size {
acc + size
} else {
acc
}
});
Ok(info::ModuleGraphInfo {
root,
modules,
size,
})
}
@ -2514,19 +2447,11 @@ pub mod tests {
async fn test_graph_info() {
let specifier = resolve_url_or_path("file:///tests/main.ts")
.expect("could not resolve module");
let (graph, _) = setup(specifier).await;
let (graph, _) = setup(specifier.clone()).await;
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,
resolve_url_or_path("file:///tests/main.ts").unwrap()
);
assert_eq!(info.total_size, 344);
assert_eq!(info.root, specifier);
assert_eq!(info.modules.len(), 7);
assert_eq!(info.size, 518);
}
#[tokio::test]

View File

@ -1,6 +1,6 @@
local: [WILDCARD]017_import_redirect.ts
type: TypeScript
deps: 1 unique (total [WILDCARD]B)
dependencies: 1 unique (total 278B)
file:///[WILDCARD]cli/tests/017_import_redirect.ts ([WILDCARD])
└── http://gist.githubusercontent.com/ry/f12b2aa3409e6b52645bc346a9e22929/raw/79318f239f51d764384a8bded8d7c6a833610dde/print_hello.ts ([WILDCARD])
└── https://gist.githubusercontent.com/ry/f12b2aa3409e6b52645bc346a9e22929/raw/79318f239f51d764384a8bded8d7c6a833610dde/print_hello.ts ([WILDCARD])

View File

@ -1,7 +1,7 @@
[WILDCARD]
local: [WILDCARD]http[WILDCARD]127.0.0.1_PORT4545[WILDCARD]
type: TypeScript
deps: 8 unique (total [WILDCARD])
dependencies: 8 unique (total [WILDCARD])
http://127.0.0.1:4545/cli/tests/019_media_types.ts ([WILDCARD])
├── http://localhost:4545/cli/tests/subdir/mt_application_ecmascript.j2.js ([WILDCARD])

View File

@ -1,5 +1,5 @@
[WILDCARD]
local: [WILDCARD]031_info_ts_error.ts
type: TypeScript
deps: 0 unique (total [WILDCARD])
dependencies: 0 unique (total [WILDCARD])
[WILDCARD]031_info_ts_error.ts ([WILDCARD])

View File

@ -1,7 +1,7 @@
[WILDCARD]
local: [WILDCARD]http[WILDCARD]127.0.0.1_PORT4545[WILDCARD]
type: TypeScript
deps: 8 unique (total [WILDCARD])
dependencies: 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_application_ecmascript_jsx.j2.jsx ([WILDCARD])

View File

@ -1,6 +1,6 @@
local: [WILDCARD]005_more_imports.ts
type: TypeScript
deps: 3 unique (total [WILDCARD])
dependencies: 3 unique (total [WILDCARD])
file://[WILDCARD]/005_more_imports.ts ([WILDCARD])
└─┬ file://[WILDCARD]/subdir/mod1.ts ([WILDCARD])

View File

@ -1,33 +1,56 @@
{
"compiled": null,
"depCount": 3,
"fileType": "TypeScript",
"files": {
"file:///[WILDCARD]/cli/tests/005_more_imports.ts": {
"deps": [
"file:///[WILDCARD]/cli/tests/subdir/mod1.ts"
"root": "file://[WILDCARD]/cli/tests/005_more_imports.ts",
"modules": [
{
"specifier": "file://[WILDCARD]/cli/tests/005_more_imports.ts",
"dependencies": [
{
"specifier": "./subdir/mod1.ts",
"isDynamic": false,
"code": "file://[WILDCARD]/cli/tests/subdir/mod1.ts"
}
],
"size": 211
"size": 211,
"mediaType": "TypeScript",
"local": "[WILDCARD]005_more_imports.ts",
[WILDCARD]
},
"file:///[WILDCARD]/cli/tests/subdir/mod1.ts": {
"deps": [
"file:///[WILDCARD]/cli/tests/subdir/subdir2/mod2.ts"
{
"specifier": "file://[WILDCARD]/cli/tests/subdir/mod1.ts",
"dependencies": [
{
"specifier": "./subdir2/mod2.ts",
"isDynamic": false,
"code": "file://[WILDCARD]/cli/tests/subdir/subdir2/mod2.ts"
}
],
"size": 320
"size": 320,
"mediaType": "TypeScript",
"local": "[WILDCARD]mod1.ts",
[WILDCARD]
},
"file:///[WILDCARD]/cli/tests/subdir/print_hello.ts": {
"deps": [],
"size": 63
{
"specifier": "file://[WILDCARD]/cli/tests/subdir/print_hello.ts",
"dependencies": [],
"size": 63,
"mediaType": "TypeScript",
"local": "[WILDCARD]print_hello.ts",
[WILDCARD]
},
"file:///[WILDCARD]/cli/tests/subdir/subdir2/mod2.ts": {
"deps": [
"file:///[WILDCARD]/cli/tests/subdir/print_hello.ts"
{
"specifier": "file://[WILDCARD]/cli/tests/subdir/subdir2/mod2.ts",
"dependencies": [
{
"specifier": "../print_hello.ts",
"isDynamic": false,
"code": "file://[WILDCARD]/cli/tests/subdir/print_hello.ts"
}
],
"size": 163
"size": 163,
"mediaType": "TypeScript",
"local": "[WILDCARD]mod2.ts",
[WILDCARD]
}
},
"local": "[WILDCARD]005_more_imports.ts",
"map": null,
"module": "file:///[WILDCARD]/cli/tests/005_more_imports.ts",
"totalSize": 757
],
"size": 757
}

View File

@ -1,5 +1,5 @@
[WILDCARD]
local: [WILDCARD]test.ts
type: TypeScript
deps: 7 unique (total [WILDCARD])
dependencies: 7 unique (total [WILDCARD])
[WILDCARD]

View File

@ -1,38 +1,85 @@
{
"compiled": null,
"depCount": 4,
"fileType": "TypeScript",
"files": {
"[WILDCARD]cli/tests/076_info_json_deps_order.ts": {
"deps": [
"[WILDCARD]cli/tests/recursive_imports/A.ts"
"root": "file://[WILDCARD]/cli/tests/076_info_json_deps_order.ts",
"modules": [
{
"specifier": "file://[WILDCARD]/cli/tests/076_info_json_deps_order.ts",
"dependencies": [
{
"specifier": "./recursive_imports/A.ts",
"isDynamic": false,
"code": "file://[WILDCARD]/cli/tests/recursive_imports/A.ts"
}
],
"size": [WILDCARD]
"size": 46,
"mediaType": "TypeScript",
"local": "[WILDCARD]076_info_json_deps_order.ts",
"checksum": "88b144f362d31ac42263648aadef727dd36d039d3b8ac0248fdaff25d4de415a"
},
"[WILDCARD]cli/tests/recursive_imports/A.ts": {
"deps": [
"[WILDCARD]cli/tests/recursive_imports/B.ts",
"[WILDCARD]cli/tests/recursive_imports/common.ts"
{
"specifier": "file://[WILDCARD]/cli/tests/recursive_imports/A.ts",
"dependencies": [
{
"specifier": "./B.ts",
"isDynamic": false,
"code": "file://[WILDCARD]/cli/tests/recursive_imports/B.ts"
},
{
"specifier": "./common.ts",
"isDynamic": false,
"code": "file://[WILDCARD]/cli/tests/recursive_imports/common.ts"
}
],
"size": [WILDCARD]
"size": 114,
"mediaType": "TypeScript",
"local": "[WILDCARD]A.ts",
"checksum": "da204c16d3114763810864083af8891a887d65fbe34e4c8b5bf985dbc8f0b01f"
},
"[WILDCARD]cli/tests/recursive_imports/B.ts": {
"deps": [
"[WILDCARD]cli/tests/recursive_imports/C.ts",
"[WILDCARD]cli/tests/recursive_imports/common.ts"
{
"specifier": "file://[WILDCARD]/cli/tests/recursive_imports/B.ts",
"dependencies": [
{
"specifier": "./C.ts",
"isDynamic": false,
"code": "file://[WILDCARD]/cli/tests/recursive_imports/C.ts"
},
{
"specifier": "./common.ts",
"isDynamic": false,
"code": "file://[WILDCARD]/cli/tests/recursive_imports/common.ts"
}
],
"size": [WILDCARD]
"size": 114,
"mediaType": "TypeScript",
"local": "[WILDCARD]B.ts",
"checksum": "060ef62435d7e3a3276e8894307b19cf17772210a20dd091d24a670fadec6b83"
},
"[WILDCARD]cli/tests/recursive_imports/C.ts": {
"deps": [
"[WILDCARD]cli/tests/recursive_imports/A.ts",
"[WILDCARD]cli/tests/recursive_imports/common.ts"
{
"specifier": "file://[WILDCARD]/cli/tests/recursive_imports/C.ts",
"dependencies": [
{
"specifier": "./A.ts",
"isDynamic": false,
"code": "file://[WILDCARD]/cli/tests/recursive_imports/A.ts"
},
{
"specifier": "./common.ts",
"isDynamic": false,
"code": "file://[WILDCARD]/cli/tests/recursive_imports/common.ts"
}
],
"size": [WILDCARD]
"size": 132,
"mediaType": "TypeScript",
"local": "[WILDCARD]C.ts",
"checksum": "5190563583617a69f190f1cc76e6552df878df278cfaa5d5e30ebe0938cf5e0b"
},
"[WILDCARD]cli/tests/recursive_imports/common.ts": {
"deps": [],
"size": [WILDCARD]
{
"specifier": "file://[WILDCARD]/cli/tests/recursive_imports/common.ts",
"dependencies": [],
"size": 34,
"mediaType": "TypeScript",
"local": "[WILDCARD]common.ts",
"checksum": "01b595d69514bfd001ba2cf421feabeaef559513f10697bf1a22781f8a8ed7f0"
}
},
[WILDCARD]
],
"size": 440
}

View File

@ -1,2 +1,6 @@
error: Cannot resolve module "file://[WILDCARD]/bad-module.js" from "file://[WILDCARD]/error_009_missing_js_module.js".
at file://[WILDCARD]/error_009_missing_js_module.js:1:0
local: [WILDCARD]error_009_missing_js_module.js
type: JavaScript
dependencies: 1 unique (total 26B)
file://[WILDCARD]/cli/tests/error_009_missing_js_module.js (26B)
└── file://[WILDCARD]/cli/tests/bad-module.js (error)

View File

@ -1,12 +1,12 @@
local: [WILDCARD]info_recursive_imports_test.ts
type: TypeScript
deps: 4 unique (total [WILDCARD])
dependencies: 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 [WILDCARD]
└── 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 *
└── file://[WILDCARD]cli/tests/recursive_imports/common.ts *

View File

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

View File

@ -1732,7 +1732,7 @@ mod integration {
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!(str_output.contains("emit: "));
assert_eq!(output.stderr, b"");
}
@ -3720,7 +3720,6 @@ console.log("finish");
itest!(info_missing_module {
args: "info error_009_missing_js_module.js",
output: "info_missing_module.out",
exit_code: 1,
});
itest!(info_recursive_modules {

View File

@ -521,7 +521,7 @@ impl CoverageReporter for PrettyCoverageReporter {
// Put a horizontal separator between disjoint runs of lines
if let Some(last_line) = last_line {
if last_line + 1 != line_index {
let dash = colors::gray(&"-".repeat(WIDTH + 1));
let dash = colors::gray("-".repeat(WIDTH + 1));
println!("{}{}{}", dash, colors::gray(SEPERATOR), dash);
}
}

View File

@ -0,0 +1,116 @@
{
"$id": "https://deno.land/schemas/module-graph.json",
"$schema": "http://json-schema.org/draft-07/schema",
"description": "A JSON representation of a Deno module dependency graph.",
"required": [
"root",
"modules",
"size"
],
"title": "Deno Dependency Graph Schema",
"type": "object",
"properties": {
"root": {
"default": "",
"description": "The root specifier for the graph.",
"examples": [
"https://deno.land/x/mod.ts"
],
"type": "string"
},
"modules": {
"default": [],
"description": "The modules that are part of the graph.",
"type": "array",
"items": {
"$ref": "#/definitions/module"
}
},
"size": {
"type": "integer",
"description": "The total size of all the unique dependencies in the graph in bytes.",
"default": 0
}
},
"definitions": {
"module": {
"type": "object",
"required": [
"specifier"
],
"properties": {
"specifier": {
"type": "string",
"description": "The fully qualified module specifier (URL) for the module."
},
"dependencies": {
"type": "array",
"description": "An array of dependencies of the module.",
"items": {
"$ref": "#/definitions/dependency"
}
},
"size": {
"type": "integer",
"description": "The size of the module on disk in bytes."
},
"mediaType": {
"type": "string",
"description": "How the file is treated within Deno. All the possible media types that Deno considers are listed here, but in practice, several of them would never appear in a module graph.",
"enum": [
"JavaScript",
"TypeScript",
"JSX",
"TSX",
"Dts",
"Json",
"Wasm",
"TsBuildInfo",
"SourceMap",
"Unknown"
]
},
"local": {
"type": "string",
"description": "The path to the local file. For local modules this will be the local file path, for remote modules and data URLs, this would be the path to the file in the Deno cache."
},
"checksum": {
"type": "string",
"description": "The checksum of the local source file. This can be used to validate if the current on disk version matches the version described here."
},
"emit": {
"type": "string",
"description": "The path to an emitted version of the module, if the module requires transpilation to be loaded into the Deno runtime."
},
"map": {
"type": "string",
"description": "The path to an optionally emitted source map between the original and emitted version of the file."
},
"error": {
"type": "string",
"description": "If when resolving the module, Deno encountered an error and the module is unavailable, the text of that error will be indicated here."
}
}
},
"dependency": {
"type": "object",
"required": [
"specifier"
],
"properties": {
"specifier": {
"type": "string",
"description": "The specifier provided from within the module."
},
"code": {
"type": "string",
"description": "The fully qualified module specifier (URL) for the code dependency."
},
"type": {
"type": "string",
"description": "The fully qualified module specifier (URL) for the type only dependency."
}
}
}
}
}