links-notation 0.18.0 → 0.20.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/dist/index.js CHANGED
@@ -44,7 +44,7 @@ class Link {
44
44
  }
45
45
  const hasSingleQuote = reference.includes("'");
46
46
  const hasDoubleQuote = reference.includes('"');
47
- const needsQuoting = reference.includes(":") || reference.includes("(") || reference.includes(")") || reference.includes(" ") || reference.includes("\t") || reference.includes(`
47
+ const needsQuoting = reference.startsWith("#") || reference.includes(":") || reference.includes("(") || reference.includes(")") || reference.includes(" ") || reference.includes("\t") || reference.includes(`
48
48
  `) || reference.includes("\r") || hasDoubleQuote || hasSingleQuote;
49
49
  if (hasSingleQuote && hasDoubleQuote) {
50
50
  return `'${reference.replace(/'/g, "\\'")}'`;
@@ -320,6 +320,93 @@ function windowAround(lineText, column) {
320
320
  return [quoted, target - start + shift + 1];
321
321
  }
322
322
 
323
+ // src/comments.js
324
+ var COMMENT = "#";
325
+ var QUOTES = ['"', "'", "`"];
326
+ var BEFORE_REFERENCE = [" ", "\t", `
327
+ `, "\r", "(", ":"];
328
+ var BEFORE_COMMENT = [" ", "\t", `
329
+ `, "\r"];
330
+ function stripComments(document) {
331
+ let blanked = null;
332
+ let position = 0;
333
+ while (position < document.length) {
334
+ const character = document[position];
335
+ if (QUOTES.includes(character) && follows(document, position, BEFORE_REFERENCE)) {
336
+ const end = quotedReferenceEnd(document, position);
337
+ position = end === null ? position + 1 : end;
338
+ continue;
339
+ }
340
+ if (character === COMMENT && follows(document, position, BEFORE_COMMENT)) {
341
+ blanked = blanked ?? document.split("");
342
+ while (position < document.length && document[position] !== `
343
+ ` && document[position] !== "\r") {
344
+ blanked[position] = " ";
345
+ position++;
346
+ }
347
+ continue;
348
+ }
349
+ position++;
350
+ }
351
+ return blanked === null ? document : blanked.join("");
352
+ }
353
+ function follows(document, position, allowed) {
354
+ return position === 0 || allowed.includes(document[position - 1]);
355
+ }
356
+ function quotedReferenceEnd(document, start) {
357
+ const quote = document[start];
358
+ if (!QUOTES.includes(quote)) {
359
+ return null;
360
+ }
361
+ let position = start;
362
+ while (position < document.length && document[position] === quote) {
363
+ position++;
364
+ }
365
+ const count = position - start;
366
+ const isEvenRun = count % 2 === 0;
367
+ const emptyReference = isEvenRun ? start + count : null;
368
+ const closing = quote.repeat(count);
369
+ const escape = quote.repeat(count * 2);
370
+ let content = "";
371
+ while (position < document.length) {
372
+ if (document.startsWith(escape, position)) {
373
+ content += closing;
374
+ position += escape.length;
375
+ continue;
376
+ }
377
+ if (document.startsWith(closing, position)) {
378
+ const afterClosing = position + count;
379
+ if (afterClosing >= document.length || document[afterClosing] !== quote) {
380
+ if (isEvenRun && !isSubstantiveBody(content)) {
381
+ return emptyReference;
382
+ }
383
+ return afterClosing;
384
+ }
385
+ }
386
+ content += document[position];
387
+ position++;
388
+ }
389
+ return emptyReference;
390
+ }
391
+ function isSubstantiveBody(content) {
392
+ let depth = 0;
393
+ let hasVisible = false;
394
+ for (const character of content) {
395
+ if (character === "(") {
396
+ depth++;
397
+ } else if (character === ")") {
398
+ depth--;
399
+ if (depth < 0) {
400
+ return false;
401
+ }
402
+ }
403
+ if (!/\s/.test(character)) {
404
+ hasVisible = true;
405
+ }
406
+ }
407
+ return hasVisible && depth === 0;
408
+ }
409
+
323
410
  // src/parser-generated.js
