@xlr-lib/xlr-utils 0.2.0--canary.2.120 → 1.0.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.
@@ -1,180 +0,0 @@
1
- import { test, expect, describe } from "vitest";
2
- import * as ts from "typescript";
3
- import { NodeType } from "@xlr-lib/xlr";
4
-
5
- import {
6
- tsStripOptionalType,
7
- isExportedDeclaration,
8
- applyPickOrOmitToNodeType,
9
- getStringLiteralsFromUnion,
10
- applyPartialOrRequiredToNodeType,
11
- } from "../ts-helpers";
12
-
13
- test("tsStripOptionalType", () => {
14
- const input: ts.TypeNode = ts.factory.createKeywordTypeNode(
15
- ts.SyntaxKind.StringKeyword,
16
- );
17
- const expected: ts.TypeNode = ts.factory.createKeywordTypeNode(
18
- ts.SyntaxKind.StringKeyword,
19
- );
20
- const actual = tsStripOptionalType(input);
21
- expect(actual).toEqual(expected);
22
- });
23
-
24
- describe("isExportedDeclaration", () => {
25
- test("should return false for a non exported Statement", () => {
26
- const source = ts.createSourceFile(
27
- "test.ts",
28
- `
29
- interface Test {
30
- prop?: string;
31
- }
32
- `,
33
- ts.ScriptTarget.Latest,
34
- true,
35
- );
36
- const node = source.statements[0] as ts.Statement;
37
-
38
- const result = isExportedDeclaration(node);
39
- expect(result).toBe(false);
40
- });
41
-
42
- test("should return true for an exported Statement", () => {
43
- const source = ts.createSourceFile(
44
- "test.ts",
45
- `
46
- export interface Test {
47
- prop?: string;
48
- }
49
- `,
50
- ts.ScriptTarget.Latest,
51
- true,
52
- );
53
- const node = source.statements[0] as ts.Statement;
54
-
55
- const result = isExportedDeclaration(node);
56
- expect(result).toBe(true);
57
- });
58
- });
59
-
60
- test("getStringLiteralsFromUnion", () => {
61
- const input: ts.Node = ts.factory.createUnionTypeNode([
62
- ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("foo")),
63
- ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("bar")),
64
- ]);
65
- const expected: Set<string> = new Set(["foo", "bar"]);
66
- const actual = getStringLiteralsFromUnion(input);
67
- expect(actual).toEqual(expected);
68
- });
69
-
70
- describe("applyPickOrOmitToNodeType", () => {
71
- test("Omit - Doesn't filter property that doesn't exist", () => {
72
- const baseObject: NodeType = {
73
- type: "object",
74
- properties: { foo: { node: { type: "string" }, required: false } },
75
- additionalProperties: false,
76
- };
77
- const operation = "Omit";
78
- const properties = new Set(["bar"]);
79
-
80
- const result = applyPickOrOmitToNodeType(baseObject, operation, properties);
81
-
82
- expect(result).toStrictEqual(baseObject);
83
- });
84
-
85
- test("Omit - Filters property that do exist", () => {
86
- const baseObject: NodeType = {
87
- type: "object",
88
- properties: {
89
- foo: { node: { type: "string" }, required: false },
90
- bar: { node: { type: "string" }, required: false },
91
- },
92
- additionalProperties: false,
93
- };
94
-
95
- const filteredObject: NodeType = {
96
- type: "object",
97
- properties: { foo: { node: { type: "string" }, required: false } },
98
- additionalProperties: false,
99
- };
100
- const operation = "Omit";
101
- const properties = new Set(["bar"]);
102
-
103
- const result = applyPickOrOmitToNodeType(baseObject, operation, properties);
104
-
105
- expect(result).toStrictEqual(filteredObject);
106
- });
107
-
108
- test("Pick - Selects property that do exist", () => {
109
- const baseObject: NodeType = {
110
- type: "object",
111
- properties: {
112
- foo: { node: { type: "string" }, required: false },
113
- bar: { node: { type: "string" }, required: false },
114
- },
115
- additionalProperties: false,
116
- };
117
-
118
- const filteredObject: NodeType = {
119
- type: "object",
120
- properties: { bar: { node: { type: "string" }, required: false } },
121
- additionalProperties: false,
122
- };
123
- const operation = "Pick";
124
- const properties = new Set(["bar"]);
125
-
126
- const result = applyPickOrOmitToNodeType(baseObject, operation, properties);
127
-
128
- expect(result).toStrictEqual(filteredObject);
129
- });
130
- });
131
-
132
- describe("applyPartialOrRequiredToNodeType", () => {
133
- test("Partial - Makes required properties optional", () => {
134
- const baseObject: NodeType = {
135
- type: "object",
136
- properties: {
137
- foo: { node: { type: "string" }, required: false },
138
- bar: { node: { type: "string" }, required: true },
139
- },
140
- additionalProperties: false,
141
- };
142
-
143
- const modifiedObject: NodeType = {
144
- type: "object",
145
- properties: {
146
- foo: { node: { type: "string" }, required: false },
147
- bar: { node: { type: "string" }, required: false },
148
- },
149
- additionalProperties: false,
150
- };
151
-
152
- const result = applyPartialOrRequiredToNodeType(baseObject, false);
153
-
154
- expect(result).toStrictEqual(modifiedObject);
155
- });
156
-
157
- test("Required - Makes optional properties required", () => {
158
- const baseObject: NodeType = {
159
- type: "object",
160
- properties: {
161
- foo: { node: { type: "string" }, required: false },
162
- bar: { node: { type: "string" }, required: true },
163
- },
164
- additionalProperties: false,
165
- };
166
-
167
- const modifiedObject: NodeType = {
168
- type: "object",
169
- properties: {
170
- foo: { node: { type: "string" }, required: true },
171
- bar: { node: { type: "string" }, required: true },
172
- },
173
- additionalProperties: false,
174
- };
175
-
176
- const result = applyPartialOrRequiredToNodeType(baseObject, true);
177
-
178
- expect(result).toStrictEqual(modifiedObject);
179
- });
180
- });
@@ -1,39 +0,0 @@
1
- import * as ts from "typescript";
2
- import { isOptionalProperty } from "../type-checks";
3
- import { describe, test, expect } from "vitest";
4
-
5
- describe("isOptionalProperty", () => {
6
- test("should return true for an optional property", () => {
7
- const source = ts.createSourceFile(
8
- "test.ts",
9
- `
10
- interface Test {
11
- prop?: string;
12
- }
13
- `,
14
- ts.ScriptTarget.Latest,
15
- true,
16
- );
17
- const node = source.statements[0] as ts.InterfaceDeclaration;
18
- const prop = node.members[0] as ts.PropertySignature;
19
-
20
- expect(isOptionalProperty(prop)).toBe(true);
21
- });
22
-
23
- test("should return false for a non-optional property", () => {
24
- const source = ts.createSourceFile(
25
- "test.ts",
26
- `
27
- interface Test {
28
- prop: string;
29
- }
30
- `,
31
- ts.ScriptTarget.Latest,
32
- true,
33
- );
34
- const node = source.statements[0] as ts.InterfaceDeclaration;
35
- const prop = node.members[0] as ts.PropertySignature;
36
-
37
- expect(isOptionalProperty(prop)).toBe(false);
38
- });
39
- });
@@ -1,237 +0,0 @@
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
- }
@@ -1,243 +0,0 @@
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
- }
@@ -1,7 +0,0 @@
1
- import ts from "typescript";
2
- import type { Annotations } from "@xlr-lib/xlr";
3
- /**
4
- * Converts JSDoc comments to strings
5
- */
6
- export declare function decorateNode(node: ts.Node): Annotations;
7
- //# sourceMappingURL=annotations.d.ts.map