punctilio 3.11.3 → 3.11.4

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.
Files changed (66) hide show
  1. package/dist/cli.d.ts +1 -12
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +4 -26
  4. package/dist/cli.js.map +1 -1
  5. package/dist/constants.d.ts +13 -61
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/constants.js +12 -61
  8. package/dist/constants.js.map +1 -1
  9. package/dist/dashes.d.ts +0 -9
  10. package/dist/dashes.d.ts.map +1 -1
  11. package/dist/dashes.js +4 -26
  12. package/dist/dashes.js.map +1 -1
  13. package/dist/html.d.ts +1 -32
  14. package/dist/html.d.ts.map +1 -1
  15. package/dist/html.js +1 -29
  16. package/dist/html.js.map +1 -1
  17. package/dist/index.d.ts +0 -39
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +0 -39
  20. package/dist/index.js.map +1 -1
  21. package/dist/markdown.d.ts +1 -34
  22. package/dist/markdown.d.ts.map +1 -1
  23. package/dist/markdown.js +1 -34
  24. package/dist/markdown.js.map +1 -1
  25. package/dist/nbsp.d.ts +1 -68
  26. package/dist/nbsp.d.ts.map +1 -1
  27. package/dist/nbsp.js +6 -86
  28. package/dist/nbsp.js.map +1 -1
  29. package/dist/prettier-plugin.d.ts +0 -14
  30. package/dist/prettier-plugin.d.ts.map +1 -1
  31. package/dist/prettier-plugin.js +0 -19
  32. package/dist/prettier-plugin.js.map +1 -1
  33. package/dist/quotes.d.ts +0 -3
  34. package/dist/quotes.d.ts.map +1 -1
  35. package/dist/quotes.js +11 -58
  36. package/dist/quotes.js.map +1 -1
  37. package/dist/rehype.d.ts +4 -141
  38. package/dist/rehype.d.ts.map +1 -1
  39. package/dist/rehype.js +7 -144
  40. package/dist/rehype.js.map +1 -1
  41. package/dist/remark.d.ts +0 -30
  42. package/dist/remark.d.ts.map +1 -1
  43. package/dist/remark.js +0 -41
  44. package/dist/remark.js.map +1 -1
  45. package/dist/symbols.d.ts +1 -7
  46. package/dist/symbols.d.ts.map +1 -1
  47. package/dist/symbols.js +9 -53
  48. package/dist/symbols.js.map +1 -1
  49. package/dist/utils.d.ts +10 -58
  50. package/dist/utils.d.ts.map +1 -1
  51. package/dist/utils.js +10 -58
  52. package/dist/utils.js.map +1 -1
  53. package/package.json +5 -5
  54. package/src/cli.ts +5 -31
  55. package/src/constants.ts +13 -62
  56. package/src/dashes.ts +4 -27
  57. package/src/html.ts +1 -33
  58. package/src/index.ts +0 -40
  59. package/src/markdown.ts +1 -38
  60. package/src/nbsp.ts +6 -87
  61. package/src/prettier-plugin.ts +0 -20
  62. package/src/quotes.ts +11 -59
  63. package/src/rehype.ts +8 -162
  64. package/src/remark.ts +0 -42
  65. package/src/symbols.ts +9 -66
  66. package/src/utils.ts +10 -60
package/src/symbols.ts CHANGED
@@ -1,7 +1,3 @@
1
- /**
2
- * Symbol transformations: ellipses, multiplication, math symbols, arrows.
3
- */
4
-
5
1
  import { cachedRegExp, DEFAULT_SEPARATOR, getEscapedSeparator, LATIN_LETTERS, SPACE_CHARS, UNICODE_SYMBOLS, wordBoundaryEnd } from "./constants.js"
6
2
  import { namedGroups } from "./utils.js"
7
3
 
@@ -138,19 +134,10 @@ export function multiplication(text: string, options: SymbolOptions = {}): strin
138
134
  return text
139
135
  }
140
136
 
141
- /**
142
- * Builds a negative lookahead that must see through an optional separator
143
- * so e.g. `!<SEP>=` is correctly recognized as the start of `!==`.
144
- */
145
137
  type MathLookaheadBuilder = (escapedSeparator: string) => string
146
138
 
147
- /** [leftChars, rightChars, negativeLookaheadBuilder, replacement] */
148
139
  type MathSymbolRule = [string, string, MathLookaheadBuilder, string]
149
140
 
