forked from vega/ts-json-schema-generator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ExtendedAnnotationsReader.ts
109 lines (94 loc) · 3.62 KB
/
ExtendedAnnotationsReader.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import json5 from "json5";
import ts from "typescript";
import { Annotations } from "../Type/AnnotatedType";
import { symbolAtNode } from "../Utils/symbolAtNode";
import { BasicAnnotationsReader } from "./BasicAnnotationsReader";
export class ExtendedAnnotationsReader extends BasicAnnotationsReader {
public constructor(private typeChecker: ts.TypeChecker, extraTags?: Set<string>) {
super(extraTags);
}
public getAnnotations(node: ts.Node): Annotations | undefined {
const annotations: Annotations = {
...this.getDescriptionAnnotation(node),
...this.getTypeAnnotation(node),
...this.getExampleAnnotation(node),
...super.getAnnotations(node),
};
return Object.keys(annotations).length ? annotations : undefined;
}
public isNullable(node: ts.Node): boolean {
const symbol = symbolAtNode(node);
if (!symbol) {
return false;
}
const jsDocTags: ts.JSDocTagInfo[] = symbol.getJsDocTags();
if (!jsDocTags || !jsDocTags.length) {
return false;
}
const jsDocTag: ts.JSDocTagInfo | undefined = jsDocTags.find((tag: ts.JSDocTagInfo) => tag.name === "nullable");
return !!jsDocTag;
}
private getDescriptionAnnotation(node: ts.Node): Annotations | undefined {
const symbol = symbolAtNode(node);
if (!symbol) {
return undefined;
}
const comments: ts.SymbolDisplayPart[] = symbol.getDocumentationComment(this.typeChecker);
if (!comments || !comments.length) {
return undefined;
}
return {
description: comments
.map((comment) => comment.text.replace(/\r/g, "").replace(/(?<=[^\n])\n(?=[^\n*-])/g, " "))
.join(" ")
// strip newlines
.replace(/^\s+|\s+$/g, ""),
};
}
private getTypeAnnotation(node: ts.Node): Annotations | undefined {
const symbol = symbolAtNode(node);
if (!symbol) {
return undefined;
}
const jsDocTags: ts.JSDocTagInfo[] = symbol.getJsDocTags();
if (!jsDocTags || !jsDocTags.length) {
return undefined;
}
const jsDocTag = jsDocTags.find((tag) => tag.name === "asType");
if (!jsDocTag) {
return undefined;
}
const text = (jsDocTag.text ?? []).map((part) => part.text).join("");
return { type: text };
}
/**
* Attempts to gather examples from the @-example jsdoc tag.
* See https://tsdoc.org/pages/tags/example/
*/
private getExampleAnnotation(node: ts.Node): Annotations | undefined {
const symbol = symbolAtNode(node);
if (!symbol) {
return undefined;
}
const jsDocTags: ts.JSDocTagInfo[] = symbol.getJsDocTags();
if (!jsDocTags || !jsDocTags.length) {
return undefined;
}
const examples: unknown[] = [];
for (const example of jsDocTags.filter((tag) => tag.name === "example")) {
console.log(JSON.stringify(example.text));
const text = (example.text ?? []).map((part) => part.text).join("");
try {
examples.push(json5.parse(text));
} catch (e) {
// ignore examples which don't parse to valid JSON
// This could be improved to support a broader range of usages,
// such as if the example has a title (as explained in the tsdoc spec).
}
}
if (examples.length === 0) {
return undefined;
}
return { examples };
}
}