text-sanctifier 1.3.8 → 1.3.9

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/hiddenText.js CHANGED
@@ -1,233 +1,243 @@
1
- // src/hiddenText.js
2
- //
3
- // Finds runs of the characters used to hide instructions in text a model reads:
4
- // tag characters, variation selectors, invisible operators and the reserved code points
5
- // of the tag plane. Tag runs are decoded
6
- // to the ASCII they spell. Used by `text-sanctifier check --ai-config`, and exported
7
- // as `text-sanctifier/hidden`.
8
- //
9
- // Plain JavaScript with no Node APIs, so it also runs in a browser. src/index.js does
10
- // not import this file, so it stays out of the browser bundle and its size budget.
11
-
12
- export { VERSION } from './version.generated.js';
13
-
14
- /** @typedef {'tag' | 'variation_selector' | 'invisible_operator' | 'reserved_tag_plane'} HiddenRunKind */
15
-
16
- /**
17
- * @typedef {Object} HiddenRun
18
- * @property {HiddenRunKind} kind
19
- * @property {number} offset - UTF-16 index of the first character
20
- * @property {number} end - UTF-16 index just past the last character
21
- * @property {number} count - number of code points in the run
22
- * @property {number} line - 1-based line of the first character (as in inspectTextDetailed)
23
- * @property {number} column - 1-based column of the first character, in UTF-16 units
24
- * @property {string} [decoded] - tag runs only: the ASCII the tags spell (see decodeTagText),
25
- * at most `maxDecodedLength` UTF-16 units
26
- * @property {boolean} [decodedTruncated] - tag runs only: true when `decoded` stops short of the run
27
- */
28
-
29
- /**
30
- * @typedef {Object} HiddenRunCounts
31
- * @property {number} tag
32
- * @property {number} variation_selector
33
- * @property {number} invisible_operator
34
- * @property {number} reserved_tag_plane
35
- */
36
-
37
- /**
38
- * @typedef {Object} HiddenTextScan
39
- * @property {HiddenRun[]} runs - runs that may hide text, in order, at most `maxRuns` long
40
- * @property {Array<[number, number]>} benign - [offset, end) spans of legitimate
41
- * sequences (subdivision flags, a single emoji or text presentation selector), at most `maxRuns` long
42
- * @property {HiddenRunCounts} counts - every run per kind, listed or not
43
- * @property {HiddenRunCounts} codePointCounts - code points in those runs per kind, listed or not
44
- * @property {number} supplementarySelectorCount - code points U+E0100–U+E01EF in those runs, which
45
- * (unlike the emoji selectors U+FE00–U+FE0F) can carry data; included in codePointCounts.variation_selector
46
- * @property {number} total - sum of `counts`
47
- * @property {number} benignTotal - every benign span, listed or not
48
- * @property {boolean} truncated - true when runs or benign spans were left out of the lists
49
- */
50
-
51
- /**
52
- * @typedef {Object} HiddenScanOptions
53
- * @property {number} [maxRuns=Infinity] - cap on listed runs, and separately on listed benign spans
54
- * (non-negative integer or Infinity). Counts stay exact.
55
- * @property {number} [maxDecodedLength=Infinity] - cap on each run's `decoded` text, in UTF-16 units.
56
- * For both, any other value (negative, fractional, NaN, a string) means the default, no cap.
57
- */
58
-
59
- const TAG_FIRST = 0xE0000;
60
- const TAG_LAST = 0xE007F;
61
- const CANCEL_TAG = 0xE007F;
62
- const BLACK_FLAG = 0x1F3F4;
63
-
64
- // RGI subdivision flags: U+1F3F4, the tag letters, U+E007F
65
- const SUBDIVISION_FLAGS = new Set(['gbeng', 'gbsct', 'gbwls']);
66
-
67
- /**
68
- * @param {number} cp
69
- * @returns {HiddenRunKind | null}
70
- */
71
- function kindOf(cp) {
72
- if (cp >= TAG_FIRST && cp <= TAG_LAST) return 'tag';
73
- if ((cp >= 0xFE00 && cp <= 0xFE0F) || (cp >= 0xE0100 && cp <= 0xE01EF) ||
74
- (cp >= 0x180B && cp <= 0x180D) || cp === 0x180F) return 'variation_selector';
75
- if (cp >= 0x2061 && cp <= 0x2064) return 'invisible_operator';
76
- // Unassigned and default ignorable: U+E0080-U+E00FF and U+E01F0-U+E0FFF
77
- if (cp >= 0xE0080 && cp <= 0xE0FFF) return 'reserved_tag_plane';
78
- return null;
79
- }
80
-
81
- /** @param {number} cp */
82
- const hex = (cp) => 'U+' + cp.toString(16).toUpperCase().padStart(4, '0');
83
-
84
- /**
85
- * Decodes tag characters to the ASCII they shadow: U+E0020–U+E007E become
86
- * U+0020–U+007E. Anything else (U+E0001 LANGUAGE TAG, U+E007F CANCEL TAG, unassigned
87
- * tags, other characters) is written as `<U+XXXX>`, so the result is printable.
88
- * @param {string} text
89
- * @returns {string}
90
- */
91
- export function decodeTagText(text) {
92
- return decodeRange(text, 0, text.length, Infinity).decoded;
93
- }
94
-
95
- /**
96
- * Decodes text[start, end) as decodeTagText does, stopping before the output would
97
- * pass `limit` UTF-16 units. A `<U+XXXX>` marker is written whole or not at all.
98
- * @param {string} text
99
- * @param {number} start
100
- * @param {number} end
101
- * @param {number} limit
102
- * @returns {{ decoded: string, truncated: boolean }}
103
- */
104
- function decodeRange(text, start, end, limit) {
105
- let out = '';
106
- let i = start;
107
- while (i < end) {
108
- const cp = /** @type {number} */ (text.codePointAt(i));
109
- const piece = cp >= 0xE0020 && cp <= 0xE007E ? String.fromCharCode(cp - TAG_FIRST) : `<${hex(cp)}>`;
110
- if (out.length + piece.length > limit) return { decoded: out, truncated: true };
111
- out += piece;
112
- i += cp > 0xFFFF ? 2 : 1;
113
- }
114
- return { decoded: out, truncated: false };
115
- }
116
-
117
- // Whitespace as JavaScript's \s defines it, tested at an index of the text itself,
118
- // so the check allocates nothing.
119
- const SPACE_AT = /\s/y;
120
-
121
- /**
122
- * @param {string} text
123
- * @param {number} index
124
- * @returns {boolean}
125
- */
126
- function isSpaceAt(text, index) {
127
- SPACE_AT.lastIndex = index;
128
- return SPACE_AT.test(text);
129
- }
130
-
131
- /**
132
- * A cap option: a non-negative integer or Infinity; anything else means no cap.
133
- * @param {unknown} value
134
- * @returns {number}
135
- */
136
- function cap(value) {
137
- return typeof value === 'number' && (value === Infinity || (Number.isInteger(value) && value >= 0))
138
- ? value
139
- : Infinity;
140
- }
141
-
142
- /**
143
- * Finds runs of tag characters, variation selectors, invisible operators and reserved
144
- * tag-plane code points (U+E0080–U+E00FF, U+E01F0–U+E0FFF).
145
- * Consecutive characters of the same kind form one run. Two kinds of sequence are
146
- * legitimate and listed under `benign` instead of `runs`:
147
- * - the subdivision flags of England, Scotland and Wales: U+1F3F4 followed by the
148
- * tags for "gbeng", "gbsct" or "gbwls" and U+E007F
149
- * - a single U+FE0E or U+FE0F (text or emoji presentation) after another character
150
- *
151
- * `line` and `column` are counted as in inspectTextDetailed. Every run and benign
152
- * span is counted; only the first `maxRuns` of each are listed. With the caps set,
153
- * memory stays flat however large the input is. One pass over the string, no copy.
154
- * Set both caps for untrusted input: without them, results grow with the input.
155
- *
156
- * @param {string} text
157
- * @param {HiddenScanOptions} [options]
158
- * @returns {HiddenTextScan}
159
- */
160
- export function findHiddenRuns(text, options = {}) {
161
- const maxRuns = cap(options && options.maxRuns);
162
- const maxDecodedLength = cap(options && options.maxDecodedLength);
163
-
164
- /** @type {HiddenRun[]} */
165
- const runs = [];
166
- /** @type {Array<[number, number]>} */
167
- const benign = [];
168
- /** @type {HiddenRunCounts} */
169
- const counts = { tag: 0, variation_selector: 0, invisible_operator: 0, reserved_tag_plane: 0 };
170
- /** @type {HiddenRunCounts} */
171
- const codePointCounts = { tag: 0, variation_selector: 0, invisible_operator: 0, reserved_tag_plane: 0 };
172
- let supplementarySelectorCount = 0;
173
- let total = 0;
174
- let benignTotal = 0;
175
- let line = 1;
176
- let lineStart = 0;
177
-
178
- let i = 0;
179
- while (i < text.length) {
180
- const cp = /** @type {number} */ (text.codePointAt(i));
181
- const kind = kindOf(cp);
182
- if (kind === null) {
183
- if (cp === 0x0A || (cp === 0x0D && text.charCodeAt(i + 1) !== 0x0A)) { // CRLF ends at its LF
184
- line++;
185
- lineStart = i + 1;
186
- }
187
- i += cp > 0xFFFF ? 2 : 1;
188
- continue;
189
- }
190
-
191
- const offset = i;
192
- let count = 0;
193
- let supplementary = 0;
194
- let last = cp;
195
- while (i < text.length) {
196
- const c = /** @type {number} */ (text.codePointAt(i));
197
- if (kindOf(c) !== kind) break;
198
- last = c;
199
- count++;
200
- if (c >= 0xE0100 && c <= 0xE01EF) supplementary++;
201
- i += c > 0xFFFF ? 2 : 1;
202
- }
203
- const end = i;
204
-
205
- // A subdivision flag is exactly five tag letters and CANCEL TAG after U+1F3F4.
206
- const isBenign = kind === 'tag'
207
- ? count === 6 && last === CANCEL_TAG && offset >= 2 && text.codePointAt(offset - 2) === BLACK_FLAG &&
208
- SUBDIVISION_FLAGS.has(decodeRange(text, offset, end - 2, Infinity).decoded)
209
- : kind === 'variation_selector' && count === 1 && (cp === 0xFE0E || cp === 0xFE0F) &&
210
- offset > 0 && !isSpaceAt(text, offset - 1);
211
- if (isBenign) {
212
- benignTotal++;
213
- if (benign.length < maxRuns) benign.push([offset, end]);
214
- continue;
215
- }
216
-
217
- counts[kind]++;
218
- codePointCounts[kind] += count;
219
- supplementarySelectorCount += supplementary;
220
- total++;
221
- if (runs.length < maxRuns) {
222
- /** @type {HiddenRun} */
223
- const run = { kind, offset, end, line, column: offset - lineStart + 1, count };
224
- if (kind === 'tag') {
225
- const { decoded, truncated } = decodeRange(text, offset, end, maxDecodedLength);
226
- run.decoded = decoded;
227
- run.decodedTruncated = truncated;
228
- }
229
- runs.push(run);
230
- }
231
- }
232
- return { runs, benign, counts, codePointCounts, supplementarySelectorCount, total, benignTotal, truncated: total > runs.length || benignTotal > benign.length };
233
- }
1
+ // src/hiddenText.js
2
+ //
3
+ // Finds runs of the characters used to hide instructions in text a model reads:
4
+ // tag characters, variation selectors, invisible operators and the reserved code points
5
+ // of the tag plane. Tag runs are decoded
6
+ // to the ASCII they spell. Used by `text-sanctifier check --ai-config`, and exported
7
+ // as `text-sanctifier/hidden`.
8
+ //
9
+ // Plain JavaScript with no Node APIs, so it also runs in a browser. src/index.js does
10
+ // not import this file, so it stays out of the browser bundle and its size budget.
11
+
12
+ export { VERSION } from './version.generated.js';
13
+
14
+ /** @typedef {'tag' | 'variation_selector' | 'invisible_operator' | 'reserved_tag_plane'} HiddenRunKind */
15
+
16
+ /**
17
+ * @typedef {Object} HiddenRun
18
+ * @property {HiddenRunKind} kind
19
+ * @property {number} offset - UTF-16 index of the first character
20
+ * @property {number} end - UTF-16 index just past the last character
21
+ * @property {number} count - number of code points in the run
22
+ * @property {number} line - 1-based line of the first character (as in inspectTextDetailed)
23
+ * @property {number} column - 1-based column of the first character, in UTF-16 units
24
+ * @property {string} [decoded] - tag runs only: the ASCII the tags spell (see decodeTagText),
25
+ * at most `maxDecodedLength` UTF-16 units
26
+ * @property {boolean} [decodedTruncated] - tag runs only: true when `decoded` stops short of the run
27
+ */
28
+
29
+ /**
30
+ * @typedef {Object} HiddenRunCounts
31
+ * @property {number} tag
32
+ * @property {number} variation_selector
33
+ * @property {number} invisible_operator
34
+ * @property {number} reserved_tag_plane
35
+ */
36
+
37
+ /**
38
+ * @typedef {Object} HiddenTextScan
39
+ * @property {HiddenRun[]} runs - runs that may hide text, in order, at most `maxRuns` long
40
+ * @property {Array<[number, number]>} benign - [offset, end) spans of legitimate
41
+ * sequences (subdivision flags, a single emoji or text presentation selector), at most `maxRuns` long
42
+ * @property {HiddenRunCounts} counts - every run per kind, listed or not
43
+ * @property {HiddenRunCounts} codePointCounts - code points in those runs per kind, listed or not
44
+ * @property {number} supplementarySelectorCount - code points U+E0100–U+E01EF in those runs, which
45
+ * (unlike the emoji selectors U+FE00–U+FE0F) can carry data; included in codePointCounts.variation_selector
46
+ * @property {number} total - sum of `counts`
47
+ * @property {number} benignTotal - every benign span, listed or not
48
+ * @property {boolean} truncated - true when runs or benign spans were left out of the lists
49
+ */
50
+
51
+ /**
52
+ * @typedef {Object} HiddenScanOptions
53
+ * @property {number} [maxRuns=Infinity] - cap on listed runs, and separately on listed benign spans
54
+ * (non-negative integer or Infinity). Counts stay exact.
55
+ * @property {number} [maxDecodedLength=Infinity] - cap on each run's `decoded` text, in UTF-16 units.
56
+ * For both, any other value (negative, fractional, NaN, a string) means the default, no cap.
57
+ */
58
+
59
+ const TAG_FIRST = 0xE0000;
60
+ const TAG_LAST = 0xE007F;
61
+ const CANCEL_TAG = 0xE007F;
62
+ const BLACK_FLAG = 0x1F3F4;
63
+
64
+ // RGI subdivision flags: U+1F3F4, the tag letters, U+E007F
65
+ const SUBDIVISION_FLAGS = new Set(['gbeng', 'gbsct', 'gbwls']);
66
+
67
+ /**
68
+ * @param {number} cp
69
+ * @returns {HiddenRunKind | null}
70
+ */
71
+ function kindOf(cp) {
72
+ if (cp >= TAG_FIRST && cp <= TAG_LAST) return 'tag';
73
+ if ((cp >= 0xFE00 && cp <= 0xFE0F) || (cp >= 0xE0100 && cp <= 0xE01EF) ||
74
+ (cp >= 0x180B && cp <= 0x180D) || cp === 0x180F) return 'variation_selector';
75
+ if (cp >= 0x2061 && cp <= 0x2064) return 'invisible_operator';
76
+ // Unassigned and default ignorable: U+E0080-U+E00FF and U+E01F0-U+E0FFF
77
+ if (cp >= 0xE0080 && cp <= 0xE0FFF) return 'reserved_tag_plane';
78
+ return null;
79
+ }
80
+
81
+ /** @param {number} cp */
82
+ const hex = (cp) => 'U+' + cp.toString(16).toUpperCase().padStart(4, '0');
83
+
84
+ /**
85
+ * Decodes tag characters to the ASCII they shadow: U+E0020–U+E007E become
86
+ * U+0020–U+007E. Anything else (U+E0001 LANGUAGE TAG, U+E007F CANCEL TAG, unassigned
87
+ * tags, other characters) is written as `<U+XXXX>`, so the result is printable.
88
+ * @param {string} text
89
+ * @returns {string}
90
+ */
91
+ export function decodeTagText(text) {
92
+ return decodeRange(text, 0, text.length, Infinity).decoded;
93
+ }
94
+
95
+ /**
96
+ * Decodes text[start, end) as decodeTagText does, stopping before the output would
97
+ * pass `limit` UTF-16 units. A `<U+XXXX>` marker is written whole or not at all.
98
+ * @param {string} text
99
+ * @param {number} start
100
+ * @param {number} end
101
+ * @param {number} limit
102
+ * @returns {{ decoded: string, truncated: boolean }}
103
+ */
104
+ function decodeRange(text, start, end, limit) {
105
+ let out = '';
106
+ let i = start;
107
+ while (i < end) {
108
+ const cp = /** @type {number} */ (text.codePointAt(i));
109
+ const piece = cp >= 0xE0020 && cp <= 0xE007E ? String.fromCharCode(cp - TAG_FIRST) : `<${hex(cp)}>`;
110
+ if (out.length + piece.length > limit) return { decoded: out, truncated: true };
111
+ out += piece;
112
+ i += cp > 0xFFFF ? 2 : 1;
113
+ }
114
+ return { decoded: out, truncated: false };
115
+ }
116
+
117
+ // Tested at an index of the text itself, so the check allocates nothing.
118
+ const PICTOGRAPHIC_AT = /\p{Extended_Pictographic}/yu;
119
+
120
+ /**
121
+ * True when a presentation selector at `index` follows an emoji-capable character:
122
+ * an Extended_Pictographic code point, or a keycap base (#, * or a digit) with U+20E3 after.
123
+ * @param {string} text
124
+ * @param {number} index
125
+ * @returns {boolean}
126
+ */
127
+ function isPresentationBase(text, index) {
128
+ if (index === 0) return false;
129
+ const low = text.charCodeAt(index - 1);
130
+ const p = low >= 0xDC00 && low <= 0xDFFF && index >= 2 &&
131
+ (text.charCodeAt(index - 2) & 0xFC00) === 0xD800 ? index - 2 : index - 1;
132
+ const before = text.charCodeAt(p);
133
+ if ((before === 0x23 || before === 0x2A || (before >= 0x30 && before <= 0x39)) && text.charCodeAt(index + 1) === 0x20E3) return true;
134
+ PICTOGRAPHIC_AT.lastIndex = p;
135
+ return PICTOGRAPHIC_AT.test(text);
136
+ }
137
+
138
+ /**
139
+ * A cap option: a non-negative integer or Infinity; anything else means no cap.
140
+ * @param {unknown} value
141
+ * @returns {number}
142
+ */
143
+ function cap(value) {
144
+ return typeof value === 'number' && (value === Infinity || (Number.isInteger(value) && value >= 0))
145
+ ? value
146
+ : Infinity;
147
+ }
148
+
149
+ /**
150
+ * Finds runs of tag characters, variation selectors, invisible operators and reserved
151
+ * tag-plane code points (U+E0080–U+E00FF, U+E01F0–U+E0FFF).
152
+ * Consecutive characters of the same kind form one run. Two kinds of sequence are
153
+ * legitimate and listed under `benign` instead of `runs`:
154
+ * - the subdivision flags of England, Scotland and Wales: U+1F3F4 followed by the
155
+ * tags for "gbeng", "gbsct" or "gbwls" and U+E007F
156
+ * - a single U+FE0E or U+FE0F (text or emoji presentation) after an
157
+ * Extended_Pictographic character, or between #, * or a digit and U+20E3 (a keycap),
158
+ * the same rule as text-sanctifier/context. After a letter it is a run: one selector
159
+ * per letter can carry a bit each.
160
+ *
161
+ * `line` and `column` are counted as in inspectTextDetailed. Every run and benign
162
+ * span is counted; only the first `maxRuns` of each are listed. With the caps set,
163
+ * memory stays flat however large the input is. One pass over the string, no copy.
164
+ * Set both caps for untrusted input: without them, results grow with the input.
165
+ *
166
+ * @param {string} text
167
+ * @param {HiddenScanOptions} [options]
168
+ * @returns {HiddenTextScan}
169
+ */
170
+ export function findHiddenRuns(text, options = {}) {
171
+ const maxRuns = cap(options && options.maxRuns);
172
+ const maxDecodedLength = cap(options && options.maxDecodedLength);
173
+
174
+ /** @type {HiddenRun[]} */
175
+ const runs = [];
176
+ /** @type {Array<[number, number]>} */
177
+ const benign = [];
178
+ /** @type {HiddenRunCounts} */
179
+ const counts = { tag: 0, variation_selector: 0, invisible_operator: 0, reserved_tag_plane: 0 };
180
+ /** @type {HiddenRunCounts} */
181
+ const codePointCounts = { tag: 0, variation_selector: 0, invisible_operator: 0, reserved_tag_plane: 0 };
182
+ let supplementarySelectorCount = 0;
183
+ let total = 0;
184
+ let benignTotal = 0;
185
+ let line = 1;
186
+ let lineStart = 0;
187
+
188
+ let i = 0;
189
+ while (i < text.length) {
190
+ const cp = /** @type {number} */ (text.codePointAt(i));
191
+ const kind = kindOf(cp);
192
+ if (kind === null) {
193
+ if (cp === 0x0A || (cp === 0x0D && text.charCodeAt(i + 1) !== 0x0A)) { // CRLF ends at its LF
194
+ line++;
195
+ lineStart = i + 1;
196
+ }
197
+ i += cp > 0xFFFF ? 2 : 1;
198
+ continue;
199
+ }
200
+
201
+ const offset = i;
202
+ let count = 0;
203
+ let supplementary = 0;
204
+ let last = cp;
205
+ while (i < text.length) {
206
+ const c = /** @type {number} */ (text.codePointAt(i));
207
+ if (kindOf(c) !== kind) break;
208
+ last = c;
209
+ count++;
210
+ if (c >= 0xE0100 && c <= 0xE01EF) supplementary++;
211
+ i += c > 0xFFFF ? 2 : 1;
212
+ }
213
+ const end = i;
214
+
215
+ // A subdivision flag is exactly five tag letters and CANCEL TAG after U+1F3F4.
216
+ const isBenign = kind === 'tag'
217
+ ? count === 6 && last === CANCEL_TAG && offset >= 2 && text.codePointAt(offset - 2) === BLACK_FLAG &&
218
+ SUBDIVISION_FLAGS.has(decodeRange(text, offset, end - 2, Infinity).decoded)
219
+ : kind === 'variation_selector' && count === 1 && (cp === 0xFE0E || cp === 0xFE0F) &&
220
+ isPresentationBase(text, offset);
221
+ if (isBenign) {
222
+ benignTotal++;
223
+ if (benign.length < maxRuns) benign.push([offset, end]);
224
+ continue;
225
+ }
226
+
227
+ counts[kind]++;
228
+ codePointCounts[kind] += count;
229
+ supplementarySelectorCount += supplementary;
230
+ total++;
231
+ if (runs.length < maxRuns) {
232
+ /** @type {HiddenRun} */
233
+ const run = { kind, offset, end, line, column: offset - lineStart + 1, count };
234
+ if (kind === 'tag') {
235
+ const { decoded, truncated } = decodeRange(text, offset, end, maxDecodedLength);
236
+ run.decoded = decoded;
237
+ run.decodedTruncated = truncated;
238
+ }
239
+ runs.push(run);
240
+ }
241
+ }
242
+ return { runs, benign, counts, codePointCounts, supplementarySelectorCount, total, benignTotal, truncated: total > runs.length || benignTotal > benign.length };
243
+ }
package/src/index.d.ts CHANGED
@@ -1,22 +1,22 @@
1
- import { inspectText } from './inspectText.js';
2
- import { inspectTextDetailed } from './inspectText.js';
3
- import { getNewlineStats } from './inspectText.js';
4
- import { getRecommendedSanctifierOptions } from './inspectText.js';
5
- import { summonSanctifier } from './sanctifyText.js';
6
- import { sanctifyStrict } from './sanctifyText.js';
7
- import { sanctifyLoose } from './sanctifyText.js';
8
- import { sanctifyKeyboardOnlyEmoji } from './sanctifyText.js';
9
- import { sanctifyKeyboardOnly } from './sanctifyText.js';
10
- import { sanctifyPasted } from './sanctifyText.js';
11
- import { revealInvisible } from './reveal.js';
12
- import { VERSION } from './version.generated.js';
13
- export { inspectText, inspectTextDetailed, getNewlineStats, getRecommendedSanctifierOptions, summonSanctifier, sanctifyStrict, sanctifyLoose, sanctifyKeyboardOnlyEmoji, sanctifyKeyboardOnly, sanctifyPasted, revealInvisible, VERSION };
14
- export type UnicodeTrashReport = import('./inspectText.js').UnicodeTrashReport;
15
- export type TrashCategory = import('./inspectText.js').TrashCategory;
16
- export type TrashFinding = import('./inspectText.js').TrashFinding;
17
- export type TrashCounts = import('./inspectText.js').TrashCounts;
18
- export type DetailedTrashReport = import('./inspectText.js').DetailedTrashReport;
19
- export type InspectDetailedOptions = import('./inspectText.js').InspectDetailedOptions;
20
- export type NewlineStats = import('./inspectText.js').NewlineStats;
21
- export type SanctifyOptions = import('./sanctifyText.js').SanctifyOptions;
22
- export type RevealOptions = import('./reveal.js').RevealOptions;
1
+ import { inspectText } from './inspectText.js';
2
+ import { inspectTextDetailed } from './inspectText.js';
3
+ import { getNewlineStats } from './inspectText.js';
4
+ import { getRecommendedSanctifierOptions } from './inspectText.js';
5
+ import { summonSanctifier } from './sanctifyText.js';
6
+ import { sanctifyStrict } from './sanctifyText.js';
7
+ import { sanctifyLoose } from './sanctifyText.js';
8
+ import { sanctifyKeyboardOnlyEmoji } from './sanctifyText.js';
9
+ import { sanctifyKeyboardOnly } from './sanctifyText.js';
10
+ import { sanctifyPasted } from './sanctifyText.js';
11
+ import { revealInvisible } from './reveal.js';
12
+ import { VERSION } from './version.generated.js';
13
+ export { inspectText, inspectTextDetailed, getNewlineStats, getRecommendedSanctifierOptions, summonSanctifier, sanctifyStrict, sanctifyLoose, sanctifyKeyboardOnlyEmoji, sanctifyKeyboardOnly, sanctifyPasted, revealInvisible, VERSION };
14
+ export type UnicodeTrashReport = import('./inspectText.js').UnicodeTrashReport;
15
+ export type TrashCategory = import('./inspectText.js').TrashCategory;
16
+ export type TrashFinding = import('./inspectText.js').TrashFinding;
17
+ export type TrashCounts = import('./inspectText.js').TrashCounts;
18
+ export type DetailedTrashReport = import('./inspectText.js').DetailedTrashReport;
19
+ export type InspectDetailedOptions = import('./inspectText.js').InspectDetailedOptions;
20
+ export type NewlineStats = import('./inspectText.js').NewlineStats;
21
+ export type SanctifyOptions = import('./sanctifyText.js').SanctifyOptions;
22
+ export type RevealOptions = import('./reveal.js').RevealOptions;
package/src/index.js CHANGED
@@ -1,29 +1,29 @@
1
- // src/index.js
2
-
3
-
4
- import { inspectText, inspectTextDetailed, getNewlineStats, getRecommendedSanctifierOptions } from './inspectText.js';
5
- export { inspectText, inspectTextDetailed, getNewlineStats, getRecommendedSanctifierOptions };
6
-
7
- import { summonSanctifier, sanctifyStrict, sanctifyLoose, sanctifyKeyboardOnlyEmoji, sanctifyKeyboardOnly, sanctifyPasted } from './sanctifyText.js';
8
- export { summonSanctifier, sanctifyStrict, sanctifyLoose, sanctifyKeyboardOnlyEmoji, sanctifyKeyboardOnly, sanctifyPasted };
9
-
10
- import { revealInvisible } from './reveal.js';
11
- export { revealInvisible };
12
-
13
- import { VERSION } from './version.generated.js';
14
- export { VERSION };
15
-
16
- // Type-only re-exports so TypeScript consumers can name these from the package root.
17
- /** @typedef {import('./inspectText.js').UnicodeTrashReport} UnicodeTrashReport */
18
- /** @typedef {import('./inspectText.js').TrashCategory} TrashCategory */
19
- /** @typedef {import('./inspectText.js').TrashFinding} TrashFinding */
20
- /** @typedef {import('./inspectText.js').TrashCounts} TrashCounts */
21
- /** @typedef {import('./inspectText.js').DetailedTrashReport} DetailedTrashReport */
22
- /** @typedef {import('./inspectText.js').InspectDetailedOptions} InspectDetailedOptions */
23
- /** @typedef {import('./inspectText.js').NewlineStats} NewlineStats */
24
- /** @typedef {import('./sanctifyText.js').SanctifyOptions} SanctifyOptions */
25
- /** @typedef {import('./reveal.js').RevealOptions} RevealOptions */
26
-
27
-
28
- // In Future: export helpers for power users
29
- // export { purgeInvisibleTrash, purgeEmojisCharacters, normalizeNewlines, trimSpacesAroundNewlines, collapseParagraphs, collapseExtraSpaces, purgeControlCharacters } from './sanctifyText.js';
1
+ // src/index.js
2
+
3
+
4
+ import { inspectText, inspectTextDetailed, getNewlineStats, getRecommendedSanctifierOptions } from './inspectText.js';
5
+ export { inspectText, inspectTextDetailed, getNewlineStats, getRecommendedSanctifierOptions };
6
+
7
+ import { summonSanctifier, sanctifyStrict, sanctifyLoose, sanctifyKeyboardOnlyEmoji, sanctifyKeyboardOnly, sanctifyPasted } from './sanctifyText.js';
8
+ export { summonSanctifier, sanctifyStrict, sanctifyLoose, sanctifyKeyboardOnlyEmoji, sanctifyKeyboardOnly, sanctifyPasted };
9
+
10
+ import { revealInvisible } from './reveal.js';
11
+ export { revealInvisible };
12
+
13
+ import { VERSION } from './version.generated.js';
14
+ export { VERSION };
15
+
16
+ // Type-only re-exports so TypeScript consumers can name these from the package root.
17
+ /** @typedef {import('./inspectText.js').UnicodeTrashReport} UnicodeTrashReport */
18
+ /** @typedef {import('./inspectText.js').TrashCategory} TrashCategory */
19
+ /** @typedef {import('./inspectText.js').TrashFinding} TrashFinding */
20
+ /** @typedef {import('./inspectText.js').TrashCounts} TrashCounts */
21
+ /** @typedef {import('./inspectText.js').DetailedTrashReport} DetailedTrashReport */
22
+ /** @typedef {import('./inspectText.js').InspectDetailedOptions} InspectDetailedOptions */
23
+ /** @typedef {import('./inspectText.js').NewlineStats} NewlineStats */
24
+ /** @typedef {import('./sanctifyText.js').SanctifyOptions} SanctifyOptions */
25
+ /** @typedef {import('./reveal.js').RevealOptions} RevealOptions */
26
+
27
+
28
+ // In Future: export helpers for power users
29
+ // export { purgeInvisibleTrash, purgeEmojisCharacters, normalizeNewlines, trimSpacesAroundNewlines, collapseParagraphs, collapseExtraSpaces, purgeControlCharacters } from './sanctifyText.js';
@@ -278,7 +278,7 @@ export const summonSanctifier = /* @__PURE__ */ Object.assign(createSanctifier,
278
278
  * Brutal text normalizer and invisible trash scrubber,
279
279
  * configurable to kill whatever ghosts you want dead.
280
280
  *
281
- * ⚠️ Note: This is plain-text normalization/filtering — not an HTML/XSS sanitizer.
281
+ * Note: This is plain-text normalization/filtering — not an HTML/XSS sanitizer.
282
282
  *
283
283
  * @param {string} text
284
284
  * @param {boolean} [purgeInvisibleChars=false] - Remove ZWSP, NBSP, bidi, tags, variation selectors, etc.
@@ -413,8 +413,8 @@ function purgeInvisibleTrash(text) {
413
413
  * Uses the global `EMOJI_REGEX` to match full emoji grapheme sequences,
414
414
  * including:
415
415
  * - Extended pictographic characters
416
- * - Zero-width joiner (ZWJ) sequences (e.g. 👨‍👩‍👧‍👦)
417
- * - Variation Selector-16 (VS16) emoji presentation forms (e.g. ✌️, ‼️)
416
+ * - Zero-width joiner (ZWJ) sequences (e.g. U+1F468 U+200D U+1F469 U+200D U+1F467 U+200D U+1F466)
417
+ * - Variation Selector-16 (VS16) emoji presentation forms (e.g. U+270C U+FE0F, U+203C U+FE0F)
418
418
  *
419
419
  * Any non-emoji characters are ignored.
420
420
  *
@@ -451,7 +451,7 @@ function purgeNonKeyboardChars(text, purgeEmojis = false) {
451
451
  }
452
452
 
453
453
  // Replace each non-ASCII run with ONLY the emoji sequences inside it.
454
- // This preserves ZWJ sequences and VS16 variants (e.g. 👨‍👩‍👧‍👦, ✌️, ‼️),
454
+ // This preserves ZWJ sequences and VS16 variants (e.g. the family emoji, U+270C U+FE0F, U+203C U+FE0F),
455
455
  // while dropping non-emoji non-ASCII (e.g. 𝌆).
456
456
  return normalized.replace(ASCII_KEYBOARD_SAFE_REGEX, (run) => extractEmojiSequences(run));
457
457
  }
@@ -1,5 +1,5 @@
1
- // src/version.generated.js
2
- // Generated from package.json by scripts/gen-version.js (`npm run gen`); do not edit.
3
-
4
- /** The installed version of text-sanctifier, as in its package.json. */
5
- export const VERSION = '1.3.8';
1
+ // src/version.generated.js
2
+ // Generated from package.json by scripts/gen-version.js (`npm run gen`); do not edit.
3
+
4
+ /** The installed version of text-sanctifier, as in its package.json. */
5
+ export const VERSION = '1.3.9';