@spintax/core 0.9.0 → 0.10.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/CHANGELOG.md CHANGED
@@ -3,6 +3,57 @@
3
3
  All notable changes to `@spintax/core` are documented here. This project adheres to
4
4
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
5
 
6
+ ## 0.10.0 — 2026-09-19
7
+
8
+ ### Fixed
9
+
10
+ - **No space between a punctuation mark and the quote or bracket that closes it.** The spacing passes
11
+ put their space after `.!?` and `,;:` whenever the next character was not a space, a digit, a tag or
12
+ the end — so a closing quote or bracket got one in front of it: `"Is it audited? ", the figure`,
13
+ `He called it "the best. "`, `She said "yes, " and left`, `«Как дела? », и ушёл`, `(really? )`,
14
+ `(apples, pears, etc. )`. Found in a landing lead that quotes a question-shaped keyword; every engine
15
+ in the family did the same. Now no space goes between the mark and a closer, and what follows the
16
+ closer is left as written. `)` and `]` always close (a stray `]` stays literal). A quote is read by
17
+ what follows its run, because its shape says nothing — `“` opens English and closes German, `"` and `'`
18
+ do both: whitespace, the end, a tag, the end of a tag (`title="Really?">`), `.,;:!?…`, `)`, `]` or a
19
+ dash make it a closer. Anything else keeps the space it always got — a word or a number
20
+ (`Is it?"Next"` → `Is it? "Next"`), `(`, `$`, a shielded value. The accepted cost: an opening quote
21
+ glued to the mark whose text starts with a follower (`is it?"—no"`, `is it?"<em>yes</em>"`) reads as a
22
+ closer and loses that space. Both PHP engines take the same rule.
23
+
24
+ ### Corpus
25
+
26
+ Nineteen new cases in `render-postprocess.json`, every expectation taken from both PHP engines with the
27
+ rule applied: the reported shapes (`postprocess/closing-quote-after-question`, `closing-quote-before-space`,
28
+ `closing-typographic-quote-after-exclamation`, `closing-single-quote`), the rest of the class
29
+ (`closing-quote-after-period-at-end-of-text`, `closing-quote-after-comma`, `closing-guillemet-ru`,
30
+ `closing-quote-ends-attribute`, `closing-bracket`, `closing-square-bracket`, `closing-quote-before-tag`,
31
+ `closing-run-of-nested-quotes`, `bracket-always-closes`), `closing-quote-followers` and
32
+ `closing-reversed-quotes-and-more-followers` (between them every quote as a closer and every follower),
33
+ the cost `glued-opener-before-a-follower-reads-as-closer`, and three guards —
34
+ `opening-quote-after-sentence-keeps-space` (a quote or a run of quotes before a letter or a digit still
35
+ opens), `quote-before-anything-else-keeps-its-space` (`(`, `$`) and `quote-before-shielded-value-opens`.
36
+
37
+ ### Notes
38
+
39
+ **The list names what follows a CLOSING quote.** The first version of the rule excluded instead — any
40
+ quote run not followed by a letter, a digit or a placeholder closed. That also took the space from an
41
+ opening quote glued to the mark before `(`, `$` or an emoji (`is it?"(really)"`), which the old rule had
42
+ repaired (found by the Codex gate, as were the attribute case and the stray `]`). A list changes output
43
+ only where the quote is known to close, and an unnamed shape keeps the old output. Left as it was: a
44
+ bracket that arrives through `neutralize()` is a sentinel during this stage and is restored after it, so a
45
+ neutralized `[sic.]` still renders `[sic. ]`.
46
+
47
+ **Verified.** 143 968 generated post-process inputs — random soup over letters, digits, whitespace,
48
+ `.,;:!?…`, every quote and bracket, tags and shieldable values, plus an exhaustive grid of mark × quote run
49
+ × follower — through this engine and both patched PHP engines: identical everywhere. The three agreed
50
+ before the change as well, and every one of the 66 988 outputs that moved differs from the old output only
51
+ by a deleted space between a mark and a closer. Thirty control mutations, one per character of the rule —
52
+ each quote dropped, each follower dropped (including whitespace, the end, a tag, `/>` and `>`), `)` or `]`
53
+ no longer closing, the comma pass left alone, and the first version's letter-or-digit rule — each turn the
54
+ differential red (363 to 25 905 inputs) and fail at least one fixture. Cost unchanged here and in PHP:
55
+ 200 000 marks before 200 000 quotes take single-digit milliseconds in all three.
56
+
6
57
  ## 0.9.0 — 2026-09-17
7
58
 
8
59
  ### Changed, visibly
package/dist/index.cjs CHANGED
@@ -1911,9 +1911,11 @@ function trailingPunctuationStart(value) {
1911
1911
  }
1912
1912
  var SENTENCE_OPENERS = "\xBF\xA1";
1913
1913
  var LEAD = `(?:<[^>]+>|[${SENTENCE_OPENERS}]|${S})*`;
1914
+ var QUOTES = `"'\xAB\xBB\u2039\u203A\u201C\u201D\u2018\u2019`;
1915
+ var CLOSER = `[)\\]]|[${QUOTES}]+(?=${S}|$|<|\\/?>|[.,;:!?\u2026)\\]\u2014\u2013])`;
1914
1916
  var SPACE_BEFORE_PUNCT_RE = new RegExp(`(?<!${S})${S}+([,;:!?.])`, "gu");
1915
- var SPACE_AFTER_COMMA_RE = new RegExp(`([,;:])(?!\\p{Nd})(?!${S}|$|<)`, "gu");
1916
- var SPACE_AFTER_SENTENCE_RE = new RegExp(`(?<![.!?])([.!?]+)(?![.!?])(?!\\p{Nd})(?!${S}|$|<)`, "gu");
1917
+ var SPACE_AFTER_COMMA_RE = new RegExp(`([,;:])(?!\\p{Nd})(?!${S}|$|<|${CLOSER})`, "gu");
1918
+ var SPACE_AFTER_SENTENCE_RE = new RegExp(`(?<![.!?])([.!?]+)(?![.!?])(?!\\p{Nd})(?!${S}|$|<|${CLOSER})`, "gu");
1917
1919
  var SPACE_AFTER_OPENER_RE = new RegExp(`([${SENTENCE_OPENERS}])${S}+`, "gu");
1918
1920
  var CAP_FIRST_RE = new RegExp(`^(${LEAD})(\\p{Ll})`, "u");
1919
1921
  var up = (ch) => ch.toUpperCase();