ts-json-schema-generator
Version:
Generate JSON schema from your Typescript sources
122 lines (99 loc) • 3.27 kB
text/typescript
import json5 from "json5";
import type ts from "typescript";
import type { AnnotationsReader } from "../AnnotationsReader.js";
import type { Annotations } from "../Type/AnnotatedType.js";
import { symbolAtNode } from "../Utils/symbolAtNode.js";
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",
"required",
// 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 {
return undefined;
}
}
}