diff options
-rw-r--r-- | cli/doc/class.rs | 34 | ||||
-rw-r--r-- | cli/doc/module.rs | 1 | ||||
-rw-r--r-- | cli/doc/parser.rs | 70 | ||||
-rw-r--r-- | cli/doc/tests.rs | 232 |
4 files changed, 322 insertions, 15 deletions
diff --git a/cli/doc/class.rs b/cli/doc/class.rs index 2370dd18f..8ebb49f65 100644 --- a/cli/doc/class.rs +++ b/cli/doc/class.rs @@ -85,15 +85,15 @@ fn prop_name_to_string( } } -pub fn get_doc_for_class_decl( +pub fn class_to_class_def( doc_parser: &DocParser, - class_decl: &swc_ecma_ast::ClassDecl, -) -> (String, ClassDef) { + class: &swc_ecma_ast::Class, +) -> ClassDef { let mut constructors = vec![]; let mut methods = vec![]; let mut properties = vec![]; - let extends: Option<String> = match &class_decl.class.super_class { + let extends: Option<String> = match &class.super_class { Some(boxed) => { use crate::swc_ecma_ast::Expr; let expr: &Expr = &**boxed; @@ -105,14 +105,13 @@ pub fn get_doc_for_class_decl( None => None, }; - let implements: Vec<String> = class_decl - .class + let implements: Vec<String> = class .implements .iter() .map(|expr| ts_entity_name_to_name(&expr.expr)) .collect(); - for member in &class_decl.class.body { + for member in &class.body { use crate::swc_ecma_ast::ClassMember::*; match member { @@ -207,19 +206,26 @@ pub fn get_doc_for_class_decl( } } - let type_params = maybe_type_param_decl_to_type_param_defs( - class_decl.class.type_params.as_ref(), - ); - let class_name = class_decl.ident.sym.to_string(); - let class_def = ClassDef { - is_abstract: class_decl.class.is_abstract, + let type_params = + maybe_type_param_decl_to_type_param_defs(class.type_params.as_ref()); + + ClassDef { + is_abstract: class.is_abstract, extends, implements, constructors, properties, methods, type_params, - }; + } +} + +pub fn get_doc_for_class_decl( + doc_parser: &DocParser, + class_decl: &swc_ecma_ast::ClassDecl, +) -> (String, ClassDef) { + let class_name = class_decl.ident.sym.to_string(); + let class_def = class_to_class_def(doc_parser, &class_decl.class); (class_name, class_def) } diff --git a/cli/doc/module.rs b/cli/doc/module.rs index 2de9c7ca8..ab137904b 100644 --- a/cli/doc/module.rs +++ b/cli/doc/module.rs @@ -16,6 +16,7 @@ pub fn get_doc_node_for_export_decl( let js_doc = doc_parser.js_doc_for_span(export_span); let location = doc_parser.ast_parser.get_span_location(export_span).into(); + eprintln!("decl {:#?}", export_decl); match &export_decl.decl { Decl::Class(class_decl) => { let (name, class_def) = diff --git a/cli/doc/parser.rs b/cli/doc/parser.rs index 2a15daa59..0ab3c8d34 100644 --- a/cli/doc/parser.rs +++ b/cli/doc/parser.rs @@ -4,6 +4,7 @@ use crate::swc_common::comments::CommentKind; use crate::swc_common::Span; use crate::swc_ecma_ast; use crate::swc_ecma_ast::Decl; +use crate::swc_ecma_ast::DefaultDecl; use crate::swc_ecma_ast::ModuleDecl; use crate::swc_ecma_ast::Stmt; use crate::swc_util::AstParser; @@ -200,6 +201,74 @@ impl DocParser { export_decl, )] } + ModuleDecl::ExportDefaultDecl(export_default_decl) => { + let (js_doc, location) = + self.details_for_span(export_default_decl.span); + let name = "default".to_string(); + + let doc_node = match &export_default_decl.decl { + DefaultDecl::Class(class_expr) => { + let class_def = + crate::doc::class::class_to_class_def(self, &class_expr.class); + 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, + } + } + DefaultDecl::Fn(fn_expr) => { + let function_def = + crate::doc::function::function_to_function_def(&fn_expr.function); + DocNode { + kind: DocNodeKind::Function, + name, + location, + js_doc, + class_def: None, + function_def: Some(function_def), + variable_def: None, + enum_def: None, + type_alias_def: None, + namespace_def: None, + interface_def: None, + } + } + DefaultDecl::TsInterfaceDecl(interface_decl) => { + let (_, interface_def) = + crate::doc::interface::get_doc_for_ts_interface_decl( + self, + interface_decl, + ); + DocNode { + kind: DocNodeKind::Interface, + name, + location, + js_doc, + class_def: None, + function_def: None, + variable_def: None, + enum_def: None, + type_alias_def: None, + namespace_def: None, + interface_def: Some(interface_def), + } + } + }; + + vec![doc_node] + } + ModuleDecl::ExportDefaultExpr(export_default_expr) => { + eprintln!("export default expr {:#?}", export_default_expr); + vec![] + } _ => vec![], } } @@ -386,6 +455,7 @@ impl DocParser { if let swc_ecma_ast::ModuleItem::ModuleDecl(module_decl) = node { let r = match module_decl { ModuleDecl::ExportNamed(named_export) => { + eprintln!("export named {:#?}", named_export); if let Some(src) = &named_export.src { let src_str = src.value.to_string(); named_export diff --git a/cli/doc/tests.rs b/cli/doc/tests.rs index 2317c3707..d30736f0d 100644 --- a/cli/doc/tests.rs +++ b/cli/doc/tests.rs @@ -1064,6 +1064,234 @@ declare namespace RootNs { .contains("namespace RootNs") ); } + +#[tokio::test] +async fn export_default_fn() { + let source_code = r#" +export default function foo(a: number) { + return a; +} + "#; + let loader = + TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]); + let entries = DocParser::new(loader).parse("test.ts").await.unwrap(); + assert_eq!(entries.len(), 1); + let entry = &entries[0]; + let expected_json = json!({ + "kind": "function", + "name": "default", + "location": { + "filename": "test.ts", + "line": 2, + "col": 15 + }, + "jsDoc": null, + "functionDef": { + "params": [ + { + "name": "a", + "kind": "identifier", + "optional": false, + "tsType": { + "keyword": "number", + "kind": "keyword", + "repr": "number", + }, + } + ], + "typeParams": [], + "returnType": null, + "isAsync": false, + "isGenerator": false + } + }); + let actual = serde_json::to_value(entry).unwrap(); + assert_eq!(actual, expected_json); + + assert!( + colors::strip_ansi_codes(super::printer::format(entries).as_str()) + .contains("function default(a: number)") + ); +} + +#[tokio::test] +async fn export_default_class() { + let source_code = r#" +/** Class doc */ +export default class Foobar { + /** Constructor js doc */ + constructor(name: string, private private2: number, protected protected2: number) {} +} +"#; + let loader = + TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]); + let entries = DocParser::new(loader).parse("test.ts").await.unwrap(); + assert_eq!(entries.len(), 1); + let expected_json = json!({ + "kind": "class", + "name": "default", + "location": { + "filename": "test.ts", + "line": 3, + "col": 0 + }, + "jsDoc": "Class doc", + "classDef": { + "isAbstract": false, + "extends": null, + "implements": [], + "typeParams": [], + "constructors": [ + { + "jsDoc": "Constructor js doc", + "accessibility": null, + "name": "constructor", + "params": [ + { + "name": "name", + "kind": "identifier", + "optional": false, + "tsType": { + "repr": "string", + "kind": "keyword", + "keyword": "string" + } + }, + { + "name": "private2", + "kind": "identifier", + "optional": false, + "tsType": { + "repr": "number", + "kind": "keyword", + "keyword": "number" + } + }, + { + "name": "protected2", + "kind": "identifier", + "optional": false, + "tsType": { + "repr": "number", + "kind": "keyword", + "keyword": "number" + } + } + ], + "location": { + "filename": "test.ts", + "line": 5, + "col": 4 + } + } + ], + "properties": [], + "methods": [] + } + }); + let entry = &entries[0]; + let actual = serde_json::to_value(entry).unwrap(); + assert_eq!(actual, expected_json); + + assert!( + colors::strip_ansi_codes(super::printer::format(entries).as_str()) + .contains("class default") + ); +} + +#[tokio::test] +async fn export_default_interface() { + let source_code = r#" +/** + * Interface js doc + */ +export default interface Reader { + /** Read n bytes */ + read?(buf: Uint8Array, something: unknown): Promise<number> +} + "#; + let loader = + TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]); + let entries = DocParser::new(loader).parse("test.ts").await.unwrap(); + assert_eq!(entries.len(), 1); + let entry = &entries[0]; + let expected_json = json!({ + "kind": "interface", + "name": "default", + "location": { + "filename": "test.ts", + "line": 5, + "col": 0 + }, + "jsDoc": "Interface js doc", + "interfaceDef": { + "extends": [], + "methods": [ + { + "name": "read", + "location": { + "filename": "test.ts", + "line": 7, + "col": 4 + }, + "optional": true, + "jsDoc": "Read n bytes", + "params": [ + { + "name": "buf", + "kind": "identifier", + "optional": false, + "tsType": { + "repr": "Uint8Array", + "kind": "typeRef", + "typeRef": { + "typeParams": null, + "typeName": "Uint8Array" + } + } + }, + { + "name": "something", + "kind": "identifier", + "optional": false, + "tsType": { + "repr": "unknown", + "kind": "keyword", + "keyword": "unknown" + } + } + ], + "typeParams": [], + "returnType": { + "repr": "Promise", + "kind": "typeRef", + "typeRef": { + "typeParams": [ + { + "repr": "number", + "kind": "keyword", + "keyword": "number" + } + ], + "typeName": "Promise" + } + } + } + ], + "properties": [], + "callSignatures": [], + "typeParams": [], + } + }); + let actual = serde_json::to_value(entry).unwrap(); + assert_eq!(actual, expected_json); + + assert!( + colors::strip_ansi_codes(super::printer::format(entries).as_str()) + .contains("interface default") + ); +} + #[tokio::test] async fn optional_return_type() { let source_code = r#" @@ -1120,6 +1348,8 @@ async fn reexports() { * JSDoc for bar */ export const bar = "bar"; + +export default 42; "#; let reexport_source_code = r#" import { bar } from "./nested_reexport.ts"; @@ -1130,7 +1360,7 @@ import { bar } from "./nested_reexport.ts"; export const foo = "foo"; "#; let test_source_code = r#" -export { foo as fooConst } from "./reexport.ts"; +export { default, foo as fooConst } from "./reexport.ts"; /** JSDoc for function */ export function fooFn(a: number) { |