links-notation 0.11.2 → 0.12.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/bun.lock +1 -0
- package/dist/index.js +191 -21
- package/package.json +1 -1
- package/src/FormatConfig.js +19 -0
- package/src/FormatOptions.js +52 -0
- package/src/Link.js +287 -25
- package/src/Parser.js +43 -8
- package/src/index.js +3 -1
- package/tests/ApiTests.test.js +104 -7
- package/tests/EdgeCaseParser.test.js +9 -9
- package/tests/FormatConfig.test.js +126 -0
- package/tests/IndentationConsistency.test.js +1 -1
- package/tests/IndentedIdSyntax.test.js +2 -2
- package/tests/Link.test.js +1 -1
- package/tests/LinksGroup.test.js +28 -1
- package/tests/MixedIndentationModes.test.js +5 -5
- package/tests/MultilineParser.test.js +5 -5
- package/tests/NestedParser.test.js +31 -4
- package/tests/NestedSelfReference.test.js +102 -0
- package/tests/SingleLineParser.test.js +86 -6
|
@@ -4,13 +4,13 @@ import { formatLinks } from '../src/Link.js';
|
|
|
4
4
|
|
|
5
5
|
const parser = new Parser();
|
|
6
6
|
|
|
7
|
-
test('
|
|
7
|
+
test('Empty link', () => {
|
|
8
8
|
const source = ':';
|
|
9
9
|
// Standalone ':' is now forbidden and should throw an error
|
|
10
10
|
expect(() => parser.parse(source)).toThrow();
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
-
test('
|
|
13
|
+
test('Empty link with parentheses', () => {
|
|
14
14
|
const source = '()';
|
|
15
15
|
const target = '()';
|
|
16
16
|
const links = parser.parse(source);
|
|
@@ -18,13 +18,13 @@ test('EmptyLinkWithParenthesesTest', () => {
|
|
|
18
18
|
expect(formattedLinks).toBe(target);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
test('
|
|
21
|
+
test('Empty link with empty self reference', () => {
|
|
22
22
|
const source = '(:)';
|
|
23
23
|
// '(:)' is now forbidden and should throw an error
|
|
24
24
|
expect(() => parser.parse(source)).toThrow();
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
test('
|
|
27
|
+
test('All features', () => {
|
|
28
28
|
// Test single-line link with id
|
|
29
29
|
let input = 'id: value1 value2';
|
|
30
30
|
let result = parser.parse(input);
|
|
@@ -73,21 +73,21 @@ test('TestAllFeaturesTest', () => {
|
|
|
73
73
|
expect(result.length).toBeGreaterThan(0);
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
test('
|
|
76
|
+
test('Empty document', () => {
|
|
77
77
|
const input = '';
|
|
78
78
|
// Empty document should return empty array
|
|
79
79
|
const result = parser.parse(input);
|
|
80
80
|
expect(result).toEqual([]);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
test('
|
|
83
|
+
test('Whitespace only', () => {
|
|
84
84
|
const input = ' \n \n ';
|
|
85
85
|
// Whitespace-only document should return empty array
|
|
86
86
|
const result = parser.parse(input);
|
|
87
87
|
expect(result).toEqual([]);
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
test('
|
|
90
|
+
test('Empty links', () => {
|
|
91
91
|
let input = '()';
|
|
92
92
|
let result = parser.parse(input);
|
|
93
93
|
expect(result.length).toBe(1);
|
|
@@ -105,7 +105,7 @@ test('TestEmptyLinksTest', () => {
|
|
|
105
105
|
expect(result[0].values).toEqual([]);
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
test('
|
|
108
|
+
test('Singlet links', () => {
|
|
109
109
|
// Test singlet (1)
|
|
110
110
|
let input = '(1)';
|
|
111
111
|
let result = parser.parse(input);
|
|
@@ -155,7 +155,7 @@ test('TestSingletLinksTest', () => {
|
|
|
155
155
|
expect(result[0].values[3].values).toEqual([]);
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
-
test('
|
|
158
|
+
test('Invalid input', () => {
|
|
159
159
|
const input = '(invalid';
|
|
160
160
|
// Unclosed parentheses should throw an error
|
|
161
161
|
expect(() => parser.parse(input)).toThrow();
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test';
|
|
2
|
+
import { Parser, Link, formatLinks, FormatConfig } from '../src/index.js';
|
|
3
|
+
|
|
4
|
+
const parser = new Parser();
|
|
5
|
+
|
|
6
|
+
test('Format config basic', () => {
|
|
7
|
+
const config = new FormatConfig();
|
|
8
|
+
expect(config.lessParentheses).toBe(false);
|
|
9
|
+
expect(config.maxLineLength).toBe(80);
|
|
10
|
+
expect(config.indentLongLines).toBe(false);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('Format with line length limit', () => {
|
|
14
|
+
// Create a link with many references that exceeds line length
|
|
15
|
+
const link = new Link('sequence', [
|
|
16
|
+
new Link('1'), new Link('2'), new Link('3'), new Link('4'), new Link('5'),
|
|
17
|
+
new Link('6'), new Link('7'), new Link('8'), new Link('9'), new Link('10')
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
// Format with line length limit
|
|
21
|
+
// The line '(sequence: 1 2 3 4 5 6 7 8 9 10)' is 32 chars, so use threshold of 30
|
|
22
|
+
const config = new FormatConfig({
|
|
23
|
+
indentLongLines: true,
|
|
24
|
+
maxLineLength: 30,
|
|
25
|
+
preferInline: false
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const output = link.format(config);
|
|
29
|
+
expect(output).toContain('sequence:');
|
|
30
|
+
expect(output).toContain('\n'); // Should be indented across multiple lines
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('Format with max inline refs', () => {
|
|
34
|
+
// Create a link with 4 references
|
|
35
|
+
const link = new Link('id', [new Link('1'), new Link('2'), new Link('3'), new Link('4')]);
|
|
36
|
+
|
|
37
|
+
// Format with maxInlineRefs=3 (should trigger indentation)
|
|
38
|
+
const config = new FormatConfig({
|
|
39
|
+
maxInlineRefs: 3,
|
|
40
|
+
preferInline: false
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const output = link.format(config);
|
|
44
|
+
expect(output).toContain('id:');
|
|
45
|
+
expect(output).toContain('\n'); // Should be indented
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('Format with consecutive grouping', () => {
|
|
49
|
+
const links = [
|
|
50
|
+
new Link('SetA', [new Link('a')]),
|
|
51
|
+
new Link('SetA', [new Link('b')]),
|
|
52
|
+
new Link('SetA', [new Link('c')])
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const config = new FormatConfig({ groupConsecutive: true });
|
|
56
|
+
|
|
57
|
+
const output = formatLinks(links, config);
|
|
58
|
+
|
|
59
|
+
// Should group consecutive SetA links
|
|
60
|
+
// The output should have SetA with all values a, b, c
|
|
61
|
+
expect(output).toContain('SetA');
|
|
62
|
+
expect(output).toContain('a');
|
|
63
|
+
expect(output).toContain('b');
|
|
64
|
+
expect(output).toContain('c');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('Format config less parentheses', () => {
|
|
68
|
+
const link = new Link('id', [new Link('value')]);
|
|
69
|
+
|
|
70
|
+
const config = new FormatConfig({ lessParentheses: true });
|
|
71
|
+
|
|
72
|
+
const output = link.format(config);
|
|
73
|
+
// Should not have outer parentheses
|
|
74
|
+
expect(output).toBe('id: value');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('Format config custom indent', () => {
|
|
78
|
+
const link = new Link('id', [new Link('1'), new Link('2'), new Link('3'), new Link('4')]);
|
|
79
|
+
|
|
80
|
+
const config = new FormatConfig({
|
|
81
|
+
maxInlineRefs: 3,
|
|
82
|
+
preferInline: false,
|
|
83
|
+
indentString: ' ' // 4 spaces
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const output = link.format(config);
|
|
87
|
+
// Check for custom indentation
|
|
88
|
+
expect(output).toContain(' '); // Should use 4 spaces
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('Roundtrip with line length formatting', () => {
|
|
92
|
+
// Create a simple link
|
|
93
|
+
const originalLink = new Link('test', [new Link('a'), new Link('b'), new Link('c')]);
|
|
94
|
+
|
|
95
|
+
// Format with indentation
|
|
96
|
+
const config = new FormatConfig({
|
|
97
|
+
maxInlineRefs: 2,
|
|
98
|
+
preferInline: false
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const formatted = originalLink.format(config);
|
|
102
|
+
|
|
103
|
+
// Parse it back
|
|
104
|
+
const parsed = parser.parse(formatted);
|
|
105
|
+
|
|
106
|
+
// Should preserve the structure (though format may differ)
|
|
107
|
+
expect(parsed.length).toBeGreaterThan(0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('Should indent by length', () => {
|
|
111
|
+
const config = new FormatConfig({ indentLongLines: true, maxLineLength: 80 });
|
|
112
|
+
|
|
113
|
+
const shortLine = 'short';
|
|
114
|
+
const longLine = 'a'.repeat(100);
|
|
115
|
+
|
|
116
|
+
expect(config.shouldIndentByLength(shortLine)).toBe(false);
|
|
117
|
+
expect(config.shouldIndentByLength(longLine)).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('Should indent by ref count', () => {
|
|
121
|
+
const config = new FormatConfig({ maxInlineRefs: 3 });
|
|
122
|
+
|
|
123
|
+
expect(config.shouldIndentByRefCount(2)).toBe(false);
|
|
124
|
+
expect(config.shouldIndentByRefCount(3)).toBe(false);
|
|
125
|
+
expect(config.shouldIndentByRefCount(4)).toBe(true);
|
|
126
|
+
});
|
|
@@ -8,7 +8,7 @@ describe('Indentation Consistency Tests (Issue #135)', () => {
|
|
|
8
8
|
parser = new Parser();
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
test('
|
|
11
|
+
test('Leading spaces vs no leading spaces', () => {
|
|
12
12
|
// Example with 2 leading spaces (from issue #135)
|
|
13
13
|
const withLeading = ` TELEGRAM_BOT_TOKEN: '849...355:AAG...rgk_YZk...aPU'
|
|
14
14
|
TELEGRAM_ALLOWED_CHATS:
|
|
@@ -4,7 +4,7 @@ import { formatLinks } from "../src/Link.js";
|
|
|
4
4
|
|
|
5
5
|
const parser = new Parser();
|
|
6
6
|
|
|
7
|
-
test("Basic indented ID syntax
|
|
7
|
+
test("Basic indented ID syntax", () => {
|
|
8
8
|
const indentedSyntax = `3:
|
|
9
9
|
papa
|
|
10
10
|
loves
|
|
@@ -145,7 +145,7 @@ test("Empty indented ID should work", () => {
|
|
|
145
145
|
expect(formatted).toBe("(empty)");
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
-
test("Equivalence test
|
|
148
|
+
test("Equivalence test comprehensive", () => {
|
|
149
149
|
const testCases = [
|
|
150
150
|
{
|
|
151
151
|
indented: "test:\n one",
|
package/tests/Link.test.js
CHANGED
|
@@ -30,7 +30,7 @@ test('Link toString with id and values', () => {
|
|
|
30
30
|
expect(link.toString()).toBe('(parent: child1 child2)');
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
test('Link escapeReference
|
|
33
|
+
test('Link escapeReference simple', () => {
|
|
34
34
|
expect(Link.escapeReference('simple')).toBe('simple');
|
|
35
35
|
});
|
|
36
36
|
|
package/tests/LinksGroup.test.js
CHANGED
|
@@ -32,9 +32,36 @@ test('LinksGroup toString', () => {
|
|
|
32
32
|
const element = new Link('root');
|
|
33
33
|
const children = [new Link('child1'), new Link('child2')];
|
|
34
34
|
const group = new LinksGroup(element, children);
|
|
35
|
-
|
|
35
|
+
|
|
36
36
|
const str = group.toString();
|
|
37
37
|
expect(str).toContain('(root)');
|
|
38
38
|
expect(str).toContain('(child1)');
|
|
39
39
|
expect(str).toContain('(child2)');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('LinksGroup constructor equivalent test', () => {
|
|
43
|
+
// Test creating a LinksGroup structure in an equivalent way
|
|
44
|
+
const root = new Link('root');
|
|
45
|
+
const children = [new Link('child1'), new Link('child2')];
|
|
46
|
+
|
|
47
|
+
// Create a group with an id
|
|
48
|
+
const group = new LinksGroup(new Link('group'), [new LinksGroup(root), ...children.map(c => new LinksGroup(c))]);
|
|
49
|
+
|
|
50
|
+
expect(group.element.id).toBe('group');
|
|
51
|
+
expect(group.children.length).toBe(3);
|
|
52
|
+
expect(group.children[0].element).toBe(root);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('LinksGroup append to links list test', () => {
|
|
56
|
+
const element = new Link('root');
|
|
57
|
+
const children = [new Link('child1'), new Link('child2')];
|
|
58
|
+
const group = new LinksGroup(element, children);
|
|
59
|
+
|
|
60
|
+
const list = [];
|
|
61
|
+
group._appendToList(list);
|
|
62
|
+
|
|
63
|
+
expect(list.length).toBe(3);
|
|
64
|
+
expect(list[0]).toBe(element);
|
|
65
|
+
expect(list[1]).toBe(children[0]);
|
|
66
|
+
expect(list[2]).toBe(children[1]);
|
|
40
67
|
});
|
|
@@ -4,7 +4,7 @@ import { formatLinks } from "../src/Link.js";
|
|
|
4
4
|
|
|
5
5
|
const parser = new Parser();
|
|
6
6
|
|
|
7
|
-
test("Hero example
|
|
7
|
+
test("Hero example mixed modes", () => {
|
|
8
8
|
const input = `empInfo
|
|
9
9
|
employees:
|
|
10
10
|
(
|
|
@@ -31,7 +31,7 @@ test("Hero example - mixed modes - issue #105", () => {
|
|
|
31
31
|
expect(formatted).toContain("Wesley Crusher");
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
test("Hero example
|
|
34
|
+
test("Hero example alternative format", () => {
|
|
35
35
|
const input = `empInfo
|
|
36
36
|
(
|
|
37
37
|
employees:
|
|
@@ -60,7 +60,7 @@ test("Hero example - alternative format - issue #105", () => {
|
|
|
60
60
|
expect(formatted).toContain("Wesley Crusher");
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
-
test("Hero example
|
|
63
|
+
test("Hero example equivalence", () => {
|
|
64
64
|
const version1 = `empInfo
|
|
65
65
|
employees:
|
|
66
66
|
(
|
|
@@ -102,7 +102,7 @@ test("Hero example - equivalence test - issue #105", () => {
|
|
|
102
102
|
expect(formatted1).toBe(formatted2);
|
|
103
103
|
});
|
|
104
104
|
|
|
105
|
-
test("Set
|
|
105
|
+
test("Set context without colon", () => {
|
|
106
106
|
const input = `empInfo
|
|
107
107
|
employees`;
|
|
108
108
|
|
|
@@ -114,7 +114,7 @@ test("Set/object context without colon", () => {
|
|
|
114
114
|
expect(formatted).toContain("employees");
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
-
test("Sequence
|
|
117
|
+
test("Sequence context with colon", () => {
|
|
118
118
|
const input = `employees:
|
|
119
119
|
James Kirk
|
|
120
120
|
Jean-Luc Picard
|
|
@@ -4,7 +4,7 @@ import { formatLinks } from '../src/Link.js';
|
|
|
4
4
|
|
|
5
5
|
const parser = new Parser();
|
|
6
6
|
|
|
7
|
-
test('
|
|
7
|
+
test('Two links', () => {
|
|
8
8
|
const source = `(first: x y)
|
|
9
9
|
(second: a b)`;
|
|
10
10
|
const links = parser.parse(source);
|
|
@@ -12,7 +12,7 @@ test('TwoLinksTest', () => {
|
|
|
12
12
|
expect(target).toBe(source);
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
test('
|
|
15
|
+
test('Parse and stringify', () => {
|
|
16
16
|
const source = `(papa (lovesMama: loves mama))
|
|
17
17
|
(son lovesMama)
|
|
18
18
|
(daughter lovesMama)
|
|
@@ -22,7 +22,7 @@ test('ParseAndStringifyTest', () => {
|
|
|
22
22
|
expect(target).toBe(source);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
test('
|
|
25
|
+
test('Parse and stringify test 2', () => {
|
|
26
26
|
const source = `father (lovesMom: loves mom)
|
|
27
27
|
son lovesMom
|
|
28
28
|
daughter lovesMom
|
|
@@ -32,7 +32,7 @@ all (love mom)`;
|
|
|
32
32
|
expect(target).toBe(source);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
test('
|
|
35
|
+
test('Parse and stringify with less parentheses', () => {
|
|
36
36
|
const source = `lovesMama: loves mama
|
|
37
37
|
papa lovesMama
|
|
38
38
|
son lovesMama
|
|
@@ -43,7 +43,7 @@ all (love mama)`;
|
|
|
43
43
|
expect(target).toBe(source);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
test('
|
|
46
|
+
test('Duplicate identifiers', () => {
|
|
47
47
|
const source = `(a: a b)
|
|
48
48
|
(a: b c)`;
|
|
49
49
|
const target = `(a: a b)
|
|
@@ -105,7 +105,7 @@ test('Parse nested structure with indentation', () => {
|
|
|
105
105
|
expect(result[2].values.length).toBe(2);
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
test('
|
|
108
|
+
test('Indentation consistency', () => {
|
|
109
109
|
// Test that indentation must be consistent
|
|
110
110
|
const input = `parent
|
|
111
111
|
child1
|
|
@@ -135,7 +135,7 @@ test('Complex indentation', () => {
|
|
|
135
135
|
expect(result.length).toBe(6);
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
-
test('
|
|
138
|
+
test('Nested links', () => {
|
|
139
139
|
const input = '(1: (2: (3: 3)))';
|
|
140
140
|
const parsed = parser.parse(input);
|
|
141
141
|
expect(parsed.length).toBeGreaterThan(0);
|
|
@@ -148,7 +148,7 @@ test('Test nested links', () => {
|
|
|
148
148
|
expect(parsed.length).toBe(1);
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
-
test('
|
|
151
|
+
test('Indentation parser', () => {
|
|
152
152
|
const input = 'parent\n child1\n child2';
|
|
153
153
|
const result = parser.parse(input);
|
|
154
154
|
expect(result.length).toBeGreaterThan(0);
|
|
@@ -157,10 +157,37 @@ test('Test indentation (parser)', () => {
|
|
|
157
157
|
expect(hasParentLink).toBe(true);
|
|
158
158
|
});
|
|
159
159
|
|
|
160
|
-
test('
|
|
160
|
+
test('Nested indentation parser', () => {
|
|
161
161
|
const input = 'parent\n child\n grandchild';
|
|
162
162
|
const result = parser.parse(input);
|
|
163
163
|
expect(result.length).toBeGreaterThan(0);
|
|
164
164
|
// Should create nested structure with proper hierarchy
|
|
165
165
|
expect(result.length).toBeGreaterThanOrEqual(1);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('Three level nesting roundtrip', () => {
|
|
169
|
+
const input = '(1: (2: (3: 3)))';
|
|
170
|
+
const parsed = parser.parse(input);
|
|
171
|
+
|
|
172
|
+
// Validate round-trip
|
|
173
|
+
const output = formatLinks(parsed);
|
|
174
|
+
expect(output).toBe(input);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('Deep nested structure roundtrip', () => {
|
|
178
|
+
const input = '(a: (b: (c: (d: d))))';
|
|
179
|
+
const parsed = parser.parse(input);
|
|
180
|
+
|
|
181
|
+
// Validate round-trip
|
|
182
|
+
const output = formatLinks(parsed);
|
|
183
|
+
expect(output).toBe(input);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('Multiple nested links roundtrip', () => {
|
|
187
|
+
const input = '(parent: (child1: value1) (child2: value2))';
|
|
188
|
+
const parsed = parser.parse(input);
|
|
189
|
+
|
|
190
|
+
// Validate round-trip
|
|
191
|
+
const output = formatLinks(parsed);
|
|
192
|
+
expect(output).toBe(input);
|
|
166
193
|
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { test, expect } from 'bun:test';
|
|
2
|
+
import { Parser } from '../src/Parser.js';
|
|
3
|
+
|
|
4
|
+
test('Nested self-referenced object in pair value', () => {
|
|
5
|
+
// Test case from PARSER_BUG.md
|
|
6
|
+
// This should parse a dict with two pairs, where the second pair's value
|
|
7
|
+
// is itself a self-referenced dict definition (obj_1: dict ...)
|
|
8
|
+
const notation = '(obj_0: dict ((str bmFtZQ==) (str ZGljdDE=)) ((str b3RoZXI=) (obj_1: dict ((str bmFtZQ==) (str ZGljdDI=)) ((str b3RoZXI=) obj_0))))';
|
|
9
|
+
|
|
10
|
+
const parser = new Parser();
|
|
11
|
+
const links = parser.parse(notation);
|
|
12
|
+
|
|
13
|
+
// Should parse exactly one top-level link
|
|
14
|
+
expect(links.length).toBe(1);
|
|
15
|
+
|
|
16
|
+
const link = links[0];
|
|
17
|
+
|
|
18
|
+
// Top-level link should have ID "obj_0"
|
|
19
|
+
expect(link.id).toBe('obj_0');
|
|
20
|
+
|
|
21
|
+
// Should have: type marker + 2 pairs = 3 values
|
|
22
|
+
expect(link.values.length).toBe(3);
|
|
23
|
+
|
|
24
|
+
// First value is the type marker "dict"
|
|
25
|
+
expect(link.values[0].id).toBe('dict');
|
|
26
|
+
|
|
27
|
+
// Second and third values are the two pairs
|
|
28
|
+
const pair1 = link.values[1];
|
|
29
|
+
const pair2 = link.values[2];
|
|
30
|
+
|
|
31
|
+
// Pair 1: ((str bmFtZQ==) (str ZGljdDE=))
|
|
32
|
+
// This is a parenthesized expression containing two sub-expressions
|
|
33
|
+
expect(pair1.id).toBe(null);
|
|
34
|
+
expect(pair1.values.length).toBe(2);
|
|
35
|
+
|
|
36
|
+
// First element of pair1: (str bmFtZQ==)
|
|
37
|
+
expect(pair1.values[0].id).toBe(null);
|
|
38
|
+
expect(pair1.values[0].values.length).toBe(2); // "str" and "bmFtZQ=="
|
|
39
|
+
expect(pair1.values[0].values[0].id).toBe('str');
|
|
40
|
+
expect(pair1.values[0].values[1].id).toBe('bmFtZQ==');
|
|
41
|
+
|
|
42
|
+
// Second element of pair1: (str ZGljdDE=)
|
|
43
|
+
expect(pair1.values[1].id).toBe(null);
|
|
44
|
+
expect(pair1.values[1].values.length).toBe(2); // "str" and "ZGljdDE="
|
|
45
|
+
expect(pair1.values[1].values[0].id).toBe('str');
|
|
46
|
+
expect(pair1.values[1].values[1].id).toBe('ZGljdDE=');
|
|
47
|
+
|
|
48
|
+
// Pair 2: ((str b3RoZXI=) (obj_1: dict ...))
|
|
49
|
+
// This is the critical test - the second element should be a self-referenced dict
|
|
50
|
+
expect(pair2.id).toBe(null);
|
|
51
|
+
expect(pair2.values.length).toBe(2);
|
|
52
|
+
|
|
53
|
+
// First element of pair2: (str b3RoZXI=)
|
|
54
|
+
expect(pair2.values[0].id).toBe(null);
|
|
55
|
+
expect(pair2.values[0].values.length).toBe(2);
|
|
56
|
+
expect(pair2.values[0].values[0].id).toBe('str');
|
|
57
|
+
expect(pair2.values[0].values[1].id).toBe('b3RoZXI=');
|
|
58
|
+
|
|
59
|
+
// Second element of pair2: (obj_1: dict ((str bmFtZQ==) (str ZGljdDI=)) ((str b3RoZXI=) obj_0))
|
|
60
|
+
// THIS IS THE KEY TEST - obj_1 should have its ID preserved
|
|
61
|
+
const obj1 = pair2.values[1];
|
|
62
|
+
expect(obj1.id).toBe('obj_1'); // This is the self-reference ID
|
|
63
|
+
expect(obj1.values).toBeTruthy(); // Should have nested values (dict definition)
|
|
64
|
+
expect(obj1.values.length).toBe(3); // type marker + 2 pairs
|
|
65
|
+
|
|
66
|
+
// obj_1's type marker
|
|
67
|
+
expect(obj1.values[0].id).toBe('dict');
|
|
68
|
+
|
|
69
|
+
// obj_1's first pair: ((str bmFtZQ==) (str ZGljdDI=))
|
|
70
|
+
const obj1_pair1 = obj1.values[1];
|
|
71
|
+
expect(obj1_pair1.values.length).toBe(2);
|
|
72
|
+
expect(obj1_pair1.values[0].values[0].id).toBe('str');
|
|
73
|
+
expect(obj1_pair1.values[0].values[1].id).toBe('bmFtZQ==');
|
|
74
|
+
expect(obj1_pair1.values[1].values[0].id).toBe('str');
|
|
75
|
+
expect(obj1_pair1.values[1].values[1].id).toBe('ZGljdDI=');
|
|
76
|
+
|
|
77
|
+
// obj_1's second pair: ((str b3RoZXI=) obj_0) - reference back to obj_0
|
|
78
|
+
const obj1_pair2 = obj1.values[2];
|
|
79
|
+
expect(obj1_pair2.values.length).toBe(2);
|
|
80
|
+
expect(obj1_pair2.values[0].values[0].id).toBe('str');
|
|
81
|
+
expect(obj1_pair2.values[0].values[1].id).toBe('b3RoZXI=');
|
|
82
|
+
expect(obj1_pair2.values[1].id).toBe('obj_0'); // Reference to obj_0
|
|
83
|
+
expect(obj1_pair2.values[1].values).toEqual([]); // Just a reference, no nested values
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('Self-reference as direct child works correctly', () => {
|
|
87
|
+
// This pattern should work (and did work before)
|
|
88
|
+
const notation = '(obj_0: list (int 1) (int 2) (obj_1: list (int 3) (int 4) obj_0))';
|
|
89
|
+
|
|
90
|
+
const parser = new Parser();
|
|
91
|
+
const links = parser.parse(notation);
|
|
92
|
+
|
|
93
|
+
expect(links.length).toBe(1);
|
|
94
|
+
expect(links[0].id).toBe('obj_0');
|
|
95
|
+
expect(links[0].values.length).toBe(4); // list + 1 + 2 + obj_1
|
|
96
|
+
|
|
97
|
+
// The fourth value should be obj_1 with a self-reference
|
|
98
|
+
const obj1 = links[0].values[3];
|
|
99
|
+
expect(obj1.id).toBe('obj_1');
|
|
100
|
+
expect(obj1.values.length).toBe(4); // list + 3 + 4 + obj_0
|
|
101
|
+
expect(obj1.values[3].id).toBe('obj_0'); // Reference back to obj_0
|
|
102
|
+
});
|
|
@@ -4,28 +4,28 @@ import { formatLinks } from '../src/Link.js';
|
|
|
4
4
|
|
|
5
5
|
const parser = new Parser();
|
|
6
6
|
|
|
7
|
-
test('
|
|
7
|
+
test('Single link', () => {
|
|
8
8
|
const source = '(address: source target)';
|
|
9
9
|
const links = parser.parse(source);
|
|
10
10
|
const target = formatLinks(links);
|
|
11
11
|
expect(target).toBe(source);
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
test('
|
|
14
|
+
test('Triplet single link', () => {
|
|
15
15
|
const source = '(papa has car)';
|
|
16
16
|
const links = parser.parse(source);
|
|
17
17
|
const target = formatLinks(links);
|
|
18
18
|
expect(target).toBe(source);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
test('
|
|
21
|
+
test('Bug test 1', () => {
|
|
22
22
|
const source = '(ignore conan-center-index repository)';
|
|
23
23
|
const links = parser.parse(source);
|
|
24
24
|
const target = formatLinks(links);
|
|
25
25
|
expect(target).toBe(source);
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
test('
|
|
28
|
+
test('Quoted references', () => {
|
|
29
29
|
const source = `(a: 'b' "c")`;
|
|
30
30
|
const target = `(a: b c)`;
|
|
31
31
|
const links = parser.parse(source);
|
|
@@ -33,7 +33,7 @@ test('QuotedReferencesTest', () => {
|
|
|
33
33
|
expect(formattedLinks).toBe(target);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
test('
|
|
36
|
+
test('Quoted references with spaces', () => {
|
|
37
37
|
const source = `('a a': 'b b' "c c")`;
|
|
38
38
|
const target = `('a a': 'b b' 'c c')`;
|
|
39
39
|
const links = parser.parse(source);
|
|
@@ -127,7 +127,7 @@ test('Test value link', () => {
|
|
|
127
127
|
expect(result.length).toBeGreaterThan(0);
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
-
test('
|
|
130
|
+
test('Parse quoted references values only', () => {
|
|
131
131
|
const source = `"has space" 'has:colon'`;
|
|
132
132
|
const parser = new Parser();
|
|
133
133
|
const links = parser.parse(source);
|
|
@@ -226,4 +226,84 @@ test('Test value link (parser)', () => {
|
|
|
226
226
|
expect(result.length).toBe(1);
|
|
227
227
|
expect(result[0].id).toBe(null);
|
|
228
228
|
expect(result[0].values.length).toBe(3);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('Multiline without id', () => {
|
|
232
|
+
// Test that '(:)' syntax is forbidden and should throw an error
|
|
233
|
+
const input = '(: value1 value2)';
|
|
234
|
+
// JS parser forbids this syntax
|
|
235
|
+
expect(() => parser.parse(input)).toThrow();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('Parse values only standalone colon', () => {
|
|
239
|
+
// Test that standalone ':' is forbidden and should throw an error
|
|
240
|
+
const input = ': value1 value2';
|
|
241
|
+
// JS parser forbids this syntax
|
|
242
|
+
expect(() => parser.parse(input)).toThrow();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('Quoted references with spaces in link', () => {
|
|
246
|
+
const input = '(id: "value with spaces")';
|
|
247
|
+
const result = parser.parse(input);
|
|
248
|
+
expect(result.length).toBe(1);
|
|
249
|
+
expect(result[0].id).toBe('id');
|
|
250
|
+
expect(result[0].values.length).toBe(1);
|
|
251
|
+
expect(result[0].values[0].id).toBe('value with spaces');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('Single line without id', () => {
|
|
255
|
+
// Test parsing a single line without ID (just values)
|
|
256
|
+
const input = '(value1 value2)';
|
|
257
|
+
const result = parser.parse(input);
|
|
258
|
+
expect(result.length).toBe(1);
|
|
259
|
+
expect(result[0].id).toBe(null);
|
|
260
|
+
expect(result[0].values.length).toBe(2);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('Link with id', () => {
|
|
264
|
+
const input = '(id: a b c)';
|
|
265
|
+
const result = parser.parse(input);
|
|
266
|
+
expect(result.length).toBe(1);
|
|
267
|
+
expect(result[0].id).toBe('id');
|
|
268
|
+
expect(result[0].values.length).toBe(3);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('Quoted reference', () => {
|
|
272
|
+
const input = '"quoted value"';
|
|
273
|
+
const result = parser.parse(input);
|
|
274
|
+
expect(result.length).toBe(1);
|
|
275
|
+
expect(result[0].id).toBe(null);
|
|
276
|
+
expect(result[0].values.length).toBe(1);
|
|
277
|
+
expect(result[0].values[0].id).toBe('quoted value');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('Quoted references with special chars', () => {
|
|
281
|
+
const input = '("special:char" "another@char")';
|
|
282
|
+
const result = parser.parse(input);
|
|
283
|
+
expect(result.length).toBe(1);
|
|
284
|
+
expect(result[0].id).toBe(null);
|
|
285
|
+
expect(result[0].values.length).toBe(2);
|
|
286
|
+
expect(result[0].values[0].id).toBe('special:char');
|
|
287
|
+
expect(result[0].values[1].id).toBe('another@char');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('Simple reference', () => {
|
|
291
|
+
const input = 'simplereference';
|
|
292
|
+
const result = parser.parse(input);
|
|
293
|
+
expect(result.length).toBeGreaterThan(0);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test('Single line link', () => {
|
|
297
|
+
const input = 'id: value1 value2';
|
|
298
|
+
const result = parser.parse(input);
|
|
299
|
+
expect(result.length).toBe(1);
|
|
300
|
+
expect(result[0].id).toBe('id');
|
|
301
|
+
expect(result[0].values.length).toBe(2);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('Single line with id', () => {
|
|
305
|
+
const input = 'myid: val1 val2';
|
|
306
|
+
const result = parser.parse(input);
|
|
307
|
+
expect(result.length).toBeGreaterThan(0);
|
|
308
|
+
expect(result[0].id).toBe('myid');
|
|
229
309
|
});
|