links-notation 0.30.1 → 0.30.2

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/README.md CHANGED
@@ -215,6 +215,40 @@ console.log(group.children[0].children[0].element.getValuesString()); // Detail
215
215
  `Parser.parse()` continues to return the existing list of links and expanded
216
216
  paths. `parseGroups()` preserves indentation even for the `name:` syntax.
217
217
 
218
+ #### Indented ID Groups
219
+
220
+ `parse` defines the canonical link values for `x:\n a\n b`: `(x: a b)`.
221
+ `parseGroups` keeps the `x:` element as a self reference (`id: 'x'`,
222
+ `values: []`), with children separate from that empty value list. Leaf lines
223
+ containing a single reference inside an indented ID's value subtree become
224
+ references, so folding that subtree agrees with `parse`:
225
+
226
+ ```javascript
227
+ import { Link, Parser } from 'links-notation';
228
+
229
+ const parser = new Parser();
230
+ const unindent = ({ element, children }) =>
231
+ children.length
232
+ ? new Link(element.id, [...element.values, ...children.map(unindent)])
233
+ : element;
234
+ const text = 'x:\n a\n b';
235
+ console.log(parser.parse(text)[0].format()); // (x: a b)
236
+ console.log(parser.parseGroups(text).map(unindent)[0].format()); // (x: a b)
237
+ ```
238
+
239
+ Parentheses keep their meaning: `x:\n (a)` gives `(x: (a))` in both readings.
240
+ Named and multi-value children keep their structure too. Under an ordinary
241
+ head such as `x: kind`, the child `a` remains a one-value link `(a)`;
242
+ `parse` expands those lines into paths, so this folding function is only an
243
+ equivalence with `parse` for indented ID subtrees. A standalone `id:` or
244
+ `(id:)` has no values in either API. The Link model represents it with
245
+ `id: 'id', values: []`; its existing default formatter writes `(id)`.
246
+
247
+ Before 0.30.2, `parseGroups` kept the anonymous one-value wrapper around leaf
248
+ references under `x:`. Callers that accessed `child.element.values[0]` there
249
+ should now use `child.element`. C++ (`parse_groups`), Swift and Kotlin expose
250
+ the same group contract; the other six bindings do not expose this parser API.
251
+
218
252
  `escapeReference` and `unescapeReference` are exported functions and static
219
253
  methods on `Link`. Use minimal mode only for whole text lines; default escaping
220
254
  still quotes spaces so references can be used inside tuples:
package/README.ru.md CHANGED
@@ -213,6 +213,16 @@ stream.end('следующая связь');
213
213
  `listenerCount` и другие). До версии 0.27.1 `StreamParser` импортировал модуль
214
214
  `events`, и для браузерной сборки требовался пакет-полифил `events`.
215
215
 
216
+ ### Группы отступных ID
217
+
218
+ В JavaScript, C++, Swift и Kotlin доступен `parseGroups`.
219
+ Для отступного ID, например `x:\n a\n b`, элемент `x:` имеет пустой
220
+ список значений, а его дети — ссылки `a` и `b`. Свёртка детей в значения
221
+ элемента даёт `(x: a b)`, как канонический `parse`. Явное `(a)` сохраняет
222
+ связь с одним значением; обычный заголовок `x: kind` сохраняет прежние
223
+ дочерние связи и развёртку путей. Начиная с 0.30.2, ссылка в листе доступна
224
+ как `child.element`, а не `child.element.values[0]`.
225
+
216
226
  ## Примеры синтаксиса
217
227
 
218
228
  ### Дуплеты (2-кортежи)
package/dist/index.js CHANGED
@@ -2943,8 +2943,10 @@ class Parser {
2943
2943
  this.parse(input);
2944
2944
  return buildCst(input, { comments: this.comments });
2945
2945
  }