324
411
  class peg$SyntaxError extends SyntaxError {
325
412
  constructor(message, expected, found, location) {
@@ -1751,42 +1838,189 @@ function peg$parse(input, options) {
1751
1838
  return s0;
1752
1839
  }
1753
1840
  function peg$parseeol() {
1754
- let s0, s1, s2, s3;
1841
+ let s0, s1, s2;
1755
1842
  s0 = peg$currPos;
1756
1843
  s1 = peg$parse__();
1757
- s2 = [];
1758
- s3 = input.charAt(peg$currPos);
1759
- if (peg$r1.test(s3)) {
1844
+ s2 = peg$parselineBreaks();
1845
+ if (s2 === peg$FAILED) {
1846
+ s2 = peg$parseeof();
1847
+ if (s2 === peg$FAILED) {
1848
+ s2 = peg$parsenestedGroupEnd();
1849
+ }
1850
+ }
1851
+ if (s2 !== peg$FAILED) {
1852
+ s1 = [s1, s2];
1853
+ s0 = s1;
1854
+ } else {
1855
+ peg$currPos = s0;
1856
+ s0 = peg$FAILED;
1857
+ }
1858
+ return s0;
1859
+ }
1860
+ function peg$parselineBreaks() {
1861
+ let s0, s1, s2, s3, s4, s5, s6;
1862
+ s0 = peg$currPos;
1863
+ s1 = [];
1864
+ s2 = input.charAt(peg$currPos);
1865
+ if (peg$r1.test(s2)) {
1760
1866
  peg$currPos++;
1761
1867
  } else {
1762
- s3 = peg$FAILED;
1868
+ s2 = peg$FAILED;
1763
1869
  if (peg$silentFails === 0) {
1764
1870
  peg$fail(peg$e1);
1765
1871
  }
1766
1872
  }
1767
- if (s3 !== peg$FAILED) {
1768
- while (s3 !== peg$FAILED) {
1769
- s2.push(s3);
1770
- s3 = input.charAt(peg$currPos);
1771
- if (peg$r1.test(s3)) {
1873
+ if (s2 !== peg$FAILED) {
1874
+ while (s2 !== peg$FAILED) {
1875
+ s1.push(s2);
1876
+ s2 = input.charAt(peg$currPos);
1877
+ if (peg$r1.test(s2)) {
1772
1878
  peg$currPos++;
1773
1879
  } else {
1774
- s3 = peg$FAILED;
1880
+ s2 = peg$FAILED;
1775
1881
  if (peg$silentFails === 0) {
1776
1882
  peg$fail(peg$e1);
1777
1883
  }
1778
1884
  }
1779
1885
  }
1780
1886
  } else {
1781
- s2 = peg$FAILED;
1887
+ s1 = peg$FAILED;
1782
1888
  }
1783
- if (s2 === peg$FAILED) {
1784
- s2 = peg$parseeof();
1785
- if (s2 === peg$FAILED) {
1786
- s2 = peg$parsenestedGroupEnd();
1889
+ if (s1 !== peg$FAILED) {
1890
+ s2 = [];
1891
+ s3 = peg$currPos;
1892
+ s4 = [];
1893
+ s5 = input.charAt(peg$currPos);
1894
+ if (peg$r0.test(s5)) {
1895
+ peg$currPos++;
1896
+ } else {
1897
+ s5 = peg$FAILED;
1898
+ if (peg$silentFails === 0) {
1899
+ peg$fail(peg$e0);
1900
+ }
1901
+ }
1902
+ if (s5 !== peg$FAILED) {
1903
+ while (s5 !== peg$FAILED) {
1904
+ s4.push(s5);
1905
+ s5 = input.charAt(peg$currPos);
1906
+ if (peg$r0.test(s5)) {
1907
+ peg$currPos++;
1908
+ } else {
1909
+ s5 = peg$FAILED;
1910
+ if (peg$silentFails === 0) {
1911
+ peg$fail(peg$e0);
1912
+ }
1913
+ }
1914
+ }
1915
+ } else {
1916
+ s4 = peg$FAILED;
1917
+ }
1918
+ if (s4 !== peg$FAILED) {
1919
+ s5 = [];
1920
+ s6 = input.charAt(peg$currPos);
1921
+ if (peg$r1.test(s6)) {
1922
+ peg$currPos++;
1923
+ } else {
1924
+ s6 = peg$FAILED;
1925
+ if (peg$silentFails === 0) {
1926
+ peg$fail(peg$e1);
1927
+ }
1928
+ }
1929
+ if (s6 !== peg$FAILED) {
1930
+ while (s6 !== peg$FAILED) {
1931
+ s5.push(s6);
1932
+ s6 = input.charAt(peg$currPos);
1933
+ if (peg$r1.test(s6)) {
1934
+ peg$currPos++;
1935
+ } else {
1936
+ s6 = peg$FAILED;
1937
+ if (peg$silentFails === 0) {
1938
+ peg$fail(peg$e1);
1939
+ }
1940
+ }
1941
+ }
1942
+ } else {
1943
+ s5 = peg$FAILED;
1944
+ }
1945
+ if (s5 !== peg$FAILED) {
1946
+ s4 = [s4, s5];
1947
+ s3 = s4;
1948
+ } else {
1949
+ peg$currPos = s3;
1950
+ s3 = peg$FAILED;
1951
+ }
1952
+ } else {
1953
+ peg$currPos = s3;
1954
+ s3 = peg$FAILED;
1955
+ }
1956
+ while (s3 !== peg$FAILED) {
1957
+ s2.push(s3);
1958
+ s3 = peg$currPos;
1959
+ s4 = [];
1960
+ s5 = input.charAt(peg$currPos);
1961
+ if (peg$r0.test(s5)) {
1962
+ peg$currPos++;
1963
+ } else {
1964
+ s5 = peg$FAILED;
1965
+ if (peg$silentFails === 0) {
1966
+ peg$fail(peg$e0);
1967
+ }
1968
+ }
1969
+ if (s5 !== peg$FAILED) {
1970
+ while (s5 !== peg$FAILED) {
1971
+ s4.push(s5);
1972
+ s5 = input.charAt(peg$currPos);
1973
+ if (peg$r0.test(s5)) {
1974
+ peg$currPos++;
1975
+ } else {
1976
+ s5 = peg$FAILED;
1977
+ if (peg$silentFails === 0) {
1978
+ peg$fail(peg$e0);
1979
+ }
1980
+ }
1981
+ }
1982
+ } else {
1983
+ s4 = peg$FAILED;
1984
+ }
1985
+ if (s4 !== peg$FAILED) {
1986
+ s5 = [];
1987
+ s6 = input.charAt(peg$currPos);
1988
+ if (peg$r1.test(s6)) {
1989
+ peg$currPos++;
1990
+ } else {
1991
+ s6 = peg$FAILED;
1992
+ if (peg$silentFails === 0) {
1993
+ peg$fail(peg$e1);
1994
+ }
1995
+ }
1996
+ if (s6 !== peg$FAILED) {
1997
+ while (s6 !== peg$FAILED) {
1998
+ s5.push(s6);
1999
+ s6 = input.charAt(peg$currPos);
2000
+ if (peg$r1.test(s6)) {
2001
+ peg$currPos++;
2002
+ } else {
2003
+ s6 = peg$FAILED;
2004
+ if (peg$silentFails === 0) {
2005
+ peg$fail(peg$e1);
2006
+ }
2007
+ }
2008
+ }
2009
+ } else {
2010
+ s5 = peg$FAILED;
2011
+ }
2012
+ if (s5 !== peg$FAILED) {
2013
+ s4 = [s4, s5];
2014
+ s3 = s4;
2015
+ } else {
2016
+ peg$currPos = s3;
2017
+ s3 = peg$FAILED;
2018
+ }
2019
+ } else {
2020
+ peg$currPos = s3;
2021
+ s3 = peg$FAILED;
2022
+ }
1787
2023
  }
1788
- }
1789
- if (s2 !== peg$FAILED) {
1790
2024
  s1 = [s1, s2];
1791
2025
  s0 = s1;
1792
2026
  } else {
@@ -2063,6 +2297,7 @@ class Parser {
2063
2297
  constructor(options = {}) {
2064
2298
  this.maxInputSize = options.maxInputSize || 10 * 1024 * 1024;
2065
2299
  this.maxDepth = options.maxDepth || 1000;
2300
+ this.comments = options.comments ?? true;
2066
2301
  }
2067
2302
  parse(input) {
2068
2303
  if (typeof input !== "string") {
@@ -2071,8 +2306,9 @@ class Parser {
2071
2306
  if (input.length > this.maxInputSize) {
2072
2307
  throw new Error(`Input size exceeds maximum allowed size of ${this.maxInputSize} bytes`);
2073
2308
  }
2309
+ const prepared = this.comments ? stripComments(input) : input;
2074
2310
  try {
2075
- const rawResult = peg$parse(input);
2311
+ const rawResult = peg$parse(prepared);
2076
2312
  return this.transformResult(rawResult);
2077
2313
  } catch (error) {
2078
2314
  if (error && error.location) {
@@ -2183,6 +2419,222 @@ class Parser {
2183
2419
  return new Link(item.id ?? null, []);
2184
2420
  }
2185
2421
  }
2422
+ // src/StreamParser.js
2423
+ import { EventEmitter } from "events";
2424
+ var DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
2425
+
2426
+ class StreamParseError extends Error {
2427
+ constructor(error, startOffset, startLine) {
2428
+ const localOffset = error?.offset ?? 0;
2429
+ const localLine = error?.line ?? 1;
2430
+ const column = error?.column ?? 1;
2431
+ const line = startLine + localLine - 1;
2432
+ super(`Stream parse error at line ${line}, column ${column}: ${error.message}`);
2433
+ this.name = "StreamParseError";
2434
+ this.cause = error;
2435
+ this.offset = startOffset + localOffset;
2436
+ this.line = line;
2437
+ this.column = column;
2438
+ this.found = error?.found ?? null;
2439
+ this.lineText = error?.lineText ?? "";
2440
+ this.snippet = error?.snippet ?? "";
2441
+ }
2442
+ }
2443
+
2444
+ class StreamParser extends EventEmitter {
2445
+ constructor(options = {}) {
2446
+ super();
2447
+ this.parser = options.parser ?? new Parser(options);
2448
+ this.comments = options.comments ?? this.parser.comments ?? true;
2449
+ this.maxBufferSize = options.maxBufferSize ?? options.maxInputSize ?? DEFAULT_MAX_BUFFER_SIZE;
2450
+ this.collect = options.collect ?? true;
2451
+ this.reset();
2452
+ }
2453
+ write(chunk) {
2454
+ if (typeof chunk !== "string") {
2455
+ return this._fail(new TypeError("Input must be a string"));
2456
+ }
2457
+ if (this.ended) {
2458
+ return this._fail(new Error("Cannot write after end()"));
2459
+ }
2460
+ const emitted = [];
2461
+ for (let index = 0;index < chunk.length; index++) {
2462
+ const character = chunk[index];
2463
+ this.currentLine += character;
2464
+ this.offset += 1;
2465
+ if (character === `
2466
+ `) {
2467
+ this.buffer += this.currentLine;
2468
+ this.currentLine = "";
2469
+ this.lineClassified = false;
2470
+ this.line += 1;
2471
+ this.column = 1;
2472
+ } else {
2473
+ if (!this.lineClassified && character !== " " && character !== "\t" && character !== "\r") {
2474
+ this.lineClassified = true;
2475
+ const isComment = this.comments && character === "#";
2476
+ if (!isComment) {
2477
+ const indentation = leadingSpaces(this.currentLine);
2478
+ this._startContentLine(indentation, emitted);
2479
+ }
2480
+ }
2481
+ this.column += 1;
2482
+ }
2483
+ if (this.buffer.length + this.currentLine.length > this.maxBufferSize) {
2484
+ return this._fail(new RangeError(`Buffered record exceeds maximum size of ${this.maxBufferSize} characters`));
2485
+ }
2486
+ }
2487
+ return emitted;
2488
+ }
2489
+ end(chunk = "") {
2490
+ const emitted = chunk === "" ? [] : this.write(chunk);
2491
+ if (this.ended) {
2492
+ return this.collect ? [...this.links] : emitted;
2493
+ }
2494
+ const document = this.buffer + this.currentLine;
2495
+ if (document.length > 0) {
2496
+ let links;
2497
+ try {
2498
+ links = this.parser.parse(document);
2499
+ } catch (error) {
2500
+ const streamError = new StreamParseError(error, this.segmentOffset, this.segmentLine);
2501
+ return this._fail(streamError);
2502
+ }
2503
+ this._publish(links, emitted);
2504
+ this._advanceSegment(document);
2505
+ }
2506
+ this.buffer = "";
2507
+ this.currentLine = "";
2508
+ this.baseIndentation = null;
2509
+ this.ended = true;
2510
+ const result = this.collect ? [...this.links] : emitted;
2511
+ this.emit("end", result);
2512
+ return result;
2513
+ }
2514
+ drain() {
2515
+ const links = this.links;
2516
+ this.links = [];
2517
+ return links;
2518
+ }
2519
+ reset() {
2520
+ this.buffer = "";
2521
+ this.currentLine = "";
2522
+ this.baseIndentation = null;
2523
+ this.lineClassified = false;
2524
+ this.links = [];
2525
+ this.offset = 0;
2526
+ this.line = 1;
2527
+ this.column = 1;
2528
+ this.segmentOffset = 0;
2529
+ this.segmentLine = 1;
2530
+ this.ended = false;
2531
+ return this;
2532
+ }
2533
+ position() {
2534
+ return {
2535
+ offset: this.offset,
2536
+ line: this.line,
2537
+ column: this.column,
2538
+ buffered: this.buffer.length + this.currentLine.length
2539
+ };
2540
+ }
2541
+ static *parse(chunks, options = {}) {
2542
+ const parser = new StreamParser({ ...options, collect: false });
2543
+ for (const chunk of chunks) {
2544
+ yield* parser.write(chunk);
2545
+ }
2546
+ yield* parser.end();
2547
+ }
2548
+ static async* parseAsync(chunks, options = {}) {
2549
+ const parser = new StreamParser({ ...options, collect: false });
2550
+ for await (const chunk of chunks) {
2551
+ yield* parser.write(chunk);
2552
+ }
2553
+ yield* parser.end();
2554
+ }
2555
+ _startContentLine(indentation, emitted) {
2556
+ if (this.buffer.length > 0 && this.baseIndentation !== null && indentation <= this.baseIndentation && structurallyComplete(this.buffer, this.comments)) {
2557
+ let links;
2558
+ try {
2559
+ links = this.parser.parse(this.buffer);
2560
+ } catch {
2561
+ links = null;
2562
+ }
2563
+ if (links !== null) {
2564
+ this._publish(links, emitted);
2565
+ this._advanceSegment(this.buffer);
2566
+ this.buffer = "";
2567
+ this.baseIndentation = null;
2568
+ }
2569
+ }
2570
+ if (this.baseIndentation === null) {
2571
+ this.baseIndentation = indentation;
2572
+ }
2573
+ }
2574
+ _publish(links, emitted) {
2575
+ for (const link of links) {
2576
+ emitted.push(link);
2577
+ if (this.collect)
2578
+ this.links.push(link);
2579
+ this.emit("link", link);
2580
+ }
2581
+ }
2582
+ _advanceSegment(document) {
2583
+ this.segmentOffset += document.length;
2584
+ this.segmentLine += countNewlines(document);
2585
+ }
2586
+ _fail(error) {
2587
+ this.emit("error", error);
2588
+ throw error;
2589
+ }
2590
+ }
2591
+ function leadingSpaces(line) {
2592
+ let indentation = 0;
2593
+ while (line[indentation] === " ")
2594
+ indentation += 1;
2595
+ return indentation;
2596
+ }
2597
+ function countNewlines(value) {
2598
+ let count = 0;
2599
+ for (const character of value) {
2600
+ if (character === `
2601
+ `)
2602
+ count += 1;
2603
+ }
2604
+ return count;
2605
+ }
2606
+ function structurallyComplete(document, comments) {
2607
+ const quotes = ['"', "'", "`"];
2608
+ const beforeReference = [" ", "\t", `
2609
+ `, "\r", "(", ":"];
2610
+ const beforeComment = [" ", "\t", `
2611
+ `, "\r"];
2612
+ let depth = 0;
2613
+ for (let position = 0;position < document.length; position++) {
2614
+ const character = document[position];
2615
+ const previous = position === 0 ? null : document[position - 1];
2616
+ if (quotes.includes(character) && (previous === null || beforeReference.includes(previous))) {
2617
+ const end = quotedReferenceEnd(document, position);
2618
+ if (end === null)
2619
+ return false;
2620
+ position = end - 1;
2621
+ continue;
2622
+ }
2623
+ if (comments && character === "#" && (previous === null || beforeComment.includes(previous))) {
2624
+ const newline = document.indexOf(`
2625
+ `, position);
2626
+ if (newline === -1)
2627
+ return true;
2628
+ position = newline;
2629
+ continue;
2630
+ }
2631
+ if (character === "(")
2632
+ depth += 1;
2633
+ if (character === ")")
2634
+ depth -= 1;
2635
+ }
2636
+ return depth === 0;
2637
+ }
2186
2638
  // src/FormatOptions.js
2187
2639
  class FormatOptions {
2188
2640
  constructor(options = {}) {
@@ -2218,5 +2670,8 @@ export {
2218
2670
  LinksGroup,
2219
2671
  ParseError,
2220
2672
  Parser,
2221
- formatLinks
2673
+ StreamParseError,
2674
+ StreamParser,
2675
+ formatLinks,
2676
+ stripComments
2222
2677
  };
package/index.d.ts CHANGED
@@ -116,6 +116,12 @@ export interface ParserOptions {
116
116
  * Maximum nesting depth (default: 1000)
117
117
  */
118
118
  maxDepth?: number;
119
+
120
+ /**
121
+ * If false, read `#` as an ordinary character instead of the start of a
122
+ * comment (default: true)
123
+ */
124
+ comments?: boolean;
119
125
  }
120
126
 
121
127
  /**
@@ -134,6 +140,11 @@ export class Parser {
134
140
  */
135
141
  maxDepth: number;
136
142
 
143
+ /**
144
+ * Whether `#` starts a comment that runs to the end of its line
145
+ */
146
+ comments: boolean;
147
+
137
148
  /**
138
149
  * Create a new Parser instance
139
150
  * @param options - Parser options
@@ -149,6 +160,90 @@ export class Parser {
149
160
  parse(input: string): Link[];
150
161
  }
151
162
 
163
+ /** Options for incremental parsing. */
164
+ export interface StreamParserOptions extends ParserOptions {
165
+ /** Canonical parser instance to use for completed records. */
166
+ parser?: Parser;
167
+
168
+ /** Largest unresolved record retained in memory (default: 10MB). */
169
+ maxBufferSize?: number;
170
+
171
+ /** Retain emitted links for end() and drain() (default: true). */
172
+ collect?: boolean;
173
+ }
174
+
175
+ /** Position immediately after the last character written. */
176
+ export interface StreamPosition {
177
+ offset: number;
178
+ line: number;
179
+ column: number;
180
+ buffered: number;
181
+ }
182
+
183
+ /** Canonical parse error translated to coordinates in the complete stream. */
184
+ export class StreamParseError extends Error {
185
+ cause: Error;
186
+ offset: number;
187
+ line: number;
188
+ column: number;
189
+ found: string | null;
190
+ lineText: string;
191
+ snippet: string;
192
+ }
193
+
194
+ /** Incrementally emits complete top-level Links Notation records. */
195
+ export class StreamParser {
196
+ parser: Parser;
197
+ comments: boolean;
198
+ maxBufferSize: number;
199
+ collect: boolean;
200
+
201
+ constructor(options?: StreamParserOptions);
202
+
203
+ /** Feed a chunk and return links made complete by it. */
204
+ write(chunk: string): Link[];
205
+
206
+ /** Finish the stream, optionally after consuming one final chunk. */
207
+ end(chunk?: string): Link[];
208
+
209
+ /** Return and forget retained links. */
210
+ drain(): Link[];
211
+
212
+ /** Reuse this parser while preserving its options and listeners. */
213
+ reset(): this;
214
+
215
+ /** Return the absolute stream position and unresolved buffer size. */
216
+ position(): StreamPosition;
217
+
218
+ on(event: 'link', listener: (link: Link) => void): this;
219
+ on(event: 'end', listener: (links: Link[]) => void): this;
220
+ on(event: 'error', listener: (error: Error) => void): this;
221
+ once(event: 'link', listener: (link: Link) => void): this;
222
+ once(event: 'end', listener: (links: Link[]) => void): this;
223
+ once(event: 'error', listener: (error: Error) => void): this;
224
+ off(event: string, listener: (...arguments_: any[]) => void): this;
225
+
226
+ /** Lazily parse a synchronous iterable of chunks. */
227
+ static parse(
228
+ chunks: Iterable<string>,
229
+ options?: StreamParserOptions
230
+ ): IterableIterator<Link>;
231
+
232
+ /** Lazily parse an asynchronous iterable of chunks. */
233
+ static parseAsync(
234
+ chunks: AsyncIterable<string> | Iterable<string>,
235
+ options?: StreamParserOptions
236
+ ): AsyncIterableIterator<Link>;
237
+ }
238
+
239
+ /**
240
+ * Blank out every comment in a document, keeping its length so that every
241
+ * character stays at the offset it was written at.
242
+ * @param document - The document to read
243
+ * @returns The document, of the same length, without its comments
244
+ */
245
+ export function stripComments(document: string): string;
246
+
152
247
  /**
153
248
  * The position a parse stopped at, as the generated parser reports it
154
249
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "links-notation",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
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
@@ -95,7 +95,11 @@ export class Link {
95
95
  const hasSingleQuote = reference.includes("'");
96
96
  const hasDoubleQuote = reference.includes('"');
97
97
 
98
+ // A reference that begins with a `#` has to be quoted, or it would read
99
+ // back as a comment. A `#` anywhere else in a reference is content
100
+ // (`issue#1047`), so only the first character matters.
98
101
  const needsQuoting =
102
+ reference.startsWith('#') ||
99
103
  reference.includes(':') ||
100
104
  reference.includes('(') ||
101
105
  reference.includes(')') ||