1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-26 16:09:27 -05:00
denoland-deno/cli/doc/printer.rs
Valentin Anger 3374c73fba
feat(doc): Improve terminal printer (#6594)
- Add more support for generics
- Add the --private flag - displays documentation for
  not exported and private nodes
- Display more attributes like abstract, static and readonly
- Display type aliases
- Refactor module to use the Display trait
- Use a bit more color
2020-07-12 14:16:33 +02:00

474 lines
12 KiB
Rust

// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
// TODO(ry) This module builds up output by appending to a string. Instead it
// should either use a formatting trait
// https://doc.rust-lang.org/std/fmt/index.html#formatting-traits
// Or perhaps implement a Serializer for serde
// https://docs.serde.rs/serde/ser/trait.Serializer.html
// TODO(ry) The methods in this module take ownership of the DocNodes, this is
// unnecessary and can result in unnecessary copying. Instead they should take
// references.
use crate::colors;
use crate::doc;
use crate::doc::display::{
display_abstract, display_async, display_generator, Indent, SliceDisplayer,
};
use crate::doc::DocNodeKind;
use crate::swc_ecma_ast;
use std::fmt::{Display, Formatter, Result as FmtResult};
pub struct DocPrinter<'a> {
doc_nodes: &'a [doc::DocNode],
details: bool,
private: bool,
}
impl<'a> DocPrinter<'a> {
pub fn new(
doc_nodes: &[doc::DocNode],
details: bool,
private: bool,
) -> DocPrinter {
DocPrinter {
doc_nodes,
details,
private,
}
}
pub fn format(&self, w: &mut Formatter<'_>) -> FmtResult {
if self.details {
self.format_details(w, self.doc_nodes, 0)
} else {
self.format_summary(w, self.doc_nodes, 0)
}
}
fn format_summary(
&self,
w: &mut Formatter<'_>,
doc_nodes: &[doc::DocNode],
indent: i64,
) -> FmtResult {
let mut sorted = Vec::from(doc_nodes);
sorted.sort_unstable_by(|a, b| {
let kind_cmp = self.kind_order(&a.kind).cmp(&self.kind_order(&b.kind));
if kind_cmp == core::cmp::Ordering::Equal {
a.name.cmp(&b.name)
} else {
kind_cmp
}
});
for node in sorted {
self.format_signature(w, &node, indent)?;
if let Some(js_doc) = &node.js_doc {
self.format_jsdoc(w, js_doc, indent + 1, self.details)?;
}
writeln!(w)?;
if DocNodeKind::Namespace == node.kind {
self.format_summary(
w,
&node.namespace_def.as_ref().unwrap().elements,
indent + 1,
)?;
writeln!(w)?;
};
}
Ok(())
}
fn format_details(
&self,
w: &mut Formatter<'_>,
doc_nodes: &[doc::DocNode],
indent: i64,
) -> FmtResult {
for node in doc_nodes {
write!(
w,
"{}",
colors::italic_gray(&format!(
"Defined in {}:{}:{} \n\n",
node.location.filename, node.location.line, node.location.col
))
)?;
self.format_signature(w, &node, indent)?;
let js_doc = &node.js_doc;
if let Some(js_doc) = js_doc {
self.format_jsdoc(w, js_doc, indent + 1, self.details)?;
}
writeln!(w)?;
match node.kind {
DocNodeKind::Class => self.format_class_details(w, node)?,
DocNodeKind::Enum => self.format_enum_details(w, node)?,
DocNodeKind::Interface => self.format_interface_details(w, node)?,
DocNodeKind::Namespace => self.format_namespace_details(w, node)?,
_ => {}
}
}
Ok(())
}
fn kind_order(&self, kind: &doc::DocNodeKind) -> i64 {
match kind {
DocNodeKind::Function => 0,
DocNodeKind::Variable => 1,
DocNodeKind::Class => 2,
DocNodeKind::Enum => 3,
DocNodeKind::Interface => 4,
DocNodeKind::TypeAlias => 5,
DocNodeKind::Namespace => 6,
}
}
fn format_signature(
&self,
w: &mut Formatter<'_>,
node: &doc::DocNode,
indent: i64,
) -> FmtResult {
match node.kind {
DocNodeKind::Function => self.format_function_signature(w, node, indent),
DocNodeKind::Variable => self.format_variable_signature(w, node, indent),
DocNodeKind::Class => self.format_class_signature(w, node, indent),
DocNodeKind::Enum => self.format_enum_signature(w, node, indent),
DocNodeKind::Interface => {
self.format_interface_signature(w, node, indent)
}
DocNodeKind::TypeAlias => {
self.format_type_alias_signature(w, node, indent)
}
DocNodeKind::Namespace => {
self.format_namespace_signature(w, node, indent)
}
}
}
// TODO(SyrupThinker) this should use a JSDoc parser
fn format_jsdoc(
&self,
w: &mut Formatter<'_>,
jsdoc: &str,
indent: i64,
details: bool,
) -> FmtResult {
for line in jsdoc.lines() {
// Only show the first paragraph when summarising
// This should use the @summary JSDoc tag instead
if !details && line.is_empty() {
break;
}
writeln!(w, "{}{}", Indent(indent), colors::gray(&line))?;
}
Ok(())
}
fn format_class_details(
&self,
w: &mut Formatter<'_>,
node: &doc::DocNode,
) -> FmtResult {
let class_def = node.class_def.as_ref().unwrap();
for node in &class_def.constructors {
writeln!(w, "{}{}", Indent(1), node,)?;
if let Some(js_doc) = &node.js_doc {
self.format_jsdoc(w, &js_doc, 2, self.details)?;
}
}
for node in class_def.properties.iter().filter(|node| {
self.private
|| node
.accessibility
.unwrap_or(swc_ecma_ast::Accessibility::Public)
!= swc_ecma_ast::Accessibility::Private
}) {
writeln!(w, "{}{}", Indent(1), node,)?;
if let Some(js_doc) = &node.js_doc {
self.format_jsdoc(w, &js_doc, 2, self.details)?;
}
}
for index_sign_def in &class_def.index_signatures {
writeln!(w, "{}{}", Indent(1), index_sign_def)?;
}
for node in class_def.methods.iter().filter(|node| {
self.private
|| node
.accessibility
.unwrap_or(swc_ecma_ast::Accessibility::Public)
!= swc_ecma_ast::Accessibility::Private
}) {
writeln!(w, "{}{}", Indent(1), node,)?;
if let Some(js_doc) = &node.js_doc {
self.format_jsdoc(w, js_doc, 2, self.details)?;
}
}
writeln!(w)
}
fn format_enum_details(
&self,
w: &mut Formatter<'_>,
node: &doc::DocNode,
) -> FmtResult {
let enum_def = node.enum_def.as_ref().unwrap();
for member in &enum_def.members {
writeln!(w, "{}{}", Indent(1), colors::bold(&member.name))?;
}
writeln!(w)
}
fn format_interface_details(
&self,
w: &mut Formatter<'_>,
node: &doc::DocNode,
) -> FmtResult {
let interface_def = node.interface_def.as_ref().unwrap();
for property_def in &interface_def.properties {
writeln!(w, "{}{}", Indent(1), property_def)?;
if let Some(js_doc) = &property_def.js_doc {
self.format_jsdoc(w, js_doc, 2, self.details)?;
}
}
for method_def in &interface_def.methods {
writeln!(w, "{}{}", Indent(1), method_def)?;
if let Some(js_doc) = &method_def.js_doc {
self.format_jsdoc(w, js_doc, 2, self.details)?;
}
}
for index_sign_def in &interface_def.index_signatures {
writeln!(w, "{}{}", Indent(1), index_sign_def)?;
}
writeln!(w)
}
fn format_namespace_details(
&self,
w: &mut Formatter<'_>,
node: &doc::DocNode,
) -> FmtResult {
let elements = &node.namespace_def.as_ref().unwrap().elements;
for node in elements {
self.format_signature(w, &node, 1)?;
if let Some(js_doc) = &node.js_doc {
self.format_jsdoc(w, js_doc, 2, false)?;
}
}
writeln!(w)
}
fn format_class_signature(
&self,
w: &mut Formatter<'_>,
node: &doc::DocNode,
indent: i64,
) -> FmtResult {
let class_def = node.class_def.as_ref().unwrap();
write!(
w,
"{}{}{} {}",
Indent(indent),
display_abstract(class_def.is_abstract),
colors::magenta("class"),
colors::bold(&node.name),
)?;
if !class_def.type_params.is_empty() {
write!(
w,
"<{}>",
SliceDisplayer::new(&class_def.type_params, ", ", false)
)?;
}
if let Some(extends) = &class_def.extends {
write!(w, " {} {}", colors::magenta("extends"), extends)?;
}
if !class_def.super_type_params.is_empty() {
write!(
w,
"<{}>",
SliceDisplayer::new(&class_def.super_type_params, ", ", false)
)?;
}
if !class_def.implements.is_empty() {
write!(
w,
" {} {}",
colors::magenta("implements"),
SliceDisplayer::new(&class_def.implements, ", ", false)
)?;
}
writeln!(w)
}
fn format_enum_signature(
&self,
w: &mut Formatter<'_>,
node: &doc::DocNode,
indent: i64,
) -> FmtResult {
writeln!(
w,
"{}{} {}",
Indent(indent),
colors::magenta("enum"),
colors::bold(&node.name)
)
}
fn format_function_signature(
&self,
w: &mut Formatter<'_>,
node: &doc::DocNode,
indent: i64,
) -> FmtResult {
let function_def = node.function_def.as_ref().unwrap();
write!(
w,
"{}{}{}{} {}",
Indent(indent),
display_async(function_def.is_async),
colors::magenta("function"),
display_generator(function_def.is_generator),
colors::bold(&node.name)
)?;
if !function_def.type_params.is_empty() {
write!(
w,
"<{}>",
SliceDisplayer::new(&function_def.type_params, ", ", false)
)?;
}
write!(
w,
"({})",
SliceDisplayer::new(&function_def.params, ", ", false)
)?;
if let Some(return_type) = &function_def.return_type {
write!(w, ": {}", return_type)?;
}
writeln!(w)
}
fn format_interface_signature(
&self,
w: &mut Formatter<'_>,
node: &doc::DocNode,
indent: i64,
) -> FmtResult {
let interface_def = node.interface_def.as_ref().unwrap();
write!(
w,
"{}{} {}",
Indent(indent),
colors::magenta("interface"),
colors::bold(&node.name)
)?;
if !interface_def.type_params.is_empty() {
write!(
w,
"<{}>",
SliceDisplayer::new(&interface_def.type_params, ", ", false)
)?;
}
if !interface_def.extends.is_empty() {
write!(
w,
" {} {}",
colors::magenta("extends"),
SliceDisplayer::new(&interface_def.extends, ", ", false)
)?;
}
writeln!(w)
}
fn format_type_alias_signature(
&self,
w: &mut Formatter<'_>,
node: &doc::DocNode,
indent: i64,
) -> FmtResult {
let type_alias_def = node.type_alias_def.as_ref().unwrap();
write!(
w,
"{}{} {}",
Indent(indent),
colors::magenta("type"),
colors::bold(&node.name),
)?;
if !type_alias_def.type_params.is_empty() {
write!(
w,
"<{}>",
SliceDisplayer::new(&type_alias_def.type_params, ", ", false)
)?;
}
writeln!(w, " = {}", type_alias_def.ts_type)
}
fn format_namespace_signature(
&self,
w: &mut Formatter<'_>,
node: &doc::DocNode,
indent: i64,
) -> FmtResult {
writeln!(
w,
"{}{} {}",
Indent(indent),
colors::magenta("namespace"),
colors::bold(&node.name)
)
}
fn format_variable_signature(
&self,
w: &mut Formatter<'_>,
node: &doc::DocNode,
indent: i64,
) -> FmtResult {
let variable_def = node.variable_def.as_ref().unwrap();
write!(
w,
"{}{} {}",
Indent(indent),
colors::magenta(match variable_def.kind {
swc_ecma_ast::VarDeclKind::Const => "const",
swc_ecma_ast::VarDeclKind::Let => "let",
swc_ecma_ast::VarDeclKind::Var => "var",
}),
colors::bold(&node.name),
)?;
if let Some(ts_type) = &variable_def.ts_type {
write!(w, ": {}", ts_type)?;
}
writeln!(w)
}
}
impl<'a> Display for DocPrinter<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
self.format(f)
}
}