punctilio 4.3.0 → 5.0.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.
Files changed (65) hide show
  1. package/README.md +36 -22
  2. package/dist/constants.d.ts +0 -34
  3. package/dist/constants.d.ts.map +1 -1
  4. package/dist/constants.js +0 -49
  5. package/dist/constants.js.map +1 -1
  6. package/dist/dashes.d.ts +11 -7
  7. package/dist/dashes.d.ts.map +1 -1
  8. package/dist/dashes.js +1170 -176
  9. package/dist/dashes.js.map +1 -1
  10. package/dist/index.d.ts +14 -118
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +65 -116
  13. package/dist/index.js.map +1 -1
  14. package/dist/markdown.js +1 -1
  15. package/dist/markdown.js.map +1 -1
  16. package/dist/nbsp.d.ts +19 -13
  17. package/dist/nbsp.d.ts.map +1 -1
  18. package/dist/nbsp.js +214 -69
  19. package/dist/nbsp.js.map +1 -1
  20. package/dist/prose-view.d.ts +52 -1
  21. package/dist/prose-view.d.ts.map +1 -1
  22. package/dist/prose-view.js +179 -17
  23. package/dist/prose-view.js.map +1 -1
  24. package/dist/quote-classifier.d.ts +5 -4
  25. package/dist/quote-classifier.d.ts.map +1 -1
  26. package/dist/quote-classifier.js +601 -96
  27. package/dist/quote-classifier.js.map +1 -1
  28. package/dist/quotes.d.ts +12 -4
  29. package/dist/quotes.d.ts.map +1 -1
  30. package/dist/quotes.js +24 -27
  31. package/dist/quotes.js.map +1 -1
  32. package/dist/rehype.d.ts +53 -8
  33. package/dist/rehype.d.ts.map +1 -1
  34. package/dist/rehype.js +105 -62
  35. package/dist/rehype.js.map +1 -1
  36. package/dist/remark.d.ts +1 -1
  37. package/dist/remark.d.ts.map +1 -1
  38. package/dist/remark.js +6 -9
  39. package/dist/remark.js.map +1 -1
  40. package/dist/symbols.d.ts +25 -14
  41. package/dist/symbols.d.ts.map +1 -1
  42. package/dist/symbols.js +812 -220
  43. package/dist/symbols.js.map +1 -1
  44. package/dist/transform-options.d.ts +97 -0
  45. package/dist/transform-options.d.ts.map +1 -0
  46. package/dist/transform-options.js +34 -0
  47. package/dist/transform-options.js.map +1 -0
  48. package/dist/utils.d.ts +0 -16
  49. package/dist/utils.d.ts.map +1 -1
  50. package/dist/utils.js +0 -56
  51. package/dist/utils.js.map +1 -1
  52. package/package.json +6 -2
  53. package/src/constants.ts +0 -56
  54. package/src/dashes.ts +1217 -208
  55. package/src/index.ts +71 -291
  56. package/src/markdown.ts +1 -1
  57. package/src/nbsp.ts +255 -78
  58. package/src/prose-view.ts +250 -25
  59. package/src/quote-classifier.ts +618 -97
  60. package/src/quotes.ts +36 -19
  61. package/src/rehype.ts +169 -88
  62. package/src/remark.ts +6 -11
  63. package/src/symbols.ts +814 -251
  64. package/src/transform-options.ts +143 -0
  65. package/src/utils.ts +0 -82
package/src/quotes.ts CHANGED
@@ -1,31 +1,41 @@
1
- import { DEFAULT_SEPARATOR, UNICODE_SYMBOLS } from "./constants.js"
2
- import { classifyAndRenderQuotes, type PunctuationStyle } from "./quote-classifier.js"
1
+ import { cachedRegExp, UNICODE_SYMBOLS } from "./constants.js"
2
+ import { classifyAndRenderQuotes, convertPrimeMarks, type PunctuationStyle } from "./quote-classifier.js"
3
+ import { overInput, type ProseView, replaceAllInView } from "./prose-view.js"
3
4
 
4
5
  const { RIGHT_SINGLE_QUOTE, MODIFIER_LETTER_APOSTROPHE } = UNICODE_SYMBOLS
5
6
 
6
7
  export { PUNCTUATION_STYLES, type PunctuationStyle, type QuoteRole } from "./quote-classifier.js"
