mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
feat: Add "deno doc" subcommand (#4500)
This commit is contained in:
parent
bced52505f
commit
3fac487461
18 changed files with 3233 additions and 4 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
207
cli/doc/class.rs
Normal file
207
cli/doc/class.rs
Normal file
|
@ -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)
|
||||
}
|
41
cli/doc/enum.rs
Normal file
41
cli/doc/enum.rs
Normal file
|
@ -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)
|
||||
}
|
70
cli/doc/function.rs
Normal file
70
cli/doc/function.rs
Normal file
|
@ -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)
|
||||
}
|
243
cli/doc/interface.rs
Normal file
243
cli/doc/interface.rs
Normal file
|
@ -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)
|
||||
}
|
63
cli/doc/mod.rs
Normal file
63
cli/doc/mod.rs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
189
cli/doc/module.rs
Normal file
189
cli/doc/module.rs
Normal file
|
@ -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
|
||||
}
|
81
cli/doc/namespace.rs
Normal file
81
cli/doc/namespace.rs
Normal file
|
@ -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)
|
||||
}
|
77
cli/doc/node.rs
Normal file
77
cli/doc/node.rs
Normal file
|
@ -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>,
|
||||
}
|
189
cli/doc/parser.rs
Normal file
189
cli/doc/parser.rs
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
432
cli/doc/printer.rs
Normal file
432
cli/doc/printer.rs
Normal file
|
@ -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)
|
||||
}
|
568
cli/doc/tests.rs
Normal file
568
cli/doc/tests.rs
Normal file
|
@ -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"));
|
||||
}
|
821
cli/doc/ts_type.rs
Normal file
821
cli/doc/ts_type.rs
Normal file
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
cli/doc/type_alias.rs
Normal file
25
cli/doc/type_alias.rs
Normal file
|
@ -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)
|
||||
}
|
43
cli/doc/variable.rs
Normal file
43
cli/doc/variable.rs
Normal file
|
@ -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)
|
||||
}
|
97
cli/flags.rs
97
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",
|
||||
|
@ -2379,6 +2441,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"]);
|
||||
|
|
80
cli/lib.rs
80
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 } => {
|
||||
|
|
Loading…
Add table
Reference in a new issue