/
BasicAnnotationsReader.ts
119 lines (98 loc) · 3.15 KB
/
BasicAnnotationsReader.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
110
111
112
113
114
115
116
117
118
119
import json5 from "json5";
import ts from "typescript";
import { AnnotationsReader } from "../AnnotationsReader";
import { Annotations } from "../Type/AnnotatedType";
import { symbolAtNode } from "../Utils/symbolAtNode";
export class BasicAnnotationsReader implements AnnotationsReader {
private static requiresDollar = new Set<string>(["id", "comment", "ref"]);
private static textTags = new Set<string>([
"title",
"description",
"id",
"format",
"pattern",
"ref",
// New since draft-07:
"comment",
"contentMediaType",
"contentEncoding",
// Custom tag for if-then-else support.
"discriminator",
]);
private static jsonTags = new Set<string>([
"minimum",
"exclusiveMinimum",
"maximum",
"exclusiveMaximum",
"multipleOf",
"minLength",
"maxLength",
"minProperties",
"maxProperties",
"minItems",
"maxItems",
"uniqueItems",
"propertyNames",
"contains",
"const",
"examples",
"default",
// New since draft-07:
"if",
"then",
"else",
"readOnly",
"writeOnly",
// New since draft 2019-09:
"deprecated",
]);
public constructor(private extraTags?: Set<string>) {}
public getAnnotations(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 annotations = jsDocTags.reduce((result: Annotations, jsDocTag) => {
const value = this.parseJsDocTag(jsDocTag);
if (value !== undefined) {
if (BasicAnnotationsReader.requiresDollar.has(jsDocTag.name)) {
result["$" + jsDocTag.name] = value;
} else {
result[jsDocTag.name] = value;
}
}
return result;
}, {});
return Object.keys(annotations).length ? annotations : undefined;
}
private parseJsDocTag(jsDocTag: ts.JSDocTagInfo): any {
const isTextTag = BasicAnnotationsReader.textTags.has(jsDocTag.name);
// Non-text tags without explicit value (e.g. `@deprecated`) default to `true`.
const defaultText = isTextTag ? "" : "true";
const text = jsDocTag.text?.map((part) => part.text).join("") || defaultText;
if (isTextTag) {
return text;
}
let parsed = this.parseJson(text);
parsed = parsed === undefined ? text : parsed;
if (BasicAnnotationsReader.jsonTags.has(jsDocTag.name)) {
return parsed;
} else if (this.extraTags?.has(jsDocTag.name)) {
return parsed;
} else {
// Unknown jsDoc tag.
return undefined;
}
}
private parseJson(value: string): any {
try {
return json5.parse(value);
} catch (e) {
return undefined;
}
}
}