1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-24 16:19:12 -05:00
denoland-deno/cli/doc/tests.rs
Valentin Anger 3374c73fba
feat(doc): Improve terminal printer (#6594)
- Add more support for generics
- Add the --private flag - displays documentation for
  not exported and private nodes
- Display more attributes like abstract, static and readonly
- Display type aliases
- Refactor module to use the Display trait
- Use a bit more color
2020-07-12 14:16:33 +02:00

2073 lines
45 KiB
Rust

// 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<String, String>,
}
impl TestLoader {
pub fn new(files_vec: Vec<(String, String)>) -> Box<Self> {
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<Box<dyn Future<Output = Result<String, OpError>>>> {
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";
"#;
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, 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"
}
},
{
"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(
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<void> {
//
}
/** 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<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;
}
interface Bar {
bar(): void;
}
/**
* Interface js doc
*/
export interface Reader extends Foo, Bar {
/** Read n bytes */
read?(buf: Uint8Array, something: unknown): Promise<number>
}
"#;
[{
"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<T> {
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<number>;
"#;
[{
"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<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]>"
);
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<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"
);
}