150
- /**
151
- * Math symbol replacement map.
152
- * The separator is inserted between left and right when building the regex.
153
- */
154
141
  const MATH_SYMBOL_MAP: MathSymbolRule[] = [
155
142
  ["!", "=", (chr) => `(?!${chr}?=)`, NOT_EQUAL],
156
143
  ["\\+/", "-", () => "", PLUS_MINUS],
@@ -178,18 +165,11 @@ export function mathSymbols(text: string, options: SymbolOptions = {}): string {
178
165
  return text
179
166
  }
180
167
 
181
- /** Predicate that decides whether a legal symbol should be converted based on surrounding text. */
182
168
  type ContextPredicate = (before: string, after: string) => boolean
183
169
 
184
- /**
185
- * Context window, in characters, examined on each side of a legal-symbol
186
- * candidate. Large enough to fit a 4-digit year plus surrounding whitespace
187
- * or the word "copyright" plus a space, but small enough that the slice
188
- * stays cheap on long inputs.
189
- */
170
+ // 25 chars: fits "copyright " or a 4-digit year with padding, without making slicing expensive.
190
171
  const LEGAL_SYMBOL_CONTEXT_WINDOW = 25
191
172
 
192
- /** Returns true when the context window ends with a path-like fragment (slash + non-whitespace). */
193
173
  const isPathContext = (before: string): boolean => {
194
174
  const parts = before.split(/\s+/)
195
175
  const trailing = parts[parts.length - 1]
@@ -197,11 +177,6 @@ const isPathContext = (before: string): boolean => {
197
177
  return slashIdx >= 0 && slashIdx < trailing.length - 1
198
178
  }
199
179
 
200
- /**
201
- * Context-aware replacement for legal symbols like (c), (r), (tm).
202
- * Extracts surrounding text, strips separator characters from the context
203
- * windows, and delegates the convert/skip decision to a predicate.
204
- */
205
180
  function contextAwareLegalReplace(
206
181
  text: string,
207
182
  pattern: RegExp,
@@ -249,19 +224,9 @@ export function legalSymbols(text: string, options: SymbolOptions = {}): string
249
224
  return text
250
225
  }
251
226
 
252
- /**
253
- * Builds an arrow-shape regex fragment from the escaped separator pattern.
254
- * The separator may appear between the halves of a bidirectional `<->`.
255
- */
256
227
  type ArrowPatternBuilder = (escapedSeparator: string) => string
257
228
 
258
- /**
259
- * Arrow pattern map: shape builder → Unicode symbol.
260
- *
261
- * The leading `<` on the bidi pattern anchors its repeated-dash-run group
262
- * so it stays linear-time; the right/left arrows can't anchor that way, so
263
- * they only admit one optional separator at the un-anchored end.
264
- */
229
+ // Leading `<` on bidi anchors the dash-run group for linear time.
265
230
  const ARROW_RULES: readonly [ArrowPatternBuilder, string][] = [
266
231
  [(sep) => `<${sep}?-+(?:${sep}-+)*${sep}?>`, ARROW_LEFT_RIGHT],
267
232
  [(sep) => `-+${sep}?>`, ARROW_RIGHT],
@@ -292,7 +257,6 @@ export function arrows(text: string, options: SymbolOptions = {}): string {
292
257
  return text
293
258
  }
294
259
 
295
- /** Convert "20 C" or "20 F" to "20 °C" or "20 °F". Only matches uppercase C/F. */
296
260
  export function degrees(text: string, options: SymbolOptions = {}): string {
297
261
  const chr = getEscapedSeparator(options)
298
262
 
@@ -309,7 +273,7 @@ export function degrees(text: string, options: SymbolOptions = {}): string {
309
273
  }
310
274
 
311
275
  /**
312
- * Build a regex that matches all quote characters in priority order:
276
+ * Matches quote characters in priority order:
313
277
  * 1. Prime candidate: digit + quote + non-letter (e.g., 5', 12")
314
278
  * 2. Contraction: letter + quote + letter (e.g., it's, don't, O'Brien)
315
279
  * 3. Trailing apostrophe: letter + quote + non-letter (e.g., dogs')
@@ -329,19 +293,9 @@ function buildQuoteClassificationPattern(
329
293
  )
330
294
  }
331
295
 
332
- /**
333
- * Replace callback that converts prime candidates while respecting quote balance.
334
- * Contractions and trailing apostrophes are recognized so they don't poison the
335
- * balance tracker. A prime candidate converts only when no unmatched opening
336
- * quote precedes it; otherwise it acts as a closing quote.
337
- *
338
- * For double primes (″): since ' and " are processed in separate passes,
339
- * 5'10" becomes 5′10" after the single-quote pass. Inside balanced double
340
- * quotes (e.g., "He is 5′10" tall"), the balance tracker would normally treat
341
- * the " after 10 as a closing quote. To handle this, we check whether the "
342
- * is preceded by ′ + digits — if so, it's inches notation and converts
343
- * regardless of balance.
344
- */
296
+ // Converts prime candidates while tracking quote balance. A prime after a
297
+ // digit converts only when no unmatched opener precedes it. Inside balanced
298
+ // quotes, a ′+digits context (feet-inches like 5′10") still converts.
345
299
  function balancedPrimeReplacer(primeChar: string, escapedSeparator: string) {
346
300
  // Pre-compile the feet-inches context pattern for the double-prime pass.
347
301
  // Matches when the text before a prime candidate ends with ′ followed by
@@ -421,13 +375,8 @@ export function primeMarks(text: string, options: SymbolOptions = {}): string {
421
375
  return text
422
376
  }
423
377
 
424
- /** [numerator, denominator, unicodeChar] */
425
378
  type FractionRule = [string, string, string]
426
379
 
427
- /**
428
- * Supported Unicode fractions.
429
- * The regex alternation and lookup map are both derived from this list.
430
- */
431
380
  const FRACTION_TUPLES: FractionRule[] = [
432
381
  ["1", "4", UNICODE_SYMBOLS.FRACTION_1_4],
433
382
  ["1", "2", UNICODE_SYMBOLS.FRACTION_1_2],
@@ -504,7 +453,7 @@ export function superscriptOrdinal(text: string, options: SymbolOptions = {}): s
504
453
  })
505
454
  }
506
455
 
507
- /** Collapse multiple spaces (including tabs) to single space. Preserves the highest-priority space type present in the run: NBSP > NNBSP > regular. Leading whitespace at the start of a line (after `\n` or start-of-string) is preserved so indented blocks (e.g. HN-style code) survive. */
456
+ // Preserves highest-priority space type (NBSP > NNBSP > regular) and leading indentation.
508
457
  export function collapseSpaces(text: string): string {
509
458
  return text.replace(cachedRegExp(`(?<=[^\\n${SPACE_CHARS}])[${SPACE_CHARS}]{2,}`, "g"), (match) => {
510
459
  if (match.includes(NBSP)) return NBSP
@@ -513,14 +462,9 @@ export function collapseSpaces(text: string): string {
513
462
  })
514
463
  }
515
464
 
516
- /** [firstChar, repeatedChar, replacement] */
517
465
  type LigatureRule = [string, string, string]
518
466
 
519
- /**
520
- * Punctuation ligature map.
521
- * Pattern: first(sep)?repeated(?:sep?repeated)* → replacement
522
- * Order matters: handle mixed punctuation first, then repeated
523
- */
467
+ // Order matters: mixed punctuation first, then repeated.
524
468
  const PUNCTUATION_LIGATURE_MAP: LigatureRule[] = [
525
469
  ["\\?", "!", QUESTION_EXCLAMATION], // ?!+ → ⁈
526
470
  ["!", "\\?", EXCLAMATION_QUESTION], // !?+ → ⁉
@@ -528,7 +472,7 @@ const PUNCTUATION_LIGATURE_MAP: LigatureRule[] = [
528
472
  ["!", "!", "!"], // !!+ → ! (normalize)
529
473
  ]
530
474
 
531
- /** Convert ?? to ⁇, ?! to ⁈, !? to ⁉. Poor font support, disabled by default. */
475
+ /** Convert ?? to ⁇, ?! to ⁈, !? to ⁉. Disabled by default (poor font support). */
532
476
  export function punctuationLigatures(text: string, options: SymbolOptions = {}): string {
533
477
  const chr = getEscapedSeparator(options)
534
478
  const sepPattern = cachedRegExp(chr, "g")
@@ -548,7 +492,6 @@ export function punctuationLigatures(text: string, options: SymbolOptions = {}):
548
492
  return text
549
493
  }
550
494
 
551
- /** Apply all symbol transforms. degrees/fractions excluded (too aggressive). */
552
495
  export function symbolTransform(text: string, options: SymbolOptions = {}): string {
553
496
  text = ellipsis(text, options)
554
497
  text = multiplication(text, options)
package/src/utils.ts CHANGED
@@ -1,20 +1,10 @@
1
- /**
2
- * Utility functions for text transformation validation.
3
- *
4
- * @module utils
5
- */
6
-
7
1
  import { DEFAULT_SEPARATOR, ISSUES_URL } from "./constants.js"
8
2
 
9
- /** Threshold above which strings are truncated in error messages. */
10
3
  const ERROR_STRING_THRESHOLD = 2000
11
4
 
12
5
  /**
13
- * Formats a string for error messages. If the string exceeds the threshold,
14
- * truncates it and shows the total length.
15
- *
16
- * In Node.js environments, also writes the full content to stderr
17
- * so it's available in build logs for debugging.
6
+ * Formats a string for error messages, truncating above the threshold.
7
+ * Writes full content to stderr in Node.js so build logs capture it.
18
8
  */
19
9
  export function formatErrorString(content: string, label: string): string {
20
10
  if (content.length <= ERROR_STRING_THRESHOLD) {
@@ -36,12 +26,7 @@ export function formatErrorString(content: string, label: string): string {
36
26
  return `[${label}: ${JSON.stringify(truncated)}... (${content.length} chars total)]`
37
27
  }
38
28
 
39
- /**
40
- * Throws if any text node contains the separator character, which would
41
- * corrupt the split/join mechanism used by the rehype and remark plugins.
42
- *
43
- * @throws Error if the separator is found in any text value
44
- */
29
+ /** Throws if any text node contains the separator (would corrupt split/join). */
45
30
  export function assertSeparatorAbsent(textValues: string[], separator: string): void {
46
31
  for (const value of textValues) {
47
32
  if (!value.includes(separator)) continue
@@ -57,9 +42,6 @@ export function assertSeparatorAbsent(textValues: string[], separator: string):
57
42
  }
58
43
  }
59
44
 
60
- /**
61
- * Counts non-overlapping occurrences of the separator string in the text.
62
- */
63
45
  export function countSeparators(text: string, separator: string = DEFAULT_SEPARATOR): number {
64
46
  if (separator.length === 0) return 0
65
47
  let count = 0
@@ -71,24 +53,15 @@ export function countSeparators(text: string, separator: string = DEFAULT_SEPARA
71
53
  return count
72
54
  }
73
55
 
74
- /** Minimal text-node interface shared by mdast.Text and hast.Text. */
75
56
  interface TextNode {
76
57
  value: string
77
58
  }
78
59
 
79
60
  /**
80
61
  * Applies a text transformation across an array of text nodes using the
81
- * separator-marking technique.
82
- *
83
- * 1. Validates no text node already contains the separator.
84
- * 2. Appends the separator to each node's value, concatenates into one string.
85
- * 3. Runs the transform function on the concatenated string.
86
- * 4. Splits the result back on the separator and writes each fragment
87
- * back into the corresponding text node.
88
- *
89
- * `textNodes` is mutated in place.
90
- *
91
- * @throws Error if the transformation alters the number of text nodes
62
+ * separator-marking technique: appends the separator to each node's value,
63
+ * concatenates, transforms, then splits back on the separator and writes
64
+ * each fragment into the corresponding text node. Mutates `textNodes`.
92
65
  */
93
66
  export function transformTextNodes(
94
67
  textNodes: TextNode[],
@@ -115,12 +88,7 @@ export function transformTextNodes(
115
88
  })
116
89
  }
117
90
 
118
- /**
119
- * Validates that a transformation preserved the separator count.
120
- * Throws an error if separators were added or removed.
121
- *
122
- * @throws Error if separator count changed
123
- */
91
+ /** Throws if a transformation changed the separator count. */
124
92
  export function assertSeparatorCountPreserved(
125
93
  original: string,
126
94
  transformed: string,
@@ -138,12 +106,7 @@ export function assertSeparatorCountPreserved(
138
106
  }
139
107
  }
140
108
 
141
- /**
142
- * Returns a copy of `obj` with `undefined`-valued keys removed.
143
- * Used so caller-supplied `{ foo: undefined }` doesn't override a default.
144
- *
145
- * @internal
146
- */
109
+ /** Returns a copy of `obj` with `undefined`-valued keys removed. @internal */
147
110
  export function filterUndefined<T extends object>(obj: T): Partial<T> {
148
111
  const result: Record<string, unknown> = {}
149
112
  for (const [k, v] of Object.entries(obj)) {
@@ -152,13 +115,7 @@ export function filterUndefined<T extends object>(obj: T): Partial<T> {
152
115
  return result as Partial<T>
153
116
  }
154
117
 
155
- /**
156
- * Returns a deterministic JSON-style string for an options object: drops
157
- * `undefined` values and sorts keys, so equivalent option sets always
158
- * produce the same string (suitable as a cache or LRU key).
159
- *
160
- * @internal
161
- */
118
+ /** Deterministic JSON string for an options object, suitable as cache key. @internal */
162
119
  export function stableStringify(obj: object): string {
163
120
  const entries = Object.entries(obj)
164
121
  .filter(([, v]) => v !== undefined)
@@ -166,14 +123,7 @@ export function stableStringify(obj: object): string {
166
123
  return JSON.stringify(entries)
167
124
  }
168
125
 
169
- /**
170
- * Extracts the named-groups object from a `String.prototype.replace`
171
- * callback's argument tuple. The last element is always the groups
172
- * object when the regex has named captures. Callers supply the group
173
- * shape via the type parameter to localise the unavoidable type cast.
174
- *
175
- * @internal
176
- */
126
+ /** Extracts named groups from a `.replace()` callback's arguments. @internal */
177
127
  export function namedGroups<G>(args: unknown[]): G {
178
128
  return args[args.length - 1] as G
179
129
  }