@player-tools/dsl 0.10.1 → 0.11.0--canary.212.4707
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 +97 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.legacy-esm.js +87 -1
- package/dist/index.mjs +87 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/helpers/mock-data-refs.ts +1 -1
- package/src/__tests__/util.test.tsx +185 -8
- package/src/compiler/schema.ts +15 -7
- package/src/expressions/__tests__/native.test.tsx +159 -0
- package/src/expressions/native.ts +141 -0
- package/src/index.ts +1 -0
- package/src/string-templates/index.ts +20 -10
- package/src/types.ts +27 -1
- package/src/utils.tsx +65 -6
- package/types/compiler/schema.d.ts +5 -2
- package/types/expressions/native.d.ts +50 -0
- package/types/index.d.ts +1 -0
- package/types/string-templates/index.d.ts +9 -7
- package/types/types.d.ts +11 -1
- package/types/utils.d.ts +15 -3
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { test, expect, describe } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getObjectReferences,
|
|
4
|
+
mapExpressionHandlersToFunctions,
|
|
5
|
+
wrapFunctionInType,
|
|
6
|
+
} from "../utils";
|
|
7
|
+
import { Binding, Expression, ExpressionHandler } from "@player-ui/player";
|
|
8
|
+
import { binding, expression } from "../string-templates";
|
|
9
|
+
import { LocalBazType } from "./helpers/mock-data-refs";
|
|
10
|
+
import { DataTypeRefs } from "../types";
|
|
11
|
+
import { makeBindingsForObject } from "../compiler/schema";
|
|
3
12
|
|
|
4
13
|
describe("Testing the 'getObjectReferences' helper that creates same property references into a new object", () => {
|
|
5
14
|
test("should return the object properties in referenced format", () => {
|
|
6
15
|
const dataTypes = {
|
|
7
|
-
|
|
8
|
-
type: "BooleanType",
|
|
9
|
-
},
|
|
16
|
+
LocalBazType,
|
|
10
17
|
};
|
|
11
18
|
|
|
12
19
|
const validators = {
|
|
@@ -15,12 +22,182 @@ describe("Testing the 'getObjectReferences' helper that creates same property re
|
|
|
15
22
|
},
|
|
16
23
|
};
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
25
|
+
type DTREF = DataTypeRefs<typeof dataTypes>;
|
|
26
|
+
|
|
27
|
+
const dataReferences = getObjectReferences<typeof dataTypes, DTREF>(
|
|
28
|
+
dataTypes,
|
|
29
|
+
);
|
|
20
30
|
|
|
21
|
-
|
|
22
|
-
|
|
31
|
+
type VALREF = DataTypeRefs<typeof validators>;
|
|
32
|
+
const validatorReferences = getObjectReferences<typeof validators, VALREF>(
|
|
33
|
+
validators,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
expect(dataReferences.LocalBazTypeRef).toStrictEqual({
|
|
37
|
+
type: "LocalBazType",
|
|
23
38
|
});
|
|
24
39
|
expect(validatorReferences.requiredRef).toStrictEqual({ type: "required" });
|
|
25
40
|
});
|
|
26
41
|
});
|
|
42
|
+
|
|
43
|
+
describe("DSL Expression Generation Helper", () => {
|
|
44
|
+
test("Returns the correct serialization for bindings", () => {
|
|
45
|
+
const mockFunction: ExpressionHandler<[Binding], boolean> = (ctx, val) => {
|
|
46
|
+
return false;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const mockFunctionDSL = wrapFunctionInType(mockFunction);
|
|
50
|
+
|
|
51
|
+
const foo = binding`some.binding`;
|
|
52
|
+
const test = expression`${mockFunctionDSL(foo)} == true`;
|
|
53
|
+
|
|
54
|
+
expect(test.toValue()).toEqual("mockFunction('some.binding') == true");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("Returns the correct serialization for mixed values and bindings", () => {
|
|
58
|
+
const mockFunction: ExpressionHandler<[string, string, number], boolean> = (
|
|
59
|
+
ctx,
|
|
60
|
+
val,
|
|
61
|
+
) => {
|
|
62
|
+
return false;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const mockFunctionDSL = wrapFunctionInType(mockFunction);
|
|
66
|
+
|
|
67
|
+
const foo = binding`some.binding`;
|
|
68
|
+
const test = expression`${mockFunctionDSL(foo.toRefString(), "test", 1)} == true`;
|
|
69
|
+
|
|
70
|
+
expect(test.toValue()).toEqual(
|
|
71
|
+
"mockFunction('{{some.binding}}', 'test', 1) == true",
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("Returns the correct serialization for array items", () => {
|
|
76
|
+
const mockFunction: ExpressionHandler<[Array<unknown>], boolean> = (
|
|
77
|
+
ctx,
|
|
78
|
+
val,
|
|
79
|
+
) => {
|
|
80
|
+
return false;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const mockFunctionDSL = wrapFunctionInType(mockFunction);
|
|
84
|
+
|
|
85
|
+
const foo = binding`some.binding`;
|
|
86
|
+
const test = expression`${mockFunctionDSL([1, "2", foo])}`;
|
|
87
|
+
|
|
88
|
+
expect(test.toValue()).toEqual(
|
|
89
|
+
"mockFunction([1, '2', '{{some.binding}}'])",
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("Can Dereferenced Binding", () => {
|
|
94
|
+
const mockFunction: ExpressionHandler<[boolean], boolean> = (ctx, val) => {
|
|
95
|
+
return false;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const mockFunctionDSL = wrapFunctionInType(mockFunction);
|
|
99
|
+
|
|
100
|
+
const foo = binding`some.binding`;
|
|
101
|
+
const test = expression`${mockFunctionDSL(foo.toRefString())} == true`;
|
|
102
|
+
|
|
103
|
+
expect(test.toValue()).toEqual("mockFunction('{{some.binding}}') == true");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("Can Pass Nested Expressions", () => {
|
|
107
|
+
const mockFunction: ExpressionHandler<[Expression, number], boolean> = (
|
|
108
|
+
ctx,
|
|
109
|
+
val,
|
|
110
|
+
) => {
|
|
111
|
+
return false;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const mockFunctionDSL = wrapFunctionInType(mockFunction);
|
|
115
|
+
|
|
116
|
+
const foo = binding`foo.bar`;
|
|
117
|
+
const bar = expression`${foo} != 1`;
|
|
118
|
+
const test = expression`${mockFunctionDSL(bar, 1)}`;
|
|
119
|
+
|
|
120
|
+
expect(test.toValue()).toEqual("mockFunction('{{foo.bar}} != 1', 1)");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("Export Generator", () => {
|
|
124
|
+
const mockFunction: ExpressionHandler<[string, number], boolean> = (
|
|
125
|
+
ctx,
|
|
126
|
+
val,
|
|
127
|
+
) => {
|
|
128
|
+
return false;
|
|
129
|
+
};
|
|
130
|
+
const expressionFunctions = { mockFunction };
|
|
131
|
+
|
|
132
|
+
const usableFunctions =
|
|
133
|
+
mapExpressionHandlersToFunctions<typeof expressionFunctions>(
|
|
134
|
+
expressionFunctions,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
expect(usableFunctions.mockFunction("1", 0).toValue()).toMatch(
|
|
138
|
+
"mockFunction('1', 0)",
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("Return Type Chaining", () => {
|
|
143
|
+
const mockFunction: ExpressionHandler<[string, number], string> = (
|
|
144
|
+
ctx,
|
|
145
|
+
val,
|
|
146
|
+
) => {
|
|
147
|
+
return "false";
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const mockFunction2: ExpressionHandler<[string], boolean> = (ctx, val) => {
|
|
151
|
+
return false;
|
|
152
|
+
};
|
|
153
|
+
const expressionFunctions = { mockFunction, mockFunction2 };
|
|
154
|
+
|
|
155
|
+
const usableFunctions =
|
|
156
|
+
mapExpressionHandlersToFunctions<typeof expressionFunctions>(
|
|
157
|
+
expressionFunctions,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
expect(
|
|
161
|
+
usableFunctions
|
|
162
|
+
.mockFunction2(usableFunctions.mockFunction("1", 0).toValue())
|
|
163
|
+
.toValue(),
|
|
164
|
+
).toMatch("mockFunction2('mockFunction('1', 0)')");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("Works with DSL Schema", () => {
|
|
168
|
+
const mockFunction: ExpressionHandler<[string, boolean], string> = (
|
|
169
|
+
ctx,
|
|
170
|
+
val,
|
|
171
|
+
) => {
|
|
172
|
+
return "false";
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const dataTypes = { LocalBazType };
|
|
176
|
+
|
|
177
|
+
type DTREF = DataTypeRefs<typeof dataTypes>;
|
|
178
|
+
|
|
179
|
+
const refableTypes = getObjectReferences<typeof dataTypes, DTREF>(
|
|
180
|
+
dataTypes,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const data = {
|
|
184
|
+
some: {
|
|
185
|
+
value: refableTypes.LocalBazTypeRef,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const schema = makeBindingsForObject(data);
|
|
190
|
+
const expressionFunctions = { mockFunction };
|
|
191
|
+
|
|
192
|
+
const usableFunctions =
|
|
193
|
+
mapExpressionHandlersToFunctions<typeof expressionFunctions>(
|
|
194
|
+
expressionFunctions,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
expect(
|
|
198
|
+
usableFunctions
|
|
199
|
+
.mockFunction("1", schema.some.value.toRefString())
|
|
200
|
+
.toValue(),
|
|
201
|
+
).toMatch("mockFunction('1', '{{some.value}}')");
|
|
202
|
+
});
|
|
203
|
+
});
|
package/src/compiler/schema.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type { BindingTemplateInstance } from "../string-templates";
|
|
|
8
8
|
const bindingSymbol = Symbol("binding");
|
|
9
9
|
|
|
10
10
|
/** Symbol to indicate that a schema node should be generated with a different name */
|
|
11
|
-
export const SchemaTypeName = Symbol("Schema Rename");
|
|
11
|
+
export const SchemaTypeName: unique symbol = Symbol("Schema Rename");
|
|
12
12
|
|
|
13
13
|
interface SchemaChildren {
|
|
14
14
|
/** Object property that will be used to create the intermediate type */
|
|
@@ -47,7 +47,15 @@ export class SchemaGenerator {
|
|
|
47
47
|
private generatedDataTypes: Map<string, GeneratedDataType>;
|
|
48
48
|
private logger: LoggingInterface;
|
|
49
49
|
|
|
50
|
-
public hooks
|
|
50
|
+
public hooks: {
|
|
51
|
+
createSchemaNode: SyncWaterfallHook<
|
|
52
|
+
[
|
|
53
|
+
node: Schema.DataType<unknown>,
|
|
54
|
+
originalProperty: Record<string | symbol, unknown>,
|
|
55
|
+
],
|
|
56
|
+
Record<string, any>
|
|
57
|
+
>;
|
|
58
|
+
} = {
|
|
51
59
|
createSchemaNode: new SyncWaterfallHook<
|
|
52
60
|
[
|
|
53
61
|
node: Schema.DataType,
|
|
@@ -197,14 +205,14 @@ export type MakeBindingRefable<T> = {
|
|
|
197
205
|
: T[P] extends unknown[]
|
|
198
206
|
? T[P]
|
|
199
207
|
: MakeBindingRefable<T[P]>;
|
|
200
|
-
} & BindingTemplateInstance
|
|
208
|
+
} & BindingTemplateInstance<T>;
|
|
201
209
|
|
|
202
210
|
/**
|
|
203
211
|
* Adds bindings to an object so that the object can be directly used in JSX
|
|
204
212
|
*/
|
|
205
213
|
export function makeBindingsForObject<Type>(
|
|
206
214
|
obj: Type,
|
|
207
|
-
arrayAccessorKeys = ["_index_"],
|
|
215
|
+
arrayAccessorKeys: string[] = ["_index_"],
|
|
208
216
|
): MakeBindingRefable<Type> {
|
|
209
217
|
/** Proxy to track binding callbacks */
|
|
210
218
|
const accessor = (paths: string[]) => {
|
|
@@ -258,7 +266,7 @@ export function makeBindingsForObject<Type>(
|
|
|
258
266
|
/**
|
|
259
267
|
* Generates binding for an object property
|
|
260
268
|
*/
|
|
261
|
-
export const getBindingFromObject = (obj: any) => {
|
|
269
|
+
export const getBindingFromObject = (obj: any): BindingTemplateInstance => {
|
|
262
270
|
const baseBindings = obj[bindingSymbol] as string[];
|
|
263
271
|
if (!Array.isArray(baseBindings) || baseBindings.length === 0) {
|
|
264
272
|
throw new Error(`Unable to get binding for ${obj}`);
|
|
@@ -270,13 +278,13 @@ export const getBindingFromObject = (obj: any) => {
|
|
|
270
278
|
/**
|
|
271
279
|
* Returns the binding string from an object path
|
|
272
280
|
*/
|
|
273
|
-
export const getBindingStringFromObject = (obj: any) => {
|
|
281
|
+
export const getBindingStringFromObject = (obj: any): string => {
|
|
274
282
|
return getBindingFromObject(obj).toString();
|
|
275
283
|
};
|
|
276
284
|
|
|
277
285
|
/**
|
|
278
286
|
* Returns the ref string from an object path
|
|
279
287
|
*/
|
|
280
|
-
export const getRefStringFromObject = (obj: any) => {
|
|
288
|
+
export const getRefStringFromObject = (obj: any): string => {
|
|
281
289
|
return getBindingFromObject(obj).toRefString();
|
|
282
290
|
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { test, expect, describe } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
and,
|
|
4
|
+
assign,
|
|
5
|
+
binding as b,
|
|
6
|
+
expression as e,
|
|
7
|
+
equals,
|
|
8
|
+
nand,
|
|
9
|
+
nor,
|
|
10
|
+
not,
|
|
11
|
+
or,
|
|
12
|
+
} from "../../";
|
|
13
|
+
|
|
14
|
+
describe("assign", () => {
|
|
15
|
+
test("string", () => {
|
|
16
|
+
const binding = b`foo.bar`;
|
|
17
|
+
expect(assign(binding, "1").toValue()).toStrictEqual("{{foo.bar}} = '1'");
|
|
18
|
+
});
|
|
19
|
+
test("number", () => {
|
|
20
|
+
const binding = b`foo.bar`;
|
|
21
|
+
expect(assign(binding, 1).toValue()).toStrictEqual("{{foo.bar}} = 1");
|
|
22
|
+
});
|
|
23
|
+
test("boolean", () => {
|
|
24
|
+
const binding = b`foo.bar`;
|
|
25
|
+
expect(assign(binding, true).toValue()).toStrictEqual("{{foo.bar}} = true");
|
|
26
|
+
});
|
|
27
|
+
test("undefined", () => {
|
|
28
|
+
const binding = b`foo.bar`;
|
|
29
|
+
expect(assign(binding, undefined).toValue()).toStrictEqual(
|
|
30
|
+
"{{foo.bar}} = null",
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("not", () => {
|
|
36
|
+
test("binding", () => {
|
|
37
|
+
const binding = b<boolean>`foo.bar`;
|
|
38
|
+
expect(not(binding).toValue()).toStrictEqual("!({{foo.bar}})");
|
|
39
|
+
});
|
|
40
|
+
test("expression", () => {
|
|
41
|
+
const expression = e<boolean>`${b`foo.bar`} = true`;
|
|
42
|
+
|
|
43
|
+
expect(not(expression).toValue()).toStrictEqual("!({{foo.bar}} = true)");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("and", () => {
|
|
48
|
+
test("bindings", () => {
|
|
49
|
+
const binding = b<boolean>`foo.bar`;
|
|
50
|
+
const binding2 = b<boolean>`foo.baz`;
|
|
51
|
+
expect(and(binding, binding2).toValue()).toStrictEqual(
|
|
52
|
+
"{{foo.bar}} && {{foo.baz}}",
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("expressions", () => {
|
|
57
|
+
const expression = e<boolean>`${b`foo.bar`} == true`;
|
|
58
|
+
const expression2 = e<boolean>`${b`foo.baz`} == '1'`;
|
|
59
|
+
expect(and(expression, expression2).toValue()).toStrictEqual(
|
|
60
|
+
"({{foo.bar}} == true) && ({{foo.baz}} == '1')",
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("or", () => {
|
|
66
|
+
test("bindings", () => {
|
|
67
|
+
const binding = b<boolean>`foo.bar`;
|
|
68
|
+
const binding2 = b<boolean>`foo.baz`;
|
|
69
|
+
expect(or(binding, binding2).toValue()).toStrictEqual(
|
|
70
|
+
"{{foo.bar}} || {{foo.baz}}",
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("expressions", () => {
|
|
75
|
+
const expression = e<boolean>`${b`foo.bar`} == true`;
|
|
76
|
+
const expression2 = e<boolean>`${b`foo.baz`} == '1'`;
|
|
77
|
+
expect(or(expression, expression2).toValue()).toStrictEqual(
|
|
78
|
+
"({{foo.bar}} == true) || ({{foo.baz}} == '1')",
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("nand", () => {
|
|
84
|
+
test("bindings", () => {
|
|
85
|
+
const binding = b<boolean>`foo.bar`;
|
|
86
|
+
const binding2 = b<boolean>`foo.baz`;
|
|
87
|
+
expect(nand(binding, binding2).toValue()).toStrictEqual(
|
|
88
|
+
"!({{foo.bar}} && {{foo.baz}})",
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("expressions", () => {
|
|
93
|
+
const expression = e<boolean>`${b`foo.bar`} == true`;
|
|
94
|
+
const expression2 = e<boolean>`${b`foo.baz`} == '1'`;
|
|
95
|
+
expect(nand(expression, expression2).toValue()).toStrictEqual(
|
|
96
|
+
"!(({{foo.bar}} == true) && ({{foo.baz}} == '1'))",
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("nor", () => {
|
|
102
|
+
test("bindings", () => {
|
|
103
|
+
const binding = b<boolean>`foo.bar`;
|
|
104
|
+
const binding2 = b<boolean>`foo.baz`;
|
|
105
|
+
expect(nor(binding, binding2).toValue()).toStrictEqual(
|
|
106
|
+
"!({{foo.bar}} || {{foo.baz}})",
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("expressions", () => {
|
|
111
|
+
const expression = e<boolean>`${b`foo.bar`} == true`;
|
|
112
|
+
const expression2 = e<boolean>`${b`foo.baz`} == '1'`;
|
|
113
|
+
expect(nor(expression, expression2).toValue()).toStrictEqual(
|
|
114
|
+
"!(({{foo.bar}} == true) || ({{foo.baz}} == '1'))",
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("equals", () => {
|
|
120
|
+
test("binding-string", () => {
|
|
121
|
+
const binding = b`foo.bar`;
|
|
122
|
+
expect(equals(binding, "1").toValue()).toStrictEqual("{{foo.bar}} == '1'");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("binding-number", () => {
|
|
126
|
+
const binding = b`foo.bar`;
|
|
127
|
+
expect(equals(binding, 1).toValue()).toStrictEqual("{{foo.bar}} == 1");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("binding-boolean", () => {
|
|
131
|
+
const binding = b`foo.bar`;
|
|
132
|
+
expect(equals(binding, true).toValue()).toStrictEqual(
|
|
133
|
+
"{{foo.bar}} == true",
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("binding-undefined", () => {
|
|
138
|
+
const binding = b`foo.bar`;
|
|
139
|
+
expect(equals(binding, undefined).toValue()).toStrictEqual(
|
|
140
|
+
"{{foo.bar}} == null",
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("bindings", () => {
|
|
145
|
+
const binding = b<boolean>`foo.bar`;
|
|
146
|
+
const binding2 = b<boolean>`foo.baz`;
|
|
147
|
+
expect(equals(binding, binding2).toValue()).toStrictEqual(
|
|
148
|
+
"{{foo.bar}} == {{foo.baz}}",
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("expressions", () => {
|
|
153
|
+
const expression = e<boolean>`${b`foo.bar`} == true`;
|
|
154
|
+
const expression2 = e<boolean>`${b`foo.baz`} == '1'`;
|
|
155
|
+
expect(equals(expression, expression2).toValue()).toStrictEqual(
|
|
156
|
+
"({{foo.bar}} == true) == ({{foo.baz}} == '1')",
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
expression as e,
|
|
3
|
+
BindingTemplateInstance,
|
|
4
|
+
ExpressionTemplateInstance,
|
|
5
|
+
isTemplateStringInstance,
|
|
6
|
+
isBindingTemplateInstance,
|
|
7
|
+
} from "..";
|
|
8
|
+
|
|
9
|
+
type Argument<T> =
|
|
10
|
+
| string
|
|
11
|
+
| boolean
|
|
12
|
+
| number
|
|
13
|
+
| undefined
|
|
14
|
+
| BindingTemplateInstance<T>
|
|
15
|
+
| ExpressionTemplateInstance<T>;
|
|
16
|
+
|
|
17
|
+
const escapePrimitive = <T>(
|
|
18
|
+
val: Exclude<
|
|
19
|
+
Argument<T>,
|
|
20
|
+
BindingTemplateInstance<T> | ExpressionTemplateInstance<T>
|
|
21
|
+
>,
|
|
22
|
+
): string => {
|
|
23
|
+
switch (typeof val) {
|
|
24
|
+
case "string": {
|
|
25
|
+
return `'${val}'`;
|
|
26
|
+
}
|
|
27
|
+
case "undefined": {
|
|
28
|
+
return "null";
|
|
29
|
+
}
|
|
30
|
+
default: {
|
|
31
|
+
return `${val}`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const handleBindingOrExpression = (
|
|
37
|
+
val: BindingTemplateInstance | ExpressionTemplateInstance,
|
|
38
|
+
) => {
|
|
39
|
+
if (isBindingTemplateInstance(val)) {
|
|
40
|
+
return val.toRefString();
|
|
41
|
+
} else {
|
|
42
|
+
return `(${val.toValue()})`;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const handleArgument = <T>(arg: Argument<T>): string => {
|
|
47
|
+
return isTemplateStringInstance(arg)
|
|
48
|
+
? handleBindingOrExpression(arg)
|
|
49
|
+
: escapePrimitive(arg);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Performs an assigment of a value to a binding by returning the expression
|
|
54
|
+
* {{<binding>}} = <value>
|
|
55
|
+
* @param binding
|
|
56
|
+
* @param value
|
|
57
|
+
*/
|
|
58
|
+
export const assign = <T>(
|
|
59
|
+
binding: BindingTemplateInstance<any>,
|
|
60
|
+
value: Argument<T>,
|
|
61
|
+
): ExpressionTemplateInstance<void> => {
|
|
62
|
+
return e`${binding} = ${isTemplateStringInstance(value) ? value : escapePrimitive(value)}`;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns an equality comparison between the two values
|
|
67
|
+
*/
|
|
68
|
+
export const equals = <A, B>(
|
|
69
|
+
a: Argument<A>,
|
|
70
|
+
b: Argument<B>,
|
|
71
|
+
): ExpressionTemplateInstance<boolean> => {
|
|
72
|
+
return e`${handleArgument(a)} == ${handleArgument(b)}`;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Returns the negated version of the binding/expression
|
|
77
|
+
* by returning !(<value>)
|
|
78
|
+
* @param binding Binding/Expression to invert
|
|
79
|
+
* @returns Negated binding/expression
|
|
80
|
+
*/
|
|
81
|
+
export const not = (
|
|
82
|
+
value: BindingTemplateInstance<boolean> | ExpressionTemplateInstance<boolean>,
|
|
83
|
+
): ExpressionTemplateInstance<boolean> => {
|
|
84
|
+
return e<boolean>`!(${value})`;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Creates an expression for the logical or'ing of the provided values
|
|
89
|
+
* e.g: <exp1> || <exp2> || ...
|
|
90
|
+
* @param values Array of bindings/expressions to logically or
|
|
91
|
+
* @returns boolean
|
|
92
|
+
*/
|
|
93
|
+
export const or = (
|
|
94
|
+
...values: Array<
|
|
95
|
+
BindingTemplateInstance<boolean> | ExpressionTemplateInstance<boolean>
|
|
96
|
+
>
|
|
97
|
+
): ExpressionTemplateInstance<boolean> => {
|
|
98
|
+
return e`${values.map(handleBindingOrExpression).join(" || ")}` as ExpressionTemplateInstance<boolean>;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Creates an expression for the logical nor'ing of the provided values
|
|
103
|
+
* e.g: !(<exp1> || <exp2> || ...)
|
|
104
|
+
* @param values Array of bindings/expressions to logically nor
|
|
105
|
+
* @returns boolean
|
|
106
|
+
*/
|
|
107
|
+
export const nor = (
|
|
108
|
+
...values: Array<
|
|
109
|
+
BindingTemplateInstance<boolean> | ExpressionTemplateInstance<boolean>
|
|
110
|
+
>
|
|
111
|
+
): ExpressionTemplateInstance<boolean> => {
|
|
112
|
+
return not(or(...values));
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Creates an expression for the logical and'ing of the provided values
|
|
117
|
+
* e.g: <exp1> && <exp2> && ...
|
|
118
|
+
* @param values Array of bindings/expressions to logically and
|
|
119
|
+
* @returns boolean
|
|
120
|
+
*/
|
|
121
|
+
export const and = (
|
|
122
|
+
...values: Array<
|
|
123
|
+
BindingTemplateInstance<boolean> | ExpressionTemplateInstance<boolean>
|
|
124
|
+
>
|
|
125
|
+
): ExpressionTemplateInstance<boolean> => {
|
|
126
|
+
return e`${values.map(handleBindingOrExpression).join(" && ")}` as ExpressionTemplateInstance<boolean>;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Creates an expression for the logical nand'ing of the provided values
|
|
131
|
+
* e.g: !(<exp1> && <exp2> && ...)
|
|
132
|
+
* @param values Array of bindings/expressions to logically nand
|
|
133
|
+
* @returns boolean
|
|
134
|
+
*/
|
|
135
|
+
export const nand = (
|
|
136
|
+
...values: Array<
|
|
137
|
+
BindingTemplateInstance<boolean> | ExpressionTemplateInstance<boolean>
|
|
138
|
+
>
|
|
139
|
+
): ExpressionTemplateInstance<boolean> => {
|
|
140
|
+
return not(and(...values));
|
|
141
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -22,9 +22,11 @@ export interface TemplateInstanceRefStringOptions {
|
|
|
22
22
|
) => string;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const OpaqueIdentifier = Symbol("TemplateStringType");
|
|
25
|
+
const OpaqueIdentifier: unique symbol = Symbol("TemplateStringType");
|
|
26
26
|
|
|
27
|
-
export type TemplateStringType
|
|
27
|
+
export type TemplateStringType<
|
|
28
|
+
T extends string | number | boolean | unknown = any,
|
|
29
|
+
> = React.ReactElement & {
|
|
28
30
|
/** An identifier to show that this is a template type */
|
|
29
31
|
[OpaqueIdentifier]: true;
|
|
30
32
|
/** The value of the template string when in another string */
|
|
@@ -32,15 +34,21 @@ export type TemplateStringType = React.ReactElement & {
|
|
|
32
34
|
/** the raw value of the template string */
|
|
33
35
|
toValue: () => string;
|
|
34
36
|
/** the dereferenced value when used in another */
|
|
35
|
-
toRefString: (options?: TemplateRefStringOptions) =>
|
|
37
|
+
toRefString: (options?: TemplateRefStringOptions) => T;
|
|
38
|
+
/** Underlying type of this binding */
|
|
39
|
+
type: T;
|
|
36
40
|
};
|
|
37
41
|
|
|
38
|
-
export type BindingTemplateInstance
|
|
42
|
+
export type BindingTemplateInstance<
|
|
43
|
+
DataType extends string | number | boolean | unknown = any,
|
|
44
|
+
> = TemplateStringType<DataType> & {
|
|
39
45
|
/** An identifier for a binding instance */
|
|
40
46
|
__type: "binding";
|
|
41
47
|
};
|
|
42
48
|
|
|
43
|
-
export type ExpressionTemplateInstance
|
|
49
|
+
export type ExpressionTemplateInstance<
|
|
50
|
+
ReturnType extends string | number | boolean | unknown = any,
|
|
51
|
+
> = TemplateStringType<ReturnType> & {
|
|
44
52
|
/** The identifier for an expression instance */
|
|
45
53
|
__type: "expression";
|
|
46
54
|
};
|
|
@@ -49,7 +57,9 @@ export type ExpressionTemplateInstance = TemplateStringType & {
|
|
|
49
57
|
export const TemplateStringComponent = (props: {
|
|
50
58
|
/** The string value of the child template string */
|
|
51
59
|
value: string;
|
|
52
|
-
})
|
|
60
|
+
}): React.ReactElement<{
|
|
61
|
+
value: string;
|
|
62
|
+
}> => {
|
|
53
63
|
return React.createElement(
|
|
54
64
|
"value",
|
|
55
65
|
{
|
|
@@ -179,10 +189,10 @@ const createExpressionTemplateInstance = (
|
|
|
179
189
|
};
|
|
180
190
|
|
|
181
191
|
/** A tagged-template constructor for a binding */
|
|
182
|
-
export const binding = (
|
|
192
|
+
export const binding = <T>(
|
|
183
193
|
strings: TemplateStringsArray,
|
|
184
194
|
...nested: Array<TemplateStringType | string>
|
|
185
|
-
): BindingTemplateInstance => {
|
|
195
|
+
): BindingTemplateInstance<T> => {
|
|
186
196
|
return createBindingTemplateInstance({
|
|
187
197
|
strings,
|
|
188
198
|
other: nested,
|
|
@@ -191,12 +201,12 @@ export const binding = (
|
|
|
191
201
|
};
|
|
192
202
|
|
|
193
203
|
/** A tagged-template constructor for an expression */
|
|
194
|
-
export const expression = (
|
|
204
|
+
export const expression = <T>(
|
|
195
205
|
strings: TemplateStringsArray,
|
|
196
206
|
...nested: Array<
|
|
197
207
|
ExpressionTemplateInstance | BindingTemplateInstance | string
|
|
198
208
|
>
|
|
199
|
-
): ExpressionTemplateInstance => {
|
|
209
|
+
): ExpressionTemplateInstance<T> => {
|
|
200
210
|
return createExpressionTemplateInstance({
|
|
201
211
|
strings,
|
|
202
212
|
other: nested,
|