7
8
 
8
9
  export interface QuoteOptions {
9
- /** Boundary marker for HTML element boundaries. Default: "\uE000\uE001" */
10
- separator?: string
11
10
  /** "american" (inside), "british" (outside), "none". Default: "american" */
12
11
  punctuationStyle?: PunctuationStyle
13
- }
14
-
15
- function processQuotes(text: string, options: QuoteOptions, apostrophe: string): string {
16
- const separator = options.separator ?? DEFAULT_SEPARATOR
17
- const punctuationStyle = options.punctuationStyle ?? "american"
18
- if (punctuationStyle === "none") return text
19
- return classifyAndRenderQuotes(text, separator, punctuationStyle, apostrophe)
12
+ /**
13
+ * Convert prime-mark candidates (5'10" → 5′10″) before quote
14
+ * classification. `false` leaves them to the quote rules. Applies to
15
+ * `niceQuotes` only; `classifyApostrophes` never converts primes.
16
+ * Default: true
17
+ */
18
+ primes?: boolean
20
19
  }
21
20
 
22
21
  /** Convert straight quotes to smart quotes. */
23
- export function niceQuotes(text: string, options: QuoteOptions = {}): string {
24
- // Apostrophes render as U+2019, the same glyph as closing single quotes,
25
- // per the Unicode Standard. Any U+02BC already in the input is normalized
26
- // to U+2019 as well (including under punctuationStyle "none").
27
- return processQuotes(text, options, RIGHT_SINGLE_QUOTE)
28
- .replaceAll(MODIFIER_LETTER_APOSTROPHE, RIGHT_SINGLE_QUOTE)
22
+ export function niceQuotes(input: string, options?: QuoteOptions): string
23
+ export function niceQuotes(input: ProseView, options?: QuoteOptions): void
24
+ export function niceQuotes(input: string | ProseView, options: QuoteOptions = {}): string | void {
25
+ const punctuationStyle = options.punctuationStyle ?? "american"
26
+ return overInput(input, (view) => {
27
+ if (punctuationStyle !== "none") {
28
+ if (options.primes !== false) {
29
+ convertPrimeMarks(view)
30
+ }
31
+ // Apostrophes render as U+2019, the same glyph as closing single quotes,
32
+ // per the Unicode Standard.
33
+ classifyAndRenderQuotes(view, punctuationStyle, RIGHT_SINGLE_QUOTE)
34
+ }
35
+ // Any U+02BC already in the input is normalized to U+2019 as well
36
+ // (including under punctuationStyle "none").
37
+ replaceAllInView(view, cachedRegExp(MODIFIER_LETTER_APOSTROPHE, "g"), () => RIGHT_SINGLE_QUOTE)
38
+ })
29
39
  }
30
40
 
31
41
  /**
@@ -38,6 +48,13 @@ export function niceQuotes(text: string, options: QuoteOptions = {}): string {
38
48
  * For display output, use {@link niceQuotes} or `transform()` instead,
39
49
  * which use U+2019 for both per the Unicode Standard.
40
50
  */
41
- export function classifyApostrophes(text: string, options: QuoteOptions = {}): string {
42
- return processQuotes(text, options, MODIFIER_LETTER_APOSTROPHE)
51
+ export function classifyApostrophes(input: string, options?: QuoteOptions): string
52
+ export function classifyApostrophes(input: ProseView, options?: QuoteOptions): void
53
+ export function classifyApostrophes(input: string | ProseView, options: QuoteOptions = {}): string | void {
54
+ const punctuationStyle = options.punctuationStyle ?? "american"
55
+ return overInput(input, (view) => {
56
+ if (punctuationStyle !== "none") {
57
+ classifyAndRenderQuotes(view, punctuationStyle, MODIFIER_LETTER_APOSTROPHE)
58
+ }
59
+ })
43
60
  }
package/src/rehype.ts CHANGED
@@ -3,14 +3,13 @@ import type { Transformer } from "unified"
3
3
 
4
4
  import { SKIP, visitParents } from "unist-util-visit-parents"
5
5
 
6
- import { transform, TRANSFORM_OPTION_KEYS, type TransformOptions } from "./index.js"
7
- import { DEFAULT_SEPARATOR, MAX_RECURSION_DEPTH } from "./constants.js"
6
+ import { transformView } from "./index.js"
7
+ import { TRANSFORM_OPTION_KEYS, type TransformOptions } from "./transform-options.js"
8
+ import { MAX_RECURSION_DEPTH } from "./constants.js"
8
9
  import { assertKnownOptionKeys, formatErrorString } from "./utils.js"
9
- import { buildProseView, runLegacyPass } from "./prose-view.js"
10
+ import { buildProseView, type ProsePass, type ProseView } from "./prose-view.js"
10
11
 
11
- type ElementPredicate = (node: Element) => boolean
12
-
13
- type TextTransformer = (input: string) => string
12
+ export type ElementPredicate = (node: Element) => boolean
14
13
 
15
14
  /** Per-text-node skip predicate, called after element-level `shouldSkip`. */
16
15
  export type TextNodeSkipPredicate = (
@@ -186,50 +185,101 @@ export function assertSmartQuotesMatch(input: string): void {
186
185
  }
187
186
  }
188
187
 
188
+ export interface ProseViewOfOptions extends ElementTransformOptions {
189
+ /** Element-level skip predicate; skipped subtrees contribute no text nodes. */
190
+ shouldSkip?: ElementPredicate
191
+ }
192
+
189
193
  /**
190
- * Transforms element text using the separator-marking technique. When
191
- * `checkInvariance` is true, verifies `stripMarkers(transform(marked)) ===
192
- * transform(stripMarkers(text))` for debugging marker interactions.
194
+ * Builds a ProseView over the element's transformable text nodes, honoring
195
+ * the element-level `shouldSkip` and per-text-node `shouldSkipText`
196
+ * predicates. Returns null when the element holds no transformable text.
193
197
  */
194
- export function transformElement(
195
- node: Element,
196
- transformFn: TextTransformer,
197
- shouldSkip: ElementPredicate,
198
- separator: string,
199
- checkInvariance: boolean = false,
200
- options?: ElementTransformOptions
201
- ): void {
198
+ export function proseViewOf(element: Element, options: ProseViewOfOptions = {}): ProseView | null {
202
199
  /* istanbul ignore if -- defensive: elements should always have children array */
203
- if (!node?.children) {
204
- return
200
+ if (!element?.children) {
201
+ return null
205
202
  }
206
203
 
207
- const textNodes = flattenTextNodes(node, shouldSkip, options)
204
+ const shouldSkip = options.shouldSkip ?? (() => false)
205
+ const textNodes = flattenTextNodes(element, shouldSkip, options)
208
206
  if (textNodes.length === 0) {
209
- return
207
+ return null
210
208
  }
211
209
 
212
- // Capture marked content before transformation for invariance check
213
- const markedContent = checkInvariance
214
- ? textNodes.map((n) => n.value + separator).join("")
215
- : ""
216
-
217
- const view = buildProseView(textNodes)
218
- runLegacyPass(view, transformFn, separator)
219
-
220
- if (checkInvariance) {
221
- const transformedContent = textNodes.map((n) => n.value + separator).join("")
222
- const stripSep = (s: string) => s.replaceAll(separator, "")
223
- const strippedTransformed = stripSep(transformedContent)
224
- const expected = transformFn(stripSep(markedContent))
225
-
226
- if (expected !== strippedTransformed) {
227
- throw new Error(
228
- `Transform invariance check failed: ` +
229
- `expected ${formatErrorString(expected, "expected")} ` +
230
- `but got ${formatErrorString(strippedTransformed, "actual")}`
231
- )
210
+ return buildProseView(textNodes)
211
+ }
212
+
213
+ /**
214
+ * One entry in an `applyPasses` sequence: a bare pass, or a pass with its own
215
+ * skip predicates layered on top of the base options (the per-transform
216
+ * skip-set case, e.g. a fractions pass that additionally skips `<a>`).
217
+ */
218
+ export type PassEntry =
219
+ | ProsePass
220
+ | { pass: ProsePass; shouldSkip?: ElementPredicate; shouldSkipText?: TextNodeSkipPredicate }
221
+
222
+ interface ResolvedPassEntry {
223
+ pass: ProsePass
224
+ shouldSkip?: ElementPredicate
225
+ shouldSkipText?: TextNodeSkipPredicate
226
+ }
227
+
228
+ function resolvePassEntry(entry: PassEntry): ResolvedPassEntry {
229
+ return typeof entry === "function" ? { pass: entry } : entry
230
+ }
231
+
232
+ /** OR-combines an optional entry predicate onto an optional base predicate. */
233
+ function mergePredicates<Args extends unknown[]>(
234
+ base: ((...args: Args) => boolean) | undefined,
235
+ extra: ((...args: Args) => boolean) | undefined,
236
+ ): ((...args: Args) => boolean) | undefined {
237
+ if (!base) return extra
238
+ if (!extra) return base
239
+ return (...args) => base(...args) || extra(...args)
240
+ }
241
+
242
+ /**
243
+ * Runs `passes` in order over `element`'s transformable text, owning the
244
+ * ProseView lifecycle so callers never touch a view directly. Each pass
245
+ * commits its edits before the next runs, so passes see each other's
246
+ * committed output — the same sequencing `transform()`'s pipeline uses.
247
+ *
248
+ * Entries whose predicates match the previous entry's share one view;
249
+ * an entry with different `shouldSkip`/`shouldSkipText` predicates gets a
250
+ * fresh view (built after the previous pass committed) over the text nodes
251
+ * that survive both the base `options` predicates and its own.
252
+ */
253
+ export function applyPasses(
254
+ element: Element,
255
+ passes: readonly PassEntry[],
256
+ options: ProseViewOfOptions = {},
257
+ ): void {
258
+ let currentView: ProseView | null = null
259
+ let currentPredicates: Pick<ResolvedPassEntry, "shouldSkip" | "shouldSkipText"> | null = null
260
+
261
+ for (const entry of passes) {
262
+ const { pass, shouldSkip, shouldSkipText } = resolvePassEntry(entry)
263
+
264
+ const samePredicates =
265
+ currentPredicates !== null &&
266
+ currentPredicates.shouldSkip === shouldSkip &&
267
+ currentPredicates.shouldSkipText === shouldSkipText
268
+ if (!samePredicates) {
269
+ currentView = proseViewOf(element, {
270
+ shouldSkip: mergePredicates(options.shouldSkip, shouldSkip),
271
+ shouldSkipText: mergePredicates(options.shouldSkipText, shouldSkipText),
272
+ })
273
+ currentPredicates = { shouldSkip, shouldSkipText }
274
+ }
275
+
276
+ // No transformable text under this entry's predicates; later entries may
277
+ // still see text (their skip sets differ), so keep going.
278
+ if (currentView === null) {
279
+ continue
232
280
  }
281
+
282
+ pass(currentView)
233
283
  }
234
284
  }
235
285
 
@@ -334,12 +384,72 @@ function hasTextDescendant(
334
384
  return false
335
385
  }
336
386
 
337
- export function collectTransformableElements(
387
+ export interface CollectProseBlocksOptions {
388
+ /**
389
+ * HTML tag names to skip. Default: the plugin's default skip list
390
+ * ("code", "pre", "script", ...).
391
+ */
392
+ skipTags?: string[]
393
+ /** CSS class names whose elements (and their subtrees) are skipped. Default: [] */
394
+ skipClasses?: string[]
395
+ /** Additional element-level skip predicate, OR-ed with the tag/class skips. */
396
+ shouldSkip?: ElementPredicate
397
+ /** Invert the element model as in {@link RehypePunctilioOptions.transformAllElements}. Default: false */
398
+ transformAllElements?: boolean
399
+ }
400
+
401
+ /** Skip predicate and transformable-tag test resolved from the options. */
402
+ interface ResolvedCollectOptions {
403
+ shouldSkip: ElementPredicate
404
+ isTransformable: (tagName: string) => boolean
405
+ }
406
+
407
+ function resolveCollectOptions(options: CollectProseBlocksOptions): ResolvedCollectOptions {
408
+ const { skipTags = DEFAULT_SKIP_TAGS, skipClasses = [], shouldSkip, transformAllElements = false } = options
409
+
410
+ // In inverted mode, form-value elements join the skip-list so their literal
411
+ // values are neither transformed nor flattened into a transformable ancestor.
412
+ const skipTagSet = new Set(transformAllElements ? [...skipTags, ...FORM_VALUE_TAGS] : skipTags)
413
+ const skipClassSet = new Set(skipClasses)
414
+
415
+ const hasSkipClass = (node: Element): boolean => {
416
+ if (skipClassSet.size === 0) return false
417
+ const classNames = node.properties?.className
418
+ const classes = Array.isArray(classNames)
419
+ ? classNames.filter((c): c is string => typeof c === "string")
420
+ : typeof classNames === "string" ? classNames.split(/\s+/) : []
421
+ return classes.some((cls) => skipClassSet.has(cls))
422
+ }
423
+
424
+ return {
425
+ shouldSkip: (node) =>
426
+ skipTagSet.has(node.tagName) || hasSkipClass(node) || (shouldSkip?.(node) ?? false),
427
+ // Inverted mode transforms every non-skipped element except non-prose
428
+ // containers; the default keeps the `TRANSFORMABLE_ELEMENTS` allowlist
429
+ // (plus custom elements).
430
+ isTransformable: transformAllElements
431
+ ? (tagName) => !NON_PROSE_CONTAINER_TAGS.has(tagName)
432
+ : isTransformableElement,
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Collects the elements under `root` (inclusive) whose text should be
438
+ * transformed as one prose block: transformable elements with direct text or
439
+ * inline-only text descendants. Elements with block-level children recurse so
440
+ * each block transforms independently.
441
+ */
442
+ export function collectProseBlocks(root: Element, options: CollectProseBlocksOptions = {}): Element[] {
443
+ const { shouldSkip, isTransformable } = resolveCollectOptions(options)
444
+ return collectProseBlocksImpl(root, shouldSkip, 0, undefined, isTransformable)
445
+ }
446
+
447
+ function collectProseBlocksImpl(
338
448
  node: Element,
339
449
  shouldSkip: ElementPredicate,
340
- depth: number = 0,
341
- alreadyTransformed?: ReadonlySet<Element>,
342
- isTransformable: (tagName: string) => boolean = isTransformableElement
450
+ depth: number,
451
+ alreadyTransformed: ReadonlySet<Element> | undefined,
452
+ isTransformable: (tagName: string) => boolean
343
453
  ): Element[] {
344
454
  /* istanbul ignore if -- defensive: prevents stack overflow from malicious HTML */
345
455
  if (depth > MAX_RECURSION_DEPTH) {
@@ -371,7 +481,7 @@ export function collectTransformableElements(
371
481
  // children that should be independent, or has no text to transform.
372
482
  for (const child of node.children) {
373
483
  if (child.type === "element") {
374
- const childResults = collectTransformableElements(child, shouldSkip, depth + 1, alreadyTransformed, isTransformable)
484
+ const childResults = collectProseBlocksImpl(child, shouldSkip, depth + 1, alreadyTransformed, isTransformable)
375
485
  for (const r of childResults) results.push(r)
376
486
  }
377
487
  }
@@ -400,53 +510,21 @@ export function rehypePunctilio(
400
510
  assertKnownOptionKeys(options, REHYPE_OPTION_KEYS, "rehypePunctilio")
401
511
 
402
512
  const {
403
- skipTags = DEFAULT_SKIP_TAGS,
404
- skipClasses = [],
405
- separator = DEFAULT_SEPARATOR,
513
+ skipTags,
514
+ skipClasses,
406
515
  shouldSkipText,
407
- transformAllElements = false,
516
+ transformAllElements,
408
517
  ...transformOptions
409
518
  } = options
410
519
 
411
- // In inverted mode, form-value elements join the skip-list so their literal
412
- // values are neither transformed nor flattened into a transformable ancestor.
413
- const skipTagSet = new Set(transformAllElements ? [...skipTags, ...FORM_VALUE_TAGS] : skipTags)
414
- const skipClassSet = new Set(skipClasses)
415
-
416
- // Inverted mode transforms every non-skipped element except non-prose
417
- // containers; the default keeps the `TRANSFORMABLE_ELEMENTS` allowlist (plus
418
- // custom elements).
419
- const isTransformable = transformAllElements
420
- ? (tagName: string) => !NON_PROSE_CONTAINER_TAGS.has(tagName)
421
- : isTransformableElement
422
-
423
- const hasSkipClass = (node: Element): boolean => {
424
- if (skipClassSet.size === 0) return false
425
- const classNames = node.properties?.className
426
- const classes = Array.isArray(classNames)
427
- ? classNames.filter((c): c is string => typeof c === "string")
428
- : typeof classNames === "string" ? classNames.split(/\s+/) : []
429
- return classes.some((cls) => skipClassSet.has(cls))
430
- }
431
-
432
- const shouldSkip = (node: Element): boolean =>
433
- skipTagSet.has(node.tagName) || hasSkipClass(node)
434
-
435
- const pluginOptions = {
436
- ...transformOptions,
437
- separator,
438
- }
439
-
440
- const transformFn = (text: string): string => {
441
- return transform(text, pluginOptions)
442
- }
520
+ const { shouldSkip, isTransformable } = resolveCollectOptions({ skipTags, skipClasses, transformAllElements })
443
521
 
444
522
  // Allocate the per-element options object once; it's read-only inside
445
- // transformElement, so sharing across calls is safe and avoids a fresh
523
+ // proseViewOf, so sharing across calls is safe and avoids a fresh
446
524
  // allocation per transformable element.
447
- const elementOptions: ElementTransformOptions | undefined = shouldSkipText
448
- ? { shouldSkipText }
449
- : undefined
525
+ const viewOptions: ProseViewOfOptions = shouldSkipText
526
+ ? { shouldSkip, shouldSkipText }
527
+ : { shouldSkip }
450
528
 
451
529
  return (tree: Root) => {
452
530
  // Track transformed elements to avoid double-processing
@@ -463,10 +541,13 @@ export function rehypePunctilio(
463
541
  }
464
542
 
465
543
  // Collect and transform elements with text content
466
- const elementsToTransform = collectTransformableElements(node, shouldSkip, 0, transformed, isTransformable)
544
+ const elementsToTransform = collectProseBlocksImpl(node, shouldSkip, 0, transformed, isTransformable)
467
545
  for (const elt of elementsToTransform) {
468
546
  if (!transformed.has(elt)) {
469
- transformElement(elt, transformFn, shouldSkip, separator, false, elementOptions)
547
+ const view = proseViewOf(elt, viewOptions)
548
+ if (view !== null) {
549
+ transformView(view, transformOptions)
550
+ }
470
551
  transformed.add(elt)
471
552
  // Mark all descendants as processed since their text was included
472
553
  markDescendants(elt, transformed)
package/src/remark.ts CHANGED
@@ -3,9 +3,11 @@ import type { Transformer } from "unified"
3
3
 
4
4
  import { visitParents } from "unist-util-visit-parents"
5
5
 
6
- import { transform, TRANSFORM_OPTION_KEYS, type TransformOptions } from "./index.js"
7
- import { DEFAULT_SEPARATOR, MAX_RECURSION_DEPTH } from "./constants.js"
8
- import { assertKnownOptionKeys, transformTextNodes } from "./utils.js"
6
+ import { transformView } from "./index.js"
7
+ import { TRANSFORM_OPTION_KEYS, type TransformOptions } from "./transform-options.js"
8
+ import { MAX_RECURSION_DEPTH } from "./constants.js"
9
+ import { buildProseView } from "./prose-view.js"
10
+ import { assertKnownOptionKeys } from "./utils.js"
9
11
 
10
12
  /**
11
13
  * Same options as `transform()`, except `nbsp` defaults to `false`:
@@ -49,19 +51,12 @@ export function remarkPunctilio(
49
51
  ): Transformer<Root, Root> {
50
52
  assertKnownOptionKeys(options, TRANSFORM_OPTION_KEYS, "remarkPunctilio")
51
53
 
52
- const separator = options.separator ?? DEFAULT_SEPARATOR
53
-
54
54
  // Markdown is a source format, so default `nbsp` to false (`?? false`
55
55
  // rather than spread defaults so an explicit `nbsp: undefined` also gets
56
56
  // the Markdown default instead of falling through to transform's).
57
57
  const pluginOptions = {
58
58
  ...options,
59
59
  nbsp: options.nbsp ?? false,
60
- separator,
61
- }
62
-
63
- const transformFn = (text: string): string => {
64
- return transform(text, pluginOptions)
65
60
  }
66
61
 
67
62
  return (tree: Root) => {
@@ -88,7 +83,7 @@ export function remarkPunctilio(
88
83
  return
89
84
  }
90
85
 
91
- transformTextNodes(textNodes, transformFn, separator)
86
+ transformView(buildProseView(textNodes), pluginOptions)
92
87
  })
93
88
  }
94
89
  }