summaryrefslogtreecommitdiff
path: root/cli/doc/printer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/doc/printer.rs')
-rw-r--r--cli/doc/printer.rs432
1 files changed, 432 insertions, 0 deletions
diff --git a/cli/doc/printer.rs b/cli/doc/printer.rs
new file mode 100644
index 000000000..a58f2fcdc
--- /dev/null
+++ b/cli/doc/printer.rs
@@ -0,0 +1,432 @@
+// 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)
+}