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.
- package/dist/cli.d.ts +1 -12
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -26
- package/dist/cli.js.map +1 -1
- package/dist/constants.d.ts +13 -61
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +12 -61
- package/dist/constants.js.map +1 -1
- package/dist/dashes.d.ts +0 -9
- package/dist/dashes.d.ts.map +1 -1
- package/dist/dashes.js +4 -26
- package/dist/dashes.js.map +1 -1
- package/dist/html.d.ts +1 -32
- package/dist/html.d.ts.map +1 -1
- package/dist/html.js +1 -29
- package/dist/html.js.map +1 -1
- package/dist/index.d.ts +0 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -39
- package/dist/index.js.map +1 -1
- package/dist/markdown.d.ts +1 -34
- package/dist/markdown.d.ts.map +1 -1
- package/dist/markdown.js +1 -34
- package/dist/markdown.js.map +1 -1
- package/dist/nbsp.d.ts +1 -68
- package/dist/nbsp.d.ts.map +1 -1
- package/dist/nbsp.js +6 -86
- package/dist/nbsp.js.map +1 -1
- package/dist/prettier-plugin.d.ts +0 -14
- package/dist/prettier-plugin.d.ts.map +1 -1
- package/dist/prettier-plugin.js +0 -19
- package/dist/prettier-plugin.js.map +1 -1
- package/dist/quotes.d.ts +0 -3
- package/dist/quotes.d.ts.map +1 -1
- package/dist/quotes.js +11 -58
- package/dist/quotes.js.map +1 -1
- package/dist/rehype.d.ts +4 -141
- package/dist/rehype.d.ts.map +1 -1
- package/dist/rehype.js +7 -144
- package/dist/rehype.js.map +1 -1
- package/dist/remark.d.ts +0 -30
- package/dist/remark.d.ts.map +1 -1
- package/dist/remark.js +0 -41
- package/dist/remark.js.map +1 -1
- package/dist/symbols.d.ts +1 -7
- package/dist/symbols.d.ts.map +1 -1
- package/dist/symbols.js +9 -53
- package/dist/symbols.js.map +1 -1
- package/dist/utils.d.ts +10 -58
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +10 -58
- package/dist/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/cli.ts +5 -31
- package/src/constants.ts +13 -62
- package/src/dashes.ts +4 -27
- package/src/html.ts +1 -33
- package/src/index.ts +0 -40
- package/src/markdown.ts +1 -38
- package/src/nbsp.ts +6 -87
- package/src/prettier-plugin.ts +0 -20
- package/src/quotes.ts +11 -59
- package/src/rehype.ts +8 -162
- package/src/remark.ts +0 -42
- package/src/symbols.ts +9 -66
- package/src/utils.ts +10 -60
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAE9D,MAAM,sBAAsB,GAAG,IAAI,CAAA;AAEnC;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,KAAa;IAC9D,IAAI,OAAO,CAAC,MAAM,IAAI,sBAAsB,EAAE,CAAC;QAC7C,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IAChC,CAAC;IAED,yDAAyD;IACzD,IAAI,CAAC;QACH,IAAI,OAAO,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,KAAK,UAAU,EAAE,CAAC;YAC5D,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAC7B,gBAAgB,KAAK,kBAAkB,OAAO,CAAC,MAAM,cAAc,OAAO,MAAM,CACjF,CAAA;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,gDAAgD;IAClD,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAA;IAC1D,OAAO,IAAI,KAAK,KAAK,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,OAAO,CAAC,MAAM,gBAAgB,CAAA;AACtF,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,qBAAqB,CAAC,UAAoB,EAAE,SAAiB;IAC3E,KAAK,MAAM,KAAK,IAAI,UAAU,EAAE,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,SAAQ;QAExC,MAAM,UAAU,GAAG,CAAC,GAAG,SAAS,CAAC;aAC9B,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;aAChF,IAAI,CAAC,GAAG,CAAC,CAAA;QACZ,MAAM,IAAI,KAAK,CACb,wCAAwC,UAAU,yCAAyC;YAC3F,uFAAuF;YACvF,SAAS,iBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,CAC7C,CAAA;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,YAAoB,iBAAiB;IACjF,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IACpC,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QACvD,KAAK,EAAE,CAAA;QACP,KAAK,IAAI,SAAS,CAAC,MAAM,CAAA;IAC3B,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAMD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAChC,SAAqB,EACrB,WAAsC,EACtC,SAAiB;IAEjB,qBAAqB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,CAAA;IAE/D,MAAM,aAAa,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACxE,MAAM,kBAAkB,GAAG,WAAW,CAAC,aAAa,CAAC,CAAA;IACrD,MAAM,oBAAoB,GAAG,kBAAkB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IAE7E,qFAAqF;IACrF,IAAI,oBAAoB,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CACb,mDAAmD;YACjD,YAAY,SAAS,CAAC,MAAM,SAAS,oBAAoB,CAAC,MAAM,IAAI;YACpE,UAAU,iBAAiB,CAAC,aAAa,EAAE,OAAO,CAAC,EAAE,CACxD,CAAA;IACH,CAAC;IAED,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE;QAC7B,CAAC,CAAC,KAAK,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,6BAA6B,CAC3C,QAAgB,EAChB,WAAmB,EACnB,YAAoB,iBAAiB,EACrC,gBAAwB,WAAW;IAEnC,MAAM,aAAa,GAAG,eAAe,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAA;IAC1D,MAAM,gBAAgB,GAAG,eAAe,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;IAChE,IAAI,aAAa,KAAK,gBAAgB,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CACb,GAAG,aAAa,sCAAsC,aAAa,SAAS,gBAAgB,KAAK;YACjG,uDAAuD,UAAU,IAAI;YACrE,gDAAgD,CACjD,CAAA;IACH,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,eAAe,CAAmB,GAAM;IACtD,MAAM,MAAM,GAA4B,EAAE,CAAA;IAC1C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,KAAK,SAAS;YAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACpC,CAAC;IACD,OAAO,MAAoB,CAAA;AAC7B,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;SAChC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;SAClC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAA;IACzC,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;AAChC,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,WAAW,CAAI,IAAe;IAC5C,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAM,CAAA;AACnC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "punctilio",
|
|
3
|
-
"version": "3.11.
|
|
3
|
+
"version": "3.11.4",
|
|
4
4
|
"description": "Smart typography transformations: curly quotes, em-dashes, en-dashes, and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"hastscript": "9.0.1",
|
|
93
93
|
"jest": "29.7.0",
|
|
94
94
|
"lint-staged": "16.4.0",
|
|
95
|
-
"prettier": "3.8.
|
|
95
|
+
"prettier": "3.8.3",
|
|
96
96
|
"recheck": "4.5.0",
|
|
97
97
|
"rehype-parse": "9.0.1",
|
|
98
98
|
"rehype-stringify": "10.0.1",
|
|
@@ -105,10 +105,10 @@
|
|
|
105
105
|
"smartquotes": "2.3.2",
|
|
106
106
|
"smartypants": "0.2.2",
|
|
107
107
|
"tipograph": "0.7.4",
|
|
108
|
-
"ts-jest": "29.4.
|
|
109
|
-
"tsx": "4.
|
|
108
|
+
"ts-jest": "29.4.11",
|
|
109
|
+
"tsx": "4.22.3",
|
|
110
110
|
"typescript": "5.9.3",
|
|
111
|
-
"typescript-eslint": "8.59.
|
|
111
|
+
"typescript-eslint": "8.59.4",
|
|
112
112
|
"typograf": "7.7.0",
|
|
113
113
|
"unified": "11.0.5"
|
|
114
114
|
},
|
package/src/cli.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Command-line entry point: format Markdown and HTML files in place,
|
|
4
|
-
* or check whether they would be reformatted (for use as a pre-commit hook).
|
|
5
|
-
*
|
|
6
|
-
* @packageDocumentation
|
|
7
|
-
*/
|
|
8
2
|
|
|
9
3
|
import { createHash } from "node:crypto"
|
|
10
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
@@ -42,11 +36,7 @@ const RULE_MARKERS = ["-", "*", "_"] as const
|
|
|
42
36
|
const MARKDOWN_EXTENSIONS = new Set([".md", ".markdown"])
|
|
43
37
|
const HTML_EXTENSIONS = new Set([".html", ".htm"])
|
|
44
38
|
|
|
45
|
-
|
|
46
|
-
* Shape of `program.opts()` after parsing. Fields named after their CLI
|
|
47
|
-
* flags (camelCased by commander). `commander.choices()` guarantees the
|
|
48
|
-
* literal types at runtime; we assert them at the boundary.
|
|
49
|
-
*/
|
|
39
|
+
// Fields match commander's camelCased flag names; .choices() guarantees literals at runtime.
|
|
50
40
|
interface ParsedFlags {
|
|
51
41
|
check?: boolean
|
|
52
42
|
cache?: boolean
|
|
@@ -147,8 +137,7 @@ function isGlobPattern(pattern: string): boolean {
|
|
|
147
137
|
return /[*?[\]{}]/.test(pattern)
|
|
148
138
|
}
|
|
149
139
|
|
|
150
|
-
// Non-glob positionals pass through as literal paths so they error loudly
|
|
151
|
-
// if missing, matching the prior behavior of `punctilio file.md`.
|
|
140
|
+
// Non-glob positionals pass through as literal paths so they error loudly if missing.
|
|
152
141
|
async function discoverFiles(
|
|
153
142
|
patterns: string[],
|
|
154
143
|
cwd: string,
|
|
@@ -213,12 +202,7 @@ function saveCache(location: string, cache: Cache): void {
|
|
|
213
202
|
writeFileSync(location, JSON.stringify(cache))
|
|
214
203
|
}
|
|
215
204
|
|
|
216
|
-
|
|
217
|
-
* Cache key for a file. Always cwd-relative so a project that moves
|
|
218
|
-
* directories (or syncs the cache file across machines via the same
|
|
219
|
-
* repo layout) keeps hitting the cache. Files outside cwd appear as
|
|
220
|
-
* `../foo.md` and still hash consistently.
|
|
221
|
-
*/
|
|
205
|
+
// cwd-relative so the cache survives directory moves and cross-machine syncs.
|
|
222
206
|
function cacheKey(filePath: string, cwd: string): string {
|
|
223
207
|
return relative(cwd, filePath)
|
|
224
208
|
}
|
|
@@ -249,12 +233,7 @@ async function transformContent(input: string, type: FileType, opts: CliOptions)
|
|
|
249
233
|
return type === "md" ? transformMarkdown(input, opts) : transformHtml(input, opts)
|
|
250
234
|
}
|
|
251
235
|
|
|
252
|
-
|
|
253
|
-
* Preserves whether the original file ends with a trailing newline.
|
|
254
|
-
* The unified pipelines for Markdown and HTML both unconditionally append
|
|
255
|
-
* one, so without this adjustment every file lacking a trailing newline
|
|
256
|
-
* would always appear as needing a rewrite.
|
|
257
|
-
*/
|
|
236
|
+
// Unified pipelines unconditionally append a trailing newline; undo if original lacked one.
|
|
258
237
|
function matchTrailingNewline(original: string, formatted: string): string {
|
|
259
238
|
const originalHas = original.endsWith("\n")
|
|
260
239
|
const formattedHas = formatted.endsWith("\n")
|
|
@@ -282,12 +261,7 @@ async function readPackageVersion(): Promise<string> {
|
|
|
282
261
|
}
|
|
283
262
|
}
|
|
284
263
|
|
|
285
|
-
/**
|
|
286
|
-
* Runs the CLI with the given arguments and I/O streams.
|
|
287
|
-
*
|
|
288
|
-
* @returns Exit code: 0 if no changes (or all changes written),
|
|
289
|
-
* 1 if `--check` saw a file that would change, 2 for usage errors.
|
|
290
|
-
*/
|
|
264
|
+
/** Returns exit code: 0 success, 1 `--check` saw changes, 2 usage error. */
|
|
291
265
|
export async function runCli(args: string[], io: CliIO): Promise<number> {
|
|
292
266
|
const version = await readPackageVersion()
|
|
293
267
|
const program = buildProgram(version, io)
|
package/src/constants.ts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared Unicode constants and regex utilities.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
import escapeStringRegexp from "escape-string-regexp"
|
|
6
2
|
import QuickLRU from "quick-lru"
|
|
7
3
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*/
|
|
4
|
+
export { escapeStringRegexp }
|
|
5
|
+
|
|
11
6
|
export const UNICODE_SYMBOLS = {
|
|
12
7
|
ELLIPSIS: "\u2026",
|
|
13
8
|
MULTIPLICATION: "\u00D7",
|
|
@@ -82,13 +77,7 @@ export const UNICODE_SYMBOLS = {
|
|
|
82
77
|
GREEK_QUESTION_MARK: "\u037E", // ; (visually identical to ASCII semicolon)
|
|
83
78
|
} as const
|
|
84
79
|
|
|
85
|
-
/**
|
|
86
|
-
* All terminal punctuation characters that signal a quote is "already terminated".
|
|
87
|
-
* Covers ASCII, ellipsis, punctuation ligatures, interrobang, CJK fullwidth,
|
|
88
|
-
* CJK ideographic, Arabic, and Greek question mark.
|
|
89
|
-
*
|
|
90
|
-
* Used as a regex character class fragment in quotes.ts and as a test fixture.
|
|
91
|
-
*/
|
|
80
|
+
/** All terminal punctuation chars for regex character classes (ASCII through CJK/Arabic/Greek). */
|
|
92
81
|
export const TERMINAL_PUNCTUATION = [
|
|
93
82
|
"!", "?", ".", ",", ";", ":",
|
|
94
83
|
UNICODE_SYMBOLS.ELLIPSIS,
|
|
@@ -128,13 +117,8 @@ export const DEFAULT_SEPARATOR = "\uE000\uE001"
|
|
|
128
117
|
const ESCAPED_DEFAULT_SEPARATOR = escapeStringRegexp(DEFAULT_SEPARATOR)
|
|
129
118
|
|
|
130
119
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* apply to the entire separator sequence (critical for multi-character
|
|
134
|
-
* separators like the default U+E000 U+E001).
|
|
135
|
-
*
|
|
136
|
-
* @param options - Object with an optional `separator` field
|
|
137
|
-
* @returns Regex-escaped separator string wrapped in `(?:...)`
|
|
120
|
+
* Regex-escaped separator wrapped in `(?:...)` so quantifiers apply to
|
|
121
|
+
* the entire multi-character sequence.
|
|
138
122
|
*/
|
|
139
123
|
export function getEscapedSeparator(options: { separator?: string }): string {
|
|
140
124
|
const escaped = options.separator
|
|
@@ -143,10 +127,7 @@ export function getEscapedSeparator(options: { separator?: string }): string {
|
|
|
143
127
|
return `(?:${escaped})`
|
|
144
128
|
}
|
|
145
129
|
|
|
146
|
-
/**
|
|
147
|
-
* Characters that have special meaning in regular expressions.
|
|
148
|
-
* Used for testing that separator escaping works correctly.
|
|
149
|
-
*/
|
|
130
|
+
/** Regex-special characters, used for testing separator escaping. */
|
|
150
131
|
export const REGEX_SPECIAL_CHARS = [".", "*", "+", "?", "^", "$", "[", "]", "\\", "|", "(", ")"] as const
|
|
151
132
|
|
|
152
133
|
/**
|
|
@@ -179,45 +160,22 @@ export function wordBoundaryEnd(escapedSeparator: string): string {
|
|
|
179
160
|
return `\\b(?!${escapedSeparator}{0,${MAX_BOUNDARY_SEPARATORS}}\\w)`
|
|
180
161
|
}
|
|
181
162
|
|
|
182
|
-
/**
|
|
183
|
-
* Pattern string for space characters (regular space, tab, non-breaking space,
|
|
184
|
-
* and narrow no-break space). Use inside regex character classes: `[${SPACE_CHARS}]`
|
|
185
|
-
*/
|
|
163
|
+
/** Space chars for regex `[...]`: regular space, tab, NBSP, NNBSP. */
|
|
186
164
|
export const SPACE_CHARS = ` \t${UNICODE_SYMBOLS.NBSP}${UNICODE_SYMBOLS.NNBSP}`
|
|
187
165
|
|
|
188
|
-
/**
|
|
189
|
-
* Pattern string for the non-breaking space characters only (NBSP and NNBSP).
|
|
190
|
-
* Use inside regex character classes: `[${NBSP_CHARS}]`. Used by patterns
|
|
191
|
-
* that need to detect existing nbsp chains without also matching plain spaces.
|
|
192
|
-
*/
|
|
166
|
+
/** Non-breaking space chars only (NBSP, NNBSP) for regex `[...]`. */
|
|
193
167
|
export const NBSP_CHARS = `${UNICODE_SYMBOLS.NBSP}${UNICODE_SYMBOLS.NNBSP}`
|
|
194
168
|
|
|
195
|
-
/**
|
|
196
|
-
* Maximum recursion depth used by the rehype and remark tree walkers.
|
|
197
|
-
* Guards against stack overflow from maliciously or accidentally deep
|
|
198
|
-
* AST nesting. Shared so both plugins behave identically.
|
|
199
|
-
*/
|
|
169
|
+
/** Max AST recursion depth; guards against stack overflow from deep nesting. */
|
|
200
170
|
export const MAX_RECURSION_DEPTH = 1000
|
|
201
171
|
|
|
202
|
-
/**
|
|
203
|
-
* Canonical issue tracker URL, referenced in user-facing error messages
|
|
204
|
-
* so there's exactly one source of truth if the repository ever moves.
|
|
205
|
-
*/
|
|
172
|
+
/** Single source of truth for issue tracker URL in error messages. */
|
|
206
173
|
export const ISSUES_URL = "https://github.com/alexander-turner/punctilio/issues"
|
|
207
174
|
|
|
208
|
-
/**
|
|
209
|
-
* LRU cache for compiled RegExp objects keyed by `pattern + '\0' + flags`.
|
|
210
|
-
* Avoids recompiling identical regexes on every function call (common
|
|
211
|
-
* when using the default separator). Capped to prevent unbounded growth.
|
|
212
|
-
*/
|
|
213
175
|
export const MAX_REGEX_CACHE_SIZE = 1000
|
|
214
176
|
const regexCache = new QuickLRU<string, RegExp>({ maxSize: MAX_REGEX_CACHE_SIZE })
|
|
215
177
|
|
|
216
|
-
/**
|
|
217
|
-
* Returns a cached RegExp for the given pattern and flags.
|
|
218
|
-
* Resets `lastIndex` before returning to prevent stale state when
|
|
219
|
-
* callers use `.test()` or `.exec()` on global-flag regexes.
|
|
220
|
-
*/
|
|
178
|
+
/** Returns a cached RegExp, resetting `lastIndex` to prevent stale global-flag state. */
|
|
221
179
|
export function cachedRegExp(pattern: string, flags: string): RegExp {
|
|
222
180
|
const key = `${pattern}\0${flags}`
|
|
223
181
|
let re = regexCache.get(key)
|
|
@@ -229,19 +187,12 @@ export function cachedRegExp(pattern: string, flags: string): RegExp {
|
|
|
229
187
|
return re
|
|
230
188
|
}
|
|
231
189
|
|
|
232
|
-
/**
|
|
233
|
-
* Returns all currently cached RegExp objects. Exported for test
|
|
234
|
-
* introspection only (e.g. ReDoS scanning every compiled pattern).
|
|
235
|
-
* @internal
|
|
236
|
-
*/
|
|
190
|
+
/** @internal */
|
|
237
191
|
export function getCachedRegExps(): RegExp[] {
|
|
238
192
|
return Array.from(regexCache.values())
|
|
239
193
|
}
|
|
240
194
|
|
|
241
|
-
/**
|
|
242
|
-
* Clears the regex cache. Exported for test isolation only.
|
|
243
|
-
* @internal
|
|
244
|
-
*/
|
|
195
|
+
/** @internal */
|
|
245
196
|
export function clearRegexCache(): void {
|
|
246
197
|
regexCache.clear()
|
|
247
198
|
}
|
package/src/dashes.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dash transformation: hyphens → em-dashes, en-dashes, minus signs.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
1
|
import { cachedRegExp, DEFAULT_SEPARATOR, getEscapedSeparator, LATIN_LETTERS, UNICODE_SYMBOLS, wordBoundaryEnd, wordBoundaryStart } from "./constants.js"
|
|
6
2
|
import { namedGroups } from "./utils.js"
|
|
7
3
|
|
|
@@ -17,19 +13,10 @@ export interface DashOptions {
|
|
|
17
13
|
|
|
18
14
|
const { EN_DASH, EM_DASH, MINUS, LEFT_DOUBLE_QUOTE, RIGHT_DOUBLE_QUOTE, LEFT_SINGLE_QUOTE, RIGHT_SINGLE_QUOTE } = UNICODE_SYMBOLS
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
* Characters that, when preceding a number, prevent it from being
|
|
22
|
-
* treated as the start of a number range. This prevents false positives
|
|
23
|
-
* in model names like "Llama-2-7B" where "2-7" should not become "2–7".
|
|
24
|
-
*/
|
|
16
|
+
// Prevents false-positive ranges in model names like "Llama-2-7B".
|
|
25
17
|
export const numberRangeDisallowedPrefixes = ["-", EN_DASH, EM_DASH, MINUS] as const
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
* Regex character-class fragment built from {@link numberRangeDisallowedPrefixes}.
|
|
29
|
-
* Non-ASCII dashes are escaped to `\uXXXX` form so the fragment is safe to
|
|
30
|
-
* embed inside a `[...]` class regardless of the regex source encoding.
|
|
31
|
-
* Computed once at module load — the inputs are `const`.
|
|
32
|
-
*/
|
|
19
|
+
// Non-ASCII dashes escaped to \uXXXX for safe embedding in [...] classes.
|
|
33
20
|
const DISALLOWED_PREFIX_CLASS_FRAGMENT = numberRangeDisallowedPrefixes
|
|
34
21
|
.map((c) => (c === "-" ? c : `\\u${c.charCodeAt(0).toString(16).padStart(4, "0")}`))
|
|
35
22
|
.join("")
|
|
@@ -185,10 +172,6 @@ export function minusReplace(text: string, options: DashOptions = {}): string {
|
|
|
185
172
|
return text
|
|
186
173
|
}
|
|
187
174
|
|
|
188
|
-
/**
|
|
189
|
-
* Convert surrounded dashes to em/en dashes.
|
|
190
|
-
* Handles patterns like "word - word" → "word—word" (Chicago) or "word – word" (Oxford).
|
|
191
|
-
*/
|
|
192
175
|
function convertParentheticalDashes(text: string, sep: string, style: DashStyle): string {
|
|
193
176
|
const localizedDash = style === "british" ? EN_DASH : EM_DASH
|
|
194
177
|
const maybeSpace = style === "british" ? " " : ""
|
|
@@ -239,13 +222,8 @@ function convertParentheticalDashes(text: string, sep: string, style: DashStyle)
|
|
|
239
222
|
return text
|
|
240
223
|
}
|
|
241
224
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
* Removes all spaces around em-dashes per Chicago Manual of Style.
|
|
245
|
-
*
|
|
246
|
-
* TODO: Handle interrupted-then-resumed speech within quotes, where Chicago
|
|
247
|
-
* allows a space after the dash: "Don't inter— Hey! Who threw that?"
|
|
248
|
-
*/
|
|
225
|
+
// TODO: Handle interrupted-then-resumed speech within quotes, where Chicago
|
|
226
|
+
// allows a space after the dash: "Don't inter— Hey! Who threw that?"
|
|
249
227
|
function normalizeEmDashSpacing(text: string, sep: string): string {
|
|
250
228
|
const escapedSep = getEscapedSeparator({ separator: sep })
|
|
251
229
|
|
|
@@ -265,7 +243,6 @@ function normalizeEmDashSpacing(text: string, sep: string): string {
|
|
|
265
243
|
return text
|
|
266
244
|
}
|
|
267
245
|
|
|
268
|
-
/** Full dash transformation. */
|
|
269
246
|
export function hyphenReplace(text: string, options: DashOptions = {}): string {
|
|
270
247
|
const sep = options.separator ?? DEFAULT_SEPARATOR
|
|
271
248
|
const style = options.dashStyle ?? "american"
|
package/src/html.ts
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Convenience module for HTML-to-HTML typography transformations.
|
|
3
|
-
*
|
|
4
|
-
* Provides a single function that takes HTML input and returns
|
|
5
|
-
* typographically improved HTML output.
|
|
6
|
-
*
|
|
7
|
-
* Requires `unified`, `rehype-parse`, and `rehype-stringify` to be installed.
|
|
8
|
-
*
|
|
9
|
-
* @packageDocumentation
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
1
|
import { unified } from "unified"
|
|
13
2
|
import rehypeParse from "rehype-parse"
|
|
14
3
|
import rehypeStringify from "rehype-stringify"
|
|
@@ -17,9 +6,6 @@ import QuickLRU from "quick-lru"
|
|
|
17
6
|
import { rehypePunctilio, type RehypePunctilioOptions } from "./rehype.js"
|
|
18
7
|
import { stableStringify } from "./utils.js"
|
|
19
8
|
|
|
20
|
-
/**
|
|
21
|
-
* Options for the HTML transform pipeline.
|
|
22
|
-
*/
|
|
23
9
|
export interface HtmlOptions extends RehypePunctilioOptions {
|
|
24
10
|
/**
|
|
25
11
|
* Whether to parse the input as an HTML fragment (no implicit `<html>`,
|
|
@@ -43,29 +29,11 @@ const processorCache = new QuickLRU<string, ReturnType<typeof createProcessor>>(
|
|
|
43
29
|
maxSize: MAX_PROCESSOR_CACHE_SIZE,
|
|
44
30
|
})
|
|
45
31
|
|
|
46
|
-
/**
|
|
47
|
-
* Clears the processor cache. Exported for test isolation only.
|
|
48
|
-
* @internal
|
|
49
|
-
*/
|
|
32
|
+
/** @internal */
|
|
50
33
|
export function clearProcessorCache(): void {
|
|
51
34
|
processorCache.clear()
|
|
52
35
|
}
|
|
53
36
|
|
|
54
|
-
/**
|
|
55
|
-
* Transforms HTML text with typographic improvements.
|
|
56
|
-
*
|
|
57
|
-
* Parses the input as HTML, applies punctilio typography transformations,
|
|
58
|
-
* and serializes back to HTML. The unified processor pipeline is cached
|
|
59
|
-
* and reused across calls with identical options.
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* ```ts
|
|
63
|
-
* import { transformHtml } from 'punctilio/html'
|
|
64
|
-
*
|
|
65
|
-
* await transformHtml('<p>"Hello" -- world</p>')
|
|
66
|
-
* // → '<p>“Hello”—world</p>'
|
|
67
|
-
* ```
|
|
68
|
-
*/
|
|
69
37
|
export async function transformHtml(
|
|
70
38
|
input: string,
|
|
71
39
|
options: HtmlOptions = {}
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* punctilio - Smart typography transformations
|
|
3
|
-
*
|
|
4
|
-
* A library for converting plain ASCII punctuation into typographically
|
|
5
|
-
* correct Unicode characters. Handles smart quotes, em-dashes, en-dashes,
|
|
6
|
-
* minus signs, ellipses, multiplication signs, and more.
|
|
7
|
-
*
|
|
8
|
-
* @packageDocumentation
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
1
|
export { DEFAULT_SEPARATOR, UNICODE_SYMBOLS } from "./constants.js"
|
|
12
2
|
import type { PunctuationStyle } from "./quotes.js"
|
|
13
3
|
export {
|
|
@@ -194,36 +184,6 @@ const defaultOpts: Required<Omit<TransformOptions, "separator">> = {
|
|
|
194
184
|
dashStyle: "american",
|
|
195
185
|
}
|
|
196
186
|
|
|
197
|
-
/**
|
|
198
|
-
* Applies all typography transformations: smart quotes, proper dashes,
|
|
199
|
-
* and symbol improvements.
|
|
200
|
-
*
|
|
201
|
-
* This is a convenience function that applies transformations in sequence:
|
|
202
|
-
* 1. hyphenReplace (em-dashes, en-dashes, minus signs)
|
|
203
|
-
* 2. primeMarks (feet/inches, arcminutes/arcseconds)
|
|
204
|
-
* 3. niceQuotes (smart quotes)
|
|
205
|
-
* 4. symbolTransform (ellipses, multiplication, math symbols, legal symbols, arrows)
|
|
206
|
-
* 5. fractions (disabled by default)
|
|
207
|
-
* 6. degrees (disabled by default)
|
|
208
|
-
* 7. superscript (disabled by default)
|
|
209
|
-
* 8. ligatures (disabled by default)
|
|
210
|
-
* 9. collapseSpaces (collapses multiple spaces into one)
|
|
211
|
-
* 10. nbsp (non-breaking spaces, enabled by default)
|
|
212
|
-
*
|
|
213
|
-
* @example
|
|
214
|
-
* ```ts
|
|
215
|
-
* import { transform } from 'punctilio'
|
|
216
|
-
*
|
|
217
|
-
* transform('"Hello," she said - "it\'s pages 1-5."')
|
|
218
|
-
* // → '"Hello," she said—"it's pages 1–5."'
|
|
219
|
-
*
|
|
220
|
-
* transform('Wait... 5x5 != 25 (c) 2024')
|
|
221
|
-
* // → 'Wait… 5×5 ≠ 25 © 2024'
|
|
222
|
-
*
|
|
223
|
-
* transform('Add 1/2 cup', { fractions: true })
|
|
224
|
-
* // → 'Add ½ cup'
|
|
225
|
-
* ```
|
|
226
|
-
*/
|
|
227
187
|
export function transform(text: string, options: TransformOptions = {}): string {
|
|
228
188
|
const separator = options.separator ?? DEFAULT_SEPARATOR
|
|
229
189
|
|
package/src/markdown.ts
CHANGED
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Convenience module for Markdown-to-Markdown typography transformations.
|
|
3
|
-
*
|
|
4
|
-
* Provides a single function that takes Markdown input and returns
|
|
5
|
-
* typographically improved Markdown output.
|
|
6
|
-
*
|
|
7
|
-
* Requires `unified`, `remark-parse`, and `remark-stringify` to be installed.
|
|
8
|
-
*
|
|
9
|
-
* @packageDocumentation
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
1
|
import { unified } from "unified"
|
|
13
2
|
import remarkParse from "remark-parse"
|
|
14
3
|
import remarkGfm from "remark-gfm"
|
|
@@ -18,9 +7,6 @@ import QuickLRU from "quick-lru"
|
|
|
18
7
|
import { remarkPunctilio, type RemarkPunctilioOptions } from "./remark.js"
|
|
19
8
|
import { stableStringify } from "./utils.js"
|
|
20
9
|
|
|
21
|
-
/**
|
|
22
|
-
* Options for the Markdown transform pipeline.
|
|
23
|
-
*/
|
|
24
10
|
export interface MarkdownOptions extends RemarkPunctilioOptions {
|
|
25
11
|
/**
|
|
26
12
|
* Character for emphasis (`*text*` vs `_text_`) and strong (`**text**` vs `__text__`).
|
|
@@ -41,9 +27,6 @@ export interface MarkdownOptions extends RemarkPunctilioOptions {
|
|
|
41
27
|
ruleMarker?: "-" | "*" | "_"
|
|
42
28
|
}
|
|
43
29
|
|
|
44
|
-
/**
|
|
45
|
-
* Creates a unified processor pipeline for Markdown typography transformations.
|
|
46
|
-
*/
|
|
47
30
|
function createProcessor(options: MarkdownOptions) {
|
|
48
31
|
const {
|
|
49
32
|
emphasisMarker,
|
|
@@ -78,31 +61,11 @@ const processorCache = new QuickLRU<string, ReturnType<typeof createProcessor>>(
|
|
|
78
61
|
maxSize: MAX_PROCESSOR_CACHE_SIZE,
|
|
79
62
|
})
|
|
80
63
|
|
|
81
|
-
/**
|
|
82
|
-
* Clears the processor cache. Exported for test isolation only.
|
|
83
|
-
* @internal
|
|
84
|
-
*/
|
|
64
|
+
/** @internal */
|
|
85
65
|
export function clearProcessorCache(): void {
|
|
86
66
|
processorCache.clear()
|
|
87
67
|
}
|
|
88
68
|
|
|
89
|
-
/**
|
|
90
|
-
* Transforms Markdown text with typographic improvements.
|
|
91
|
-
*
|
|
92
|
-
* Parses the input as Markdown, applies punctilio typography transformations
|
|
93
|
-
* (smart quotes, em-dashes, ellipses, etc.), and serializes back to Markdown.
|
|
94
|
-
*
|
|
95
|
-
* The unified processor pipeline is cached and reused across calls with
|
|
96
|
-
* identical options, avoiding redundant pipeline construction.
|
|
97
|
-
*
|
|
98
|
-
* @example
|
|
99
|
-
* ```ts
|
|
100
|
-
* import { transformMarkdown } from 'punctilio/markdown'
|
|
101
|
-
*
|
|
102
|
-
* await transformMarkdown('"Hello" -- world')
|
|
103
|
-
* // → '\u201CHello\u201D\u2014world'
|
|
104
|
-
* ```
|
|
105
|
-
*/
|
|
106
69
|
export async function transformMarkdown(
|
|
107
70
|
input: string,
|
|
108
71
|
options: MarkdownOptions = {}
|
package/src/nbsp.ts
CHANGED
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Non-breaking space transformations for improved typography.
|
|
3
|
-
*
|
|
4
|
-
* Inserts non-breaking spaces (U+00A0) in positions where line breaks
|
|
5
|
-
* would be typographically undesirable: after short words, between
|
|
6
|
-
* numbers and units, before orphaned last words, etc.
|
|
7
|
-
*
|
|
8
|
-
* @module nbsp
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
1
|
import { cachedRegExp, getEscapedSeparator, LATIN_LETTERS, NBSP_CHARS, SPACE_CHARS, UNICODE_SYMBOLS, wordBoundaryEnd } from "./constants.js"
|
|
12
2
|
|
|
13
3
|
const {
|
|
@@ -26,15 +16,11 @@ export interface NbspOptions {
|
|
|
26
16
|
separator?: string
|
|
27
17
|
}
|
|
28
18
|
|
|
29
|
-
/** Space pattern matching regular space, tab, and nbsp. */
|
|
30
19
|
const SPACE = `[${SPACE_CHARS}]`
|
|
31
20
|
|
|
32
21
|
const UNICODE_UPPERCASE = "\\p{Lu}"
|
|
33
22
|
|
|
34
|
-
/**
|
|
35
|
-
* Common units for the number-unit nbsp pattern. Only matches these specific
|
|
36
|
-
* abbreviations after a number to avoid false positives like "chapter 3 above".
|
|
37
|
-
*/
|
|
23
|
+
/** Only specific abbreviations are matched to avoid false positives. */
|
|
38
24
|
export const UNITS: readonly string[] = [
|
|
39
25
|
// Length
|
|
40
26
|
"km", "cm", "mm", "mi", "ft", "yd", "nm", "pm", "m",
|
|
@@ -111,17 +97,8 @@ const ABBREVIATION_PATTERN = REFERENCE_ABBREVIATIONS.map((a) => `${a}\\.`).join(
|
|
|
111
97
|
const PUNCTUATION_OR_QUOTE = `[.,!?:;)(${LEFT_DOUBLE_QUOTE}${RIGHT_DOUBLE_QUOTE}\u00AB\u00BB${LEFT_SINGLE_QUOTE}${RIGHT_SINGLE_QUOTE}"]`
|
|
112
98
|
const COPYRIGHT_SYMBOLS = `[${COPYRIGHT}${REGISTERED}${TRADEMARK}]`
|
|
113
99
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
* being left alone at the end of a line.
|
|
117
|
-
*
|
|
118
|
-
* Skips when the short word is already preceded by an NBSP from an earlier
|
|
119
|
-
* transform, or when this match immediately follows the previous one
|
|
120
|
-
* (back-to-back short words in the same pass). Either case would bind three
|
|
121
|
-
* or more words into a single line-break atom — defeating widow protection.
|
|
122
|
-
*
|
|
123
|
-
* @example "a cat" → "a\u00A0cat", "I am" → "I\u00A0am"
|
|
124
|
-
*/
|
|
100
|
+
// Skips when preceded by NBSP or back-to-back with the previous match,
|
|
101
|
+
// preventing 3+ words from binding into a single line-break atom.
|
|
125
102
|
export function nbspAfterShortWords(text: string, options: NbspOptions = {}): string {
|
|
126
103
|
const sep = getEscapedSeparator(options)
|
|
127
104
|
const pattern = cachedRegExp(
|
|
@@ -137,11 +114,6 @@ export function nbspAfterShortWords(text: string, options: NbspOptions = {}): st
|
|
|
137
114
|
})
|
|
138
115
|
}
|
|
139
116
|
|
|
140
|
-
/**
|
|
141
|
-
* Adds non-breaking space between numbers and common units.
|
|
142
|
-
*
|
|
143
|
-
* @example "100 km" → "100\u00A0km", "5 kg" → "5\u00A0kg"
|
|
144
|
-
*/
|
|
145
117
|
export function nbspBetweenNumberAndUnit(text: string, options: NbspOptions = {}): string {
|
|
146
118
|
const sep = getEscapedSeparator(options)
|
|
147
119
|
const wbe = wordBoundaryEnd(sep)
|
|
@@ -152,30 +124,12 @@ export function nbspBetweenNumberAndUnit(text: string, options: NbspOptions = {}
|
|
|
152
124
|
return text.replace(pattern, `$<digit>$<marker1>${NBSP}$<marker2>$<unit>`)
|
|
153
125
|
}
|
|
154
126
|
|
|
155
|
-
/**
|
|
156
|
-
* Maximum length, in characters, of the "last word" that gets an NBSP
|
|
157
|
-
* glued to its preceding space. Short enough that a typical English word
|
|
158
|
-
* qualifies but an entire trailing URL or long token does not. Only affects
|
|
159
|
-
* widow prevention at end-of-string and paragraph breaks.
|
|
160
|
-
*/
|
|
161
127
|
const MAX_LAST_WORD_LENGTH = 10
|
|
162
128
|
|
|
163
|
-
|
|
164
|
-
* Maximum word length, in characters, considered for the cascade check.
|
|
165
|
-
* Bounded so the lookbehind in `nbspBeforeLastWord` stays ReDoS-safe.
|
|
166
|
-
*/
|
|
129
|
+
// Bounded so the lookbehind stays ReDoS-safe.
|
|
167
130
|
const MAX_MIDDLE_WORD_LENGTH = 15
|
|
168
131
|
|
|
169
|
-
|
|
170
|
-
* Adds non-breaking space before the last word to prevent widows.
|
|
171
|
-
*
|
|
172
|
-
* Only applies to final words of 1 to {@link MAX_LAST_WORD_LENGTH} characters,
|
|
173
|
-
* at end of string or paragraph break (\n\n). Uses non-multiline mode so $
|
|
174
|
-
* matches only the true end of string.
|
|
175
|
-
*
|
|
176
|
-
* Skips when the second-to-last word is already glued backwards via NBSP,
|
|
177
|
-
* so the phrase doesn't become a 3-word non-breaking atom.
|
|
178
|
-
*/
|
|
132
|
+
// Skips when the second-to-last word is already glued backwards via NBSP.
|
|
179
133
|
export function nbspBeforeLastWord(text: string, options: NbspOptions = {}): string {
|
|
180
134
|
const sep = getEscapedSeparator(options)
|
|
181
135
|
const cascadeBlock = `(?<![${NBSP_CHARS}][${LATIN_LETTERS}]{1,${MAX_MIDDLE_WORD_LENGTH}})`
|
|
@@ -187,12 +141,6 @@ export function nbspBeforeLastWord(text: string, options: NbspOptions = {}): str
|
|
|
187
141
|
return text.replace(pattern, `${NBSP}$<lastWord>$<ending>`)
|
|
188
142
|
}
|
|
189
143
|
|
|
190
|
-
/**
|
|
191
|
-
* Adds non-breaking space after reference abbreviations (Fig., Vol., p., etc.)
|
|
192
|
-
* when followed by a number.
|
|
193
|
-
*
|
|
194
|
-
* @example "Fig. 1" → "Fig.\u00A01", "p. 42" → "p.\u00A042"
|
|
195
|
-
*/
|
|
196
144
|
export function nbspAfterReferenceAbbreviations(text: string, options: NbspOptions = {}): string {
|
|
197
145
|
const sep = getEscapedSeparator(options)
|
|
198
146
|
const pattern = cachedRegExp(
|
|
@@ -202,12 +150,6 @@ export function nbspAfterReferenceAbbreviations(text: string, options: NbspOptio
|
|
|
202
150
|
return text.replace(pattern, `$<abbrev>$<marker>${NBSP}`)
|
|
203
151
|
}
|
|
204
152
|
|
|
205
|
-
/**
|
|
206
|
-
* Adds non-breaking space after section (§) and paragraph (¶) symbols
|
|
207
|
-
* when followed by a number.
|
|
208
|
-
*
|
|
209
|
-
* @example "§ 5" → "§\u00A05"
|
|
210
|
-
*/
|
|
211
153
|
export function nbspAfterSectionSymbols(text: string, options: NbspOptions = {}): string {
|
|
212
154
|
const sep = getEscapedSeparator(options)
|
|
213
155
|
const pattern = cachedRegExp(
|
|
@@ -217,11 +159,6 @@ export function nbspAfterSectionSymbols(text: string, options: NbspOptions = {})
|
|
|
217
159
|
return text.replace(pattern, `$<symbol>$<marker>${NBSP}`)
|
|
218
160
|
}
|
|
219
161
|
|
|
220
|
-
/**
|
|
221
|
-
* Adds non-breaking space after honorific titles when followed by a capitalized name.
|
|
222
|
-
*
|
|
223
|
-
* @example "Dr. Smith" → "Dr.\u00A0Smith", "Mr. Jones" → "Mr.\u00A0Jones"
|
|
224
|
-
*/
|
|
225
162
|
export function nbspAfterHonorifics(text: string, options: NbspOptions = {}): string {
|
|
226
163
|
const sep = getEscapedSeparator(options)
|
|
227
164
|
const pattern = cachedRegExp(
|
|
@@ -231,12 +168,6 @@ export function nbspAfterHonorifics(text: string, options: NbspOptions = {}): st
|
|
|
231
168
|
return text.replace(pattern, `$<honorific>$<marker>${NBSP}`)
|
|
232
169
|
}
|
|
233
170
|
|
|
234
|
-
/**
|
|
235
|
-
* Adds non-breaking space after copyright, registered, and trademark
|
|
236
|
-
* symbols when followed by a year or capitalized name.
|
|
237
|
-
*
|
|
238
|
-
* @example "© 2024" → "©\u00A02024", "® Brand" → "®\u00A0Brand"
|
|
239
|
-
*/
|
|
240
171
|
export function nbspAfterCopyrightSymbols(text: string, options: NbspOptions = {}): string {
|
|
241
172
|
const sep = getEscapedSeparator(options)
|
|
242
173
|
const pattern = cachedRegExp(
|
|
@@ -246,11 +177,6 @@ export function nbspAfterCopyrightSymbols(text: string, options: NbspOptions = {
|
|
|
246
177
|
return text.replace(pattern, `$<symbol>$<marker>${NBSP}`)
|
|
247
178
|
}
|
|
248
179
|
|
|
249
|
-
/**
|
|
250
|
-
* Adds non-breaking space between initials (e.g., "J. K. Rowling").
|
|
251
|
-
*
|
|
252
|
-
* @example "J. K. Rowling" → "J.\u00A0K.\u00A0Rowling"
|
|
253
|
-
*/
|
|
254
180
|
export function nbspBetweenInitials(text: string, options: NbspOptions = {}): string {
|
|
255
181
|
const sep = getEscapedSeparator(options)
|
|
256
182
|
const pattern = cachedRegExp(
|
|
@@ -262,13 +188,7 @@ export function nbspBetweenInitials(text: string, options: NbspOptions = {}): st
|
|
|
262
188
|
|
|
263
189
|
type NbspFn = (text: string, options: NbspOptions) => string
|
|
264
190
|
|
|
265
|
-
|
|
266
|
-
* Ordered list of nbsp transforms. Specific patterns (honorifics, abbreviations,
|
|
267
|
-
* initials) come first so they claim their matches before the generic
|
|
268
|
-
* `nbspAfterShortWords`. For example, "No. 5" is matched by
|
|
269
|
-
* `nbspAfterReferenceAbbreviations` first, preventing `nbspAfterShortWords`
|
|
270
|
-
* from also matching "No" as a 2-letter short word.
|
|
271
|
-
*/
|
|
191
|
+
// Specific patterns first so they claim matches before generic nbspAfterShortWords.
|
|
272
192
|
const NBSP_TRANSFORMS: NbspFn[] = [
|
|
273
193
|
nbspAfterHonorifics,
|
|
274
194
|
nbspAfterReferenceAbbreviations,
|
|
@@ -280,7 +200,6 @@ const NBSP_TRANSFORMS: NbspFn[] = [
|
|
|
280
200
|
nbspBeforeLastWord,
|
|
281
201
|
]
|
|
282
202
|
|
|
283
|
-
/** Apply all non-breaking space transformations in sequence. */
|
|
284
203
|
export function nbspTransform(text: string, options: NbspOptions = {}): string {
|
|
285
204
|
// All nbsp patterns require a space, tab, or existing nbsp to match.
|
|
286
205
|
// Short-circuit if the text contains none of these.
|