summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBartek IwaƄczuk <biwanczuk@gmail.com>2020-03-28 19:16:57 +0100
committerGitHub <noreply@github.com>2020-03-28 14:16:57 -0400
commit3fac487461abf055165fe0e2bb962573950277b8 (patch)
treec0ba09a2049975f9eb8625365e3ba395e67d8b50
parentbced52505f32d6cca4f944bb610a8a26767908a8 (diff)
feat: Add "deno doc" subcommand (#4500)
-rw-r--r--Cargo.lock4
-rw-r--r--cli/Cargo.toml7
-rw-r--r--cli/doc/class.rs207
-rw-r--r--cli/doc/enum.rs41
-rw-r--r--cli/doc/function.rs70
-rw-r--r--cli/doc/interface.rs243
-rw-r--r--cli/doc/mod.rs63
-rw-r--r--cli/doc/module.rs189
-rw-r--r--cli/doc/namespace.rs81
-rw-r--r--cli/doc/node.rs77
-rw-r--r--cli/doc/parser.rs189
-rw-r--r--cli/doc/printer.rs432
-rw-r--r--cli/doc/tests.rs568
-rw-r--r--cli/doc/ts_type.rs821
-rw-r--r--cli/doc/type_alias.rs25
-rw-r--r--cli/doc/variable.rs43
-rw-r--r--cli/flags.rs97
-rw-r--r--cli/lib.rs80
18 files changed, 3233 insertions, 4 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 6a178bf03..ca7a0a367 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -493,6 +493,10 @@ dependencies = [
"serde_derive",
"serde_json",
"sourcemap",
+ "swc_common",
+ "swc_ecma_ast",
+ "swc_ecma_parser",
+ "swc_ecma_parser_macros",
"sys-info",
"tempfile",
"termcolor",
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 1483324bf..acabcf7d0 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -63,9 +63,14 @@ webpki-roots = "0.19.0"
walkdir = "2.3.1"
warp = "0.2.2"
semver-parser = "0.9.0"
+# TODO(bartlomieju): make sure we're using exactly same versions
+# of "swc_*" as dprint-plugin-typescript
+swc_common = "=0.5.9"
+swc_ecma_ast = "=0.18.1"
+swc_ecma_parser = "=0.21.8"
+swc_ecma_parser_macros = "=0.4.1"
uuid = { version = "0.8", features = ["v4"] }
-
[target.'cfg(windows)'.dependencies]
winapi = "0.3.8"
fwdansi = "1.1.0"
diff --git a/cli/doc/class.rs b/cli/doc/class.rs
new file mode 100644
index 000000000..635fd585a
--- /dev/null
+++ b/cli/doc/class.rs
@@ -0,0 +1,207 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use serde::Serialize;
+use swc_common;
+use swc_common::SourceMap;
+use swc_common::Spanned;
+use swc_ecma_ast;
+
+use super::function::function_to_function_def;
+use super::function::FunctionDef;
+use super::parser::DocParser;
+use super::ts_type::ts_type_ann_to_def;
+use super::ts_type::TsTypeDef;
+use super::Location;
+use super::ParamDef;
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct ClassConstructorDef {
+ pub js_doc: Option<String>,
+ pub accessibility: Option<swc_ecma_ast::Accessibility>,
+ pub name: String,
+ pub params: Vec<ParamDef>,
+ pub location: Location,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct ClassPropertyDef {
+ pub js_doc: Option<String>,
+ pub ts_type: Option<TsTypeDef>,
+ pub readonly: bool,
+ pub accessibility: Option<swc_ecma_ast::Accessibility>,
+ pub is_abstract: bool,
+ pub is_static: bool,
+ pub name: String,
+ pub location: Location,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct ClassMethodDef {
+ pub js_doc: Option<String>,
+ // pub ts_type: Option<TsTypeDef>,
+ // pub readonly: bool,
+ pub accessibility: Option<swc_ecma_ast::Accessibility>,
+ pub is_abstract: bool,
+ pub is_static: bool,
+ pub name: String,
+ pub kind: swc_ecma_ast::MethodKind,
+ pub function_def: FunctionDef,
+ pub location: Location,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct ClassDef {
+ // TODO: decorators, super_class, implements,
+ // type_params, super_type_params
+ pub is_abstract: bool,
+ pub constructors: Vec<ClassConstructorDef>,
+ pub properties: Vec<ClassPropertyDef>,
+ pub methods: Vec<ClassMethodDef>,
+}
+
+fn prop_name_to_string(
+ source_map: &SourceMap,
+ prop_name: &swc_ecma_ast::PropName,
+) -> String {
+ use swc_ecma_ast::PropName;
+ match prop_name {
+ PropName::Ident(ident) => ident.sym.to_string(),
+ PropName::Str(str_) => str_.value.to_string(),
+ PropName::Num(num) => num.value.to_string(),
+ PropName::Computed(comp_prop_name) => {
+ source_map.span_to_snippet(comp_prop_name.span).unwrap()
+ }
+ }
+}
+
+pub fn get_doc_for_class_decl(
+ doc_parser: &DocParser,
+ class_decl: &swc_ecma_ast::ClassDecl,
+) -> (String, ClassDef) {
+ let mut constructors = vec![];
+ let mut methods = vec![];
+ let mut properties = vec![];
+
+ for member in &class_decl.class.body {
+ use swc_ecma_ast::ClassMember::*;
+
+ match member {
+ Constructor(ctor) => {
+ let ctor_js_doc = doc_parser.js_doc_for_span(ctor.span());
+ let constructor_name =
+ prop_name_to_string(&doc_parser.source_map, &ctor.key);
+
+ let mut params = vec![];
+
+ for param in &ctor.params {
+ use swc_ecma_ast::Pat;
+ use swc_ecma_ast::PatOrTsParamProp::*;
+
+ let param_def = match param {
+ Pat(pat) => match pat {
+ Pat::Ident(ident) => {
+ let ts_type = ident
+ .type_ann
+ .as_ref()
+ .map(|rt| ts_type_ann_to_def(&doc_parser.source_map, rt));
+
+ ParamDef {
+ name: ident.sym.to_string(),
+ ts_type,
+ }
+ }
+ _ => ParamDef {
+ name: "<TODO>".to_string(),
+ ts_type: None,
+ },
+ },
+ TsParamProp(_) => ParamDef {
+ name: "<TODO>".to_string(),
+ ts_type: None,
+ },
+ };
+ params.push(param_def);
+ }
+
+ let constructor_def = ClassConstructorDef {
+ js_doc: ctor_js_doc,
+ accessibility: ctor.accessibility,
+ name: constructor_name,
+ params,
+ location: doc_parser
+ .source_map
+ .lookup_char_pos(ctor.span.lo())
+ .into(),
+ };
+ constructors.push(constructor_def);
+ }
+ Method(class_method) => {
+ let method_js_doc = doc_parser.js_doc_for_span(class_method.span());
+ let method_name =
+ prop_name_to_string(&doc_parser.source_map, &class_method.key);
+ let fn_def =
+ function_to_function_def(doc_parser, &class_method.function);
+ let method_def = ClassMethodDef {
+ js_doc: method_js_doc,
+ accessibility: class_method.accessibility,
+ is_abstract: class_method.is_abstract,
+ is_static: class_method.is_static,
+ name: method_name,
+ kind: class_method.kind,
+ function_def: fn_def,
+ location: doc_parser
+ .source_map
+ .lookup_char_pos(class_method.span.lo())
+ .into(),
+ };
+ methods.push(method_def);
+ }
+ ClassProp(class_prop) => {
+ let prop_js_doc = doc_parser.js_doc_for_span(class_prop.span());
+
+ let ts_type = class_prop
+ .type_ann
+ .as_ref()
+ .map(|rt| ts_type_ann_to_def(&doc_parser.source_map, rt));
+
+ use swc_ecma_ast::Expr;
+ let prop_name = match &*class_prop.key {
+ Expr::Ident(ident) => ident.sym.to_string(),
+ _ => "<TODO>".to_string(),
+ };
+
+ let prop_def = ClassPropertyDef {
+ js_doc: prop_js_doc,
+ ts_type,
+ readonly: class_prop.readonly,
+ is_abstract: class_prop.is_abstract,
+ is_static: class_prop.is_static,
+ accessibility: class_prop.accessibility,
+ name: prop_name,
+ location: doc_parser
+ .source_map
+ .lookup_char_pos(class_prop.span.lo())
+ .into(),
+ };
+ properties.push(prop_def);
+ }
+ // TODO:
+ TsIndexSignature(_) => {}
+ PrivateMethod(_) => {}
+ PrivateProp(_) => {}
+ }
+ }
+
+ let class_name = class_decl.ident.sym.to_string();
+ let class_def = ClassDef {
+ is_abstract: class_decl.class.is_abstract,
+ constructors,
+ properties,
+ methods,
+ };
+
+ (class_name, class_def)
+}
diff --git a/cli/doc/enum.rs b/cli/doc/enum.rs
new file mode 100644
index 000000000..f71c15537
--- /dev/null
+++ b/cli/doc/enum.rs
@@ -0,0 +1,41 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use serde::Serialize;
+use swc_ecma_ast;
+
+use super::parser::DocParser;
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct EnumMemberDef {
+ pub name: String,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct EnumDef {
+ pub members: Vec<EnumMemberDef>,
+}
+
+pub fn get_doc_for_ts_enum_decl(
+ _doc_parser: &DocParser,
+ enum_decl: &swc_ecma_ast::TsEnumDecl,
+) -> (String, EnumDef) {
+ let enum_name = enum_decl.id.sym.to_string();
+ let mut members = vec![];
+
+ for enum_member in &enum_decl.members {
+ use swc_ecma_ast::TsEnumMemberId::*;
+
+ let member_name = match &enum_member.id {
+ Ident(ident) => ident.sym.to_string(),
+ Str(str_) => str_.value.to_string(),
+ };
+
+ let member_def = EnumMemberDef { name: member_name };
+ members.push(member_def);
+ }
+
+ let enum_def = EnumDef { members };
+
+ (enum_name, enum_def)
+}
diff --git a/cli/doc/function.rs b/cli/doc/function.rs
new file mode 100644
index 000000000..ec7f9bf38
--- /dev/null
+++ b/cli/doc/function.rs
@@ -0,0 +1,70 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use serde::Serialize;
+use swc_ecma_ast;
+
+use super::parser::DocParser;
+use super::ts_type::ts_type_ann_to_def;
+use super::ts_type::TsTypeDef;
+use super::ParamDef;
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct FunctionDef {
+ pub params: Vec<ParamDef>,
+ pub return_type: Option<TsTypeDef>,
+ pub is_async: bool,
+ pub is_generator: bool,
+ // TODO: type_params, decorators
+}
+
+pub fn function_to_function_def(
+ doc_parser: &DocParser,
+ function: &swc_ecma_ast::Function,
+) -> FunctionDef {
+ let mut params = vec![];
+
+ for param in &function.params {
+ use swc_ecma_ast::Pat;
+
+ let param_def = match param {
+ Pat::Ident(ident) => {
+ let ts_type = ident
+ .type_ann
+ .as_ref()
+ .map(|rt| ts_type_ann_to_def(&doc_parser.source_map, rt));
+
+ ParamDef {
+ name: ident.sym.to_string(),
+ ts_type,
+ }
+ }
+ _ => ParamDef {
+ name: "<TODO>".to_string(),
+ ts_type: None,
+ },
+ };
+
+ params.push(param_def);
+ }
+
+ let maybe_return_type = function
+ .return_type
+ .as_ref()
+ .map(|rt| ts_type_ann_to_def(&doc_parser.source_map, rt));
+
+ FunctionDef {
+ params,
+ return_type: maybe_return_type,
+ is_async: function.is_async,
+ is_generator: function.is_generator,
+ }
+}
+
+pub fn get_doc_for_fn_decl(
+ doc_parser: &DocParser,
+ fn_decl: &swc_ecma_ast::FnDecl,
+) -> (String, FunctionDef) {
+ let name = fn_decl.ident.sym.to_string();
+ let fn_def = function_to_function_def(doc_parser, &fn_decl.function);
+ (name, fn_def)
+}
diff --git a/cli/doc/interface.rs b/cli/doc/interface.rs
new file mode 100644
index 000000000..b7e123773
--- /dev/null
+++ b/cli/doc/interface.rs
@@ -0,0 +1,243 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use serde::Serialize;
+use swc_ecma_ast;
+
+use super::parser::DocParser;
+use super::ts_type::ts_type_ann_to_def;
+use super::ts_type::TsTypeDef;
+use super::Location;
+use super::ParamDef;
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct InterfaceMethodDef {
+ // TODO: type_params
+ pub name: String,
+ pub location: Location,
+ pub js_doc: Option<String>,
+ pub params: Vec<ParamDef>,
+ pub return_type: Option<TsTypeDef>,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct InterfacePropertyDef {
+ // TODO: type_params
+ pub name: String,
+ pub location: Location,
+ pub js_doc: Option<String>,
+ pub params: Vec<ParamDef>,
+ pub computed: bool,
+ pub optional: bool,
+ pub ts_type: Option<TsTypeDef>,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct InterfaceCallSignatureDef {
+ // TODO: type_params
+ pub location: Location,
+ pub js_doc: Option<String>,
+ pub params: Vec<ParamDef>,
+ pub ts_type: Option<TsTypeDef>,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct InterfaceDef {
+ // TODO: extends, type params
+ pub methods: Vec<InterfaceMethodDef>,
+ pub properties: Vec<InterfacePropertyDef>,
+ pub call_signatures: Vec<InterfaceCallSignatureDef>,
+}
+
+fn expr_to_name(expr: &swc_ecma_ast::Expr) -> String {
+ use swc_ecma_ast::Expr::*;
+ use swc_ecma_ast::ExprOrSuper::*;
+
+ match expr {
+ Ident(ident) => ident.sym.to_string(),
+ Member(member_expr) => {
+ let left = match &member_expr.obj {
+ Super(_) => "TODO".to_string(),
+ Expr(boxed_expr) => expr_to_name(&*boxed_expr),
+ };
+ let right = expr_to_name(&*member_expr.prop);
+ format!("[{}.{}]", left, right)
+ }
+ _ => "<TODO>".to_string(),
+ }
+}
+
+pub fn get_doc_for_ts_interface_decl(
+ doc_parser: &DocParser,
+ interface_decl: &swc_ecma_ast::TsInterfaceDecl,
+) -> (String, InterfaceDef) {
+ let interface_name = interface_decl.id.sym.to_string();
+
+ let mut methods = vec![];
+ let mut properties = vec![];
+ let mut call_signatures = vec![];
+
+ for type_element in &interface_decl.body.body {
+ use swc_ecma_ast::TsTypeElement::*;
+
+ match &type_element {
+ TsMethodSignature(ts_method_sig) => {
+ let method_js_doc = doc_parser.js_doc_for_span(ts_method_sig.span);
+
+ let mut params = vec![];
+
+ for param in &ts_method_sig.params {
+ use swc_ecma_ast::TsFnParam::*;
+
+ let param_def = match param {
+ Ident(ident) => {
+ let ts_type = ident
+ .type_ann
+ .as_ref()
+ .map(|rt| ts_type_ann_to_def(&doc_parser.source_map, rt));
+
+ ParamDef {
+ name: ident.sym.to_string(),
+ ts_type,
+ }
+ }
+ _ => ParamDef {
+ name: "<TODO>".to_string(),
+ ts_type: None,
+ },
+ };
+
+ params.push(param_def);
+ }
+
+ let name = expr_to_name(&*ts_method_sig.key);
+
+ let maybe_return_type = ts_method_sig
+ .type_ann
+ .as_ref()
+ .map(|rt| ts_type_ann_to_def(&doc_parser.source_map, rt));
+
+ let method_def = InterfaceMethodDef {
+ name,
+ js_doc: method_js_doc,
+ location: doc_parser
+ .source_map
+ .lookup_char_pos(ts_method_sig.span.lo())
+ .into(),
+ params,
+ return_type: maybe_return_type,
+ };
+ methods.push(method_def);
+ }
+ TsPropertySignature(ts_prop_sig) => {
+ let prop_js_doc = doc_parser.js_doc_for_span(ts_prop_sig.span);
+ let name = match &*ts_prop_sig.key {
+ swc_ecma_ast::Expr::Ident(ident) => ident.sym.to_string(),
+ _ => "TODO".to_string(),
+ };
+
+ let mut params = vec![];
+
+ for param in &ts_prop_sig.params {
+ use swc_ecma_ast::TsFnParam::*;
+
+ let param_def = match param {
+ Ident(ident) => {
+ let ts_type = ident
+ .type_ann
+ .as_ref()
+ .map(|rt| ts_type_ann_to_def(&doc_parser.source_map, rt));
+
+ ParamDef {
+ name: ident.sym.to_string(),
+ ts_type,
+ }
+ }
+ _ => ParamDef {
+ name: "<TODO>".to_string(),
+ ts_type: None,
+ },
+ };
+
+ params.push(param_def);
+ }
+
+ let ts_type = ts_prop_sig
+ .type_ann
+ .as_ref()
+ .map(|rt| ts_type_ann_to_def(&doc_parser.source_map, rt));
+
+ let prop_def = InterfacePropertyDef {
+ name,
+ js_doc: prop_js_doc,
+ location: doc_parser
+ .source_map
+ .lookup_char_pos(ts_prop_sig.span.lo())
+ .into(),
+ params,
+ ts_type,
+ computed: ts_prop_sig.computed,
+ optional: ts_prop_sig.optional,
+ };
+ properties.push(prop_def);
+ }
+ TsCallSignatureDecl(ts_call_sig) => {
+ let call_sig_js_doc = doc_parser.js_doc_for_span(ts_call_sig.span);
+
+ let mut params = vec![];
+ for param in &ts_call_sig.params {
+ use swc_ecma_ast::TsFnParam::*;
+
+ let param_def = match param {
+ Ident(ident) => {
+ let ts_type = ident
+ .type_ann
+ .as_ref()
+ .map(|rt| ts_type_ann_to_def(&doc_parser.source_map, rt));
+
+ ParamDef {
+ name: ident.sym.to_string(),
+ ts_type,
+ }
+ }
+ _ => ParamDef {
+ name: "<TODO>".to_string(),
+ ts_type: None,
+ },
+ };
+
+ params.push(param_def);
+ }
+
+ let ts_type = ts_call_sig
+ .type_ann
+ .as_ref()
+ .map(|rt| ts_type_ann_to_def(&doc_parser.source_map, rt));
+
+ let call_sig_def = InterfaceCallSignatureDef {
+ js_doc: call_sig_js_doc,
+ location: doc_parser
+ .source_map
+ .lookup_char_pos(ts_call_sig.span.lo())
+ .into(),
+ params,
+ ts_type,
+ };
+ call_signatures.push(call_sig_def);
+ }
+ // TODO:
+ TsConstructSignatureDecl(_) => {}
+ TsIndexSignature(_) => {}
+ }
+ }
+
+ let interface_def = InterfaceDef {
+ methods,
+ properties,
+ call_signatures,
+ };
+
+ (interface_name, interface_def)
+}
diff --git a/cli/doc/mod.rs b/cli/doc/mod.rs
new file mode 100644
index 000000000..4926dccd7
--- /dev/null
+++ b/cli/doc/mod.rs
@@ -0,0 +1,63 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+pub mod class;
+pub mod r#enum;
+pub mod function;
+pub mod interface;
+pub mod module;
+pub mod namespace;
+mod node;
+pub mod parser;
+pub mod printer;
+pub mod ts_type;
+pub mod type_alias;
+pub mod variable;
+
+pub use node::DocNode;
+pub use node::DocNodeKind;
+pub use node::Location;
+pub use node::ParamDef;
+pub use parser::DocParser;
+
+#[cfg(test)]
+mod tests;
+
+pub fn find_node_by_name_recursively(
+ doc_nodes: Vec<DocNode>,
+ name: String,
+) -> Option<DocNode> {
+ let mut parts = name.splitn(2, '.');
+ let name = parts.next();
+ let leftover = parts.next();
+ name?;
+ let node = find_node_by_name(doc_nodes, name.unwrap().to_string());
+ match node {
+ Some(node) => match node.kind {
+ DocNodeKind::Namespace => {
+ if let Some(leftover) = leftover {
+ find_node_by_name_recursively(
+ node.namespace_def.unwrap().elements,
+ leftover.to_string(),
+ )
+ } else {
+ Some(node)
+ }
+ }
+ _ => {
+ if leftover.is_none() {
+ Some(node)
+ } else {
+ None
+ }
+ }
+ },
+ _ => None,
+ }
+}
+
+fn find_node_by_name(doc_nodes: Vec<DocNode>, name: String) -> Option<DocNode> {
+ let node = doc_nodes.iter().find(|node| node.name == name);
+ match node {
+ Some(node) => Some(node.clone()),
+ None => None,
+ }
+}
diff --git a/cli/doc/module.rs b/cli/doc/module.rs
new file mode 100644
index 000000000..e6c97771a
--- /dev/null
+++ b/cli/doc/module.rs
@@ -0,0 +1,189 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use swc_common;
+use swc_common::Spanned;
+use swc_ecma_ast;
+
+use super::parser::DocParser;
+use super::DocNode;
+use super::DocNodeKind;
+
+pub fn get_doc_node_for_export_decl(
+ doc_parser: &DocParser,
+ export_decl: &swc_ecma_ast::ExportDecl,
+) -> DocNode {
+ let export_span = export_decl.span();
+ use swc_ecma_ast::Decl;
+
+ let js_doc = doc_parser.js_doc_for_span(export_span);
+ let location = doc_parser
+ .source_map
+ .lookup_char_pos(export_span.lo())
+ .into();
+
+ match &export_decl.decl {
+ Decl::Class(class_decl) => {
+ let (name, class_def) =
+ super::class::get_doc_for_class_decl(doc_parser, class_decl);
+ DocNode {
+ kind: DocNodeKind::Class,
+ name,
+ location,
+ js_doc,
+ class_def: Some(class_def),
+ function_def: None,
+ variable_def: None,
+ enum_def: None,
+ type_alias_def: None,
+ namespace_def: None,
+ interface_def: None,
+ }
+ }
+ Decl::Fn(fn_decl) => {
+ let (name, function_def) =
+ super::function::get_doc_for_fn_decl(doc_parser, fn_decl);
+ DocNode {
+ kind: DocNodeKind::Function,
+ name,
+ location,
+ js_doc,
+ function_def: Some(function_def),
+ class_def: None,
+ variable_def: None,
+ enum_def: None,
+ type_alias_def: None,
+ namespace_def: None,
+ interface_def: None,
+ }
+ }
+ Decl::Var(var_decl) => {
+ let (name, var_def) =
+ super::variable::get_doc_for_var_decl(doc_parser, var_decl);
+ DocNode {
+ kind: DocNodeKind::Variable,
+ name,
+ location,
+ js_doc,
+ variable_def: Some(var_def),
+ function_def: None,
+ class_def: None,
+ enum_def: None,
+ type_alias_def: None,
+ namespace_def: None,
+ interface_def: None,
+ }
+ }
+ Decl::TsInterface(ts_interface_decl) => {
+ let (name, interface_def) =
+ super::interface::get_doc_for_ts_interface_decl(
+ doc_parser,
+ ts_interface_decl,
+ );
+ DocNode {
+ kind: DocNodeKind::Interface,
+ name,
+ location,
+ js_doc,
+ interface_def: Some(interface_def),
+ variable_def: None,
+ function_def: None,
+ class_def: None,
+ enum_def: None,
+ type_alias_def: None,
+ namespace_def: None,
+ }
+ }
+ Decl::TsTypeAlias(ts_type_alias) => {
+ let (name, type_alias_def) =
+ super::type_alias::get_doc_for_ts_type_alias_decl(
+ doc_parser,
+ ts_type_alias,
+ );
+ DocNode {
+ kind: DocNodeKind::TypeAlias,
+ name,
+ location,
+ js_doc,
+ type_alias_def: Some(type_alias_def),
+ interface_def: None,
+ variable_def: None,
+ function_def: None,
+ class_def: None,
+ enum_def: None,
+ namespace_def: None,
+ }
+ }
+ Decl::TsEnum(ts_enum) => {
+ let (name, enum_def) =
+ super::r#enum::get_doc_for_ts_enum_decl(doc_parser, ts_enum);
+ DocNode {
+ kind: DocNodeKind::Enum,
+ name,
+ location,
+ js_doc,
+ enum_def: Some(enum_def),
+ type_alias_def: None,
+ interface_def: None,
+ variable_def: None,
+ function_def: None,
+ class_def: None,
+ namespace_def: None,
+ }
+ }
+ Decl::TsModule(ts_module) => {
+ let (name, namespace_def) =
+ super::namespace::get_doc_for_ts_module(doc_parser, ts_module);
+ DocNode {
+ kind: DocNodeKind::Namespace,
+ name,
+ location,
+ js_doc,
+ namespace_def: Some(namespace_def),
+ enum_def: None,
+ type_alias_def: None,
+ interface_def: None,
+ variable_def: None,
+ function_def: None,
+ class_def: None,
+ }
+ }
+ }
+}
+
+#[allow(unused)]
+pub fn get_doc_nodes_for_named_export(
+ doc_parser: &DocParser,
+ named_export: &swc_ecma_ast::NamedExport,
+) -> Vec<DocNode> {
+ let file_name = named_export.src.as_ref().expect("").value.to_string();
+ // TODO: resolve specifier
+ let source_code =
+ std::fs::read_to_string(&file_name).expect("Failed to read file");
+ let doc_nodes = doc_parser
+ .parse(file_name, source_code)
+ .expect("Failed to print docs");
+ let reexports: Vec<String> = named_export
+ .specifiers
+ .iter()
+ .map(|export_specifier| {
+ use swc_ecma_ast::ExportSpecifier::*;
+
+ match export_specifier {
+ Named(named_export_specifier) => {
+ Some(named_export_specifier.orig.sym.to_string())
+ }
+ // TODO:
+ Namespace(_) => None,
+ Default(_) => None,
+ }
+ })
+ .filter(|s| s.is_some())
+ .map(|s| s.unwrap())
+ .collect();
+
+ let reexports_docs: Vec<DocNode> = doc_nodes
+ .into_iter()
+ .filter(|doc_node| reexports.contains(&doc_node.name))
+ .collect();
+
+ reexports_docs
+}
diff --git a/cli/doc/namespace.rs b/cli/doc/namespace.rs
new file mode 100644
index 000000000..ed6aac2f3
--- /dev/null
+++ b/cli/doc/namespace.rs
@@ -0,0 +1,81 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use serde::Serialize;
+use swc_ecma_ast;
+
+use super::parser::DocParser;
+use super::DocNode;
+use super::DocNodeKind;
+
+#[derive(Debug, Serialize, Clone)]
+pub struct NamespaceDef {
+ pub elements: Vec<DocNode>,
+}
+
+pub fn get_doc_for_ts_namespace_decl(
+ doc_parser: &DocParser,
+ ts_namespace_decl: &swc_ecma_ast::TsNamespaceDecl,
+) -> DocNode {
+ let js_doc = doc_parser.js_doc_for_span(ts_namespace_decl.span);
+ let location = doc_parser
+ .source_map
+ .lookup_char_pos(ts_namespace_decl.span.lo())
+ .into();
+ let namespace_name = ts_namespace_decl.id.sym.to_string();
+
+ use swc_ecma_ast::TsNamespaceBody::*;
+
+ let elements = match &*ts_namespace_decl.body {
+ TsModuleBlock(ts_module_block) => {
+ doc_parser.get_doc_nodes_for_module_body(ts_module_block.body.clone())
+ }
+ TsNamespaceDecl(ts_namespace_decl) => {
+ vec![get_doc_for_ts_namespace_decl(doc_parser, ts_namespace_decl)]
+ }
+ };
+
+ let ns_def = NamespaceDef { elements };
+
+ DocNode {
+ kind: DocNodeKind::Namespace,
+ name: namespace_name,
+ location,
+ js_doc,
+ namespace_def: Some(ns_def),
+ function_def: None,
+ variable_def: None,
+ enum_def: None,
+ class_def: None,
+ type_alias_def: None,
+ interface_def: None,
+ }
+}
+
+pub fn get_doc_for_ts_module(
+ doc_parser: &DocParser,
+ ts_module_decl: &swc_ecma_ast::TsModuleDecl,
+) -> (String, NamespaceDef) {
+ use swc_ecma_ast::TsModuleName;
+ let namespace_name = match &ts_module_decl.id {
+ TsModuleName::Ident(ident) => ident.sym.to_string(),
+ TsModuleName::Str(str_) => str_.value.to_string(),
+ };
+
+ let elements = if let Some(body) = &ts_module_decl.body {
+ use swc_ecma_ast::TsNamespaceBody::*;
+
+ match &body {
+ TsModuleBlock(ts_module_block) => {
+ doc_parser.get_doc_nodes_for_module_body(ts_module_block.body.clone())
+ }
+ TsNamespaceDecl(ts_namespace_decl) => {
+ vec![get_doc_for_ts_namespace_decl(doc_parser, ts_namespace_decl)]
+ }
+ }
+ } else {
+ vec![]
+ };
+
+ let ns_def = NamespaceDef { elements };
+
+ (namespace_name, ns_def)
+}
diff --git a/cli/doc/node.rs b/cli/doc/node.rs
new file mode 100644
index 000000000..da4b81c11
--- /dev/null
+++ b/cli/doc/node.rs
@@ -0,0 +1,77 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use serde::Serialize;
+use swc_common;
+
+#[derive(Debug, PartialEq, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub enum DocNodeKind {
+ Function,
+ Variable,
+ Class,
+ Enum,
+ Interface,
+ TypeAlias,
+ Namespace,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct ParamDef {
+ pub name: String,
+ pub ts_type: Option<super::ts_type::TsTypeDef>,
+}
+
+#[derive(Debug, Serialize, Clone)]
+pub struct Location {
+ pub filename: String,
+ pub line: usize,
+ pub col: usize,
+}
+
+impl Into<Location> for swc_common::Loc {
+ fn into(self) -> Location {
+ use swc_common::FileName::*;
+
+ let filename = match &self.file.name {
+ Real(path_buf) => path_buf.to_string_lossy().to_string(),
+ Custom(str_) => str_.to_string(),
+ _ => panic!("invalid filename"),
+ };
+
+ Location {
+ filename,
+ line: self.line,
+ col: self.col_display,
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct DocNode {
+ pub kind: DocNodeKind,
+ pub name: String,
+ pub location: Location,
+ pub js_doc: Option<String>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub function_def: Option<super::function::FunctionDef>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub variable_def: Option<super::variable::VariableDef>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub enum_def: Option<super::r#enum::EnumDef>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub class_def: Option<super::class::ClassDef>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub type_alias_def: Option<super::type_alias::TypeAliasDef>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub namespace_def: Option<super::namespace::NamespaceDef>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub interface_def: Option<super::interface::InterfaceDef>,
+}
diff --git a/cli/doc/parser.rs b/cli/doc/parser.rs
new file mode 100644
index 000000000..bd3a64806
--- /dev/null
+++ b/cli/doc/parser.rs
@@ -0,0 +1,189 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use regex::Regex;
+use std::sync::Arc;
+use std::sync::RwLock;
+use swc_common;
+use swc_common::comments::CommentKind;
+use swc_common::comments::Comments;
+use swc_common::errors::Diagnostic;
+use swc_common::errors::DiagnosticBuilder;
+use swc_common::errors::Emitter;
+use swc_common::errors::Handler;
+use swc_common::errors::HandlerFlags;
+use swc_common::FileName;
+use swc_common::Globals;
+use swc_common::SourceMap;
+use swc_common::Span;
+use swc_ecma_parser::lexer::Lexer;
+use swc_ecma_parser::JscTarget;
+use swc_ecma_parser::Parser;
+use swc_ecma_parser::Session;
+use swc_ecma_parser::SourceFileInput;
+use swc_ecma_parser::Syntax;
+use swc_ecma_parser::TsConfig;
+
+use super::DocNode;
+
+pub type SwcDiagnostics = Vec<Diagnostic>;
+
+#[derive(Clone, Default)]
+pub struct BufferedError(Arc<RwLock<SwcDiagnostics>>);
+
+impl Emitter for BufferedError {
+ fn emit(&mut self, db: &DiagnosticBuilder) {
+ self.0.write().unwrap().push((**db).clone());
+ }
+}
+
+impl From<BufferedError> for Vec<Diagnostic> {
+ fn from(buf: BufferedError) -> Self {
+ let s = buf.0.read().unwrap();
+ s.clone()
+ }
+}
+
+pub struct DocParser {
+ pub buffered_error: BufferedError,
+ pub source_map: Arc<SourceMap>,
+ pub handler: Handler,
+ pub comments: Comments,
+ pub globals: Globals,
+}
+
+impl DocParser {
+ pub fn default() -> Self {
+ let buffered_error = BufferedError::default();
+
+ let handler = Handler::with_emitter_and_flags(
+ Box::new(buffered_error.clone()),
+ HandlerFlags {
+ dont_buffer_diagnostics: true,
+ can_emit_warnings: true,
+ ..Default::default()
+ },
+ );
+
+ DocParser {
+ buffered_error,
+ source_map: Arc::new(SourceMap::default()),
+ handler,
+ comments: Comments::default(),
+ globals: Globals::new(),
+ }
+ }
+
+ pub fn parse(
+ &self,
+ file_name: String,
+ source_code: String,
+ ) -> Result<Vec<DocNode>, SwcDiagnostics> {
+ swc_common::GLOBALS.set(&self.globals, || {
+ let swc_source_file = self
+ .source_map
+ .new_source_file(FileName::Custom(file_name), source_code);
+
+ let buffered_err = self.buffered_error.clone();
+ let session = Session {
+ handler: &self.handler,
+ };
+
+ let mut ts_config = TsConfig::default();
+ ts_config.dynamic_import = true;
+ let syntax = Syntax::Typescript(ts_config);
+
+ let lexer = Lexer::new(
+ session,
+ syntax,
+ JscTarget::Es2019,
+ SourceFileInput::from(&*swc_source_file),
+ Some(&self.comments),
+ );
+
+ let mut parser = Parser::new_from(session, lexer);
+
+ let module =
+ parser
+ .parse_module()
+ .map_err(move |mut err: DiagnosticBuilder| {
+ err.cancel();
+ SwcDiagnostics::from(buffered_err)
+ })?;
+
+ let doc_entries = self.get_doc_nodes_for_module_body(module.body);
+ Ok(doc_entries)
+ })
+ }
+
+ pub fn get_doc_nodes_for_module_decl(
+ &self,
+ module_decl: &swc_ecma_ast::ModuleDecl,
+ ) -> Vec<DocNode> {
+ use swc_ecma_ast::ModuleDecl;
+
+ match module_decl {
+ ModuleDecl::ExportDecl(export_decl) => {
+ vec![super::module::get_doc_node_for_export_decl(
+ self,
+ export_decl,
+ )]
+ }
+ ModuleDecl::ExportNamed(_named_export) => {
+ vec![]
+ // TODO(bartlomieju):
+ // super::module::get_doc_nodes_for_named_export(self, named_export)
+ }
+ ModuleDecl::ExportDefaultDecl(_) => vec![],
+ ModuleDecl::ExportDefaultExpr(_) => vec![],
+ ModuleDecl::ExportAll(_) => vec![],
+ ModuleDecl::TsExportAssignment(_) => vec![],
+ ModuleDecl::TsNamespaceExport(_) => vec![],
+ _ => vec![],
+ }
+ }
+
+ pub fn get_doc_nodes_for_module_body(
+ &self,
+ module_body: Vec<swc_ecma_ast::ModuleItem>,
+ ) -> Vec<DocNode> {
+ let mut doc_entries: Vec<DocNode> = vec![];
+ for node in module_body.iter() {
+ if let swc_ecma_ast::ModuleItem::ModuleDecl(module_decl) = node {
+ doc_entries.extend(self.get_doc_nodes_for_module_decl(module_decl));
+ }
+ }
+ doc_entries
+ }
+
+ pub fn js_doc_for_span(&self, span: Span) -> Option<String> {
+ let comments = self.comments.take_leading_comments(span.lo())?;
+ let js_doc_comment = comments.iter().find(|comment| {
+ comment.kind == CommentKind::Block && comment.text.starts_with('*')
+ })?;
+
+ let mut margin_pat = String::from("");
+ if let Some(margin) = self.source_map.span_to_margin(span) {
+ for _ in 0..margin {
+ margin_pat.push(' ');
+ }
+ }
+
+ let js_doc_re = Regex::new(r#" ?\* ?"#).unwrap();
+ let txt = js_doc_comment
+ .text
+ .split('\n')
+ .map(|line| js_doc_re.replace(line, "").to_string())
+ .map(|line| {
+ if line.starts_with(&margin_pat) {
+ line[margin_pat.len()..].to_string()
+ } else {
+ line
+ }
+ })
+ .collect::<Vec<String>>()
+ .join("\n");
+
+ let txt = txt.trim_start().trim_end().to_string();
+
+ Some(txt)
+ }
+}
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)
+}
diff --git a/cli/doc/tests.rs b/cli/doc/tests.rs
new file mode 100644
index 000000000..c9408e8cf
--- /dev/null
+++ b/cli/doc/tests.rs
@@ -0,0 +1,568 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use super::DocParser;
+use serde_json;
+use serde_json::json;
+
+#[test]
+fn export_fn() {
+ let source_code = r#"/**
+* Hello there, this is a multiline JSdoc.
+*
+* It has many lines
+*
+* Or not that many?
+*/
+export function foo(a: string, b: number): void {
+ console.log("Hello world");
+}
+"#;
+ let entries = DocParser::default()
+ .parse("test.ts".to_string(), source_code.to_string())
+ .unwrap();
+ assert_eq!(entries.len(), 1);
+ let entry = &entries[0];
+ let expected_json = json!({
+ "functionDef": {
+ "isAsync": false,
+ "isGenerator": false,
+ "params": [
+ {
+ "name": "a",
+ "tsType": {
+ "keyword": "string",
+ "kind": "keyword",
+ "repr": "string",
+ },
+ },
+ {
+ "name": "b",
+ "tsType": {
+ "keyword": "number",
+ "kind": "keyword",
+ "repr": "number",
+ },
+ },
+ ],
+ "returnType": {
+ "keyword": "void",
+ "kind": "keyword",
+ "repr": "void",
+ },
+ },
+ "jsDoc": "Hello there, this is a multiline JSdoc.\n\nIt has many lines\n\nOr not that many?",
+ "kind": "function",
+ "location": {
+ "col": 0,
+ "filename": "test.ts",
+ "line": 8,
+ },
+ "name": "foo",
+ });
+ let actual = serde_json::to_value(entry).unwrap();
+ assert_eq!(actual, expected_json);
+
+ assert!(super::printer::format(entries).contains("Hello there"));
+}
+
+#[test]
+fn export_const() {
+ let source_code =
+ "/** Something about fizzBuzz */\nexport const fizzBuzz = \"fizzBuzz\";\n";
+ let entries = DocParser::default()
+ .parse("test.ts".to_string(), source_code.to_string())
+ .unwrap();
+ assert_eq!(entries.len(), 1);
+ let entry = &entries[0];
+ let expected_json = json!({
+ "kind": "variable",
+ "name": "fizzBuzz",
+ "location": {
+ "filename": "test.ts",
+ "line": 2,
+ "col": 0
+ },
+ "jsDoc": "Something about fizzBuzz",
+ "variableDef": {
+ "tsType": null,
+ "kind": "const"
+ }
+ });
+ let actual = serde_json::to_value(entry).unwrap();
+ assert_eq!(actual, expected_json);
+
+ assert!(super::printer::format(entries).contains("Something about fizzBuzz"));
+}
+
+#[test]
+fn export_class() {
+ let source_code = r#"
+/** Class doc */
+export class Foobar extends Fizz implements Buzz {
+ private private1: boolean;
+ protected protected1: number;
+ public public1: boolean;
+ public2: number;
+
+ /** Constructor js doc */
+ constructor(name: string, private private2: number, protected protected2: number) {}
+
+ /** Async foo method */
+ async foo(): Promise<void> {
+ //
+ }
+
+ /** Sync bar method */
+ bar(): void {
+ //
+ }
+}
+"#;
+ let entries = DocParser::default()
+ .parse("test.ts".to_string(), source_code.to_string())
+ .unwrap();
+ assert_eq!(entries.len(), 1);
+ let expected_json = json!({
+ "kind": "class",
+ "name": "Foobar",
+ "location": {
+ "filename": "test.ts",
+ "line": 3,
+ "col": 0
+ },
+ "jsDoc": "Class doc",
+ "classDef": {
+ "isAbstract": false,
+ "constructors": [
+ {
+ "jsDoc": "Constructor js doc",
+ "accessibility": null,
+ "name": "constructor",
+ "params": [
+ {
+ "name": "name",
+ "tsType": {
+ "repr": "string",
+ "kind": "keyword",
+ "keyword": "string"
+ }
+ },
+ {
+ "name": "<TODO>",
+ "tsType": null
+ },
+ {
+ "name": "<TODO>",
+ "tsType": null
+ }
+ ],
+ "location": {
+ "filename": "test.ts",
+ "line": 10,
+ "col": 4
+ }
+ }
+ ],
+ "properties": [
+ {
+ "jsDoc": null,
+ "tsType": {
+ "repr": "boolean",
+ "kind": "keyword",
+ "keyword": "boolean"
+ },
+ "readonly": false,
+ "accessibility": "private",
+ "isAbstract": false,
+ "isStatic": false,
+ "name": "private1",
+ "location": {
+ "filename": "test.ts",
+ "line": 4,
+ "col": 4
+ }
+ },
+ {
+ "jsDoc": null,
+ "tsType": {
+ "repr": "number",
+ "kind": "keyword",
+ "keyword": "number"
+ },
+ "readonly": false,
+ "accessibility": "protected",
+ "isAbstract": false,
+ "isStatic": false,
+ "name": "protected1",
+ "location": {
+ "filename": "test.ts",
+ "line": 5,
+ "col": 4
+ }
+ },
+ {
+ "jsDoc": null,
+ "tsType": {
+ "repr": "boolean",
+ "kind": "keyword",
+ "keyword": "boolean"
+ },
+ "readonly": false,
+ "accessibility": "public",
+ "isAbstract": false,
+ "isStatic": false,
+ "name": "public1",
+ "location": {
+ "filename": "test.ts",
+ "line": 6,
+ "col": 4
+ }
+ },
+ {
+ "jsDoc": null,
+ "tsType": {
+ "repr": "number",
+ "kind": "keyword",
+ "keyword": "number"
+ },
+ "readonly": false,
+ "accessibility": null,
+ "isAbstract": false,
+ "isStatic": false,
+ "name": "public2",
+ "location": {
+ "filename": "test.ts",
+ "line": 7,
+ "col": 4
+ }
+ }
+ ],
+ "methods": [
+ {
+ "jsDoc": "Async foo method",
+ "accessibility": null,
+ "isAbstract": false,
+ "isStatic": false,
+ "name": "foo",
+ "kind": "method",
+ "functionDef": {
+ "params": [],
+ "returnType": {
+ "repr": "Promise",
+ "kind": "typeRef",
+ "typeRef": {
+ "typeParams": [
+ {
+ "repr": "void",
+ "kind": "keyword",
+ "keyword": "void"
+ }
+ ],
+ "typeName": "Promise"
+ }
+ },
+ "isAsync": true,
+ "isGenerator": false
+ },
+ "location": {
+ "filename": "test.ts",
+ "line": 13,
+ "col": 4
+ }
+ },
+ {
+ "jsDoc": "Sync bar method",
+ "accessibility": null,
+ "isAbstract": false,
+ "isStatic": false,
+ "name": "bar",
+ "kind": "method",
+ "functionDef": {
+ "params": [],
+ "returnType": {
+ "repr": "void",
+ "kind": "keyword",
+ "keyword": "void"
+ },
+ "isAsync": false,
+ "isGenerator": false
+ },
+ "location": {
+ "filename": "test.ts",
+ "line": 18,
+ "col": 4
+ }
+ }
+ ]
+ }
+ });
+ let entry = &entries[0];
+ let actual = serde_json::to_value(entry).unwrap();
+ assert_eq!(actual, expected_json);
+
+ assert!(super::printer::format(entries).contains("class Foobar"));
+}
+
+#[test]
+fn export_interface() {
+ let source_code = r#"
+/**
+ * Interface js doc
+ */
+export interface Reader {
+ /** Read n bytes */
+ read(buf: Uint8Array, something: unknown): Promise<number>
+}
+ "#;
+ let entries = DocParser::default()
+ .parse("test.ts".to_string(), source_code.to_string())
+ .unwrap();
+ assert_eq!(entries.len(), 1);
+ let entry = &entries[0];
+ let expected_json = json!({
+ "kind": "interface",
+ "name": "Reader",
+ "location": {
+ "filename": "test.ts",
+ "line": 5,
+ "col": 0
+ },
+ "jsDoc": "Interface js doc",
+ "interfaceDef": {
+ "methods": [
+ {
+ "name": "read",
+ "location": {
+ "filename": "test.ts",
+ "line": 7,
+ "col": 4
+ },
+ "jsDoc": "Read n bytes",
+ "params": [
+ {
+ "name": "buf",
+ "tsType": {
+ "repr": "Uint8Array",
+ "kind": "typeRef",
+ "typeRef": {
+ "typeParams": null,
+ "typeName": "Uint8Array"
+ }
+ }
+ },
+ {
+ "name": "something",
+ "tsType": {
+ "repr": "unknown",
+ "kind": "keyword",
+ "keyword": "unknown"
+ }
+ }
+ ],
+ "returnType": {
+ "repr": "Promise",
+ "kind": "typeRef",
+ "typeRef": {
+ "typeParams": [
+ {
+ "repr": "number",
+ "kind": "keyword",
+ "keyword": "number"
+ }
+ ],
+ "typeName": "Promise"
+ }
+ }
+ }
+ ],
+ "properties": [],
+ "callSignatures": []
+ }
+ });
+ let actual = serde_json::to_value(entry).unwrap();
+ assert_eq!(actual, expected_json);
+
+ assert!(super::printer::format(entries).contains("interface Reader"));
+}
+
+#[test]
+fn export_type_alias() {
+ let source_code = r#"
+/** Array holding numbers */
+export type NumberArray = Array<number>;
+ "#;
+ let entries = DocParser::default()
+ .parse("test.ts".to_string(), source_code.to_string())
+ .unwrap();
+ assert_eq!(entries.len(), 1);
+ let entry = &entries[0];
+ let expected_json = json!({
+ "kind": "typeAlias",
+ "name": "NumberArray",
+ "location": {
+ "filename": "test.ts",
+ "line": 3,
+ "col": 0
+ },
+ "jsDoc": "Array holding numbers",
+ "typeAliasDef": {
+ "tsType": {
+ "repr": "Array",
+ "kind": "typeRef",
+ "typeRef": {
+ "typeParams": [
+ {
+ "repr": "number",
+ "kind": "keyword",
+ "keyword": "number"
+ }
+ ],
+ "typeName": "Array"
+ }
+ }
+ }
+ });
+ let actual = serde_json::to_value(entry).unwrap();
+ assert_eq!(actual, expected_json);
+
+ assert!(super::printer::format(entries).contains("Array holding numbers"));
+}
+
+#[test]
+fn export_enum() {
+ let source_code = r#"
+/**
+ * Some enum for good measure
+ */
+export enum Hello {
+ World = "world",
+ Fizz = "fizz",
+ Buzz = "buzz",
+}
+ "#;
+ let entries = DocParser::default()
+ .parse("test.ts".to_string(), source_code.to_string())
+ .unwrap();
+ assert_eq!(entries.len(), 1);
+ let entry = &entries[0];
+ let expected_json = json!({
+ "kind": "enum",
+ "name": "Hello",
+ "location": {
+ "filename": "test.ts",
+ "line": 5,
+ "col": 0
+ },
+ "jsDoc": "Some enum for good measure",
+ "enumDef": {
+ "members": [
+ {
+ "name": "World"
+ },
+ {
+ "name": "Fizz"
+ },
+ {
+ "name": "Buzz"
+ }
+ ]
+ }
+ });
+ let actual = serde_json::to_value(entry).unwrap();
+ assert_eq!(actual, expected_json);
+
+ assert!(super::printer::format(entries.clone())
+ .contains("Some enum for good measure"));
+ assert!(super::printer::format(entries).contains("enum Hello"));
+}
+
+#[test]
+fn export_namespace() {
+ let source_code = r#"
+/** Namespace JSdoc */
+export namespace RootNs {
+ export const a = "a";
+
+ /** Nested namespace JSDoc */
+ export namespace NestedNs {
+ export enum Foo {
+ a = 1,
+ b = 2,
+ c = 3,
+ }
+ }
+}
+ "#;
+ let entries = DocParser::default()
+ .parse("test.ts".to_string(), source_code.to_string())
+ .unwrap();
+ assert_eq!(entries.len(), 1);
+ let entry = &entries[0];
+ let expected_json = json!({
+ "kind": "namespace",
+ "name": "RootNs",
+ "location": {
+ "filename": "test.ts",
+ "line": 3,
+ "col": 0
+ },
+ "jsDoc": "Namespace JSdoc",
+ "namespaceDef": {
+ "elements": [
+ {
+ "kind": "variable",
+ "name": "a",
+ "location": {
+ "filename": "test.ts",
+ "line": 4,
+ "col": 4
+ },
+ "jsDoc": null,
+ "variableDef": {
+ "tsType": null,
+ "kind": "const"
+ }
+ },
+ {
+ "kind": "namespace",
+ "name": "NestedNs",
+ "location": {
+ "filename": "test.ts",
+ "line": 7,
+ "col": 4
+ },
+ "jsDoc": "Nested namespace JSDoc",
+ "namespaceDef": {
+ "elements": [
+ {
+ "kind": "enum",
+ "name": "Foo",
+ "location": {
+ "filename": "test.ts",
+ "line": 8,
+ "col": 6
+ },
+ "jsDoc": null,
+ "enumDef": {
+ "members": [
+ {
+ "name": "a"
+ },
+ {
+ "name": "b"
+ },
+ {
+ "name": "c"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+ });
+ let actual = serde_json::to_value(entry).unwrap();
+ assert_eq!(actual, expected_json);
+ assert!(super::printer::format(entries).contains("namespace RootNs"));
+}
diff --git a/cli/doc/ts_type.rs b/cli/doc/ts_type.rs
new file mode 100644
index 000000000..9a7d191ba
--- /dev/null
+++ b/cli/doc/ts_type.rs
@@ -0,0 +1,821 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use super::ParamDef;
+use serde::Serialize;
+use swc_common::SourceMap;
+use swc_ecma_ast;
+use swc_ecma_ast::TsArrayType;
+use swc_ecma_ast::TsConditionalType;
+use swc_ecma_ast::TsFnOrConstructorType;
+use swc_ecma_ast::TsIndexedAccessType;
+use swc_ecma_ast::TsKeywordType;
+use swc_ecma_ast::TsLit;
+use swc_ecma_ast::TsLitType;
+use swc_ecma_ast::TsOptionalType;
+use swc_ecma_ast::TsParenthesizedType;
+use swc_ecma_ast::TsRestType;
+use swc_ecma_ast::TsThisType;
+use swc_ecma_ast::TsTupleType;
+use swc_ecma_ast::TsType;
+use swc_ecma_ast::TsTypeAnn;
+use swc_ecma_ast::TsTypeLit;
+use swc_ecma_ast::TsTypeOperator;
+use swc_ecma_ast::TsTypeQuery;
+use swc_ecma_ast::TsTypeRef;
+use swc_ecma_ast::TsUnionOrIntersectionType;
+
+// pub enum TsType {
+// * TsKeywordType(TsKeywordType),
+// * TsThisType(TsThisType),
+// * TsFnOrConstructorType(TsFnOrConstructorType),
+// * TsTypeRef(TsTypeRef),
+// * TsTypeQuery(TsTypeQuery),
+// * TsTypeLit(TsTypeLit),
+// * TsArrayType(TsArrayType),
+// * TsTupleType(TsTupleType),
+// * TsOptionalType(TsOptionalType),
+// * TsRestType(TsRestType),
+// * TsUnionOrIntersectionType(TsUnionOrIntersectionType),
+// * TsConditionalType(TsConditionalType),
+// TsInferType(TsInferType),
+// * TsParenthesizedType(TsParenthesizedType),
+// * TsTypeOperator(TsTypeOperator),
+// * TsIndexedAccessType(TsIndexedAccessType),
+// TsMappedType(TsMappedType),
+// * TsLitType(TsLitType),
+// TsTypePredicate(TsTypePredicate),
+// TsImportType(TsImportType),
+// }
+
+impl Into<TsTypeDef> for &TsLitType {
+ fn into(self) -> TsTypeDef {
+ let (repr, lit) = match &self.lit {
+ TsLit::Number(num) => (
+ format!("{}", num.value),
+ LiteralDef {
+ kind: LiteralDefKind::Number,
+ number: Some(num.value),
+ string: None,
+ boolean: None,
+ },
+ ),
+ TsLit::Str(str_) => (
+ str_.value.to_string(),
+ LiteralDef {
+ kind: LiteralDefKind::String,
+ number: None,
+ string: Some(str_.value.to_string()),
+ boolean: None,
+ },
+ ),
+ TsLit::Bool(bool_) => (
+ bool_.value.to_string(),
+ LiteralDef {
+ kind: LiteralDefKind::Boolean,
+ number: None,
+ string: None,
+ boolean: Some(bool_.value),
+ },
+ ),
+ };
+
+ TsTypeDef {
+ repr,
+ kind: Some(TsTypeDefKind::Literal),
+ literal: Some(lit),
+ ..Default::default()
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsArrayType {
+ fn into(self) -> TsTypeDef {
+ let ts_type_def: TsTypeDef = (&*self.elem_type).into();
+
+ TsTypeDef {
+ array: Some(Box::new(ts_type_def)),
+ kind: Some(TsTypeDefKind::Array),
+ ..Default::default()
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsTupleType {
+ fn into(self) -> TsTypeDef {
+ let mut type_defs = vec![];
+
+ for type_box in &self.elem_types {
+ let ts_type: &TsType = &(*type_box);
+ let def: TsTypeDef = ts_type.into();
+ type_defs.push(def)
+ }
+
+ TsTypeDef {
+ tuple: Some(type_defs),
+ kind: Some(TsTypeDefKind::Tuple),
+ ..Default::default()
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsUnionOrIntersectionType {
+ fn into(self) -> TsTypeDef {
+ use swc_ecma_ast::TsUnionOrIntersectionType::*;
+
+ match self {
+ TsUnionType(union_type) => {
+ let mut types_union = vec![];
+
+ for type_box in &union_type.types {
+ let ts_type: &TsType = &(*type_box);
+ let def: TsTypeDef = ts_type.into();
+ types_union.push(def);
+ }
+
+ TsTypeDef {
+ union: Some(types_union),
+ kind: Some(TsTypeDefKind::Union),
+ ..Default::default()
+ }
+ }
+ TsIntersectionType(intersection_type) => {
+ let mut types_intersection = vec![];
+
+ for type_box in &intersection_type.types {
+ let ts_type: &TsType = &(*type_box);
+ let def: TsTypeDef = ts_type.into();
+ types_intersection.push(def);
+ }
+
+ TsTypeDef {
+ intersection: Some(types_intersection),
+ kind: Some(TsTypeDefKind::Intersection),
+ ..Default::default()
+ }
+ }
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsKeywordType {
+ fn into(self) -> TsTypeDef {
+ use swc_ecma_ast::TsKeywordTypeKind::*;
+
+ let keyword_str = match self.kind {
+ TsAnyKeyword => "any",
+ TsUnknownKeyword => "unknown",
+ TsNumberKeyword => "number",
+ TsObjectKeyword => "object",
+ TsBooleanKeyword => "boolean",
+ TsBigIntKeyword => "bigint",
+ TsStringKeyword => "string",
+ TsSymbolKeyword => "symbol",
+ TsVoidKeyword => "void",
+ TsUndefinedKeyword => "undefined",
+ TsNullKeyword => "null",
+ TsNeverKeyword => "never",
+ };
+
+ TsTypeDef {
+ repr: keyword_str.to_string(),
+ kind: Some(TsTypeDefKind::Keyword),
+ keyword: Some(keyword_str.to_string()),
+ ..Default::default()
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsTypeOperator {
+ fn into(self) -> TsTypeDef {
+ let ts_type = (&*self.type_ann).into();
+ let type_operator_def = TsTypeOperatorDef {
+ operator: self.op.as_str().to_string(),
+ ts_type,
+ };
+
+ TsTypeDef {
+ type_operator: Some(Box::new(type_operator_def)),
+ kind: Some(TsTypeDefKind::TypeOperator),
+ ..Default::default()
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsParenthesizedType {
+ fn into(self) -> TsTypeDef {
+ let ts_type = (&*self.type_ann).into();
+
+ TsTypeDef {
+ parenthesized: Some(Box::new(ts_type)),
+ kind: Some(TsTypeDefKind::Parenthesized),
+ ..Default::default()
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsRestType {
+ fn into(self) -> TsTypeDef {
+ let ts_type = (&*self.type_ann).into();
+
+ TsTypeDef {
+ rest: Some(Box::new(ts_type)),
+ kind: Some(TsTypeDefKind::Rest),
+ ..Default::default()
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsOptionalType {
+ fn into(self) -> TsTypeDef {
+ let ts_type = (&*self.type_ann).into();
+
+ TsTypeDef {
+ optional: Some(Box::new(ts_type)),
+ kind: Some(TsTypeDefKind::Optional),
+ ..Default::default()
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsThisType {
+ fn into(self) -> TsTypeDef {
+ TsTypeDef {
+ repr: "this".to_string(),
+ this: Some(true),
+ kind: Some(TsTypeDefKind::This),
+ ..Default::default()
+ }
+ }
+}
+
+fn ts_entity_name_to_name(entity_name: &swc_ecma_ast::TsEntityName) -> String {
+ use swc_ecma_ast::TsEntityName::*;
+
+ match entity_name {
+ Ident(ident) => ident.sym.to_string(),
+ TsQualifiedName(ts_qualified_name) => {
+ let left = ts_entity_name_to_name(&ts_qualified_name.left);
+ let right = ts_qualified_name.right.sym.to_string();
+ format!("{}.{}", left, right)
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsTypeQuery {
+ fn into(self) -> TsTypeDef {
+ use swc_ecma_ast::TsTypeQueryExpr::*;
+
+ let type_name = match &self.expr_name {
+ TsEntityName(entity_name) => ts_entity_name_to_name(&*entity_name),
+ Import(import_type) => import_type.arg.value.to_string(),
+ };
+
+ TsTypeDef {
+ repr: type_name.to_string(),
+ type_query: Some(type_name),
+ kind: Some(TsTypeDefKind::TypeQuery),
+ ..Default::default()
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsTypeRef {
+ fn into(self) -> TsTypeDef {
+ let type_name = ts_entity_name_to_name(&self.type_name);
+
+ let type_params = if let Some(type_params_inst) = &self.type_params {
+ let mut ts_type_defs = vec![];
+
+ for type_box in &type_params_inst.params {
+ let ts_type: &TsType = &(*type_box);
+ let def: TsTypeDef = ts_type.into();
+ ts_type_defs.push(def);
+ }
+
+ Some(ts_type_defs)
+ } else {
+ None
+ };
+
+ TsTypeDef {
+ repr: type_name.to_string(),
+ type_ref: Some(TsTypeRefDef {
+ type_name,
+ type_params,
+ }),
+ kind: Some(TsTypeDefKind::TypeRef),
+ ..Default::default()
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsIndexedAccessType {
+ fn into(self) -> TsTypeDef {
+ let indexed_access_def = TsIndexedAccessDef {
+ readonly: self.readonly,
+ obj_type: Box::new((&*self.obj_type).into()),
+ index_type: Box::new((&*self.index_type).into()),
+ };
+
+ TsTypeDef {
+ indexed_access: Some(indexed_access_def),
+ kind: Some(TsTypeDefKind::IndexedAccess),
+ ..Default::default()
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsTypeLit {
+ fn into(self) -> TsTypeDef {
+ let mut methods = vec![];
+ let mut properties = vec![];
+ let mut call_signatures = vec![];
+
+ for type_element in &self.members {
+ use swc_ecma_ast::TsTypeElement::*;
+
+ match &type_element {
+ TsMethodSignature(ts_method_sig) => {
+ let mut params = vec![];
+
+ for param in &ts_method_sig.params {
+ use swc_ecma_ast::TsFnParam::*;
+
+ let param_def = match param {
+ Ident(ident) => {
+ let ts_type =
+ ident.type_ann.as_ref().map(|rt| (&*rt.type_ann).into());
+
+ ParamDef {
+ name: ident.sym.to_string(),
+ ts_type,
+ }
+ }
+ _ => ParamDef {
+ name: "<TODO>".to_string(),
+ ts_type: None,
+ },
+ };
+
+ params.push(param_def);
+ }
+
+ let maybe_return_type = ts_method_sig
+ .type_ann
+ .as_ref()
+ .map(|rt| (&*rt.type_ann).into());
+
+ let method_def = LiteralMethodDef {
+ name: "<TODO>".to_string(),
+ params,
+ return_type: maybe_return_type,
+ };
+ methods.push(method_def);
+ }
+ TsPropertySignature(ts_prop_sig) => {
+ let name = match &*ts_prop_sig.key {
+ swc_ecma_ast::Expr::Ident(ident) => ident.sym.to_string(),
+ _ => "TODO".to_string(),
+ };
+
+ let mut params = vec![];
+
+ for param in &ts_prop_sig.params {
+ use swc_ecma_ast::TsFnParam::*;
+
+ let param_def = match param {
+ Ident(ident) => {
+ let ts_type =
+ ident.type_ann.as_ref().map(|rt| (&*rt.type_ann).into());
+
+ ParamDef {
+ name: ident.sym.to_string(),
+ ts_type,
+ }
+ }
+ _ => ParamDef {
+ name: "<TODO>".to_string(),
+ ts_type: None,
+ },
+ };
+
+ params.push(param_def);
+ }
+
+ let ts_type = ts_prop_sig
+ .type_ann
+ .as_ref()
+ .map(|rt| (&*rt.type_ann).into());
+
+ let prop_def = LiteralPropertyDef {
+ name,
+ params,
+ ts_type,
+ computed: ts_prop_sig.computed,
+ optional: ts_prop_sig.optional,
+ };
+ properties.push(prop_def);
+ }
+ TsCallSignatureDecl(ts_call_sig) => {
+ let mut params = vec![];
+ for param in &ts_call_sig.params {
+ use swc_ecma_ast::TsFnParam::*;
+
+ let param_def = match param {
+ Ident(ident) => {
+ let ts_type =
+ ident.type_ann.as_ref().map(|rt| (&*rt.type_ann).into());
+
+ ParamDef {
+ name: ident.sym.to_string(),
+ ts_type,
+ }
+ }
+ _ => ParamDef {
+ name: "<TODO>".to_string(),
+ ts_type: None,
+ },
+ };
+
+ params.push(param_def);
+ }
+
+ let ts_type = ts_call_sig
+ .type_ann
+ .as_ref()
+ .map(|rt| (&*rt.type_ann).into());
+
+ let call_sig_def = LiteralCallSignatureDef { params, ts_type };
+ call_signatures.push(call_sig_def);
+ }
+ // TODO:
+ TsConstructSignatureDecl(_) => {}
+ TsIndexSignature(_) => {}
+ }
+ }
+
+ let type_literal = TsTypeLiteralDef {
+ methods,
+ properties,
+ call_signatures,
+ };
+
+ TsTypeDef {
+ kind: Some(TsTypeDefKind::TypeLiteral),
+ type_literal: Some(type_literal),
+ ..Default::default()
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsConditionalType {
+ fn into(self) -> TsTypeDef {
+ let conditional_type_def = TsConditionalDef {
+ check_type: Box::new((&*self.check_type).into()),
+ extends_type: Box::new((&*self.extends_type).into()),
+ true_type: Box::new((&*self.true_type).into()),
+ false_type: Box::new((&*self.false_type).into()),
+ };
+
+ TsTypeDef {
+ kind: Some(TsTypeDefKind::Conditional),
+ conditional_type: Some(conditional_type_def),
+ ..Default::default()
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsFnOrConstructorType {
+ fn into(self) -> TsTypeDef {
+ use swc_ecma_ast::TsFnOrConstructorType::*;
+
+ let fn_def = match self {
+ TsFnType(ts_fn_type) => {
+ let mut params = vec![];
+
+ for param in &ts_fn_type.params {
+ use swc_ecma_ast::TsFnParam::*;
+
+ let param_def = match param {
+ Ident(ident) => {
+ let ts_type: Option<TsTypeDef> =
+ ident.type_ann.as_ref().map(|rt| {
+ let type_box = &*rt.type_ann;
+ (&*type_box).into()
+ });
+
+ ParamDef {
+ name: ident.sym.to_string(),
+ ts_type,
+ }
+ }
+ _ => ParamDef {
+ name: "<TODO>".to_string(),
+ ts_type: None,
+ },
+ };
+
+ params.push(param_def);
+ }
+
+ TsFnOrConstructorDef {
+ constructor: false,
+ ts_type: (&*ts_fn_type.type_ann.type_ann).into(),
+ params,
+ }
+ }
+ TsConstructorType(ctor_type) => {
+ let mut params = vec![];
+
+ for param in &ctor_type.params {
+ use swc_ecma_ast::TsFnParam::*;
+
+ let param_def = match param {
+ Ident(ident) => {
+ let ts_type: Option<TsTypeDef> =
+ ident.type_ann.as_ref().map(|rt| {
+ let type_box = &*rt.type_ann;
+ (&*type_box).into()
+ });
+
+ ParamDef {
+ name: ident.sym.to_string(),
+ ts_type,
+ }
+ }
+ _ => ParamDef {
+ name: "<TODO>".to_string(),
+ ts_type: None,
+ },
+ };
+
+ params.push(param_def);
+ }
+
+ TsFnOrConstructorDef {
+ constructor: true,
+ ts_type: (&*ctor_type.type_ann.type_ann).into(),
+ params: vec![],
+ }
+ }
+ };
+
+ TsTypeDef {
+ kind: Some(TsTypeDefKind::FnOrConstructor),
+ fn_or_constructor: Some(Box::new(fn_def)),
+ ..Default::default()
+ }
+ }
+}
+
+impl Into<TsTypeDef> for &TsType {
+ fn into(self) -> TsTypeDef {
+ use swc_ecma_ast::TsType::*;
+
+ match self {
+ TsKeywordType(ref keyword_type) => keyword_type.into(),
+ TsLitType(ref lit_type) => lit_type.into(),
+ TsTypeRef(ref type_ref) => type_ref.into(),
+ TsUnionOrIntersectionType(union_or_inter) => union_or_inter.into(),
+ TsArrayType(array_type) => array_type.into(),
+ TsTupleType(tuple_type) => tuple_type.into(),
+ TsTypeOperator(type_op_type) => type_op_type.into(),
+ TsParenthesizedType(paren_type) => paren_type.into(),
+ TsRestType(rest_type) => rest_type.into(),
+ TsOptionalType(optional_type) => optional_type.into(),
+ TsTypeQuery(type_query) => type_query.into(),
+ TsThisType(this_type) => this_type.into(),
+ TsFnOrConstructorType(fn_or_con_type) => fn_or_con_type.into(),
+ TsConditionalType(conditional_type) => conditional_type.into(),
+ TsIndexedAccessType(indexed_access_type) => indexed_access_type.into(),
+ TsTypeLit(type_literal) => type_literal.into(),
+ _ => TsTypeDef {
+ repr: "<UNIMPLEMENTED>".to_string(),
+ ..Default::default()
+ },
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct TsTypeRefDef {
+ pub type_params: Option<Vec<TsTypeDef>>,
+ pub type_name: String,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub enum LiteralDefKind {
+ Number,
+ String,
+ Boolean,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct LiteralDef {
+ pub kind: LiteralDefKind,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub number: Option<f64>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub string: Option<String>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub boolean: Option<bool>,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct TsTypeOperatorDef {
+ pub operator: String,
+ pub ts_type: TsTypeDef,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct TsFnOrConstructorDef {
+ // TODO: type_params
+ pub constructor: bool,
+ pub ts_type: TsTypeDef,
+ pub params: Vec<ParamDef>,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct TsConditionalDef {
+ pub check_type: Box<TsTypeDef>,
+ pub extends_type: Box<TsTypeDef>,
+ pub true_type: Box<TsTypeDef>,
+ pub false_type: Box<TsTypeDef>,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct TsIndexedAccessDef {
+ pub readonly: bool,
+ pub obj_type: Box<TsTypeDef>,
+ pub index_type: Box<TsTypeDef>,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct LiteralMethodDef {
+ // TODO: type_params
+ pub name: String,
+ // pub location: Location,
+ // pub js_doc: Option<String>,
+ pub params: Vec<ParamDef>,
+ pub return_type: Option<TsTypeDef>,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct LiteralPropertyDef {
+ // TODO: type_params
+ pub name: String,
+ // pub location: Location,
+ // pub js_doc: Option<String>,
+ pub params: Vec<ParamDef>,
+ pub computed: bool,
+ pub optional: bool,
+ pub ts_type: Option<TsTypeDef>,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct LiteralCallSignatureDef {
+ // TODO: type_params
+ // pub location: Location,
+ // pub js_doc: Option<String>,
+ pub params: Vec<ParamDef>,
+ pub ts_type: Option<TsTypeDef>,
+}
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct TsTypeLiteralDef {
+ pub methods: Vec<LiteralMethodDef>,
+ pub properties: Vec<LiteralPropertyDef>,
+ pub call_signatures: Vec<LiteralCallSignatureDef>,
+}
+
+#[derive(Debug, PartialEq, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub enum TsTypeDefKind {
+ Keyword,
+ Literal,
+ TypeRef,
+ Union,
+ Intersection,
+ Array,
+ Tuple,
+ TypeOperator,
+ Parenthesized,
+ Rest,
+ Optional,
+ TypeQuery,
+ This,
+ FnOrConstructor,
+ Conditional,
+ IndexedAccess,
+ TypeLiteral,
+}
+
+#[derive(Debug, Default, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct TsTypeDef {
+ pub repr: String,
+
+ pub kind: Option<TsTypeDefKind>,
+
+ // TODO: make this struct more conrete
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub keyword: Option<String>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub literal: Option<LiteralDef>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub type_ref: Option<TsTypeRefDef>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub union: Option<Vec<TsTypeDef>>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub intersection: Option<Vec<TsTypeDef>>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub array: Option<Box<TsTypeDef>>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub tuple: Option<Vec<TsTypeDef>>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub type_operator: Option<Box<TsTypeOperatorDef>>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub parenthesized: Option<Box<TsTypeDef>>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub rest: Option<Box<TsTypeDef>>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub optional: Option<Box<TsTypeDef>>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub type_query: Option<String>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub this: Option<bool>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub fn_or_constructor: Option<Box<TsFnOrConstructorDef>>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub conditional_type: Option<TsConditionalDef>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub indexed_access: Option<TsIndexedAccessDef>,
+
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub type_literal: Option<TsTypeLiteralDef>,
+}
+
+pub fn ts_type_ann_to_def(
+ source_map: &SourceMap,
+ type_ann: &TsTypeAnn,
+) -> TsTypeDef {
+ use swc_ecma_ast::TsType::*;
+
+ match &*type_ann.type_ann {
+ TsKeywordType(keyword_type) => keyword_type.into(),
+ TsLitType(lit_type) => lit_type.into(),
+ TsTypeRef(type_ref) => type_ref.into(),
+ TsUnionOrIntersectionType(union_or_inter) => union_or_inter.into(),
+ TsArrayType(array_type) => array_type.into(),
+ TsTupleType(tuple_type) => tuple_type.into(),
+ TsTypeOperator(type_op_type) => type_op_type.into(),
+ TsParenthesizedType(paren_type) => paren_type.into(),
+ TsRestType(rest_type) => rest_type.into(),
+ TsOptionalType(optional_type) => optional_type.into(),
+ TsTypeQuery(type_query) => type_query.into(),
+ TsThisType(this_type) => this_type.into(),
+ TsFnOrConstructorType(fn_or_con_type) => fn_or_con_type.into(),
+ TsConditionalType(conditional_type) => conditional_type.into(),
+ TsIndexedAccessType(indexed_access_type) => indexed_access_type.into(),
+ TsTypeLit(type_literal) => type_literal.into(),
+ _ => {
+ let repr = source_map
+ .span_to_snippet(type_ann.span)
+ .expect("Class prop type not found");
+ let repr = repr.trim_start_matches(':').trim_start().to_string();
+
+ TsTypeDef {
+ repr,
+ ..Default::default()
+ }
+ }
+ }
+}
diff --git a/cli/doc/type_alias.rs b/cli/doc/type_alias.rs
new file mode 100644
index 000000000..3740aee84
--- /dev/null
+++ b/cli/doc/type_alias.rs
@@ -0,0 +1,25 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use serde::Serialize;
+use swc_ecma_ast;
+
+use super::parser::DocParser;
+use super::ts_type::TsTypeDef;
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct TypeAliasDef {
+ pub ts_type: TsTypeDef,
+ // TODO: type_params
+}
+
+pub fn get_doc_for_ts_type_alias_decl(
+ _doc_parser: &DocParser,
+ type_alias_decl: &swc_ecma_ast::TsTypeAliasDecl,
+) -> (String, TypeAliasDef) {
+ let alias_name = type_alias_decl.id.sym.to_string();
+ let ts_type = type_alias_decl.type_ann.as_ref().into();
+
+ let type_alias_def = TypeAliasDef { ts_type };
+
+ (alias_name, type_alias_def)
+}
diff --git a/cli/doc/variable.rs b/cli/doc/variable.rs
new file mode 100644
index 000000000..16bf26d25
--- /dev/null
+++ b/cli/doc/variable.rs
@@ -0,0 +1,43 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+use serde::Serialize;
+use swc_ecma_ast;
+
+use super::parser::DocParser;
+use super::ts_type::ts_type_ann_to_def;
+use super::ts_type::TsTypeDef;
+
+#[derive(Debug, Serialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct VariableDef {
+ pub ts_type: Option<TsTypeDef>,
+ pub kind: swc_ecma_ast::VarDeclKind,
+}
+
+pub fn get_doc_for_var_decl(
+ doc_parser: &DocParser,
+ var_decl: &swc_ecma_ast::VarDecl,
+) -> (String, VariableDef) {
+ assert!(!var_decl.decls.is_empty());
+ // TODO: support multiple declarators
+ let var_declarator = var_decl.decls.get(0).unwrap();
+
+ let var_name = match &var_declarator.name {
+ swc_ecma_ast::Pat::Ident(ident) => ident.sym.to_string(),
+ _ => "<TODO>".to_string(),
+ };
+
+ let maybe_ts_type = match &var_declarator.name {
+ swc_ecma_ast::Pat::Ident(ident) => ident
+ .type_ann
+ .as_ref()
+ .map(|rt| ts_type_ann_to_def(&doc_parser.source_map, rt)),
+ _ => None,
+ };
+
+ let variable_def = VariableDef {
+ ts_type: maybe_ts_type,
+ kind: var_decl.kind,
+ };
+
+ (var_name, variable_def)
+}
diff --git a/cli/flags.rs b/cli/flags.rs
index 4f55d69ef..d90f1f3b9 100644
--- a/cli/flags.rs
+++ b/cli/flags.rs
@@ -31,6 +31,11 @@ pub enum DenoSubcommand {
Completions {
buf: Box<[u8]>,
},
+ Doc {
+ json: bool,
+ source_file: String,
+ filter: Option<String>,
+ },
Eval {
code: String,
as_typescript: bool,
@@ -258,6 +263,8 @@ pub fn flags_from_vec_safe(args: Vec<String>) -> clap::Result<Flags> {
test_parse(&mut flags, m);
} else if let Some(m) = matches.subcommand_matches("upgrade") {
upgrade_parse(&mut flags, m);
+ } else if let Some(m) = matches.subcommand_matches("doc") {
+ doc_parse(&mut flags, m);
} else {
unimplemented!();
}
@@ -311,6 +318,7 @@ If the flag is set, restrict these messages to errors.",
.subcommand(test_subcommand())
.subcommand(types_subcommand())
.subcommand(upgrade_subcommand())
+ .subcommand(doc_subcommand())
.long_about(DENO_HELP)
.after_help(ENV_VARIABLES_HELP)
}
@@ -550,6 +558,22 @@ fn upgrade_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
flags.subcommand = DenoSubcommand::Upgrade { dry_run, force };
}
+fn doc_parse(flags: &mut Flags, matches: &clap::ArgMatches) {
+ reload_arg_parse(flags, matches);
+ let source_file = matches.value_of("source_file").map(String::from).unwrap();
+ let json = matches.is_present("json");
+ let filter = if matches.is_present("filter") {
+ Some(matches.value_of("filter").unwrap().to_string())
+ } else {
+ None
+ };
+ flags.subcommand = DenoSubcommand::Doc {
+ source_file,
+ json,
+ filter,
+ };
+}
+
fn types_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("types")
.about("Print runtime TypeScript declarations")
@@ -770,6 +794,43 @@ and is used to replace the current executable.",
)
}
+fn doc_subcommand<'a, 'b>() -> App<'a, 'b> {
+ SubCommand::with_name("doc")
+ .about("Show documentation for module")
+ .long_about(
+ "Show documentation for module.
+
+Output documentation to terminal:
+ deno doc ./path/to/module.ts
+
+Show detail of symbol:
+ deno doc ./path/to/module.ts MyClass.someField
+
+Output documentation in JSON format:
+ deno doc --json ./path/to/module.ts",
+ )
+ .arg(reload_arg())
+ .arg(
+ Arg::with_name("json")
+ .long("json")
+ .help("Output documentation in JSON format.")
+ .takes_value(false),
+ )
+ .arg(
+ Arg::with_name("source_file")
+ .takes_value(true)
+ .required(true),
+ )
+ .arg(
+ Arg::with_name("filter")
+ .help("Dot separated path to symbol.")
+ .takes_value(true)
+ .required(false)
+ .conflicts_with("json")
+ .conflicts_with("pretty"),
+ )
+}
+
fn permission_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
app
.arg(
@@ -1219,6 +1280,7 @@ fn arg_hacks(mut args: Vec<String>) -> Vec<String> {
let subcommands = sset![
"bundle",
"completions",
+ "doc",
"eval",
"fetch",
"fmt",
@@ -2380,6 +2442,41 @@ fn repl_with_cafile() {
}
#[test]
+fn doc() {
+ let r =
+ flags_from_vec_safe(svec!["deno", "doc", "--json", "path/to/module.ts"]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Doc {
+ json: true,
+ source_file: "path/to/module.ts".to_string(),
+ filter: None,
+ },
+ ..Flags::default()
+ }
+ );
+
+ let r = flags_from_vec_safe(svec![
+ "deno",
+ "doc",
+ "path/to/module.ts",
+ "SomeClass.someField"
+ ]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Doc {
+ json: false,
+ source_file: "path/to/module.ts".to_string(),
+ filter: Some("SomeClass.someField".to_string()),
+ },
+ ..Flags::default()
+ }
+ );
+}
+
+#[test]
fn inspect_default_host() {
let r = flags_from_vec_safe(svec!["deno", "run", "--inspect", "foo.js"]);
assert_eq!(
diff --git a/cli/lib.rs b/cli/lib.rs
index 7b5b56ba2..11ab26626 100644
--- a/cli/lib.rs
+++ b/cli/lib.rs
@@ -27,6 +27,7 @@ pub mod compilers;
pub mod deno_dir;
pub mod diagnostics;
mod disk_cache;
+mod doc;
mod file_fetcher;
pub mod flags;
mod fmt;
@@ -111,6 +112,18 @@ impl log::Log for Logger {
fn flush(&self) {}
}
+fn write_to_stdout_ignore_sigpipe(bytes: &[u8]) -> Result<(), std::io::Error> {
+ use std::io::ErrorKind;
+
+ match std::io::stdout().write_all(bytes) {
+ Ok(()) => Ok(()),
+ Err(e) => match e.kind() {
+ ErrorKind::BrokenPipe => Ok(()),
+ _ => Err(e),
+ },
+ }
+}
+
fn create_main_worker(
global_state: GlobalState,
main_module: ModuleSpecifier,
@@ -344,6 +357,57 @@ async fn bundle_command(
bundle_result
}
+async fn doc_command(
+ flags: Flags,
+ source_file: String,
+ json: bool,
+ maybe_filter: Option<String>,
+) -> Result<(), ErrBox> {
+ let global_state = GlobalState::new(flags.clone())?;
+ let module_specifier =
+ ModuleSpecifier::resolve_url_or_path(&source_file).unwrap();
+ let source_file = global_state
+ .file_fetcher
+ .fetch_source_file(&module_specifier, None)
+ .await?;
+ let source_code = String::from_utf8(source_file.source_code)?;
+
+ let doc_parser = doc::DocParser::default();
+ let parse_result =
+ doc_parser.parse(module_specifier.to_string(), source_code);
+
+ let doc_nodes = match parse_result {
+ Ok(nodes) => nodes,
+ Err(e) => {
+ eprintln!("Failed to parse documentation:");
+ for diagnostic in e {
+ eprintln!("{}", diagnostic.message());
+ }
+
+ std::process::exit(1);
+ }
+ };
+
+ if json {
+ let writer = std::io::BufWriter::new(std::io::stdout());
+ serde_json::to_writer_pretty(writer, &doc_nodes).map_err(ErrBox::from)
+ } else {
+ let details = if let Some(filter) = maybe_filter {
+ let node = doc::find_node_by_name_recursively(doc_nodes, filter.clone());
+ if let Some(node) = node {
+ doc::printer::format_details(node)
+ } else {
+ eprintln!("Node {} was not found!", filter);
+ std::process::exit(1);
+ }
+ } else {
+ doc::printer::format(doc_nodes)
+ };
+
+ write_to_stdout_ignore_sigpipe(details.as_bytes()).map_err(ErrBox::from)
+ }
+}
+
async fn run_repl(flags: Flags) -> Result<(), ErrBox> {
let main_module =
ModuleSpecifier::resolve_url_or_path("./__$deno$repl.ts").unwrap();
@@ -451,6 +515,11 @@ pub fn main() {
source_file,
out_file,
} => bundle_command(flags, source_file, out_file).boxed_local(),
+ DenoSubcommand::Doc {
+ source_file,
+ json,
+ filter,
+ } => doc_command(flags, source_file, json, filter).boxed_local(),
DenoSubcommand::Eval {
code,
as_typescript,
@@ -478,7 +547,10 @@ pub fn main() {
allow_none,
} => test_command(flags, include, fail_fast, allow_none).boxed_local(),
DenoSubcommand::Completions { buf } => {
- print!("{}", std::str::from_utf8(&buf).unwrap());
+ if let Err(e) = write_to_stdout_ignore_sigpipe(&buf) {
+ eprintln!("{}", e);
+ std::process::exit(1);
+ }
return;
}
DenoSubcommand::Types => {
@@ -488,8 +560,10 @@ pub fn main() {
crate::js::SHARED_GLOBALS_LIB,
crate::js::WINDOW_LIB
);
- // TODO(ry) Only ignore SIGPIPE. Currently ignoring all errors.
- let _r = std::io::stdout().write_all(types.as_bytes());
+ if let Err(e) = write_to_stdout_ignore_sigpipe(types.as_bytes()) {
+ eprintln!("{}", e);
+ std::process::exit(1);
+ }
return;
}
DenoSubcommand::Upgrade { force, dry_run } => {