text-sanctifier 1.0.17 → 1.0.19

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/README.md CHANGED
@@ -1,163 +1,163 @@
1
- # text-sanctifier
2
-
3
- [![npm](https://img.shields.io/npm/v/text-sanctifier)](https://www.npmjs.com/package/text-sanctifier)
4
- [![gzip size](https://img.shields.io/bundlephobia/minzip/text-sanctifier)](https://bundlephobia.com/package/text-sanctifier)
5
- [![downloads](https://img.shields.io/npm/dw/text-sanctifier)](https://www.npmjs.com/package/text-sanctifier)
6
- [![GitHub stars](https://img.shields.io/github/stars/iWhatty/text-sanctifier?style=social)](https://github.com/iWhatty/text-sanctifier)
7
-
8
- Brutal text normalizer and invisible Unicode scrubber for modern web projects.
9
-
10
- > ⚠️ **Not an HTML/XSS sanitizer.**
11
- > This library normalizes and filters plain text. It does not parse or sanitize HTML.
12
-
13
- * Minified: (3.09 KB)
14
- * Gzipped (GCC): (1.36 KB)
15
-
16
- ## Features
17
-
18
- * Purges zero-width Unicode garbage
19
- * Normalizes line endings (CRLF, CR, LF) → LF
20
- * Collapses unwanted spaces and paragraphs
21
- * Nukes control characters (if enabled)
22
- * Smart normalization of typographic junk (quotes, dashes, bullets, full-width punctuation)
23
- * Keyboard-only filtering (retain printable ASCII + full emoji sequences)
24
-
25
- * Preserves ZWJ emoji clusters (👨‍👩‍👧‍👦)
26
- * Preserves VS16 emoji presentation variants (✌️, ‼️)
27
- * Configurable via fine-grained flags or ready-made presets
28
- * Includes strict, loose, and keyboard-only modes
29
- * Deterministic RegExp usage (no global `lastIndex` state leaks)
30
-
31
- ## Install
32
-
33
- ```bash
34
- npm install text-sanctifier
35
- ```
36
-
37
- ## Runtime Requirements
38
-
39
- Requires modern JavaScript runtime with ES2020+ support.
40
-
41
- * Node.js 14+
42
- * Modern evergreen browsers
43
-
44
- ---
45
-
46
- ## 📦 Package & Build Info
47
-
48
- * **Source (`src/`)**: ES2020+ ESM modules with JSDoc
49
- * **Browser Build (`dist/`)**: Minified ESM bundle for `<script type="module">`
50
- * **Tree-shaking Friendly**: Fully optimized with `sideEffects: false`
51
- * **Zero Transpilation**: No built-in polyfills or runtime overhead
52
- * **Bundler Ready**: Works great with Vite, Rollup, Webpack, Parcel, etc.
53
-
54
- ---
55
-
56
- ## 🔧 Quick Usage
57
-
58
- ### Custom Config
59
-
60
- ```js
61
- import { summonSanctifier } from 'text-sanctifier';
62
-
63
- const clean = summonSanctifier({
64
- purgeInvisibleChars: true,
65
- purgeEmojis: true,
66
- collapseSpaces: true,
67
- collapseNewLines: true,
68
- preserveParagraphs: true,
69
- finalTrim: true,
70
- });
71
-
72
- const output = clean(rawText);
73
- ```
74
-
75
- ### Strict Preset
76
-
77
- ```js
78
- const output = summonSanctifier.strict(rawText);
79
- ```
80
-
81
- ### Loose Preset
82
-
83
- ```js
84
- const output = summonSanctifier.loose(rawText);
85
- ```
86
-
87
- ### Keyboard-Only (No Emojis)
88
-
89
- ```js
90
- const output = summonSanctifier.keyboardOnly(userInput);
91
- ```
92
-
93
- ### Keyboard-Only (With Emojis)
94
-
95
- ```js
96
- const output = summonSanctifier.keyboardOnlyEmoji(commentText);
97
- ```
98
-
99
- ---
100
-
101
- ## 🔍 Unicode Trash Detection
102
-
103
- ```js
104
- import { inspectText } from 'text-sanctifier';
105
-
106
- const report = inspectText(input);
107
-
108
- /*
109
- {
110
- hasControlChars: true,
111
- hasInvisibleChars: true,
112
- hasMixedNewlines: false,
113
- newlineStyle: 'LF',
114
- hasEmojis: true,
115
- hasNonKeyboardChars: false,
116
- summary: [
117
- 'Control characters detected.',
118
- 'Invisible Unicode characters detected.',
119
- 'Emojis detected.',
120
- 'Consistent newline style: LF'
121
- ]
122
- }
123
- */
124
- ```
125
-
126
- Use `inspectText` to preflight text content before rendering, storing, or linting. It's a diagnostic tool to help inform sanitization needs.
127
-
128
- Pass the report to `getRecommendedSanctifierOptions(report)` to auto-generate config flags for `summonSanctifier()`.
129
-
130
- ---
131
-
132
- ## API
133
-
134
- ### `summonSanctifier(options?: SanctifyOptions): (text: string) => string`
135
-
136
- Creates a reusable sanitizer from an option object.
137
-
138
- ### `summonSanctifier.strict`
139
-
140
- Aggressively purges: emojis, control characters, extra spacing, and newlines.
141
-
142
- ### `summonSanctifier.loose`
143
-
144
- Gently normalizes spacing and newlines while preserving emojis and paragraphs.
145
-
146
- ### `summonSanctifier.keyboardOnly`
147
-
148
- Restricts to printable ASCII only (removes emojis).
149
-
150
- ### `summonSanctifier.keyboardOnlyEmoji`
151
-
152
- Restricts to printable ASCII + full emoji sequences.
153
- Preserves ZWJ emoji clusters and emoji presentation variants.
154
-
155
- ### `inspectText(text: string): UnicodeTrashReport`
156
-
157
- Returns a structural report of control codes, invisible chars, newline styles, and more.
158
-
159
- ---
160
-
161
- ## License
162
-
163
- --{DR.WATT v3.0}--
1
+ # text-sanctifier
2
+
3
+ [![npm](https://img.shields.io/npm/v/text-sanctifier)](https://www.npmjs.com/package/text-sanctifier)
4
+ [![downloads](https://img.shields.io/npm/dm/text-sanctifier)](https://www.npmjs.com/package/text-sanctifier)
5
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/text-sanctifier)](https://bundlephobia.com/package/text-sanctifier)
6
+ [![license](https://img.shields.io/npm/l/text-sanctifier)](https://github.com/iWhatty/text-sanctifier/blob/main/LICENSE)
7
+ [![stars](https://img.shields.io/github/stars/iWhatty/text-sanctifier?style=social)](https://github.com/iWhatty/text-sanctifier)
8
+
9
+ Brutal text normalizer and invisible Unicode scrubber for modern web projects. ~1.66 KB gzipped.
10
+
11
+ ## Features
12
+
13
+ - Purges zero-width Unicode garbage
14
+ - Normalizes line endings (CRLF, CR, LF) to LF
15
+ - Collapses unwanted spaces and paragraphs
16
+ - Nukes control characters (if enabled)
17
+ - Smart normalization of typographic junk (quotes, dashes, bullets, full-width punctuation)
18
+ - Keyboard-only filtering (retain printable ASCII + full emoji sequences)
19
+ - Preserves ZWJ emoji clusters (👨‍👩‍👧‍👦)
20
+ - Preserves VS16 emoji presentation variants (✌️, ‼️)
21
+ - Configurable via fine-grained flags or ready-made presets
22
+ - Includes strict, loose, and keyboard-only modes
23
+ - Deterministic RegExp usage (no global `lastIndex` state leaks)
24
+
25
+ ---
26
+
27
+ ## Install
28
+
29
+ ```sh
30
+ pnpm add text-sanctifier
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Quick start
36
+
37
+ **Custom config:**
38
+
39
+ ```js
40
+ import { summonSanctifier } from 'text-sanctifier';
41
+
42
+ const clean = summonSanctifier({
43
+ purgeInvisibleChars: true,
44
+ purgeEmojis: true,
45
+ collapseSpaces: true,
46
+ collapseNewLines: true,
47
+ preserveParagraphs: true,
48
+ finalTrim: true,
49
+ });
50
+
51
+ const output = clean(rawText);
52
+ ```
53
+
54
+ **Strict preset:**
55
+
56
+ ```js
57
+ const output = summonSanctifier.strict(rawText);
58
+ ```
59
+
60
+ **Loose preset:**
61
+
62
+ ```js
63
+ const output = summonSanctifier.loose(rawText);
64
+ ```
65
+
66
+ **Keyboard-only (no emojis):**
67
+
68
+ ```js
69
+ const output = summonSanctifier.keyboardOnly(userInput);
70
+ ```
71
+
72
+ **Keyboard-only (with emojis):**
73
+
74
+ ```js
75
+ const output = summonSanctifier.keyboardOnlyEmoji(commentText);
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Security notes
81
+
82
+ - **Not an HTML/XSS sanitizer.** This library normalizes and filters plain text.
83
+ - If you need to render **untrusted content**, render it as text (e.g. `textContent`), not HTML (`innerHTML`).
84
+ - If you need to sanitize **HTML**, use a dedicated HTML sanitizer (e.g. DOMPurify / sanitize-html).
85
+ - Like any text-processing library, extremely large untrusted inputs can be used for CPU/DoS pressure; consider input size limits in high-risk environments.
86
+
87
+ ---
88
+
89
+ ## API
90
+
91
+ ### `summonSanctifier(options?: SanctifyOptions): (text: string) => string`
92
+
93
+ Creates a reusable sanitizer from an option object.
94
+
95
+ ### `summonSanctifier.strict`
96
+
97
+ Aggressively purges: emojis, control characters, extra spacing, and newlines.
98
+
99
+ ### `summonSanctifier.loose`
100
+
101
+ Gently normalizes spacing and newlines while preserving emojis and paragraphs.
102
+
103
+ ### `summonSanctifier.keyboardOnly`
104
+
105
+ Restricts to printable ASCII only (removes emojis).
106
+
107
+ ### `summonSanctifier.keyboardOnlyEmoji`
108
+
109
+ Restricts to printable ASCII + full emoji sequences. Preserves ZWJ emoji clusters and emoji presentation variants.
110
+
111
+ ### `inspectText(text: string): UnicodeTrashReport`
112
+
113
+ Returns a structural report of control codes, invisible chars, newline styles, and more.
114
+
115
+ ```js
116
+ import { inspectText } from 'text-sanctifier';
117
+
118
+ const report = inspectText(input);
119
+
120
+ /*
121
+ {
122
+ hasControlChars: true,
123
+ hasInvisibleChars: true,
124
+ hasMixedNewlines: false,
125
+ newlineStyle: 'LF',
126
+ hasEmojis: true,
127
+ hasNonKeyboardChars: false,
128
+ summary: [
129
+ 'Control characters detected.',
130
+ 'Invisible Unicode characters detected.',
131
+ 'Emojis detected.',
132
+ 'Consistent newline style: LF'
133
+ ]
134
+ }
135
+ */
136
+ ```
137
+
138
+ Use `inspectText` to preflight text content before rendering, storing, or linting. It's a diagnostic tool to help inform sanitization needs. Pass the report to `getRecommendedSanctifierOptions(report)` to auto-generate config flags for `summonSanctifier()`.
139
+
140
+ ---
141
+
142
+ ## Notes
143
+
144
+ ### Runtime requirements
145
+
146
+ Requires a modern JavaScript runtime with ES2020+ support:
147
+
148
+ - Node.js 14+
149
+ - Modern evergreen browsers
150
+
151
+ ### Package and build info
152
+
153
+ - **Source (`src/`):** ES2020+ ESM modules with JSDoc
154
+ - **Browser build (`dist/`):** Minified ESM bundle for `<script type="module">`
155
+ - **Tree-shake friendly:** ships `sideEffects: false`
156
+ - **Zero transpilation:** no built-in polyfills or runtime overhead
157
+ - **Bundler ready:** works with Vite, Rollup, Webpack, Parcel, esbuild
158
+
159
+ ---
160
+
161
+ ## License
162
+
163
+ Licensed under AGPL-3.0 with WATT3D Additional Terms. See [LICENSE](./LICENSE) and [ADDITIONAL_TERMS.md](./ADDITIONAL_TERMS.md). Commercial AI/model-training use requires compliance with those terms or a separate WATT3D license. © WATT3D.
@@ -1,7 +1,9 @@
1
- function h(a={}){const b=!!a.purgeInvisibleChars,c=!!a.purgeEmojis,d=!!a.nukeControls,f=!!a.keyboardOnlyFilter,g=!!a.normalizeNewlines,l=!!a.trimSpacesAroundNewlines,e=!!a.collapseNewLines,p=!!a.preserveParagraphs,q=!!a.collapseSpaces,r=!!a.finalTrim;return x=>k(x,b,c,d,f,g,l,e,p,q,r)}h.strict=a=>k(a,!0,!0,!0,!1,!0,!0,!0,!1,!0,!0);h.loose=a=>k(a,!1,!1,!1,!1,!0,!0,!0,!0,!0,!0);h.keyboardOnlyEmoji=a=>k(a,!1,!1,!1,!0,!0,!0,!1,!1,!1,!0);h.keyboardOnly=a=>k(a,!0,!0,!0,!0,!0,!0,!0,!1,!0,!0);
2
- function k(a,b=!1,c=!1,d=!1,f=!1,g=!1,l=!1,e=!1,p=!1,q=!1,r=!1){if("string"!==typeof a)throw new TypeError("sanctifyText expects a string input.");b&&(a=a.replace(m,""));c&&(a=a.replace(n,""));d&&(a=a.replace(t,""));f&&(a=u(a,c));g&&(a=a.replace(v,"\n"));l&&(a=a.replace(w,"$1"));e&&(b=a,a=p?b.replace(y,"\n\n"):b.replace(z,"\n"));q&&(a=a.replace(A," "));return r?a.trim():a}var m=/[\u00A0\u2000-\u200D\u202F\u2060\u3000\uFEFF\u200E\u200F\u202A-\u202E]+/g,B=/[^\x20-\x7E\r\n]+/gu;
3
- function u(a,b=!1){a=C(a);return b?a.replace(B,""):a.replace(B,c=>{n.lastIndex=0;let d="";for(const f of c.matchAll(n))d+=f[0];return d})}var D=/[\u2018\u2019\u201A\u201B\u2032\u2035]/g,E=/[\u201C\u201D\u201E\u201F\u2033\u2036\u00AB\u00BB]/g,F=/[\u2012\u2013\u2014\u2015\u2212]/g,G=/\u2026/g,H=/[\u2022\u00B7]/g,I=/[\uFF01-\uFF5E]/g;function C(a){return a.replace(D,"'").replace(E,'"').replace(F,"-").replace(G,"...").replace(H,"*").replace(I,b=>String.fromCharCode(b.charCodeAt(0)-65248))}var n;
4
- try{n=RegExp("(?:\\p{Extended_Pictographic}(?:\\uFE0F|\\uFE0E)?(?:\\u200D(?:\\p{Extended_Pictographic}|\\w)+)*)","gu")}catch{n=/[\u{2700}-\u{27BF}\u{1F300}-\u{1FAFF}\u{1F3FB}-\u{1F3FF}\u200D\uFE0F\u{1F1E6}-\u{1F1FF}]/gu}var v=/\r\n|\r|\n/g,w=/[ \t]*(\n+)[ \t]*/g,z=/\n{2,}/g,y=/\n{3,}/g,A=/ {2,}/g,t=/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F\u0080-\u009F\u200E\u200F\u202A-\u202E]+/g;function J(a,b){a.lastIndex=0;return a.test(b)}
5
- function K(a){if("string"!==typeof a)throw new TypeError("inspectText expects a string input.");const b=[],c={hasControlChars:!1,hasInvisibleChars:!1,hasMixedNewlines:!1,newlineStyle:null,hasEmojis:!1,hasNonKeyboardChars:!1,summary:b};J(t,a)&&(c.hasControlChars=!0,b.push("Control characters detected."));J(m,a)&&(c.hasInvisibleChars=!0,b.push("Invisible Unicode characters detected."));J(n,a)&&(c.hasEmojis=!0,b.push("Emojis detected."));if("string"!==typeof a)throw new TypeError("getNewlineStats expects a string input.");
6
- var d=a.replace(/\r\n/g,"");d={h:(a.match(/\r\n/g)||[]).length,g:(d.match(/\r/g)||[]).length,i:(d.match(/\n/g)||[]).length};const f=[];0<d.h&&f.push("CRLF");0<d.g&&f.push("CR");0<d.i&&f.push("LF");const {j:g,types:l}={...d,types:f,j:1<f.length};c.hasMixedNewlines=g;c.newlineStyle=g?"Mixed":l[0]||null;c.newlineStyle&&b.push(g?"Mixed newline styles detected.":`Consistent newline style: ${c.newlineStyle}`);a:{a=C(a);if(J(B,a))for(e of a)if(!("\n"===e||"\r"===e||" "<=e&&"~">=e||J(n,e))){var e=!0;break a}e=
7
- !1}e&&(c.hasNonKeyboardChars=!0,b.push("Non-keyboard characters detected."));return c}function L(a){return{purgeInvisibleChars:a.hasInvisibleChars,purgeEmojis:a.hasEmojis,nukeControls:a.hasControlChars,keyboardOnlyFilter:a.hasNonKeyboardChars,normalizeNewlines:a.hasMixedNewlines||"CRLF"===a.newlineStyle||"CR"===a.newlineStyle}}export { h as summonSanctifier, K as inspectText, L as getRecommendedSanctifierOptions };
1
+ function g(a={}){const c=!!a.purgeInvisibleChars,b=!!a.purgeEmojis,d=!!a.nukeControls,f=!!a.keyboardOnlyFilter,h=!!a.normalizeNewlines,m=!!a.trimSpacesAroundNewlines,e=!!a.collapseNewLines,r=!!a.preserveParagraphs,t=!!a.collapseSpaces,u=!!a.finalTrim,n=Number.isFinite(a.maxLength)&&0<=a.maxLength?a.maxLength:Infinity,k="truncate"===a.g||"noop"===a.g||"throw"===a.g?a.g:"throw";return z=>l(z,c,b,d,f,h,m,e,r,t,u,n,k)}g.strict=a=>l(a,!0,!0,!0,!1,!0,!0,!0,!1,!0,!0);
2
+ g.loose=a=>l(a,!1,!1,!1,!1,!0,!0,!0,!0,!0,!0);g.keyboardOnlyEmoji=a=>l(a,!1,!1,!1,!0,!0,!0,!1,!1,!1,!0);g.keyboardOnly=a=>l(a,!0,!0,!0,!0,!0,!0,!0,!1,!0,!0);
3
+ function l(a,c=!1,b=!1,d=!1,f=!1,h=!1,m=!1,e=!1,r=!1,t=!1,u=!1,n=Infinity,k="throw"){if("string"!==typeof a)throw new TypeError("sanctifyText expects a string input.");"truncate"!==k&&"noop"!==k&&"throw"!==k&&(k="throw");if(a.length>n)switch(k){case "truncate":a=a.slice(0,n);break;case "noop":return a;default:throw new RangeError(`sanctifyText input length ${a.length} exceeds maxLength ${n}.`);}c&&(a=a.replace(p,""));b&&(a=a.replace(q,""));d&&(a=a.replace(v,""));f&&(a=w(a,b));h&&(a=a.replace(x,"\n"));
4
+ m&&(a=a.replace(y,"$1"));e&&(c=a,a=r?c.replace(A,"\n\n"):c.replace(B,"\n"));t&&(a=a.replace(C," "));return u?a.trim():a}var p=/[\u00A0\u2000-\u200D\u202F\u2060\u3000\uFEFF\u200E\u200F\u202A-\u202E]+/g,D=/[^\x20-\x7E\r\n]+/gu;function w(a,c=!1){a=E(a);return c?a.replace(D,""):a.replace(D,b=>{q.lastIndex=0;if(q.test(b)){q.lastIndex=0;var d="";for(const f of b.matchAll(q))d+=f[0];b=d}else b="";return b})}
5
+ var F=/[\u2018\u2019\u201A\u201B\u2032\u2035]/g,G=/[\u201C\u201D\u201E\u201F\u2033\u2036\u00AB\u00BB]/g,H=/[\u2012\u2013\u2014\u2015\u2212]/g,I=/\u2026/g,J=/[\u2022\u00B7]/g,K=/[\uFF01-\uFF5E]/g;function E(a){return a.replace(F,"'").replace(G,'"').replace(H,"-").replace(I,"...").replace(J,"*").replace(K,c=>String.fromCharCode(c.charCodeAt(0)-65248))}var q;try{q=RegExp("(?:\\p{Extended_Pictographic}(?:\\uFE0F|\\uFE0E)?(?:\\u200D(?:\\p{Extended_Pictographic}|\\w)+)*)","gu")}catch{q=/[\u{2700}-\u{27BF}\u{1F300}-\u{1FAFF}\u{1F3FB}-\u{1F3FF}\u200D\uFE0F\u{1F1E6}-\u{1F1FF}]/gu}
6
+ var x=/\r\n|\r|\n/g,y=/[ \t]*(\n+)[ \t]*/g,B=/\n{2,}/g,A=/\n{3,}/g,C=/ {2,}/g,v=/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F\u0080-\u009F\u200E\u200F\u202A-\u202E]+/g;function L(a,c){a.lastIndex=0;return a.test(c)}
7
+ function M(a){if("string"!==typeof a)throw new TypeError("inspectText expects a string input.");const c=[],b={hasControlChars:!1,hasInvisibleChars:!1,hasMixedNewlines:!1,newlineStyle:null,hasEmojis:!1,hasNonKeyboardChars:!1,summary:c};L(v,a)&&(b.hasControlChars=!0,c.push("Control characters detected."));L(p,a)&&(b.hasInvisibleChars=!0,c.push("Invisible Unicode characters detected."));L(q,a)&&(b.hasEmojis=!0,c.push("Emojis detected."));if("string"!==typeof a)throw new TypeError("getNewlineStats expects a string input.");
8
+ var d=a.replace(/\r\n/g,"");d={i:(a.match(/\r\n/g)||[]).length,h:(d.match(/\r/g)||[]).length,j:(d.match(/\n/g)||[]).length};const f=[];0<d.i&&f.push("CRLF");0<d.h&&f.push("CR");0<d.j&&f.push("LF");const {l:h,types:m}={...d,types:f,l:1<f.length};b.hasMixedNewlines=h;b.newlineStyle=h?"Mixed":m[0]||null;b.newlineStyle&&c.push(h?"Mixed newline styles detected.":`Consistent newline style: ${b.newlineStyle}`);a:{a=E(a);if(L(D,a))for(e of a)if(!("\n"===e||"\r"===e||" "<=e&&"~">=e||L(q,e))){var e=!0;break a}e=
9
+ !1}e&&(b.hasNonKeyboardChars=!0,c.push("Non-keyboard characters detected."));return b}function N(a){return{purgeInvisibleChars:a.hasInvisibleChars,purgeEmojis:a.hasEmojis,nukeControls:a.hasControlChars,keyboardOnlyFilter:a.hasNonKeyboardChars,normalizeNewlines:a.hasMixedNewlines||"CRLF"===a.newlineStyle||"CR"===a.newlineStyle}}export { g as summonSanctifier, M as inspectText, N as getRecommendedSanctifierOptions };
@@ -0,0 +1,4 @@
1
+ import { inspectText } from './inspectText.js';
2
+ import { getRecommendedSanctifierOptions } from './inspectText.js';
3
+ import { summonSanctifier } from './sanctifyText.js';
4
+ export { inspectText, getRecommendedSanctifierOptions, summonSanctifier };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Detects textual "trash" or anomalies in a given string.
3
+ * @param {string} text @returns {UnicodeTrashReport}
4
+ */
5
+ export function inspectText(text: string): UnicodeTrashReport;
6
+ /**
7
+ * Counts the number of different newline types in a string.
8
+ * @param {string} text
9
+ * @returns {{
10
+ * crlf: number,
11
+ * cr: number,
12
+ * lf: number,
13
+ * types: string[],
14
+ * mixed: boolean
15
+ * }}
16
+ */
17
+ export function getNewlineStats(text: string): {
18
+ crlf: number;
19
+ cr: number;
20
+ lf: number;
21
+ types: string[];
22
+ mixed: boolean;
23
+ };
24
+ /**
25
+ * Creates defaultOptions for summonSanctifier based on inspectText result
26
+ * @param {!UnicodeTrashReport} report
27
+ * @return {!SanctifyOptions}
28
+ */
29
+ export function getRecommendedSanctifierOptions(report: UnicodeTrashReport): SanctifyOptions;
30
+ export type UnicodeTrashReport = {
31
+ hasControlChars: boolean;
32
+ hasInvisibleChars: boolean;
33
+ hasMixedNewlines: boolean;
34
+ newlineStyle: "LF" | "CRLF" | "CR" | "Mixed" | null;
35
+ hasEmojis: boolean;
36
+ hasNonKeyboardChars: boolean;
37
+ summary: string[];
38
+ };
@@ -0,0 +1,167 @@
1
+ /**
2
+ * @typedef {Object} SanctifyOptions
3
+ * @property {boolean} [purgeInvisibleChars]
4
+ * @property {boolean} [purgeEmojis]
5
+ * @property {boolean} [nukeControls]
6
+ * @property {boolean} [keyboardOnlyFilter]
7
+ * @property {boolean} [normalizeNewlines]
8
+ * @property {boolean} [trimSpacesAroundNewlines]
9
+ * @property {boolean} [collapseNewLines]
10
+ * @property {boolean} [preserveParagraphs]
11
+ * @property {boolean} [collapseSpaces]
12
+ * @property {boolean} [finalTrim]
13
+ * @property {number} [maxLength]
14
+ * @property {'throw'|'truncate'|'noop'} [onMaxLength]
15
+ */
16
+ /**
17
+ * Summons a customized sanctifier function with pre-bound booleans.
18
+ *
19
+ * Accepts full flag names and returns a text-cleaning function.
20
+ *
21
+ * @param {Object} [defaultOptions={}]
22
+ * @param {boolean} [defaultOptions.purgeInvisibleChars]
23
+ * @param {boolean} [defaultOptions.purgeEmojis]
24
+ * @param {boolean} [defaultOptions.nukeControls]
25
+ * @param {boolean} [defaultOptions.keyboardOnlyFilter]
26
+ * @param {boolean} [defaultOptions.normalizeNewlines]
27
+ * @param {boolean} [defaultOptions.trimSpacesAroundNewlines]
28
+ * @param {boolean} [defaultOptions.collapseNewLines]
29
+ * @param {boolean} [defaultOptions.preserveParagraphs]
30
+ * @param {boolean} [defaultOptions.collapseSpaces]
31
+ * @param {boolean} [defaultOptions.finalTrim]
32
+ * @param {number} [defaultOptions.maxLength=Infinity] - Hard input length cap (UTF-16 code units).
33
+ * @param {'throw' | 'truncate' | 'noop'} [defaultOptions.onMaxLength='throw'] - Behavior when input exceeds maxLength.
34
+ * @returns {(text: string) => string}
35
+ */
36
+ export function summonSanctifier(defaultOptions?: {
37
+ purgeInvisibleChars?: boolean;
38
+ purgeEmojis?: boolean;
39
+ nukeControls?: boolean;
40
+ keyboardOnlyFilter?: boolean;
41
+ normalizeNewlines?: boolean;
42
+ trimSpacesAroundNewlines?: boolean;
43
+ collapseNewLines?: boolean;
44
+ preserveParagraphs?: boolean;
45
+ collapseSpaces?: boolean;
46
+ finalTrim?: boolean;
47
+ maxLength?: number;
48
+ onMaxLength?: "throw" | "truncate" | "noop";
49
+ }): (text: string) => string;
50
+ export namespace summonSanctifier {
51
+ /**
52
+ * Strict sanitizer:
53
+ * - Purge emojis
54
+ * - Collapse all newlines
55
+ * - Collapse spaces
56
+ * - Nuke control characters
57
+ */
58
+ /** @param {string} text @returns {string} */
59
+ function strict(text: string): string;
60
+ /**
61
+ * Loose sanitizer:
62
+ * - Collapse spaces
63
+ * - Preserve paragraphs
64
+ * - Normalize newlines
65
+ */
66
+ /** @param {string} text @returns {string} */
67
+ function loose(text: string): string;
68
+ /**
69
+ * Keyboard-only (with emojis):
70
+ * - Keeps emojis and printable ASCII
71
+ * - Strips non-standard characters
72
+ * - Normalizes typographic trash
73
+ */
74
+ /** @param {string} text @returns {string} */
75
+ function keyboardOnlyEmoji(text: string): string;
76
+ /**
77
+ * Keyboard-only (strict):
78
+ * - Removes emojis
79
+ * - Collapses all whitespace
80
+ * - Restricts to printable ASCII only
81
+ */
82
+ /** @param {string} text @returns {string} */
83
+ function keyboardOnly(text: string): string;
84
+ }
85
+ /**
86
+ * Text Sanctifier
87
+ *
88
+ * Brutal text normalizer and invisible trash scrubber,
89
+ * configurable to kill whatever ghosts you want dead.
90
+ *
91
+ * ⚠️ Note: This is plain-text normalization/filtering — not an HTML/XSS sanitizer.
92
+ *
93
+ * @param {string} text
94
+ * @param {boolean} [purgeInvisibleChars=false] - Remove ZWSP, NBSP, bidi, etc.
95
+ * @param {boolean} [purgeEmojis=false] - Remove emoji characters entirely.
96
+ * @param {boolean} [nukeControls=false] - Remove non-whitespace control characters.
97
+ * @param {boolean} [keyboardOnlyFilter=false] - Keep printable ASCII + full emoji sequences only (drops other Unicode).
98
+ * @param {boolean} [normalizeNewlines=false] - Convert all newlines to `\n`.
99
+ * @param {boolean} [trimSpacesAroundNewlines=false] - Remove spaces/tabs around newlines.
100
+ * @param {boolean} [collapseNewLines=false] - Collapse `\n` runs (optionally preserve paragraphs).
101
+ * @param {boolean} [preserveParagraphs=false] - Preserve paragraph breaks when collapsing newlines.
102
+ * @param {boolean} [collapseSpaces=false] - Collapse multiple spaces into one.
103
+ * @param {boolean} [finalTrim=false] - `.trim()` the final output (head/tail).
104
+ * @param {number} [maxLength=Infinity] - Hard input length cap (UTF-16 code units via `text.length`).
105
+ * @param {'throw'|'truncate'|'noop'} [onMaxLength='throw'] - Behavior when `text.length` exceeds `maxLength`:
106
+ * - `'throw'`: throw a `RangeError`
107
+ * - `'truncate'`: slice to `maxLength` before processing
108
+ * - `'noop'`: return the original input unchanged (skips processing)
109
+ * @returns {string}
110
+ * @throws {TypeError} If `text` is not a string.
111
+ * @throws {RangeError} If input exceeds `maxLength` and `onMaxLength` is `'throw'`.
112
+ */
113
+ export function sanctifyText(text: string, purgeInvisibleChars?: boolean, purgeEmojis?: boolean, nukeControls?: boolean, keyboardOnlyFilter?: boolean, normalizeNewlines?: boolean, trimSpacesAroundNewlines?: boolean, collapseNewLines?: boolean, preserveParagraphs?: boolean, collapseSpaces?: boolean, finalTrim?: boolean, maxLength?: number, onMaxLength?: "throw" | "truncate" | "noop"): string;
114
+ /**
115
+ * Normalizes typographic Unicode punctuation into ASCII equivalents.
116
+ * @param {string} text
117
+ * @returns {string}
118
+ */
119
+ export function normalizeTypographicJank(text: string): string;
120
+ /**
121
+ * Purges invisible Unicode "trash" characters and removes them.
122
+ *
123
+ * Targets:
124
+ * - Non-breaking spaces (\u00A0)
125
+ * - Zero-width spaces and miscellaneous Unicode spaces (\u2000–\u200D, \u202F, \u2060, \u3000, \uFEFF)
126
+ * - Left-to-right/right-to-left markers and overrides (\u200E, \u200F, \u202A–\u202E)
127
+ *
128
+ * @param {string} text
129
+ * @returns {string}
130
+ */
131
+ export const INVISIBLE_TRASH_REGEX: RegExp;
132
+ /**
133
+ * Matches any character that is NOT:
134
+ * - Printable ASCII (U+0020–U+007E)
135
+ * - Newline characters: \n (LF), \r (CR)
136
+ * This allows for \n, \r, and \r\n line endings.
137
+ */
138
+ export const ASCII_KEYBOARD_SAFE_REGEX: RegExp;
139
+ /** @type {RegExp} */
140
+ export let EMOJI_REGEX: RegExp;
141
+ /**
142
+ * Nukes hidden control characters that are invisible and often dangerous.
143
+ * (Excludes necessary whitespace like \n and \t.)
144
+ *
145
+ * Control characters nuked:
146
+ * - ASCII control range (0x00-0x1F, 0x7F)
147
+ * - Unicode control range (0x80-0x9F)
148
+ * - RTL/LTR markers (U+200E, U+200F, U+202A–U+202E)
149
+ *
150
+ * @param {string} text
151
+ * @returns {string}
152
+ */
153
+ export const CONTROL_CHARS_REGEX: RegExp;
154
+ export type SanctifyOptions = {
155
+ purgeInvisibleChars?: boolean;
156
+ purgeEmojis?: boolean;
157
+ nukeControls?: boolean;
158
+ keyboardOnlyFilter?: boolean;
159
+ normalizeNewlines?: boolean;
160
+ trimSpacesAroundNewlines?: boolean;
161
+ collapseNewLines?: boolean;
162
+ preserveParagraphs?: boolean;
163
+ collapseSpaces?: boolean;
164
+ finalTrim?: boolean;
165
+ maxLength?: number;
166
+ onMaxLength?: "throw" | "truncate" | "noop";
167
+ };