deno 1.11.0

Provides the deno executable
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

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::ModuleSpecifier;
use std::collections::HashSet;
use std::fmt;
use std::iter::Iterator;
use std::path::PathBuf;

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 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 ModuleGraphInfoDep {
  fn write_info<S: AsRef<str> + fmt::Display + Clone>(
    &self,
    f: &mut fmt::Formatter,
    prefix: S,
    last: bool,
    modules: &[ModuleGraphInfoMod],
    seen: &mut HashSet<ModuleSpecifier>,
  ) -> fmt::Result {
    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 {
      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!(
        "{}{}{}",
        prefix, sibling_connector, child_connector
      )),
      specifier,
      size
    )?;

    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(())
  }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ModuleGraphInfo {
  pub root: ModuleSpecifier,
  pub modules: Vec<ModuleGraphInfoMod>,
  pub size: usize,
}

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(())
    }
  }
}

/// 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,
}

/// 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 size < 1_f64 {
    return format!("{}{}{}", negative, size, "B");
  }
  let delimiter = 1024_f64;
  let exponent = std::cmp::min(
    (size.ln() / delimiter.ln()).floor() as i32,
    (units.len() - 1) as i32,
  );
  let pretty_bytes = format!("{:.2}", size / 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 deno_core::resolve_url;
  use deno_core::serde_json::json;

  #[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");
  }

  fn get_fixture() -> ModuleGraphInfo {
    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()
      },
      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()
      },
      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 {
      root: specifier_a,
      modules,
      size: 99999,
    }
  }

  #[test]
  fn text_module_graph_info_display() {
    let fixture = get_fixture();
    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]
  fn test_module_graph_info_json() {
    let fixture = get_fixture();
    let actual = json!(fixture);
    assert_eq!(
      actual,
      json!({
        "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"
          }
        ],
        "size": 99999
      })
    );
  }
}