@valbuild/core 0.26.0 → 0.27.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.
Files changed (92) hide show
  1. package/package.json +12 -3
  2. package/CHANGELOG.md +0 -0
  3. package/ROADMAP.md +0 -106
  4. package/jest.config.js +0 -4
  5. package/src/Json.ts +0 -4
  6. package/src/ValApi.ts +0 -81
  7. package/src/expr/README.md +0 -193
  8. package/src/expr/eval.test.ts +0 -198
  9. package/src/expr/eval.ts +0 -251
  10. package/src/expr/expr.ts +0 -91
  11. package/src/expr/index.ts +0 -3
  12. package/src/expr/parser.test.ts +0 -158
  13. package/src/expr/parser.ts +0 -229
  14. package/src/expr/repl.ts +0 -88
  15. package/src/expr/tokenizer.test.ts +0 -539
  16. package/src/expr/tokenizer.ts +0 -117
  17. package/src/fp/array.ts +0 -30
  18. package/src/fp/index.ts +0 -3
  19. package/src/fp/result.ts +0 -214
  20. package/src/fp/util.ts +0 -52
  21. package/src/future/fetchVal.test.ts +0 -164
  22. package/src/future/fetchVal.ts +0 -206
  23. package/src/getSha256.ts +0 -8
  24. package/src/index.ts +0 -132
  25. package/src/initSchema.ts +0 -50
  26. package/src/initVal.ts +0 -73
  27. package/src/module.test.ts +0 -170
  28. package/src/module.ts +0 -397
  29. package/src/patch/deref.test.ts +0 -298
  30. package/src/patch/deref.ts +0 -136
  31. package/src/patch/index.ts +0 -12
  32. package/src/patch/json.test.ts +0 -582
  33. package/src/patch/json.ts +0 -304
  34. package/src/patch/operation.ts +0 -86
  35. package/src/patch/ops.ts +0 -83
  36. package/src/patch/parse.test.ts +0 -202
  37. package/src/patch/parse.ts +0 -202
  38. package/src/patch/patch.ts +0 -49
  39. package/src/patch/util.ts +0 -74
  40. package/src/schema/array.ts +0 -93
  41. package/src/schema/boolean.ts +0 -49
  42. package/src/schema/future/i18n.ts +0 -69
  43. package/src/schema/future/oneOf.ts +0 -63
  44. package/src/schema/image.ts +0 -137
  45. package/src/schema/index.ts +0 -70
  46. package/src/schema/keyOf.ts +0 -167
  47. package/src/schema/literal.ts +0 -63
  48. package/src/schema/number.ts +0 -56
  49. package/src/schema/object.ts +0 -110
  50. package/src/schema/record.ts +0 -103
  51. package/src/schema/richtext.ts +0 -44
  52. package/src/schema/string.ts +0 -95
  53. package/src/schema/union.ts +0 -63
  54. package/src/schema/validation/ValidationError.ts +0 -16
  55. package/src/schema/validation/ValidationFix.ts +0 -6
  56. package/src/schema/validation.test.ts +0 -291
  57. package/src/selector/SelectorProxy.ts +0 -238
  58. package/src/selector/array.ts +0 -13
  59. package/src/selector/boolean.ts +0 -4
  60. package/src/selector/file.ts +0 -6
  61. package/src/selector/future/ExprProxy.test.ts +0 -203
  62. package/src/selector/future/ExprProxy.ts +0 -216
  63. package/src/selector/future/SelectorProxy.test.ts +0 -172
  64. package/src/selector/future/SelectorProxy.ts +0 -238
  65. package/src/selector/future/array.ts +0 -37
  66. package/src/selector/future/boolean.ts +0 -4
  67. package/src/selector/future/file.ts +0 -14
  68. package/src/selector/future/i18n.ts +0 -13
  69. package/src/selector/future/index.ts +0 -169
  70. package/src/selector/future/number.ts +0 -4
  71. package/src/selector/future/object.ts +0 -22
  72. package/src/selector/future/primitive.ts +0 -17
  73. package/src/selector/future/remote.ts +0 -9
  74. package/src/selector/future/selector.test.ts +0 -429
  75. package/src/selector/future/selectorOf.ts +0 -7
  76. package/src/selector/future/string.ts +0 -4
  77. package/src/selector/index.ts +0 -121
  78. package/src/selector/number.ts +0 -4
  79. package/src/selector/object.ts +0 -5
  80. package/src/selector/primitive.ts +0 -4
  81. package/src/selector/string.ts +0 -4
  82. package/src/source/file.ts +0 -45
  83. package/src/source/future/i18n.ts +0 -60
  84. package/src/source/future/remote.ts +0 -54
  85. package/src/source/index.ts +0 -53
  86. package/src/source/link.ts +0 -14
  87. package/src/source/richtext.ts +0 -178
  88. package/src/val/array.ts +0 -10
  89. package/src/val/index.ts +0 -100
  90. package/src/val/object.ts +0 -13
  91. package/src/val/primitive.ts +0 -8
  92. package/tsconfig.json +0 -8
