1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-01 09:24:20 -04:00
denoland-deno/cli/doc/printer.rs
2020-03-28 14:16:57 -04:00

432 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::doc;
use crate::doc::ts_type::TsTypeDefKind;
use crate::doc::DocNodeKind;
pub fn format(doc_nodes: Vec<doc::DocNode>) -> String {
format_(doc_nodes, 0)
}
pub fn format_details(node: doc::DocNode) -> String {
let mut details = String::new();
details.push_str(&format!(
"Defined in {}:{}:{}.\n",
node.location.filename, node.location.line, node.location.col
));
details.push_str(&format_signature(&node, 0));
let js_doc = node.js_doc.clone();
if let Some(js_doc) = js_doc {
details.push_str(&format_jsdoc(js_doc, false, 1));
}
details.push_str("\n");
let maybe_extra = match node.kind {
DocNodeKind::Class => Some(format_class_details(node)),
DocNodeKind::Namespace => Some(format_namespace_details(node)),
_ => None,
};
if let Some(extra) = maybe_extra {
details.push_str(&extra);
}
details
}
fn kind_order(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(node: &doc::DocNode, indent: i64) -> String {
match node.kind {
DocNodeKind::Function => format_function_signature(&node, indent),
DocNodeKind::Variable => format_variable_signature(&node, indent),
DocNodeKind::Class => format_class_signature(&node, indent),
DocNodeKind::Enum => format_enum_signature(&node, indent),
DocNodeKind::Interface => format_interface_signature(&node, indent),
DocNodeKind::TypeAlias => format_type_alias_signature(&node, indent),
DocNodeKind::Namespace => format_namespace_signature(&node, indent),
}
}
fn format_(doc_nodes: Vec<doc::DocNode>, indent: i64) -> String {
let mut sorted = doc_nodes;
sorted.sort_unstable_by(|a, b| {
let kind_cmp = kind_order(&a.kind).cmp(&kind_order(&b.kind));
if kind_cmp == core::cmp::Ordering::Equal {
a.name.cmp(&b.name)
} else {
kind_cmp
}
});
let mut output = String::new();
for node in sorted {
output.push_str(&format_signature(&node, indent));
if node.js_doc.is_some() {
output.push_str(&format_jsdoc(
node.js_doc.as_ref().unwrap().to_string(),
true,
indent,
));
}
output.push_str("\n");
if DocNodeKind::Namespace == node.kind {
output.push_str(&format_(
node.namespace_def.as_ref().unwrap().elements.clone(),
indent + 1,
));
output.push_str("\n");
};
}
output
}
fn render_params(params: Vec<doc::ParamDef>) -> String {
let mut rendered = String::from("");
if !params.is_empty() {
for param in params {
rendered += param.name.as_str();
if param.ts_type.is_some() {
rendered += ": ";
rendered += render_ts_type(param.ts_type.unwrap()).as_str();
}
rendered += ", ";
}
rendered.truncate(rendered.len() - 2);
}
rendered
}
fn render_ts_type(ts_type: doc::ts_type::TsTypeDef) -> String {
let kind = ts_type.kind.unwrap();
match kind {
TsTypeDefKind::Array => {
format!("{}[]", render_ts_type(*ts_type.array.unwrap()))
}
TsTypeDefKind::Conditional => {
let conditional = ts_type.conditional_type.unwrap();
format!(
"{} extends {} ? {} : {}",
render_ts_type(*conditional.check_type),
render_ts_type(*conditional.extends_type),
render_ts_type(*conditional.true_type),
render_ts_type(*conditional.false_type)
)
}
TsTypeDefKind::FnOrConstructor => {
let fn_or_constructor = ts_type.fn_or_constructor.unwrap();
format!(
"{}({}) => {}",
if fn_or_constructor.constructor {
"new "
} else {
""
},
render_params(fn_or_constructor.params),
render_ts_type(fn_or_constructor.ts_type),
)
}
TsTypeDefKind::IndexedAccess => {
let indexed_access = ts_type.indexed_access.unwrap();
format!(
"{}[{}]",
render_ts_type(*indexed_access.obj_type),
render_ts_type(*indexed_access.index_type)
)
}
TsTypeDefKind::Intersection => {
let intersection = ts_type.intersection.unwrap();
let mut output = "".to_string();
if !intersection.is_empty() {
for ts_type in intersection {
output += render_ts_type(ts_type).as_str();
output += " & "
}
output.truncate(output.len() - 3);
}
output
}
TsTypeDefKind::Keyword => ts_type.keyword.unwrap(),
TsTypeDefKind::Literal => {
let literal = ts_type.literal.unwrap();
match literal.kind {
doc::ts_type::LiteralDefKind::Boolean => {
format!("{}", literal.boolean.unwrap())
}
doc::ts_type::LiteralDefKind::String => {
"\"".to_string() + literal.string.unwrap().as_str() + "\""
}
doc::ts_type::LiteralDefKind::Number => {
format!("{}", literal.number.unwrap())
}
}
}
TsTypeDefKind::Optional => "_optional_".to_string(),
TsTypeDefKind::Parenthesized => {
format!("({})", render_ts_type(*ts_type.parenthesized.unwrap()))
}
TsTypeDefKind::Rest => {
format!("...{}", render_ts_type(*ts_type.rest.unwrap()))
}
TsTypeDefKind::This => "this".to_string(),
TsTypeDefKind::Tuple => {
let tuple = ts_type.tuple.unwrap();
let mut output = "".to_string();
if !tuple.is_empty() {
for ts_type in tuple {
output += render_ts_type(ts_type).as_str();
output += ", "
}
output.truncate(output.len() - 2);
}
output
}
TsTypeDefKind::TypeLiteral => {
let mut output = "".to_string();
let type_literal = ts_type.type_literal.unwrap();
for node in type_literal.call_signatures {
output += format!(
"({}): {}, ",
render_params(node.params),
render_ts_type(node.ts_type.unwrap())
)
.as_str()
}
for node in type_literal.methods {
output += format!(
"{}({}): {}, ",
node.name,
render_params(node.params),
render_ts_type(node.return_type.unwrap())
)
.as_str()
}
for node in type_literal.properties {
output +=
format!("{}: {}, ", node.name, render_ts_type(node.ts_type.unwrap()))
.as_str()
}
if !output.is_empty() {
output.truncate(output.len() - 2);
}
"{ ".to_string() + output.as_str() + " }"
}
TsTypeDefKind::TypeOperator => {
let operator = ts_type.type_operator.unwrap();
format!("{} {}", operator.operator, render_ts_type(operator.ts_type))
}
TsTypeDefKind::TypeQuery => {
format!("typeof {}", ts_type.type_query.unwrap())
}
TsTypeDefKind::TypeRef => {
let type_ref = ts_type.type_ref.unwrap();
let mut final_output = type_ref.type_name;
if type_ref.type_params.is_some() {
let mut output = "".to_string();
let type_params = type_ref.type_params.unwrap();
if !type_params.is_empty() {
for ts_type in type_params {
output += render_ts_type(ts_type).as_str();
output += ", "
}
output.truncate(output.len() - 2);
}
final_output += format!("<{}>", output).as_str();
}
final_output
}
TsTypeDefKind::Union => {
let union = ts_type.union.unwrap();
let mut output = "".to_string();
if !union.is_empty() {
for ts_type in union {
output += render_ts_type(ts_type).as_str();
output += " | "
}
output.truncate(output.len() - 3);
}
output
}
}
}
fn format_indent(indent: i64) -> String {
let mut indent_str = String::new();
for _ in 0..indent {
indent_str.push_str(" ");
}
indent_str
}
// TODO: this should use some sort of markdown to console parser.
fn format_jsdoc(jsdoc: String, truncated: bool, indent: i64) -> String {
let mut lines = jsdoc.split("\n\n").map(|line| line.replace("\n", " "));
let mut js_doc = String::new();
if truncated {
let first_line = lines.next().unwrap_or_else(|| "".to_string());
js_doc.push_str(&format_indent(indent + 1));
js_doc.push_str(&format!("{}\n", first_line));
} else {
for line in lines {
js_doc.push_str(&format_indent(indent + 1));
js_doc.push_str(&format!("{}\n", line));
}
}
js_doc
}
fn format_class_details(node: doc::DocNode) -> String {
let mut details = String::new();
let class_def = node.class_def.unwrap();
for node in class_def.constructors {
details.push_str(&format!(
"constructor {}({})\n",
node.name,
render_params(node.params),
));
}
for node in class_def.properties.iter().filter(|node| {
node
.accessibility
.unwrap_or(swc_ecma_ast::Accessibility::Public)
!= swc_ecma_ast::Accessibility::Private
}) {
details.push_str(&format!(
"{} {}: {}\n",
match node
.accessibility
.unwrap_or(swc_ecma_ast::Accessibility::Public)
{
swc_ecma_ast::Accessibility::Protected => "protected".to_string(),
swc_ecma_ast::Accessibility::Public => "public".to_string(),
_ => "".to_string(),
},
node.name,
render_ts_type(node.ts_type.clone().unwrap())
));
}
for node in class_def.methods.iter().filter(|node| {
node
.accessibility
.unwrap_or(swc_ecma_ast::Accessibility::Public)
!= swc_ecma_ast::Accessibility::Private
}) {
let function_def = node.function_def.clone();
details.push_str(&format!(
"{} {}{}({}): {}\n",
match node
.accessibility
.unwrap_or(swc_ecma_ast::Accessibility::Public)
{
swc_ecma_ast::Accessibility::Protected => "protected".to_string(),
swc_ecma_ast::Accessibility::Public => "public".to_string(),
_ => "".to_string(),
},
match node.kind {
swc_ecma_ast::MethodKind::Getter => "get ".to_string(),
swc_ecma_ast::MethodKind::Setter => "set ".to_string(),
_ => "".to_string(),
},
node.name,
render_params(function_def.params),
render_ts_type(function_def.return_type.unwrap())
));
}
details.push_str("\n");
details
}
fn format_namespace_details(node: doc::DocNode) -> String {
let mut ns = String::new();
let elements = node.namespace_def.unwrap().elements;
for node in elements {
ns.push_str(&format_signature(&node, 0));
}
ns.push_str("\n");
ns
}
fn format_function_signature(node: &doc::DocNode, indent: i64) -> String {
format_indent(indent);
let function_def = node.function_def.clone().unwrap();
let return_type = function_def.return_type.unwrap();
format!(
"function {}({}): {}\n",
node.name,
render_params(function_def.params),
render_ts_type(return_type).as_str()
)
}
fn format_class_signature(node: &doc::DocNode, indent: i64) -> String {
format_indent(indent);
format!("class {}\n", node.name)
}
fn format_variable_signature(node: &doc::DocNode, indent: i64) -> String {
format_indent(indent);
let variable_def = node.variable_def.clone().unwrap();
format!(
"{} {}{}\n",
match variable_def.kind {
swc_ecma_ast::VarDeclKind::Const => "const".to_string(),
swc_ecma_ast::VarDeclKind::Let => "let".to_string(),
swc_ecma_ast::VarDeclKind::Var => "var".to_string(),
},
node.name,
if variable_def.ts_type.is_some() {
format!(": {}", render_ts_type(variable_def.ts_type.unwrap()))
} else {
"".to_string()
}
)
}
fn format_enum_signature(node: &doc::DocNode, indent: i64) -> String {
format_indent(indent);
format!("enum {}\n", node.name)
}
fn format_interface_signature(node: &doc::DocNode, indent: i64) -> String {
format_indent(indent);
format!("interface {}\n", node.name)
}
fn format_type_alias_signature(node: &doc::DocNode, indent: i64) -> String {
format_indent(indent);
format!("type {}\n", node.name)
}
fn format_namespace_signature(node: &doc::DocNode, indent: i64) -> String {
format_indent(indent);
format!("namespace {}\n", node.name)
}