links-notation 0.30.0 → 0.30.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -348,7 +348,7 @@ class Link {
348
348
  return this._formatWithOptions(lessParentheses, isCompoundValue);
349
349
  }
350
350
  if (this.id === null && (!this.values || this.values.length === 0)) {
351
- return lessParentheses ? "" : "()";
351
+ return "()";
352
352
  }
353
353
  if (!this.values || this.values.length === 0) {
354
354
  const escapedId = Link.escapeReference(this.id);
@@ -359,7 +359,7 @@ class Link {
359
359
  }
360
360
  const valuesStr = this.values.map((v) => this.formatValue(v)).join(" ");
361
361
  if (this.id === null) {
362
- if (lessParentheses) {
362
+ if (lessParentheses && !this._hasLoneNestedValue()) {
363
363
  const allSimple = this.values.every((v) => !v.values || v.values.length === 0);
364
364
  if (allSimple) {
365
365
  const simpleValuesStr = this.values.map((v) => Link.escapeReference(v.id)).join(" ");
@@ -389,9 +389,12 @@ class Link {
389
389
  needsParentheses(str) {
390
390
  return str && (str.includes(" ") || str.includes(":") || str.includes("(") || str.includes(")"));
391
391
  }
392
+ _hasLoneNestedValue() {
393
+ return this.values.length === 1 && this.values[0].values?.length > 0;
394
+ }
392
395
  _formatWithOptions(options, isCompoundValue = false) {
393
396
  if (this.id === null && (!this.values || this.values.length === 0)) {
394
- return options.lessParentheses ? "" : "()";
397
+ return "()";
395
398
  }
396
399
  if (!this.values || this.values.length === 0) {
397
400
  const escapedId = Link.escapeReference(this.id);
@@ -410,7 +413,7 @@ class Link {
410
413
  const idStr = Link.escapeReference(this.id);
411
414
  testLine = options.lessParentheses ? `${idStr}: ${valuesStr}` : `(${idStr}: ${valuesStr})`;
412
415
  } else {
413
- testLine = options.lessParentheses ? valuesStr : `(${valuesStr})`;
416
+ testLine = options.lessParentheses && !this._hasLoneNestedValue() ? valuesStr : `(${valuesStr})`;
414
417
  }
415
418
  if (options.shouldIndentByLength(testLine)) {
416
419
  shouldIndent = true;
@@ -421,7 +424,7 @@ class Link {
421
424
  }
422
425
  const valuesStr = this.values.map((v) => this.formatValue(v)).join(" ");
423
426
  if (this.id === null) {
424
- if (options.lessParentheses) {
427
+ if (options.lessParentheses && !this._hasLoneNestedValue()) {
425
428
  const allSimple = this.values.every((v) => !v.values || v.values.length === 0);
426
429
  if (allSimple) {
427
430
  return this.values.map((v) => Link.escapeReference(v.id)).join(" ");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "links-notation",
3
- "version": "0.30.0",
3
+ "version": "0.30.1",
4
4
  "description": "Links Notation parser for JavaScript",
5
5
  "main": "dist/index.js",
6
6
  "types": "index.d.ts",
package/src/Link.js CHANGED
@@ -196,7 +196,7 @@ export class Link {
196
196
  // Original implementation for backward compatibility
197
197
  // Empty link
198
198
  if (this.id === null && (!this.values || this.values.length === 0)) {
199
- return lessParentheses ? '' : '()';
199
+ return '()';
200
200
  }
201
201
 
202
202
  // Link with only ID, no values
@@ -217,7 +217,7 @@ export class Link {
217
217
  // Link with values only (null id)
218
218
  if (this.id === null) {
219
219
  // For lessParentheses mode with simple values, don't wrap the whole thing
220
- if (lessParentheses) {
220
+ if (lessParentheses && !this._hasLoneNestedValue()) {
221
221
  // Check if all values are simple (no nested values)
222
222
  const allSimple = this.values.every(
223
223
  (v) => !v.values || v.values.length === 0
@@ -288,6 +288,16 @@ export class Link {
288
288
  );
289
289
  }
290
290
 
291
+ /**
292
+ * Whether this link holds exactly one link that has values. The parentheses
293
+ * of an anonymous link are what nest such a value: `((b c))` without them is
294
+ * `(b c)`, a different link, so lessParentheses has to keep them.
295
+ * @returns {boolean} True if the only value has values of its own
296
+ */
297
+ _hasLoneNestedValue() {
298
+ return this.values.length === 1 && this.values[0].values?.length > 0;
299
+ }
300
+
291
301
  /**
292
302
  * Format the link using a FormatOptions object
293
303
  * @param {FormatOptions} options - FormatOptions object with formatting options
@@ -297,7 +307,7 @@ export class Link {
297
307
  _formatWithOptions(options, isCompoundValue = false) {
298
308
  // Empty link
299
309
  if (this.id === null && (!this.values || this.values.length === 0)) {
300
- return options.lessParentheses ? '' : '()';
310
+ return '()';
301
311
  }
302
312
 
303
313
  // Link with only ID, no values
@@ -325,7 +335,10 @@ export class Link {
325
335
  ? `${idStr}: ${valuesStr}`
326
336
  : `(${idStr}: ${valuesStr})`;
327
337
  } else {
328
- testLine = options.lessParentheses ? valuesStr : `(${valuesStr})`;
338
+ testLine =
339
+ options.lessParentheses && !this._hasLoneNestedValue()
340
+ ? valuesStr
341
+ : `(${valuesStr})`;
329
342
  }
330
343
 
331
344
  if (options.shouldIndentByLength(testLine)) {
@@ -343,7 +356,7 @@ export class Link {
343
356
 
344
357
  // Link with values only (null id)
345
358
  if (this.id === null) {
346
- if (options.lessParentheses) {
359
+ if (options.lessParentheses && !this._hasLoneNestedValue()) {
347
360
  const allSimple = this.values.every(
348
361
  (v) => !v.values || v.values.length === 0
349
362
  );
@@ -1,7 +1,13 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { URL } from 'node:url';
3
3
  import { expect, test } from 'bun:test';
4
- import { Parser, StreamParser, formatLinks } from '../src/index.js';
4
+ import {
5
+ FormatOptions,
6
+ Link,
7
+ Parser,
8
+ StreamParser,
9
+ formatLinks,
10
+ } from '../src/index.js';
5
11
 
6
12
  const fixtures = JSON.parse(
7
13
  readFileSync(
@@ -25,3 +31,49 @@ test('shared text parity', () => {
25
31
  expect(tree(stream.end()), fixture.id).toEqual(fixture.expected);
26
32
  }
27
33
  });
34
+
35
+ test('shared less parentheses round trip', () => {
36
+ const options = new FormatOptions({ lessParentheses: true });
37
+ for (const fixture of fixtures) {
38
+ const parsed = new Parser().parse(fixture.input);
39
+ expect(formatLinks(parsed, true), fixture.id).toBe(fixture.lessParentheses);
40
+ expect(formatLinks(parsed, options), fixture.id).toBe(
41
+ fixture.lessParentheses
42
+ );
43
+ expect(
44
+ tree(new Parser().parse(fixture.lessParentheses)),
45
+ fixture.id
46
+ ).toEqual(fixture.expected);
47
+ for (const link of parsed) {
48
+ const [readBack] = new Parser().parse(link.format(true));
49
+ expect(link.equals(readBack), fixture.id).toBe(true);
50
+ }
51
+ }
52
+ });
53
+
54
+ test('less parentheses keeps the parentheses of a lone nested value', () => {
55
+ const nested = new Link(null, [new Link('b'), new Link('c')]);
56
+ expect(new Link(null, [nested]).format(true)).toBe('((b c))');
57
+ expect(new Link(null, [new Link('b')]).format(true)).toBe('b');
58
+ expect(new Link(null, [new Link('a'), nested]).format(true)).toBe('a (b c)');
59
+ });
60
+
61
+ test('less parentheses keeps the parentheses of an empty link', () => {
62
+ const options = new FormatOptions({ lessParentheses: true });
63
+ expect(new Link(null, []).format(true)).toBe('()');
64
+ expect(new Link(null, []).format(options)).toBe('()');
65
+ expect(formatLinks([], true)).toBe('');
66
+ });
67
+
68
+ test('line length is measured on the parentheses a lone nested value keeps', () => {
69
+ const link = new Link(null, [new Link(null, [new Link('b'), new Link('c')])]);
70
+ const options = (maxLineLength) =>
71
+ new FormatOptions({
72
+ lessParentheses: true,
73
+ indentLongLines: true,
74
+ maxLineLength,
75
+ preferInline: false,
76
+ });
77
+ expect(link.format(options(7))).toBe('((b c))');
78
+ expect(link.format(options(6))).toBe(' (b c)');
79
+ });