@malloydata/malloy-tag 0.0.335 → 0.0.336
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/CONTEXT.md +83 -9
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/peggy/dist/peg-tag-parser.d.ts +11 -0
- package/dist/peggy/dist/peg-tag-parser.js +3130 -0
- package/dist/peggy/dist/peg-tag-parser.js.map +1 -0
- package/dist/peggy/index.d.ts +13 -0
- package/dist/peggy/index.js +117 -0
- package/dist/peggy/index.js.map +1 -0
- package/dist/peggy/interpreter.d.ts +32 -0
- package/dist/peggy/interpreter.js +208 -0
- package/dist/peggy/interpreter.js.map +1 -0
- package/dist/peggy/statements.d.ts +51 -0
- package/dist/peggy/statements.js +7 -0
- package/dist/peggy/statements.js.map +1 -0
- package/dist/schema.d.ts +41 -0
- package/dist/schema.js +573 -0
- package/dist/schema.js.map +1 -0
- package/dist/schema.spec.d.ts +1 -0
- package/dist/schema.spec.js +980 -0
- package/dist/schema.spec.js.map +1 -0
- package/dist/tags.d.ts +144 -37
- package/dist/tags.js +535 -344
- package/dist/tags.js.map +1 -1
- package/dist/tags.spec.js +524 -45
- package/dist/tags.spec.js.map +1 -1
- package/package.json +6 -8
- package/src/index.ts +3 -0
- package/src/motly-schema.motly +52 -0
- package/src/peggy/dist/peg-tag-parser.js +2790 -0
- package/src/peggy/index.ts +89 -0
- package/src/peggy/interpreter.ts +265 -0
- package/src/peggy/malloy-tag.peggy +224 -0
- package/src/peggy/statements.ts +49 -0
- package/src/schema.spec.ts +1280 -0
- package/src/schema.ts +852 -0
- package/src/tags.spec.ts +591 -46
- package/src/tags.ts +597 -398
- package/tsconfig.json +3 -2
- package/dist/lib/Malloy/MalloyTagLexer.d.ts +0 -42
- package/dist/lib/Malloy/MalloyTagLexer.js +0 -395
- package/dist/lib/Malloy/MalloyTagLexer.js.map +0 -1
- package/dist/lib/Malloy/MalloyTagParser.d.ts +0 -180
- package/dist/lib/Malloy/MalloyTagParser.js +0 -1077
- package/dist/lib/Malloy/MalloyTagParser.js.map +0 -1
- package/dist/lib/Malloy/MalloyTagVisitor.d.ts +0 -120
- package/dist/lib/Malloy/MalloyTagVisitor.js +0 -4
- package/dist/lib/Malloy/MalloyTagVisitor.js.map +0 -1
- package/scripts/build_parser.js +0 -98
- package/src/MalloyTag.g4 +0 -104
- package/src/lib/Malloy/MalloyTag.interp +0 -61
- package/src/lib/Malloy/MalloyTag.tokens +0 -32
- package/src/lib/Malloy/MalloyTagLexer.interp +0 -85
- package/src/lib/Malloy/MalloyTagLexer.tokens +0 -32
- package/src/lib/Malloy/MalloyTagLexer.ts +0 -386
- package/src/lib/Malloy/MalloyTagParser.ts +0 -1065
- package/src/lib/Malloy/MalloyTagVisitor.ts +0 -141
- package/src/lib/Malloy/_BUILD_DIGEST_ +0 -1
package/dist/tags.spec.js
CHANGED
|
@@ -23,10 +23,11 @@
|
|
|
23
23
|
*/
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
25
|
const tags_1 = require("./tags");
|
|
26
|
+
const peggy_1 = require("./peggy");
|
|
26
27
|
expect.extend({
|
|
27
28
|
tagsAre(src, result) {
|
|
28
29
|
if (typeof src === 'string') {
|
|
29
|
-
const { tag, log } =
|
|
30
|
+
const { tag, log } = (0, peggy_1.parseTag)(src);
|
|
30
31
|
const errs = log.map(e => e.message);
|
|
31
32
|
if (log.length > 0) {
|
|
32
33
|
return {
|
|
@@ -36,7 +37,7 @@ expect.extend({
|
|
|
36
37
|
}
|
|
37
38
|
src = tag;
|
|
38
39
|
}
|
|
39
|
-
const got = src.properties;
|
|
40
|
+
const got = src.properties ? (0, tags_1.interfaceFromDict)(src.properties) : undefined;
|
|
40
41
|
if (this.equals(got, result)) {
|
|
41
42
|
return {
|
|
42
43
|
pass: true,
|
|
@@ -97,26 +98,19 @@ describe('tagParse to Tag', () => {
|
|
|
97
98
|
['x={y} -x.y', { x: { properties: { y: { deleted: true } } } }],
|
|
98
99
|
['x={y z} -x.y', { x: { properties: { z: {}, y: { deleted: true } } } }],
|
|
99
100
|
['x={y z} x {-y}', { x: { properties: { z: {}, y: { deleted: true } } } }],
|
|
100
|
-
['x=1 x {xx=11}', { x: { eq:
|
|
101
|
-
['x.y=xx x=1 {...}', { x: { eq:
|
|
102
|
-
['a {b c} a=1', { a: { eq:
|
|
103
|
-
['a=1 a=...{b}', { a: { eq:
|
|
104
|
-
[
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
],
|
|
112
|
-
['x=.01', { x: { eq: '.01' } }],
|
|
113
|
-
['x=-7', { x: { eq: '-7' } }],
|
|
114
|
-
['x=7', { x: { eq: '7' } }],
|
|
115
|
-
['x=7.0', { x: { eq: '7.0' } }],
|
|
116
|
-
['x=.7', { x: { eq: '.7' } }],
|
|
117
|
-
['x=.7e2', { x: { eq: '.7e2' } }],
|
|
118
|
-
['x=7E2', { x: { eq: '7E2' } }],
|
|
101
|
+
['x=1 x {xx=11}', { x: { eq: 1, properties: { xx: { eq: 11 } } } }],
|
|
102
|
+
['x.y=xx x=1 {...}', { x: { eq: 1, properties: { y: { eq: 'xx' } } } }],
|
|
103
|
+
['a {b c} a=1', { a: { eq: 1 } }],
|
|
104
|
+
['a=1 a=...{b}', { a: { eq: 1, properties: { b: {} } } }],
|
|
105
|
+
['x=.01', { x: { eq: 0.01 } }],
|
|
106
|
+
['x=-7', { x: { eq: -7 } }],
|
|
107
|
+
['x=7', { x: { eq: 7 } }],
|
|
108
|
+
['x=7.0', { x: { eq: 7.0 } }],
|
|
109
|
+
['x=.7', { x: { eq: 0.7 } }],
|
|
110
|
+
['x=.7e2', { x: { eq: 70 } }],
|
|
111
|
+
['x=7E2', { x: { eq: 700 } }],
|
|
119
112
|
['`spacey name`=Zaphod', { 'spacey name': { eq: 'Zaphod' } }],
|
|
113
|
+
["name='single quoted'", { name: { eq: 'single quoted' } }],
|
|
120
114
|
[
|
|
121
115
|
'image { alt=hello { field=department } }',
|
|
122
116
|
{
|
|
@@ -138,6 +132,57 @@ describe('tagParse to Tag', () => {
|
|
|
138
132
|
},
|
|
139
133
|
],
|
|
140
134
|
['can remove.properties -...', {}],
|
|
135
|
+
// Colon syntax REPLACES properties (deletes old props)
|
|
136
|
+
['name: { prop }', { name: { properties: { prop: {} } } }],
|
|
137
|
+
['name: { a=1 b=2 }', { name: { properties: { a: { eq: 1 }, b: { eq: 2 } } } }],
|
|
138
|
+
['name { old } name: { new }', { name: { properties: { new: {} } } }],
|
|
139
|
+
// Space syntax MERGES properties (keeps old props)
|
|
140
|
+
['name { old } name { new }', { name: { properties: { old: {}, new: {} } } }],
|
|
141
|
+
// Colon and space syntax with dotted paths
|
|
142
|
+
['a.b: { c }', { a: { properties: { b: { properties: { c: {} } } } } }],
|
|
143
|
+
[
|
|
144
|
+
'a.b { c } a.b { d }',
|
|
145
|
+
{ a: { properties: { b: { properties: { c: {}, d: {} } } } } },
|
|
146
|
+
],
|
|
147
|
+
['a.b { c } a.b: { d }', { a: { properties: { b: { properties: { d: {} } } } } }],
|
|
148
|
+
// Colon syntax deletes existing value
|
|
149
|
+
['name=val name: { prop }', { name: { properties: { prop: {} } } }],
|
|
150
|
+
// Space syntax preserves existing value
|
|
151
|
+
['name=val name { prop }', { name: { eq: 'val', properties: { prop: {} } } }],
|
|
152
|
+
// Multi-line input
|
|
153
|
+
[
|
|
154
|
+
'person {\n name="ted"\n age=42\n}',
|
|
155
|
+
{ person: { properties: { name: { eq: 'ted' }, age: { eq: 42 } } } },
|
|
156
|
+
],
|
|
157
|
+
// Triple-quoted strings (multi-line values)
|
|
158
|
+
['desc="""hello"""', { desc: { eq: 'hello' } }],
|
|
159
|
+
['desc="""line one\nline two"""', { desc: { eq: 'line one\nline two' } }],
|
|
160
|
+
['desc="""has " quote"""', { desc: { eq: 'has " quote' } }],
|
|
161
|
+
['desc="""has "" two quotes"""', { desc: { eq: 'has "" two quotes' } }],
|
|
162
|
+
// Boolean values
|
|
163
|
+
['enabled=@true', { enabled: { eq: true } }],
|
|
164
|
+
['disabled=@false', { disabled: { eq: false } }],
|
|
165
|
+
// Date values
|
|
166
|
+
['created=@2024-01-15', { created: { eq: new Date('2024-01-15') } }],
|
|
167
|
+
[
|
|
168
|
+
'updated=@2024-01-15T10:30:00Z',
|
|
169
|
+
{ updated: { eq: new Date('2024-01-15T10:30:00Z') } },
|
|
170
|
+
],
|
|
171
|
+
// Mixed types
|
|
172
|
+
[
|
|
173
|
+
'config { enabled=@true count=42 name=test }',
|
|
174
|
+
{
|
|
175
|
+
config: {
|
|
176
|
+
properties: {
|
|
177
|
+
enabled: { eq: true },
|
|
178
|
+
count: { eq: 42 },
|
|
179
|
+
name: { eq: 'test' },
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
// Arrays with typed values
|
|
185
|
+
['flags=[@true, @false]', { flags: { eq: [{ eq: true }, { eq: false }] } }],
|
|
141
186
|
];
|
|
142
187
|
test.each(tagTests)('tag %s', (expression, expected) => {
|
|
143
188
|
expect(expression).tagsAre(expected);
|
|
@@ -150,7 +195,7 @@ describe('tagParse to Tag', () => {
|
|
|
150
195
|
describe('Tag access', () => {
|
|
151
196
|
test('just text', () => {
|
|
152
197
|
const strToParse = 'a=b';
|
|
153
|
-
const getTags =
|
|
198
|
+
const getTags = (0, peggy_1.parseTag)(strToParse);
|
|
154
199
|
expect(getTags.log).toEqual([]);
|
|
155
200
|
const a = getTags.tag.tag('a');
|
|
156
201
|
expect(a).toBeDefined();
|
|
@@ -158,7 +203,7 @@ describe('Tag access', () => {
|
|
|
158
203
|
});
|
|
159
204
|
test('tag path', () => {
|
|
160
205
|
const strToParse = 'a.b.c.d.e=f';
|
|
161
|
-
const tagParse =
|
|
206
|
+
const tagParse = (0, peggy_1.parseTag)(strToParse);
|
|
162
207
|
expect(tagParse.log).toEqual([]);
|
|
163
208
|
const abcde = tagParse.tag.tag('a', 'b', 'c', 'd', 'e');
|
|
164
209
|
expect(abcde).toBeDefined();
|
|
@@ -166,7 +211,7 @@ describe('Tag access', () => {
|
|
|
166
211
|
});
|
|
167
212
|
test('just array', () => {
|
|
168
213
|
const strToParse = 'a=[b]';
|
|
169
|
-
const getTags =
|
|
214
|
+
const getTags = (0, peggy_1.parseTag)(strToParse);
|
|
170
215
|
expect(getTags.log).toEqual([]);
|
|
171
216
|
const a = getTags.tag.tag('a');
|
|
172
217
|
const aval = a === null || a === void 0 ? void 0 : a.array();
|
|
@@ -178,7 +223,7 @@ describe('Tag access', () => {
|
|
|
178
223
|
});
|
|
179
224
|
test('tag path into array', () => {
|
|
180
225
|
const strToParse = 'a.b.c = [{d=e}]';
|
|
181
|
-
const tagParse =
|
|
226
|
+
const tagParse = (0, peggy_1.parseTag)(strToParse);
|
|
182
227
|
expect(tagParse.log).toEqual([]);
|
|
183
228
|
const abcde = tagParse.tag.tag('a', 'b', 'c', 0, 'd');
|
|
184
229
|
expect(abcde).toBeDefined();
|
|
@@ -186,7 +231,7 @@ describe('Tag access', () => {
|
|
|
186
231
|
});
|
|
187
232
|
test('array as text', () => {
|
|
188
233
|
const strToParse = 'a=[b]';
|
|
189
|
-
const getTags =
|
|
234
|
+
const getTags = (0, peggy_1.parseTag)(strToParse);
|
|
190
235
|
expect(getTags.log).toEqual([]);
|
|
191
236
|
const a = getTags.tag.tag('a');
|
|
192
237
|
expect(a).toBeDefined();
|
|
@@ -194,7 +239,7 @@ describe('Tag access', () => {
|
|
|
194
239
|
});
|
|
195
240
|
test('text as array', () => {
|
|
196
241
|
const strToParse = 'a=b';
|
|
197
|
-
const getTags =
|
|
242
|
+
const getTags = (0, peggy_1.parseTag)(strToParse);
|
|
198
243
|
expect(getTags.log).toEqual([]);
|
|
199
244
|
const a = getTags.tag.tag('a');
|
|
200
245
|
expect(a).toBeDefined();
|
|
@@ -202,7 +247,7 @@ describe('Tag access', () => {
|
|
|
202
247
|
});
|
|
203
248
|
test('just numeric', () => {
|
|
204
249
|
const strToParse = 'a=7';
|
|
205
|
-
const getTags =
|
|
250
|
+
const getTags = (0, peggy_1.parseTag)(strToParse);
|
|
206
251
|
expect(getTags.log).toEqual([]);
|
|
207
252
|
const a = getTags.tag.tag('a');
|
|
208
253
|
expect(a).toBeDefined();
|
|
@@ -212,7 +257,7 @@ describe('Tag access', () => {
|
|
|
212
257
|
});
|
|
213
258
|
test('text as numeric', () => {
|
|
214
259
|
const strToParse = 'a=seven';
|
|
215
|
-
const getTags =
|
|
260
|
+
const getTags = (0, peggy_1.parseTag)(strToParse);
|
|
216
261
|
expect(getTags.log).toEqual([]);
|
|
217
262
|
const a = getTags.tag.tag('a');
|
|
218
263
|
expect(a).toBeDefined();
|
|
@@ -221,7 +266,7 @@ describe('Tag access', () => {
|
|
|
221
266
|
});
|
|
222
267
|
test('array as numeric', () => {
|
|
223
268
|
const strToParse = 'a=[seven]';
|
|
224
|
-
const getTags =
|
|
269
|
+
const getTags = (0, peggy_1.parseTag)(strToParse);
|
|
225
270
|
expect(getTags.log).toEqual([]);
|
|
226
271
|
const a = getTags.tag.tag('a');
|
|
227
272
|
expect(a).toBeDefined();
|
|
@@ -230,7 +275,7 @@ describe('Tag access', () => {
|
|
|
230
275
|
});
|
|
231
276
|
test('full text array', () => {
|
|
232
277
|
const strToParse = 'a=[b,c]';
|
|
233
|
-
const getTags =
|
|
278
|
+
const getTags = (0, peggy_1.parseTag)(strToParse);
|
|
234
279
|
expect(getTags.log).toEqual([]);
|
|
235
280
|
const a = getTags.tag.tag('a');
|
|
236
281
|
expect(a).toBeDefined();
|
|
@@ -239,7 +284,7 @@ describe('Tag access', () => {
|
|
|
239
284
|
});
|
|
240
285
|
test('filtered text array', () => {
|
|
241
286
|
const strToParse = 'a=[b,c,{d}]';
|
|
242
|
-
const getTags =
|
|
287
|
+
const getTags = (0, peggy_1.parseTag)(strToParse);
|
|
243
288
|
expect(getTags.log).toEqual([]);
|
|
244
289
|
const a = getTags.tag.tag('a');
|
|
245
290
|
expect(a).toBeDefined();
|
|
@@ -248,7 +293,7 @@ describe('Tag access', () => {
|
|
|
248
293
|
});
|
|
249
294
|
test('full numeric array', () => {
|
|
250
295
|
const strToParse = 'a=[1,2]';
|
|
251
|
-
const getTags =
|
|
296
|
+
const getTags = (0, peggy_1.parseTag)(strToParse);
|
|
252
297
|
expect(getTags.log).toEqual([]);
|
|
253
298
|
const a = getTags.tag.tag('a');
|
|
254
299
|
expect(a).toBeDefined();
|
|
@@ -257,7 +302,7 @@ describe('Tag access', () => {
|
|
|
257
302
|
});
|
|
258
303
|
test('filtered numeric array', () => {
|
|
259
304
|
const strToParse = 'a=[1,2,three]';
|
|
260
|
-
const getTags =
|
|
305
|
+
const getTags = (0, peggy_1.parseTag)(strToParse);
|
|
261
306
|
expect(getTags.log).toEqual([]);
|
|
262
307
|
const a = getTags.tag.tag('a');
|
|
263
308
|
expect(a).toBeDefined();
|
|
@@ -266,15 +311,51 @@ describe('Tag access', () => {
|
|
|
266
311
|
});
|
|
267
312
|
test('has', () => {
|
|
268
313
|
const strToParse = 'a b.d';
|
|
269
|
-
const getTags =
|
|
314
|
+
const getTags = (0, peggy_1.parseTag)(strToParse);
|
|
270
315
|
expect(getTags.log).toEqual([]);
|
|
271
316
|
expect(getTags.tag.has('a')).toBeTruthy();
|
|
272
317
|
expect(getTags.tag.has('b', 'd')).toBeTruthy();
|
|
273
318
|
expect(getTags.tag.has('c')).toBeFalsy();
|
|
274
319
|
});
|
|
320
|
+
test('boolean accessor', () => {
|
|
321
|
+
const { tag } = (0, peggy_1.parseTag)('enabled=@true disabled=@false');
|
|
322
|
+
expect(tag.boolean('enabled')).toBe(true);
|
|
323
|
+
expect(tag.boolean('disabled')).toBe(false);
|
|
324
|
+
expect(tag.boolean('missing')).toBeUndefined();
|
|
325
|
+
});
|
|
326
|
+
test('isTrue and isFalse', () => {
|
|
327
|
+
const { tag } = (0, peggy_1.parseTag)('enabled=@true disabled=@false name=test');
|
|
328
|
+
expect(tag.isTrue('enabled')).toBe(true);
|
|
329
|
+
expect(tag.isFalse('enabled')).toBe(false);
|
|
330
|
+
expect(tag.isTrue('disabled')).toBe(false);
|
|
331
|
+
expect(tag.isFalse('disabled')).toBe(true);
|
|
332
|
+
// Non-boolean values
|
|
333
|
+
expect(tag.isTrue('name')).toBe(false);
|
|
334
|
+
expect(tag.isFalse('name')).toBe(false);
|
|
335
|
+
// Missing values
|
|
336
|
+
expect(tag.isTrue('missing')).toBe(false);
|
|
337
|
+
expect(tag.isFalse('missing')).toBe(false);
|
|
338
|
+
});
|
|
339
|
+
test('date accessor', () => {
|
|
340
|
+
const { tag } = (0, peggy_1.parseTag)('created=@2024-01-15 updated=@2024-01-15T10:30:00Z');
|
|
341
|
+
const created = tag.date('created');
|
|
342
|
+
expect(created).toBeInstanceOf(Date);
|
|
343
|
+
expect(created === null || created === void 0 ? void 0 : created.toISOString()).toBe('2024-01-15T00:00:00.000Z');
|
|
344
|
+
const updated = tag.date('updated');
|
|
345
|
+
expect(updated).toBeInstanceOf(Date);
|
|
346
|
+
expect(updated === null || updated === void 0 ? void 0 : updated.toISOString()).toBe('2024-01-15T10:30:00.000Z');
|
|
347
|
+
expect(tag.date('missing')).toBeUndefined();
|
|
348
|
+
});
|
|
349
|
+
test('text returns string for all scalar types', () => {
|
|
350
|
+
const { tag } = (0, peggy_1.parseTag)('n=42 b=@true d=@2024-01-15 s=hello');
|
|
351
|
+
expect(tag.text('n')).toBe('42');
|
|
352
|
+
expect(tag.text('b')).toBe('true');
|
|
353
|
+
expect(tag.text('d')).toBe('2024-01-15T00:00:00.000Z');
|
|
354
|
+
expect(tag.text('s')).toBe('hello');
|
|
355
|
+
});
|
|
275
356
|
test('property access on existing tag (which does not yet have properties)', () => {
|
|
276
|
-
const parsePlot =
|
|
277
|
-
const parsed =
|
|
357
|
+
const parsePlot = (0, peggy_1.parseTag)('# plot');
|
|
358
|
+
const parsed = (0, peggy_1.parseTag)('# plot.x=2', parsePlot.tag);
|
|
278
359
|
const allTags = parsed.tag;
|
|
279
360
|
const plotTag = allTags.tag('plot');
|
|
280
361
|
const xTag = plotTag.tag('x');
|
|
@@ -287,7 +368,7 @@ describe('Tag access', () => {
|
|
|
287
368
|
const base = tags_1.Tag.withPrefix('# ');
|
|
288
369
|
const ext = base.set(['a', 'b', 0], 3).set(['a', 'b', 1], 4);
|
|
289
370
|
expect(ext).tagsAre({
|
|
290
|
-
a: { properties: { b: { eq: [{ eq:
|
|
371
|
+
a: { properties: { b: { eq: [{ eq: 3 }, { eq: 4 }] } } },
|
|
291
372
|
});
|
|
292
373
|
expect(ext.toString()).toBe('# a.b = [3, 4]\n');
|
|
293
374
|
});
|
|
@@ -298,13 +379,13 @@ describe('Tag access', () => {
|
|
|
298
379
|
.set(['c', 0], 'foo')
|
|
299
380
|
.set(['c', 0, 'a'], 4);
|
|
300
381
|
expect(ext).tagsAre({
|
|
301
|
-
a: { properties: { b: { eq: [{ properties: { a: { eq:
|
|
302
|
-
c: { eq: [{ eq: 'foo', properties: { a: { eq:
|
|
382
|
+
a: { properties: { b: { eq: [{ properties: { a: { eq: 3 } } }] } } },
|
|
383
|
+
c: { eq: [{ eq: 'foo', properties: { a: { eq: 4 } } }] },
|
|
303
384
|
});
|
|
304
385
|
expect(ext.toString()).toBe('# a.b = [{ a = 3 }] c = [foo { a = 4 }]\n');
|
|
305
386
|
});
|
|
306
387
|
test('soft remove', () => {
|
|
307
|
-
const base =
|
|
388
|
+
const base = (0, peggy_1.parseTag)('# a.b.c = [{ d = 1 }]').tag;
|
|
308
389
|
const ext = base.delete('a', 'b', 'c', 0, 'd').delete('a', 'b', 'c', 0);
|
|
309
390
|
expect(ext).tagsAre({
|
|
310
391
|
a: { properties: { b: { properties: { c: { eq: [] } } } } },
|
|
@@ -312,7 +393,7 @@ describe('Tag access', () => {
|
|
|
312
393
|
expect(ext.toString()).toBe('# a.b.c = []\n');
|
|
313
394
|
});
|
|
314
395
|
test('hard remove', () => {
|
|
315
|
-
const base =
|
|
396
|
+
const base = (0, peggy_1.parseTag)('# hello').tag;
|
|
316
397
|
const ext = base.unset('goodbye').unset('a', 'dieu');
|
|
317
398
|
expect(ext).tagsAre({
|
|
318
399
|
hello: {},
|
|
@@ -367,7 +448,7 @@ describe('Tag access', () => {
|
|
|
367
448
|
const base = tags_1.Tag.withPrefix('#(malloy) ');
|
|
368
449
|
const ext = base.set(['value'], '\n');
|
|
369
450
|
expect(ext.toString()).toBe('#(malloy) value = "\\n"\n');
|
|
370
|
-
expect(
|
|
451
|
+
expect(ext.text('value')).toBe('\n');
|
|
371
452
|
idempotent(ext);
|
|
372
453
|
});
|
|
373
454
|
test('value has a double quote', () => {
|
|
@@ -376,7 +457,7 @@ describe('Tag access', () => {
|
|
|
376
457
|
expect(ext.toString()).toBe('#(malloy) value = "\\""\n');
|
|
377
458
|
idempotent(ext);
|
|
378
459
|
});
|
|
379
|
-
test
|
|
460
|
+
test('value is empty string', () => {
|
|
380
461
|
const base = tags_1.Tag.withPrefix('#(malloy) ');
|
|
381
462
|
const ext = base.set(['value'], '');
|
|
382
463
|
expect(ext.toString()).toBe('#(malloy) value = ""\n');
|
|
@@ -395,11 +476,409 @@ describe('Tag access', () => {
|
|
|
395
476
|
idempotent(ext);
|
|
396
477
|
});
|
|
397
478
|
});
|
|
479
|
+
describe('parsing escape sequences in strings', () => {
|
|
480
|
+
test('\\n becomes newline', () => {
|
|
481
|
+
const { tag } = (0, peggy_1.parseTag)('x="hello\\nworld"');
|
|
482
|
+
expect(tag.text('x')).toBe('hello\nworld');
|
|
483
|
+
});
|
|
484
|
+
test('\\t becomes tab', () => {
|
|
485
|
+
const { tag } = (0, peggy_1.parseTag)('x="hello\\tworld"');
|
|
486
|
+
expect(tag.text('x')).toBe('hello\tworld');
|
|
487
|
+
});
|
|
488
|
+
test('\\r becomes carriage return', () => {
|
|
489
|
+
const { tag } = (0, peggy_1.parseTag)('x="hello\\rworld"');
|
|
490
|
+
expect(tag.text('x')).toBe('hello\rworld');
|
|
491
|
+
});
|
|
492
|
+
test('\\b becomes backspace', () => {
|
|
493
|
+
const { tag } = (0, peggy_1.parseTag)('x="hello\\bworld"');
|
|
494
|
+
expect(tag.text('x')).toBe('hello\bworld');
|
|
495
|
+
});
|
|
496
|
+
test('\\f becomes form feed', () => {
|
|
497
|
+
const { tag } = (0, peggy_1.parseTag)('x="hello\\fworld"');
|
|
498
|
+
expect(tag.text('x')).toBe('hello\fworld');
|
|
499
|
+
});
|
|
500
|
+
test('\\uXXXX becomes unicode character', () => {
|
|
501
|
+
const { tag } = (0, peggy_1.parseTag)('x="hello\\u0026world"');
|
|
502
|
+
expect(tag.text('x')).toBe('hello&world');
|
|
503
|
+
});
|
|
504
|
+
test('\\uXXXX with uppercase hex', () => {
|
|
505
|
+
const { tag } = (0, peggy_1.parseTag)('x="\\u003F"');
|
|
506
|
+
expect(tag.text('x')).toBe('?');
|
|
507
|
+
});
|
|
508
|
+
test('\\\\ becomes backslash', () => {
|
|
509
|
+
const { tag } = (0, peggy_1.parseTag)('x="hello\\\\world"');
|
|
510
|
+
expect(tag.text('x')).toBe('hello\\world');
|
|
511
|
+
});
|
|
512
|
+
test('\\" becomes double quote', () => {
|
|
513
|
+
const { tag } = (0, peggy_1.parseTag)('x="hello\\"world"');
|
|
514
|
+
expect(tag.text('x')).toBe('hello"world');
|
|
515
|
+
});
|
|
516
|
+
test("\\' in single quoted string", () => {
|
|
517
|
+
const { tag } = (0, peggy_1.parseTag)("x='hello\\'world'");
|
|
518
|
+
expect(tag.text('x')).toBe("hello'world");
|
|
519
|
+
});
|
|
520
|
+
test('\\` in backtick identifier', () => {
|
|
521
|
+
const { tag } = (0, peggy_1.parseTag)('`hello\\`world`=value');
|
|
522
|
+
expect(tag.text('hello`world')).toBe('value');
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
describe('Tag prefix handling', () => {
|
|
527
|
+
test('# prefix skips to first space', () => {
|
|
528
|
+
const { tag, log } = (0, peggy_1.parseTag)('# name=value');
|
|
529
|
+
expect(log).toEqual([]);
|
|
530
|
+
expect(tag.text('name')).toEqual('value');
|
|
531
|
+
});
|
|
532
|
+
test('#(docs) prefix skips to first space', () => {
|
|
533
|
+
const { tag, log } = (0, peggy_1.parseTag)('#(docs) name=value');
|
|
534
|
+
expect(log).toEqual([]);
|
|
535
|
+
expect(tag.text('name')).toEqual('value');
|
|
536
|
+
});
|
|
537
|
+
test('# with no space returns empty tag', () => {
|
|
538
|
+
const { tag, log } = (0, peggy_1.parseTag)('#noSpace');
|
|
539
|
+
expect(log).toEqual([]);
|
|
540
|
+
expect(tag.properties).toBeUndefined();
|
|
541
|
+
});
|
|
542
|
+
test('everything after # on same line is ignored (comment behavior)', () => {
|
|
543
|
+
// When parsing a single tag line, # at start means "skip prefix"
|
|
544
|
+
// The rest of the line after the space is the tag content
|
|
545
|
+
const { tag, log } = (0, peggy_1.parseTag)('# name=value # this is not a comment');
|
|
546
|
+
expect(log).toEqual([]);
|
|
547
|
+
// The "# this is not a comment" is parsed as tag content, not ignored
|
|
548
|
+
// because single-line parsing doesn't have comment support
|
|
549
|
+
expect(tag.has('name')).toBe(true);
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
describe('Empty and whitespace input', () => {
|
|
553
|
+
test('empty string produces empty tag', () => {
|
|
554
|
+
const { tag, log } = (0, peggy_1.parseTag)('');
|
|
555
|
+
expect(log).toEqual([]);
|
|
556
|
+
expect(tag.properties).toBeUndefined();
|
|
557
|
+
});
|
|
558
|
+
test('whitespace only produces empty tag', () => {
|
|
559
|
+
const { tag, log } = (0, peggy_1.parseTag)(' ');
|
|
560
|
+
expect(log).toEqual([]);
|
|
561
|
+
expect(tag.properties).toBeUndefined();
|
|
562
|
+
});
|
|
563
|
+
test('whitespace with comment produces empty tag', () => {
|
|
564
|
+
const { tag, log } = (0, peggy_1.parseTag)(' # this is a comment');
|
|
565
|
+
expect(log).toEqual([]);
|
|
566
|
+
expect(tag.properties).toBeUndefined();
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
describe('Error handling', () => {
|
|
570
|
+
test('syntax error has 0-based line and offset', () => {
|
|
571
|
+
const { log } = (0, peggy_1.parseTag)('a = [');
|
|
572
|
+
expect(log.length).toBe(1);
|
|
573
|
+
expect(log[0].code).toBe('tag-parse-syntax-error');
|
|
574
|
+
expect(log[0].line).toBe(0);
|
|
575
|
+
// Error at position 5 (after "a = [")
|
|
576
|
+
expect(log[0].offset).toBeGreaterThan(0);
|
|
577
|
+
});
|
|
578
|
+
test('error offset accounts for input position', () => {
|
|
579
|
+
const { log } = (0, peggy_1.parseTag)('valid another_valid oops=');
|
|
580
|
+
expect(log.length).toBe(1);
|
|
581
|
+
expect(log[0].line).toBe(0);
|
|
582
|
+
// Error should be near end of line
|
|
583
|
+
expect(log[0].offset).toBeGreaterThan(20);
|
|
584
|
+
});
|
|
585
|
+
test('error offset after prefix stripping', () => {
|
|
586
|
+
// "# " is stripped, so input becomes " a = ["
|
|
587
|
+
const { log } = (0, peggy_1.parseTag)('# a = [');
|
|
588
|
+
expect(log.length).toBe(1);
|
|
589
|
+
expect(log[0].line).toBe(0);
|
|
590
|
+
// Offset is relative to stripped input (after "#")
|
|
591
|
+
expect(log[0].offset).toBeGreaterThan(0);
|
|
592
|
+
});
|
|
593
|
+
test('longer prefix is stripped correctly', () => {
|
|
594
|
+
// "#(docs) " is stripped
|
|
595
|
+
const { log } = (0, peggy_1.parseTag)('#(docs) a = [');
|
|
596
|
+
expect(log.length).toBe(1);
|
|
597
|
+
expect(log[0].line).toBe(0);
|
|
598
|
+
// Offset is relative to stripped input (after "#(docs)")
|
|
599
|
+
expect(log[0].offset).toBeGreaterThan(0);
|
|
600
|
+
});
|
|
601
|
+
test('error on second line reports correct line number', () => {
|
|
602
|
+
// Error is on line 1 (0-based), the unclosed bracket
|
|
603
|
+
const { log } = (0, peggy_1.parseTag)('valid=1\ninvalid=[');
|
|
604
|
+
expect(log.length).toBe(1);
|
|
605
|
+
expect(log[0].line).toBe(1);
|
|
606
|
+
expect(log[0].offset).toBeGreaterThan(0);
|
|
607
|
+
});
|
|
608
|
+
test('unclosed string with newline produces error', () => {
|
|
609
|
+
// Regular strings cannot contain raw newlines - must close on same line
|
|
610
|
+
const { log } = (0, peggy_1.parseTag)('desc="forgot to close\n');
|
|
611
|
+
expect(log.length).toBe(1);
|
|
612
|
+
expect(log[0].line).toBe(0);
|
|
613
|
+
});
|
|
398
614
|
});
|
|
399
615
|
function idempotent(tag) {
|
|
400
616
|
const str = tag.toString();
|
|
401
|
-
const clone =
|
|
617
|
+
const clone = (0, peggy_1.parseTag)(str).tag;
|
|
402
618
|
clone.prefix = tag.prefix;
|
|
403
619
|
expect(clone.toString()).toBe(str);
|
|
404
620
|
}
|
|
621
|
+
describe('toObject', () => {
|
|
622
|
+
test('bare tag becomes true', () => {
|
|
623
|
+
const { tag } = (0, peggy_1.parseTag)('hidden');
|
|
624
|
+
expect(tag.toObject()).toEqual({ hidden: true });
|
|
625
|
+
});
|
|
626
|
+
test('string value becomes string', () => {
|
|
627
|
+
const { tag } = (0, peggy_1.parseTag)('color=blue');
|
|
628
|
+
expect(tag.toObject()).toEqual({ color: 'blue' });
|
|
629
|
+
});
|
|
630
|
+
test('numeric value becomes number', () => {
|
|
631
|
+
const { tag } = (0, peggy_1.parseTag)('size=10');
|
|
632
|
+
expect(tag.toObject()).toEqual({ size: 10 });
|
|
633
|
+
});
|
|
634
|
+
test('float value becomes number', () => {
|
|
635
|
+
const { tag } = (0, peggy_1.parseTag)('ratio=3.14');
|
|
636
|
+
expect(tag.toObject()).toEqual({ ratio: 3.14 });
|
|
637
|
+
});
|
|
638
|
+
test('properties only becomes nested object', () => {
|
|
639
|
+
const { tag } = (0, peggy_1.parseTag)('box { width=100 height=200 }');
|
|
640
|
+
expect(tag.toObject()).toEqual({ box: { width: 100, height: 200 } });
|
|
641
|
+
});
|
|
642
|
+
test('value and properties uses = key', () => {
|
|
643
|
+
const { tag } = (0, peggy_1.parseTag)('link="http://example.com" { target=_blank }');
|
|
644
|
+
expect(tag.toObject()).toEqual({
|
|
645
|
+
link: { '=': 'http://example.com', 'target': '_blank' },
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
test('array of simple values', () => {
|
|
649
|
+
const { tag } = (0, peggy_1.parseTag)('items=[a, b, c]');
|
|
650
|
+
expect(tag.toObject()).toEqual({ items: ['a', 'b', 'c'] });
|
|
651
|
+
});
|
|
652
|
+
test('array of numeric values', () => {
|
|
653
|
+
const { tag } = (0, peggy_1.parseTag)('nums=[1, 2, 3]');
|
|
654
|
+
expect(tag.toObject()).toEqual({ nums: [1, 2, 3] });
|
|
655
|
+
});
|
|
656
|
+
test('array with properties on elements', () => {
|
|
657
|
+
const { tag } = (0, peggy_1.parseTag)('items=[a { x=1 }, b { y=2 }]');
|
|
658
|
+
expect(tag.toObject()).toEqual({
|
|
659
|
+
items: [
|
|
660
|
+
{ '=': 'a', 'x': 1 },
|
|
661
|
+
{ '=': 'b', 'y': 2 },
|
|
662
|
+
],
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
test('complex nested structure', () => {
|
|
666
|
+
const { tag } = (0, peggy_1.parseTag)('# hidden color=blue size=10 box { width=100 }');
|
|
667
|
+
expect(tag.toObject()).toEqual({
|
|
668
|
+
hidden: true,
|
|
669
|
+
color: 'blue',
|
|
670
|
+
size: 10,
|
|
671
|
+
box: { width: 100 },
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
test('deleted properties are excluded', () => {
|
|
675
|
+
const { tag } = (0, peggy_1.parseTag)('a b -a');
|
|
676
|
+
expect(tag.toObject()).toEqual({ b: true });
|
|
677
|
+
});
|
|
678
|
+
test('empty tag returns empty object', () => {
|
|
679
|
+
const { tag } = (0, peggy_1.parseTag)('');
|
|
680
|
+
expect(tag.toObject()).toEqual({});
|
|
681
|
+
});
|
|
682
|
+
test('deeply nested properties', () => {
|
|
683
|
+
const { tag } = (0, peggy_1.parseTag)('a.b.c=1');
|
|
684
|
+
expect(tag.toObject()).toEqual({ a: { b: { c: 1 } } });
|
|
685
|
+
});
|
|
686
|
+
test('array of objects (dictionaries)', () => {
|
|
687
|
+
const { tag } = (0, peggy_1.parseTag)('items=[{name=alice age=30}, {name=bob age=25}]');
|
|
688
|
+
expect(tag.toObject()).toEqual({
|
|
689
|
+
items: [
|
|
690
|
+
{ name: 'alice', age: 30 },
|
|
691
|
+
{ name: 'bob', age: 25 },
|
|
692
|
+
],
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
describe('Tag parent tracking', () => {
|
|
697
|
+
test('root tag has no parent', () => {
|
|
698
|
+
const { tag } = (0, peggy_1.parseTag)('a=1');
|
|
699
|
+
expect(tag.parent).toBeUndefined();
|
|
700
|
+
expect(tag.root).toBe(tag);
|
|
701
|
+
});
|
|
702
|
+
test('child tag has parent set', () => {
|
|
703
|
+
const { tag } = (0, peggy_1.parseTag)('a { b=1 }');
|
|
704
|
+
const a = tag.tag('a');
|
|
705
|
+
expect(a === null || a === void 0 ? void 0 : a.parent).toBe(tag);
|
|
706
|
+
});
|
|
707
|
+
test('nested child has correct parent chain', () => {
|
|
708
|
+
const { tag } = (0, peggy_1.parseTag)('a { b { c=1 } }');
|
|
709
|
+
const a = tag.tag('a');
|
|
710
|
+
const b = a === null || a === void 0 ? void 0 : a.tag('b');
|
|
711
|
+
const c = b === null || b === void 0 ? void 0 : b.tag('c');
|
|
712
|
+
expect(a === null || a === void 0 ? void 0 : a.parent).toBe(tag);
|
|
713
|
+
expect(b === null || b === void 0 ? void 0 : b.parent).toBe(a);
|
|
714
|
+
expect(c === null || c === void 0 ? void 0 : c.parent).toBe(b);
|
|
715
|
+
});
|
|
716
|
+
test('root traverses to top of tree', () => {
|
|
717
|
+
const { tag } = (0, peggy_1.parseTag)('a { b { c=1 } }');
|
|
718
|
+
const c = tag.tag('a', 'b', 'c');
|
|
719
|
+
expect(c === null || c === void 0 ? void 0 : c.root).toBe(tag);
|
|
720
|
+
});
|
|
721
|
+
test('array elements have parent set to containing tag', () => {
|
|
722
|
+
const { tag } = (0, peggy_1.parseTag)('items=[a, b, c]');
|
|
723
|
+
const items = tag.tag('items');
|
|
724
|
+
const arr = items === null || items === void 0 ? void 0 : items.array();
|
|
725
|
+
expect(arr === null || arr === void 0 ? void 0 : arr[0].parent).toBe(items);
|
|
726
|
+
expect(arr === null || arr === void 0 ? void 0 : arr[1].parent).toBe(items);
|
|
727
|
+
expect(arr === null || arr === void 0 ? void 0 : arr[2].parent).toBe(items);
|
|
728
|
+
});
|
|
729
|
+
test('nested array elements have correct parents', () => {
|
|
730
|
+
const { tag } = (0, peggy_1.parseTag)('items=[{name=alice}, {name=bob}]');
|
|
731
|
+
const items = tag.tag('items');
|
|
732
|
+
const arr = items === null || items === void 0 ? void 0 : items.array();
|
|
733
|
+
const alice = arr === null || arr === void 0 ? void 0 : arr[0];
|
|
734
|
+
const name = alice === null || alice === void 0 ? void 0 : alice.tag('name');
|
|
735
|
+
expect(alice === null || alice === void 0 ? void 0 : alice.parent).toBe(items);
|
|
736
|
+
expect(name === null || name === void 0 ? void 0 : name.parent).toBe(alice);
|
|
737
|
+
});
|
|
738
|
+
test('dict accessor returns tags with correct parent', () => {
|
|
739
|
+
const { tag } = (0, peggy_1.parseTag)('a=1 b=2 c=3');
|
|
740
|
+
const dict = tag.dict;
|
|
741
|
+
expect(dict['a'].parent).toBe(tag);
|
|
742
|
+
expect(dict['b'].parent).toBe(tag);
|
|
743
|
+
expect(dict['c'].parent).toBe(tag);
|
|
744
|
+
});
|
|
745
|
+
test('entries iterator returns tags with correct parent', () => {
|
|
746
|
+
const { tag } = (0, peggy_1.parseTag)('a=1 b=2');
|
|
747
|
+
for (const [, child] of tag.entries()) {
|
|
748
|
+
expect(child.parent).toBe(tag);
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
describe('References (RefTag)', () => {
|
|
753
|
+
test('absolute reference resolves to root property', () => {
|
|
754
|
+
const { tag } = (0, peggy_1.parseTag)('source=hello target=$source');
|
|
755
|
+
expect(tag.text('target')).toBe('hello');
|
|
756
|
+
});
|
|
757
|
+
test('absolute reference with path resolves correctly', () => {
|
|
758
|
+
const { tag } = (0, peggy_1.parseTag)('config { db { host=localhost } } target=$config.db.host');
|
|
759
|
+
expect(tag.text('target')).toBe('localhost');
|
|
760
|
+
});
|
|
761
|
+
test('relative reference up one level', () => {
|
|
762
|
+
const { tag } = (0, peggy_1.parseTag)('outer { value=42 inner { ref=$^value } }');
|
|
763
|
+
expect(tag.numeric('outer', 'inner', 'ref')).toBe(42);
|
|
764
|
+
});
|
|
765
|
+
test('relative reference up two levels', () => {
|
|
766
|
+
const { tag } = (0, peggy_1.parseTag)('root=hello outer { inner { ref=$^^root } }');
|
|
767
|
+
expect(tag.text('outer', 'inner', 'ref')).toBe('hello');
|
|
768
|
+
});
|
|
769
|
+
test('reference with array index', () => {
|
|
770
|
+
const { tag } = (0, peggy_1.parseTag)('items=[first, second, third] target=$items[1]');
|
|
771
|
+
expect(tag.text('target')).toBe('second');
|
|
772
|
+
});
|
|
773
|
+
test('reference in array', () => {
|
|
774
|
+
const { tag } = (0, peggy_1.parseTag)('source=value refs=[$source, $source]');
|
|
775
|
+
const arr = tag.textArray('refs');
|
|
776
|
+
expect(arr).toEqual(['value', 'value']);
|
|
777
|
+
});
|
|
778
|
+
test('unresolved reference returns undefined', () => {
|
|
779
|
+
const { tag } = (0, peggy_1.parseTag)('ref=$nonexistent');
|
|
780
|
+
expect(tag.text('ref')).toBeUndefined();
|
|
781
|
+
});
|
|
782
|
+
test('RefTag.toRefString() returns source representation', () => {
|
|
783
|
+
const { tag } = (0, peggy_1.parseTag)('ref=$path.to.thing');
|
|
784
|
+
const ref = tag.tag('ref');
|
|
785
|
+
expect(ref).toBeInstanceOf(tags_1.RefTag);
|
|
786
|
+
expect(ref.toRefString()).toBe('$path.to.thing');
|
|
787
|
+
});
|
|
788
|
+
test('RefTag.toRefString() with ups', () => {
|
|
789
|
+
const { tag } = (0, peggy_1.parseTag)('a { ref=$^^root.path }');
|
|
790
|
+
const ref = tag.tag('a', 'ref');
|
|
791
|
+
expect(ref).toBeInstanceOf(tags_1.RefTag);
|
|
792
|
+
expect(ref.toRefString()).toBe('$^^root.path');
|
|
793
|
+
});
|
|
794
|
+
test('RefTag.toRefString() with array index', () => {
|
|
795
|
+
const { tag } = (0, peggy_1.parseTag)('ref=$items[0].name');
|
|
796
|
+
const ref = tag.tag('ref');
|
|
797
|
+
expect(ref).toBeInstanceOf(tags_1.RefTag);
|
|
798
|
+
expect(ref.toRefString()).toBe('$items[0].name');
|
|
799
|
+
});
|
|
800
|
+
test('chained reference access', () => {
|
|
801
|
+
const { tag } = (0, peggy_1.parseTag)('data { name=alice age=30 } ref=$data');
|
|
802
|
+
expect(tag.text('ref', 'name')).toBe('alice');
|
|
803
|
+
expect(tag.numeric('ref', 'age')).toBe(30);
|
|
804
|
+
});
|
|
805
|
+
test('reference has correct parent', () => {
|
|
806
|
+
const { tag } = (0, peggy_1.parseTag)('outer { ref=$something }');
|
|
807
|
+
const outer = tag.tag('outer');
|
|
808
|
+
const ref = outer === null || outer === void 0 ? void 0 : outer.tag('ref');
|
|
809
|
+
expect(ref === null || ref === void 0 ? void 0 : ref.parent).toBe(outer);
|
|
810
|
+
});
|
|
811
|
+
describe('validateReferences', () => {
|
|
812
|
+
test('no errors for valid references', () => {
|
|
813
|
+
const { tag } = (0, peggy_1.parseTag)('source=hello target=$source');
|
|
814
|
+
expect(tag.validateReferences()).toEqual([]);
|
|
815
|
+
});
|
|
816
|
+
test('error for unresolved reference', () => {
|
|
817
|
+
const { tag } = (0, peggy_1.parseTag)('ref=$nonexistent');
|
|
818
|
+
const errors = tag.validateReferences();
|
|
819
|
+
expect(errors).toHaveLength(1);
|
|
820
|
+
expect(errors[0]).toContain('Unresolved reference');
|
|
821
|
+
expect(errors[0]).toContain('$nonexistent');
|
|
822
|
+
});
|
|
823
|
+
test('error for unresolved nested reference', () => {
|
|
824
|
+
const { tag } = (0, peggy_1.parseTag)('outer { inner { ref=$missing } }');
|
|
825
|
+
const errors = tag.validateReferences();
|
|
826
|
+
expect(errors).toHaveLength(1);
|
|
827
|
+
expect(errors[0]).toContain('outer.inner.ref');
|
|
828
|
+
});
|
|
829
|
+
test('error for reference that goes up too far', () => {
|
|
830
|
+
const { tag } = (0, peggy_1.parseTag)('ref=$^^^^^way.too.far');
|
|
831
|
+
const errors = tag.validateReferences();
|
|
832
|
+
expect(errors).toHaveLength(1);
|
|
833
|
+
});
|
|
834
|
+
test('multiple unresolved references', () => {
|
|
835
|
+
const { tag } = (0, peggy_1.parseTag)('a=$missing1 b=$missing2');
|
|
836
|
+
const errors = tag.validateReferences();
|
|
837
|
+
expect(errors).toHaveLength(2);
|
|
838
|
+
});
|
|
839
|
+
});
|
|
840
|
+
describe('toJSON for references', () => {
|
|
841
|
+
test('RefTag serializes to linkTo marker', () => {
|
|
842
|
+
const { tag } = (0, peggy_1.parseTag)('ref=$path.to.thing');
|
|
843
|
+
const ref = tag.tag('ref');
|
|
844
|
+
expect(ref === null || ref === void 0 ? void 0 : ref.toJSON()).toEqual({ linkTo: '$path.to.thing' });
|
|
845
|
+
});
|
|
846
|
+
test('RefTag with ups serializes correctly', () => {
|
|
847
|
+
const { tag } = (0, peggy_1.parseTag)('a { ref=$^^root }');
|
|
848
|
+
const ref = tag.tag('a', 'ref');
|
|
849
|
+
expect(ref === null || ref === void 0 ? void 0 : ref.toJSON()).toEqual({ linkTo: '$^^root' });
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
describe('toObject with references', () => {
|
|
853
|
+
test('reference resolves to actual value', () => {
|
|
854
|
+
const { tag } = (0, peggy_1.parseTag)('source=hello target=$source');
|
|
855
|
+
const obj = tag.toObject();
|
|
856
|
+
expect(obj['target']).toBe('hello');
|
|
857
|
+
});
|
|
858
|
+
test('reference to object resolves correctly', () => {
|
|
859
|
+
const { tag } = (0, peggy_1.parseTag)('data { name=alice } ref=$data');
|
|
860
|
+
const obj = tag.toObject();
|
|
861
|
+
expect(obj['ref']).toEqual({ name: 'alice' });
|
|
862
|
+
});
|
|
863
|
+
test('unresolved reference becomes undefined', () => {
|
|
864
|
+
const { tag } = (0, peggy_1.parseTag)('ref=$nonexistent');
|
|
865
|
+
const obj = tag.toObject();
|
|
866
|
+
expect(obj['ref']).toBeUndefined();
|
|
867
|
+
});
|
|
868
|
+
});
|
|
869
|
+
describe('cloning with references', () => {
|
|
870
|
+
test('reference survives when extending tag is cloned', () => {
|
|
871
|
+
// Parse two lines - first creates reference, second extends it
|
|
872
|
+
const { tag } = (0, peggy_1.parseTag)(['source=hello target=$source', 'extra=data']);
|
|
873
|
+
// The reference should still work after the second parse cloned the first result
|
|
874
|
+
expect(tag.text('target')).toBe('hello');
|
|
875
|
+
});
|
|
876
|
+
test('clone preserves RefTag', () => {
|
|
877
|
+
const { tag } = (0, peggy_1.parseTag)('source=hello target=$source');
|
|
878
|
+
const cloned = tag.clone();
|
|
879
|
+
// After cloning, the reference should still resolve
|
|
880
|
+
expect(cloned.text('target')).toBe('hello');
|
|
881
|
+
});
|
|
882
|
+
});
|
|
883
|
+
});
|
|
405
884
|
//# sourceMappingURL=tags.spec.js.map
|