@xlr-lib/xlr-utils 0.1.1-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +888 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/index.legacy-esm.js +821 -0
- package/dist/index.mjs +821 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +33 -0
- package/src/__tests__/__snapshots__/annotations.test.ts.snap +22 -0
- package/src/__tests__/__snapshots__/validation-helpers.test.ts.snap +53 -0
- package/src/__tests__/annotations.test.ts +40 -0
- package/src/__tests__/documentation.test.ts +116 -0
- package/src/__tests__/ts-helpers.test.ts +180 -0
- package/src/__tests__/type-check.test.ts +39 -0
- package/src/__tests__/validation-helpers.test.ts +141 -0
- package/src/annotations.ts +237 -0
- package/src/documentation.ts +243 -0
- package/src/index.ts +5 -0
- package/src/ts-helpers.ts +410 -0
- package/src/type-checks.ts +121 -0
- package/src/validation-helpers.ts +252 -0
- package/types/annotations.d.ts +7 -0
- package/types/documentation.d.ts +14 -0
- package/types/index.d.ts +6 -0
- package/types/ts-helpers.d.ts +41 -0
- package/types/type-checks.d.ts +45 -0
- package/types/validation-helpers.d.ts +41 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { test, expect, describe } from "vitest";
|
|
2
|
+
import type { ObjectType } from "@xlr-lib/xlr";
|
|
3
|
+
import { computeEffectiveObject, makePropertyMap } from "../validation-helpers";
|
|
4
|
+
import { parseTree } from "jsonc-parser";
|
|
5
|
+
|
|
6
|
+
describe("computeEffectiveObject tests", () => {
|
|
7
|
+
test("mixed test", () => {
|
|
8
|
+
const type1: ObjectType = {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
foo: {
|
|
12
|
+
required: true,
|
|
13
|
+
node: {
|
|
14
|
+
type: "string",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
additionalProperties: false,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const type2: ObjectType = {
|
|
22
|
+
type: "object",
|
|
23
|
+
properties: {
|
|
24
|
+
bar: {
|
|
25
|
+
required: true,
|
|
26
|
+
node: {
|
|
27
|
+
type: "number",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
additionalProperties: {
|
|
32
|
+
type: "unknown",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
expect(computeEffectiveObject(type1, type2)).toMatchSnapshot();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("Error on property overlap", () => {
|
|
40
|
+
const type1: ObjectType = {
|
|
41
|
+
type: "object",
|
|
42
|
+
properties: {
|
|
43
|
+
foo: {
|
|
44
|
+
required: true,
|
|
45
|
+
node: {
|
|
46
|
+
type: "string",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
additionalProperties: false,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const type2: ObjectType = {
|
|
54
|
+
type: "object",
|
|
55
|
+
properties: {
|
|
56
|
+
foo: {
|
|
57
|
+
required: true,
|
|
58
|
+
node: {
|
|
59
|
+
type: "number",
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
additionalProperties: {
|
|
64
|
+
type: "unknown",
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
expect(() =>
|
|
69
|
+
computeEffectiveObject(type1, type2, true),
|
|
70
|
+
).toThrowErrorMatchingInlineSnapshot(
|
|
71
|
+
`[Error: Can't compute effective type for object literal and object literal because of conflicting properties foo]`,
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("Merges equal additionalProperties", () => {
|
|
76
|
+
const type1: ObjectType = {
|
|
77
|
+
type: "object",
|
|
78
|
+
properties: {
|
|
79
|
+
foo: {
|
|
80
|
+
required: true,
|
|
81
|
+
node: {
|
|
82
|
+
type: "string",
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
additionalProperties: {
|
|
87
|
+
type: "unknown",
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const type2: ObjectType = {
|
|
92
|
+
type: "object",
|
|
93
|
+
properties: {
|
|
94
|
+
bar: {
|
|
95
|
+
required: true,
|
|
96
|
+
node: {
|
|
97
|
+
type: "number",
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
additionalProperties: {
|
|
102
|
+
type: "unknown",
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
expect(computeEffectiveObject(type1, type2)).toMatchSnapshot();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("makePropertyMap tests", () => {
|
|
111
|
+
test("basic test", () => {
|
|
112
|
+
const jsonObject = {
|
|
113
|
+
key1: "value",
|
|
114
|
+
key2: true,
|
|
115
|
+
key3: 1,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const treeObject = parseTree(JSON.stringify(jsonObject));
|
|
119
|
+
|
|
120
|
+
const propertyMap = makePropertyMap(treeObject);
|
|
121
|
+
|
|
122
|
+
expect(propertyMap.get("key1")?.value).toStrictEqual("value");
|
|
123
|
+
expect(propertyMap.get("key2")?.value).toStrictEqual(true);
|
|
124
|
+
expect(propertyMap.get("key3")?.value).toStrictEqual(1);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("escaped key", () => {
|
|
128
|
+
const jsonObject = {
|
|
129
|
+
"some-key": "value",
|
|
130
|
+
// eslint-disable-next-line prettier/prettier
|
|
131
|
+
'some-otherkey': "value",
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const treeObject = parseTree(JSON.stringify(jsonObject));
|
|
135
|
+
|
|
136
|
+
const propertyMap = makePropertyMap(treeObject);
|
|
137
|
+
|
|
138
|
+
expect(propertyMap.get("'some-key'")?.value).toStrictEqual("value");
|
|
139
|
+
expect(propertyMap.get("'some-otherkey'")?.value).toStrictEqual("value");
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { Annotations } from "@xlr-lib/xlr";
|
|
3
|
+
|
|
4
|
+
interface JSDocContainer {
|
|
5
|
+
/** */
|
|
6
|
+
jsDoc: Array<ts.JSDoc>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
function extractDescription(text: string | undefined): Annotations {
|
|
13
|
+
if (!text) {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return { description: text };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Checks if the parent node is a non-object type
|
|
22
|
+
*/
|
|
23
|
+
function parentIsNonObjectPath(node: ts.Node) {
|
|
24
|
+
return (
|
|
25
|
+
node.parent &&
|
|
26
|
+
(ts.isArrayTypeNode(node.parent) ||
|
|
27
|
+
ts.isTupleTypeNode(node.parent) ||
|
|
28
|
+
ts.isOptionalTypeNode(node.parent) ||
|
|
29
|
+
ts.isRestTypeNode(node.parent) ||
|
|
30
|
+
ts.isUnionTypeNode(node.parent))
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Traverses up the node tree to build the title path down to the initial node
|
|
36
|
+
*/
|
|
37
|
+
function recurseTypeChain(
|
|
38
|
+
node: ts.Node,
|
|
39
|
+
child: ts.Node | undefined,
|
|
40
|
+
): Array<string> {
|
|
41
|
+
if (!node) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (
|
|
46
|
+
ts.isArrayTypeNode(node) &&
|
|
47
|
+
node.parent &&
|
|
48
|
+
ts.isRestTypeNode(node.parent)
|
|
49
|
+
) {
|
|
50
|
+
return recurseTypeChain(node.parent, node);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (ts.isRestTypeNode(node)) {
|
|
54
|
+
return recurseTypeChain(node.parent, node);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (ts.isOptionalTypeNode(node)) {
|
|
58
|
+
return recurseTypeChain(node.parent, node);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (ts.isUnionTypeNode(node)) {
|
|
62
|
+
return recurseTypeChain(node.parent, node);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (ts.isParenthesizedTypeNode(node)) {
|
|
66
|
+
return recurseTypeChain(node.parent, node);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (ts.isTypeLiteralNode(node)) {
|
|
70
|
+
return recurseTypeChain(node.parent, node);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (ts.isArrayTypeNode(node)) {
|
|
74
|
+
return ["[]", ...recurseTypeChain(node.parent, node)];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (ts.isTupleTypeNode(node)) {
|
|
78
|
+
const pos = node.elements.indexOf(child as any);
|
|
79
|
+
return [
|
|
80
|
+
...(pos === -1 ? [] : [`${pos}`]),
|
|
81
|
+
...recurseTypeChain(node.parent, node),
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
ts.isTypeAliasDeclaration(node) ||
|
|
87
|
+
ts.isInterfaceDeclaration(node) ||
|
|
88
|
+
ts.isPropertySignature(node)
|
|
89
|
+
) {
|
|
90
|
+
return [node.name.getText(), ...recurseTypeChain(node.parent, node)];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (parentIsNonObjectPath(node)) {
|
|
94
|
+
return recurseTypeChain(node.parent, node);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Builds the `Title` property by traversing up and noting the named types in the tree
|
|
102
|
+
*/
|
|
103
|
+
function extractTitle(node: ts.Node): Annotations {
|
|
104
|
+
const typeNames = recurseTypeChain(node, undefined).reverse().join(".");
|
|
105
|
+
|
|
106
|
+
if (!typeNames.length) {
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { title: typeNames };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
*
|
|
115
|
+
*/
|
|
116
|
+
function stringifyDoc(
|
|
117
|
+
docString: undefined | string | ts.NodeArray<ts.JSDocComment>,
|
|
118
|
+
): string | undefined {
|
|
119
|
+
if (typeof docString === "undefined" || typeof docString === "string") {
|
|
120
|
+
return docString;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return docString.map(({ text }) => text).join(" ");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Extracts JSDoc tags to strings
|
|
128
|
+
*/
|
|
129
|
+
function extractTags(tags: ReadonlyArray<ts.JSDocTag>): Annotations {
|
|
130
|
+
const descriptions: Array<string> = [];
|
|
131
|
+
const examples: Array<string> = [];
|
|
132
|
+
const _default: Array<string> = [];
|
|
133
|
+
const see: Array<string> = [];
|
|
134
|
+
const meta: Record<string, string> = {};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
*
|
|
138
|
+
*/
|
|
139
|
+
const extractSee = (tag: ts.JSDocSeeTag) => {
|
|
140
|
+
return `${tag.tagName ? `${tag.tagName?.getText()} ` : ""}${
|
|
141
|
+
stringifyDoc(tag.comment)?.trim() ?? ""
|
|
142
|
+
}`;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
tags.forEach((tag) => {
|
|
146
|
+
if (!tag.comment) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (tag.tagName.text === "example") {
|
|
151
|
+
examples.push(stringifyDoc(tag.comment)?.trim() ?? "");
|
|
152
|
+
} else if (tag.tagName.text === "default") {
|
|
153
|
+
_default.push(stringifyDoc(tag.comment)?.trim() ?? "");
|
|
154
|
+
} else if (tag.tagName.text === "see") {
|
|
155
|
+
see.push(extractSee(tag as ts.JSDocSeeTag));
|
|
156
|
+
} else if (tag.tagName.text === "meta") {
|
|
157
|
+
const [key, value] = tag.comment.toString().split(/:(.*)/);
|
|
158
|
+
meta[key] = value?.trim() ?? "";
|
|
159
|
+
} else {
|
|
160
|
+
const text = stringifyDoc(tag.comment)?.trim() ?? "";
|
|
161
|
+
descriptions.push(`@${tag.tagName.text} ${text}`);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
...(descriptions.length === 0
|
|
167
|
+
? {}
|
|
168
|
+
: { description: descriptions.join("\n") }),
|
|
169
|
+
...(examples.length === 0 ? {} : { examples }),
|
|
170
|
+
...(_default.length === 0 ? {} : { default: _default.join("\n") }),
|
|
171
|
+
...(see.length === 0 ? {} : { see }),
|
|
172
|
+
...(meta && Object.keys(meta).length === 0 ? {} : { meta }),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Joins Arrays of maybe strings with a given separator
|
|
178
|
+
*/
|
|
179
|
+
function join(t: Array<string | undefined>, separator = "\n") {
|
|
180
|
+
const unique = new Set(t).values();
|
|
181
|
+
return Array.from(unique)
|
|
182
|
+
.filter((s) => s !== undefined)
|
|
183
|
+
.join(separator)
|
|
184
|
+
.trim();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Merges Annotation nodes for various nodes
|
|
189
|
+
*/
|
|
190
|
+
function mergeAnnotations(nodes: Array<Annotations>): Annotations {
|
|
191
|
+
const name = nodes.find((n) => n.name)?.name;
|
|
192
|
+
const title = join(
|
|
193
|
+
nodes.map((n) => n.title),
|
|
194
|
+
", ",
|
|
195
|
+
);
|
|
196
|
+
const description = join(nodes.map((n) => n.description));
|
|
197
|
+
const _default = join(nodes.map((n) => n.default));
|
|
198
|
+
const comment = join(nodes.map((n) => n.comment));
|
|
199
|
+
const examples = join(
|
|
200
|
+
nodes.map((n) =>
|
|
201
|
+
Array.isArray(n.examples) ? join(n.examples) : n.examples,
|
|
202
|
+
),
|
|
203
|
+
);
|
|
204
|
+
const see = join(
|
|
205
|
+
nodes.map((n) => (Array.isArray(n.see) ? join(n.see) : n.see)),
|
|
206
|
+
);
|
|
207
|
+
const meta = nodes.find((n) => n.meta)?.meta;
|
|
208
|
+
return {
|
|
209
|
+
...(name ? { name } : {}),
|
|
210
|
+
...(title ? { title } : {}),
|
|
211
|
+
...(description ? { description } : {}),
|
|
212
|
+
...(examples ? { examples } : {}),
|
|
213
|
+
...(_default ? { default: _default } : {}),
|
|
214
|
+
...(see ? { see } : {}),
|
|
215
|
+
...(comment ? { comment } : {}),
|
|
216
|
+
...(meta ? { meta } : {}),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Converts JSDoc comments to strings
|
|
222
|
+
*/
|
|
223
|
+
export function decorateNode(node: ts.Node): Annotations {
|
|
224
|
+
const { jsDoc } = node as unknown as JSDocContainer;
|
|
225
|
+
const titleAnnotation = extractTitle(node);
|
|
226
|
+
|
|
227
|
+
if (jsDoc && jsDoc.length) {
|
|
228
|
+
const first = jsDoc[0];
|
|
229
|
+
return mergeAnnotations([
|
|
230
|
+
extractDescription(stringifyDoc(first.comment)),
|
|
231
|
+
titleAnnotation,
|
|
232
|
+
extractTags(first.tags ?? []),
|
|
233
|
+
]);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return titleAnnotation;
|
|
237
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { SymbolDisplayPart } from "typescript";
|
|
3
|
+
import type { NodeType } from "@xlr-lib/xlr";
|
|
4
|
+
import { isPrimitiveTypeNode } from "./type-checks";
|
|
5
|
+
|
|
6
|
+
const { SymbolDisplayPartKind, displayPartsToString } = ts;
|
|
7
|
+
|
|
8
|
+
/** Like `.join()` but for arrays */
|
|
9
|
+
function insertBetweenElements<T>(array: Array<T>, separator: T): T[] {
|
|
10
|
+
return array.reduce((acc, item, index) => {
|
|
11
|
+
if (index === 0) {
|
|
12
|
+
return [item];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return [...acc, separator, item];
|
|
16
|
+
}, [] as T[]);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate a documentation string for a given node
|
|
21
|
+
*
|
|
22
|
+
* @param node - The source node to author the docs string for
|
|
23
|
+
* @returns - documentation string
|
|
24
|
+
*/
|
|
25
|
+
export function createTSDocString(node: NodeType): Array<SymbolDisplayPart> {
|
|
26
|
+
if (node.type === "ref") {
|
|
27
|
+
return [
|
|
28
|
+
{
|
|
29
|
+
text: node.ref,
|
|
30
|
+
kind: SymbolDisplayPartKind.keyword as any,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (node.type === "or" || node.type === "and") {
|
|
36
|
+
const items = node.type === "and" ? node.and : node.or;
|
|
37
|
+
|
|
38
|
+
return insertBetweenElements(
|
|
39
|
+
items.map((subnode) => createTSDocString(subnode)),
|
|
40
|
+
[
|
|
41
|
+
{
|
|
42
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
43
|
+
text: node.type === "and" ? " & " : " | ",
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
).flat();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (node.type === "function") {
|
|
50
|
+
return [
|
|
51
|
+
{
|
|
52
|
+
kind: SymbolDisplayPartKind.keyword as any,
|
|
53
|
+
text: "function",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
kind: SymbolDisplayPartKind.space as any,
|
|
57
|
+
text: " ",
|
|
58
|
+
},
|
|
59
|
+
...(node.name
|
|
60
|
+
? [{ text: node.name, kind: SymbolDisplayPartKind.methodName }]
|
|
61
|
+
: []),
|
|
62
|
+
{
|
|
63
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
64
|
+
text: "(",
|
|
65
|
+
},
|
|
66
|
+
...insertBetweenElements(
|
|
67
|
+
node.parameters.map((p) => {
|
|
68
|
+
if (p.name) {
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
kind: SymbolDisplayPartKind.parameterName as any,
|
|
72
|
+
text: p.name,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
76
|
+
text: p.optional ? "?" : "",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
80
|
+
text: ": ",
|
|
81
|
+
},
|
|
82
|
+
...createTSDocString(p.type),
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return createTSDocString(p.type);
|
|
87
|
+
}),
|
|
88
|
+
[
|
|
89
|
+
{
|
|
90
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
91
|
+
text: ", ",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
).flat(),
|
|
95
|
+
{
|
|
96
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
97
|
+
text: ")",
|
|
98
|
+
},
|
|
99
|
+
...(node.returnType
|
|
100
|
+
? [
|
|
101
|
+
{
|
|
102
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
103
|
+
text: ": ",
|
|
104
|
+
},
|
|
105
|
+
...createTSDocString(node.returnType),
|
|
106
|
+
]
|
|
107
|
+
: []),
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (node.type === "tuple") {
|
|
112
|
+
return [
|
|
113
|
+
{
|
|
114
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
115
|
+
text: "[",
|
|
116
|
+
},
|
|
117
|
+
...insertBetweenElements(
|
|
118
|
+
node.elementTypes.map((t) => {
|
|
119
|
+
if (t.name) {
|
|
120
|
+
return [
|
|
121
|
+
{
|
|
122
|
+
kind: SymbolDisplayPartKind.propertyName as any,
|
|
123
|
+
text: t.name,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
127
|
+
text: ": ",
|
|
128
|
+
},
|
|
129
|
+
...createTSDocString(t.type),
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return createTSDocString(t.type);
|
|
134
|
+
}),
|
|
135
|
+
[
|
|
136
|
+
{
|
|
137
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
138
|
+
text: ", ",
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
).flat(),
|
|
142
|
+
{
|
|
143
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
144
|
+
text: "]",
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (node.type === "array") {
|
|
150
|
+
return [
|
|
151
|
+
{
|
|
152
|
+
kind: SymbolDisplayPartKind.interfaceName as any,
|
|
153
|
+
text: "Array",
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
157
|
+
text: "<",
|
|
158
|
+
},
|
|
159
|
+
...createTSDocString(node.elementType),
|
|
160
|
+
{
|
|
161
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
162
|
+
text: ">",
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (node.type === "record") {
|
|
168
|
+
return [
|
|
169
|
+
{
|
|
170
|
+
kind: SymbolDisplayPartKind.interfaceName as any,
|
|
171
|
+
text: "Record",
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
175
|
+
text: "<",
|
|
176
|
+
},
|
|
177
|
+
...createTSDocString(node.keyType),
|
|
178
|
+
{
|
|
179
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
180
|
+
text: ", ",
|
|
181
|
+
},
|
|
182
|
+
...createTSDocString(node.valueType),
|
|
183
|
+
{
|
|
184
|
+
kind: SymbolDisplayPartKind.punctuation as any,
|
|
185
|
+
text: ">",
|
|
186
|
+
},
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (
|
|
191
|
+
(node.type === "string" ||
|
|
192
|
+
node.type === "boolean" ||
|
|
193
|
+
node.type === "number") &&
|
|
194
|
+
node.const !== undefined
|
|
195
|
+
) {
|
|
196
|
+
return [
|
|
197
|
+
{
|
|
198
|
+
kind: SymbolDisplayPartKind.keyword as any,
|
|
199
|
+
text:
|
|
200
|
+
typeof node.const === "string"
|
|
201
|
+
? `"${node.const}"`
|
|
202
|
+
: String(node.const),
|
|
203
|
+
},
|
|
204
|
+
];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (isPrimitiveTypeNode(node) && node.type !== "null") {
|
|
208
|
+
return [
|
|
209
|
+
{
|
|
210
|
+
kind: SymbolDisplayPartKind.keyword as any,
|
|
211
|
+
text: node.type,
|
|
212
|
+
},
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (node.type === "object" && node.name) {
|
|
217
|
+
return [
|
|
218
|
+
{
|
|
219
|
+
kind: SymbolDisplayPartKind.interfaceName as any,
|
|
220
|
+
text: node.name,
|
|
221
|
+
},
|
|
222
|
+
];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return [
|
|
226
|
+
{
|
|
227
|
+
kind: SymbolDisplayPartKind.localName as any,
|
|
228
|
+
text: node.type,
|
|
229
|
+
},
|
|
230
|
+
];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Convert the TS SymbolDisplayParts into a single string */
|
|
234
|
+
export function symbolDisplayToString(
|
|
235
|
+
displayParts: Array<SymbolDisplayPart>,
|
|
236
|
+
): string {
|
|
237
|
+
return displayPartsToString(displayParts);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Create a documentation string from node */
|
|
241
|
+
export function createDocString(node: NodeType): string {
|
|
242
|
+
return symbolDisplayToString(createTSDocString(node));
|
|
243
|
+
}
|