• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hexojs / hexo-util / 16650178400

31 Jul 2025 01:21PM UTC coverage: 73.042% (-23.8%) from 96.875%
16650178400

Pull #426

github

web-flow
Merge ca6058b70 into d497bc760
Pull Request #426: build: restructure for dual ESM/CJS output, update tooling, and enhance utilities

811 of 1128 branches covered (71.9%)

1593 of 2027 new or added lines in 40 files covered. (78.59%)

6 existing lines in 1 file now uncovered.

5289 of 7241 relevant lines covered (73.04%)

69.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.37
/lib/_punycode.ts
1
/** Highest positive signed 32-bit float value */
2✔
2
const maxInt: number = 2147483647; // aka. 0x7FFFFFFF or 2^31-1
2✔
3

2✔
4
/** Bootstring parameters */
2✔
5
const base: number = 36;
2✔
6
const tMin: number = 1;
2✔
7
const tMax: number = 26;
2✔
8
const skew: number = 38;
2✔
9
const damp: number = 700;
2✔
10
const initialBias: number = 72;
2✔
11
const initialN: number = 128; // 0x80
2✔
12
const delimiter: string = '-'; // '\x2D'
2✔
13

2✔
14
/** Regular expressions */
2✔
15
const regexPunycode: RegExp = /^xn--/;
2✔
16
const regexNonASCII: RegExp = /[^\0-\x7F]/; // Note: U+007F DEL is excluded too.
2✔
17
const regexSeparators: RegExp = /[\x2E\u3002\uFF0E\uFF61]/g; // RFC 3490 separators
2✔
18

2✔
19
/** Error messages */
2✔
20
const errors: Record<string, string> = {
2✔
21
  overflow: 'Overflow: input needs wider integers to process',
2✔
22
  'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
2✔
23
  'invalid-input': 'Invalid input'
2✔
24
};
2✔
25

2✔
26
/** Convenience shortcuts */
2✔
27
const baseMinusTMin: number = base - tMin;
2✔
28
const floor: (n: number) => number = Math.floor;
2✔
29
const stringFromCharCode: (...codes: number[]) => string = String.fromCharCode;
2✔
30

2✔
31
/* --------------------------------------------------------------------------*/
2✔
32

2✔
33
/**
2✔
34
 * A generic error utility function.
2✔
35
 * @private
2✔
36
 */
2✔
NEW
37
function error(type: string): never {
×
NEW
38
  throw new RangeError(errors[type]);
×
NEW
39
}
×
40

2✔
41
/**
2✔
42
 * A generic `Array#map` utility function.
2✔
43
 * @private
2✔
44
 */
2✔
45
function map<T, U>(array: T[], callback: (item: T) => U): U[] {
51✔
46
  const result: U[] = [];
51✔
47
  let length = array.length;
51✔
48
  while (length--) {
51✔
49
    result[length] = callback(array[length]);
102✔
50
  }
102✔
51
  return result;
51✔
52
}
51✔
53

2✔
54
/**
2✔
55
 * A simple `Array#map`-like wrapper to work with domain name strings or email
2✔
56
 * addresses.
2✔
57
 * @private
2✔
58
 */
2✔
59
function mapDomain(domain: string, callback: (item: string) => string): string {
51✔
60
  const parts = domain.split('@');
51✔
61
  let result = '';
51✔
62
  if (parts.length > 1) {
51!
NEW
63
    // In email addresses, only the domain name should be punycoded. Leave
×
NEW
64
    // the local part (i.e. everything up to `@`) intact.
×
NEW
65
    result = parts[0] + '@';
×
NEW
66
    domain = parts[1];
×
NEW
67
  }
×
68
  // Avoid `split(regex)` for IE8 compatibility. See #17.
51✔
69
  domain = domain.replace(regexSeparators, '\x2E');
51✔
70
  const labels = domain.split('.');
51✔
71
  const encoded = map(labels, callback).join('.');
51✔
72
  return result + encoded;
51✔
73
}
51✔
74

2✔
75
/**
2✔
76
 * Creates an array containing the numeric code points of each Unicode
2✔
77
 * character in the string. While JavaScript uses UCS-2 internally,
2✔
78
 * this function will convert a pair of surrogate halves (each of which
2✔
79
 * UCS-2 exposes as separate characters) into a single code point,
2✔
80
 * matching UTF-16.
2✔
81
 */
