@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,252 @@
|
|
|
1
|
+
import type { Node } from "jsonc-parser";
|
|
2
|
+
import type {
|
|
3
|
+
ConditionalType,
|
|
4
|
+
NodeType,
|
|
5
|
+
ObjectType,
|
|
6
|
+
RefNode,
|
|
7
|
+
} from "@xlr-lib/xlr";
|
|
8
|
+
import { isGenericNodeType, isPrimitiveTypeNode } from "./type-checks";
|
|
9
|
+
import { fillInGenerics } from "./ts-helpers";
|
|
10
|
+
|
|
11
|
+
export interface PropertyNode {
|
|
12
|
+
/** Equivalent Property Name */
|
|
13
|
+
key: string;
|
|
14
|
+
|
|
15
|
+
/** Equivalent Property Value */
|
|
16
|
+
value: Node;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Takes a property node and returns the underlying Key/Value Pairs
|
|
21
|
+
*/
|
|
22
|
+
export function propertyToTuple(node: Node): PropertyNode {
|
|
23
|
+
let key = node.children?.[0].value as string;
|
|
24
|
+
if (key.includes("-")) {
|
|
25
|
+
// XLR outputs all escaped properties with single quotes
|
|
26
|
+
key = `'${key}'`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
key,
|
|
31
|
+
value: node.children?.[1] as Node,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Turns a node's children into a map for easy access
|
|
37
|
+
*/
|
|
38
|
+
export function makePropertyMap(node: Node): Map<string, Node> {
|
|
39
|
+
const m = new Map();
|
|
40
|
+
node.children?.forEach((child) => {
|
|
41
|
+
const property = propertyToTuple(child);
|
|
42
|
+
m.set(property.key, property.value);
|
|
43
|
+
});
|
|
44
|
+
return m;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Checks if property is a leaf node or another node
|
|
49
|
+
*/
|
|
50
|
+
export function isNode(obj: Node | string | number | boolean): obj is Node {
|
|
51
|
+
return (
|
|
52
|
+
typeof obj !== "string" ||
|
|
53
|
+
typeof obj !== "number" ||
|
|
54
|
+
typeof obj !== "boolean"
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Computes if the first arg extends the second arg
|
|
60
|
+
*/
|
|
61
|
+
export function computeExtends(a: NodeType, b: NodeType): boolean {
|
|
62
|
+
// special case for any/unknown being functionally the same
|
|
63
|
+
if (
|
|
64
|
+
(a.type === "any" || a.type === "unknown") &&
|
|
65
|
+
(b.type === "any" || b.type === "unknown")
|
|
66
|
+
) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// special case for null/undefined being functionally the same
|
|
71
|
+
if (
|
|
72
|
+
(a.type === "null" || a.type === "undefined") &&
|
|
73
|
+
(b.type === "null" || b.type === "undefined")
|
|
74
|
+
) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// check simple case of equal types
|
|
79
|
+
if (a.type === b.type) {
|
|
80
|
+
if (isPrimitiveTypeNode(a) && isPrimitiveTypeNode(b)) {
|
|
81
|
+
if (a.const && b.const) {
|
|
82
|
+
if (a.const === b.const) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (a.type === "ref" && b.type === "ref") {
|
|
91
|
+
return a.ref === b.ref;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (a.type === "object" && b.type === "object") {
|
|
95
|
+
for (const property in b.properties) {
|
|
96
|
+
const propertyNode = b.properties[property];
|
|
97
|
+
if (
|
|
98
|
+
!a.properties[property] ||
|
|
99
|
+
!computeExtends(a.properties[property].node, propertyNode.node)
|
|
100
|
+
) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (isPrimitiveTypeNode(a) && b.type === "or") {
|
|
110
|
+
return b.or.every((member) => computeExtends(a, member));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (isPrimitiveTypeNode(b) && a.type === "or") {
|
|
114
|
+
return a.or.every((member) => computeExtends(b, member));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (a.type === "or" && b.type === "or") {
|
|
118
|
+
return a.or.every((x) => b.or.some((y) => computeExtends(x, y)));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Attempts to resolve a conditional type
|
|
126
|
+
*/
|
|
127
|
+
export function resolveConditional(conditional: ConditionalType): NodeType {
|
|
128
|
+
const { left, right } = conditional.check;
|
|
129
|
+
const conditionalResult = computeExtends(left, right)
|
|
130
|
+
? conditional.value.true
|
|
131
|
+
: conditional.value.false;
|
|
132
|
+
|
|
133
|
+
// Compose first level generics here since `conditionalResult` won't have them
|
|
134
|
+
if (isGenericNodeType(conditional)) {
|
|
135
|
+
const genericMap: Map<string, NodeType> = new Map();
|
|
136
|
+
conditional.genericTokens.forEach((token) => {
|
|
137
|
+
genericMap.set(
|
|
138
|
+
token.symbol,
|
|
139
|
+
token.default ?? token.constraints ?? { type: "any" },
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return fillInGenerics(conditionalResult, genericMap);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return conditionalResult;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Resolve referenced node with potential generic arguments
|
|
151
|
+
*/
|
|
152
|
+
export function resolveReferenceNode(
|
|
153
|
+
genericReference: RefNode,
|
|
154
|
+
typeToFill: NodeType,
|
|
155
|
+
): NodeType {
|
|
156
|
+
const genericArgs = genericReference.genericArguments;
|
|
157
|
+
const genericMap: Map<string, NodeType> = new Map();
|
|
158
|
+
|
|
159
|
+
// Compose first level generics here from `genericReference`
|
|
160
|
+
if (genericArgs && isGenericNodeType(typeToFill)) {
|
|
161
|
+
typeToFill.genericTokens.forEach((token, index) => {
|
|
162
|
+
genericMap.set(
|
|
163
|
+
token.symbol,
|
|
164
|
+
genericArgs[index] ?? token.default ?? token.constraints,
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Fill in generics
|
|
170
|
+
const filledInNode = fillInGenerics(typeToFill, genericMap);
|
|
171
|
+
|
|
172
|
+
// Remove generic tokens that were resolve
|
|
173
|
+
if (isGenericNodeType(filledInNode) && genericArgs?.length) {
|
|
174
|
+
if (genericArgs.length < filledInNode.genericTokens.length) {
|
|
175
|
+
filledInNode.genericTokens = filledInNode.genericTokens.slice(
|
|
176
|
+
genericArgs?.length,
|
|
177
|
+
);
|
|
178
|
+
} else if (genericArgs.length === filledInNode.genericTokens.length) {
|
|
179
|
+
filledInNode.genericTokens = [];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Resolve index access
|
|
184
|
+
if (genericReference.property && filledInNode.type === "object") {
|
|
185
|
+
return (
|
|
186
|
+
filledInNode.properties[genericReference.property]?.node ??
|
|
187
|
+
filledInNode.additionalProperties ?? { type: "undefined" }
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return filledInNode;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Combines two ObjectType objects to get a representation of the effective TypeScript interface of `base` extending `operand`
|
|
196
|
+
- * @param base The base interface
|
|
197
|
+
- * @param operand The interface that is extended
|
|
198
|
+
- * @param errorOnOverlap whether or not conflicting properties should throw an error or use the property from operand
|
|
199
|
+
- * @returns `ObjectType`
|
|
200
|
+
*/
|
|
201
|
+
export function computeEffectiveObject(
|
|
202
|
+
base: ObjectType,
|
|
203
|
+
operand: ObjectType,
|
|
204
|
+
errorOnOverlap = true,
|
|
205
|
+
): ObjectType {
|
|
206
|
+
const baseObjectName = base.name ?? base.title ?? "object literal";
|
|
207
|
+
const operandObjectName = operand.name ?? operand.title ?? "object literal";
|
|
208
|
+
const newObject = {
|
|
209
|
+
...JSON.parse(JSON.stringify(base)),
|
|
210
|
+
name: `${baseObjectName} & ${operandObjectName}`,
|
|
211
|
+
description: `Effective type combining ${baseObjectName} and ${operandObjectName}`,
|
|
212
|
+
genericTokens: [
|
|
213
|
+
...(isGenericNodeType(base) ? base.genericTokens : []),
|
|
214
|
+
...(isGenericNodeType(operand) ? operand.genericTokens : []),
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
// TODO this check needs to account for primitive -> primitive generic overlap
|
|
218
|
+
|
|
219
|
+
for (const property in operand.properties) {
|
|
220
|
+
if (newObject.properties[property] !== undefined && errorOnOverlap) {
|
|
221
|
+
if (
|
|
222
|
+
!computeExtends(
|
|
223
|
+
newObject.properties[property].node,
|
|
224
|
+
operand.properties[property].node,
|
|
225
|
+
)
|
|
226
|
+
) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
`Can't compute effective type for ${baseObjectName} and ${operandObjectName} because of conflicting properties ${property}`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
newObject.properties[property] = operand.properties[property];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (newObject.additionalProperties && operand.additionalProperties) {
|
|
237
|
+
if (
|
|
238
|
+
!isPrimitiveTypeNode(newObject.additionalProperties) ||
|
|
239
|
+
!isPrimitiveTypeNode(operand.additionalProperties) ||
|
|
240
|
+
newObject.additionalProperties.type !== operand.additionalProperties.type
|
|
241
|
+
) {
|
|
242
|
+
newObject.additionalProperties = {
|
|
243
|
+
type: "and",
|
|
244
|
+
and: [newObject.additionalProperties, operand.additionalProperties],
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
} else if (operand.additionalProperties) {
|
|
248
|
+
newObject.additionalProperties = operand.additionalProperties;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return newObject;
|
|
252
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SymbolDisplayPart } from "typescript";
|
|
2
|
+
import type { NodeType } from "@xlr-lib/xlr";
|
|
3
|
+
/**
|
|
4
|
+
* Generate a documentation string for a given node
|
|
5
|
+
*
|
|
6
|
+
* @param node - The source node to author the docs string for
|
|
7
|
+
* @returns - documentation string
|
|
8
|
+
*/
|
|
9
|
+
export declare function createTSDocString(node: NodeType): Array<SymbolDisplayPart>;
|
|
10
|
+
/** Convert the TS SymbolDisplayParts into a single string */
|
|
11
|
+
export declare function symbolDisplayToString(displayParts: Array<SymbolDisplayPart>): string;
|
|
12
|
+
/** Create a documentation string from node */
|
|
13
|
+
export declare function createDocString(node: NodeType): string;
|
|
14
|
+
//# sourceMappingURL=documentation.d.ts.map
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { NodeType, OrType } from "@xlr-lib/xlr";
|
|
3
|
+
/**
|
|
4
|
+
* Returns the required type or the optionally required type
|
|
5
|
+
*/
|
|
6
|
+
export declare function tsStripOptionalType(node: ts.TypeNode): ts.TypeNode;
|
|
7
|
+
/**
|
|
8
|
+
* Returns if the top level declaration is exported
|
|
9
|
+
*/
|
|
10
|
+
export declare function isExportedDeclaration(node: ts.Statement): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Returns if the node is exported from the source file
|
|
13
|
+
*/
|
|
14
|
+
export declare function isNodeExported(node: ts.Node): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Returns the actual type and will following import chains if needed
|
|
17
|
+
*/
|
|
18
|
+
export declare function getReferencedType(node: ts.TypeReferenceNode, typeChecker: ts.TypeChecker): {
|
|
19
|
+
declaration: ts.TypeAliasDeclaration | ts.InterfaceDeclaration;
|
|
20
|
+
exported: boolean;
|
|
21
|
+
} | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Returns list of string literals from potential union of strings
|
|
24
|
+
*/
|
|
25
|
+
export declare function getStringLiteralsFromUnion(node: ts.Node): Set<string>;
|
|
26
|
+
/**
|
|
27
|
+
* Converts a format string into a regex that can be used to validate a given string matches the template
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildTemplateRegex(node: ts.TemplateLiteralTypeNode, typeChecker: ts.TypeChecker): string;
|
|
30
|
+
/**
|
|
31
|
+
* Walks generics to fill in values from a combination of the default, constraint, and passed in map values
|
|
32
|
+
* TODO convert this to use simpleTransformGenerator
|
|
33
|
+
*/
|
|
34
|
+
export declare function fillInGenerics(xlrNode: NodeType, generics?: Map<string, NodeType>): NodeType;
|
|
35
|
+
/** Applies the TS `Pick` or `Omit` type to an interface/union/intersection */
|
|
36
|
+
export declare function applyPickOrOmitToNodeType(baseObject: NodeType, operation: "Pick" | "Omit", properties: Set<string>): NodeType | undefined;
|
|
37
|
+
/** Applies the TS `Partial` or `Required` type to an interface/union/intersection */
|
|
38
|
+
export declare function applyPartialOrRequiredToNodeType(baseObject: NodeType, modifier: boolean): NodeType;
|
|
39
|
+
/** Applies the TS `Exclude` type to a union */
|
|
40
|
+
export declare function applyExcludeToNodeType(baseObject: OrType, filters: NodeType | OrType): NodeType;
|
|
41
|
+
//# sourceMappingURL=ts-helpers.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { NamedType, NamedTypeWithGenerics, NodeType, NodeTypeWithGenerics, PrimitiveTypes } from "@xlr-lib/xlr";
|
|
3
|
+
/**
|
|
4
|
+
* Returns if the Object Property is optional
|
|
5
|
+
*/
|
|
6
|
+
export declare function isOptionalProperty(node: ts.PropertySignature): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Returns if the node is an Interface or Type with Generics
|
|
9
|
+
*/
|
|
10
|
+
export declare function isGenericInterfaceDeclaration(node: ts.InterfaceDeclaration): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Returns if the node is an Type Declaration with Generics
|
|
13
|
+
*/
|
|
14
|
+
export declare function isGenericTypeDeclaration(node: ts.TypeAliasDeclaration): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Returns if the referenced type is a generic
|
|
17
|
+
*/
|
|
18
|
+
export declare function isTypeReferenceGeneric(node: ts.TypeReferenceNode, typeChecker: ts.TypeChecker): boolean;
|
|
19
|
+
export type TopLevelDeclaration = ts.InterfaceDeclaration | ts.TypeAliasDeclaration;
|
|
20
|
+
/**
|
|
21
|
+
* Returns if the node is an interface or a type declaration
|
|
22
|
+
*/
|
|
23
|
+
export declare function isTopLevelDeclaration(node: ts.Node): node is TopLevelDeclaration;
|
|
24
|
+
export type TopLevelNode = TopLevelDeclaration | ts.VariableStatement;
|
|
25
|
+
/**
|
|
26
|
+
* Returns if the node is an interface or a type declaration
|
|
27
|
+
*/
|
|
28
|
+
export declare function isTopLevelNode(node: ts.Node): node is TopLevelNode;
|
|
29
|
+
/**
|
|
30
|
+
* Returns if the NodeType has generic tokens
|
|
31
|
+
*/
|
|
32
|
+
export declare function isGenericNodeType<T extends NodeType = NodeType>(nt: NodeType): nt is NodeTypeWithGenerics<T>;
|
|
33
|
+
/**
|
|
34
|
+
* Returns if the named type has generic tokens
|
|
35
|
+
*/
|
|
36
|
+
export declare function isGenericNamedType<T extends NamedType = NamedType>(nt: NodeType): nt is NamedTypeWithGenerics<T>;
|
|
37
|
+
/**
|
|
38
|
+
* Returns if the node is a `PrimitiveTypes`
|
|
39
|
+
*/
|
|
40
|
+
export declare function isPrimitiveTypeNode(node: NodeType): node is PrimitiveTypes;
|
|
41
|
+
/**
|
|
42
|
+
* Type Guard for non-null values
|
|
43
|
+
*/
|
|
44
|
+
export declare function isNonNullable<T>(a: T | null | undefined): a is NonNullable<T>;
|
|
45
|
+
//# sourceMappingURL=type-checks.d.ts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Node } from "jsonc-parser";
|
|
2
|
+
import type { ConditionalType, NodeType, ObjectType, RefNode } from "@xlr-lib/xlr";
|
|
3
|
+
export interface PropertyNode {
|
|
4
|
+
/** Equivalent Property Name */
|
|
5
|
+
key: string;
|
|
6
|
+
/** Equivalent Property Value */
|
|
7
|
+
value: Node;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Takes a property node and returns the underlying Key/Value Pairs
|
|
11
|
+
*/
|
|
12
|
+
export declare function propertyToTuple(node: Node): PropertyNode;
|
|
13
|
+
/**
|
|
14
|
+
* Turns a node's children into a map for easy access
|
|
15
|
+
*/
|
|
16
|
+
export declare function makePropertyMap(node: Node): Map<string, Node>;
|
|
17
|
+
/**
|
|
18
|
+
* Checks if property is a leaf node or another node
|
|
19
|
+
*/
|
|
20
|
+
export declare function isNode(obj: Node | string | number | boolean): obj is Node;
|
|
21
|
+
/**
|
|
22
|
+
* Computes if the first arg extends the second arg
|
|
23
|
+
*/
|
|
24
|
+
export declare function computeExtends(a: NodeType, b: NodeType): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Attempts to resolve a conditional type
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveConditional(conditional: ConditionalType): NodeType;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve referenced node with potential generic arguments
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveReferenceNode(genericReference: RefNode, typeToFill: NodeType): NodeType;
|
|
33
|
+
/**
|
|
34
|
+
* Combines two ObjectType objects to get a representation of the effective TypeScript interface of `base` extending `operand`
|
|
35
|
+
- * @param base The base interface
|
|
36
|
+
- * @param operand The interface that is extended
|
|
37
|
+
- * @param errorOnOverlap whether or not conflicting properties should throw an error or use the property from operand
|
|
38
|
+
- * @returns `ObjectType`
|
|
39
|
+
*/
|
|
40
|
+
export declare function computeEffectiveObject(base: ObjectType, operand: ObjectType, errorOnOverlap?: boolean): ObjectType;
|
|
41
|
+
//# sourceMappingURL=validation-helpers.d.ts.map
|