diff options
Diffstat (limited to 'cli/doc/tests.rs')
-rw-r--r-- | cli/doc/tests.rs | 2377 |
1 files changed, 1413 insertions, 964 deletions
diff --git a/cli/doc/tests.rs b/cli/doc/tests.rs index 7efc3857b..e46fff621 100644 --- a/cli/doc/tests.rs +++ b/cli/doc/tests.rs @@ -1,5 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use super::DocParser; +use super::DocPrinter; use crate::colors; use serde_json::json; @@ -41,360 +42,334 @@ impl DocFileLoader for TestLoader { } } +macro_rules! doc_test { + ( $name:ident, $source:expr; $block:block ) => { + doc_test!($name, $source, false, false; $block); + }; + + ( $name:ident, $source:expr, details; $block:block ) => { + doc_test!($name, $source, true, false; $block); + }; + + ( $name:ident, $source:expr, private; $block:block ) => { + doc_test!($name, $source, false, true; $block); + }; + + ( $name:ident, $source:expr, details, private; $block:block ) => { + doc_test!($name, $source, true, true; $block); + }; + + ( $name:ident, $source:expr, $details:expr, $private:expr; $block:block ) => { + #[tokio::test] + async fn $name() { + let source_code = $source; + let details = $details; + let private = $private; + + let loader = + TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]); + let entries = DocParser::new(loader, private) + .parse("test.ts") + .await + .unwrap(); + + let doc = DocPrinter::new(&entries, details, private).to_string(); + #[allow(unused_variables)] + let doc = colors::strip_ansi_codes(&doc); + + $block + } + }; +} + +macro_rules! contains_test { + ( $name:ident, $source:expr; + $( $contains:expr ),* $( ; $( $notcontains:expr ),* )? ) => { + contains_test!($name, $source, false, false; $($contains),* $(;$($notcontains),*)?); + }; + + ( $name:ident, $source:expr, details; + $( $contains:expr ),* $( ; $( $notcontains:expr ),* )? ) => { + contains_test!($name, $source, true, false; $($contains),* $(;$($notcontains),*)?); + }; + + ( $name:ident, $source:expr, private; + $( $contains:expr ),* $( ; $( $notcontains:expr ),* )? ) => { + contains_test!($name, $source, false, true; $($contains),* $(;$($notcontains),*)?); + }; + + ( $name:ident, $source:expr, details, private; + $( $contains:expr ),* $( ; $( $notcontains:expr ),* )? ) => { + contains_test!($name, $source, true, true; $($contains),* $(;$($notcontains),*)?); + }; + + ( $name:ident, $source:expr, $details:expr, $private:expr; + $( $contains:expr ),* $( ; $( $notcontains:expr ),* )? ) => { + doc_test!($name, $source, $details, $private; { + $( + assert!(doc.contains($contains)); + )* + $( + $( + assert!(!doc.contains($notcontains)); + )* + )? + }); + }; +} + +macro_rules! json_test { + ( $name:ident, $source:expr; $json:tt ) => { + json_test!($name, $source, false; $json); + }; + + ( $name:ident, $source:expr, private; $json:tt ) => { + json_test!($name, $source, true; $json); + }; + + ( $name:ident, $source:expr, $private:expr; $json:tt ) => { + doc_test!($name, $source, false, $private; { + let actual = serde_json::to_value(&entries).unwrap(); + let expected_json = json!($json); + assert_eq!(actual, expected_json); + }); + }; +} + #[tokio::test] -async fn export_fn() { - let source_code = r#"/** -* @module foo -*/ +async fn reexports() { + let nested_reexport_source_code = r#" +/** + * JSDoc for bar + */ +export const bar = "bar"; + +export default 42; +"#; + let reexport_source_code = r#" +import { bar } from "./nested_reexport.ts"; /** -* Hello there, this is a multiline JSdoc. -* -* It has many lines -* -* Or not that many? -*/ -export function foo(a: string, b?: number, cb: (...cbArgs: unknown[]) => void, ...args: unknown[]): void { - /** - * @todo document all the things. - */ - console.log("Hello world"); + * JSDoc for const + */ +export const foo = "foo"; +"#; + let test_source_code = r#" +export { default, foo as fooConst } from "./reexport.ts"; + +/** JSDoc for function */ +export function fooFn(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!({ - "functionDef": { - "isAsync": false, - "isGenerator": false, - "typeParams": [], - "params": [ - { - "name": "a", - "kind": "identifier", - "optional": false, - "tsType": { - "keyword": "string", - "kind": "keyword", - "repr": "string", - }, - }, - { - "name": "b", - "kind": "identifier", - "optional": true, - "tsType": { - "keyword": "number", - "kind": "keyword", - "repr": "number", - }, - }, - { - "name": "cb", - "kind": "identifier", - "optional": false, - "tsType": { - "repr": "", - "kind": "fnOrConstructor", - "fnOrConstructor": { - "constructor": false, - "tsType": { - "keyword": "void", - "kind": "keyword", - "repr": "void" - }, - "typeParams": [], - "params": [{ - "kind": "rest", - "name": "cbArgs", - "optional": false, - "tsType": { - "repr": "", - "kind": "array", - "array": { - "repr": "unknown", - "kind": "keyword", - "keyword": "unknown" - } - }, - }] - } - }, - }, - { - "name": "args", - "kind": "rest", - "optional": false, - "tsType": { - "repr": "", - "kind": "array", - "array": { - "repr": "unknown", - "kind": "keyword", - "keyword": "unknown" - } - } - } - ], - "returnType": { - "keyword": "void", - "kind": "keyword", - "repr": "void", + let loader = TestLoader::new(vec![ + ("file:///test.ts".to_string(), test_source_code.to_string()), + ( + "file:///reexport.ts".to_string(), + reexport_source_code.to_string(), + ), + ( + "file:///nested_reexport.ts".to_string(), + nested_reexport_source_code.to_string(), + ), + ]); + let entries = DocParser::new(loader, false) + .parse_with_reexports("file:///test.ts") + .await + .unwrap(); + assert_eq!(entries.len(), 2); + + let expected_json = json!([ + { + "kind": "variable", + "name": "fooConst", + "location": { + "filename": "file:///reexport.ts", + "line": 7, + "col": 0 }, + "jsDoc": "JSDoc for const", + "variableDef": { + "tsType": null, + "kind": "const" + } }, - "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": 12, - }, - "name": "foo", - }); - - let actual = serde_json::to_value(entry).unwrap(); + { + "kind": "function", + "name": "fooFn", + "location": { + "filename": "file:///test.ts", + "line": 5, + "col": 0 + }, + "jsDoc": "JSDoc for function", + "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(&entries).unwrap(); assert_eq!(actual, expected_json); assert!(colors::strip_ansi_codes( - super::printer::format(entries.clone()).as_str() + DocPrinter::new(&entries, false, false).to_string().as_str() ) - .contains("Hello there")); - assert!( - colors::strip_ansi_codes(super::printer::format(entries).as_str()) - .contains("b?: number") - ); -} - -#[tokio::test] -async fn format_type_predicate() { - let source_code = r#" -export function isFish(pet: Fish | Bird): pet is Fish { - return (pet as Fish).swim !== undefined; -} -"#; - let loader = - TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]); - let entries = DocParser::new(loader).parse("test.ts").await.unwrap(); - super::printer::format(entries); + .contains("function fooFn(a: number)")); } #[tokio::test] -async fn export_fn2() { +async fn filter_nodes_by_name() { + use super::find_nodes_by_name_recursively; let source_code = r#" -interface AssignOpts { - a: string; - b: number; +export namespace Deno { + export class Buffer {} + export function test(options: object): void; + export function test(name: string, fn: Function): void; + export function test(name: string | object, fn?: Function): void {} } -export function foo([e,,f, ...g]: number[], { c, d: asdf, i = "asdf", ...rest}, ops: AssignOpts = {}): void { - console.log("Hello world"); +export namespace Deno { + export namespace Inner { + export function a(): void {} + export const b = 100; + } } "#; 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!({ - "functionDef": { - "isAsync": false, - "isGenerator": false, - "typeParams": [], - "params": [ - { - "name": "", - "kind": "array", - "optional": false, - "tsType": { - "repr": "", - "kind": "array", - "array": { - "repr": "number", - "kind": "keyword", - "keyword": "number" - } - } - }, - { - "name": "", - "kind": "object", - "optional": false, - "tsType": null - }, - { - "name": "ops", - "kind": "identifier", - "optional": false, - "tsType": { - "repr": "AssignOpts", - "kind": "typeRef", - "typeRef": { - "typeName": "AssignOpts", - "typeParams": null, - } - } - }, - ], - "returnType": { - "keyword": "void", - "kind": "keyword", - "repr": "void", - }, - }, - "jsDoc": null, - "kind": "function", - "location": { - "col": 0, - "filename": "test.ts", - "line": 7, - }, - "name": "foo", - }); + let entries = DocParser::new(loader, false) + .parse("test.ts") + .await + .unwrap(); - let actual = serde_json::to_value(entry).unwrap(); - assert_eq!(actual, expected_json); + let found = + find_nodes_by_name_recursively(entries.clone(), "Deno".to_string()); + assert_eq!(found.len(), 2); + assert_eq!(found[0].name, "Deno".to_string()); + assert_eq!(found[1].name, "Deno".to_string()); - assert!( - colors::strip_ansi_codes(super::printer::format(entries).as_str()) - .contains("foo") - ); -} + let found = + find_nodes_by_name_recursively(entries.clone(), "Deno.test".to_string()); + assert_eq!(found.len(), 3); + assert_eq!(found[0].name, "test".to_string()); + assert_eq!(found[1].name, "test".to_string()); + assert_eq!(found[2].name, "test".to_string()); -#[tokio::test] -async fn export_const() { - let source_code = r#" -/** Something about fizzBuzz */ -export const fizzBuzz = "fizzBuzz"; + let found = + find_nodes_by_name_recursively(entries.clone(), "Deno.Inner.a".to_string()); + assert_eq!(found.len(), 1); + assert_eq!(found[0].name, "a".to_string()); -export const env: { - /** get doc */ - get(key: string): string | undefined; + let found = + find_nodes_by_name_recursively(entries.clone(), "Deno.test.a".to_string()); + assert_eq!(found.len(), 0); - /** set doc */ - set(key: string, value: string): void; + let found = find_nodes_by_name_recursively(entries, "a.b.c".to_string()); + assert_eq!(found.len(), 0); } -"#; - 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(), 2); - let expected_json = json!([ - { - "kind":"variable", - "name":"fizzBuzz", - "location":{ - "filename":"test.ts", - "line":3, - "col":0 - }, - "jsDoc":"Something about fizzBuzz", - "variableDef":{ - "tsType":null, - "kind":"const" + +mod serialization { + use super::*; + + json_test!(declare_namespace, + r#" +/** Namespace JSdoc */ +declare namespace RootNs { + declare const a = "a"; + + /** Nested namespace JSDoc */ + declare namespace NestedNs { + declare enum Foo { + a = 1, + b = 2, + c = 3, + } } - }, - { - "kind":"variable", - "name":"env", - "location":{ - "filename":"test.ts", - "line":5, - "col":0 +} + "#; + [{ + "kind": "namespace", + "name": "RootNs", + "location": { + "filename": "test.ts", + "line": 3, + "col": 0 }, - "jsDoc":null, - "variableDef":{ - "tsType":{ - "repr":"", - "kind":"typeLiteral", - "typeLiteral":{ - "methods":[{ - "name":"get", - "params":[ + "jsDoc": "Namespace JSdoc", + "namespaceDef": { + "elements": [ + { + "kind": "variable", + "name": "a", + "location": { + "filename": "test.ts", + "line": 4, + "col": 12 + }, + "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": [ { - "name":"key", - "kind":"identifier", - "optional":false, - "tsType":{ - "repr":"string", - "kind":"keyword", - "keyword":"string" - } - } - ], - "returnType":{ - "repr":"", - "kind":"union", - "union":[ - { - "repr":"string", - "kind":"keyword", - "keyword":"string" + "kind": "enum", + "name": "Foo", + "location": { + "filename": "test.ts", + "line": 8, + "col": 6 }, - { - "repr":"undefined", - "kind":"keyword", - "keyword":"undefined" - } - ] - }, - "typeParams":[] - }, { - "name":"set", - "params":[ - { - "name":"key", - "kind":"identifier", - "optional":false, - "tsType":{ - "repr":"string", - "kind":"keyword", - "keyword":"string" - } - }, - { - "name":"value", - "kind":"identifier", - "optional":false, - "tsType":{ - "repr":"string", - "kind":"keyword", - "keyword":"string" + "jsDoc": null, + "enumDef": { + "members": [ + { + "name": "a" + }, + { + "name": "b" + }, + { + "name": "c" + } + ] } } - ], - "returnType":{ - "repr":"void", - "kind":"keyword", - "keyword":"void" - }, - "typeParams":[] - } - ], - "properties":[], - "callSignatures":[] + ] } - }, - "kind":"const" - } + } + ] } - ] - ); - - let actual = serde_json::to_value(entries.clone()).unwrap(); - assert_eq!(actual, expected_json); - - assert!( - colors::strip_ansi_codes(super::printer::format(entries).as_str()) - .contains("Something about fizzBuzz") - ); -} + }]); -#[tokio::test] -async fn export_class() { - let source_code = r#" + json_test!(export_class, + r#" /** Class doc */ export class Foobar extends Fizz implements Buzz, Aldrin { private private1?: boolean; @@ -415,12 +390,8 @@ export class Foobar extends Fizz implements Buzz, Aldrin { // } } -"#; - 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": "Foobar", "location": { @@ -432,8 +403,26 @@ export class Foobar extends Fizz implements Buzz, Aldrin { "classDef": { "isAbstract": false, "extends": "Fizz", - "implements": ["Buzz", "Aldrin"], + "implements": [ + { + "repr": "Buzz", + "kind": "typeRef", + "typeRef": { + "typeParams": null, + "typeName": "Buzz" + } + }, + { + "repr": "Aldrin", + "kind": "typeRef", + "typeRef": { + "typeParams": null, + "typeName": "Aldrin" + } + } + ], "typeParams": [], + "superTypeParams": [], "constructors": [ { "jsDoc": "Constructor js doc", @@ -556,6 +545,7 @@ export class Foobar extends Fizz implements Buzz, Aldrin { } } ], + "indexSignatures": [], "methods": [ { "jsDoc": "Async foo method", @@ -618,25 +608,591 @@ export class Foobar extends Fizz implements Buzz, Aldrin { } ] } - }); - 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_details(entry.clone()).as_str() - ) - .contains("bar?(): void")); + json_test!(export_const, + r#" +/** Something about fizzBuzz */ +export const fizzBuzz = "fizzBuzz"; + +export const env: { + /** get doc */ + get(key: string): string | undefined; - assert!( - colors::strip_ansi_codes(super::printer::format(entries).as_str()) - .contains("class Foobar extends Fizz implements Buzz, Aldrin") + /** set doc */ + set(key: string, value: string): void; +} + "#; + [ + { + "kind":"variable", + "name":"fizzBuzz", + "location":{ + "filename":"test.ts", + "line":3, + "col":0 + }, + "jsDoc":"Something about fizzBuzz", + "variableDef":{ + "tsType":null, + "kind":"const" + } + }, + { + "kind":"variable", + "name":"env", + "location":{ + "filename":"test.ts", + "line":5, + "col":0 + }, + "jsDoc":null, + "variableDef":{ + "tsType":{ + "repr":"", + "kind":"typeLiteral", + "typeLiteral":{ + "methods":[{ + "name":"get", + "params":[ + { + "name":"key", + "kind":"identifier", + "optional":false, + "tsType":{ + "repr":"string", + "kind":"keyword", + "keyword":"string" + } + } + ], + "returnType":{ + "repr":"", + "kind":"union", + "union":[ + { + "repr":"string", + "kind":"keyword", + "keyword":"string" + }, + { + "repr":"undefined", + "kind":"keyword", + "keyword":"undefined" + } + ] + }, + "typeParams":[] + }, { + "name":"set", + "params":[ + { + "name":"key", + "kind":"identifier", + "optional":false, + "tsType":{ + "repr":"string", + "kind":"keyword", + "keyword":"string" + } + }, + { + "name":"value", + "kind":"identifier", + "optional":false, + "tsType":{ + "repr":"string", + "kind":"keyword", + "keyword":"string" + } + } + ], + "returnType":{ + "repr":"void", + "kind":"keyword", + "keyword":"void" + }, + "typeParams":[] + } + ], + "properties":[], + "callSignatures":[], + "indexSignatures": [] + } + }, + "kind":"const" + } + } + ] ); + + json_test!(export_default_class, + r#" +/** Class doc */ +export default class Foobar { + /** Constructor js doc */ + constructor(name: string, private private2: number, protected protected2: number) {} +} + "#; + [{ + "kind": "class", + "name": "default", + "location": { + "filename": "test.ts", + "line": 3, + "col": 0 + }, + "jsDoc": "Class doc", + "classDef": { + "isAbstract": false, + "extends": null, + "implements": [], + "typeParams": [], + "superTypeParams": [], + "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": [], + "indexSignatures": [], + "methods": [] + } + }]); + + json_test!(export_default_fn, + r#" +export default function foo(a: number) { + return a; } + "#; + [{ + "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 + } + }]); -#[tokio::test] -async fn export_interface() { - let source_code = r#" + json_test!(export_default_interface, + r#" +/** + * Interface js doc + */ +export default interface Reader { + /** Read n bytes */ + read?(buf: Uint8Array, something: unknown): Promise<number> +} + "#; + [{ + "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": [], + "indexSignatures": [], + "typeParams": [] + } + }]); + + json_test!(export_enum, + r#" +/** + * Some enum for good measure + */ +export enum Hello { + World = "world", + Fizz = "fizz", + Buzz = "buzz", +} + "#; + [{ + "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" + } + ] + } + }]); + + json_test!(export_fn, + r#"/** +* @module foo +*/ + +/** +* Hello there, this is a multiline JSdoc. +* +* It has many lines +* +* Or not that many? +*/ +export function foo(a: string, b?: number, cb: (...cbArgs: unknown[]) => void, ...args: unknown[]): void { + /** + * @todo document all the things. + */ + console.log("Hello world"); +} + "#; + [{ + "functionDef": { + "isAsync": false, + "isGenerator": false, + "typeParams": [], + "params": [ + { + "name": "a", + "kind": "identifier", + "optional": false, + "tsType": { + "keyword": "string", + "kind": "keyword", + "repr": "string", + }, + }, + { + "name": "b", + "kind": "identifier", + "optional": true, + "tsType": { + "keyword": "number", + "kind": "keyword", + "repr": "number", + }, + }, + { + "name": "cb", + "kind": "identifier", + "optional": false, + "tsType": { + "repr": "", + "kind": "fnOrConstructor", + "fnOrConstructor": { + "constructor": false, + "tsType": { + "keyword": "void", + "kind": "keyword", + "repr": "void" + }, + "typeParams": [], + "params": [{ + "arg": { + "name": "cbArgs", + "kind": "identifier", + "optional": false, + "tsType": null + }, + "kind": "rest", + "tsType": { + "repr": "", + "kind": "array", + "array": { + "repr": "unknown", + "kind": "keyword", + "keyword": "unknown" + } + }, + }] + } + }, + }, + { + "arg": { + "name": "args", + "kind": "identifier", + "optional": false, + "tsType": null + }, + "kind": "rest", + "tsType": { + "array": { + "keyword": "unknown", + "kind": "keyword", + "repr": "unknown" + }, + "kind": "array", + "repr": "" + } + } + ], + "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": 12, + }, + "name": "foo", + }]); + + json_test!(export_fn2, + r#" +interface AssignOpts { + a: string; + b: number; +} + +export function foo([e,,f, ...g]: number[], { c, d: asdf, i = "asdf", ...rest}, ops: AssignOpts = {}): void { + console.log("Hello world"); +} + "#; + [{ + "functionDef": { + "isAsync": false, + "isGenerator": false, + "typeParams": [], + "params": [ + { + "elements": [ + { + "name": "e", + "kind": "identifier", + "optional": false, + "tsType": null + }, + null, + { + "name": "f", + "kind": "identifier", + "optional": false, + "tsType": null + }, + { + "arg": { + "name": "g", + "kind": "identifier", + "optional": false, + "tsType": null + }, + "kind": "rest", + "tsType": null + } + ], + "kind": "array", + "optional": false, + "tsType": { + "repr": "", + "kind": "array", + "array": { + "repr": "number", + "kind": "keyword", + "keyword": "number" + } + } + }, + { + "kind": "object", + "optional": false, + "props": [ + { + "kind": "assign", + "key": "c", + "value": null + }, + { + "kind": "keyValue", + "key": "d", + "value": { + "name": "asdf", + "kind": "identifier", + "optional": false, + "tsType": null + } + }, + { + "kind": "assign", + "key": "i", + "value": "<UNIMPLEMENTED>" + }, + { + "arg": { + "name": "rest", + "kind": "identifier", + "optional": false, + "tsType": null + }, + "kind": "rest" + } + ], + "tsType": null + }, + { + "kind": "assign", + "left": { + "name": "ops", + "kind": "identifier", + "optional": false, + "tsType": { + "repr": "AssignOpts", + "kind": "typeRef", + "typeRef": { + "typeName": "AssignOpts", + "typeParams": null, + } + } + }, + "right": "<UNIMPLEMENTED>", + "tsType": null + } + ], + "returnType": { + "keyword": "void", + "kind": "keyword", + "repr": "void", + }, + }, + "jsDoc": null, + "kind": "function", + "location": { + "col": 0, + "filename": "test.ts", + "line": 7, + }, + "name": "foo", + }]); + + json_test!(export_interface, + r#" interface Foo { foo(): void; } @@ -651,12 +1207,7 @@ export interface Reader extends Foo, Bar { 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": "Reader", "location": { @@ -666,7 +1217,24 @@ export interface Reader extends Foo, Bar { }, "jsDoc": "Interface js doc", "interfaceDef": { - "extends": ["Foo", "Bar"], + "extends": [ + { + "repr": "Foo", + "kind": "typeRef", + "typeRef": { + "typeParams": null, + "typeName": "Foo" + } + }, + { + "repr": "Bar", + "kind": "typeRef", + "typeRef": { + "typeParams": null, + "typeName": "Bar" + } + } + ], "methods": [ { "name": "read", @@ -721,31 +1289,18 @@ export interface Reader extends Foo, Bar { ], "properties": [], "callSignatures": [], + "indexSignatures": [], "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 Reader extends Foo, Bar") - ); -} + }]); -#[tokio::test] -async fn export_interface2() { - let source_code = r#" + json_test!(export_interface2, + r#" export interface TypedIface<T> { something(): T } "#; - 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": "TypedIface", "location": { @@ -780,32 +1335,19 @@ export interface TypedIface<T> { ], "properties": [], "callSignatures": [], + "indexSignatures": [], "typeParams": [ { "name": "T" } ], } - }); - 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 TypedIface") - ); -} + }]); -#[tokio::test] -async fn export_type_alias() { - let source_code = r#" + json_test!(export_type_alias, + r#" /** Array holding numbers */ export type NumberArray = Array<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": "typeAlias", "name": "NumberArray", "location": { @@ -831,76 +1373,10 @@ export type NumberArray = Array<number>; } } } - }); - 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("Array holding numbers") - ); -} - -#[tokio::test] -async fn export_enum() { - let source_code = r#" -/** - * Some enum for good measure - */ -export enum Hello { - World = "world", - Fizz = "fizz", - Buzz = "buzz", -} - "#; - 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": "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!(colors::strip_ansi_codes( - super::printer::format_details(entry.clone()).as_str() - ) - .contains("World")); - assert!(colors::strip_ansi_codes( - super::printer::format(entries.clone()).as_str() - ) - .contains("Some enum for good measure")); - assert!( - colors::strip_ansi_codes(super::printer::format(entries).as_str()) - .contains("enum Hello") - ); -} - -#[tokio::test] -async fn export_namespace() { - let source_code = r#" + json_test!(export_namespace, + r#" /** Namespace JSdoc */ export namespace RootNs { export const a = "a"; @@ -915,12 +1391,7 @@ export namespace RootNs { } } "#; - 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": "namespace", "name": "RootNs", "location": { @@ -984,453 +1455,23 @@ export namespace RootNs { } ] } - }); - 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("namespace RootNs") - ); -} - -#[tokio::test] -async fn declare_namespace() { - let source_code = r#" -/** Namespace JSdoc */ -declare namespace RootNs { - declare const a = "a"; - - /** Nested namespace JSDoc */ - declare namespace NestedNs { - declare enum Foo { - a = 1, - b = 2, - c = 3, - } - } -} - "#; - 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": "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": 12 - }, - "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!( - colors::strip_ansi_codes(super::printer::format(entries).as_str()) - .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#" + json_test!(optional_return_type, + r#" export 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": "foo", - "location": { - "filename": "test.ts", - "line": 2, - "col": 2 - }, - "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 foo(a: number)") - ); -} - -#[tokio::test] -async fn reexports() { - let nested_reexport_source_code = r#" -/** - * JSDoc for bar - */ -export const bar = "bar"; - -export default 42; -"#; - let reexport_source_code = r#" -import { bar } from "./nested_reexport.ts"; - -/** - * JSDoc for const - */ -export const foo = "foo"; -"#; - let test_source_code = r#" -export { default, foo as fooConst } from "./reexport.ts"; - -/** JSDoc for function */ -export function fooFn(a: number) { - return a; -} -"#; - let loader = TestLoader::new(vec![ - ("file:///test.ts".to_string(), test_source_code.to_string()), - ( - "file:///reexport.ts".to_string(), - reexport_source_code.to_string(), - ), - ( - "file:///nested_reexport.ts".to_string(), - nested_reexport_source_code.to_string(), - ), - ]); - let entries = DocParser::new(loader) - .parse_with_reexports("file:///test.ts") - .await - .unwrap(); - assert_eq!(entries.len(), 2); - - let expected_json = json!([ - { - "kind": "variable", - "name": "fooConst", - "location": { - "filename": "file:///reexport.ts", - "line": 7, - "col": 0 - }, - "jsDoc": "JSDoc for const", - "variableDef": { - "tsType": null, - "kind": "const" - } - }, - { + [{ "kind": "function", - "name": "fooFn", + "name": "foo", "location": { - "filename": "file:///test.ts", - "line": 5, - "col": 0 + "filename": "test.ts", + "line": 2, + "col": 2 }, - "jsDoc": "JSDoc for function", + "jsDoc": null, "functionDef": { "params": [ { @@ -1449,30 +1490,17 @@ export function fooFn(a: number) { "isAsync": false, "isGenerator": false } - } - ]); - let actual = serde_json::to_value(entries.clone()).unwrap(); - assert_eq!(actual, expected_json); - - assert!( - colors::strip_ansi_codes(super::printer::format(entries).as_str()) - .contains("function fooFn(a: number)") + }] ); -} -#[tokio::test] -async fn ts_lit_types() { - let source_code = r#" + json_test!(ts_lit_types, + r#" export type boolLit = false; export type strLit = "text"; export type tplLit = `text`; export type numLit = 5; -"#; - let loader = - TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]); - let entries = DocParser::new(loader).parse("test.ts").await.unwrap(); - let actual = serde_json::to_value(entries).unwrap(); - let expected_json = json!([ + "#; + [ { "kind": "typeAlias", "name": "boolLit", @@ -1555,70 +1583,491 @@ export type numLit = 5; } } ]); - assert_eq!(actual, expected_json); } -#[tokio::test] -async fn filter_nodes_by_name() { - use super::find_nodes_by_name_recursively; - let source_code = r#" -export namespace Deno { - export class Buffer {} - export function test(options: object): void; - export function test(name: string, fn: Function): void; - export function test(name: string | object, fn?: Function): void {} +mod printer { + use super::*; + + contains_test!(abstract_class, + "export abstract class Class {}", + details; + "abstract class Class" + ); + + contains_test!(abstract_class_abstract_method, + r#" +export abstract class Class { + abstract method() {} } + "#, + details; + "abstract method()" + ); -export namespace Deno { - export namespace Inner { - export function a(): void {} - export const b = 100; - } + contains_test!(class_async_method, + r#" +export class Class { + async amethod(v) {} } -"#; - let loader = - TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]); - let entries = DocParser::new(loader).parse("test.ts").await.unwrap(); + "#, + details; + "async amethod(v)" + ); - let found = - find_nodes_by_name_recursively(entries.clone(), "Deno".to_string()); - assert_eq!(found.len(), 2); - assert_eq!(found[0].name, "Deno".to_string()); - assert_eq!(found[1].name, "Deno".to_string()); + contains_test!(class_constructor, + r#" +export class Class { + constructor(a, b) {} +} + "#, + details; + "constructor(a, b)" + ); - let found = - find_nodes_by_name_recursively(entries.clone(), "Deno.test".to_string()); - assert_eq!(found.len(), 3); - assert_eq!(found[0].name, "test".to_string()); - assert_eq!(found[1].name, "test".to_string()); - assert_eq!(found[2].name, "test".to_string()); + const CLASS_SOURCE: &str = r#" +export class C { + /** a doc */ + a() {} + f: number; +} + "#; - let found = - find_nodes_by_name_recursively(entries.clone(), "Deno.Inner.a".to_string()); - assert_eq!(found.len(), 1); - assert_eq!(found[0].name, "a".to_string()); + contains_test!(class_details, + CLASS_SOURCE, + details; + "class C", + "a()", + "f: number" + ); - let found = - find_nodes_by_name_recursively(entries.clone(), "Deno.test.a".to_string()); - assert_eq!(found.len(), 0); + contains_test!(class_details_all_with_private, + r#" +export class Class { + private pri() {} + protected pro() {} + public pub() {} +} + "#, + details, + private; + "private pri()", + "protected pro()", + "pub()" + ); - let found = find_nodes_by_name_recursively(entries, "a.b.c".to_string()); - assert_eq!(found.len(), 0); + contains_test!(class_details_only_non_private_without_private, + r#" +export class Class { + private pri() {} + protected pro() {} + public pub() {} } + "#, + details; + "protected pro()", + "pub()" + ); -#[tokio::test] -async fn generic_instantiated_with_tuple_type() { - let source_code = r#" + contains_test!(class_declaration, + "export class Class {}"; + "class Class" + ); + + contains_test!(class_extends, + "export class Class extends Object {}"; + "class Class extends Object" + ); + + contains_test!(class_extends_implements, + "export class Class extends Object implements Iterator, Iterable {}"; + "class Class extends Object implements Iterator, Iterable" + ); + + contains_test!(class_generic_extends_implements, + "export class Class<A, B> extends Map<A, B> implements Iterator<A>, Iterable<B> {}"; + "class Class<A, B> extends Map<A, B> implements Iterator<A>, Iterable<B>" + ); + + contains_test!(class_getter_and_setter, + r#" +export class Class { + get a(): void {} + set b(_v: void) {} +} + "#, + details; + "get a(): void", + "set b(_v: void)" + ); + + contains_test!(class_index_signature, + r#" +export class C { + [key: string]: number; +} + "#, + details; + "[key: string]: number" + ); + + contains_test!(class_implements, + "export class Class implements Iterator {}"; + "class Class implements Iterator" + ); + + contains_test!(class_implements2, + "export class Class implements Iterator, Iterable {}"; + "class Class implements Iterator, Iterable" + ); + + contains_test!(class_method, + r#" +export class Class { + method(v) {} +} + "#, + details; + "method(v)" + ); + + contains_test!(class_property, + r#" +export class Class { + someproperty: bool; + optproperty: bigint; +} + "#, + details; + "someproperty: bool", + "optproperty: bigint" + ); + + contains_test!(class_readonly_index_signature, + r#" +export class C { + readonly [key: string]: number; +} + "#, + details; + "readonly [key: string]: number" + ); + + contains_test!(class_static_property, + r#" +export class Class { + static property = ""; +} + "#, + details; + "static property" + ); + + contains_test!(class_summary, + CLASS_SOURCE; + "class C"; + "a()", + "f: number" + ); + + contains_test!(class_readonly_property, + r#" +export class Class { + readonly property = ""; +} + "#, + details; + "readonly property" + ); + + contains_test!(class_private_property, + r#" +export class Class { + private property = ""; +} + "#, + details, + private; + "private property" + ); + + contains_test!(const_declaration, + "export const Const = 0;"; + "const Const" + ); + + contains_test!(enum_declaration, + "export enum Enum {}"; + "enum Enum" + ); + + const EXPORT_SOURCE: &str = r#" +export function a() {} +function b() {} +export class C {} +class D {} +export interface E {} +interface F {} +export namespace G {} +namespace H {} + "#; + + contains_test!(exports_all_with_private, + EXPORT_SOURCE, + private; + "function a()", + "class C", + "interface E", + "namespace G", + "function b()", + "class D", + "interface F", + "namespace H" + ); + + contains_test!(exports_only_exports_without_private, + EXPORT_SOURCE; + "function a()", + "class C", + "interface E", + "namespace G"; + "function b()", + "class D", + "interface F", + "namespace H" + ); + + contains_test!(function_async, + "export async function a() {}"; + "async function a()" + ); + + contains_test!(function_array_deconstruction, + "export function f([a, b, ...c]) {}"; + "function f([a, b, ...c])" + ); + + contains_test!(function_async_generator, + "export async function* ag() {}"; + "async function* ag()" + ); + + contains_test!(function_declaration, + "export function fun() {}"; + "function fun()" + ); + + contains_test!(function_generator, + "export function* g() {}"; + "function* g()" + ); + + contains_test!(function_generic, + "export function add<T>(a: T, b: T) { return a + b; }"; + "function add<T>(a: T, b: T)" + ); + + contains_test!(function_object_deconstruction, + "export function f({ a, b, ...c }) {}"; + "function f({a, b, ...c})" + ); + + /* TODO(SyrupThinker) NYI + contains_test!(function_type_predicate, + r#" + export function isFish(pet: Fish | Bird): pet is Fish { + return (pet as Fish).swim !== undefined; + } + "#; + "pet is Fish" + ); + */ + + contains_test!(generic_instantiated_with_tuple_type, + r#" interface Generic<T> {} export function f(): Generic<[string, number]> { return {}; } - "#; + "#; + "Generic<[string, number]>" + ); - let loader = - TestLoader::new(vec![("test.ts".to_string(), source_code.to_string())]); - let entries = DocParser::new(loader).parse("test.ts").await.unwrap(); + contains_test!(type_literal_declaration, + "export type T = {}"; + "{ }" + ); - assert!(colors::strip_ansi_codes( - crate::doc::printer::format(entries).as_str() - ) - .contains("Generic<[string, number]>")) + contains_test!(type_literal_index_signature, + "export type T = { [key: string]: number; }"; + "[key: string]: number" + ); + + contains_test!(type_literal_readonly_index_signature, + "export type T = { readonly [key: string]: number; }"; + "readonly [key: string]: number" + ); + + contains_test!(interface_declaration, + "export interface Interface {}"; + "interface Interface" + ); + + contains_test!(interface_extends, + "export interface Interface extends Iterator {}"; + "interface Interface extends Iterator" + ); + + contains_test!(interface_extends2, + "export interface Interface extends Iterator, Iterable {}"; + "interface Interface extends Iterator, Iterable" + ); + + contains_test!(interface_generic, + "export interface Interface<T> {}"; + "interface Interface<T>" + ); + + contains_test!(interface_generic_extends, + "export interface Interface<V> extends Iterable<V> {}"; + "interface Interface<V> extends Iterable<V>" + ); + + contains_test!(interface_index_signature, + r#" +export interface Interface { + [index: number]: Interface; +} + "#, + details; + "[index: number]: Interface" + ); + + contains_test!(interface_method, + r#" +export interface I { + m(a, b); + mo?(c); +} + "#, + details; + "m(a, b)", + "mo?(c)" + ); + + contains_test!(interface_property, + r#" +export interface I { + p: string; + po?: number; +} + "#, + details; + "p: string", + "po?: number" + ); + + contains_test!(interface_readonly_index_signature, + r#" +export interface Interface { + readonly [index: number]: Interface; +} + "#, + details; + "readonly [index: number]: Interface" + ); + + const JSDOC_SOURCE: &str = r#" +/** + * A is a class + * + * Nothing more + */ +export class A {} +/** + * B is an interface + * + * Should be + */ +export interface B {} +/** + * C is a function + * + * Summarised + */ +export function C() {} + "#; + + contains_test!(jsdoc_details, + JSDOC_SOURCE, + details; + "A is a class", + "B is an interface", + "C is a function", + "Nothing more", + "Should be", + "Summarised" + ); + + contains_test!(jsdoc_summary, + JSDOC_SOURCE; + "A is a class", + "B is an interface", + "C is a function"; + "Nothing more", + "Should be", + "Summarised" + ); + + contains_test!(namespace_declaration, + "export namespace Namespace {}"; + "namespace Namespace" + ); + + const NAMESPACE_SOURCE: &str = r#" +export namespace Namespace { + /** + * Doc comment 1 + * + * Details 1 + */ + export function a() {} + /** + * Doc comment 2 + * + * Details 2 + */ + export class B {} +} + "#; + + contains_test!(namespace_details, + NAMESPACE_SOURCE, + details; + "namespace Namespace", + "function a()", + "class B", + "Doc comment 1", + "Doc comment 2"; + "Details 1", + "Details 2" + ); + + contains_test!(namespace_summary, + NAMESPACE_SOURCE; + "namespace Namespace", + "function a()", + "class B", + "Doc comment 1", + "Doc comment 2"; + "Details 1", + "Details 2" + ); + + contains_test!(type_alias, + "export type A = number"; + "type A = number" + ); + + contains_test!(type_generic_alias, + "export type A<T> = T"; + "type A<T> = T" + ); } |