2✔
NEW
82
function ucs2decode(string: string): number[] {
×
NEW
83
  const output: number[] = [];
×
NEW
84
  let counter = 0;
×
NEW
85
  const length = string.length;
×
NEW
86
  while (counter < length) {
×
NEW
87
    const value = string.charCodeAt(counter++);
×
NEW
88
    if (value >= 0xd800 && value <= 0xdbff && counter < length) {
×
NEW
89
      // It's a high surrogate, and there is a next character.
×
NEW
90
      const extra = string.charCodeAt(counter++);
×
NEW
91
      if ((extra & 0xfc00) === 0xdc00) {
×
NEW
92
        // Low surrogate.
×
NEW
93
        output.push(((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000);
×
NEW
94
      } else {
×
NEW
95
        // It's an unmatched surrogate; only append this code unit, in case the
×
NEW
96
        // next code unit is the high surrogate of a surrogate pair.
×
NEW
97
        output.push(value);
×
NEW
98
        counter--;
×
NEW
99
      }
×
NEW
100
    } else {
×
NEW
101
      output.push(value);
×
NEW
102
    }
×
NEW
103
  }
×
NEW
104
  return output;
×
NEW
105
}
×
106

2✔
107
/**
2✔
108
 * Creates a string based on an array of numeric code points.
2✔
109
 */
2✔
110
const ucs2encode = (codePoints: number[]): string => String.fromCodePoint(...codePoints);
2✔
111

2✔
112
/**
2✔
113
 * Converts a basic code point into a digit/integer.
2✔
114
 * @see `digitToBasic()`
2✔
115
 * @private
2✔
116
 */
2✔
117
const basicToDigit = function(codePoint: number): number {
2✔
118
  if (codePoint >= 0x30 && codePoint < 0x3a) {
18✔
119
    return 26 + (codePoint - 0x30);
3✔
120
  }
3✔
121
  if (codePoint >= 0x41 && codePoint < 0x5b) {
18!
NEW
122
    return codePoint - 0x41;
×
NEW
123
  }
×
124
  if (codePoint >= 0x61 && codePoint < 0x7b) {
18✔
125
    return codePoint - 0x61;
15✔
126
  }
15✔
NEW
127
  return base;
×
NEW
128
};
×
129

2✔
130
/**
2✔
131
 * Converts a digit/integer into a basic code point.
2✔
132
 * @see `basicToDigit()`
2✔
133
 * @private
2✔
134
 */
2✔
135
const digitToBasic = function(digit: number, flag: number): number {
2✔
NEW
136
  //  0..25 map to ASCII a..z or A..Z
×
NEW
137
  // 26..35 map to ASCII 0..9
×
NEW
138
  return digit + 22 + (75 * Number(digit < 26)) - (Number(flag !== 0) << 5);
×
NEW
139
};
×
140

2✔
141
/**
2✔
142
 * Bias adaptation function as per section 3.4 of RFC 3492.
2✔
143
 * https://tools.ietf.org/html/rfc3492#section-3.4
2✔
144
 * @private
2✔
145
 */
2✔
146
const adapt = function(delta: number, numPoints: number, firstTime: boolean): number {
2✔
147
  let k = 0;
6✔
148
  delta = firstTime ? floor(delta / damp) : delta >> 1;
6!
149
  delta += floor(delta / numPoints);
6✔
150
  for (; /* no initialization */ delta > (baseMinusTMin * tMax) >> 1; k += base) {
6!
NEW
151
    delta = floor(delta / baseMinusTMin);
×
NEW
152
  }
×
153
  return floor(k + (((baseMinusTMin + 1) * delta) / (delta + skew)));
6✔
154
};
6✔
155

2✔
156
/**
2✔
157
 * Converts a Punycode string of ASCII-only symbols to a string of Unicode
2✔
158
 * symbols.
2✔
159
 */
2✔
160
const decode = function(input: string): string {
2✔
161
  // Don't use UCS-2.
6✔
162
  const output = [];
6✔
163
  const inputLength = input.length;
6✔
164
  let i = 0;
6✔
165
  let n = initialN;
6✔
166
  let bias = initialBias;
6✔
167

6✔
168
  // Handle the basic code points: let `basic` be the number of input code
6✔
169
  // points before the last delimiter, or `0` if there is none, then copy
6✔
170
  // the first basic code points to the output.
6✔
171

6✔
172
  let basic = input.lastIndexOf(delimiter);
6✔
173
  if (basic < 0) {
6!
NEW
174
    basic = 0;
×
NEW
175
  }
×
176

6✔
177
  for (let j = 0; j < basic; ++j) {
6✔
178
    // if it's not a basic code point
12✔
179
    if (input.charCodeAt(j) >= 0x80) {
12!
NEW
180
      error('not-basic');
×
NEW
181
    }
×
182
    output.push(input.charCodeAt(j));
12✔
183
  }
12✔
184

6✔
185
  // Main decoding loop: start just after the last delimiter if any basic code
6✔
186
  // points were copied; start at the beginning otherwise.
6✔
187

6✔
188
  for (let index = basic > 0 ? basic + 1 : 0; index < inputLength;) {
6!
189
    // `index` is the index of the next character to be consumed.
6✔
190
    // Decode a generalized variable-length integer into `delta`,
6✔
191
    // which gets added to `i`. The overflow checking is easier
6✔
192
    // if we increase `i` as we go, then subtract off its starting
6✔
193
    // value at the end to obtain `delta`.
6✔
194
    const oldi = i;
6✔
195
    for (let w = 1, k = base; ; k += base) {
6✔
196
      if (index >= inputLength) {
18!
NEW
197
        error('invalid-input');
×
NEW
198
      }
×
199

18✔
200
      const digit = basicToDigit(input.charCodeAt(index++));
18✔
201

18✔
202
      if (digit >= base) {
18!
NEW
203
        error('invalid-input');
×
NEW
204
      }
×
205
      if (digit > floor((maxInt - i) / w)) {
18!
NEW
206
        error('overflow');
×
NEW
207
      }
×
208

18✔
209
      i += digit * w;
18✔
210
      let t: number;
18✔
211
      if (k <= bias) {
18✔
212
        t = tMin;
12✔
213
      } else if (k >= bias + tMax) {
18✔
214
        t = tMax;
6✔
215
      } else {
6!
NEW
216
        t = k - bias;
×
NEW
217
      }
×
218

18✔
219
      if (digit < t) {
18✔
220
        break;
6✔
221
      }
6✔
222

12✔
223
      const baseMinusT = base - t;
12✔
224
      if (w > floor(maxInt / baseMinusT)) {
18!
NEW
225
        error('overflow');
×
NEW
226
      }
×
227

12✔
228
      w *= baseMinusT;
12✔
229
    }
12✔
230

6✔
231
    const out = output.length + 1;
6✔
232
    bias = adapt(i - oldi, out, oldi === 0);
6✔
233

6✔
234
    // `i` was supposed to wrap around from `out` to `0`,
6✔
235
    // incrementing `n` each time, so we'll fix that now:
6✔
236
    if (floor(i / out) > maxInt - n) {
6!
NEW
237
      error('overflow');
×
NEW
238
    }
×
239

6✔
240
    n += floor(i / out);
6✔
241
    i %= out;
6✔
242

6✔
243
    // Insert `n` at position `i` of the output.
6✔
244
    output.splice(i++, 0, n);
6✔
245
  }
6✔
246

6✔
247
  return String.fromCodePoint(...output);
6✔
248
};
6✔
249

2✔
250
/**
2✔
251
 * Converts a string of Unicode symbols (e.g. a domain name label) to a
2✔
252
 * Punycode string of ASCII-only symbols.
2✔
253
 */
2✔
254
const encode = function(input: string): string {
2✔
NEW
255
  const output = [];
×
NEW
256

×
NEW
257
  // Convert the input in UCS-2 to an array of Unicode code points.
×
NEW
258
  const inputArr = ucs2decode(input);
×
NEW
259

×
NEW
260
  // Cache the length.
×
NEW
261
  const inputLength = inputArr.length;
×
NEW
262

×
NEW
263
  // Initialize the state.
×
NEW
264
  let n = initialN;
×
NEW
265
  let delta = 0;
×
NEW
266
  let bias = initialBias;
×
NEW
267

×
NEW
268
  // Handle the basic code points.
×
NEW
269
  for (const currentValue of inputArr) {
×
NEW
270
    if (currentValue < 0x80) {
×
NEW
271
      output.push(stringFromCharCode(currentValue));
×
NEW
272
    }
×
NEW
273
  }
×
NEW
274

×
NEW
275
  const basicLength = output.length;
×
NEW
276
  let handledCPCount = basicLength;
×
NEW
277

×
NEW
278
  // `handledCPCount` is the number of code points that have been handled;
×
NEW
279
  // `basicLength` is the number of basic code points.
×
NEW
280

×
NEW
281
  // Finish the basic string with a delimiter unless it's empty.
×
NEW
282
  if (basicLength) {
×
NEW
283
    output.push(delimiter);
×
NEW
284
  }
×
NEW
285

×
NEW
286
  // Main encoding loop:
×
NEW
287
  while (handledCPCount < inputLength) {
×
NEW
288
    // All non-basic code points < n have been handled already. Find the next larger one:
×
NEW
289
    let m = maxInt;
×
NEW
290
    for (const currentValue of inputArr) {
×
NEW
291
      if (currentValue >= n && currentValue < m) {
×
NEW
292
        m = currentValue;
×
NEW
293
      }
×
NEW
294
    }
×
NEW
295

×
NEW
296
    // Increase `delta` enough to advance the decoder's <n,i> state to <m,0>, but guard against overflow.
×
NEW
297
    const handledCPCountPlusOne = handledCPCount + 1;
×
NEW
298
    if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
×
NEW
299
      error('overflow');
×
NEW
300
    }
×
NEW
301

×
NEW
302
    delta += (m - n) * handledCPCountPlusOne;
×
NEW
303
    n = m;
×
NEW
304

×
NEW
305
    for (const currentValue of inputArr) {
×
NEW
306
      if (currentValue < n && ++delta > maxInt) {
×
NEW
307
        error('overflow');
×
NEW
308
      }
×
NEW
309
      if (currentValue === n) {
×
NEW
310
        // Represent delta as a generalized variable-length integer.
×
NEW
311
        let q = delta;
×
NEW
312
        for (let k = base; ; k += base) {
×
NEW
313
          let t: number;
×
NEW
314
          if (k <= bias) {
×
NEW
315
            t = tMin;
×
NEW
316
          } else if (k >= bias + tMax) {
×
NEW
317
            t = tMax;
×
NEW
318
          } else {
×
NEW
319
            t = k - bias;
×
NEW
320
          }
×
NEW
321
          if (q < t) {
×
NEW
322
            break;
×
NEW
323
          }
×
NEW
324
          const qMinusT = q - t;
×
NEW
325
          const baseMinusT = base - t;
×
NEW
326
          output.push(stringFromCharCode(digitToBasic(t + (qMinusT % baseMinusT), 0)));
×
NEW
327
          q = floor(qMinusT / baseMinusT);
×
NEW
328
        }
×
NEW
329

×
NEW
330
        output.push(stringFromCharCode(digitToBasic(q, 0)));
×
NEW
331
        bias = adapt(delta, handledCPCountPlusOne, handledCPCount === basicLength);
×
NEW
332
        delta = 0;
×
NEW
333
        ++handledCPCount;
×
NEW
334
      }
×
NEW
335
    }
×
NEW
336

×
NEW
337
    ++delta;
×
NEW
338
    ++n;
×
NEW
339
  }
×
NEW
340
  return output.join('');
×
NEW
341
};
×
342

2✔
343
/**
2✔
344
 * Converts a Punycode string representing a domain name or an email address
2✔
345
 * to Unicode. Only the Punycoded parts of the input will be converted, i.e.
2✔
346
 * it doesn't matter if you call it on a string that has already been
2✔
347
 * converted to Unicode.
2✔
348
 */
2✔
349
const toUnicode = function(input: string): string {
2✔
350
  return mapDomain(input, string => {
51✔
351
    return regexPunycode.test(string) ? decode(string.slice(4).toLowerCase()) : string;
102✔
352
  });
51✔
353
};
51✔
354

2✔
355
/**
2✔
356
 * Converts a Unicode string representing a domain name or an email address to
2✔
357
 * Punycode. Only the non-ASCII parts of the domain name will be converted,
2✔
358
 * i.e. it doesn't matter if you call it with a domain that's already in
2✔
359
 * ASCII.
2✔
360
 */
2✔
361
const toASCII = function(input: string): string {
2✔
NEW
362
  return mapDomain(input, string => {
×
NEW
363
    return regexNonASCII.test(string) ? 'xn--' + encode(string) : string;
×
NEW
364
  });
×
NEW
365
};
×
366

2✔
367
/* --------------------------------------------------------------------------*/
2✔
368

2✔
369
/** Define the public API */
2✔
370
const punycode: {
2✔
371
  version: string;
2✔
372
  ucs2: {
2✔
373
    decode: (string: string) => number[];
2✔
374
    encode: (codePoints: number[]) => string;
2✔
375
  };
2✔
376
  decode: (input: string) => string;
2✔
377
  encode: (input: string) => string;
2✔
378
  toASCII: (input: string) => string;
2✔
379
  toUnicode: (input: string) => string;
2✔
380
} = {
2✔
381
  version: '2.3.1',
2✔
382
  ucs2: {
2✔
383
    decode: ucs2decode,
2✔
384
    encode: ucs2encode
2✔
385
  },
2✔
386
  decode: decode,
2✔
387
  encode: encode,
2✔
388
  toASCII: toASCII,
2✔
389
  toUnicode: toUnicode
2✔
390
};
2✔
391

2✔
392
export { punycode };
2✔
393
// For CommonJS compatibility
2✔
394
if (typeof module !== 'undefined' && typeof module.exports === 'object' && module.exports !== null) {
2!
395
  module.exports.punycode = punycode;
1✔
396
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc