text-sanctifier 1.1.1 → 1.3.1
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 +78 -5
- package/bin/text-sanctifier.js +519 -0
- package/dist/text-sanctifier.min.js +14 -12
- package/dist/types/index.d.ts +3 -1
- package/dist/types/inspectText.d.ts +47 -18
- package/dist/types/sanctifyText.d.ts +8 -4
- package/package.json +6 -2
- package/src/index.d.ts +3 -1
- package/src/index.js +3 -2
- package/src/inspectText.js +390 -341
- package/src/sanctifyText.js +511 -507
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
function n(a={}){const c=!!a.purgeInvisibleChars,d=!!a.purgeEmojis,
|
|
2
|
-
n.loose=a=>
|
|
3
|
-
function
|
|
4
|
-
|
|
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;
|
|
6
|
-
var x=/\r\n|\r|\n/g,y=/[ \t]*(\n+)[ \t]*/g,B=/\n{2,}/g,
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
e))){var
|
|
10
|
-
function
|
|
11
|
-
b
|
|
12
|
-
|
|
1
|
+
function n(a={}){const c=!!a.purgeInvisibleChars,d=!!a.purgeEmojis,e=!!a.nukeControls,g=!!a.keyboardOnlyFilter,f=!!a.normalizeNewlines,l=!!a.trimSpacesAroundNewlines,b=!!a.collapseNewLines,h=!!a.preserveParagraphs,k=!!a.collapseSpaces,r=!!a.finalTrim,m=Number.isFinite(a.maxLength)&&0<=a.maxLength?a.maxLength:Infinity,p="truncate"===a.g||"noop"===a.g||"throw"===a.g?a.g:"throw";return A=>q(A,c,d,e,g,f,l,b,h,k,r,m,p)}n.strict=a=>q(a,!0,!0,!0,!1,!0,!0,!0,!1,!0,!0);
|
|
2
|
+
n.loose=a=>q(a,!1,!1,!1,!1,!0,!0,!0,!0,!0,!0);n.keyboardOnlyEmoji=a=>q(a,!1,!1,!1,!0,!0,!0,!1,!1,!1,!0);n.keyboardOnly=a=>q(a,!0,!0,!0,!0,!0,!0,!0,!1,!0,!0);
|
|
3
|
+
function q(a,c=!1,d=!1,e=!1,g=!1,f=!1,l=!1,b=!1,h=!1,k=!1,r=!1,m=Infinity,p="throw"){if("string"!==typeof a)throw new TypeError("sanctifyText expects a string input.");"truncate"!==p&&"noop"!==p&&"throw"!==p&&(p="throw");if(a.length>m)switch(p){case "truncate":a=a.slice(0,m);break;case "noop":return a;default:throw new RangeError(`sanctifyText input length ${a.length} exceeds maxLength ${m}.`);}c&&(a=a.replace(t,""));d&&(a=a.replace(u,""));e&&(a=a.replace(v,""));g&&(a=w(a,d));f&&(a=a.replace(x,"\n"));
|
|
4
|
+
l&&(a=a.replace(y,"$1"));b&&(c=a,a=h?c.replace(z,"\n\n"):c.replace(B,"\n"));k&&(a=a.replace(C," "));return r?a.trim():a}var t=/[\u00a0\u00ad\u034f\u061c\u115f\u1160\u180e\u2000-\u200f\u202a-\u202f\u205f\u2060-\u2064\u2066-\u2069\u3000\u3164\ufe00-\ufe0f\ufeff\uffa0\u{E0000}-\u{E007F}\u{E0100}-\u{E01EF}]+/gu,D=/[^\x20-\x7E\r\n]+/gu;
|
|
5
|
+
function w(a,c=!1){a=E(a);return c?a.replace(D,""):a.replace(D,d=>{u.lastIndex=0;if(u.test(d)){u.lastIndex=0;var e="";for(const g of d.matchAll(u))e+=g[0];d=e}else d="";return d})}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;
|
|
6
|
+
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 u;try{u=RegExp("(?:\\p{Extended_Pictographic}(?:\\uFE0F|\\uFE0E)?(?:\\u200D(?:\\p{Extended_Pictographic}|\\w)+)*)","gu")}catch{u=/[\u{2700}-\u{27BF}\u{1F300}-\u{1FAFF}\u{1F3FB}-\u{1F3FF}\u200D\uFE0F\u{1F1E6}-\u{1F1FF}]/gu}var x=/\r\n|\r|\n/g,y=/[ \t]*(\n+)[ \t]*/g,B=/\n{2,}/g,z=/\n{3,}/g,C=/ {2,}/g,v=/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F\u0080-\u009F\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]+/g;
|
|
7
|
+
function L(a,c){a.lastIndex=0;return a.test(c)}
|
|
8
|
+
function M(a){if("string"!==typeof a)throw new TypeError("inspectText expects a string input.");const c=[],d={hasControlChars:!1,hasInvisibleChars:!1,hasMixedNewlines:!1,newlineStyle:null,hasEmojis:!1,hasNonKeyboardChars:!1,summary:c};L(v,a)&&(d.hasControlChars=!0,c.push("Control characters detected."));L(t,a)&&(d.hasInvisibleChars=!0,c.push("Invisible Unicode characters detected."));L(u,a)&&(d.hasEmojis=!0,c.push("Emojis detected."));const {mixed:e,types:g}=N(a);d.hasMixedNewlines=e;d.newlineStyle=
|
|
9
|
+
e?"Mixed":g[0]||null;d.newlineStyle&&c.push(e?"Mixed newline styles detected.":`Consistent newline style: ${d.newlineStyle}`);a:{a=E(a);if(L(D,a))for(f of a)if(!("\n"===f||"\r"===f||" "<=f&&"~">=f||L(u,f))){var f=!0;break a}f=!1}f&&(d.hasNonKeyboardChars=!0,c.push("Non-keyboard characters detected."));return d}
|
|
10
|
+
function O(a,c={}){if("string"!==typeof a)throw new TypeError("inspectTextDetailed expects a string input.");c=c&&c.maxFindings;c=Infinity===c||Number.isInteger(c)&&0<=c?c:100;const d={bidi_control:0,zero_width:0,nonbreaking_space:0,bom:0,nul:0,c0_control:0,c1_control:0,invisible_format:0,hangul_filler:0,variation_selector:0,tag:0},e=[];let g=0,f=1,l=0;for(let k=0,r=a.length;k<r;k++){var b=a.charCodeAt(k);if(32<=b&&127>b)continue;if(10===b){f++;l=k+1;continue}if(13===b){10!==a.charCodeAt(k+1)&&(f++,
|
|
11
|
+
l=k+1);continue}const m=k;var h=b;if(56128===b){h=a.charCodeAt(k+1);if(!(56320<=h&&57343>=h))continue;h=65536+(b-55296<<10)+(h-56320);b=917504<=h&&917631>=h?"tag":917760<=h&&917999>=h?"variation_selector":null;k++}else b=128>b?0===b?"nul":32>b?9===b||10===b||13===b?null:"c0_control":127===b?"c0_control":null:159>=b?"c1_control":160===b?"nonbreaking_space":173===b||847===b||6158===b?"invisible_format":1564===b?"bidi_control":4447===b||4448===b?"hangul_filler":8192>b?null:8202>=b?"nonbreaking_space":
|
|
12
|
+
8205>=b?"zero_width":8207>=b||8234<=b&&8238>=b?"bidi_control":8239===b||8287===b?"nonbreaking_space":8288===b?"zero_width":8289<=b&&8292>=b?"invisible_format":8294<=b&&8297>=b?"bidi_control":12288===b?"nonbreaking_space":12644===b||65440===b?"hangul_filler":65024<=b&&65039>=b?"variation_selector":65279===b?"bom":null;null!==b&&(d[b]++,g++,e.length<c&&(h={offset:m,line:f,column:m-l+1,codePoint:h,category:b},"bom"===b&&(h.atStart=0===m),e.push(h)))}return{findings:e,counts:d,total:g,truncated:g>e.length}}
|
|
13
|
+
function N(a){if("string"!==typeof a)throw new TypeError("getNewlineStats expects a string input.");var c=0;let d=0,e=0;for(let g=0,f=a.length;g<f;g++){const l=a.charCodeAt(g);13===l?10===a.charCodeAt(g+1)?(c++,g++):d++:10===l&&e++}a={crlf:c,cr:d,lf:e};c=[];0<a.crlf&&c.push("CRLF");0<a.cr&&c.push("CR");0<a.lf&&c.push("LF");return{...a,types:c,mixed:1<c.length}}
|
|
14
|
+
function P(a){return{purgeInvisibleChars:a.hasInvisibleChars,purgeEmojis:a.hasEmojis,nukeControls:a.hasControlChars,keyboardOnlyFilter:a.hasNonKeyboardChars,normalizeNewlines:a.hasMixedNewlines||"CRLF"===a.newlineStyle||"CR"===a.newlineStyle}}export { n as summonSanctifier, M as inspectText, P as getRecommendedSanctifierOptions, O as inspectTextDetailed, N as getNewlineStats };
|
package/dist/types/index.d.ts
CHANGED
|
@@ -4,9 +4,11 @@ export type TrashFinding = import("./inspectText.js").TrashFinding;
|
|
|
4
4
|
export type TrashCounts = import("./inspectText.js").TrashCounts;
|
|
5
5
|
export type DetailedTrashReport = import("./inspectText.js").DetailedTrashReport;
|
|
6
6
|
export type InspectDetailedOptions = import("./inspectText.js").InspectDetailedOptions;
|
|
7
|
+
export type NewlineStats = import("./inspectText.js").NewlineStats;
|
|
7
8
|
export type SanctifyOptions = import("./sanctifyText.js").SanctifyOptions;
|
|
8
9
|
import { inspectText } from './inspectText.js';
|
|
9
10
|
import { inspectTextDetailed } from './inspectText.js';
|
|
11
|
+
import { getNewlineStats } from './inspectText.js';
|
|
10
12
|
import { getRecommendedSanctifierOptions } from './inspectText.js';
|
|
11
13
|
import { summonSanctifier } from './sanctifyText.js';
|
|
12
|
-
export { inspectText, inspectTextDetailed, getRecommendedSanctifierOptions, summonSanctifier };
|
|
14
|
+
export { inspectText, inspectTextDetailed, getNewlineStats, getRecommendedSanctifierOptions, summonSanctifier };
|
|
@@ -9,12 +9,17 @@ export function inspectText(text: string): UnicodeTrashReport;
|
|
|
9
9
|
* Categories:
|
|
10
10
|
* - `bidi_control`: U+061C, U+200E, U+200F, U+202A–U+202E, U+2066–U+2069
|
|
11
11
|
* - `zero_width`: U+200B–U+200D, U+2060
|
|
12
|
-
* - `nonbreaking_space`: non-breaking and typographic spaces U+00A0, U+2000–U+200A, U+202F, U+3000
|
|
12
|
+
* - `nonbreaking_space`: non-breaking and typographic spaces U+00A0, U+2000–U+200A, U+202F, U+205F, U+3000
|
|
13
13
|
* - `bom`: U+FEFF (`atStart` says whether it sits at offset 0)
|
|
14
14
|
* - `nul`: U+0000
|
|
15
15
|
* - `c0_control`: U+0001–U+001F except tab, LF and CR; and U+007F
|
|
16
16
|
* - `c1_control`: U+0080–U+009F
|
|
17
|
+
* - `invisible_format`: soft hyphen U+00AD, U+034F, U+180E, invisible operators U+2061–U+2064
|
|
18
|
+
* - `hangul_filler`: U+115F, U+1160, U+3164, U+FFA0 (letters that render blank)
|
|
19
|
+
* - `variation_selector`: U+FE00–U+FE0F, U+E0100–U+E01EF (including VS16 in emoji)
|
|
20
|
+
* - `tag`: U+E0000–U+E007F (tag characters, used for "ASCII smuggling")
|
|
17
21
|
*
|
|
22
|
+
* `codePoint` is the full code point; `offset` and `column` count UTF-16 units.
|
|
18
23
|
* Every occurrence is counted; only the first `maxFindings` are listed.
|
|
19
24
|
* One pass over the string, no regex, no copy.
|
|
20
25
|
*
|
|
@@ -24,23 +29,19 @@ export function inspectText(text: string): UnicodeTrashReport;
|
|
|
24
29
|
*/
|
|
25
30
|
export function inspectTextDetailed(text: string, options?: InspectDetailedOptions): DetailedTrashReport;
|
|
26
31
|
/**
|
|
27
|
-
*
|
|
32
|
+
* @typedef {Object} NewlineStats
|
|
33
|
+
* @property {number} crlf - CR LF pairs.
|
|
34
|
+
* @property {number} cr - CRs not followed by LF.
|
|
35
|
+
* @property {number} lf - LFs not preceded by CR.
|
|
36
|
+
* @property {('CRLF'|'CR'|'LF')[]} types - Styles present, in the order CRLF, CR, LF.
|
|
37
|
+
* @property {boolean} mixed - True when more than one style is present.
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Counts each newline style in one pass, with no regex and no copy.
|
|
28
41
|
* @param {string} text
|
|
29
|
-
* @returns {
|
|
30
|
-
* crlf: number,
|
|
31
|
-
* cr: number,
|
|
32
|
-
* lf: number,
|
|
33
|
-
* types: string[],
|
|
34
|
-
* mixed: boolean
|
|
35
|
-
* }}
|
|
42
|
+
* @returns {NewlineStats}
|
|
36
43
|
*/
|
|
37
|
-
export function getNewlineStats(text: string):
|
|
38
|
-
crlf: number;
|
|
39
|
-
cr: number;
|
|
40
|
-
lf: number;
|
|
41
|
-
types: string[];
|
|
42
|
-
mixed: boolean;
|
|
43
|
-
};
|
|
44
|
+
export function getNewlineStats(text: string): NewlineStats;
|
|
44
45
|
/**
|
|
45
46
|
* Creates defaultOptions for summonSanctifier based on inspectText result
|
|
46
47
|
* @param {!UnicodeTrashReport} report
|
|
@@ -57,10 +58,33 @@ export type UnicodeTrashReport = {
|
|
|
57
58
|
hasNonKeyboardChars: boolean;
|
|
58
59
|
summary: string[];
|
|
59
60
|
};
|
|
60
|
-
export type
|
|
61
|
+
export type NewlineStats = {
|
|
62
|
+
/**
|
|
63
|
+
* - CR LF pairs.
|
|
64
|
+
*/
|
|
65
|
+
crlf: number;
|
|
66
|
+
/**
|
|
67
|
+
* - CRs not followed by LF.
|
|
68
|
+
*/
|
|
69
|
+
cr: number;
|
|
70
|
+
/**
|
|
71
|
+
* - LFs not preceded by CR.
|
|
72
|
+
*/
|
|
73
|
+
lf: number;
|
|
74
|
+
/**
|
|
75
|
+
* - Styles present, in the order CRLF, CR, LF.
|
|
76
|
+
*/
|
|
77
|
+
types: ("CRLF" | "CR" | "LF")[];
|
|
78
|
+
/**
|
|
79
|
+
* - True when more than one style is present.
|
|
80
|
+
*/
|
|
81
|
+
mixed: boolean;
|
|
82
|
+
};
|
|
83
|
+
export type TrashCategory = "bidi_control" | "zero_width" | "nonbreaking_space" | "bom" | "nul" | "c0_control" | "c1_control" | "invisible_format" | "hangul_filler" | "variation_selector" | "tag";
|
|
61
84
|
/**
|
|
62
85
|
* One located character.
|
|
63
|
-
* - `offset`: 0-based UTF-16 index into the input (a JS string index)
|
|
86
|
+
* - `offset`: 0-based UTF-16 index into the input (a JS string index); for a code point
|
|
87
|
+
* above U+FFFF, the index of its high surrogate.
|
|
64
88
|
* - `line`: 1-based; lines end at LF, CR, or CRLF (counted once).
|
|
65
89
|
* - `column`: 1-based, in UTF-16 code units from the start of the line.
|
|
66
90
|
* - `atStart`: present on `bom` findings only; true at offset 0, where a BOM is legitimate.
|
|
@@ -81,6 +105,10 @@ export type TrashCounts = {
|
|
|
81
105
|
nul: number;
|
|
82
106
|
c0_control: number;
|
|
83
107
|
c1_control: number;
|
|
108
|
+
invisible_format: number;
|
|
109
|
+
hangul_filler: number;
|
|
110
|
+
variation_selector: number;
|
|
111
|
+
tag: number;
|
|
84
112
|
};
|
|
85
113
|
export type DetailedTrashReport = {
|
|
86
114
|
/**
|
|
@@ -103,6 +131,7 @@ export type DetailedTrashReport = {
|
|
|
103
131
|
export type InspectDetailedOptions = {
|
|
104
132
|
/**
|
|
105
133
|
* - Cap on listed findings (non-negative integer or Infinity).
|
|
134
|
+
* Each listed finding is an object, so memory grows with the number listed; 0 builds none (counts only).
|
|
106
135
|
*/
|
|
107
136
|
maxFindings?: number;
|
|
108
137
|
};
|
|
@@ -91,7 +91,7 @@ export namespace summonSanctifier {
|
|
|
91
91
|
* ⚠️ Note: This is plain-text normalization/filtering — not an HTML/XSS sanitizer.
|
|
92
92
|
*
|
|
93
93
|
* @param {string} text
|
|
94
|
-
* @param {boolean} [purgeInvisibleChars=false] - Remove ZWSP, NBSP, bidi, etc.
|
|
94
|
+
* @param {boolean} [purgeInvisibleChars=false] - Remove ZWSP, NBSP, bidi, tags, variation selectors, etc.
|
|
95
95
|
* @param {boolean} [purgeEmojis=false] - Remove emoji characters entirely.
|
|
96
96
|
* @param {boolean} [nukeControls=false] - Remove non-whitespace control characters.
|
|
97
97
|
* @param {boolean} [keyboardOnlyFilter=false] - Keep printable ASCII + full emoji sequences only (drops other Unicode).
|
|
@@ -121,9 +121,13 @@ export function normalizeTypographicJank(text: string): string;
|
|
|
121
121
|
* Purges invisible Unicode "trash" characters and removes them.
|
|
122
122
|
*
|
|
123
123
|
* Targets:
|
|
124
|
-
* - Non-breaking spaces (
|
|
125
|
-
* - Zero-width spaces and miscellaneous Unicode spaces (
|
|
126
|
-
* - Bidi controls: marks, embeddings/overrides and isolates (
|
|
124
|
+
* - Non-breaking spaces ( )
|
|
125
|
+
* - Zero-width spaces and miscellaneous Unicode spaces ( –, , , , , )
|
|
126
|
+
* - Bidi controls: marks, embeddings/overrides and isolates (, , , –, –)
|
|
127
|
+
* - Invisible format characters: soft hyphen, CGJ, Mongolian vowel separator, invisible operators (, ͏, , –)
|
|
128
|
+
* - Hangul fillers (ᅟ, ᅠ, ㅤ, ᅠ)
|
|
129
|
+
* - Variation selectors (︀–️, \u{E0100}–\u{E01EF}), including VS16 in emoji
|
|
130
|
+
* - Tag characters (\u{E0000}–\u{E007F}), used for "ASCII smuggling"
|
|
127
131
|
*
|
|
128
132
|
* @param {string} text
|
|
129
133
|
* @returns {string}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "text-sanctifier",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A brutal text normalizer and invisible trash scrubber for modern web projects.",
|
|
6
6
|
"engines": {
|
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
"main": "./src/index.js",
|
|
10
10
|
"module": "./src/index.js",
|
|
11
11
|
"browser": "./dist/text-sanctifier.min.js",
|
|
12
|
+
"bin": {
|
|
13
|
+
"text-sanctifier": "bin/text-sanctifier.js"
|
|
14
|
+
},
|
|
12
15
|
"files": [
|
|
13
16
|
"src",
|
|
17
|
+
"bin",
|
|
14
18
|
"dist/text-sanctifier.min.js",
|
|
15
19
|
"dist/types",
|
|
16
20
|
"LICENSE",
|
|
@@ -34,7 +38,7 @@
|
|
|
34
38
|
"build": "node scripts/buildc.js",
|
|
35
39
|
"build:types": "tsc -p tsconfig.types.json",
|
|
36
40
|
"test:types": "tsc -p tests/types/tsconfig.json",
|
|
37
|
-
"test": "node tests/sanctifyText.test.js && node tests/inspectTextDetailed.test.js",
|
|
41
|
+
"test": "node tests/sanctifyText.test.js && node tests/inspectTextDetailed.test.js && node tests/cli.test.js",
|
|
38
42
|
"test-min": "node tests/sanctifyText.test.min.js",
|
|
39
43
|
"ci": "npm run test && npm run build && npm run build:types && npm run test:types && npm run test-min"
|
|
40
44
|
},
|
package/src/index.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { inspectText } from './inspectText.js';
|
|
2
2
|
import { inspectTextDetailed } from './inspectText.js';
|
|
3
|
+
import { getNewlineStats } from './inspectText.js';
|
|
3
4
|
import { getRecommendedSanctifierOptions } from './inspectText.js';
|
|
4
5
|
import { summonSanctifier } from './sanctifyText.js';
|
|
5
|
-
export { inspectText, inspectTextDetailed, getRecommendedSanctifierOptions, summonSanctifier };
|
|
6
|
+
export { inspectText, inspectTextDetailed, getNewlineStats, getRecommendedSanctifierOptions, summonSanctifier };
|
|
6
7
|
export type UnicodeTrashReport = import('./inspectText.js').UnicodeTrashReport;
|
|
7
8
|
export type TrashCategory = import('./inspectText.js').TrashCategory;
|
|
8
9
|
export type TrashFinding = import('./inspectText.js').TrashFinding;
|
|
9
10
|
export type TrashCounts = import('./inspectText.js').TrashCounts;
|
|
10
11
|
export type DetailedTrashReport = import('./inspectText.js').DetailedTrashReport;
|
|
11
12
|
export type InspectDetailedOptions = import('./inspectText.js').InspectDetailedOptions;
|
|
13
|
+
export type NewlineStats = import('./inspectText.js').NewlineStats;
|
|
12
14
|
export type SanctifyOptions = import('./sanctifyText.js').SanctifyOptions;
|
package/src/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// src/index.js
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
import { inspectText, inspectTextDetailed, getRecommendedSanctifierOptions } from './inspectText.js';
|
|
5
|
-
export { inspectText, inspectTextDetailed, getRecommendedSanctifierOptions };
|
|
4
|
+
import { inspectText, inspectTextDetailed, getNewlineStats, getRecommendedSanctifierOptions } from './inspectText.js';
|
|
5
|
+
export { inspectText, inspectTextDetailed, getNewlineStats, getRecommendedSanctifierOptions };
|
|
6
6
|
|
|
7
7
|
import { summonSanctifier } from './sanctifyText.js';
|
|
8
8
|
export { summonSanctifier };
|
|
@@ -14,6 +14,7 @@ export { summonSanctifier };
|
|
|
14
14
|
/** @typedef {import('./inspectText.js').TrashCounts} TrashCounts */
|
|
15
15
|
/** @typedef {import('./inspectText.js').DetailedTrashReport} DetailedTrashReport */
|
|
16
16
|
/** @typedef {import('./inspectText.js').InspectDetailedOptions} InspectDetailedOptions */
|
|
17
|
+
/** @typedef {import('./inspectText.js').NewlineStats} NewlineStats */
|
|
17
18
|
/** @typedef {import('./sanctifyText.js').SanctifyOptions} SanctifyOptions */
|
|
18
19
|
|
|
19
20
|
|