// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. use super::DocParser; use super::DocPrinter; use crate::colors; use serde_json::json; use super::parser::DocFileLoader; use crate::op_error::OpError; use std::collections::HashMap; use futures::Future; use futures::FutureExt; use std::pin::Pin; pub struct TestLoader { files: HashMap, } impl TestLoader { pub fn new(files_vec: Vec<(String, String)>) -> Box { let mut files = HashMap::new(); for file_tuple in files_vec { files.insert(file_tuple.0, file_tuple.1); } Box::new(Self { files }) } } impl DocFileLoader for TestLoader { fn load_source_code( &self, specifier: &str, ) -> Pin>>> { let res = match self.files.get(specifier) { Some(source_code) => Ok(source_code.to_string()), None => Err(OpError::other("not found".to_string())), }; async move { res }.boxed_local() } } 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 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"; export const fizz = "fizz"; "#; let test_source_code = r#" export { default, foo as fooConst } from "./reexport.ts"; import { fizz as buzz } 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, false) .parse_with_reexports("file:///test.ts") .await .unwrap(); assert_eq!(entries.len(), 3); 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", "location": { "filename": "file:///test.ts", "line": 6, "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 }, }, { "kind": "import", "name": "buzz", "location": { "filename": "file:///test.ts", "line": 3, "col": 0 }, "jsDoc": null, "importDef": { "src": "file:///reexport.ts", "imported": "fizz", } } ]); let actual = serde_json::to_value(&entries).unwrap(); assert_eq!(actual, expected_json); assert!(colors::strip_ansi_codes( DocPrinter::new(&entries, false, false).to_string().as_str() ) .contains("function fooFn(a: number)")); } #[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 {} } 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, false) .parse("test.ts") .await .unwrap(); 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()); 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()); 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()); let found = find_nodes_by_name_recursively(entries.clone(), "Deno.test.a".to_string()); assert_eq!(found.len(), 0); let found = find_nodes_by_name_recursively(entries, "a.b.c".to_string()); assert_eq!(found.len(), 0); } 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": "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" } ] } } ] } } ] } }]); json_test!(export_class, r#" /** Class doc */ export class Foobar extends Fizz implements Buzz, Aldrin { 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 { // } /** Sync bar method */ bar?(): void { // } } "#; [{ "kind": "class", "name": "Foobar", "location": { "filename": "test.ts", "line": 3, "col": 0 }, "jsDoc": "Class doc", "classDef": { "isAbstract": false, "extends": "Fizz", "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", "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": 10, "col": 4 } } ], "properties": [ { "jsDoc": null, "tsType": { "repr": "boolean", "kind": "keyword", "keyword": "boolean" }, "readonly": false, "accessibility": "private", "optional": true, "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", "optional": false, "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", "optional": false, "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, "optional": false, "isAbstract": false, "isStatic": false, "name": "public2", "location": { "filename": "test.ts", "line": 7, "col": 4 } } ], "indexSignatures": [], "methods": [ { "jsDoc": "Async foo method", "accessibility": null, "optional": false, "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" } }, "typeParams": [], "isAsync": true, "isGenerator": false }, "location": { "filename": "test.ts", "line": 13, "col": 4 } }, { "jsDoc": "Sync bar method", "accessibility": null, "optional": true, "isAbstract": false, "isStatic": false, "name": "bar", "kind": "method", "functionDef": { "params": [], "returnType": { "repr": "void", "kind": "keyword", "keyword": "void" }, "isAsync": false, "isGenerator": false, "typeParams": [] }, "location": { "filename": "test.ts", "line": 18, "col": 4 } } ] } }]); json_test!(export_const, r#" /** Something about fizzBuzz */ export const fizzBuzz = "fizzBuzz"; export const env: { /** get doc */ get(key: string): string | undefined; /** 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 } }]); json_test!(export_default_interface, r#" /** * Interface js doc */ export default interface Reader { /** Read n bytes */ read?(buf: Uint8Array, something: unknown): Promise } "#; [{ "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": "" }, { "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": "", "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; } interface Bar { bar(): void; } /** * Interface js doc */ export interface Reader extends Foo, Bar { /** Read n bytes */ read?(buf: Uint8Array, something: unknown): Promise } "#; [{ "kind": "interface", "name": "Reader", "location": { "filename": "test.ts", "line": 11, "col": 0 }, "jsDoc": "Interface js doc", "interfaceDef": { "extends": [ { "repr": "Foo", "kind": "typeRef", "typeRef": { "typeParams": null, "typeName": "Foo" } }, { "repr": "Bar", "kind": "typeRef", "typeRef": { "typeParams": null, "typeName": "Bar" } } ], "methods": [ { "name": "read", "location": { "filename": "test.ts", "line": 13, "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_interface2, r#" export interface TypedIface { something(): T } "#; [{ "kind": "interface", "name": "TypedIface", "location": { "filename": "test.ts", "line": 2, "col": 0 }, "jsDoc": null, "interfaceDef": { "extends": [], "methods": [ { "name": "something", "location": { "filename": "test.ts", "line": 3, "col": 4 }, "jsDoc": null, "optional": false, "params": [], "typeParams": [], "returnType": { "repr": "T", "kind": "typeRef", "typeRef": { "typeParams": null, "typeName": "T" } } } ], "properties": [], "callSignatures": [], "indexSignatures": [], "typeParams": [ { "name": "T" } ], } }]); json_test!(export_type_alias, r#" /** Array holding numbers */ export type NumberArray = Array; "#; [{ "kind": "typeAlias", "name": "NumberArray", "location": { "filename": "test.ts", "line": 3, "col": 0 }, "jsDoc": "Array holding numbers", "typeAliasDef": { "typeParams": [], "tsType": { "repr": "Array", "kind": "typeRef", "typeRef": { "typeParams": [ { "repr": "number", "kind": "keyword", "keyword": "number" } ], "typeName": "Array" } } } }]); json_test!(export_namespace, 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, } } } "#; [{ "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" } ] } } ] } } ] } }]); json_test!(optional_return_type, r#" export function foo(a: number) { return a; } "#; [{ "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 } }] ); json_test!(ts_lit_types, r#" export type boolLit = false; export type strLit = "text"; export type tplLit = `text`; export type numLit = 5; "#; [ { "kind": "typeAlias", "name": "boolLit", "location": { "filename": "test.ts", "line": 2, "col": 0 }, "jsDoc": null, "typeAliasDef": { "tsType": { "repr": "false", "kind": "literal", "literal": { "kind": "boolean", "boolean": false } }, "typeParams": [] } }, { "kind": "typeAlias", "name": "strLit", "location": { "filename": "test.ts", "line": 3, "col": 0 }, "jsDoc": null, "typeAliasDef": { "tsType": { "repr": "text", "kind": "literal", "literal": { "kind": "string", "string": "text" } }, "typeParams": [] } }, { "kind": "typeAlias", "name": "tplLit", "location": { "filename": "test.ts", "line": 4, "col": 0 }, "jsDoc": null, "typeAliasDef": { "tsType": { "repr": "text", "kind": "literal", "literal": { "kind": "string", "string": "text" } }, "typeParams": [] } }, { "kind": "typeAlias", "name": "numLit", "location": { "filename": "test.ts", "line": 5, "col": 0 }, "jsDoc": null, "typeAliasDef": { "tsType": { "repr": "5", "kind": "literal", "literal": { "kind": "number", "number": 5.0 } }, "typeParams": [] } } ]); } 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()" ); contains_test!(class_async_method, r#" export class Class { async amethod(v) {} } "#, details; "async amethod(v)" ); contains_test!(class_constructor, r#" export class Class { constructor(a, b) {} } "#, details; "constructor(a, b)" ); const CLASS_SOURCE: &str = r#" export class C { /** a doc */ a() {} f: number; } "#; contains_test!(class_details, CLASS_SOURCE, details; "class C", "a()", "f: number" ); contains_test!(class_details_all_with_private, r#" export class Class { private pri() {} protected pro() {} public pub() {} } "#, details, private; "private pri()", "protected pro()", "pub()" ); contains_test!(class_details_only_non_private_without_private, r#" export class Class { private pri() {} protected pro() {} public pub() {} } "#, details; "protected pro()", "pub()" ); 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 extends Map implements Iterator, Iterable {}"; "class Class extends Map implements Iterator, Iterable" ); 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(a: T, b: T) { return a + b; }"; "function add(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 {} export function f(): Generic<[string, number]> { return {}; } "#; "Generic<[string, number]>" ); contains_test!(type_literal_declaration, "export type T = {}"; "{ }" ); 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 {}"; "interface Interface" ); contains_test!(interface_generic_extends, "export interface Interface extends Iterable {}"; "interface Interface extends Iterable" ); 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"; "type A = T" ); }