2946
- _transformGroup(item) {
2947
- return new LinksGroup(this.transformLink(item), (item.children || []).map((child) => this._transformGroup(child)));
2946
+ _transformGroup(item, indentedValue = false) {
2947
+ const children = item.children || [];
2948
+ const childValues = indentedValue || item.id != null && !item.values?.length;
2949
+ return new LinksGroup(indentedValue && children.length === 0 ? this.transformIndentedValue(item) : this.transformLink(item), children.map((child) => this._transformGroup(child, childValues)));
2948
2950
  }
2949
2951
  _parse(input, transform) {
2950
2952
  if (typeof input !== "string") {
package/index.d.ts CHANGED
@@ -264,7 +264,11 @@ export class Parser {
264
264
  */
265
265
  parse(input: string): Link[];
266
266
 
267
- /** Read the indentation tree without expanding it into path combinations. */
267
+ /**
268
+ * Read the indentation tree without expanding it into path combinations.
269
+ * Indented ID elements have no values; their single-reference leaf children
270
+ * are references, so folding an indented ID subtree agrees with parse().
271
+ */
268
272
  parseGroups(input: string): IndentedGroup[];
269
273
 
270
274
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "links-notation",
3
- "version": "0.30.1",
3
+ "version": "0.30.2",
4
4
  "description": "Links Notation parser for JavaScript",
5
5
  "main": "dist/index.js",
6
6
  "types": "index.d.ts",
package/src/Parser.js CHANGED
@@ -42,6 +42,8 @@ export class Parser {
42
42
  * Read the indentation tree before it is expanded into path combinations.
43
43
  * Each group's element is the Link on that line; its children are groups
44
44
  * for the immediately indented lines. Parenthesized values remain Links.
45
+ * In an indented ID value subtree, single-reference leaves are references,
46
+ * as in parse(); the ID element itself keeps an empty value list.
45
47
  * @param {string} input - The Lino document
46
48
  * @returns {LinksGroup[]}
47
49
  */
@@ -65,10 +67,18 @@ export class Parser {
65
67
  return buildCst(input, { comments: this.comments });
66
68
  }
67
69
 
68
- _transformGroup(item) {
70
+ _transformGroup(item, indentedValue = false) {
71
+ const children = item.children || [];
72
+ // Carry the value context through an indented ID subtree, just as
73
+ // transformIndentedValue does. Keep non-leaf containers: folding their
74
+ // children later must retain the reference written on the parent line.
75
+ const childValues =
76
+ indentedValue || (item.id != null && !item.values?.length);
69
77
  return new LinksGroup(
70
- this.transformLink(item),
71
- (item.children || []).map((child) => this._transformGroup(child))
78
+ indentedValue && children.length === 0
79
+ ? this.transformIndentedValue(item)
80
+ : this.transformLink(item),
81
+ children.map((child) => this._transformGroup(child, childValues))
72
82
  );
73
83
  }
74
84
 
@@ -0,0 +1,69 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { URL } from 'node:url';
3
+ import { expect, test } from 'bun:test';
4
+ import { Link, Parser } from '../src/index.js';
5
+
6
+ const unindent = ({ element, children }) =>
7
+ children.length
8
+ ? new Link(element.id, [...element.values, ...children.map(unindent)])
9
+ : element;
10
+
11
+ const fixtures = JSON.parse(
12
+ readFileSync(
13
+ new URL('../../parity/text-fixtures.json', import.meta.url),
14
+ 'utf8'
15
+ )
16
+ );
17
+
18
+ test('indented ID groups fold to the same links as parse (#381)', () => {
19
+ for (const input of [
20
+ 'x:\n a\n b',
21
+ "'x':\n 'a'\n ~1{62}",
22
+ 'x:\n (a)\n b c\n child: value',
23
+ 'x:\n inner:\n a\n branch\n b',
24
+ 'x:\n child: kind\n a\n (branch)\n b',
25
+ 'x:\r\n a # comment\r\n\r\n b',
26
+ ...fixtures
27
+ .filter((fixture) => fixture.id.startsWith('indented-id-'))
28
+ .map((fixture) => fixture.input),
29
+ ]) {
30
+ const parser = new Parser();
31
+ expect(parser.parseGroups(input).map(unindent), input).toEqual(
32
+ parser.parse(input)
33
+ );
34
+ }
35
+ });
36
+
37
+ test('a bare ID group is a self reference with an empty value list', () => {
38
+ const parser = new Parser();
39
+ for (const input of ['id:', '(id:)', 'id:\n a']) {
40
+ const [group] = parser.parseGroups(input);
41
+ expect(group.element.id).toBe('id');
42
+ expect(group.element.values).toEqual([]);
43
+ expect(group.children).toHaveLength(input.includes('\n') ? 1 : 0);
44
+ }
45
+ expect(parser.parse('id:')).toEqual([new Link('id', [])]);
46
+ });
47
+
48
+ test('reference children keep syntax metadata and explicit parentheses', () => {
49
+ const [group] = new Parser().parseGroups("'x':\n 'a'\n (b)");
50
+ expect(group.element.rawId).toBe("'x'");
51
+ expect(group.children[0].element.id).toBe('a');
52
+ expect(group.children[0].element.rawId).toBe("'a'");
53
+ expect(group.children[0].element.quote).toBe("'");
54
+ expect(group.children[1].element).toEqual(new Link(null, [new Link('b')]));
55
+ const [ast] = new Parser({ syntax: false }).parseGroups("x:\n 'a'");
56
+ expect(ast.children[0].element.rawId).toBeNull();
57
+ });
58
+
59
+ test('ordinary heads retain one-value child links and path expansion', () => {
60
+ const parser = new Parser();
61
+ for (const input of ['x\n a\n b', 'x: kind\n a\n b']) {
62
+ const [group] = parser.parseGroups(input);
63
+ expect(group.children.map((child) => child.element)).toEqual([
64
+ new Link(null, [new Link('a')]),
65
+ new Link(null, [new Link('b')]),
66
+ ]);
67
+ expect(parser.parse(input)).toHaveLength(3);
68
+ }
69
+ });
@@ -69,7 +69,7 @@ describe('syntax metadata on parsed links (issue #364)', () => {
69
69
  test('parseGroups and StreamParser keep the metadata', () => {
70
70
  const groups = new Parser().parseGroups('\'root\':\n "child"');
71
71
  expect(groups[0].element.rawId).toBe("'root'");
72
- expect(groups[0].children[0].element.values[0].rawId).toBe('"child"');
72
+ expect(groups[0].children[0].element.rawId).toBe('"child"');
73
73
  const stream = new StreamParser();
74
74
  stream.write("(a '");
75
75
  stream.write("b')");