punctilio 5.4.9 → 5.4.10

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/src/symbols.ts CHANGED
@@ -147,10 +147,14 @@ const DIGIT_SUFFIX = `(?:${PRIME_ALT}|${UNIT_ALT})?`
147
147
  // (1e5x3 — the `e` is a letter) and model SKUs (Surface5x3, RTX3060x2);
148
148
  // signed exponents (1e-5x3, 3.5E+10x2) are unambiguously scientific, their
149
149
  // operand is preceded by a sign, and they convert.
150
- const MULTIPLICATION_CHAIN = `(?<firstNum>\\d+${DIGIT_SUFFIX})(?<rest>(?:\\s*[xX*]\\s*\\d+${DIGIT_SUFFIX})+)`
151
- const MULTIPLICATION_SEGMENT = `(?<spaceBefore>\\s*)[xX*](?<spaceAfter>\\s*)(?<num>\\d+${DIGIT_SUFFIX})`
150
+ //
151
+ // Operator spacing is horizontal whitespace only: an operator across a line
152
+ // break is a list marker (`Step 1\n* 2 cups`), not multiplication.
153
+ const OPERATOR_SPACE = "[^\\S\\n\\v\\f\\r\\u2028\\u2029]*"
154
+ const MULTIPLICATION_CHAIN = `(?<firstNum>\\d+${DIGIT_SUFFIX})(?<rest>(?:${OPERATOR_SPACE}[xX*]${OPERATOR_SPACE}\\d+${DIGIT_SUFFIX})+)`
155
+ const MULTIPLICATION_SEGMENT = `(?<spaceBefore>${OPERATOR_SPACE})[xX*](?<spaceAfter>${OPERATOR_SPACE})(?<num>\\d+${DIGIT_SUFFIX})`
152
156
 
153
- /** A `\s*[xX*]\s*\d+suffix` segment located by absolute clean-text offsets. */
157
+ /** An `OPERATOR_SPACE [xX*] OPERATOR_SPACE \d+suffix` segment located by absolute clean-text offsets. */
154
158
  interface ChainSegment {
155
159
  /** Absolute offset of the operator character. */
156
160
  operatorOffset: number
@@ -183,7 +187,7 @@ function chainSegments(chainStart: number, firstNum: string, rest: string): Chai
183
187
  }
184
188
 
185
189
  /**
186
- * Each operator's spacing slot (`\s*[xX*]\s*`) tolerates one node boundary at
190
+ * Each operator's spacing slot (`OPERATOR_SPACE [xX*] OPERATOR_SPACE`) tolerates one node boundary at
187
191
  * each outer edge and none inside the spacing; a second adjacent boundary at an
188
192
  * edge breaks the segment. The operator's slot is the span [slotStart, slotEnd)
189
193
  * covering its surrounding spaces and the operator itself.
@@ -35,8 +35,9 @@ export interface TransformOptions {
35
35
  * (U+201E/U+201C double, U+201A/U+2018 single). Punctuation outside quotes.
36
36
  * - `"french"`: French style. Uses guillemets with NNBSP padding: «\u202F...\u202F»
37
37
  * Single quotes remain as curly quotes. Punctuation outside quotes.
38
- * - `"none"`: Skip all quote and punctuation transforms entirely.
39
- * Straight quotes, apostrophes, and prime marks are left unmodified.
38
+ * - `"none"`: Skip quote classification and prime conversion. Straight
39
+ * quotes, apostrophes, and prime marks are left unmodified; a U+02BC
40
+ * modifier-letter apostrophe is still normalized to U+2019.
40
41
  *
41
42
  * Default: "american"
42
43
  */
@@ -121,6 +122,14 @@ export type ResolvedTransformOptions = Required<TransformOptions>
121
122
 
122
123
  /** Validates `options` and fills in the defaults. */
123
124
  export function resolveTransformOptions(options: TransformOptions): ResolvedTransformOptions {
125
+ // JS callers outside the type system can pass null, a string, or an array;
126
+ // `Object.keys` would otherwise throw an opaque TypeError or enumerate a
127
+ // string's indices as unknown option names.
128
+ if (options === null || typeof options !== "object" || Array.isArray(options)) {
129
+ throw new TypeError(
130
+ `transform options must be an object, got ${options === null ? "null" : Array.isArray(options) ? "an array" : typeof options}.`
131
+ )
132
+ }
124
133
  assertKnownOptionKeys(options, TRANSFORM_OPTION_KEYS, "transform")
125
134
 
126
135
  const punctuationStyle = options.punctuationStyle ?? "american"