@email-utils/validator-syntax 0.0.1-9 → 1.0.0-rc.2

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/dist/index.mjs ADDED
@@ -0,0 +1,2307 @@
1
+ //#region src/chars.ts
2
+ const charCodeAt = String.prototype.charCodeAt;
3
+ /**
4
+ * `text.charCodeAt(i)`, through the builtin itself. Called as a method,
5
+ * `charCodeAt` is looked up on the string, and a call site that has seen
6
+ * enough kinds of string (one-byte and two-byte, flat, sliced, and
7
+ * concatenated) goes megamorphic: V8 stops inlining it, and after arbitrary
8
+ * Unicode input every scan ran 2–3× slower (validator-syntax#15).
9
+ */
10
+ function codeAt(text, i) {
11
+ return charCodeAt.call(text, i);
12
+ }
13
+ const table = /* @__PURE__ */ new Uint8Array(128);
14
+ function mark(chars, flag) {
15
+ for (let i = 0; i < chars.length; i++) table[codeAt(chars, i)] |= flag;
16
+ }
17
+ const alnum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
18
+ mark(alnum + "!#$%&'*+-/=?^_`{|}~", 1);
19
+ mark(alnum + "#$&'*+-/=?^_`{|}~", 2);
20
+ mark(alnum + "!#$%&'*+-/=?^_`{|}~.", 4);
21
+ mark(alnum + "-", 8);
22
+ /** Whether the character with this code is in the class `flag`. */
23
+ function inClass(code, flag) {
24
+ return code < 128 && (table[code] & flag) !== 0;
25
+ }
26
+ /** Space, tab, CR, and LF: the characters folding whitespace is made of. */
27
+ function isWhitespace(code) {
28
+ return code === 32 || code === 9 || code === 13 || code === 10;
29
+ }
30
+ /**
31
+ * The code units in the non-ASCII code point at `i`, before `end`: 2 for a
32
+ * surrogate pair, 1 for anything else outside ASCII, and 0 for ASCII or a
33
+ * lone surrogate, which no UTF-8 text can hold.
34
+ */
35
+ function nonAsciiAt(text, i, end) {
36
+ const code = codeAt(text, i);
37
+ if (code < 128 || code >= 56320 && code <= 57343) return 0;
38
+ if (code < 55296 || code > 56319) return 1;
39
+ const low = i + 1 < end ? codeAt(text, i + 1) : 0;
40
+ return low >= 56320 && low <= 57343 ? 2 : 0;
41
+ }
42
+ /** The length of `text` in UTF-8 octets, the unit RFC 6531 caps a local part in. */
43
+ function utf8Length(text) {
44
+ const end = text.length;
45
+ let octets = end;
46
+ for (let i = 0; i < end; i++) {
47
+ const code = codeAt(text, i);
48
+ if (code >= 2048 && (code < 55296 || code > 57343)) octets += 2;
49
+ else if (code >= 128) octets += 1;
50
+ }
51
+ return octets;
52
+ }
53
+ /**
54
+ * The code points in `text` from `start` to `end`, which must not split a
55
+ * surrogate pair: its length with each pair counted once.
56
+ */
57
+ function codePoints(text, start, end) {
58
+ let count = end - start;
59
+ for (let i = start; i < end; i++) {
60
+ const code = codeAt(text, i);
61
+ if (code >= 56320 && code <= 57343) count--;
62
+ }
63
+ return count;
64
+ }
65
+ //#endregion
66
+ //#region src/options.ts
67
+ const presets = {
68
+ practical: {
69
+ local: 2,
70
+ domain: 8,
71
+ quotes: false,
72
+ literals: false,
73
+ comments: false,
74
+ obs: false,
75
+ localCap: 64,
76
+ domainCap: 253,
77
+ checkTld: true,
78
+ allowNoTld: false,
79
+ unicode: false,
80
+ idn: false,
81
+ maxLength: 512
82
+ },
83
+ rfc5321: {
84
+ local: 1,
85
+ domain: 8,
86
+ quotes: true,
87
+ literals: true,
88
+ comments: false,
89
+ obs: false,
90
+ localCap: 64,
91
+ domainCap: 253,
92
+ checkTld: false,
93
+ allowNoTld: false,
94
+ unicode: false,
95
+ idn: false,
96
+ maxLength: 512
97
+ },
98
+ rfc5322: {
99
+ local: 1,
100
+ domain: 1,
101
+ quotes: true,
102
+ literals: true,
103
+ comments: true,
104
+ obs: true,
105
+ localCap: Infinity,
106
+ domainCap: 253,
107
+ checkTld: false,
108
+ allowNoTld: false,
109
+ unicode: false,
110
+ idn: false,
111
+ maxLength: 512
112
+ },
113
+ html5: {
114
+ local: 4,
115
+ domain: 8,
116
+ quotes: false,
117
+ literals: false,
118
+ comments: false,
119
+ obs: false,
120
+ localCap: Infinity,
121
+ domainCap: Infinity,
122
+ checkTld: false,
123
+ allowNoTld: true,
124
+ unicode: false,
125
+ idn: false,
126
+ maxLength: 512
127
+ }
128
+ };
129
+ const overrides = [
130
+ "checkTld",
131
+ "allowNoTld",
132
+ "allowComments",
133
+ "allowUnicode",
134
+ "allowIdn",
135
+ "allowIpLiteral"
136
+ ];
137
+ const unsupported = {
138
+ practical: [],
139
+ rfc5321: ["allowComments"],
140
+ rfc5322: [],
141
+ html5: [
142
+ "allowComments",
143
+ "allowUnicode",
144
+ "allowIdn",
145
+ "allowIpLiteral"
146
+ ]
147
+ };
148
+ /**
149
+ * Resolves `options` into the rules they select.
150
+ *
151
+ * @throws TypeError when `options` isn't an object, names an unknown option
152
+ * or preset, gives a non-boolean override or a `maxLength` that isn't a
153
+ * positive integer or `Infinity`, or turns on something the preset's grammar
154
+ * has no room for.
155
+ */
156
+ function resolve(options) {
157
+ if (options === void 0) return presets.practical;
158
+ if (typeof options !== "object" || options === null) throw new TypeError("Expected the options to be an object");
159
+ for (const key of Object.keys(options)) if (key !== "preset" && key !== "maxLength" && !overrides.includes(key)) throw new TypeError(`Unknown option: ${key}`);
160
+ const preset = options.preset ?? "practical";
161
+ if (!Object.hasOwn(presets, preset)) throw new TypeError(`Unknown preset: ${preset}`);
162
+ const base = presets[preset];
163
+ for (const key of overrides) {
164
+ const value = options[key];
165
+ if (value !== void 0 && typeof value !== "boolean") throw new TypeError(`Expected ${key} to be a boolean`);
166
+ }
167
+ const { maxLength } = options;
168
+ if (maxLength !== void 0 && maxLength !== Infinity && !(Number.isInteger(maxLength) && maxLength > 0)) throw new TypeError("Expected maxLength to be a positive integer or Infinity");
169
+ for (const key of unsupported[preset]) if (options[key] === true) throw new TypeError(`${key} can’t be true with the ${preset} preset`);
170
+ if (maxLength === void 0 && overrides.every((key) => options[key] === void 0)) return base;
171
+ return {
172
+ ...base,
173
+ checkTld: options.checkTld ?? base.checkTld,
174
+ allowNoTld: options.allowNoTld ?? base.allowNoTld,
175
+ comments: options.allowComments ?? base.comments,
176
+ unicode: options.allowUnicode ?? base.unicode,
177
+ idn: options.allowIdn ?? base.idn,
178
+ literals: options.allowIpLiteral ?? base.literals,
179
+ maxLength: maxLength ?? base.maxLength
180
+ };
181
+ }
182
+ //#endregion
183
+ //#region src/idn.ts
184
+ const ldh = /^[\da-z-]+$/;
185
+ /**
186
+ * The hostname of `http://x.` and `host`, or `undefined` when the WHATWG URL
187
+ * parser rejects it. `URL.parse` says so without the cost of throwing, a few
188
+ * µs a time, so an address built to fail many conversions stays fast;
189
+ * runtimes that predate it (Node before 22.1) throw instead.
190
+ */
191
+ const hostnameOf = typeof URL.parse === "function" ? (host) => URL.parse(`http://x.${host}`)?.hostname : (host) => {
192
+ try {
193
+ return new URL(`http://x.${host}`).hostname;
194
+ } catch {
195
+ return;
196
+ }
197
+ };
198
+ /**
199
+ * The A-label for a U-label, or `undefined` when UTS #46 rejects it or maps
200
+ * it to something other than one IDN label (plain ASCII, or two labels, as
201
+ * `。` becomes a dot).
202
+ *
203
+ * @remarks
204
+ * Converts by way of the WHATWG URL host parser, which every runtime the
205
+ * package supports has and which applies UTS #46 with its bidi and joiner
206
+ * checks. It leaves hyphens and lengths alone, so the caller checks those.
207
+ */
208
+ function toALabel(label) {
209
+ const aLabel = hostnameOf(label)?.slice(2);
210
+ return aLabel?.startsWith("xn--") === true && ldh.test(aLabel) ? aLabel : void 0;
211
+ }
212
+ const rtl = /[\u0590-\u08ff\u2135-\u2138\ufb1d-\ufdff\ufe70-\ufeff\u{10800}-\u{10fff}\u{1e800}-\u{1efff}]/u;
213
+ /**
214
+ * The A-label for each U-label in `labels`, as {@link toALabel} gives it,
215
+ * converted as few domains as it can: one URL costs about as much as one
216
+ * label does.
217
+ *
218
+ * @remarks
219
+ * Every entry up to the first `undefined` is exact; entries after it may be
220
+ * left `undefined` unconverted. Converted together, a right-to-left label
221
+ * would hold the others to RFC 5893's rules too, which it doesn't alone, so
222
+ * right-to-left labels go in a domain of their own. Converting more labels
223
+ * at once only adds rules, so a domain that converts means every label
224
+ * would alone; one that doesn't is narrowed down to its first label that
225
+ * fails alone.
226
+ */
227
+ function toALabels(labels) {
228
+ if (!labels.some((label) => rtl.test(label))) return convertGroup(labels);
229
+ const aLabels = labels.map(() => void 0);
230
+ for (const bidi of [false, true]) {
231
+ const members = [];
232
+ labels.forEach((label, k) => {
233
+ if (rtl.test(label) === bidi) members.push(k);
234
+ });
235
+ if (members.length > 0) convertGroup(members.map((k) => labels[k])).forEach((aLabel, j) => {
236
+ aLabels[members[j]] = aLabel;
237
+ });
238
+ }
239
+ return aLabels;
240
+ }
241
+ /** {@link toALabels} for labels converted together. */
242
+ function convertGroup(labels) {
243
+ const all = convert(labels);
244
+ if (all !== void 0) return all;
245
+ let pass = [];
246
+ let failing = labels.length;
247
+ while (failing - pass.length > 1) {
248
+ const some = convert(labels.slice(0, pass.length + failing >> 1));
249
+ if (some === void 0) failing = pass.length + failing >> 1;
250
+ else pass = some;
251
+ }
252
+ const aLabels = pass;
253
+ for (let k = pass.length; k < labels.length; k++) {
254
+ const aLabel = toALabel(labels[k]);
255
+ aLabels.push(aLabel);
256
+ if (aLabel === void 0) break;
257
+ }
258
+ return aLabels;
259
+ }
260
+ const aLabelHost = /^x(?:\.xn--[\da-z-]+)+$/;
261
+ /** `labels` converted as one domain, or `undefined`. */
262
+ function convert(labels) {
263
+ const hostname = hostnameOf(labels.join("."));
264
+ if (hostname === void 0 || !aLabelHost.test(hostname)) return;
265
+ const aLabels = hostname.split(".");
266
+ aLabels.shift();
267
+ return aLabels.length === labels.length ? aLabels : void 0;
268
+ }
269
+ //#endregion
270
+ //#region src/ip.ts
271
+ const octet = /^\d{1,3}$/;
272
+ const hexGroup = /^[\dA-Fa-f]{1,4}$/;
273
+ /** An IPv4 address: four dot-separated decimal octets, each 0–255. */
274
+ function isIPv4(text) {
275
+ if (text.length > 15) return false;
276
+ const parts = text.split(".");
277
+ return parts.length === 4 && parts.every((part) => octet.test(part) && Number(part) <= 255);
278
+ }
279
+ /**
280
+ * An IPv6 address as RFC 5321 writes it: eight hex groups, or a `::` with no
281
+ * more than six groups beside it, and either may end in an IPv4 address in
282
+ * place of the last two groups.
283
+ */
284
+ function isIPv6(text) {
285
+ if (text.length > 45) return false;
286
+ const colon = text.lastIndexOf(":");
287
+ const tail = text.slice(colon + 1);
288
+ if (tail.includes(".")) {
289
+ if (!isIPv4(tail)) return false;
290
+ text = `${text.slice(0, colon + 1)}0:0`;
291
+ }
292
+ const halves = text.split("::");
293
+ if (halves.length > 2) return false;
294
+ const groups = halves.flatMap((half) => half === "" ? [] : half.split(":"));
295
+ return groups.every((group) => hexGroup.test(group)) && (halves.length === 1 ? groups.length === 8 : groups.length <= 6);
296
+ }
297
+ /** The text of an RFC 5321 address literal: IPv4, or `IPv6:` and IPv6. */
298
+ function isAddressLiteral(text) {
299
+ return /^IPv6:/i.test(text) ? isIPv6(text.slice(5)) : isIPv4(text);
300
+ }
301
+ //#endregion
302
+ //#region node_modules/tlds/index.json
303
+ var tlds_default = [
304
+ "aaa",
305
+ "aarp",
306
+ "abb",
307
+ "abbott",
308
+ "abbvie",
309
+ "abc",
310
+ "able",
311
+ "abogado",
312
+ "abudhabi",
313
+ "ac",
314
+ "academy",
315
+ "accenture",
316
+ "accountant",
317
+ "accountants",
318
+ "aco",
319
+ "actor",
320
+ "ad",
321
+ "ads",
322
+ "adult",
323
+ "ae",
324
+ "aeg",
325
+ "aero",
326
+ "aetna",
327
+ "af",
328
+ "afl",
329
+ "africa",
330
+ "ag",
331
+ "agakhan",
332
+ "agency",
333
+ "ai",
334
+ "aig",
335
+ "airbus",
336
+ "airforce",
337
+ "airtel",
338
+ "akdn",
339
+ "al",
340
+ "alibaba",
341
+ "alipay",
342
+ "allfinanz",
343
+ "allstate",
344
+ "ally",
345
+ "alsace",
346
+ "alstom",
347
+ "am",
348
+ "amazon",
349
+ "americanexpress",
350
+ "americanfamily",
351
+ "amex",
352
+ "amfam",
353
+ "amica",
354
+ "amsterdam",
355
+ "analytics",
356
+ "android",
357
+ "anquan",
358
+ "anz",
359
+ "ao",
360
+ "aol",
361
+ "apartments",
362
+ "app",
363
+ "apple",
364
+ "aq",
365
+ "aquarelle",
366
+ "ar",
367
+ "arab",
368
+ "aramco",
369
+ "archi",
370
+ "army",
371
+ "arpa",
372
+ "art",
373
+ "arte",
374
+ "as",
375
+ "asda",
376
+ "asia",
377
+ "associates",
378
+ "at",
379
+ "athleta",
380
+ "attorney",
381
+ "au",
382
+ "auction",
383
+ "audi",
384
+ "audible",
385
+ "audio",
386
+ "auspost",
387
+ "author",
388
+ "auto",
389
+ "autos",
390
+ "aw",
391
+ "aws",
392
+ "ax",
393
+ "axa",
394
+ "az",
395
+ "azure",
396
+ "ba",
397
+ "baby",
398
+ "baidu",
399
+ "banamex",
400
+ "band",
401
+ "bank",
402
+ "bar",
403
+ "barcelona",
404
+ "barclaycard",
405
+ "barclays",
406
+ "barefoot",
407
+ "bargains",
408
+ "baseball",
409
+ "basketball",
410
+ "bauhaus",
411
+ "bayern",
412
+ "bb",
413
+ "bbc",
414
+ "bbt",
415
+ "bbva",
416
+ "bcg",
417
+ "bcn",
418
+ "bd",
419
+ "be",
420
+ "beats",
421
+ "beauty",
422
+ "beer",
423
+ "berlin",
424
+ "best",
425
+ "bestbuy",
426
+ "bet",
427
+ "bf",
428
+ "bg",
429
+ "bh",
430
+ "bharti",
431
+ "bi",
432
+ "bible",
433
+ "bid",
434
+ "bike",
435
+ "bing",
436
+ "bingo",
437
+ "bio",
438
+ "biz",
439
+ "bj",
440
+ "black",
441
+ "blackfriday",
442
+ "blockbuster",
443
+ "blog",
444
+ "bloomberg",
445
+ "blue",
446
+ "bm",
447
+ "bms",
448
+ "bmw",
449
+ "bn",
450
+ "bnpparibas",
451
+ "bo",
452
+ "boats",
453
+ "boehringer",
454
+ "bofa",
455
+ "bom",
456
+ "bond",
457
+ "boo",
458
+ "book",
459
+ "booking",
460
+ "bosch",
461
+ "bostik",
462
+ "boston",
463
+ "bot",
464
+ "boutique",
465
+ "box",
466
+ "br",
467
+ "bradesco",
468
+ "bridgestone",
469
+ "broadway",
470
+ "broker",
471
+ "brother",
472
+ "brussels",
473
+ "bs",
474
+ "bt",
475
+ "build",
476
+ "builders",
477
+ "business",
478
+ "buy",
479
+ "buzz",
480
+ "bv",
481
+ "bw",
482
+ "by",
483
+ "bz",
484
+ "bzh",
485
+ "ca",
486
+ "cab",
487
+ "cafe",
488
+ "cal",
489
+ "call",
490
+ "calvinklein",
491
+ "cam",
492
+ "camera",
493
+ "camp",
494
+ "canon",
495
+ "capetown",
496
+ "capital",
497
+ "capitalone",
498
+ "car",
499
+ "caravan",
500
+ "cards",
501
+ "care",
502
+ "career",
503
+ "careers",
504
+ "cars",
505
+ "casa",
506
+ "case",
507
+ "cash",
508
+ "casino",
509
+ "cat",
510
+ "catering",
511
+ "catholic",
512
+ "cba",
513
+ "cbn",
514
+ "cbre",
515
+ "cc",
516
+ "cd",
517
+ "center",
518
+ "ceo",
519
+ "cern",
520
+ "cf",
521
+ "cfa",
522
+ "cfd",
523
+ "cg",
524
+ "ch",
525
+ "chanel",
526
+ "channel",
527
+ "charity",
528
+ "chase",
529
+ "chat",
530
+ "cheap",
531
+ "chintai",
532
+ "christmas",
533
+ "chrome",
534
+ "church",
535
+ "ci",
536
+ "cipriani",
537
+ "circle",
538
+ "cisco",
539
+ "citadel",
540
+ "citi",
541
+ "citic",
542
+ "city",
543
+ "ck",
544
+ "cl",
545
+ "claims",
546
+ "cleaning",
547
+ "click",
548
+ "clinic",
549
+ "clinique",
550
+ "clothing",
551
+ "cloud",
552
+ "club",
553
+ "clubmed",
554
+ "cm",
555
+ "cn",
556
+ "co",
557
+ "coach",
558
+ "codes",
559
+ "coffee",
560
+ "college",
561
+ "cologne",
562
+ "com",
563
+ "commbank",
564
+ "community",
565
+ "company",
566
+ "compare",
567
+ "computer",
568
+ "comsec",
569
+ "condos",
570
+ "construction",
571
+ "consulting",
572
+ "contact",
573
+ "contractors",
574
+ "cooking",
575
+ "cool",
576
+ "coop",
577
+ "corsica",
578
+ "country",
579
+ "coupon",
580
+ "coupons",
581
+ "courses",
582
+ "cpa",
583
+ "cr",
584
+ "credit",
585
+ "creditcard",
586
+ "creditunion",
587
+ "cricket",
588
+ "crown",
589
+ "crs",
590
+ "cruise",
591
+ "cruises",
592
+ "cu",
593
+ "cuisinella",
594
+ "cv",
595
+ "cw",
596
+ "cx",
597
+ "cy",
598
+ "cymru",
599
+ "cyou",
600
+ "cz",
601
+ "dad",
602
+ "dance",
603
+ "data",
604
+ "date",
605
+ "dating",
606
+ "datsun",
607
+ "day",
608
+ "dclk",
609
+ "dds",
610
+ "de",
611
+ "deal",
612
+ "dealer",
613
+ "deals",
614
+ "degree",
615
+ "delivery",
616
+ "dell",
617
+ "deloitte",
618
+ "delta",
619
+ "democrat",
620
+ "dental",
621
+ "dentist",
622
+ "desi",
623
+ "design",
624
+ "dev",
625
+ "dhl",
626
+ "diamonds",
627
+ "diet",
628
+ "digital",
629
+ "direct",
630
+ "directory",
631
+ "discount",
632
+ "discover",
633
+ "dish",
634
+ "diy",
635
+ "dj",
636
+ "dk",
637
+ "dm",
638
+ "dnp",
639
+ "do",
640
+ "docs",
641
+ "doctor",
642
+ "dog",
643
+ "domains",
644
+ "dot",
645
+ "download",
646
+ "drive",
647
+ "dtv",
648
+ "dubai",
649
+ "dupont",
650
+ "durban",
651
+ "dvag",
652
+ "dvr",
653
+ "dz",
654
+ "earth",
655
+ "eat",
656
+ "ec",
657
+ "eco",
658
+ "edeka",
659
+ "edu",
660
+ "education",
661
+ "ee",
662
+ "eg",
663
+ "email",
664
+ "emerck",
665
+ "energy",
666
+ "engineer",
667
+ "engineering",
668
+ "enterprises",
669
+ "epson",
670
+ "equipment",
671
+ "er",
672
+ "ericsson",
673
+ "erni",
674
+ "es",
675
+ "esq",
676
+ "estate",
677
+ "et",
678
+ "eu",
679
+ "eurovision",
680
+ "eus",
681
+ "events",
682
+ "exchange",
683
+ "expert",
684
+ "exposed",
685
+ "express",
686
+ "extraspace",
687
+ "fage",
688
+ "fail",
689
+ "fairwinds",
690
+ "faith",
691
+ "family",
692
+ "fan",
693
+ "fans",
694
+ "farm",
695
+ "farmers",
696
+ "fashion",
697
+ "fast",
698
+ "fedex",
699
+ "feedback",
700
+ "ferrari",
701
+ "ferrero",
702
+ "fi",
703
+ "fidelity",
704
+ "fido",
705
+ "film",
706
+ "final",
707
+ "finance",
708
+ "financial",
709
+ "fire",
710
+ "firestone",
711
+ "firmdale",
712
+ "fish",
713
+ "fishing",
714
+ "fit",
715
+ "fitness",
716
+ "fj",
717
+ "fk",
718
+ "flickr",
719
+ "flights",
720
+ "flir",
721
+ "florist",
722
+ "flowers",
723
+ "fly",
724
+ "fm",
725
+ "fo",
726
+ "foo",
727
+ "food",
728
+ "football",
729
+ "ford",
730
+ "forex",
731
+ "forsale",
732
+ "forum",
733
+ "foundation",
734
+ "fox",
735
+ "fr",
736
+ "free",
737
+ "fresenius",
738
+ "frl",
739
+ "frogans",
740
+ "frontier",
741
+ "ftr",
742
+ "fujitsu",
743
+ "fun",
744
+ "fund",
745
+ "furniture",
746
+ "futbol",
747
+ "fyi",
748
+ "ga",
749
+ "gal",
750
+ "gallery",
751
+ "gallo",
752
+ "gallup",
753
+ "game",
754
+ "games",
755
+ "gap",
756
+ "garden",
757
+ "gay",
758
+ "gb",
759
+ "gbiz",
760
+ "gd",
761
+ "gdn",
762
+ "ge",
763
+ "gea",
764
+ "gent",
765
+ "genting",
766
+ "george",
767
+ "gf",
768
+ "gg",
769
+ "ggee",
770
+ "gh",
771
+ "gi",
772
+ "gift",
773
+ "gifts",
774
+ "gives",
775
+ "giving",
776
+ "gl",
777
+ "glass",
778
+ "gle",
779
+ "global",
780
+ "globo",
781
+ "gm",
782
+ "gmail",
783
+ "gmbh",
784
+ "gmo",
785
+ "gmx",
786
+ "gn",
787
+ "godaddy",
788
+ "gold",
789
+ "goldpoint",
790
+ "golf",
791
+ "goo",
792
+ "goodyear",
793
+ "goog",
794
+ "google",
795
+ "gop",
796
+ "got",
797
+ "gov",
798
+ "gp",
799
+ "gq",
800
+ "gr",
801
+ "grainger",
802
+ "graphics",
803
+ "gratis",
804
+ "green",
805
+ "gripe",
806
+ "grocery",
807
+ "group",
808
+ "gs",
809
+ "gt",
810
+ "gu",
811
+ "gucci",
812
+ "guge",
813
+ "guide",
814
+ "guitars",
815
+ "guru",
816
+ "gw",
817
+ "gy",
818
+ "hair",
819
+ "hamburg",
820
+ "hangout",
821
+ "haus",
822
+ "hbo",
823
+ "hdfc",
824
+ "hdfcbank",
825
+ "health",
826
+ "healthcare",
827
+ "help",
828
+ "helsinki",
829
+ "here",
830
+ "hermes",
831
+ "hiphop",
832
+ "hisamitsu",
833
+ "hitachi",
834
+ "hiv",
835
+ "hk",
836
+ "hkt",
837
+ "hm",
838
+ "hn",
839
+ "hockey",
840
+ "holdings",
841
+ "holiday",
842
+ "homedepot",
843
+ "homegoods",
844
+ "homes",
845
+ "homesense",
846
+ "honda",
847
+ "horse",
848
+ "hospital",
849
+ "host",
850
+ "hosting",
851
+ "hot",
852
+ "hotels",
853
+ "hotmail",
854
+ "house",
855
+ "how",
856
+ "hr",
857
+ "hsbc",
858
+ "ht",
859
+ "hu",
860
+ "hughes",
861
+ "hyatt",
862
+ "hyundai",
863
+ "ibm",
864
+ "icbc",
865
+ "ice",
866
+ "icu",
867
+ "id",
868
+ "ie",
869
+ "ieee",
870
+ "ifm",
871
+ "ikano",
872
+ "il",
873
+ "im",
874
+ "imamat",
875
+ "imdb",
876
+ "immo",
877
+ "immobilien",
878
+ "in",
879
+ "inc",
880
+ "industries",
881
+ "infiniti",
882
+ "info",
883
+ "ing",
884
+ "ink",
885
+ "institute",
886
+ "insurance",
887
+ "insure",
888
+ "int",
889
+ "international",
890
+ "intuit",
891
+ "investments",
892
+ "io",
893
+ "ipiranga",
894
+ "iq",
895
+ "ir",
896
+ "irish",
897
+ "is",
898
+ "ismaili",
899
+ "ist",
900
+ "istanbul",
901
+ "it",
902
+ "itau",
903
+ "itv",
904
+ "jaguar",
905
+ "java",
906
+ "jcb",
907
+ "je",
908
+ "jeep",
909
+ "jetzt",
910
+ "jewelry",
911
+ "jio",
912
+ "jll",
913
+ "jm",
914
+ "jmp",
915
+ "jnj",
916
+ "jo",
917
+ "jobs",
918
+ "joburg",
919
+ "jot",
920
+ "joy",
921
+ "jp",
922
+ "jpmorgan",
923
+ "jprs",
924
+ "juegos",
925
+ "juniper",
926
+ "kaufen",
927
+ "kddi",
928
+ "ke",
929
+ "kerryhotels",
930
+ "kerryproperties",
931
+ "kfh",
932
+ "kg",
933
+ "kh",
934
+ "ki",
935
+ "kia",
936
+ "kids",
937
+ "kim",
938
+ "kindle",
939
+ "kitchen",
940
+ "kiwi",
941
+ "km",
942
+ "kn",
943
+ "koeln",
944
+ "komatsu",
945
+ "kosher",
946
+ "kp",
947
+ "kpmg",
948
+ "kpn",
949
+ "kr",
950
+ "krd",
951
+ "kred",
952
+ "kuokgroup",
953
+ "kw",
954
+ "ky",
955
+ "kyoto",
956
+ "kz",
957
+ "la",
958
+ "lacaixa",
959
+ "lamborghini",
960
+ "lamer",
961
+ "land",
962
+ "landrover",
963
+ "lanxess",
964
+ "lasalle",
965
+ "lat",
966
+ "latino",
967
+ "latrobe",
968
+ "law",
969
+ "lawyer",
970
+ "lb",
971
+ "lc",
972
+ "lds",
973
+ "lease",
974
+ "leclerc",
975
+ "lefrak",
976
+ "legal",
977
+ "lego",
978
+ "lexus",
979
+ "lgbt",
980
+ "li",
981
+ "lidl",
982
+ "life",
983
+ "lifeinsurance",
984
+ "lifestyle",
985
+ "lighting",
986
+ "like",
987
+ "lilly",
988
+ "limited",
989
+ "limo",
990
+ "lincoln",
991
+ "link",
992
+ "live",
993
+ "living",
994
+ "lk",
995
+ "llc",
996
+ "llp",
997
+ "loan",
998
+ "loans",
999
+ "locker",
1000
+ "locus",
1001
+ "lol",
1002
+ "london",
1003
+ "lotte",
1004
+ "lotto",
1005
+ "love",
1006
+ "lpl",
1007
+ "lplfinancial",
1008
+ "lr",
1009
+ "ls",
1010
+ "lt",
1011
+ "ltd",
1012
+ "ltda",
1013
+ "lu",
1014
+ "lundbeck",
1015
+ "luxe",
1016
+ "luxury",
1017
+ "lv",
1018
+ "ly",
1019
+ "ma",
1020
+ "madrid",
1021
+ "maif",
1022
+ "maison",
1023
+ "makeup",
1024
+ "man",
1025
+ "management",
1026
+ "mango",
1027
+ "map",
1028
+ "market",
1029
+ "marketing",
1030
+ "markets",
1031
+ "marriott",
1032
+ "marshalls",
1033
+ "mattel",
1034
+ "mba",
1035
+ "mc",
1036
+ "mckinsey",
1037
+ "md",
1038
+ "me",
1039
+ "med",
1040
+ "media",
1041
+ "meet",
1042
+ "melbourne",
1043
+ "meme",
1044
+ "memorial",
1045
+ "men",
1046
+ "menu",
1047
+ "merckmsd",
1048
+ "mg",
1049
+ "mh",
1050
+ "miami",
1051
+ "microsoft",
1052
+ "mil",
1053
+ "mini",
1054
+ "mint",
1055
+ "mit",
1056
+ "mitsubishi",
1057
+ "mk",
1058
+ "ml",
1059
+ "mlb",
1060
+ "mls",
1061
+ "mm",
1062
+ "mma",
1063
+ "mn",
1064
+ "mo",
1065
+ "mobi",
1066
+ "mobile",
1067
+ "moda",
1068
+ "moe",
1069
+ "moi",
1070
+ "mom",
1071
+ "monash",
1072
+ "money",
1073
+ "monster",
1074
+ "mormon",
1075
+ "mortgage",
1076
+ "moscow",
1077
+ "moto",
1078
+ "motorcycles",
1079
+ "mov",
1080
+ "movie",
1081
+ "mp",
1082
+ "mq",
1083
+ "mr",
1084
+ "ms",
1085
+ "msd",
1086
+ "mt",
1087
+ "mtn",
1088
+ "mtr",
1089
+ "mu",
1090
+ "museum",
1091
+ "music",
1092
+ "mv",
1093
+ "mw",
1094
+ "mx",
1095
+ "my",
1096
+ "mz",
1097
+ "na",
1098
+ "nab",
1099
+ "nagoya",
1100
+ "name",
1101
+ "navy",
1102
+ "nba",
1103
+ "nc",
1104
+ "ne",
1105
+ "nec",
1106
+ "net",
1107
+ "netbank",
1108
+ "netflix",
1109
+ "network",
1110
+ "neustar",
1111
+ "new",
1112
+ "news",
1113
+ "next",
1114
+ "nextdirect",
1115
+ "nexus",
1116
+ "nf",
1117
+ "nfl",
1118
+ "ng",
1119
+ "ngo",
1120
+ "nhk",
1121
+ "ni",
1122
+ "nico",
1123
+ "nike",
1124
+ "nikon",
1125
+ "ninja",
1126
+ "nissan",
1127
+ "nissay",
1128
+ "nl",
1129
+ "no",
1130
+ "nokia",
1131
+ "norton",
1132
+ "now",
1133
+ "nowruz",
1134
+ "nowtv",
1135
+ "np",
1136
+ "nr",
1137
+ "nra",
1138
+ "nrw",
1139
+ "ntt",
1140
+ "nu",
1141
+ "nyc",
1142
+ "nz",
1143
+ "obi",
1144
+ "observer",
1145
+ "office",
1146
+ "okinawa",
1147
+ "olayan",
1148
+ "olayangroup",
1149
+ "ollo",
1150
+ "om",
1151
+ "omega",
1152
+ "one",
1153
+ "ong",
1154
+ "onl",
1155
+ "online",
1156
+ "ooo",
1157
+ "open",
1158
+ "oracle",
1159
+ "orange",
1160
+ "org",
1161
+ "organic",
1162
+ "origins",
1163
+ "osaka",
1164
+ "otsuka",
1165
+ "ott",
1166
+ "ovh",
1167
+ "pa",
1168
+ "page",
1169
+ "panasonic",
1170
+ "paris",
1171
+ "pars",
1172
+ "partners",
1173
+ "parts",
1174
+ "party",
1175
+ "pay",
1176
+ "pccw",
1177
+ "pe",
1178
+ "pet",
1179
+ "pf",
1180
+ "pfizer",
1181
+ "pg",
1182
+ "ph",
1183
+ "pharmacy",
1184
+ "phd",
1185
+ "philips",
1186
+ "phone",
1187
+ "photo",
1188
+ "photography",
1189
+ "photos",
1190
+ "physio",
1191
+ "pics",
1192
+ "pictet",
1193
+ "pictures",
1194
+ "pid",
1195
+ "pin",
1196
+ "ping",
1197
+ "pink",
1198
+ "pioneer",
1199
+ "pizza",
1200
+ "pk",
1201
+ "pl",
1202
+ "place",
1203
+ "play",
1204
+ "playstation",
1205
+ "plumbing",
1206
+ "plus",
1207
+ "pm",
1208
+ "pn",
1209
+ "pnc",
1210
+ "pohl",
1211
+ "poker",
1212
+ "politie",
1213
+ "porn",
1214
+ "post",
1215
+ "pr",
1216
+ "praxi",
1217
+ "press",
1218
+ "prime",
1219
+ "pro",
1220
+ "prod",
1221
+ "productions",
1222
+ "prof",
1223
+ "progressive",
1224
+ "promo",
1225
+ "properties",
1226
+ "property",
1227
+ "protection",
1228
+ "pru",
1229
+ "prudential",
1230
+ "ps",
1231
+ "pt",
1232
+ "pub",
1233
+ "pw",
1234
+ "pwc",
1235
+ "py",
1236
+ "qa",
1237
+ "qpon",
1238
+ "quebec",
1239
+ "quest",
1240
+ "racing",
1241
+ "radio",
1242
+ "re",
1243
+ "read",
1244
+ "realestate",
1245
+ "realtor",
1246
+ "realty",
1247
+ "recipes",
1248
+ "red",
1249
+ "redumbrella",
1250
+ "rehab",
1251
+ "reise",
1252
+ "reisen",
1253
+ "reit",
1254
+ "reliance",
1255
+ "ren",
1256
+ "rent",
1257
+ "rentals",
1258
+ "repair",
1259
+ "report",
1260
+ "republican",
1261
+ "rest",
1262
+ "restaurant",
1263
+ "review",
1264
+ "reviews",
1265
+ "rexroth",
1266
+ "rich",
1267
+ "richardli",
1268
+ "ricoh",
1269
+ "ril",
1270
+ "rio",
1271
+ "rip",
1272
+ "ro",
1273
+ "rocks",
1274
+ "rodeo",
1275
+ "rogers",
1276
+ "room",
1277
+ "rs",
1278
+ "rsvp",
1279
+ "ru",
1280
+ "rugby",
1281
+ "ruhr",
1282
+ "run",
1283
+ "rw",
1284
+ "rwe",
1285
+ "ryukyu",
1286
+ "sa",
1287
+ "saarland",
1288
+ "safe",
1289
+ "safety",
1290
+ "sakura",
1291
+ "sale",
1292
+ "salon",
1293
+ "samsclub",
1294
+ "samsung",
1295
+ "sandvik",
1296
+ "sandvikcoromant",
1297
+ "sanofi",
1298
+ "sap",
1299
+ "sarl",
1300
+ "sas",
1301
+ "save",
1302
+ "saxo",
1303
+ "sb",
1304
+ "sbi",
1305
+ "sbs",
1306
+ "sc",
1307
+ "scb",
1308
+ "schaeffler",
1309
+ "schmidt",
1310
+ "scholarships",
1311
+ "school",
1312
+ "schule",
1313
+ "schwarz",
1314
+ "science",
1315
+ "scot",
1316
+ "sd",
1317
+ "se",
1318
+ "search",
1319
+ "seat",
1320
+ "secure",
1321
+ "security",
1322
+ "seek",
1323
+ "select",
1324
+ "sener",
1325
+ "services",
1326
+ "seven",
1327
+ "sew",
1328
+ "sex",
1329
+ "sexy",
1330
+ "sfr",
1331
+ "sg",
1332
+ "sh",
1333
+ "shangrila",
1334
+ "sharp",
1335
+ "shell",
1336
+ "shia",
1337
+ "shiksha",
1338
+ "shoes",
1339
+ "shop",
1340
+ "shopping",
1341
+ "shouji",
1342
+ "show",
1343
+ "si",
1344
+ "silk",
1345
+ "sina",
1346
+ "singles",
1347
+ "site",
1348
+ "sj",
1349
+ "sk",
1350
+ "ski",
1351
+ "skin",
1352
+ "sky",
1353
+ "skype",
1354
+ "sl",
1355
+ "sling",
1356
+ "sm",
1357
+ "smart",
1358
+ "smile",
1359
+ "sn",
1360
+ "sncf",
1361
+ "so",
1362
+ "soccer",
1363
+ "social",
1364
+ "softbank",
1365
+ "software",
1366
+ "sohu",
1367
+ "solar",
1368
+ "solutions",
1369
+ "song",
1370
+ "sony",
1371
+ "soy",
1372
+ "spa",
1373
+ "space",
1374
+ "sport",
1375
+ "spot",
1376
+ "sr",
1377
+ "srl",
1378
+ "ss",
1379
+ "st",
1380
+ "stada",
1381
+ "staples",
1382
+ "star",
1383
+ "statebank",
1384
+ "statefarm",
1385
+ "stc",
1386
+ "stcgroup",
1387
+ "stockholm",
1388
+ "storage",
1389
+ "store",
1390
+ "stream",
1391
+ "studio",
1392
+ "study",
1393
+ "style",
1394
+ "su",
1395
+ "sucks",
1396
+ "supplies",
1397
+ "supply",
1398
+ "support",
1399
+ "surf",
1400
+ "surgery",
1401
+ "suzuki",
1402
+ "sv",
1403
+ "swatch",
1404
+ "swiss",
1405
+ "sx",
1406
+ "sy",
1407
+ "sydney",
1408
+ "systems",
1409
+ "sz",
1410
+ "tab",
1411
+ "taipei",
1412
+ "talk",
1413
+ "taobao",
1414
+ "target",
1415
+ "tatamotors",
1416
+ "tatar",
1417
+ "tattoo",
1418
+ "tax",
1419
+ "taxi",
1420
+ "tc",
1421
+ "tci",
1422
+ "td",
1423
+ "tdk",
1424
+ "team",
1425
+ "tech",
1426
+ "technology",
1427
+ "tel",
1428
+ "temasek",
1429
+ "tennis",
1430
+ "teva",
1431
+ "tf",
1432
+ "tg",
1433
+ "th",
1434
+ "thd",
1435
+ "theater",
1436
+ "theatre",
1437
+ "tiaa",
1438
+ "tickets",
1439
+ "tienda",
1440
+ "tips",
1441
+ "tires",
1442
+ "tirol",
1443
+ "tj",
1444
+ "tjmaxx",
1445
+ "tjx",
1446
+ "tk",
1447
+ "tkmaxx",
1448
+ "tl",
1449
+ "tm",
1450
+ "tmall",
1451
+ "tn",
1452
+ "to",
1453
+ "today",
1454
+ "tokyo",
1455
+ "tools",
1456
+ "top",
1457
+ "toray",
1458
+ "toshiba",
1459
+ "total",
1460
+ "tours",
1461
+ "town",
1462
+ "toyota",
1463
+ "toys",
1464
+ "tr",
1465
+ "trade",
1466
+ "trading",
1467
+ "training",
1468
+ "travel",
1469
+ "travelers",
1470
+ "travelersinsurance",
1471
+ "trust",
1472
+ "trv",
1473
+ "tt",
1474
+ "tube",
1475
+ "tui",
1476
+ "tunes",
1477
+ "tushu",
1478
+ "tv",
1479
+ "tvs",
1480
+ "tw",
1481
+ "tz",
1482
+ "ua",
1483
+ "ubank",
1484
+ "ubs",
1485
+ "ug",
1486
+ "uk",
1487
+ "unicom",
1488
+ "university",
1489
+ "uno",
1490
+ "uol",
1491
+ "ups",
1492
+ "us",
1493
+ "uy",
1494
+ "uz",
1495
+ "va",
1496
+ "vacations",
1497
+ "vana",
1498
+ "vanguard",
1499
+ "vc",
1500
+ "ve",
1501
+ "vegas",
1502
+ "ventures",
1503
+ "verisign",
1504
+ "vermögensberater",
1505
+ "vermögensberatung",
1506
+ "versicherung",
1507
+ "vet",
1508
+ "vg",
1509
+ "vi",
1510
+ "viajes",
1511
+ "video",
1512
+ "vig",
1513
+ "viking",
1514
+ "villas",
1515
+ "vin",
1516
+ "vip",
1517
+ "virgin",
1518
+ "visa",
1519
+ "vision",
1520
+ "viva",
1521
+ "vivo",
1522
+ "vlaanderen",
1523
+ "vn",
1524
+ "vodka",
1525
+ "volvo",
1526
+ "vote",
1527
+ "voting",
1528
+ "voto",
1529
+ "voyage",
1530
+ "vu",
1531
+ "wales",
1532
+ "walmart",
1533
+ "walter",
1534
+ "wang",
1535
+ "wanggou",
1536
+ "watch",
1537
+ "watches",
1538
+ "weather",
1539
+ "weatherchannel",
1540
+ "webcam",
1541
+ "weber",
1542
+ "website",
1543
+ "wed",
1544
+ "wedding",
1545
+ "weibo",
1546
+ "weir",
1547
+ "wf",
1548
+ "whoswho",
1549
+ "wien",
1550
+ "wiki",
1551
+ "williamhill",
1552
+ "win",
1553
+ "windows",
1554
+ "wine",
1555
+ "winners",
1556
+ "wme",
1557
+ "wolterskluwer",
1558
+ "woodside",
1559
+ "work",
1560
+ "works",
1561
+ "world",
1562
+ "wow",
1563
+ "ws",
1564
+ "wtc",
1565
+ "wtf",
1566
+ "xbox",
1567
+ "xerox",
1568
+ "xihuan",
1569
+ "xin",
1570
+ "xxx",
1571
+ "xyz",
1572
+ "yachts",
1573
+ "yahoo",
1574
+ "yamaxun",
1575
+ "yandex",
1576
+ "ye",
1577
+ "yodobashi",
1578
+ "yoga",
1579
+ "yokohama",
1580
+ "you",
1581
+ "youtube",
1582
+ "yt",
1583
+ "yun",
1584
+ "za",
1585
+ "zappos",
1586
+ "zara",
1587
+ "zero",
1588
+ "zip",
1589
+ "zm",
1590
+ "zone",
1591
+ "zuerich",
1592
+ "zw",
1593
+ "ελ",
1594
+ "ευ",
1595
+ "бг",
1596
+ "бел",
1597
+ "дети",
1598
+ "ею",
1599
+ "католик",
1600
+ "ком",
1601
+ "мкд",
1602
+ "мон",
1603
+ "москва",
1604
+ "онлайн",
1605
+ "орг",
1606
+ "рус",
1607
+ "рф",
1608
+ "сайт",
1609
+ "срб",
1610
+ "укр",
1611
+ "қаз",
1612
+ "հայ",
1613
+ "ישראל",
1614
+ "קום",
1615
+ "ابوظبي",
1616
+ "ارامكو",
1617
+ "الاردن",
1618
+ "البحرين",
1619
+ "الجزائر",
1620
+ "السعودية",
1621
+ "العليان",
1622
+ "المغرب",
1623
+ "امارات",
1624
+ "ایران",
1625
+ "بارت",
1626
+ "بازار",
1627
+ "بيتك",
1628
+ "بھارت",
1629
+ "تونس",
1630
+ "سودان",
1631
+ "سورية",
1632
+ "شبكة",
1633
+ "عراق",
1634
+ "عرب",
1635
+ "عمان",
1636
+ "فلسطين",
1637
+ "قطر",
1638
+ "كاثوليك",
1639
+ "كوم",
1640
+ "مصر",
1641
+ "مليسيا",
1642
+ "موريتانيا",
1643
+ "موقع",
1644
+ "همراه",
1645
+ "پاکستان",
1646
+ "ڀارت",
1647
+ "कॉम",
1648
+ "नेट",
1649
+ "भारत",
1650
+ "भारतम्",
1651
+ "भारोत",
1652
+ "संगठन",
1653
+ "বাংলা",
1654
+ "ভারত",
1655
+ "ভাৰত",
1656
+ "ਭਾਰਤ",
1657
+ "ભારત",
1658
+ "ଭାରତ",
1659
+ "இந்தியா",
1660
+ "இலங்கை",
1661
+ "சிங்கப்பூர்",
1662
+ "భారత్",
1663
+ "ಭಾರತ",
1664
+ "ഭാരതം",
1665
+ "ලංකා",
1666
+ "คอม",
1667
+ "ไทย",
1668
+ "ລາວ",
1669
+ "გე",
1670
+ "みんな",
1671
+ "アマゾン",
1672
+ "クラウド",
1673
+ "グーグル",
1674
+ "コム",
1675
+ "ストア",
1676
+ "セール",
1677
+ "ファッション",
1678
+ "ポイント",
1679
+ "世界",
1680
+ "中信",
1681
+ "中国",
1682
+ "中國",
1683
+ "中文网",
1684
+ "亚马逊",
1685
+ "企业",
1686
+ "佛山",
1687
+ "信息",
1688
+ "健康",
1689
+ "八卦",
1690
+ "公司",
1691
+ "公益",
1692
+ "台湾",
1693
+ "台灣",
1694
+ "商城",
1695
+ "商店",
1696
+ "商标",
1697
+ "嘉里",
1698
+ "嘉里大酒店",
1699
+ "在线",
1700
+ "大拿",
1701
+ "天主教",
1702
+ "娱乐",
1703
+ "家電",
1704
+ "广东",
1705
+ "微博",
1706
+ "慈善",
1707
+ "我爱你",
1708
+ "手机",
1709
+ "招聘",
1710
+ "政务",
1711
+ "政府",
1712
+ "新加坡",
1713
+ "新闻",
1714
+ "时尚",
1715
+ "書籍",
1716
+ "机构",
1717
+ "淡马锡",
1718
+ "游戏",
1719
+ "澳門",
1720
+ "点看",
1721
+ "移动",
1722
+ "组织机构",
1723
+ "网址",
1724
+ "网店",
1725
+ "网站",
1726
+ "网络",
1727
+ "联通",
1728
+ "谷歌",
1729
+ "购物",
1730
+ "通販",
1731
+ "集团",
1732
+ "電訊盈科",
1733
+ "飞利浦",
1734
+ "食品",
1735
+ "餐厅",
1736
+ "香格里拉",
1737
+ "香港",
1738
+ "닷넷",
1739
+ "닷컴",
1740
+ "삼성",
1741
+ "한국"
1742
+ ];
1743
+ //#endregion
1744
+ //#region src/tld.ts
1745
+ let known;
1746
+ /** Whether `tld` is in the IANA set, as a U-label or A-label, ignoring case. */
1747
+ function isKnownTld(tld) {
1748
+ known ??= buildSet();
1749
+ return known.has(tld.toLowerCase());
1750
+ }
1751
+ function buildSet() {
1752
+ const set = new Set(tlds_default);
1753
+ for (const tld of tlds_default) if (!/^[a-z\d]+$/.test(tld)) {
1754
+ const aLabel = toALabel(tld);
1755
+ if (aLabel !== void 0) set.add(aLabel);
1756
+ }
1757
+ return set;
1758
+ }
1759
+ //#endregion
1760
+ //#region src/parse.ts
1761
+ const messages = {
1762
+ "syntax.address.empty": "The address is empty",
1763
+ "syntax.address.no_at": "The address has no @",
1764
+ "syntax.address.too_long": "The address is longer than 254 characters",
1765
+ "syntax.local.empty": "Nothing comes before the @",
1766
+ "syntax.local.too_long": "The local part is longer than 64 characters",
1767
+ "syntax.local.invalid_char": "The local part has a character it can’t hold",
1768
+ "syntax.local.consecutive_dots": "The local part has two dots in a row",
1769
+ "syntax.local.unquoted_space": "The local part has whitespace outside quotes",
1770
+ "syntax.domain.empty": "Nothing comes after the @",
1771
+ "syntax.domain.no_dot": "The domain has no dot",
1772
+ "syntax.domain.label_invalid": "A domain label is empty, too long, or starts or ends with a hyphen",
1773
+ "syntax.domain.literal_invalid": "The domain literal isn’t accepted",
1774
+ "syntax.domain.too_long": "The domain is longer than 253 characters",
1775
+ "syntax.domain.invalid_char": "The domain has a character it can’t hold",
1776
+ "syntax.comment.not_allowed": "Comments aren’t allowed here",
1777
+ "syntax.comment.unterminated": "A comment is missing its closing parenthesis",
1778
+ "syntax.tld.unknown": "The TLD isn’t in the IANA set"
1779
+ };
1780
+ function fail(reason, index) {
1781
+ const message = messages[reason];
1782
+ return index === void 0 ? {
1783
+ ok: false,
1784
+ reason,
1785
+ message
1786
+ } : {
1787
+ ok: false,
1788
+ reason,
1789
+ message,
1790
+ index
1791
+ };
1792
+ }
1793
+ const NORMAL = 0;
1794
+ const QUOTED = 1;
1795
+ const COMMENT = 2;
1796
+ const LITERAL = 3;
1797
+ /**
1798
+ * Finds the `@` that splits `email`: the last one outside a quoted string,
1799
+ * comment, or domain literal, or -1 when there's none.
1800
+ *
1801
+ * @remarks
1802
+ * A quoted string opens only at the start of a word, and a literal only at
1803
+ * the start of the domain; both, and comments, take backslash escapes. One
1804
+ * left open runs to the end, taking any `@` with it.
1805
+ */
1806
+ function findAt(email, rules) {
1807
+ let state = NORMAL;
1808
+ let wordStart = true;
1809
+ let domainStart = false;
1810
+ let depth = 0;
1811
+ let at = -1;
1812
+ const end = email.length;
1813
+ for (let i = 0; i < end; i++) {
1814
+ const code = codeAt(email, i);
1815
+ if (state === NORMAL) {
1816
+ if (code === 64) {
1817
+ at = i;
1818
+ wordStart = true;
1819
+ domainStart = true;
1820
+ } else if (code === 40 && rules.comments) {
1821
+ state = COMMENT;
1822
+ depth = 1;
1823
+ } else if (!isWhitespace(code)) {
1824
+ if (code === 34 && rules.quotes && wordStart) state = QUOTED;
1825
+ else if (code === 91 && rules.literals && domainStart) state = LITERAL;
1826
+ wordStart = code === 46;
1827
+ domainStart = false;
1828
+ }
1829
+ } else if (code === 92) i++;
1830
+ else if (state === COMMENT) {
1831
+ if (code === 40) depth++;
1832
+ else if (code === 41 && --depth === 0) state = NORMAL;
1833
+ } else if (code === (state === QUOTED ? 34 : 93)) state = NORMAL;
1834
+ }
1835
+ return at;
1836
+ }
1837
+ /**
1838
+ * Parses `email` under `rules`.
1839
+ *
1840
+ * @throws TypeError when `email` isn't a string.
1841
+ */
1842
+ function parse(email, rules) {
1843
+ if (typeof email !== "string") throw new TypeError(`Expected a string, got ${typeof email}`);
1844
+ if (email === "") return fail("syntax.address.empty");
1845
+ if (email.length > rules.maxLength) return fail("syntax.address.too_long");
1846
+ const at = findAt(email, rules);
1847
+ if (at < 0) return fail("syntax.address.no_at");
1848
+ const comments = [];
1849
+ const local = scanLocal(email, at, rules, comments);
1850
+ if (typeof local !== "string") return local;
1851
+ const localSize = rules.unicode ? utf8Length(local) : local.length;
1852
+ if (localSize > rules.localCap) return fail("syntax.local.too_long");
1853
+ const domain = scanDomain(email, at, rules, comments);
1854
+ if ("reason" in domain) return domain;
1855
+ if (domain.size > rules.domainCap) return fail("syntax.domain.too_long");
1856
+ if (localSize + 1 + domain.size > 254) return fail("syntax.address.too_long");
1857
+ const { tld } = domain;
1858
+ if (tld === void 0 && !domain.literal && !rules.allowNoTld) return fail("syntax.domain.no_dot");
1859
+ if (tld !== void 0 && rules.checkTld && !isKnownTld(tld)) return fail("syntax.tld.unknown");
1860
+ return {
1861
+ ok: true,
1862
+ value: tld === void 0 ? {
1863
+ local,
1864
+ domain: domain.domain,
1865
+ comments
1866
+ } : {
1867
+ local,
1868
+ domain: domain.domain,
1869
+ tld,
1870
+ comments
1871
+ }
1872
+ };
1873
+ }
1874
+ /**
1875
+ * Scans the local part, `email` up to `at`, and returns it without comments
1876
+ * or folding whitespace.
1877
+ */
1878
+ function scanLocal(email, at, rules, comments) {
1879
+ let local = "";
1880
+ let words = 0;
1881
+ let afterWord = false;
1882
+ let closed = false;
1883
+ let dot = -1;
1884
+ let space = -1;
1885
+ let edge = -1;
1886
+ let trailing = comments.length;
1887
+ let i = 0;
1888
+ while (i < at) {
1889
+ const code = codeAt(email, i);
1890
+ const quote = code === 34 && rules.quotes && (rules.obs || i === 0);
1891
+ const atom = inClass(code, rules.local);
1892
+ if (quote || atom || rules.unicode && nonAsciiAt(email, i, at) > 0) {
1893
+ if (edge >= 0) return fail("syntax.comment.not_allowed", edge);
1894
+ if (afterWord) return space >= 0 ? fail("syntax.local.unquoted_space", space) : fail("syntax.local.invalid_char", i);
1895
+ let next;
1896
+ if (quote) {
1897
+ next = skipQuoted(email, i, at, rules.obs, rules.unicode);
1898
+ if (next < 0) return fail("syntax.local.invalid_char", -1 - next);
1899
+ closed = !rules.obs;
1900
+ } else {
1901
+ next = atom ? i + 1 : i;
1902
+ while (next < at && inClass(codeAt(email, next), rules.local)) next++;
1903
+ if (rules.unicode) next = skipUnicode(email, next, at, rules.local);
1904
+ }
1905
+ local += unfold(email, i, next);
1906
+ words++;
1907
+ afterWord = true;
1908
+ dot = -1;
1909
+ space = -1;
1910
+ trailing = comments.length;
1911
+ i = next;
1912
+ } else if (code === 46) {
1913
+ if (edge >= 0) return fail("syntax.comment.not_allowed", edge);
1914
+ if (!afterWord || closed) return dot >= 0 ? fail("syntax.local.consecutive_dots", i) : fail("syntax.local.invalid_char", i);
1915
+ local += ".";
1916
+ afterWord = false;
1917
+ dot = i;
1918
+ space = -1;
1919
+ i++;
1920
+ } else if (code === 40 && rules.comments) {
1921
+ if (!rules.obs && words > 0) {
1922
+ if (!afterWord) return fail("syntax.comment.not_allowed", i);
1923
+ edge = edge < 0 ? i : edge;
1924
+ }
1925
+ const next = skipComment(email, i, at, rules.unicode);
1926
+ if (next < 0) return fail("syntax.local.invalid_char", -1 - next);
1927
+ comments.push({
1928
+ text: email.slice(i + 1, next - 1),
1929
+ position: words === 0 ? "before-local" : "inside-local"
1930
+ });
1931
+ i = next;
1932
+ } else if (code === 40) return fail("syntax.comment.not_allowed", i);
1933
+ else if (rules.obs && isWhitespace(code)) {
1934
+ const next = skipSpace(email, i, at);
1935
+ if (next < 0) return fail("syntax.local.invalid_char", i);
1936
+ space = space < 0 ? i : space;
1937
+ i = next;
1938
+ } else return code === 32 ? fail("syntax.local.unquoted_space", i) : fail("syntax.local.invalid_char", i);
1939
+ }
1940
+ if (words === 0) return fail("syntax.local.empty");
1941
+ if (dot >= 0) return fail("syntax.local.invalid_char", dot);
1942
+ settle(comments, trailing, "after-local");
1943
+ return local;
1944
+ }
1945
+ /**
1946
+ * Scans the domain, `email` after `at`, and returns it without comments or
1947
+ * folding whitespace.
1948
+ */
1949
+ function scanDomain(email, at, rules, comments) {
1950
+ const uLabels = [];
1951
+ const domain = scanLabels(email, at, rules, comments, uLabels);
1952
+ if (uLabels.length === 0) return domain;
1953
+ const growth = checkULabels(email, uLabels, rules.domainCap);
1954
+ if (typeof growth !== "number") return growth;
1955
+ if (!("reason" in domain)) domain.size += growth;
1956
+ return domain;
1957
+ }
1958
+ /**
1959
+ * Checks the U-labels at `spans`, triples of start, end, and the length of
1960
+ * the domain before it, as A-labels: each must convert, and pass the
1961
+ * hostname rules as its A-label. Returns the first failure, or what the
1962
+ * A-labels add to the domain's length: `Infinity` once it's past `cap`.
1963
+ *
1964
+ * @remarks
1965
+ * The U-labels are converted a few at a time, about 32 characters of them
1966
+ * in each URL: one URL costs about as much as one label alone, and a small
1967
+ * batch wastes little past the first failure or past `cap`. Once the
1968
+ * A-labels take the domain past `cap`, the rest aren't checked: the domain
1969
+ * fails as too long, unless the scan found something earlier. Punycode's
1970
+ * cost grows with the square of a label's length, so converting labels that
1971
+ * can't change the outcome would cost the most on input built to be slow
1972
+ * (validator-syntax#15).
1973
+ */
1974
+ function checkULabels(email, spans, cap) {
1975
+ let growth = 0;
1976
+ for (let first = 0; first < spans.length;) {
1977
+ const chunk = [];
1978
+ let written = 0;
1979
+ let next = first;
1980
+ while (next < spans.length && written < 32) {
1981
+ const label = email.slice(spans[next], spans[next + 1]);
1982
+ chunk.push(label);
1983
+ written += label.length;
1984
+ next += 3;
1985
+ }
1986
+ const aLabels = toALabels(chunk);
1987
+ for (let k = 0; k < chunk.length; k++) {
1988
+ const start = spans[first + 3 * k];
1989
+ const end = spans[first + 3 * k + 1];
1990
+ const aLabel = aLabels[k];
1991
+ if (aLabel === void 0) return fail("syntax.domain.label_invalid", start);
1992
+ const bad = checkLabel(email, start, end, aLabel.length);
1993
+ if (bad >= 0) return fail("syntax.domain.label_invalid", bad);
1994
+ growth += aLabel.length - (end - start);
1995
+ if (spans[first + 3 * k + 2] + (end - start) + growth > cap) return Infinity;
1996
+ }
1997
+ first = next;
1998
+ }
1999
+ return growth;
2000
+ }
2001
+ /**
2002
+ * The scan behind {@link scanDomain}, which leaves the U-labels it finds in
2003
+ * `uLabels`, as triples of start, end, and the domain's length before it,
2004
+ * unchecked but for their length.
2005
+ */
2006
+ function scanLabels(email, at, rules, comments, uLabels) {
2007
+ const end = email.length;
2008
+ let domain = "";
2009
+ let labels = 0;
2010
+ let afterLabel = false;
2011
+ let literal = false;
2012
+ let dot = -1;
2013
+ let space = -1;
2014
+ let edge = -1;
2015
+ let trailing = comments.length;
2016
+ let tld = "";
2017
+ let i = at + 1;
2018
+ while (i < end) {
2019
+ const code = codeAt(email, i);
2020
+ const opensLiteral = code === 91 && rules.literals && labels === 0;
2021
+ const ldh = inClass(code, rules.domain);
2022
+ if (opensLiteral || ldh || rules.idn && nonAsciiAt(email, i, end) > 0) {
2023
+ if (edge >= 0) return fail("syntax.comment.not_allowed", edge);
2024
+ if (afterLabel) return fail("syntax.domain.invalid_char", space >= 0 ? space : i);
2025
+ let next;
2026
+ if (opensLiteral) {
2027
+ next = rules.obs ? skipLiteral(email, i, end) : skipAddressLiteral(email, i, end);
2028
+ if (next < 0) return fail("syntax.domain.literal_invalid", i);
2029
+ literal = true;
2030
+ } else {
2031
+ next = ldh ? i + 1 : i;
2032
+ while (next < end && inClass(codeAt(email, next), rules.domain)) next++;
2033
+ const ascii = next;
2034
+ if (rules.idn) next = skipUnicode(email, next, end, rules.domain);
2035
+ if (next > ascii) {
2036
+ if (codePoints(email, i, next) > 63) return fail("syntax.domain.label_invalid", i);
2037
+ uLabels.push(i, next, domain.length);
2038
+ } else {
2039
+ const bad = checkLabel(email, i, next, next - i);
2040
+ if (bad >= 0) return fail("syntax.domain.label_invalid", bad);
2041
+ }
2042
+ }
2043
+ tld = unfold(email, i, next);
2044
+ domain += tld;
2045
+ labels++;
2046
+ afterLabel = true;
2047
+ dot = -1;
2048
+ space = -1;
2049
+ trailing = comments.length;
2050
+ i = next;
2051
+ } else if (code === 46) {
2052
+ if (edge >= 0) return fail("syntax.comment.not_allowed", edge);
2053
+ if (!afterLabel || literal) return literal ? fail("syntax.domain.invalid_char", i) : fail("syntax.domain.label_invalid", i);
2054
+ domain += ".";
2055
+ afterLabel = false;
2056
+ dot = i;
2057
+ space = -1;
2058
+ i++;
2059
+ } else if (code === 40 && rules.comments) {
2060
+ if (!rules.obs && labels > 0) {
2061
+ if (!afterLabel) return fail("syntax.comment.not_allowed", i);
2062
+ edge = edge < 0 ? i : edge;
2063
+ }
2064
+ const next = skipComment(email, i, end, rules.unicode);
2065
+ if (next < 0) return -1 - next === i ? fail("syntax.comment.unterminated", i) : fail("syntax.domain.invalid_char", -1 - next);
2066
+ comments.push({
2067
+ text: email.slice(i + 1, next - 1),
2068
+ position: labels === 0 ? "before-domain" : "inside-domain"
2069
+ });
2070
+ i = next;
2071
+ } else if (code === 40) return fail("syntax.comment.not_allowed", i);
2072
+ else if (rules.obs && isWhitespace(code)) {
2073
+ const next = skipSpace(email, i, end);
2074
+ if (next < 0) return fail("syntax.domain.invalid_char", i);
2075
+ space = space < 0 ? i : space;
2076
+ i = next;
2077
+ } else return fail("syntax.domain.invalid_char", i);
2078
+ }
2079
+ if (labels === 0) return fail("syntax.domain.empty");
2080
+ if (dot >= 0) return fail("syntax.domain.label_invalid", dot);
2081
+ settle(comments, trailing, "after-domain");
2082
+ const size = domain.length;
2083
+ return literal || labels === 1 ? {
2084
+ domain,
2085
+ literal,
2086
+ size
2087
+ } : {
2088
+ domain,
2089
+ tld,
2090
+ literal,
2091
+ size
2092
+ };
2093
+ }
2094
+ /**
2095
+ * Carries an atom or label on from `i`, where its ASCII run stopped, through
2096
+ * any non-ASCII characters and the class-`flag` characters after them.
2097
+ */
2098
+ function skipUnicode(email, i, end, flag) {
2099
+ let width = i < end ? nonAsciiAt(email, i, end) : 0;
2100
+ while (width > 0) {
2101
+ i += width;
2102
+ while (i < end && inClass(codeAt(email, i), flag)) i++;
2103
+ width = i < end ? nonAsciiAt(email, i, end) : 0;
2104
+ }
2105
+ return i;
2106
+ }
2107
+ /**
2108
+ * Checks the hostname label from `start` to `end`, `size` characters long as
2109
+ * an A-label: returns the index of a leading or trailing hyphen, the start of
2110
+ * a label over 63 characters, or -1.
2111
+ */
2112
+ function checkLabel(email, start, end, size) {
2113
+ if (codeAt(email, start) === 45) return start;
2114
+ if (size > 63) return start;
2115
+ return codeAt(email, end - 1) === 45 ? end - 1 : -1;
2116
+ }
2117
+ /** Marks the comments from `from` on, which no word follows, as trailing. */
2118
+ function settle(comments, from, position) {
2119
+ for (let c = from; c < comments.length; c++) {
2120
+ const comment = comments[c];
2121
+ if (comment.position.startsWith("inside")) comment.position = position;
2122
+ }
2123
+ }
2124
+ const { indexOf, slice } = String.prototype;
2125
+ /**
2126
+ * `email` from `start` to `end`, without the CRLFs of folding whitespace,
2127
+ * keeping the space or tab after each.
2128
+ */
2129
+ function unfold(email, start, end) {
2130
+ const text = slice.call(email, start, end);
2131
+ return indexOf.call(text, "\r") < 0 ? text : text.replaceAll("\r\n", "");
2132
+ }
2133
+ /**
2134
+ * Skips one character of folding whitespace: a space, a tab, or a CRLF with
2135
+ * a space or tab after it. A lone CR or LF breaks it.
2136
+ */
2137
+ function skipSpace(email, i, end) {
2138
+ const code = codeAt(email, i);
2139
+ if (code === 32 || code === 9) return i + 1;
2140
+ return code === 13 && i + 2 < end && codeAt(email, i + 1) === 10 && (codeAt(email, i + 2) === 32 || codeAt(email, i + 2) === 9) ? i + 2 : -1 - i;
2141
+ }
2142
+ /** Whether a backslash may escape `code`: any ASCII with `obs`, else VCHAR and space. */
2143
+ function escapable(code, obs) {
2144
+ return obs ? code < 128 : code >= 32 && code <= 126;
2145
+ }
2146
+ /**
2147
+ * Skips the quoted string opening at `open`. `obs` allows folding whitespace
2148
+ * and control characters other than NUL; without it, the string follows
2149
+ * RFC 5321: printable ASCII and spaces only. `unicode` adds non-ASCII.
2150
+ */
2151
+ function skipQuoted(email, open, end, obs, unicode) {
2152
+ let i = open + 1;
2153
+ while (i < end) {
2154
+ const code = codeAt(email, i);
2155
+ if (code === 34) return i + 1;
2156
+ if (code === 92) {
2157
+ if (!escapable(codeAt(email, i + 1), obs)) return -2 - i;
2158
+ i += 2;
2159
+ } else if (obs && isWhitespace(code)) {
2160
+ i = skipSpace(email, i, end);
2161
+ if (i < 0) return i;
2162
+ } else if (obs ? code === 0 || code > 127 : code < 32 || code > 126) {
2163
+ const width = unicode ? nonAsciiAt(email, i, end) : 0;
2164
+ if (width === 0) return -1 - i;
2165
+ i += width;
2166
+ } else i++;
2167
+ }
2168
+ return -1 - open;
2169
+ }
2170
+ /**
2171
+ * Skips the comment opening at `open`, nested comments included. Its text
2172
+ * may hold any ASCII but NUL, with folding whitespace, and non-ASCII with
2173
+ * `unicode`; returns `-1 - open` when it never closes.
2174
+ */
2175
+ function skipComment(email, open, end, unicode) {
2176
+ let depth = 1;
2177
+ let i = open + 1;
2178
+ while (i < end) {
2179
+ const code = codeAt(email, i);
2180
+ if (code === 40 || code === 41) {
2181
+ depth += code === 40 ? 1 : -1;
2182
+ i++;
2183
+ if (depth === 0) return i;
2184
+ } else if (code === 92) {
2185
+ if (i + 1 >= end) break;
2186
+ if (!escapable(codeAt(email, i + 1), true)) return -2 - i;
2187
+ i += 2;
2188
+ } else if (isWhitespace(code)) {
2189
+ i = skipSpace(email, i, end);
2190
+ if (i < 0) return i;
2191
+ } else if (code === 0 || code > 127) {
2192
+ const width = unicode ? nonAsciiAt(email, i, end) : 0;
2193
+ if (width === 0) return -1 - i;
2194
+ i += width;
2195
+ } else i++;
2196
+ }
2197
+ return -1 - open;
2198
+ }
2199
+ /**
2200
+ * Skips the RFC 5322 domain literal opening at `open`: any ASCII but NUL,
2201
+ * `[`, and `]`, with escapes and folding whitespace. Returns -1 when it's
2202
+ * malformed or never closes.
2203
+ */
2204
+ function skipLiteral(email, open, end) {
2205
+ let i = open + 1;
2206
+ while (i < end) {
2207
+ const code = codeAt(email, i);
2208
+ if (code === 93) return i + 1;
2209
+ if (code === 92) {
2210
+ if (i + 1 >= end || !escapable(codeAt(email, i + 1), true)) return -1;
2211
+ i += 2;
2212
+ } else if (isWhitespace(code)) {
2213
+ i = skipSpace(email, i, end);
2214
+ if (i < 0) return -1;
2215
+ } else if (code === 0 || code === 91 || code > 127) return -1;
2216
+ else i++;
2217
+ }
2218
+ return -1;
2219
+ }
2220
+ /**
2221
+ * Skips the RFC 5321 address literal opening at `open`: an IPv4 address or
2222
+ * `IPv6:` and an IPv6 address. Returns -1 for anything else.
2223
+ */
2224
+ function skipAddressLiteral(email, open, end) {
2225
+ const close = email.indexOf("]", open + 1);
2226
+ return close >= 0 && close < end && isAddressLiteral(email.slice(open + 1, close)) ? close + 1 : -1;
2227
+ }
2228
+ //#endregion
2229
+ //#region src/index.ts
2230
+ /**
2231
+ * Is the address well-formed? A quote-aware parser with four presets:
2232
+ * `practical` (the default), `rfc5321`, `rfc5322`, and `html5`.
2233
+ *
2234
+ * @packageDocumentation
2235
+ */
2236
+ /**
2237
+ * Parses `email` into its local part, domain, TLD, and comments, or returns
2238
+ * the first check it fails.
2239
+ *
2240
+ * @remarks
2241
+ * Input longer than `maxLength` (512 UTF-16 code units by default) fails as
2242
+ * `syntax.address.too_long` before it's scanned. Otherwise the local part is
2243
+ * checked before the domain, each left to right, then the lengths, then a
2244
+ * dotless domain, then the TLD. `index`, where a failure has one, points at
2245
+ * the offending character.
2246
+ *
2247
+ * @example
2248
+ * ```ts
2249
+ * import { parseAddress } from '@email-utils/validator-syntax';
2250
+ *
2251
+ * parseAddress('ada.lovelace@example.co.uk');
2252
+ * // => { ok: true, value: { local: 'ada.lovelace', tld: 'uk' } }
2253
+ * parseAddress('ada..lovelace@example.com');
2254
+ * // => { ok: false, reason: 'syntax.local.consecutive_dots', index: 4 }
2255
+ * ```
2256
+ *
2257
+ * @throws TypeError when `email` isn't a string, or `options` are malformed.
2258
+ */
2259
+ function parseAddress(email, options) {
2260
+ return parse(email, resolve(options));
2261
+ }
2262
+ /**
2263
+ * Whether `email` is well-formed: exactly `parseAddress(email, options).ok`.
2264
+ *
2265
+ * @example
2266
+ * ```ts
2267
+ * import { isValidSyntax } from '@email-utils/validator-syntax';
2268
+ *
2269
+ * isValidSyntax('ada@example.com'); // => true
2270
+ * isValidSyntax('"ada"@example.com'); // => false
2271
+ * isValidSyntax('"ada"@example.com', { preset: 'rfc5321' }); // => true
2272
+ * ```
2273
+ *
2274
+ * @throws TypeError when `email` isn't a string, or `options` are malformed.
2275
+ */
2276
+ function isValidSyntax(email, options) {
2277
+ return parse(email, resolve(options)).ok;
2278
+ }
2279
+ /**
2280
+ * Binds `options` once, checking them up front, and returns
2281
+ * {@link parseAddress} and {@link isValidSyntax} with them applied.
2282
+ *
2283
+ * @example
2284
+ * ```ts
2285
+ * import { createSyntaxValidator } from '@email-utils/validator-syntax';
2286
+ *
2287
+ * const strict = createSyntaxValidator({ preset: 'rfc5321' });
2288
+ * strict.parse('a b@example.com');
2289
+ * // => { ok: false, reason: 'syntax.local.unquoted_space', index: 1 }
2290
+ * strict.isValid('"a b"@example.com'); // => true
2291
+ * createSyntaxValidator({ preset: 'html5', allowComments: true });
2292
+ * // => throws TypeError
2293
+ * ```
2294
+ *
2295
+ * @throws TypeError when `options` are malformed.
2296
+ */
2297
+ function createSyntaxValidator(options) {
2298
+ const rules = resolve(options);
2299
+ return {
2300
+ parse: (email) => parse(email, rules),
2301
+ isValid: (email) => parse(email, rules).ok
2302
+ };
2303
+ }
2304
+ //#endregion
2305
+ export { createSyntaxValidator, isValidSyntax, parseAddress };
2306
+
2307
+ //# sourceMappingURL=index.mjs.map