@@ -1,238 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { Path, GenericSelector, GetSource, GetSchema } from "./index";
3
- import { Expr } from "../expr/expr";
4
- import { Schema } from "../schema";
5
- import { convertFileSource } from "../schema/image";
6
- import { Source, SourcePrimitive, VAL_EXTENSION } from "../source";
7
- import { FILE_REF_PROP } from "../source/file";
8
- import { isSerializedVal, SourcePath } from "../val";
9
-
10
- function hasOwn<T extends PropertyKey>(obj: object, prop: T): boolean {
11
- return Object.prototype.hasOwnProperty.call(obj, prop);
12
- }
13
-
14
- function andThen(f: (...args: any[]) => any, source: any, path?: SourcePath) {
15
- if (source) {
16
- return newSelectorProxy(f(newSelectorProxy(source, path)));
17
- }
18
- return newSelectorProxy(source, path);
19
- }
20
-
21
- export function isSelector(source: any): source is GenericSelector<Source> {
22
- return (
23
- typeof source === "object" &&
24
- source !== null &&
25
- (GetSource in source || Path in source)
26
- );
27
- }
28
-
29
- export function newSelectorProxy(
30
- source: any,
31
- path?: SourcePath,
32
- moduleSchema?: any
33
- ): any {
34
- if (typeof source === "object") {
35
- if (isSelector(source)) {
36
- return source;
37
- } else if (isSerializedVal(source)) {
38
- return newSelectorProxy(source.val, source.valPath);
39
- }
40
- }
41
-
42
- if (source && source[FILE_REF_PROP] && source[VAL_EXTENSION] === "file") {
43
- const fileRef = source[FILE_REF_PROP];
44
- if (typeof fileRef !== "string") {
45
- throw Error("Invalid file ref: " + fileRef);
46
- }
47
- return newSelectorProxy(convertFileSource(source), path, moduleSchema);
48
- }
49
-
50
- switch (typeof source) {
51
- case "function":
52
- case "symbol":
53
- throw Error(`Invalid selector type: ${typeof source}: ${source}`);
54
- case "object":
55
- // Handles both objects and arrays!
56
- if (source !== null) {
57
- return new Proxy(source, {
58
- // TODO: see proxy docs if we want more traps
59
- has(target, prop: string | symbol) {
60
- if (prop === GetSource) {
61
- return true;
62
- }
63
- if (prop === Path) {
64
- return true;
65
- }
66
- if (prop === "andThen") {
67
- return true;
68
- }
69
- if (prop === GetSchema) {
70
- return true;
71
- }
72
- return prop in target;
73
- },
74
- get(target, prop: string | symbol) {
75
- if (prop === GetSource) {
76
- return source;
77
- }
78
- if (prop === Path) {
79
- return path;
80
- }
81
- if (prop === GetSchema) {
82
- return moduleSchema;
83
- }
84
- if (prop === "andThen") {
85
- return (f: any) => andThen(f, source, path);
86
- }
87
- if (Array.isArray(target)) {
88
- if (prop === "filter") {
89
- return (f: any) => {
90
- const filtered = target
91
- .map((a, i) =>
92
- newSelectorProxy(
93
- a,
94
- createValPathOfItem(path, i),
95
- moduleSchema?.item
96
- )
97
- )
98
- .filter((a) => {
99
- if (f && f instanceof Schema) {
100
- return f.assert(unValify(a));
101
- } else {
102
- return unValify(f(a));
103
- }
104
- });
105
- return newSelectorProxy(filtered, path, moduleSchema);
106
- };
107
- } else if (prop === "map") {
108
- return (f: any) => {
109
- const filtered = target.map((a, i) => {
110
- const valueOrSelector = f(
111
- newSelectorProxy(
112
- a,
113
- createValPathOfItem(path, i),
114
- moduleSchema?.item
115
- ),
116
- newSelectorProxy(i)
117
- );
118
- if (isSelector(valueOrSelector)) {
119
- return valueOrSelector;
120
- }
121
- return newSelectorProxy(valueOrSelector);
122
- });
123
- return newSelectorProxy(filtered, path, moduleSchema);
124
- };
125
- }
126
- }
127
- if (Array.isArray(target) && prop === "length") {
128
- return newSelectorProxy(target.length);
129
- }
130
- const reflectedValue = Reflect.get(target, prop);
131
-
132
- if (hasOwn(source, prop)) {
133
- if (!Number.isNaN(Number(prop))) {
134
- return newSelectorProxy(
135
- reflectedValue,
136
- createValPathOfItem(path, Number(prop)),
137
- moduleSchema?.item
138
- );
139
- }
140
- return newSelectorProxy(
141
- reflectedValue,
142
- createValPathOfItem(path, prop),
143
- moduleSchema?.items[prop]
144
- );
145
- }
146
- return reflectedValue;
147
- },
148
- });
149
- }
150
- // intentional fallthrough
151
- // eslint-disable-next-line no-fallthrough
152
- default:
153
- return {
154
- eq: (other: SourcePrimitive | GenericSelector<Source>) => {
155
- let otherValue: any = other;
156
- if (isSelector(other)) {
157
- otherValue = other[GetSource];
158
- if (otherValue instanceof Expr) {
159
- throw Error("TODO: Cannot evaluate equality with an Expr");
160
- }
161
- }
162
- return newSelectorProxy(source === otherValue, undefined);
163
- },
164
- andThen: (f: any) => {
165
- return andThen(f, source === undefined ? null : source, path);
166
- },
167
- [GetSource]: source === undefined ? null : source,
168
- [Path]: path,
169
- [GetSchema]: moduleSchema,
170
- };
171
- }
172
- }
173
-
174
- function selectorAsVal(sel: any): any {
175
- if (isSerializedVal(sel)) {
176
- // is a serialized val
177
- return selectorAsVal(newSelectorProxy(sel.val, sel.valPath));
178
- } else if (
179
- typeof sel === "object" &&
180
- sel &&
181
- !(GetSource in sel) &&
182
- !Array.isArray(sel)
183
- ) {
184
- // is object
185
- return Object.fromEntries(
186
- Object.entries(sel).map(([k, v]) => [k, selectorAsVal(v)])
187
- );
188
- } else if (
189
- typeof sel === "object" &&
190
- sel &&
191
- !(GetSource in sel) &&
192
- Array.isArray(sel)
193
- ) {
194
- // is array
195
- return sel.map((v) => selectorAsVal(v));
196
- } else if (
197
- typeof sel === "object" &&
198
- sel &&
199
- (GetSource in sel || Path in sel)
200
- ) {
201
- return selectorAsVal(sel?.[GetSource]);
202
- } else if (sel === undefined) {
203
- return null;
204
- }
205
- return sel;
206
- }
207
-
208
- export function createValPathOfItem(
209
- arrayPath: SourcePath | undefined,
210
- prop: string | number | symbol
211
- ) {
212
- if (typeof prop === "symbol") {
213
- throw Error(
214
- `Cannot create val path of array item with symbol prop: ${prop.toString()}`
215
- );
216
- }
217
- return arrayPath && (`${arrayPath}.${JSON.stringify(prop)}` as SourcePath);
218
- }
219
-
220
- export function selectorToVal(s: any): any {
221
- const v = selectorAsVal(s?.[GetSource]);
222
- return {
223
- val: v,
224
- [Path]: s?.[Path],
225
- };
226
- }
227
-
228
- // TODO: could we do .val on the objects instead?
229
- function unValify(valueOrSelector: any) {
230
- if (
231
- typeof valueOrSelector === "object" &&
232
- (GetSource in valueOrSelector || Path in valueOrSelector)
233
- ) {
234
- const selectorValue = valueOrSelector[GetSource];
235
- return selectorValue;
236
- }
237
- return valueOrSelector;
238
- }
@@ -1,13 +0,0 @@
1
- import { GenericSelector } from ".";
2
- import { Source, SourceArray } from "../source";
3
-
4
- export type UndistributedSourceArray<T extends SourceArray> = [T] extends [
5
- infer U // infer here to avoid Type instantiation is excessively deep and possibly infinite. See: https://github.com/microsoft/TypeScript/issues/30188#issuecomment-478938437. Avoiding infer extends to keep us below TS 4.9 compat
6
- ]
7
- ? U extends Source[]
8
- ? Selector<U>
9
- : never
10
- : never;
11
-
12
- // TODO: docs
13
- export type Selector<T extends SourceArray> = GenericSelector<T>;
@@ -1,4 +0,0 @@
1
- import { Selector as PrimitiveSelector } from "./primitive";
2
-
3
- // TODO: docs
4
- export type Selector<T extends boolean> = PrimitiveSelector<T>;
@@ -1,6 +0,0 @@
1
- import { Selector as UnknownSelector, GenericSelector } from "./index";
2
-
3
- // TODO: docs
4
- export type FileSelector = GenericSelector<{ url: string }> & {
5
- readonly url: UnknownSelector<string>;
6
- };
@@ -1,203 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { convertLiteralProxy, newExprSelectorProxy } from "./ExprProxy";
3
- import * as expr from "../../expr/expr";
4
- import { GenericSelector, SelectorSource, SourceOrExpr } from ".";
5
- import { Source } from "../../source";
6
-
7
- const ExprSelectorTestCases: any[] = [
8
- {
9
- description: "basic module",
10
- input: newExprSelectorProxy<string>(root("/app/foo")),
11
- expected: "(val '/app/foo')",
12
- },
13
- {
14
- description: "basic prop",
15
- input: newExprSelectorProxy<string[]>(root("/app/foo"))[0],
16
- expected: "('0' (val '/app/foo'))",
17
- },
18
- {
19
- description: "noop andThen",
20
- input: newExprSelectorProxy<string>(root("/app/foo")).andThen((v) => v),
21
- expected: "!(andThen (val '/app/foo') @[0,0])",
22
- },
23
- {
24
- description: "noop map",
25
- input: newExprSelectorProxy<string[]>(root("/app/foo")).map((v) => v),
26
- expected: "!(map (val '/app/foo') @[0,0])",
27
- },
28
- {
29
- description: "eq string",
30
- input: newExprSelectorProxy<string>(root("/app/foo")).eq("hei"),
31
- expected: "(eq (val '/app/foo') 'hei')",
32
- },
33
- {
34
- description: "eq undefined",
35
- input: newExprSelectorProxy<null>(root("/app/foo")).eq(null),
36
- expected: "(eq (val '/app/foo') ())",
37
- },
38
- {
39
- description: "eq number",
40
- input: newExprSelectorProxy<number>(root("/app/foo")).eq(1),
41
- expected: "(eq (val '/app/foo') (json '1'))",
42
- },
43
- {
44
- description: "eq boolean",
45
- input: newExprSelectorProxy<boolean>(root("/app/foo")).eq(true),
46
- expected: "(eq (val '/app/foo') (json 'true'))",
47
- },
48
- {
49
- description: "filter string",
50
- input: newExprSelectorProxy<string[]>(root("/app/foo")).filter((v) =>
51
- v.eq("hei")
52
- ),
53
- expected: "!(filter (val '/app/foo') (eq @[0,0] 'hei'))",
54
- },
55
- {
56
- description: "filter number",
57
- input: newExprSelectorProxy<number[]>(root("/app/foo")).filter((v) =>
58
- v.eq(1)
59
- ),
60
- expected: "!(filter (val '/app/foo') (eq @[0,0] (json '1')))",
61
- },
62
- {
63
- description: "filter optional",
64
- input: newExprSelectorProxy<number[]>(root("/app/foo")).filter((v) =>
65
- v.eq(null)
66
- ),
67
- expected: "!(filter (val '/app/foo') (eq @[0,0] ()))",
68
- },
69
- {
70
- description: "basic projection",
71
- input: newExprSelectorProxy<string[]>(root("/app/foo")).map((v) => ({
72
- foo: v,
73
- })),
74
- expected: "!(map (val '/app/foo') (json '{\"foo\": ${@[0,0]}}'))",
75
- },
76
- {
77
- description: "nested projection",
78
- input: newExprSelectorProxy<string[]>(root("/app/foo")).map((v) => ({
79
- foo: {
80
- bar: v,
81
- },
82
- })),
83
- expected:
84
- // TODO: this could be more readable
85
- // Example: "!(map (val '/app/foo') (json '{\"foo\": {\"bar\": {${@[0,0]}}'}'))"
86
- "!(map (val '/app/foo') (json '{\"foo\": ${'{\"bar\": ${@[0,0]}}'}}'))",
87
- },
88
- {
89
- description: "multi module",
90
- input: newExprSelectorProxy<string>(root("/app/foo")).andThen(() =>
91
- newExprSelectorProxy<string[]>(root("/app/bar"))
92
- ),
93
- expected: "!(andThen (val '/app/foo') (val '/app/bar'))",
94
- },
95
- ];
96
-
97
- /**
98
- * Useful test cases for literal conversion
99
- * There somewhat of an overlap between these cases and the selector cases,
100
- * we could prune some of these away if we want to
101
- **/
102
- const LiteralConversionTestCases: {
103
- input: SelectorSource;
104
- expected: string;
105
- description: string;
106
- }[] = [
107
- { description: "basic string", input: "foo", expected: "(json '\"foo\"')" },
108
- { description: "basic number", input: 1, expected: "(json '1')" },
109
- { description: "basic boolean", input: true, expected: "(json 'true')" },
110
- { description: "basic array", input: [1], expected: "(json '[1]')" },
111
- { description: "basic null", input: null, expected: "()" },
112
- {
113
- description: "array with 2 different",
114
- input: [1, "foo"],
115
- expected: "(json '[1, \"foo\"]')",
116
- },
117
- {
118
- description: "array with undefined",
119
- input: [1, null],
120
- expected: "(json '[1, ${()}]')",
121
- },
122
- {
123
- description: "basic object",
124
- input: { foo: "one", bar: 1 },
125
- expected: '(json \'{"foo": "one", "bar": 1}\')',
126
- },
127
- {
128
- description: "nested array",
129
- input: [1, [1]],
130
- expected: "(json '[1, [1]]')",
131
- },
132
- {
133
- description: "nested object",
134
- input: { foo: "one", bar: { zoo: 1 } },
135
- expected: '(json \'{"foo": "one", "bar": {"zoo": 1}}\')',
136
- },
137
- {
138
- description: "nested object with array",
139
- input: {
140
- foo: "one",
141
- bar: { zoo: [1, "inner"] },
142
- },
143
- expected: '(json \'{"foo": "one", "bar": {"zoo": [1, "inner"]}}\')',
144
- },
145
- {
146
- description: "basic interpolation",
147
- input: [1, newExprSelectorProxy<string[]>(root("/app/foo"))],
148
- expected: "(json '[1, ${(val '/app/foo')}]')",
149
- },
150
- {
151
- description: "advanced interpolation",
152
- input: [
153
- 123,
154
- newExprSelectorProxy<string[]>(root("/app/foo")).map((v) =>
155
- v.andThen((a) => ({ bar: a.eq("bar") }))
156
- ),
157
- ],
158
- expected:
159
- "(json '[123, ${!(map (val '/app/foo') !(andThen @[0,0] (json '{\"bar\": ${(eq @[1,0] 'bar')}}')))}]')",
160
- },
161
- {
162
- description: "interpolation with multiple selectors",
163
- input: [
164
- 123,
165
- newExprSelectorProxy<string[]>(root("/app/foo")).map((v) =>
166
- v.andThen((a) => ({
167
- bar: a.eq("bar"),
168
- more: newExprSelectorProxy<string[]>(root("/app/foo")),
169
- }))
170
- ),
171
- ],
172
- expected:
173
- "(json '[123, ${!(map (val '/app/foo') !(andThen @[0,0] (json '{\"bar\": ${(eq @[1,0] 'bar')}, \"more\": ${(val '/app/foo')}}')))}]')",
174
- },
175
- ];
176
-
177
- describe("expr", () => {
178
- test.each(ExprSelectorTestCases)(
179
- 'expr selector ($description): "$expected"',
180
- ({ input, expected }) => {
181
- const valOrExpr = (input as unknown as GenericSelector<Source>)[
182
- SourceOrExpr
183
- ];
184
- if (valOrExpr instanceof expr.Expr) {
185
- expect(valOrExpr.transpile()).toBe(expected);
186
- } else {
187
- expect(valOrExpr).toBe(expect.any(expr.Expr));
188
- }
189
- }
190
- );
191
- test.each(LiteralConversionTestCases)(
192
- 'literal conversion ($description): "$expected"',
193
- ({ input, expected }) =>
194
- expect(convertLiteralProxy(input).transpile()).toBe(expected)
195
- );
196
- });
197
-
198
- function root(sourcePath: string) {
199
- return new expr.Call(
200
- [new expr.Sym("val"), new expr.StringLiteral(sourcePath)],
201
- false
202
- );
203
- }
@@ -1,216 +0,0 @@
1
- import { Selector, SelectorSource, SourceOrExpr } from ".";
2
- import * as expr from "../../expr/expr";
3
- import { Source, SourcePrimitive } from "../../source";
4
-
5
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
- type AnyFun = (...args: any[]) => any;
7
-
8
- export function newExprSelectorProxy<T extends Source>(
9
- root: expr.Expr,
10
- depth = 0
11
- ): Selector<T> {
12
- return new Proxy(new GenericExprSelector(root, depth), {
13
- get: (target, prop) => {
14
- if (prop === SourceOrExpr) {
15
- return target[SourceOrExpr];
16
- }
17
- if (!hasOwn(target, prop)) {
18
- return newExprSelectorProxy(
19
- new expr.Call([new expr.StringLiteral(prop.toString()), root], false),
20
- depth
21
- );
22
- }
23
- return Reflect.get(target, prop);
24
- },
25
- }) as unknown as Selector<T>;
26
- }
27
-
28
- export function root(sourcePath: string) {
29
- return new expr.Call(
30
- [new expr.Sym("val"), new expr.StringLiteral(sourcePath)],
31
- false
32
- );
33
- }
34
-
35
- class GenericExprSelector {
36
- [SourceOrExpr]: expr.Expr;
37
- constructor(private readonly root: expr.Expr, private readonly depth = 0) {
38
- this[SourceOrExpr] = root;
39
- }
40
-
41
- andThen = (f: AnyFun) => {
42
- return genericHigherOrderFunction(this.root, "andThen", f, 1, this.depth);
43
- };
44
-
45
- map = (f: AnyFun) => {
46
- return genericHigherOrderFunction(this.root, "map", f, 2, this.depth);
47
- };
48
-
49
- filter = (f: AnyFun) => {
50
- return genericHigherOrderFunction(this.root, "filter", f, 1, this.depth);
51
- };
52
-
53
- eq = (other: SourcePrimitive) => {
54
- return newExprSelectorProxy(
55
- new expr.Call(
56
- [
57
- new expr.Sym("eq"),
58
- this.root,
59
- typeof other === "string"
60
- ? new expr.StringLiteral(other)
61
- : typeof other === "undefined"
62
- ? expr.NilSym
63
- : convertLiteralProxy(other),
64
- ],
65
- false
66
- ),
67
- this.depth
68
- );
69
- };
70
- }
71
-
72
- function genericHigherOrderFunction(
73
- root: expr.Expr,
74
- name: string,
75
- f: AnyFun,
76
- args: number,
77
- depth: number
78
- ) {
79
- const argsExprs: Selector<Source>[] = [];
80
- for (let i = 0; i < args; i++) {
81
- argsExprs.push(
82
- newExprSelectorProxy(new expr.Sym(`@[${depth},${i}]`), depth + 1)
83
- );
84
- }
85
- return newExprSelectorProxy(
86
- new expr.Call(
87
- [new expr.Sym(name), root, convertLiteralProxy(f(...argsExprs))],
88
- true
89
- ),
90
- depth
91
- );
92
- }
93
-
94
- function hasOwn<T extends PropertyKey>(obj: object, prop: T): boolean {
95
- return Object.prototype.hasOwnProperty.call(obj, prop);
96
- }
97
-
98
- export function convertLiteralProxy(
99
- source: expr.Expr | SelectorSource
100
- ): expr.Expr {
101
- const [convertedLiteral] = convertObjectToStringExpr(source, true);
102
- return withJsonCall(convertedLiteral, false)[0];
103
- }
104
-
105
- /**
106
- * Add a json call if the literal must be parsed
107
- * Happens at the very least at the top level, but may also happen inside e.g. a string template
108
- *
109
- */
110
- function withJsonCall(
111
- e: expr.Expr,
112
- isLiteralScope: boolean
113
- ): [e: expr.Expr, isJson: boolean] {
114
- if (
115
- !isLiteralScope &&
116
- (e instanceof expr.StringLiteral || e instanceof expr.StringTemplate)
117
- ) {
118
- return [new expr.Call([new expr.Sym("json"), e], false), true];
119
- }
120
- return [e, false];
121
- }
122
-
123
- function convertObjectToStringExpr(
124
- source: expr.Expr | SelectorSource,
125
- isLiteralScope: boolean
126
- ): [e: expr.Expr, isJson: boolean] {
127
- if (source === null || source === undefined) {
128
- return [expr.NilSym, true];
129
- } else if (typeof source === "string") {
130
- return [new expr.StringLiteral(JSON.stringify(source)), false];
131
- } else if (typeof source === "number" || typeof source === "boolean") {
132
- return withJsonCall(
133
- new expr.StringLiteral(source.toString()),
134
- isLiteralScope
135
- );
136
- } else if (typeof source === "object" && SourceOrExpr in source) {
137
- const selector = source;
138
- const valOrExpr = selector[SourceOrExpr];
139
- if (valOrExpr instanceof expr.Expr) {
140
- return [valOrExpr, true];
141
- } else {
142
- // use Val literal - UNTESTED - may happen if we are referencing local content
143
- return convertObjectToStringExpr(valOrExpr, isLiteralScope);
144
- }
145
- } else if (source instanceof expr.Expr) {
146
- return [source, true];
147
- } else if (typeof source === "object") {
148
- // source is a literal object or array, might have nested selectors
149
- const isArray = Array.isArray(source);
150
- const entries = isArray
151
- ? Array.from(source.entries())
152
- : Object.entries(source);
153
-
154
- let isStringTemplate = false;
155
- const converted = entries.map(([key, v]) => {
156
- const [converted, mustInterpolate] =
157
- typeof v === "string"
158
- ? withJsonCall(
159
- new expr.StringLiteral(JSON.stringify(v)),
160
- isLiteralScope
161
- )
162
- : convertObjectToStringExpr(v, isLiteralScope);
163
-
164
- isStringTemplate = isStringTemplate || mustInterpolate;
165
- return [key, converted] as const;
166
- });
167
-
168
- if (!isStringTemplate) {
169
- const value = isArray
170
- ? `[${converted.map(([, v]) => getStringLiteralValue(v)).join(", ")}]`
171
- : `{${converted
172
- .map(
173
- ([key, v]) =>
174
- `${JSON.stringify(key)}: ${getStringLiteralValue(v)}`
175
- )
176
- .join(", ")}}`;
177
-
178
- return [new expr.StringLiteral(value), false];
179
- }
180
-
181
- return [
182
- new expr.StringTemplate([
183
- isArray ? new expr.StringLiteral("[") : new expr.StringLiteral("{"),
184
- ...converted.flatMap(([key, entry], i) => {
185
- const convertedEntry =
186
- typeof entry === "string"
187
- ? new expr.StringLiteral(JSON.stringify(entry))
188
- : convertObjectToStringExpr(entry, isLiteralScope)[0];
189
- const maybeComma =
190
- i < converted.length - 1 ? [new expr.StringLiteral(", ")] : [];
191
- if (isArray) {
192
- return [convertedEntry].concat(maybeComma);
193
- } else {
194
- return [
195
- new expr.StringLiteral(JSON.stringify(key) + ": "),
196
- convertedEntry,
197
- ].concat(maybeComma);
198
- }
199
- }),
200
- isArray ? new expr.StringLiteral("]") : new expr.StringLiteral("}"),
201
- ]),
202
- true,
203
- ];
204
- }
205
- throw new Error(
206
- `Unsupported type '${typeof source}': ${JSON.stringify(source)}`
207
- );
208
- }
209
-
210
- function getStringLiteralValue(e: expr.Expr): string {
211
- if (e instanceof expr.StringLiteral) {
212
- return e.value;
213
- } else {
214
- throw new Error(`expected a string literal but found: ${e.transpile()}`);
215
- }
216
- }